Pular para o conteúdo principal

Review Containers

Vamos só relembrar conceitos importantes de containers.

  • Conceitos

  • Container e Images

  • Image Advanced and Dockerfile A grosso modo...

  • Dockerfile é o arquivo que descreve uma imagem.

  • Uma imagem é uma série de camadas sendo que somente a última é escrita o restante é read only.

  • Um container é uma imagem sendo executada.

  • Uma imagem pode ser guardada em um repositório com o push e o pull faz o download desta imagem.

alt text

Um container se diferencia de uma VM pois ele faz chamadas de syscall diretamente para o kernel do linux.

Container é:

  • Uma coleção de uma ou múltiplas aplicações que são agrupadas e incluem todas suas dependências para que possam executar.
  • É um processo que roda no kernel Linux com algumas restrições para que sejam encapsulados e isolados do sistema base de host.

Assim funciona uma chamada de sistema (syscall) de uma aplicação, seja ela utilizando container ou direto no sistema do host.

alt text

No caso de uma máquina virtual, não existe a interação com o sistema host como no caso dos containers.

Se temos vários containers rodando em um host temos o seguinte cenário.

alt text

Se todos esse containers fazem chamada de sistema para o mesmo kernel então precisam de isolamento, e forma com que o Linux faz isso é através de namespaces. Se não tivessem isolados poderiam explorar problemas de segurança de kernel no host.

O kernel Linux divide os containers por namespaces e os gerencia separadamente restringindo o que um processo pode ou não ver em nível de processo, usuários e filesystem. Cgroups restringe o uso de recurso do processo (RAM, Disk, CPU).

  • PID
    • Isola os processos de cada container dos outros processos.
    • Um process ID pode existir múltiplas vezes, uma vez em cada namespace.
    • Processos de namespace não conseguem ver outros processos de outros namespaces.
  • Mount.
    • Em um namespace podemos restringir acesso a pontos de montagem ou ao filesystem do root
  • Network.
    • Acessa apenas determinados dispositivos de rede.
    • Regras de firewall, sockets e portas independentes.
    • Não consegue ver todo o tráfego ou alcançar todos os endpoints.
  • User
    • Diferentes conjunto de user ids usados.
    • User 0 dentro de um namespace pode ser diferente do mesmo user 0 em outro namespace.
    • O user 0 dentro do host (root) não é o mesmo root dentro do container.

Containers Runtime

Temos diferentes container runtime, mas vamos relembrar a diferença.

  • Docker: Container Runtime + tool de gerenciamento de containers e imagens.
  • Containerd: Container Runtime sem ferramentas de gerenciamento.
  • crictl: CLI genérica e interativa compatível com todos os containers runtimes que implementam os padrões CRI. Pode rodar com o Docker, com o containerd e outros compatíveis.
  • Podman: Ferramenta para gerenciar container e imagens. Quando instalamos o podman ele instala o runc como container runtime, assim como o Buildah.

Só para ilustrar vamos criar um Dockerfile simples.

Dockerfile

FROM bash
CMD ["ping", "devsecops.puziol.com.br"]

Vamos buildar uma imagem e rodar usando o Docker.

root@cks-master:~# vim Dockerfile
root@cks-master:~# docker build -t ping . # ping é o nome da imagem e o . é o diretório que contem o dockerfile
DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
Install the buildx component to build images with BuildKit:
https://docs.docker.com/go/buildx/

Sending build context to Docker daemon 2.693MB
Step 1/2 : FROM bash
latest: Pulling from library/bash
c6a83fedfae6: Pull complete
70acf8f93de9: Pull complete
7621ec80326e: Pull complete
Digest: sha256:05de6634ac35e4ac2edcb1af21889cec8afcc3798b11a9d538a6f0c315608c48
Status: Downloaded newer image for bash:latest
---> bd4206c5bc03
Step 2/2 : CMD ["ping", "devsecops.puziol.com.br"]
---> Running in 9bb4eae80da0
Removing intermediate container 9bb4eae80da0
---> 80a23eb00c36
Successfully built 80a23eb00c36
Successfully tagged ping:latest

# As imagens que temos abaixo disponíveis são a bash que foi a base para o ping
root@cks-master:~# docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ping latest 80a23eb00c36 10 seconds ago 14.4MB
bash latest bd4206c5bc03 2 weeks ago 14.4MB

# Observe que elas tem o mesmo tamanho, pois não foi incluído nada na imagem somente um comando e isso não gera uma camada

