Docker: comandos básicos y como crear una imagen personalizada usando Dockerfile

Para los que aun no saben, Docker es un herramienta que permite crear contenedores dentro de nuestro sistema operativo, es a lo que yo llamo "una mejora con esteroides para LXC " que  a diferencia de Vagrant, Docker no virtualiza el sistema, sino que aísla los procesos y servicios separándolos de la máquina que hospeda esos contenedores creados. Una de las funcionalidades de Docker es que podemos crear imágenes que usaran procesos y servicios tales como redis, mongodb, mysql, postgresql, y separarlos de las librerías y el sistema de ficheros del sistema operativo que hospeda el contenedor, por lo que en este pequeño post veremos como instalar la nueva versión de Docker v1.3 que contiene mejoras y nuevos comandos agregados. Este post se dividirá en 5 bloques organizados de la siguiente forma:

  1. Como instalar Docker v1.3 en Fedora 20 y CentOS 7.
  2. Algunos comandos de administración para Docker.
  3. Como obtener una imagen desde Docker Hub y crear un contenedor.
  4. Como crear una imagen a partir de otra ( centos:latest ) usando un Dockerfile.
  5. Crear una imagen que usara un contenedor con un servicio, ejecutarlo y conectarnos a ese servicio.

Docker Hub es un servicio en línea que nos permite registrar nuestros Dockerfile y tenerlos públicos para otros, y hasta por un módico costo podemos tener repositorios privados.

 

1. Como instalar Docker v1.3 en Fedora 20 y CentOS 7:

Primero es necesario instalar Docker en su versión 1.3 que se encuentra en los repositorios updates de Fedora 20, para ello, debemos hacer lo siguiente:

Fedora 20:

~$ sudo yum install -y docker
~$ sudo systemctl start docker.service
~$ sudo systemctl enable docker.service
~$ sudo usermod -a -G docker username

CentOS 7:

~$ rpm -ivh http://cbs.centos.org/kojifiles/packages/docker/1.3.1/1.el7.centos/x86_64/docker-1.3.1-1.el7.centos.x86_64.rpm
~$ sudo systemctl start docker.service
~$ sudo systemctl enable docker.service
~$ sudo usermod -a -G docker username

Tanto para Fedora 20 y CentOS 7 se pueden ejecutar los últimos tres (3) comandos de la misma forma. Lo que hemos hecho es instalar Docker desde los repositorios, luego iniciar el servicio, habilitarlo y por ultimo, hemos agregado nuestro usuario al grupo docker para poder utilizar Docker sin el comando sudo o como usuario root.

2. Algunos comandos básicos de administración en Docker:

Una vez instalado y corriendo Docker, podemos ejecutar una serie de comandos que nos ayudaran con la administración de nuestros contenedores e imágenes.

~$ docker version

Con docker version podemos conocer la versión instalada en nuestro sistema operativo.

~$ docker info

Con docker info podemos obtener información del sistema, desde el espacio en disco, el numero de contenedores.

~$ docker images

Con docker images, podemos ver las imágenes que se encuentran almacenadas en nuestro sistema. Es de mencionar que cada imagen almacenada se identifican con un IMAGE ID , el cual es referenciando cuando se crea un contenedor.

~$ docker rmi IMAGE_ID

Con docker rmi , podremos eliminar las imágenes almacenadas en nuestro sistema.

~$ docker ps

Con docker ps podemos ver los contenedores que se están ejecutando actualmente. Si se desea ver todos los contenedores deberemos añadir --all=true como parámetro de ps es decir: ~$ docker ps --all=true . Los mas flojos pueden usar: -a

~$ docker start CONTAINER_ID
~$ docker stop CONTAINER_ID

Los anteriores dos comandos nos permiten iniciar y detener nuestros contenedores, se puede hacer a través de su CONTAINER_ID o bien usando el NAMES creado para el contenedor.

~$ docker search < distro name or service>

Con el parametros search podremos hacer una busqueda en Docker Hub de una imagen en concreto.

~$ docker rm CONTAINER_ID

Con comando rm de Docker, podremos eliminar el contenedor previamente creado. Se puede hacer usando su CONTAINER_ID o bien el NAMES creado para ese contenedor.

~$ docker kill CONTAINER_ID

Con el comando kill podremos interrumpir de manera inmediata un contenedor en ejecución.

~$ docker --help

Usando --help podremos listar el listado de comandos que esta disponible para tu versión instalada de Docker.

3. Como obtener una imagen desde Docker Hub y crear un contenedor:

