Skip to main content

Revisión Contenedores

Vamos solo a recordar conceptos importantes de contenedores.

  • Conceptos

  • Container e Images

  • Image Advanced and Dockerfile A grandes rasgos...

  • Dockerfile es el archivo que describe una imagen.

  • Una imagen es una serie de capas siendo que solamente la última es escrita el resto es read only.

  • Un contenedor es una imagen siendo ejecutada.

  • Una imagen puede ser guardada en un repositorio con el push y el pull hace la descarga de esta imagen.

alt text

Un contenedor se diferencia de una VM pues hace llamadas de syscall directamente para el kernel de linux.

Contenedor es:

  • Una colección de una o múltiples aplicaciones que son agrupadas e incluyen todas sus dependencias para que puedan ejecutar.
  • Es un proceso que se ejecuta en el kernel Linux con algunas restricciones para que sean encapsulados y aislados del sistema base de host.

Así funciona una llamada de sistema (syscall) de una aplicación, sea ella utilizando contenedor o directo en el sistema del host.

alt text

En el caso de una máquina virtual, no existe la interacción con el sistema host como en el caso de los contenedores.

Si tenemos varios contenedores ejecutando en un host tenemos el siguiente escenario.

alt text

Si todos estos contenedores hacen llamada de sistema para el mismo kernel entonces necesitan aislamiento, y la forma con que Linux hace eso es a través de namespaces. Si no estuvieran aislados podrían explotar problemas de seguridad de kernel en el host.

El kernel Linux divide los contenedores por namespaces y los gestiona separadamente restringiendo lo que un proceso puede o no ver en nivel de proceso, usuarios y filesystem. Cgroups restringe el uso de recurso del proceso (RAM, Disk, CPU).

  • PID
    • Aísla los procesos de cada contenedor de los otros procesos.
    • Un process ID puede existir múltiples veces, una vez en cada namespace.
    • Procesos de namespace no consiguen ver otros procesos de otros namespaces.
  • Mount.
    • En un namespace podemos restringir acceso a puntos de montaje o al filesystem del root
  • Network.
    • Accede apenas a determinados dispositivos de red.
    • Reglas de firewall, sockets y puertos independientes.
    • No consigue ver todo el tráfico o alcanzar todos los endpoints.
  • User
    • Diferentes conjunto de user ids usados.
    • User 0 dentro de un namespace puede ser diferente del mismo user 0 en otro namespace.
    • El user 0 dentro del host (root) no es el mismo root dentro del contenedor.

Containers Runtime

Tenemos diferentes container runtime, pero vamos a recordar la diferencia.

  • Docker: Container Runtime + herramienta de gestión de contenedores e imágenes.
  • Containerd: Container Runtime sin herramientas de gestión.
  • crictl: CLI genérica e interactiva compatible con todos los containers runtimes que implementan los estándares CRI. Puede ejecutar con Docker, con containerd y otros compatibles.
  • Podman: Herramienta para gestionar contenedor e imágenes. Cuando instalamos podman instala runc como container runtime, así como Buildah.

Solo para ilustrar vamos a crear un Dockerfile simple.

Dockerfile

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

Vamos a compilar una imagen y ejecutar usando Docker.

root@cks-master:~# vim Dockerfile
root@cks-master:~# docker build -t ping . # ping es el nombre de la imagen y el . es el directorio que contiene el 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

# Las imágenes que tenemos abajo disponibles son la bash que fue la base para el 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 ellas tienen el mismo tamaño, pues no fue incluido nada en la imagen solamente un comando y eso no genera una capa

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 hacer la misma cosa con podman pero quiero mostrar una curiosidad antes.

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

Esto sucede porque Docker y Podman gestionan sus imágenes en storage diferentes. Cuando creamos una imagen con Docker, ella es almacenada en el directorio de storage específico de Docker, mientras que Podman utiliza su propio directorio para almacenar las imágenes.

Aunque las imágenes sean compatibles entre Docker y Podman, no son automáticamente visibles en ambos gestores debido a esa separación en el almacenamiento.

Si quisiéramos utilizar la misma imagen sin tener que compilar una nueva con podman podríamos exportar por docker e importar por 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 la imagen de bash no está aquí pues ella solo fue descargada para compilar. Si hubiéramos hecho el build usando podman estaría aquí también.
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

Los comandos podman y docker son prácticamente los mismos. Solo cambiar docker por podman y generalmente todo funciona. Ya vamos a incluir el mismo comando con crictl para mostrar una cosa.

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

# Prácticamente la misma salida
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

Podman no encontró contenedores ni docker, pero crictl sí.

Esto sucede porque podman está usando como container runtime el crun, docker el runc y crictl el 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 el aislamiento de namespaces podemos ejecutar dos contenedores usando la misma imagen y verificar que poseen gestores de procesos diferentes teniendo el mismo PID para los procesos. Esto no sería posible si estuviera ejecutando en el mismo host.


# Creando contenedor c1 y c2 con 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

# Ejecutando un ps dentro del 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

# Ejecutando un ps dentro del 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

# Ejecutando dentro del host podemos ver que ellos tienen
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

Ahora vamos a hacer una segunda prueba que será eliminar el contenedor 2 e iniciar él en el mismo namespace del contenedor1.

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 los procesos en ambos contenedores
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

# Elimine los contenedores después de la prueba
docker rm c1 c2 --force

Cuando usamos la opción --pid=container:<container> al ejecutar un comando docker run, estamos configurando el nuevo contenedor para compartir el PID namespace (gestión de procesos) con otro contenedor. Esto significa que ambos contenedores poseen acceso a los mismos procesos, como si estuvieran ejecutando en el mismo namespace de PID. Sin embargo, esto no afecta otros namespaces como los de red, mount, o IPC (Inter-Process Communication).