root@cks-master:~# docker run ping
PING devsecops.puziol.com.br (172.67.129.115): 56 data bytes
64 bytes from 172.67.129.115: seq=0 ttl=60 time=13.352 ms
64 bytes from 172.67.129.115: seq=1 ttl=60 time=11.807 ms
64 bytes from 172.67.129.115: seq=2 ttl=60 time=11.821 ms
64 bytes from 172.67.129.115: seq=3 ttl=60 time=11.761 ms
64 bytes from 172.67.129.115: seq=4 ttl=60 time=11.839 ms

Podemos fazer a mesma coisa com o podman mas quero mostrar uma curiosidade antes.

root@cks-master:~# podman image ls
REPOSITORY TAG IMAGE ID CREATED SIZE

Isso acontece porque Docker e Podman gerenciam suas imagens em storage diferentes. Quando criamos uma imagem com Docker, ela é armazenada no diretório de storage específico do Docker, enquanto o Podman utiliza seu próprio diretório para armazenar as imagens.

Mesmo que as imagens sejam compatíveis entre Docker e Podman, elas não são automaticamente visíveis em ambos os gerenciadores devido essa separação no armazenamento.

Se quisesse utilizar a mesma imagem sem ter que buildar uma nova com o podman poderíamos exportar pelo docker e importar pelo podman.

root@cks-master:~# docker save -o ping.tar ping
root@cks-master:~# ls
Dockerfile common.sh initclustr.sh ping.tar snap

root@cks-master:~# podman load -i ping.tar
Getting image source signatures
Copying blob 8005df329219 done
Copying blob 78561cef0761 done
Copying blob 09db3fa8d4c8 done
Copying config 80a23eb00c done
Writing manifest to image destination
Storing signatures
Loaded image(s): localhost/ping:latest
root@cks-master:~# podman image ls

# Observe que a imagem do bash não esta aqui pois ela só foi baixada para buildar. Se tivessemos feito o build usando o podman estaria aqui também.
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/ping latest 80a23eb00c36 9 minutes ago 14.9 MB

root@cks-master:~# podman run ping
PING devsecops.puziol.com.br (104.21.1.153): 56 data bytes
64 bytes from 104.21.1.153: seq=0 ttl=42 time=12.717 ms
64 bytes from 104.21.1.153: seq=1 ttl=42 time=11.135 ms
64 bytes from 104.21.1.153: seq=2 ttl=42 time=11.133 ms
64 bytes from 104.21.1.153: seq=3 ttl=42 time=11.307 ms
64 bytes from 104.21.1.153: seq=4 ttl=42 time=11.104 ms

Os comando podman e docker são praticamente o mesmo. Só trocar docker por podman e geralmente tudo funciona. Já vamos incluir o mesmo comando com o crictl para mostrar uma coisa.

root@cks-master:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

# Praticamente a mesma saída
root@cks-master:~# podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

root@cks-master:~# crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD
5ce963ae949f7 f9c3c1813269c 17 minutes ago Running calico-kube-controllers 1 bfe586b33ea68 calico-kube-controllers-75bdb5b75d-d2tl9
696b9c6f4a078 cbb01a7bd410d 17 minutes ago Running coredns 1 7f43504bc7b48 coredns-7db6d8ff4d-kdb4t
4811a65d42dd3 cbb01a7bd410d 17 minutes ago Running coredns 1 35875876bef79 coredns-7db6d8ff4d-cmcff
b675aa0276e5f e6ea68648f0cd 18 minutes ago Running kube-flannel 1 abc1904b21596 canal-8nn2f
734effffec2f5 75392e3500e36 18 minutes ago Running calico-node 1 abc1904b21596 canal-8nn2f
971577d43a681 55bb025d2cfa5 18 minutes ago Running kube-proxy 1 d0f7a3832ae63 kube-proxy-c2qx6
2cfff390a9c72 3edc18e7b7672 18 minutes ago Running kube-scheduler 1 4a28794d2940a kube-scheduler-cks-master
8b6c080830c3c 76932a3b37d7e 18 minutes ago Running kube-controller-manager 1 a01c11a1a974b kube-controller-manager-cks-master
667821ced3ab2 1f6d574d502f3 18 minutes ago Running kube-apiserver 1 d9132c59e74ef kube-apiserver-cks-master
9870d8a0847ee 3861cfcd7c04c 18 minutes ago Running etcd 1 3598be3a95b48 etcd-cks-master

O podman não encontrou containers nem o docker, mas o crictl sim.

Isso acontece porque o podman esta usando como container runtime o crun o docker o runc e o crictl o containerd.

