Docker es un sistema de contenedores el cual nos permite aislar aplicaciones, este último detalle es muy importante ya que lo distingue de otros sistemas de contenedores como podría ser OpenVZ, el concepto clave en Docker es que se lanzan aplicaciones y no contenedores.
El uso de contenedores supone una gran ventaja respeto a la virtualización completa como podría ser KVM, mediante contenedores obtendremos una densidad de máquinas mayor en cada nodo de computo pero por otra parte estaremos limitados a ejecutar SOs basados en Linux ya que compartirán la misma imagen de kernel.
Otro aspecto interesante y que diferencia Docker de otros sistemas basados en contenedores es que si es apropiado las librerias básicas del SO son compartidas también, suponiendo un ahorro de espacio significativo:
El concepto clave en Docker es que se lanzan aplicaciones y no contenedores, es decir se arranca un contenedor en el que se lanza la app, en un sistema de contenedores tradicional se lanza el contenedor y dentro de este la aplicación.
El tener un contenedor arrancado solo es la consecuencia de lanzar la app, cuando esta termina su ejecución, el contenedor ya NO es necesario y se apaga de forma automatica.
Docker se apoya en varias tecnologias existentes:
- NameSpaces: Aislamiento entre contenedores
- Cgroups: Control de recursos
- UnionFS: Los cambios se aplican en forma de capas, es decir, parte de una imagen base en RO y va añadiendo cambios, cuando se commitea una imagen esta pasa a ser la capa base en RO mode.
- El sistema de ficheros UnionFS puede ser: AUFS, btrfs, vfs, and DeviceMapper
Este artículo se compone de los siguientes apartados:
INSTALACIÓN
Lo primero será compilar el kernel de nuestro nodo de computo con soporte para CGROUPS, si nos dejasemos alguna opción por habilitar al compilar el ebuild de Docker nos aparecería un warning indicándonos la opción a habilitar dentro del kernel.
Compilamos Docker:
app-emulation/docker -aufs btrfs contrib device-mapper doc lxc overlay vim-syntax zsh-completion
Arrancamos el servicio y lo metemos en el arranque:
rc-update add docker default
NOTA: Si queremos que los contenedores tengan acceso al exterior sin natear debemos habilitar el forwarding:
net.ipv4.ip_forward = 1
net.ipv4.conf.all.forwarding = 1
USO BÁSICO
Docker tiene un sistema de imágenes remoto llamado dockerhub, este tiene tanto imágenes certificadas como de terceros, para hacer algunos test iniciales con Docker puede resultarnos util recurrir a ellos pero como mas adelante se explicará nosotros vamos a utilizar nuestra propia imagen.
Corremos bash dentro del contenedor, -i: STDIN input, -t: pseudo-terminal
exit
Vemos que el contenedor se ha detenido en cuanto se ha salido de la shell, lo cual tiene sentido ya que Docker corre aplicaciones NO contenedores.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Dejamos una tarea en marcha dentro del contenedor:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8b68c1646f22 jgkim/gentoo-stage3 "/bin/sh -c 'while t 3 seconds ago Up 1 seconds trusting_lovelace
Podemos ver la salida estandar de nuestro container:
hello world
hello world
hello world
hello world
Tembién podemos verlos al estilo tail -f:
Paramos el container:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Podemos entrar en el container y NO cerrar la sesión, estilo screen:
Crtl+PQ
Para volver a entrar:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
25d21650646e jgkim/gentoo-stage3 "/bin/bash" 14 seconds ago Up 12 seconds mad_pare
NOTA: Si se attachean dos sesiones el comportamiento es muy similar a una sesión de screen ;)
Esto esta muy bien pero NO podemos fiarnos de una image de Gentoo que no hayamos instalado nosotros mismos así que nos bajamos el stage3 e importamos en docker:
docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
gentoo latest 264d2b032543 6 minutes ago 773.1 MB
Actualizamos el sistema operativo e instalamos las herramientas básicas:
emerge --sync
emerge eix htop vim
eix-sync
emerge -uDav world
emerge --depclean
python-updater
exit
COMMITS
Los cambios en un container de docker son muy parecidos a los cambios en un repo de git, cuando salgamos debemos commitear los cambios o los perderemos:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9a5a0b8c7f13 gentoo "/bin/bash" 6 minutes ago Exited (0) 2 seconds ago grave_colden
Commiteamos los cambios para que a partir de ahora nos sirva como imagen base:
docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
gentoo/updated latest 4eedf047deb9 47 seconds ago 1.175 GB
gentoo latest 264d2b032543 14 minutes ago 773.1 MB
NOTA: Para evitar la corrupción de ficheros Docker pausa el contenedor antes de hacer el commit, si queremos evitarlo utilizaremos el parametro -p false:
En este momento podriamos empaquetar nuestro container y distribuirlo si así lo deseamos:
Para importar:
docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
gentoo_imported latest b1c4c04c1d2b About a minute ago 1.345 GB
gentoo/updated latest 4eedf047deb9 5 minutes ago 1.175 GB
gentoo latest 264d2b032543 19 minutes ago 773.1 MB
NOTA: Hay una diferencia entre save y export, con save se puede hacer rollback a cualquier estado anterior del container mientras que con export solo se tiene el ultimo estado.
Empaquetar contenedores rara vez se hace ya que lo ideal es tener un servidor registry propio donde almacenar las imágenes.
Hagamos una prueba de rollback:
rm -rf /etc/*
Ups, la hemos hecho buena…
No problem, como no hemos commiteado tan solo salimos perdiendo los cambios:
docker run -t -i gentoo/updated /bin/bash
ls -la /etc/passwd
-rw-r--r-- 1 root root 651 Jul 23 04:40 /etc/passwd
Puede que cometamos algún error y ni tan siquiera lo sepamos, en tal caso la única solución es revertir a un commit en el que sepamos que todo funciona correctamente:
rm -rf /etc/*
exit
docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2500d9c520ea gentoo/updated "/bin/bash" About a minute ago Exited (0) 3 seconds ago ecstatic_colden
docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
gentoo/updated_sinpasswd latest c3b9f653bf61 5 seconds ago 1.175 GB
gentoo_imported latest b1c4c04c1d2b 5 minutes ago 1.345 GB
gentoo/updated latest 4eedf047deb9 9 minutes ago 1.175 GB
gentoo latest 264d2b032543 23 minutes ago 773.1 MB
ls -la /etc/passwd
-rw-r--r-- 1 root root 651 Jul 23 04:40 /etc/passwd
Ahora imaginemos que hemos realizado varias configuraciones realmente laboriosas además del error, no vamos a deshechar todo nuestro trabajo porque se nos haya ido el dedo, los pasos a seguir serían:
- Commitemos los cambios
- Entramos en un commit anterior(puede ser mas comodo utilizando volúmenes)
- Copiamos los ficheros borrados
- Salimos
- Entramos en el ultimo commit
- Restauramos los ficheros borrados
- Salimos
- Commiteamos
Un comando bastante útil es docker diff que nos mostrará los cambios entre la última entrada en el contenedor y el último commit:
rm /etc/passwd
exit
La última entrada:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f269a364a153 gentoo/updated "/bin/bash" 22 seconds ago Exited (0) 9 seconds ago evil_hawking
Comparamos el estado de la última entrada(22 seconds ago) con el estado del último commit de la imagen: gentoo/updated
C /root
C /root/.bash_history
C /etc
D /etc/passwd
Podemos observar que se han creado varios ficheros del history de bash y se ha eliminado /etc/passwd
NETWORKING
Docker utiliza iptables para redirigir tráfico del equipo anfitrión al contenedor, para hacer una prueba instalamos Apache:
emerge apache
cd /etc/apache2/vhosts.d/
rm *
vi 00_kr0m.conf
Listen 80
NameVirtualHost *:80
<VirtualHost *:80>
ServerAdmin kr0m@alfaexploit.com
DocumentRoot /var/www/html
ServerName *
ErrorLog /var/log/apache2/docker_http.error_log
CustomLog /var/log/apache2/docker_http.access_log combined
DirectoryIndex index.php index.htm index.html
<Directory "/var/www/html">
Options -Indexes FollowSymLinks
AllowOverride All
Order allow,deny
Allow from all
</Directory>
</VirtualHost
vi /var/www/html/index.html
<html><body><h1>DockerWeb by kr0m</h1></body></html>
ServerName localhost
Commiteamos los cambios:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1049a59f7816 gentoo/updated "/bin/bash" 3 hours ago Exited (0) 5 seconds ago trusting_perlman
Arrancamos el contenedor pero esta vez indicando que el puerto WAN:7777 se redirigirá al LAN:80
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
18d6cd9c9521 gentoo/apache "/usr/sbin/apache2 - 7 seconds ago Up 4 seconds 0.0.0.0:7777->80/tcp dreamy_lalande
Comprobamos que efectivamente la redirección funciona correctamente:
<html><body><h1>DockerWeb by kr0m</h1></body></html>
NOTA: En algunos contenedores preinstalados con Apache vemos que se lanza un script directamente:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
169027bdabbe fedora/apache "/run-apache.sh" 17 seconds ago Up 15 seconds 80/tcp fedoreando
cat run-apache.sh
#!/bin/bash
rm -rf /run/httpd/* /tmp/httpd*
exec /usr/sbin/httpd -D FOREGROUND
Podemos ver la redirección del puerto 7777 a la ip del contendor mediante iptables:
Chain DOCKER (2 references)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:7777 to:172.17.0.18:80
La red interna creada por Docker se puede ver mediante ifconfig:
eth0: flags=67<UP,BROADCAST,RUNNING> mtu 1500
inet 172.17.0.7 netmask 255.255.0.0 broadcast 0.0.0.0
ether ae:76:9a:64:c5:05 txqueuelen 0 (Ethernet)
veth1e84f17: flags=67<UP,BROADCAST,RUNNING> mtu 1500
inet6 fe80::ac76:9aff:fe64:c505 prefixlen 64 scopeid 0x20<link>
ether ae:76:9a:64:c5:05 txqueuelen 1000 (Ethernet)
Podemos ver el bridge creado, todos los contenedores que esten en el mismo bridge serán accesibles entre ellos:
bridge name bridge id STP enabled interfaces
docker0 8000.ae769a64c505 no veth1e84f17
NOTA: Como podemos ver el bridge no añade la interfaz eth0 si no veth1e84f17 cuya MAC es idéntica a la de eth0
Docker puede utilizar otro modo de interconexión entre contenedores llamado “linking system”, este se basa en el nombre de los containers para establecer los flujos de datos.
Este sistema tiene una gran ventaja y es que NO se expone ningún puerto al exterior, los datos pasan a traves del tunel creado por docker.
La conectividad se realiza mediante:
- Variables de entorno
- Modificando el /etc/hosts
Paramos y borramos el contenedor anterior:
docker rm 18d6cd9c9521
Creamos el contenedor de base de datos:
emerge dev-db/mysql
emerge --config =dev-db/mysql-5.6.24
vi /etc/mysql/my.cnf
bind-address = 0.0.0.0
mysql -uroot -p’XXXX'
use mysql
grant all privileges on *.* to root@'172.17.0.%' identified by 'XXXX';
flush privileges;
exit
docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5be78a416521 gentoo/updated "/bin/bash" 13 minutes ago Exited (0) 2 seconds ago high_newton
Commiteamos los cambios:
Arrancamos el contenedor indicando el nombre MySQL:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
aae0431579ec gentoo/mysql "/usr/bin/mysqld_saf 6 seconds ago Up 4 seconds MySQL
Permitimos el tráfico entre contenedores:
Arrancamos el contenedor Apache linkandolo con MySQL:
cat /etc/hosts
172.17.0.18 mydb aae0431579ec MySQL
PING mydb (172.17.0.18) 56(84) bytes of data.
64 bytes from mydb (172.17.0.18): icmp_seq=1 ttl=64 time=0.078 ms
64 bytes from mydb (172.17.0.18): icmp_seq=2 ttl=64 time=0.026 ms
NOTA: Las opciones de linkado son --link NOMBRE_CONTENEDOR:ALIAS --link MySQL:mydb
De este modo dentro del contenedor podremos referirnos a la base de datos como mydb
Instalamos el cliente de mysql para hacer una conexión de prueba:
dev-db/mysql -bindist -cluster -community -debug -embedded -extraengine -jemalloc -latin1 -max-idx-128 minimal -perl -profiling -selinux -ssl -static -static-libs -systemtap -tcmalloc -test
virtual/mysql -embedded minimal -static -static-libs
mysql -h mydb -uroot -p’XXXX'
Welcome to the MySQL monitor. Commands end with ; or g.
Your MySQL connection id is 1
Server version: 5.6.24-log Source distribution
NOTA: Si un contenedor es reiniciado se le asignará una nueva ip, en tal caso se actualizará de forma automática el fichero hosts en todos los contenedores enlazados con este.
VOLÚMENES
Los volúmenes son datos que pueden ser compartidos y reutilizados entre contenedores, un ejemplo básico podría ser el siguiente:
echo alfaexploit.com > /webapp/asd
docker run -t -i --name Apache -v /webapp:/webapp gentoo/apache /bin/bash
cat /webapp/asd
alfaexploit.com
NOTA: De este modo varios servers pueden acceder a la misma información en real-time
Un buen truco es meter todos los logs en un mismo directorio con subdirectorios por contenedor:
-v /var/log/container00:/var/log
También permite montarlo como RO:
-v /src/webapp:/opt/webapp:ro
O incluso montar ficheros aislados:
-v ~/.bash_history:/.bash_history
Cabe la posibilidad de copiar ficheros de un contenedor sin tener que entrar dentro de el:
cat run-apache.sh
Los volúmenes también pueden ser interesantes si queremos evitar el uso de tecnologias como NFS.
LIMPIEZA
Eliminar todos los contenedores de los que se haya salido:
Eliminar los antiguos:
SEGURIDAD
Algunas recomendaciones básicas de seguridad son:
- Configurar las capabilities mínimas: Se permite el acceso a ciertos recursos del sistema
- GRsec: Parches de seguridad a nivel de kernel
- Auditd: Auditoria de syscalls
- Docker-KVM: Solo se ejecuta el código guest una vez, todos los contenedores se ejecutan dentro de este KVM
- AppArmor: Perfiles de acciones permitidas
- Utilizar imagen base propia y servidor registry propio
TROUBLESHOOTING
- https://github.com/wsargent/docker-cheat-sheet
- dmesg
- brctl show
- iptables -L -n
- tail -f /var/log/docker.log
- docker top CONTAINERID
- docker stats CONTAINERID
- docker history IMAGE_NAME
- docker stats $(docker ps -q)
- Habilitar DEBUG:
- vi /etc/conf.d/docker
- DOCKER_OPTS="-log-level debug"
- /etc/init.d/docker restart