0% encontró este documento útil (0 votos)
356 vistas24 páginas

Como Crear Un Sitio Web Con Docker PDF

Cargado por

Daniel Nuñez
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
356 vistas24 páginas

Como Crear Un Sitio Web Con Docker PDF

Cargado por

Daniel Nuñez
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd

Como crear un sitio web con Docker

 14 mayo, 2017    roberto  14 comentarios   contenedores, docker, dockers, grafana, http, influxdb, linux, mariadb,nginx, ssl, telegraf, wordpress

Como forma de introducción al uso de Docker he decidido implementar este sitio web utilizando contenedores. Para ello he utilizado varios de los
componentes y funcionalidades de Docker que me han permitido implementar el sitio web de una forma que creo que es interesante compartir como
ejemplo del uso de contenedores.

Objetivo
El objetivo principal de esta entrada es el conocer con un detalle avanzado el funcionamiento de docker, no nos vamos a quedar en sólo descargar y
ejecutar una imagen. Para ello vamos a tomar como ejemplo la creación de un sitio web con una infraestructura que tiene las siguientes características:

Como plataforma base voy a utilizar un servidor VPS basado en CENTOS 7 con 2GB de RAM y 10GB de disco duro
Para la página web voy a utilizar WordPress con MariaDB como base de datos
Un sistema de monitorización del estado de la propia web basado en InfluxDB+Telegraf+Grafana
Un Proxy inverso basado en Nginx que me permita acceder a los diferentes «servicios» del sitio web
Publicación con http y https, utilizando certificados de Let’s Encrypt

Cuando empecé a escribir esta entrada, comencé con la idea de mostrar de forma sencilla como usar Docker para crear una página web, pero ha ido
creciendo hasta convertirse un manual de qué es Docker y como funciona.

Introducción a Docker
¿Qué es Docker?
Docker es un proyecto de código abierto que permite el uso de aplicaciones dentro contenedores con el objetivo de proporcionar una capa de aislamiento
en la ejecución de las mismas. Al iniciarse dentro de un entorno Linux, esta capa de aislamiento es proporcionada por componentes del kernel
como cgroups o namespaces, aunque debido a su auge y aceptación por la industria y la comunidad también ha sido adoptado por Microsoft de forma que
podemos también utilizar Docker en entornos Windows (a partir de Windows 10 y Windows Server 2016)

El objetivo de un contenedor, es conseguir un paquete de software ligero que incluye todos los componentes y dependencias necesarios para ejecutar una
aplicación de forma que siempre se ejecuta de la misma forma independientemente de su entorno o infraestructura donde se ejecuta.

Características de Docker
A continuación voy a enumerar algunas de las principales de Docker:

Menor uso de recursos: los contenedores comparten recursos con el kernel de la máquina donde se ejecutan. Además pueden compartir
recursos (principalmente almacenamiento) de forma que se reduce en gran medida el tamaño de los contenedores.
Rapidez de ejecución: el inicio y ejecución de las instacias de los contenedores se produce de forma casi instantánea
Estándar: utiliza componentes estándar por lo que se puede utilizar en la mayoría de las distribuciones Linux y como hemos comentado
antes, también en entornos Windows.
Aislamiento: con Docker conseguimos un nivel de aislamiento entre las aplicaciones que permiten aumentar la seguridad de la infraestructura
Distribución y compartición: permite compartir de una forma muy sencilla y rápida las aplicaciones creadas en contenedores.
Escalado: permite automatizar y escalar miles de una forma rápida y sencilla

 
Docker vs Máquinas virtuales
Aunque Dockers y Máquinas virtuales puedan ser un concepto similar y proporcionen características similares (ambos proporcionan aislamiento en la
ejecución, permiten mover las aplicaciones entre hosts …) es importante saber que estamos trabajando con sistemas y arquitecturas diferentes.

Para entender las diferencias entre dockers y máquinas virtuales me ha gustado la analogía que he visto en un vídeo de la última DockerCon 2017:

Una máquina virtual es una casa individual o chalet que proporciona una infraestructura completa a quién vive en ella. Posee recursos
individuales en cuanto a calefacción, electricidad, aislamiento…
Un docker es un apartamento o piso, proporciona una infraestructura a  quién vive en él, pero esa infraestructura es compartida con otros
apartamentos. En el edificio de apartamentos hay una infraestructura común de la que se sirven los apartamentos y pisos, calefacción,
electricidad… Además en un edificio pueden existir pisos de diferentes tamaños para diferentes necesidades.

Dejando de lado el tema inmobiliario, podríamos decir que las máquinas virtuales que se ejecutan en un host comparten los recursos físicos pero no
comparten ningún recurso software. En el caso de Docker, además de compartir los recursos hardware también comparten recursos a nivel software. El
objetivo de Docker es centrarse en la aplicación, dejando de lado el sistema operativo donde se ejecuta.

Máquina Virtual Docker

Este cambio de modelo implica también un cambio en la mentalidad de los administradores de sistemas:

¿Cómo hago el backup de un contenedor? Directamente no lo hacemos. Uno de los objetivos de los contenedores es que no incluyan datos de
la aplicación y se puedan ejecutar y reproducir su ejecución de forma muy sencilla.
Pero el contenedor tiene un sistema operativo, aunque sea una imagen mínima y optimizada: ¿cómo mantengo actualizado el contenedor?
¿cómo lo parcheo? Pues no lo parcheamos. Como los contenedores están basados en imágenes y no contienen datos, lo que hacemos es
actualizar la imagen y parar y arrancar el contenedor.