Ahora que conocemos algunos comandos de Docker podremos descargar una imagen y crear nuestro primer contenedor, para ello primero localizamos una imagen:

~$ docker search centos
NAME     DESCRIPTION                        STARS  OFFICIAL  AUTOMATED
centos   The official build of CentOS.      600    [OK]

...

Al ubicar nuestra imagen podremos obtener esa imagen, ejecutando el siguiente comando:

~$ docker pull centos:latest

Con el anterior comando obtenemos la imagen con el sistema de archivos de CentOS 7 que se encuentra registrada en Docker Hub y almacenarlas en nuestro sistema. Ahora tendremos que crear un contenedor, para ello, ejecutamos el comando create:

~$ docker create --name container_name --hostname hostname -i -t IMAGE_ID command

El anterior comando nos permite correr un nuevo contenedor y hemos usado las opciones --name en donde le asignamos un nombre a nuestro contenedor, --hostname podemos asignarle un nombre de host al contenedor creado, -i que nos permite tener control de la entrada en el contenedor, la opción -t que nos ofrece emular una terminal, luego colocamos el IMAGE_ID de nuestra imagen descargada desde Docker Hub a usar, también es posible colocar su nombre. La salida del mensaje a ejecutar será como el siguiente:

~$ docker create --name container_v1 -h container_v1 -i -t centos /bin/bash
16c34ff686ed2566f2a16f67ee060447c0b47a391164c52f2476b6258a6daa11

Ahora bien, debemos iniciar el contenedor creado por lo que ejecutaremos:

~$ docker start container_v1

Podremos acceder a nuestro contenedor ejecutando lo siguiente:

~$ docker exec -it container_v1 bash

Para salir del contenedor podemos presionar CTRL+D y saldremos a la línea de comandos de nuestros sistema operativo que hospeda las imagenes y contenedores. También podemos detener ese contenedor creado, de la siguiente manera:

~$ docker stop container_v1

4. Como crear una imagen a partir de otra ( centos:latest ) usando un Dockerfile.

Con Dockerfile podemos construir automáticamente imágenes bases de sistemas de archivos que posteriormente usaremos para crear contenedores. Un archivo Dockerfile contiene una secuencia de instrucciones con lo que crearemos una imagen base. Por lo que primero crearemos una imagen base para posterior, a partir de esta imagen base crear otras imágenes a las que le añadiremos servicios. El comando encargado de construir estas imágenes es build, que permite interpretar la secuencia de instrucciones y ejecutar lo que contengan esas instrucciones para construir la dicha imagen. Ahora bien, primero deberemos crear un archivo con el nombre Dockerfile, el mismo tendrá un formato como el siguiente:

# Comentario
INSTRUCCIONES argumentos

Los comentarios se inician con el símbolo # y pueden ser no necesarios. Las instrucciones deben ser escritas en mayúsculas ( UPPERCASE ) . Los argumentos son los comandos que utilizaremos para construir nuestra imagen base de Docker. Algunas de la instrucciones disponibles, las explicamos a continuación:

FROM: esta instrucción es utilizada para indicar la imagen base que será utilizada para construir la nuestra.

MAINTAINER: instrucción que indica el autor quien genera la imagen Docker.

RUN: permite ejecutar una instrucción en la imagen base, como por ejemplo, actualizar los repositorios.

CMD: usaremos esta instrucción para establecer el proceso o comando que se ejecutara cuando se inicie el contenedor.

EXPOSE: con esta instrucción indicaremos los puertos por los cuales accederemos a los servicios del contenedor.

ENV: a través de esta instrucción podemos setear las variables de entorno que serán usadas en nuestra imagen.

ADD: esta instrucción permite copiar nuevos archivos, directorios o archivos remotos a través desde su URL al contenedor.

COPY: esta instrucción permite copiar archivos y directorios al contenedor.

ENTRYPOINT: la instrucción permite configurar para que se inicie un comando cada vez que se cree un contenedor, como por ejemplo inicializar un servicio.

VOLUME: permite crear un punto de montaje ya sea desde el sistema operativo que hospeda los contenedores o bien desde otro contenedor, agregando así un volumen a cualquier contenedor que sea crea a partir de la imagen base.

USER: establece el nombre de usuario para cuando se ejecute la imagen o cualquier instrucción CMD , ENTRYPOINT.

WORKDIR: permite establecer el directorio de trabajo para la ejecución de instrucciones declaradas con RUN, CMD y ENTRYPOINT.

Si vamos a la documentación de Docker podremos encontrar ejemplos a las instrucciones antes mencionadas así como también algunas otras instrucciones que no he mencionado acá.

