Entendendo contêineres… escrevendo contêineres - Parte 2
Vou continuar de onde paramos, então se perdeu algo por favor, de uma conferida no primeiro artigo! Como tinhamos parado nas flags para copiar certas coisas, vamos continuar!
… Podemos usar algumas flags de cópia!
Pra isso, vamos usar o poder do pacote de syscall
‘s para isolar o processo, e copiar o UTS- Unix Time Sharing, que basicamente falando, vai copiar funcionalidades do kernel para dentro do nosso contêiner. E também queremos que ele gere processos fora dos processos do meu kernel, portanto, clonamos também um novo PID mãe, pra que processos de dentro do contêiner tenham identificadores diferentes do da minha máquina.
Adicionamos apenas uma linha aqui, e conseguimos isso:
Agora nossa função run
fica assim, para que clonemos não só o UTS mas também um novo PID.
Rodando novamente o contêiner assim, temos o seguinte resultado para o procedimento de troca de hostname anterior:
Porém, ainda sim, não conseguimos isolar o PID do contêiner pra que ele crie os próprios processos.
Como faremos isso?
Hahaha, só brincando, o problema é que mesmo gerando um novo PID, não iniciamos um processo com ele, e sim só geramos ele e ele está ali. A Docker Engine aplica isso com o conceito de namespaces que são exatamente, novos níveis de permissão para esses processos.
Mas o que isso significa na prática?
Imagine a seguinte situação, você não sabe o que um programa enorme em C++ vai fazer, quando você roda, ele exige permissão de root . A primeira coisa que alguém desesperado só por estar rodando esse programa pensaria seria, “sudo programa-grandão.cpp”, porém, o que exatamente esse programa vai fazer no seu OS? O que ele realmente vai usar de recursos disponíveis no seu kernel? A resposta é, não temos como saber exatamente!
A não ser que você depure o programa inteiro e/ou confira na mão o que ele está fazendo, ele pode se conectar com diversos serviços na web e até mesmo do seu sistema operacional e você nunca vai saber o que de verdade ele está fazendo, porém, você ainda consegue saber o que o seu kernel está recebendo de instruções, através dos syscall
‘s! Eles são pequenos comandos que o sistema operacional recebe de um programa, como create
, connect,
ou até mesmo sys_getpid.
Então, para proteger qualquer sistema hospedeiro, a Docker Engine, através de namespaces, protegem programas de chamarem certos syscall
‘s!
Então a definição na prática dos namespaces da Docker Engine são “listas” de permissões de syscall
‘s que um programa pode ou não chamar. O melhor exemplo é que um processo rodando um programa dentro de um contêiner não pode pedir pro kernel matar um processo do root. Ou seja, no código, temos que o syscall sys_exit
não pode ser chamado por nenhum programa dentro de um contêiner que “mire” em um processo externo a ele. Assim, com essa lista de syscall’s VIP pros contêineres, a Docker Engine isola os processos do contêiner de uma maneira que independe do programa, realmente colocando ele em um espaço delimitado de permissões, que eles nomearam de namespaces.
😜 Pausa para o chá e dar uma mudada no meu ZSH hehehehe….
Quando fazemos programas para web, como web services ou APIs REST, dificilmente nos depararemos num caso que precisamos matar um processo ou algo assim, portanto realmente passamos longe dessa restrição do contêiner, porém, existem mais uma série de syscall
‘s que a Docker Engine protege que nem sabemos que poderiam ser usados “agressivamente” no sistema hospedeiro, pois eles usaram especialistas em seccomp (Security Computing) para nortear essas restrições.
Porém, como então fazemos isso no nosso contêiner apenas?
Muito simples, vamos modificar um pouco o código da função run
para que ela na verdade crie esse novo PID e então rode o comando que queremos, vou fazer isso colocando na frente dos argumentos passados pro nosso programa a palavra “child”, que vai rodar uma outra função bem parecida com a run
porém já com os novos processos. (Atenção! -> Talvez quando fizer isso, em vez de apenas go run main.go run <args>
você tenha que adicionar a permissão de root do seu sistema, pois ele usa alguns recursos direto do kernel, e como não temos uma engine intermediaria como os contêineres Docker, precisamos disso 😅).
Também modificamos a função main
pra que como todo bom e velho CLI ele aceite esse novo comando, para que rodemos o processo dentro desse namespace criado. Usei a função embutida append
para concatenar a palavra “child” na frente de todo o restante do slice de argumentos, e ainda sim utilizar o slice resultante para o comando a ser executado.
Assim, já utilizando o namespace que criamos, podemos rodar uma bash novamente dentro do contêiner e teremos o seguinte resultado:
EUREKA!
Agora sim! Temos que a bash dentro do contêiner isolou seu próprio processo, se tornando PID 1! Porém, se dermos um PS dentro do contêiner:
Opa, pera ai, não temos novos processos! O que houve?
Calma que é tudo normal, porque o comando PS na verdade basicamente da um ls na pasta /proc do seu Linux,
Significa que mesmo dentro do contêiner, ele continua enxergando o meu sistema de arquivos! Ou seja, para que ele consiga identificar os próprios processos, ele precisa de um sistema de arquivos diferente do meu, que é do sistema hospedeiro. Por que? Você deve se perguntar…
E então deixemos nosso aprendizado aqui por hoje, pra podermos relaxar e trabalhar! Te vejo no proximo artigo, e já terminamos 100% do nosso contêiner! :D