Aunque son conceptos diferentes, no son excluyentes. Podemos perfectamente ejecutar Docker sobre una máquina virtual o incluso una aplicación que se
ejecuta en un contenedor puede utilizar una base de datos que se ejecuta en una máquina virtual. Los dockers no van a reemplazar de forma competa a las
máquinas virtuales. Tendremos aplicaciones donde la ejecución en contenedores nos proporcione ventajas sobre la ejecución de las mismas en máquinas
virtuales (agilidad de despliegue, consumo de recursos…) pero habrá otras donde sigamos prefiriendo que se ejecuten en máquinas virtuales (por ejemplo
bases de datos)

De esta forma tendremos centros de datos con máquinas virtuales, dockers que se ejecutan en servidores físicos, dockers que se ejecutan en máquinas
virtuales…

Componentes principales de Docker


Docker Engine: es la aplicación cliente-servidor que contiene los siguientes elementos
Un servidor que se ejecuta como un demonio en el sistema (dockerd)
Un interfaz REST API que sirve de acceso para ejecutar comandos en el servidor
Un cliente que utiliza el usuario para interactuar con el servidor(docker)
Image: es el paquete de software ejecutable que contiene todo lo necesario para ejecutar una aplicación
Container: es una instancia en ejecución de una imagen.
Network: los contenedores se comunican con otros dockers o con recursos externos a través de la red:
Data Volume: permiten almacenar datos de forma persistente.

Ediciones de Docker
A principios de este año 2017 se han producido varios anuncios relacionados con las ediciones disponibles de Docker ya que de alguna forma, se está
reorganizando su alcance y disponibilidad.

En estos momentos disponemos de las siguientes ediciones:

Docker Enterprise Edition (Docker EE): es la versión orientada a utilizarse en empresas y negocios.
Docker Community Edition (Docker CE): es la versión gratuita orientada a utilizarse en pequeños proyectos o desarrollos personales:
Docker Cloud: es la orientada a servirse en plataformas cloud como AWS o Azure

Nosotros vamos a utilizar Docker CE, y en este caso la versión Docker CE Stable (se publica una versión cada 4 meses) en lugar de la versión Docker CE Edge
(se publica una versión nueva cada mes)

Instalación de Docker
Habilitamos el repositorio para CentOS
Instalamos el paquete yum-utils para utilizar la herramienta yum-config-manager

yum install -y yum-utils


Añadimos el repositorio (es el mismo repositorio para la versión Stable como para la versión Edge)

yum-config-manager --add-repo [Link]

(Opcional) Si quisiesemos utilizar la versión Edge es necesario hablitar el repositorio

yum-config-manager --enable docker-ce-edge

Instalamos Docker
Actualizamos el índice de paquetes de yum

yum makecache fast

Instalamos Docker-CE

yum install docker-ce

Comprobamos el estado del demonio docker y lo iniciamos si no está arrancado

systemctl start docker

Hablitamos el demonio para que arranque con el sistema

systemctl enable docker

Ejecutamos el comando para obtener la versión de docker

docker version

Y obtenemos un mensaje en el que se nos indica las versiones del cliente como del servidor (en este caso los dos instalados en la misma máquina) como el
siguiente:

Client:
Version: 17.03.1-ce
API version: 1.27
Go version: go1.7.5
Git commit: c6d412e
Built: Mon Mar 27 [Link] 2017
OS/Arch: linux/amd64

Server:
Version: 17.03.1-ce
API version: 1.27 (minimum version 1.12)
Go version: go1.7.5
Git commit: c6d412e
Built: Mon Mar 27 [Link] 2017
OS/Arch: linux/amd64
Experimental: false
 

Creación del sitio Web


Imágenes y versiones de Dockers a utilizar en el sitio web
Vamos a utilizar las versiones oficiales de los diferentes productos, y como en la mayoría de los casos tenemos diferentes versiones (o tags en el lenguaje
docker) vamos a especificar la versión concreta a utilizar (en lugar de utilizar la última versión disponible)

MariaDB: mariadb:10.0.29
WordPress: wordpress:4.7.2-apache
InfluxDB: influxdb:1.2.0-alpine
Telegraf:  telegraf:1.1.2-alpine
Grafana:  grafana/grafana:4.1.2
Nginx: nginx:1.10
PhpMyAdmin: phpmyadmin/phpmyadmin:4.6.6-1

Voy a ir describiendo como vamos creando el sitio web desde una primera versión con una configuración mínima y paso a paso ver como evoluciona
añadiendo más servicios y configuraciones de forma que podamos ver el uso y las diferentes opciones de docker.

Ejecutando el primer docker


Como para gestionar el sitio web vamos a utilizar WordPress, lo primer que vamos a hacer es descargarnos la imagen de wordpress oficial.