Para nuestro ejemplo usaremos algunas instrucciones que permitirán construir y personalizar una imagen base basada en otra imagen que se puede encontrar en Docker Hub , la misma esta basada en el ultimo release de CentOS 7. El archivo Dockerfile contendrá básicamente lo siguiente:

# CentOS 7 Dockerfile
# Pull base image
FROM centos:latest

# Maintener
MAINTAINER Tu nombre completo <tu correo>

# Update CentOS 7
RUN yum update -y && yum upgrade -y

# Install packages
RUN yum install -y unzip wget curl git

# Clean CentOS 7
RUN yum clean all

# Set the environment variables
ENV HOME /root

# Working directory
WORKDIR /root

# Default command
CMD ["bash"]

Hemos creado un archivo Dockerfile con las instrucciones que permiten actualizar el sistema de archivos del contenedor y sus paquetes, instalar los paquetes curl, git, wget y unzip, y luego hacemos limpieza de la cache que genera la aplicación yum con los meta datos y paquetes. Ya listo nuestro Dockerfile utilizamos la línea de comandos, nos movemos al directorio donde tenemos nuestro archivo y ejecutamos:

docker build -t="username_docker/name_image:tag" .

Lo que estamos por ejecutar es el comando build de Docker para construir nuestra imagen base personalizada, Docker declara que se debe de usar en el nombre de la imagen base, el usuario quien la crea separado del  / , luego el nombre de la imagen y por ultimo una etiqueta que puede ser un nombre alfanumérico y debe estar separada con el : luego del nombre de la imagen. Al momento de ejecutar el comando build, se generaran una serie de salidas que son generadas por las instrucciones del contenido del archivo Dockerfile. Un ejemplo del comando build puede ser, no olviden el punto al final del nombre:etiqueta, ya que es necesario para indicar que estamos en el directorio que contiene el archivo Dockerfile y que allí se encuentra:

docker build -t zokeber/centos:latest .

Luego de creada la imagen base personalizada de Docker, podremos listarla ejecutando lo siguiente:

~$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
zokeber/centos      latest              56ee2cd648bd        54 seconds ago      535.1 MB

Luego de tener nuestra imagen base personalizada podremos construir contenedores a partir de esa imagen base generada o bien generar imágenes con servicios como por ejemplo MongoDB, Redis. Adicional les dejo acá dos enlaces de imágenes base que he creado:

Docker Hub: https://registry.hub.docker.com/u/zokeber/centos/

Github: https://github.com/zokeber/docker-centos

5. Crear una imagen que usara un contenedor con un servicio, ejecutarlo y conectarnos a ese servicio:

Luego de generada la imagen base personalizada y adaptada a nuestra necesidades para crear futuras imágenes que generarán contenedores con servicios, en este caso como ejemplo, crearemos una imagen que será usada en un contenedor que tendrá como servicio la ultima versión estable de MongoDB. Primero, crearemos nuestro respectivo archivo Dockerfile que contendrá simplemente las instrucciones necesarias para instalar y levantar el servicio, este archivo Dockerfile debe estar en otro directorio con respecto al ultimo Dockerfile que hemos creado. El archivo Dockerfile deberá contener básicamente lo siguiente:

# Pull base image
FROM zokeber/centos

# Maintener
MAINTAINER Daniel Lopez Monagas <tucorreo>

# Install MongoDB
RUN echo -e "[mongodb]\nname=MongoDB Repository\nbaseurl=http://downloads-distro.mongodb.org/repo/redhat/os/`uname -m`/\ngpgcheck=0\nenabled=1" > /etc/yum.repos.d/mongodb.repo
RUN yum update
RUN yum install mongo-10gen mongo-10gen-server -y
RUN yum clean all
RUN chown -R mongod:mongod /var/lib/mongo

# Copy config mongodb
ADD etc/ /etc/

# User
USER mongod

# Mountable directories
VOLUME ["/var/lib/mongo"]

# Set the environment variables
ENV HOME /var/lib/mongo

# Working directory
WORKDIR /var/lib/mongo

ENTRYPOINT ["/bin/mongod"]
CMD ["-f", "/etc/mongod.conf"]

# Expose ports.
EXPOSE 27017

