.. _docker-ejemplos: Ejemplos ******** Dedicaremos este último epígrafe a resolver algunos problemas usando contenedores de :program:`Docker`. dnsmasq ======= El propósito es ejecutar un contenedor con :ref:`dnsmasq ` capaz de proporcionar resolución de nombres y configuración dinámica de direcciones a los equipos de la red. Esto último nos obliga a que el contenedor comporta la red con el anfitrión. Aunque no usaremos :ref:`docker-compose `, puesto que sólo necesitamos un contenedor, Debemos preparar un directorio de trabajo con el siguiente contenido: .. code-block:: none + workdir +-- dnsmasq.d | +-- dns.conf | +-- dhcp.conf +-- Dockerfile El subdirectorio :file:`dnsmasq.d` contendrá la configuración que deseamos para nuestro :ref:`dnsmasq `. Por ejemplo: .. code-block:: bash # dns.conf no-resolv server=1.0.0.1 server=1.1.1. y: .. code-block:: bash # dhcp.conf log-dhcp dhcp-range=192.168.255.64,192.168.255.127,5m domain=internal.vm,192.168.255.0/24 Y el :file:`Dockerfile` la forma en construir una imagen con :ref:`dnsmasq ` a partir de Alpine_: .. code-block:: docker FROM alpine RUN apk update && apk add dnsmasq; \ echo 'conf-dir=/etc/dnsmasq.d/,*.conf' > /etc/dnsmasq.conf EXPOSE 53/UDP 67/UDP 69/UDP CMD ["dnsmasq", "--no-daemon", "-z"] Con ello, podemos construir la imagen:: # docker build -t dnsmasq:alpine . Y ejecutar el contenedor basado en esa imagen:: # docker run --rm --restart=always --network host -v /workdir/dnsmasq.d:/etc/dnsmasq.d dnsmasq:alpine Obsérvese un aspecto importante en esta ejecución: la opción :kbd:`--restart` que introduce cuál será la :dfn:`política de ejecución` del contenedor, esto es, qué es lo que ocurre cuando el contenedor para. Hay cuatro posilibilidades: .. table:: :class: docker-restart-policy +----------------+------------------------------------------------------+ | Política | Descripción | +================+======================================================+ | no | Es el valor por defecto. El contenedor al parar, no | | | reinicia. | +----------------+------------------------------------------------------+ | on-failure | El contenedor sólo se reinicia si la aplicación | | | acaba con un error. | +----------------+------------------------------------------------------+ | always | Reinicia el contenedor siempre, pero si se detiene | | | manualmente (con :kbd:`docker stop`), sólo reinicia | | | si se reinicia el demonio o si se reinicia | | | manualmente. | +----------------+------------------------------------------------------+ | unless-stopped | Como el caso anterior, pero no reinicia cuando el | | | demonio se reinicia. | +----------------+------------------------------------------------------+ Tenga presente que el demonio se inicia, cuando el sistema anfitrión arraca. Por tanto, si nuestra política es :kbd:`always`, el contenedor arrancará automáticamente, al arrancar el sistema anfitrión. Precisamente ese comportanamiento es el preferible en un contenedor que hemos creado para dar servicio |DHCP| a la red. PHP-FPM con socket ================== Planteamos el objetivo de utilizar la `imagen de PHP `_ basada en Alpine_, pero hacer accesible el servicio de *FastCGI* a través de un *socket* UNIX, en vez de un puerto |TCP|. Para ello tomaremos: * La imagen ya comentada, cuyo contenedor compartirá dos volúmenes: + Uno para albergar el *socket*. + Otro que contenga la aplicación. * Una segunda `imagen de nginx `_ cuyo contenedor compartirá los mismos dos volúmenes anteriores, más un tercero que contenga su configuración modular. El directorio de trabajo será el siguiente: .. code-block:: none + workdir +-- docker-compose.yaml +-- nginx/ | +-- Dockerfile | +-- archives/ | +-- conf.d | +-- php.conf | +-- default.conf +-- php-fpm/ | +-- Dockerfile | +-- archives/ | +--etc/ | +-- php-fpm.d/ | | +-- zz-docker.conf | +-- php/ | +-- conf.d/ | +-- uploads.ini | +-- [.. otros ficheros ..] +-- webapp/ +-- [.. ficheros de la aplicación ..] .. rubric:: Preparación de php-fpm Básicamente, consiste en modificar la configuración de |PHP| para adaptarla a nuestras necesidades. Un cambio consiste en sustituir el fichero :file:`zz-docker.conf` para lograr la comunicación a través de un *socket*. Su contenido es el siguiente: .. code-block:: ini [global] daemonize = no [www] listen = /var/run/php-fpm/php-fpm.sock listen.owner = www-data listen.group = www-data Además, dentro de :file:`etc/php/conf.d`, podemos crear ficheros INI que alteren la configuración general de |PHP|. Por ejemplo, uno que aumente el tamaño de los ficheros subidos al servidor: .. code-block:: ini # conf.d/uploads.ini post_max_size = 20M upload_max_filesize = 20M Por su parte, el fichero :file:`Dockerfile`, por otra parte, debe ser: .. code-block:: docker ARG version FROM php:${version}-fpm-alpine COPY ./archives /usr/local/ .. rubric:: Preparación de nginx Nuestro problema es que necesitamos que :ref:`nginx ` lo ejecute el mismo usuario\ [#]_ que ejecuta :program:`php-fpm` en el otro contenedor. :program:`php-fpm` es ejecutado por el usuario *www-data* con |UID| 82 y cuyo grupo principal tiene un |GID| y nombre idénticos. :ref:`nginx `, en cambio, lo ejecuta el usuario *nginx* con un |UID| que no es 82. Solucionaremos el inconveniente creando en este contenedor el usuario *www-data* con |UID| 82 y haciendo que ejecute el servidor web. Los ficheros de configuración del servidor son: .. code-block:: nginx # conf.d/php.conf upstream php { server unix:/var/run/php-fpm/php-fpm.sock; } y: .. code-block:: nginx # conf.d/default.conf server { listen 80; try_files $uri $uri/ =404; index index.php; root /srv/www; location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; include fastcgi.conf; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_pass php; } } y el :file:`Dockerfile`: .. code-block:: docker FROM nginx:alpine RUN adduser -Du82 -G www-data www-data; \ sed -ri '/^user/s:nginx:www-data:' /etc/nginx/nginx.conf .. rubric:: docker-compose Por último, el fichero para :program:`docker-compose` que levante estos dos contenedores puede ser el siguiente: .. code-block:: yaml version: "3" services: php: image: php:fpm-alpine-socket build: ./php-fpm volumes: - socket:/var/run/php-fpm/ - ./webapp:/srv/www restart: always nginx: image: nginx:alpine-app build: ./nginx ports: - "80:80" depends_on: - php volumes: - socket:/var/run/php-fpm/ - ./webapp:/srv/www - ./nginx/conf.d:/etc/nginx/conf.d restart: always volumes: socket: .. rubric:: Aplicación En el directorio :file:`webapp` debe colocarse la aplicación de deseemos ejecutar. Dado que únicamente queremos hacer una prueba nos basta con:: # echo '' > webapp/index.php Wordpress ========= Nuestra intención ahora es instalar un Wordpress_, para lo cual utilizaremos la siguiente infraestructura: .. image:: files/wordpress.png es decir, tres contenedores diferentes cada uno de los cuales levanta los tres servicios en que se puede descomponer la aplicación: la base de datos, la aplicación |PHP| (con el intérprete incluido) y un servidor web que sea el que ofrezca la aplicación. Además, es necesario almacenar los ficheros de la base de la datos y los datos de la aplicación, por lo que se requerirán dos volúmenes de datos. .. note:: En este caso, y a diferencia del anterior ejercicio, la aplicación y |PHP| se encuentran en el mismo contenedor y, además, no se expone mediante *socket*, sino mediante |TCP|. No es muy complicado adaptar esta solución utilizando las estrategias del ejercicio anterior. Los tres contenedores que utilizaremos son: - La `imagen oficial de mariaDB `_, que se caracteriza porque al generar un contenedor, crea los ficheros necesarios del gestor de bases de datos, según los valores de las variables de entorno que se proporcionen (véase el :file:`docker-compose.yaml` más adelante). Esta característica nos permite preparar la base de datos para *wordpress* y el usuario que la maneje. - Una `imagen oficial de wordpress que incluya PHP-FPM `_. Esta imagen contiene el |PHP| necesario y la versión de Wordpress_ en el momento de su generación, la cual acaba dejando disponible en :file:`/var/www/html`. Esta imagen también usa variables de entorno para conocer dónde se encuentra la base de datos y con qué usuarios acceder a ella. - Una `imagen mínima de nginx `_ que necesitará acceso al directorio :file:`/var/www/html` del contenedor anterior y alterar su configuración predefinida para ser capaz de servir la aplicación. Dado que actúan en comandita tres contenedores, lo más juicioso es utilizar :ref:`docker-compose `, el cual requerirá el siguiente :file:`docker-compose,yaml`: .. code-block:: yaml version: "3" services: mysql: image: mariadb volumes: - wpmysql:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: ${ROOT_PASS:-toor} MYSQL_DATABASE: ${WP_DB:-wordpress} MYSQL_USER: ${WP_USER:-wp} MYSQL_PASSWORD: ${WP_PASS:-wp} restart: always wordpress: image: wordpress:php7.4-fpm-alpine depends_on: - mysql volumes: - wpapp:/var/www/html environment: WORDPRESS_DB_HOST: mysql MYSQL_ROOT_PASSWORD: ${ROOT_PASS:-toor} WORDPRESS_DB_NAME: ${WP_DB:-wordpress} WORDPRESS_DB_USER: ${WP_USER:-wp} WORDPRESS_DB_PASSWORD: ${WP_PASS:-wp} WORDPRESS_TABLE_PREFIX: ${WP_PREFIX:-wp_} restart: always nginx: image: nginx:alpine ports: - "80:80" volumes: - ./blogs.conf:/etc/nginx/conf.d/default.conf - wpapp:/var/www/html depends_on: - wordpress restart: always volumes: wpmysql: wpapp: Si se observa el fichero, se verá que es necesario suministrar el fichero :file:`blogs.conf` con la configuración para que :ref:`nginx ` sea capaz de ejecutar la aplicación: .. code-block:: none + workdir +-- blogs.conf +-- docker-compose.yaml Su contenido puede ser este: .. code-block:: nginx server { listen 80; root /var/www/html; index index.php; location / { try_files $uri $uri/ /index.php$is_args$args; } location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass wordpress:9000; fastcgi_index index.php; include fastcgi.conf; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_NAME $fastcgi_script_name; } } .. rubric:: Notas al pie .. [#] En realidad, un usuario con el mismo |UID|. .. _Wordpress: https://wordpress.org/ .. _Alpine: https://alpinelinux.org/ .. |PHP| replace:: :abbr:`PHP (PHP Hypertext Preprocessor)` .. |TCP| replace:: :abbr:`TCP (Transmission Control Protocol)` .. |UID| replace:: :abbr:`UID (User IDentifier)` .. |GID| replace:: :abbr:`GID (Group IDentifier)`