Accedemos al repositorio ([Link] y lo primero que vemos es que tenemos decenas de versiones de imágenes disponibles
para utilizar. Además de diferentes versiones y subversiones de wordpress (4.7, 4.7.1, 4.7.2…) tenemos disponibles imágenes basadas en:

apache
fpm
fpm-alpine

Vamos a comenzar eligiendo la imagen: wordpress:4.7.2-apache

Descargamos la imagen con el comando «docker pull» y el nombre de la imagen a descargar

# docker pull wordpress:4.7.2-apache

4.7.2-apache: Pulling from library/wordpress


693502eb7dfb: Downloading [==============> ] 14.68 MB/51.36 MB
16328c296404: Downloading [=========> ] 14.6 MB/77.61 MB
8b3c97761df6: Download complete
5e1d4f4f29eb: Download complete
530750fc5019: Download complete
39e9c6c72db7: Download complete
de476ce7ac87: Download complete
4ad13cbbc7d8: Download complete
74c28aa07dc7: Downloading [======================> ] 5.545 MB/12.58 MB
a07a242e36fb: Waiting
3d491d166e88: Waiting
cb6c232330f0: Waiting

Creamos y ejecutamos un docker de nombre «wordpress01» con la imagen anterior con el comando «docker run» y los situientes parámetros:
–name: nombre que le vamos a dar al contenedor
-d: el contenedor s e ejecutará en segundo plano

# docker run --name wordpress01 -d wordpress:4.7.2-apache

0b255bad8202cb48e43af418e627d92767111edcd2dc03504df7f3d2c3f7e115
 

Si todo es correcto, el comando nos devuelve el identificador asignado al docker

Comprobamos que el contenedor se está ejecutando

# docker ps

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS


NAMES
0b255bad8202 wordpress:4.7.2-apache "docker-entrypoint..." 35 seconds ago Up 34 seconds 80/tcp
wordpress01

En este punto hemos creado el contenedor con la imagen de wordpress, pero no tiene ninguna utilidad ya que no podemos acceder a él, no
hemos configurado la conectividad desde el exterior del host.

Cuando se instala y ejecuta el servicio docker, se crean diferentes redes que pueden ser utilizadas por los contenedores

# docker network ls
NETWORK ID NAME DRIVER SCOPE
9c435956de8f bridge bridge local
98d7b2d36ca4 host host local
77b2fad06ea6 none null local

bridge: conecta el contenedor con el interfaz docker0 del host, utilizando una red privada y un servicio DHCP que asigna las direcciones IP a
los contenedores. Es la red a la que se conectan por defecto los contenedores si no se indica una red de forma explícita.
host: conecta el contenedor con la red del host, de forma que no hay aislamiento entre el host y el contenedor.
none: en este caso el contenedor no tiene conectividad

La siguiente imagen podría mostrar una representación gráfica de cada una de las 3 redes

En la instalación por defecto de Docker, se crea el interfaz docker0 en el host, como podemos ver en el siguiente ejemplo:

docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN


link/ether [Link] brd [Link]
inet [Link]/16 scope global docker0
valid_lft forever preferred_lft forever

En nuestro caso, hemos creado un contenedor conectado a la red «bridge»


Cuando arrancamos el contenedor, en el host se crea un interfaz por cada red a la que está conectado el contenedor:

veth97047f1@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP


link/ether [Link] brd [Link] link-netnsid 1
inet6 fe80::b0fe:acff:fe72:5262/64 scope link tentative
valid_lft forever preferred_lft forever

Pero aunque parece que tiene conectividad con el exterior, realmente el contenedor está aislado y tenemos que «publicar» el puerto correspondiente, en el
caso del contenedor wordpress, el puerto tcp-80.

Paramos y borramos el contenedor creado

# docker stop wordpress01

wordpress01

# docker rm wordpress01

wordpress01

Volvemos a ejecutar «docker run» pero esta vez vamos a utilizar el parámetro «-p». Con este parámetro podemos «enlazar» uno de los
puertos del host donde se está ejecutando el contenedor, con el puerto por el que está dando servicio el contenedor, en el caso de
wordpress, el puerto 80.
-p: definimos los puertos en el formato puerto(s) del host:puerto(s) del contenedor (podemos definir un rango de puertos y también
indicar una ip específica del host)

# docker run --name wordpress01 -d -p 8080:80 wordpress:4.7.2-apache

45e7fdef188bcb798cb25e3beb30da1e69db2728d227aa7cc518dcbdb6a54472

Ahora en la columna Ports vemos el enlace de los puertos entre el puerto del host (8080) y el puerto del contenedor (80)

# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS
NAMES
45e7fdef188b wordpress:4.7.2-apache "docker-entrypoint..." 17 seconds ago Up 16 seconds [Link]:8080-
>80/tcp wordpress01

Si accedemos con un navegador a la url [Link] podremos ver la página donde se inicia el asistente de configuración inicial de
wordpress.

Ya temos el sitio de wordpress listo para configurar, pero tenemos un problema, no tenemos una base de datos donde guardar la configuración de
wordpress. Para ello vamos a utilizar MariaDB como gestor de base de datos y con la idea de utilizar dockers como microservicios, crearemos un contenedor
con la instancia correspondiente.

Primera versión del sitio web (WordPress + MariaDB)


De forma similar a como hemos hecho con la imagen de wordpress vamos a proceder con la imagen de MariaDB

Descargamos la imagen
# docker pull mariadb:10.0.29

10.0.29: Pulling from library/mariadb


693502eb7dfb: Already exists
08d0e9d74b1b: Pull complete
e700ebfbe6bc: Pull complete
f718f1976629: Extracting [==================================================>] 114 B/114 B
b73d942a76fd: Downloading [> ] 67.16 kB/6.468 MB
6b34f02138e1: Download complete
b07f47800e46: Waiting
fb3499ae0cd2: Waiting

Creamos y ejecutamos un contenedor

# docker run --name mariadb01 -d mariadb:10.0.29

2754d6410c164a3d23f33f1b15d8f8740c04d9a5bdf4d87abf3c8881a83c132e

Comprobamos los contenedores que tenemos ejecutando

# docker ps

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS


NAMES
45e7fdef188b wordpress:4.7.2-apache "docker-entrypoint..." 7 minutes ago Up 7 minutes [Link]:8080-
>80/tcp wordpress01

Vemos que hay un problema, ¿dónde está el contenedor de mariadb01?  Vamos a volver a ejecutar el comando «docker ps» pero esta vez con
el parámetro «-a»

# docker ps -a

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS


NAMES
2754d6410c16 mariadb:10.0.29 "docker-entrypoint..." 15 seconds ago Exited (1) 14 seconds ago
mariadb01
45e7fdef188b wordpress:4.7.2-apache "docker-entrypoint..." 7 minutes ago Up 7 minutes
[Link]:8080->80/tcp wordpress01

Vemos que el contenedor mariadb01 se ha creado pero se ha parado, vemos que en la columna STATUS tenemos el mensaje «Exited …» Para
poder investigar un poco más que ha pasado vamos a utilizar el comando «docker logs»

# docker logs mariadb01

error: database is uninitialized and password option is not specified


You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD

El mensaje nos indica claramente que no hemos indicado la contraseña del usuario root (SIEMPRE tenemos que leer la documntación
primero) Hay imagenes que requieren o permiten utilizar variables de entorno para poder personalizar diferentes valores del contenedor.
Para poder establecer los valores de las variables utilizamos el parámetro «-e» al ejecutar «docker run»

 
En el caso de la imagen de MariaDB podemos utilizar las siguientes variables:
MYSQL_ROOT_PASSWORD: [obligatoria] permite establecer la contraseña del usuario root.
MYSQL_ALLOW_EMPTY_PASSWORD: podemos utilizar esta variable para dejar la contraseña del usuario root en blanco.
MYSQL_RANDOM_ROOT_PASSWORD: se genera una contraseña para el usuario root de forma aleatoria y se muestra por pantalla.
MYSQL_DATABASE: permite crear una base de datos, y si se indica usuario y contraseña (siguiente variable) se le asignan los permisos
de superusuario (GRANT ALL)
MYSQL_USER, MYSQL_PASSWORD: estas dos variables permiten crear un usuario y su contraseña sobre el que se aplicarán los permisos
de superusuario para la base de datos creada con la variable anterior
Tras ver las variables disponibles, vamos a volver a ejecutar el contenedor de MariaDB, pero esta vez vamos a utilizar las variables para definir
la contraseña del usuario root y ya definimos el usuario y la base de datos que vamos a utilizar con wordpress:
MYSQL_ROOT_PASSWORD: root_P@ssw0rd
MYSQL_DATABASE: wordpress01
MYSQL_USER: user_wordpress01
MYSQL_PASSWORD: user_P@ssw0rd

# docker run --name mariadb01 -d -e MYSQL_ROOT_PASSWORD=root_P@ssw0rd -e MYSQL_DATABASE=wordpress01 -e


MYSQL_USER=user_wordpress01 -e MYSQL_PASSWORD=user_P@ssw0rd mariadb:10.0.29

3f33340e0623f51aea8b2d4a8eecca64801f18d0f9c4d21a8894987f39453f44

Volvemos a comprobar los contenedores y ahora si que tenemos los dos contenedores ejecutándose:

# docker ps

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS


NAMES
3f33340e0623 mariadb:10.0.29 "docker-entrypoint..." 18 seconds ago Up 18 seconds 3306/tcp
mariadb01
45e7fdef188b wordpress:4.7.2-apache "docker-entrypoint..." 28 minutes ago Up 28 minutes [Link]:8080-
>80/tcp wordpress01

Para comprobar que el servicio de mysql está funcionando y la base de datos creada, vamos a conectarnos al contenedor con el comando
«docker exec» y los parámetros:
-i: modo interactivo
-t: pseudo-TTY
Nombre del contenedor
comando

# docker exec -i -t mariadb01 bash

root@3f33340e0623:/#

Ahora estamos en la shell del contenedor y podemos conectarnos con el cliente de mysql y comprobar la existencia de la base de datos

root@3f33340e0623:/# mysql -u user_wordpress01 -p


Enter password:

Welcome to the MariaDB monitor. Commands end with ; or \g.


Your MariaDB connection id is 5
Server version: 10.0.29-MariaDB-1~jessie [Link] binary distribution

Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> show databases;


+--------------------+
| Database |
+--------------------+
| information_schema |
| wordpress01 |
+--------------------+
2 rows in set (0.00 sec)

Hemos avanzado, pero ahora tenemos un problema: ¿cómo le indico al contenedor de wordpress que utilice la base de datos que hemos
creado en el contenedor mariadb01?

En el punto anterior, para dar acceso al contenedor de wordpress, lo que hemos hecho es «mapear» un puerto del host al puerto 80 del contenedor. En este
caso podríamos hacer lo mismo y mapear un puerto del host para el contenedor de MariaDB, pero no tiene sentido, ya que a este contenedor no tenemos
que acceder desde el exterior, sólo es necesario que otro contenedor acceda a él.

Tenemos los dos contenedores conectados a la misma red (la red bridge) por lo que tienen conectividad entre ellos, pero únicamente utilizando sus
direcciones IP. Además estas direcciones IP no son estáticas, por lo que no es recomendable utilizarlas para establecer la conectividad entre los
contenedores ya que en el futuro, si cambiamos el orden de ejecución de los contenedores o creamos más, pueden cambiar.

Para permiter la conexión entre los contenedores vamos a «enlazarlos». Cuando enlazamos los contenedores con el parámetro «–link», se crea un «tunel»
que enlaza los dos contenedores, sin la necesidad de exponer puertos. Al enlazar los contenedores, también hacemos que las variables de entorno de un
contenedor, sean accesibles desde el otro. También se actualiza el archivo /etc/hosts para poder utilizar los nombres de los contenedores como nombres de
red.

Creamos en enlace del siguiente modo:

--link <nombre del contenedor o id>:alias

Conociendo esto vamos a volver a rehacer los contenedores


El contenedor de Mariadb lo podemos dejar ejecutando pero paramos y borramos el contenedor de wordpress para volver a crearlo

# docker stop wordpress01

wordpress01

# docker rm wordpress01

wordpress01

Volvemos a ejecutar el contenedor de wordpress pero esta vez enlazándolo al contenedor de mysql

# docker run --name wordpress01 -d -p 8080:80 --link mariadb01:db wordpress:4.7.2-apache

428f50e26dd69f705f383d0b9603b31450536aef7016c96b4a8b325a44453069

Si accedemos al docker de wordpress01 podemos comprobar que ahora si hay conectividad entre los contenedores ya que se ha creado una
entrada en el archivo /etc/hosts del contenedor wordpress01

# docker exec -it wordpress01 bash

root@428f50e26dd6:/var/www/html# cat /etc/hosts


[Link] localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
[Link] db 3f33340e0623 mariadb01
[Link] 428f50e26dd6

root@428f50e26dd6:/var/www/html# ping mariadb01


PING db ([Link]): 56 data bytes
64 bytes from [Link]: icmp_seq=0 ttl=64 time=0.200 ms
64 bytes from [Link]: icmp_seq=1 ttl=64 time=0.097 ms
^C--- db ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.097/0.149/0.200/0.052 ms

Ahora podemos volver al asistente de configuración de wordpress y configurar la base de datos correspondiente

En este punto ya tendríamos un sitio web totalmente funcional con WordPress y MariaDB
 

Para llegar a este punto, en el que tenemos:

1 contenedor con MariaDB y una base de datos


1 contenedor con WordPress siendo accesible en el puerto 80 del host donde se ejecutan los contenedores

ha sido necesario ejecutar los siguientes comandos de docker para crear 2 contenedores:

# docker run --name mariadb01 -d -e MYSQL_ROOT_PASSWORD=root_P@ssw0rd -e MYSQL_DATABASE=wordpress01 -e


MYSQL_USER=user_wordpress01 -e MYSQL_PASSWORD=user_P@ssw0rd mariadb:10.0.29

# docker run --name wordpress01 -d -p 80:80 --link mariadb01:db wordpress:4.7.2-apache

Revisión de la configuración de red


Aunque ya tenemos los dockers funcionando, el utilizar la red «bridge» por defecto no es una práctica recomendada. Para seguir con las buenas prácticas
vamos a crear una nueva red, también de tipo bridge, ya que de esta forma tenemos un mayor control de la conectividad de los contenedores y nos
aprovechamos de la funcionalidad que proporciona una resolución automática DNS de forma que no tenemos que utilizar los enlaces que hemos visto
anteriormente para permitir la conexión entre contenedores.

Vamos a crear un diseño como el siguiente:


Para ello vamos a realizar los siguientes pasos:

Crear 2 redes de tipo bridge


FrontEnd
BackEnd
Conectar el contenedor mariadb01 a la red BackEnd
Conectar el contenedor wordpress01 a las dos redes, a FrontEnd y a BackEnd

Seguimos los siguientes pasos:

# docker network create --driver bridge FrontEnd


f3c2e0bde8a5545a4cca4437f176c5c7fb4a85195cbac674d068267ac5641a3c

# docker network create --driver bridge BackEnd


f73c8718a52d665c32171e7de7dc9a54c3eeba220b1ce32a895faf04a681ae37

# docker network ls

NETWORK ID NAME DRIVER SCOPE


f73c8718a52d BackEnd bridge local
f3c2e0bde8a5 FrontEnd bridge local
03774c55fe4e bridge bridge local
98d7b2d36ca4 host host local
77b2fad06ea6 none null local

Podemos inspeccionar las redes creadas y ver el direccionamiento asignado a cada una

# docker inspect FrontEnd


[
{
"Name": "FrontEnd",
"Id": "f3c2e0bde8a5545a4cca4437f176c5c7fb4a85195cbac674d068267ac5641a3c",
"Created": "2017-05-10T[Link].487193712+02:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "[Link]/16",
"Gateway": "[Link]"
}
]
},
"Internal": false,
"Attachable": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
# docker inspect BackEnd
[
{
"Name": "BackEnd",
"Id": "f73c8718a52d665c32171e7de7dc9a54c3eeba220b1ce32a895faf04a681ae37",
"Created": "2017-05-10T[Link].700385188+02:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "[Link]/16",
"Gateway": "[Link]"
}
]
},
"Internal": false,
"Attachable": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]