Con las instrucciones anteriores, tomamos una imagen base para crear nuestra propia imagen que contendrá el servicio de MongoDB, el comando FROM hace justamente eso que se esta comentando, estamos tomando de una imagen base creada, que ya se ha publicado en Docker Hub. El resto de las instrucciones, como RUN, se encarga de escribir las fuentes para el repositorio de MongoDB y luego instalarlo. El comando ADD se usa para agregar lo que se encuentra en el directorio /etc/ , con USER le decimos que se ejecute el proceso con el usuario mongod, la instrucción VOLUME permite crear un punto de montaje que sera usado para cada contenedor creado. Con la instrucció WORKDIR seteamos que el directorio de trabajo es /var/lib/mongo así como también con la instrucción ENV HOME donde se declara el home del usuario para los contenedores, ya con ENTRYPOINT y CMD declaramos los comandos que inicialmente ejecutara el contenedor, y por ultimo con EXPOSE le decimos por cual puerto escuchara ese contenedor.

Listo, nuestro archivo Dockerfile, en la línea de comandos ejecutamos los siguiente:

docker build -t zokeber/mongodb:latest .

El punto recuerden es necesario ya que con el indicaremos que estamos en el directorio en donde se encuentra el archivo Dockerfile. Como explique antes el comando build se encargara de procesar esas instrucciones y construir una imagen con el servicio de MongoDB, que posterior usaremos para crear contenedores con el servicio que ya mencionado. Podemos comprobar que la imagen base con el servicio de MongoDB se encuentra terminada por que cuando el comando build ejecuta cada instrucción la ultima de ella muestra el mensaje: Posteriormente, podemos listar esa imagen con el siguiente comando:

~$ docker images

Ahora como ejemplo, vamos a crear dos contenedores con el servicio MongoDB para ello, en la línea de comandos ejecutamos, para el primer contenedor:

~$ docker create --name mongodb-docker-1 -h mongodb-docker-1 -p 127.0.0.1:27017:27017 -it zokeber/mongodb

Para el segundo contenedor:

~$ docker create --name mongodb-docker-2 -h mongodb-docker-2 -p 127.0.0.1:27018:27017 -it zokeber/mongodb

Primero explicamos por que varían los puertos de escuchas, esto se debe a que el primer contenedor creado se ha utilizado el parámetro -p 127.0.0.1:27017:27017 donde se indica que escuche en el dispositivo localhost en el puerto 27017 del sistema que hospeda los contenedores en donde el mismo daemon de Docker se encarga de enrutarlo a la dirección interna 0.0.0.0 en el puerto 27017 del primer contenedor. Al segundo contenedor lo seteamos con el parámetro -p 127.0.0.1:27018:27017 , lo que se ha hecho es indicar que escuche en el dispositivo localhost en el puerto 27018, recuerden que el puerto 27017 ya se encuentra ocupado por el primer contenedor. para el resto de los parámetros con --name hemos indicado un nombre al contendor y con el parámetro -h asignamos un nombre de host, por ultimo hemos indicado con el parámetro -i que nos permita tener control de la entrada en el contenedor y con la opción -t que nos permita emular una terminal.

Podemos listar los contenedores creados y que aun no han sido iniciado con el siguiente comando:

~$ docker ps -a

Luego, procedemos a iniciar los contenedores creados, de la siguiente manera, para el primer contenedor ejecutamos en la terminal:

~$ docker start mongodb-docker-1

y para el segundo contenedor ejecutamos:

~$ docker start mongodb-docker-2

Podremos listar los contenedores iniciados de la siguiente manera:

~$ docker ps

Al iniciar la primera vez debemos esperar que mongod genere su directorio de trabajo. Por lo que transcurrido algunos minutos, podremos conectarnos a el servicio iniciado en los contenedores, para el primer contenedor podemos usar el mismo comando mongo:

~$ mongo --port 27017

Para conectarnos con el segundo contenedor que contiene el servicio MongoDB, ejecutamos:

~$ mongo --port 27018

Podemos también conectarnos a nuestro contenedor y revisar su sistema de archivos así como también usar el mongo shell interno del contenedor, para ello ejecutamos, para el primer contenedor:

~$ docker exec -it mongodb-docker-1 bash

y luego conectarnos con nuestro mongo shell interno:

bash-4.2$ mongo

Para salir del contenedor simplemente ejecutamos CTRL+D.  Por ultimo pueden encontrar una referencia a una imagen base creada con el servicio de MongoDB en las siguientes url:

en Docker Hub: https://registry.hub.docker.com/u/zokeber/mongodb/

en Github: https://github.com/zokeber/docker-mongodb

Por lo que hasta aquí este "pequeño" post en donde se ha indicado como crear imágenes bases para contenedores con servicios, en otras publicaciones extenderemos como registrarlas en Docker Hub , un poco sobre como es administrar los puntos de montajes o volúmenes y enlazarnos con otros contenedores. Nos vemos...