root@cks-master:~# docker info | grep Runtime
Runtimes: io.containerd.runc.v2 runc
Default Runtime: runc #<<<<<

root@cks-master:~# podman info | grep ociRuntime -A 5
ociRuntime:
name: crun
package: 'crun: /usr/bin/crun'
path: /usr/bin/crun
version: |-
crun version UNKNOWN

root@cks-master:~# cat /etc/crictl.yaml
runtime-endpoint: unix:///run/containerd/containerd.sock

Para confirmar o isolamento de namespaces podemos executar dois containers usando a mesma imagem e conferir que eles possuem gerenciadores de processos diferentes tendo o mesmo PID para os processos. Isso não seria possível se estivesse rodando no mesmo host.


# Criando container c1 e c2 com comandos diferentes
root@cks-master:~# docker run --name c1 -d ubuntu sh -c 'sleep 1d'
1d4f888a9c7c123d4fbf37156f0843066ae10579c95620debe45b5742632125b
root@cks-master:~# docker run --name c2 -d ubuntu sh -c 'sleep 10d'
43f842e87efe4059c8bbab6c3487cb3d95917c14e7c5fd5a3193820489da34d2

#Executando um ps dentro do c1
root@cks-master:~# docker exec c1 ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.1 0.0 2800 1176 ? Ss 14:26 0:00 sh -c sleep 1d
root 7 0.0 0.0 2696 1064 ? S 14:26 0:00 sleep 1d
root 8 50.0 0.1 7888 4036 ? Rs 14:27 0:00 ps aux

#Executando um ps dentro do c2
root@cks-master:~# docker exec c2 ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.1 0.0 2800 1052 ? Ss 14:27 0:00 sh -c sleep 10d
root 7 0.0 0.0 2696 1096 ? S 14:27 0:00 sleep 10d
root 8 60.0 0.0 7888 3992 ? Rs 14:27 0:00 ps aux

#EXecutando dentro do host podemos ver que eles tem
root@cks-master:~# ps aux | grep sleep
root 20380 0.0 0.0 2800 1176 ? Ss 14:26 0:00 sh -c sleep 1d
root 20405 0.0 0.0 2696 1064 ? S 14:26 0:00 sleep 1d
root 20555 0.0 0.0 2800 1052 ? Ss 14:27 0:00 sh -c sleep 10d
root 20578 0.0 0.0 2696 1096 ? S 14:27 0:00 sleep 10d
root 28565 0.0 0.0 8168 656 pts/0 S+ 14:43 0:00 grep --color=auto sleep

Agora vamos fazer um segundo teste que será remover o container 2 e iniciar ele no mesmo namespace do container1.

root@cks-master:~# docker rm c2 --force
c2

root@cks-master:~# docker run --name c2 --pid=container:c1 -d ubuntu sh -c 'sleep 10d'
ad5ff9d45ed9f9e44a2aee586fa47dab3184bb7daf1d0949b250968bff4afe8d

# Podemos ver os processos em ambos os containers
root@cks-master:~# docker exec c2 ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 2800 1176 ? Ss 14:26 0:00 sh -c sleep 1d
root 7 0.0 0.0 2696 1064 ? S 14:26 0:00 sleep 1d
root 14 0.1 0.0 2800 1064 ? Ss 14:45 0:00 sh -c sleep 10d
root 20 0.0 0.0 2696 1088 ? S 14:45 0:00 sleep 10d
root 21 50.0 0.1 7888 4036 ? Rs 14:45 0:00 ps aux

root@cks-master:~# docker exec c1 ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 2800 1176 ? Ss 14:26 0:00 sh -c sleep 1d
root 7 0.0 0.0 2696 1064 ? S 14:26 0:00 sleep 1d
root 14 0.0 0.0 2800 1064 ? Ss 14:45 0:00 sh -c sleep 10d
root 20 0.0 0.0 2696 1088 ? S 14:45 0:00 sleep 10d
root 28 75.0 0.1 7888 4028 ? Rs 14:46 0:00 ps aux

# Remova os containers depois do teste
docker rm c1 c2 --force

Quando usamos a opção --pid=container:<container> ao executar um comando docker run, estamos configurando o novo container para compartilhar o PID namespace (gerenciamento de processos) com outro container. Isso significa que ambos os containers possuem acesso aos mesmos processos, como se estivessem rodando no mesmo namespace de PID. No entanto, isso não afeta outros namespaces como os de rede, mount, ou IPC (Inter-Process Communication).