Al crear las redes, vemos también que se han creado las tarjetas de red asociadas en el host:

br-e104556ea223: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP


link/ether [Link] brd [Link]
inet [Link]/16 scope global br-e104556ea223
valid_lft forever preferred_lft forever
inet6 fe80::42:efff:fe3a:5957/64 scope link
valid_lft forever preferred_lft forever

br-7259dde2f075: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP


link/ether [Link] brd [Link]
inet [Link]/16 scope global br-7259dde2f075
valid_lft forever preferred_lft forever
inet6 fe80::42:e5ff:fe54:bead/64 scope link
valid_lft forever preferred_lft forever

Ejecutamos los contenedores, pero esta vez indicando a que red conectamos cada contenedor

El contenedor de mariadb01

# docker run --name mariadb01 -d -e MYSQL_ROOT_PASSWORD=root_P@ssw0rd -e MYSQL_DATABASE=wordpress01 -e


MYSQL_USER=user_wordpress01 -e MYSQL_PASSWORD=user_P@ssw0rd --network=BackEnd mariadb:10.0.29

Y el contenedor de wordpress01

# docker run --name wordpress01 -d -p 80:80 --network=FrontEnd wordpress:4.7.2-apache

# docker network connect BackEnd wordpress01

 
 

Configuración del almacenamiento de los contenedores


Contenedores y capas
El siguiente punto que tenemos que revisar es la configuración del almacenamiento para tener claro donde se están guardando los contenedores y la
información generada en el sitio web.

Como hemos hecho antes con la red, vamos ver de forma rápida como funciona el almacenamiento en los contenedores de Docker.

El almacenamiento de los archivos y carpetas de las imágenes y contenedores está organizado por capas. Una imagen está formada por diferentes capas de
sólo lectura, que a través del driver de almacenamiento (la implementación correspondiente de Union File System) es mostrada de forma única.

Por ejemplo, la imagen de ubuntu:15:04 está formada por 4 capas, cada una de ellas con diferente información.

El driver es el encargado de mostrar esas 4 capas como una vista única de la imagen. Cada vez que se añade una modificación a una imagen, se le crea una
nueva capa con los cambios introducidos, quedando el resto sin tocar.

Al crear un contenedor de esa imagen, lo que hacemos es añadir una nueva capa, en este caso de lectura/escritura. De esta forma, el contenedor está
formado por las 4 capas de sólo lectura que proporciona la imagen y la capa de lectura/escritura propia del contenedor.
Los identificadores de cada una de las capas de la imagen corresponden con un hash del contenido. El identificador de la capa del contenedor es generado
de forma aleatoria.

Comentábamos que una de las características de Docker es que los contenedores ocupan poco espacio en disco. Esto es así, porque diferentes
contenedores creados a partir de una misma imagen, comparten todas las capas de sólo lectura de la imagen y cada uno de ellos tiene su propia capa de
escritura.

Si alguno de los contenedores necesita modificar alguno de los archivos que se ubican en las capas de la imagen, se crea una copia (Copy-On-Write) de
forma que sólo ese contenedor accede a esa copia modificada y el resto de contenedores siguen accediendo a la imagen original.

Si el contenedor es eliminado, esa capa propia de lectura/escritura también es eliminada.

Docker permite utilizar diferentes drivers para gestionar el almacenamiento. Algunos de los drivers que nos podemos encontrar o utilizar son: OverlayFS,
AUFS, Btrfs, Device mapper, VFS, ZFS …

Para ver que driver estamos utilizando podemos ejecutar docker info

# docker info

Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 1
Server Version: 17.03.1-ce
Storage Driver: overlay
Backing Filesystem: xfs
Supports d_type: false
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
Volume: local
Network: bridge host macvlan null overlay
Swarm: inactive

Aunque hay diferentes drivers y cada uno tiene diferentes características que se pueden adaptar de una mejor forma a diferentes entornos y necesidades,
vamos a utilizar el driver por defecto que proporciona la instalación de Docker en CentOS: overlay

Data Volumes
Como hemos visto, los datos que modificar un contenedor respecto a los datos de la imagen a partir del cual se crean se gestionan en una capa de
lectura/escritura que tiene la vida del mismo contenedor, se elimina cuando se borra el contenedor. Pero podemos tener casos en los que queramos
mantener esa información o queramos compartir la información entre dos o más contenedores de forma que accedan a los mismos datos. Para esto
tenemos los Data Volumes

Los Data Volumes, son directorios específicos que no utilizan los drivers de almacenamiento y que permiten disponer de un recurso de datos permanente y
con la opción de compartirlo entre varios contenedores.

Para poder añadir un Data Volume a un contenedor, utilizamos la opción -v al crearlo, por ejemplo:

# docker run --name ubuntu01 -it -v /volume01 ubuntu:17.04 /bin/bash

Podemos ver que en el contenedor tenemos el directorio /volume01

root@26ee79b0ea44:/# ls -la /volume01

/volume01:
total 0
drwxr-xr-x. 2 root root 6 May 14 08:05 .
drwxr-xr-x. 1 root root 58 May 14 08:05 ..

Si inspeccionamos el contenedor vemos en el apartado Mount, los volúmenes a los que accede el contenedor:

"Mounts": [
{
"Type": "volume",
"Name": "5a7c85ba5d84ed6c823d66a5c663777d2557f1dab6975c0adbd63145ff78391f",
"Source": "/var/lib/docker/volumes/5a7c85ba5d84ed6c823d66a5c663777d2557f1dab6975c0adbd63145ff78391f/_data",
"Destination": "/volume01",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],

Si ejecutamos el comando docker volume ls, podemos ver el listado de volúmenes del host, donde se encuentra el creado para el contenedor anterior:

# docker volume ls

DRIVER VOLUME NAME


local 5a7c85ba5d84ed6c823d66a5c663777d2557f1dab6975c0adbd63145ff78391f
Todo lo que el contenedor escriba o modifique en la carpeta local /volume01, se estará escribiendo en el siguiente directorio del host.

/var/lib/docker/volumes/5a7c85ba5d84ed6c823d66a5c663777d2557f1dab6975c0adbd63145ff78391f/_data

Acceso a directorios y archivos del host


También tenemos la opción de mapear un directorio o archivo concreto del host (sin crear un volumen gestionado por docker) de forma que sea
compartido entre el host y el contenedor. También utilizamos la opción -v pero en este caso en el formato dir-host:dir-contenedor, siendo dir-contenedor
una ruta absoluta dentro del contenedor y dir-host puede ser una ruta absoluta o relativa del host donde se ejecuta el contenedor

# docker run --name ubuntu02 -it -v /mnt/volume02:/volume02 ubuntu:17.04 /bin/bash

#docker inspect ubuntu02


...
"Mounts": [
{
"Type": "bind",
"Source": "/mnt/volume01",
"Destination": "/volume01",
"Mode": "",
"RW": true,
"Propagation": ""
}
],

De igual forma que hemos indicado un directorio tanto en el host como en el contenedor podemos indicar un archivo en concreto.

Por defecto, cuando se monta un volumen en el contendor, se hace con permisos de lectura y escritura, pero podemos indicar que sea de sólo lectura

# docker run --name ubuntu03 -it -v /mnt/volume03:/volume03:ro ubuntu:17.04 /bin/bash

Almacenamiento persistente para nuestro sitio web


Retomamos nuestro ejemplo de crear un sitio web y volvemos a la situación en la que teníamos creados ya los dos contenedores de wordpress y mariadb

# docker network create --driver bridge FrontEnd


# docker network create --driver bridge BackEnd

# docker run --name mariadb01 -d -e MYSQL_ROOT_PASSWORD=root_P@ssw0rd -e MYSQL_DATABASE=wordpress01 -e


MYSQL_USER=user_wordpress01 -e MYSQL_PASSWORD=user_P@ssw0rd --network=BackEnd mariadb:10.0.29

# docker run --name wordpress01 -d -p 80:80 --network=FrontEnd wordpress:4.7.2-apache


# docker network connect BackEnd wordpress01

Podemos ver que al crear los contenedores, tenemos dos volúmenes creados:

# docker volume ls
DRIVER VOLUME NAME
local aeaaf3890d571f4189d0abe08036101db31c5cf1aeb17b2c8f64522b7cbe7721
local f560914d5536100c648a2cbaca874c47e3be2bd2f583a01bd309013188cdc76e

Si inspeccionamos los contenedores podemos ver los volúmenes asociados a cada uno de ellos:

# docker inspect wordpress01

"Mounts": [
{
"Type": "volume",
"Name": "aeaaf3890d571f4189d0abe08036101db31c5cf1aeb17b2c8f64522b7cbe7721",
"Source": "/var/lib/docker/volumes/aeaaf3890d571f4189d0abe08036101db31c5cf1aeb17b2c8f64522b7cbe7721/_data",
"Destination": "/var/www/html",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],

# docker inspect mariadb01

"Mounts": [
{
"Type": "volume",
"Name": "f560914d5536100c648a2cbaca874c47e3be2bd2f583a01bd309013188cdc76e",
"Source": "/var/lib/docker/volumes/f560914d5536100c648a2cbaca874c47e3be2bd2f583a01bd309013188cdc76e/_data",
"Destination": "/var/lib/mysql",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],

Estos volúmenes se han creado, aunque nosotros no se lo hemos indicado, porque están así definidos en la imagen de la que partimos:

La imagen de wordpress tiene definido el volumen


VOLUME /var/www/html
La imagen de mariadb tiene definido el volumen
VOLUME /var/lib/mysql

De esta forma tenemos ya creados los volúmenes persistentes, pero no los estamos gestionando nosotros, por lo que vamos a modificar la forma en la que
creamos los contenedores para ajustarlo a nuestras necesidades y tener un mayor control de los datos, ya que si borramos estos contenedores y volvemos
a crear unos nuevos (por un cambio en la configuración o por un actualización) se crearán nuevos volúmenes y los antiguos se quedarán sin utilizar.

Podemos crear un volumen y asignarle un nombre al mismo para poder identificarlo

# docker volume create VL_mariadb01

# docker volume create VL_wordpress01

y volvemos a crear los contenedores, pero ahora con la opción -v

# docker run --name mariadb01 -d -v VL_mariadb01:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root_P@ssw0rd -e


MYSQL_DATABASE=wordpress01 -e MYSQL_USER=user_wordpress01 -e MYSQL_PASSWORD=user_P@ssw0rd --network=BackEnd mariadb:10.0.29

# docker run --name wordpress01 -d -v VL_wordpress01:/var/www/html -p 80:80 --network=FrontEnd wordpress:4.7.2-apache

Podemos comprobar el contenido de los volúmenes:

# docker volume inspect VL_wordpress01

[
{
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/VL_wordpress01/_data",
"Name": "VL_wordpress01",
"Options": {},
"Scope": "local"
}
]
# ls /var/lib/docker/volumes/VL_wordpress01/_data
[Link] [Link] [Link] [Link] [Link] [Link] [Link]
[Link] wp-admin [Link] wp-includes [Link] [Link]
[Link] [Link] wp-content [Link] [Link] [Link]

Vemos que el directorio «/var/lib/docker/volumes/VL_wordpress01/_data» contiene los archivos y directorios de la aplicación wordpress

El esquema del sitio web en este punto es el siguiente:

Vamos a trabajar con servicios


Si habéis llegado hasta aquí y habéis seguido todo el proceso, habréis comprobado que es bastante tedioso el crear un contenedor, pararlo, borrarlo, crear
una nueva versión …

Esto es así porque estamos trabajando con cada uno de los contenedores, redes y volúmenes de forma individual, sin tener la visión en conjunto de lo que
es la aplicación (que está formada por todos los componentes) Para facilitar esta gestión tenemos la herramienta Docker-Compose, que con un único
comando nos va a permitir gestionar todos los contenedores (servicios) que forman parte de la aplicación.

Características de Docker-Compose
Permite aislar en un host múltiples proyectos utilizando un nombre de proyecto. Por defecto el nombre del proyecto es el nombre del
directorio de trabajo aunque se puede modificar.
Mantiene la información de los volúmenes de datos
Sólo crea los contenedores que han tenido cambios desde la última ejecución

Instalación de Docker-Compose
Docker-Compose no se incluye en los paquetes por defecto de Docker, por lo que vamos a descargar el binario directamente de la página de Git-Hub

Vemos la última versión disponible: [Link]

En estos momentos, vemos que la última versión es la 1.13.0, por lo que ejecutamos

curl -L [Link] -s`-`uname -m` >


/usr/local/bin/docker-compose

Asignamos permisos de ejecución al archivo descargado

chmod +x /usr/local/bin/docker-compose

Comprobamos que tenemos Docker-Compose disponible:

# docker-compose version
docker-compose version 1.13.0, build 1719ceb
docker-py version: 2.2.1
CPython version: 2.7.13
OpenSSL version: OpenSSL 1.0.1t 3 May 2016

Creación del archivo [Link]


El primer paso es crear una carpeta en nuestro directorio de trabajo con el nombre que le vamos a dar a nuestro proyecto, por ejemplo SitioWeb01

mkdir -p SitioWeb01

Accedemos al directorio y creamos el archivo [Link] (también se acepta la extensión .yaml)

El archivo [Link] utiliza lenguaje YAML para definir los servicios (contenedores), redes y volúmenes.

Vamos a comenzar con un ejemplo simple, que va a replicar el sitio web que habíamos creado:

version: '3'

services:
mariadb01:
image: mariadb:10.0.29
volumes:
- VL_mariadb01:/var/lib/mysql
restart: always
environment:
- MYSQL_ROOT_PASSWORD=root_P@ssw0rd
- MYSQL_DATABASE=wordpress01
- MYSQL_USER=user_wordpress01
- MYSQL_PASSWORD=user_P@ssw0rd
networks:
- BackEnd

wordpress01:
depends_on:
- mariadb01
image: wordpress:4.7.2-apache
ports:
- "80:80"
restart: always
volumes:
- VL_wordpress01:/var/www/html
environment:
- WORDPRESS_DB_HOST=mariadb01
- WORDPRESS_DB_NAME=wordpress01
- WORDPRESS_DB_USER=user_wordpress01
- WORDPRESS_DB_PASSWORD=user_P@ssw0rd
networks:
- FrontEnd
- BackEnd
volumes:
VL_mariadb01:
VL_wordpress01:

networks:
FrontEnd:
driver: bridge
BackEnd:
driver: bridge

Una vez creado el archivo [Link] con el contenido indicado, ejecutamos el comando:

docker-compose up

También podemos ejecutarlo con la opción -d, para que se ejecute en segundo plano y sin mostrarnos los logs de ejecución de los contenedores
# docker-compose up -d
Creating network "sitioweb01_FrontEnd" with driver "bridge"
Creating network "sitioweb01_BackEnd" with driver "bridge"
Creating sitioweb01_mariadb01_1 ...
Creating sitioweb01_mariadb01_1 ... done
Creating sitioweb01_wordpress01_1 ...
Creating sitioweb01_wordpress01_1 ... done

Tras levantar los servicios comprobamos que elementos se han creado:

Redes
sitioweb01_BackEnd
sitioweb01_FrontEnd

# docker network ls
NETWORK ID NAME DRIVER SCOPE
bb9e7f4cc904 bridge bridge local
98d7b2d36ca4 host host local
77b2fad06ea6 none null local
67abdb08ebe8 sitioweb01_BackEnd bridge local
63b6b2ed7641 sitioweb01_FrontEnd bridge local

Volúmenes
sitioweb01_VL_mariadb01
sitioweb01_VL_wordpress01

# docker volume ls
DRIVER VOLUME NAME
local sitioweb01_VL_mariadb01
local sitioweb01_VL_wordpress01

Contenedores:
sitioeweb01_wordpress01_1
sitioeweb01_mariadb01_1

# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS
NAMES
545063308b87 wordpress:4.7.2-apache "docker-entrypoint..." 7 minutes ago Up 4 minutes [Link]:80-
>80/tcp sitioweb01_wordpress01_1
27fd925ad553 mariadb:10.0.29 "docker-entrypoint..." 8 minutes ago Up 4 minutes 3306/tcp
sitioweb01_mariadb01_1

Y podemos comprobar que el sitio web sigue funcionando.

Si queremos reiniciar los servicios

# docker-compose restart
Restarting sitioweb01_wordpress01_1 ... done
Restarting sitioweb01_mariadb01_1 ... done

Si queremos parar la aplicación:

# docker-compose down
Stopping sitioweb01_wordpress01_1 ... done
Stopping sitioweb01_mariadb01_1 ... done
Removing sitioweb01_wordpress01_1 ... done
Removing sitioweb01_mariadb01_1 ... done
Removing network sitioweb01_FrontEnd
Removing network sitioweb01_BackEnd
Si queremos borrar los servicios parados

# docker-compose rm
Going to remove sitioweb01_wordpress01_1, sitioweb01_mariadb01_1
Are you sure? [yN] y
Removing sitioweb01_wordpress01_1 ... done
Removing sitioweb01_mariadb01_1 ... done

De momento llegamos hasta aquí. Más adelante continuaremos añadiendo contenedores que nos proporcionen una monitorización de la infraestructura
(Grafana + Influxdb + Telegraf) un publicador (Nginx) y certificados (Let’s encypt)

También podría gustarte