CentOS 8, Nanode 1GB: 1 CPU, 25GB Storage, 1GB RAM), nos exige profundizar más en cómo crear estos recursos.
Si has realizado todo el tutorial anterior comprobarás que la imagen resultado debería tener un tamaño aproximado de 1.13 Gb., excesivo para un arranque inmediato y un espacio muy limitado.
1.- Distribución Linux
Si observar en docker hub hay múltiples versiones de cada software, alguna etiquetada con la palabra 'alpine'. Utilizan como base la distribución Alpine Linux, una distribución mínima, con los recursos necesarios e imprescindibles, que permite crear imágenes muy ajustadas. La ventaja : descarga de imágenes muy rápidas y arranques instantáneos.
Alpine Linux tiene su propio gestor de paquetes APK y su propio repositorio de paquetes.
2.- Qué es Dockerfile
Podemos usar los contenedores disponibles en docker hub, donde existen múltiples aplicaciones ya enlatadas. Pero podemos definir nuestras propias imágenes atendiendo a nuestras necesidades. Un fichero Dockerfile es la receta para construir una imagen de forma personalizada.
Previamente veamos algunas instrucciones necesarias para elaborar un Dockerfile :
- FROM: imagen base sobre la que crearemos la imagen que construirá el Dockerfile.
- MAINTAINER (deprecated): informa sobre el creador de la imagen. Cambiar esta instrucción por
LABEL maintainer="email_prueba@home.org"
- ENV HOME: establece el directorio HOME que usarán los comandos RUN.
- RUN: permite ejecutar una instrucción en el contenedor, por ejemplo, para instalar algún paquete mediante el gestor de paquetes (apt-get, yum, rpm, apk…). Existen dos formatos :
RUN
(shell form, se ejecutará en un entorno que será, por defecto/bin/sh -c
en Linux ocmd /S /C
en Windows)RUN ["executable", "param1", "param2"]
(ejecutar form)
- ADD: permite añadir un archivo al contenedor, en muchas ocasiones se utiliza para proporcionar la configuración de los servicios (ssh, mysql, …).
- VOLUME: establece puntos de montaje que al usar el contenedor se pueden proporcionar, los volúmenes son la forma de externalizar un determinado directorio y proporcionar persistencia (una imagen es de SOLO LECTURA y no almacena datos entre una ejecución y la siguiente).
- EXPOSE: indica los puertos TCP/IP por los que se pueden acceder a los servicios del contenedor, los típicos son 22 (SSH), 80 (HTTP), 443 (HTTPS), 3306 (MySQL)...
EXPOSE
[ / ...] - CDM: establece el comando del proceso de inicio que se usará si no se indica uno al iniciar un contenedor con la imagen. Solo puede existir una instrucción CMD por contenedor, si hubiera mas de una, solo se ejecutará la última. Existen tres formatos :
CMD ["executable","param1","param2"]
(es el formato preferido)CMD ["param1","param2"]
(como default parameters a ENTRYPOINT)CMD command param1 param2
(entorno form)
ADD
solo soportado en contenedores que se ejecutan sobre linux
ADD [--chown=
: ] ... ADD [--chown=
(cuando el path contiene espacios): ] [" ",... " "]
COPY solo soportado en contenedores que se ejecutan sobre linux
COPY [--chown=
: ] ... COPY [--chown=
(cuando el path contiene espacios): ] [" ",... " "]
ENV o ARG si existe la misma variable con ARG y ENV, ENV sobreescribe ARG
ENV
ENV
= ...
EXPOSE
FROM
LABEL
incorpora metadatos a una imagen: LABEL
= = = ... STOPSIGNAL
USER
VOLUME
- WORKDIR establece el directorio de trabajo para cualquier instrucción RUN, CMD, ENTRYPOINT, COPY y ADD que la siga en el Dockerfile, tambien crea el directorio si no existe.
ENTRYPOINT permite configurar un contenedor que arrancará como ejecutable
ENTRYPOINT ["executable", "param1", "param2"]
( ejecución preferida)ENTRYPOINT command param1 param2
# comment
Argumentos predefinidos, no debe usar ARG :
HTTP_PROXY
http_proxy
HTTPS_PROXY
https_proxy
FTP_PROXY
ftp_proxy
NO_PROXY
no_proxy
Podrás obtener una información más pormenorizada en https://docs.docker.com/engine/reference/builder/
Y ahora un fichero DOCKERFILE, dedique un tiempo para identificar cada acción :
# Alpine Linux - Python 3 and Apache/MOD_WSGI (NOT mod_wsgi-express)
# =============================================================================
# alpine linux has very up-to-date python3 and it can be used as the base.
# =============================================================================
FROM python:3.6-alpine
LABEL maintainer="PYMETRICK
# Report short python version, e.g. "3.6" for mod_wsgi build
# =============================================================================
ENV PYTHON_VERSION=3.6
# HTTP Proxy Settings - it is not necessary for me
#ARG http_proxy="http://one.proxy.example.com:8080"
#ARG https_proxy="http://one.proxy.example.com:8080"
#ARG HTTP_PROXY="http://one.proxy.example.com:8080"
#ARG HTTPS_PROXY="http://one.proxy.example.com:8080"
USER root
# Add apache2 packages (alpine package manager apk)
# =============================================================================
RUN apk --update --no-cache add apache2 apache2-dev \
wget ca-certificates make gcc musl-dev
# Add mod_wsgi shared library compiled to this python3
# =============================================================================
ENV MOD_WSGI_VERSION=4.6.8
ENV MOD_WSGI_SRC_URL="https://github.com/GrahamDumpleton/mod_wsgi/archive/${MOD_WSGI_VERSION}.tar.gz"
RUN wget -O /usr/src/mod_wsgi.tar.gz "${MOD_WSGI_SRC_URL}" && \
tar -zxvf /usr/src/mod_wsgi.tar.gz -C /usr/src && \
rm /usr/src/mod_wsgi.tar.gz
WORKDIR /usr/src/mod_wsgi-${MOD_WSGI_VERSION}
ENV CFLAGS="-I/usr/local/include/python${PYTHON_VERSION}m/ -L/usr/local/lib/"
RUN ln -s /usr/lib/libpython${PYTHON_VERSION}m.so /usr/lib/libpython${PYTHON_VERSION}.so && \
./configure \
--with-python=/usr/local/bin/python${PYTHON_VERSION} \
--with-apxs=/usr/bin/apxs && \
make && make install clean
RUN rm -rf /usr/src/mod_wsgi-${MOD_WSGI_VERSION}
# Set Apache2 Configurations
# =============================================================================
# Create PID file directory for /run/apache2/httpd.pid
RUN mkdir -p /run/apache2
# Set Servername to something.
RUN sed -i -r 's@#Servername.*@Servername wsgi@i' /etc/apache2/httpd.conf
# Direct access and error logs to stderr for Docker.
RUN sed -i -r 's@(CustomLog .*)@\1\nTransferLog /dev/stderr@i' /etc/apache2/httpd.conf
RUN sed -i -r 's@Errorlog .*@Errorlog /dev/stderr@i' /etc/apache2/httpd.conf
# Direct *.wsgi scripts to mod_wsgi
RUN echo -e "\n\n\
LoadModule wsgi_module modules/mod_wsgi.so\n\
WSGIPythonPath /usr/lib/python${PYTHON_VERSION}\n\
Alias / /home/apache/\n\
Options ExecCGI FollowSymLinks\n\
AllowOverride All\n\
Require all granted\n\
AddHandler wsgi-script .wsgi\n\
# "apache" runs a sample "hello world" WSGI script.
# =============================================================================
WORKDIR /home/apache
COPY ./hello.wsgi ./hello.wsgi
# Start Apache2 service with mod_wsgi
EXPOSE 80
EXPOSE 443
CMD ["httpd", "-D", "FOREGROUND", "-e", "info"]
# =============================================================================
# Clean up the package index.
# =============================================================================
RUN rm -rf /var/cache/apk/*
Partimos de la distribución Alpine Linux FROM python:3.6-alpine
Informa del mantenedor MAINTAINER pymetrick
Ejecutar una acción de instalación RUN apk --update --no-cache add apache2 apache2-dev \
Copiamos un fichero desde el host al contenedor
COPY ./hello.wsgi ./hello.wsgi
COPY ./hello.wsgi ./hello.wsgi
El contenido del fichero HELLO.WSGI es solo una prueba para comprobar que todo funciona correctamente una vez instalado y su código es el siguiente :
#! /usr/bin/env python
import logging
import logging.handlers
logger = logging.getLogger('hello_wsgi_logger')
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter("""
{
"loggerName": "%(name)s",
"asciTime": "%(asctime)s",
"pathName": "%(pathname)s",
"logRecordCreationTime": "%(created)f",
"functionName": "%(funcName)s",
"levelNo": "%(levelno)s",
"lineNo": "%(lineno)d",
"time": "%(msecs)d",
"levelName": "%(levelname)s",
"message":"%(message)s"
}
""")
handler.formatter = formatter
logger.addHandler(handler)
def application(environ, start_response):
status = '200 OK'
output = [b'Hello Dockerized World!', b'\n' * 2]
request_params = [
'{}: {}'.format(key, value).encode('ascii')
for key, value in sorted(environ.items())
if key.startswith('wsgi.')
]
output.extend(request_params)
output = b'\n'.join(output)
response_headers = [('Content-type', 'text/plain'),
('Content-Length', str(len(output)))]
start_response(status, response_headers)
logger.info("Test Message")
return [output]
import logging
import logging.handlers
logger = logging.getLogger('hello_wsgi_logger')
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter("""
{
"loggerName": "%(name)s",
"asciTime": "%(asctime)s",
"pathName": "%(pathname)s",
"logRecordCreationTime": "%(created)f",
"functionName": "%(funcName)s",
"levelNo": "%(levelno)s",
"lineNo": "%(lineno)d",
"time": "%(msecs)d",
"levelName": "%(levelname)s",
"message":"%(message)s"
}
""")
handler.formatter = formatter
logger.addHandler(handler)
def application(environ, start_response):
status = '200 OK'
output = [b'Hello Dockerized World!', b'\n' * 2]
request_params = [
'{}: {}'.format(key, value).encode('ascii')
for key, value in sorted(environ.items())
if key.startswith('wsgi.')
]
output.extend(request_params)
output = b'\n'.join(output)
response_headers = [('Content-type', 'text/plain'),
('Content-Length', str(len(output)))]
start_response(status, response_headers)
logger.info("Test Message")
return [output]
3.- Fichero COMPOSE
Es un fichero YAML donde definimos los servicios, redes y volúmenes usados. Existen varias versiones de COMPOSE que debemos indicar en nuestro fichero al inicio, según la versión del motor Docker admita.
Compose file format | Docker Engine release |
---|---|
3.7 | 18.06.0+ |
3.6 | 18.02.0+ |
3.5 | 17.12.0+ |
3.4 | 17.09.0+ |
3.3 | 17.06.0+ |
3.2 | 17.04.0+ |
3.1 | 1.13.1+ |
3.0 | 1.13.0+ |
2.4 | 17.12.0+ |
2.3 | 17.06.0+ |
2.2 | 1.13.0+ |
2.1 | 1.12.0+ |
2.0 | 1.10.0+ |
1.0 | 1.9.1.+ |
Mi fichero DOCKER-COMPOSE.YML :
version: '3.0'
services:
py36:
image: pymetrick/alpine-http-python-mod_wsgi:3.6
build:
context: .
args:
- pyversion=3.6
latest:
image: pymetrick/alpine-http-python-mod_wsgi:latest
build: .
services:
py36:
image: pymetrick/alpine-http-python-mod_wsgi:3.6
build:
context: .
args:
- pyversion=3.6
latest:
image: pymetrick/alpine-http-python-mod_wsgi:latest
build: .
Para construir la imagen, puedes introducir toda la información necesaria en un script que posteriormente ejecutamos ( build_dockerfile.sh ):
#! /bin/bash
#
# Docker Image: Alpine Linux - Python 3 and Apache/MOD_WSGI
# =============================================================================
# Build docker image
# =============================================================================
NAMESPACE="pymetrick"
IMAGE_NAME="alpine-http-python-mod_wsgi"
TAG="3.6"
FULL_IMAGE_NAME="${NAMESPACE}/${IMAGE_NAME}:${TAG}"
docker build -t $FULL_IMAGE_NAME ./ \
--build-arg http_proxy=$http_proxy \
--build-arg https_proxy=$https_proxy \
-f ./Dockerfile
Puede eliminar las siguientes lineas si no necesita parametrizar un proxy
--build-arg http_proxy=$http_proxy \
--build-arg https_proxy=$https_proxy \
Ya solo queda ejecutar el script ./build_dockerfile.sh y obtenemos como resultado correcto :
Successfully built bb2a85d0f6ea
Successfully tagged pymetrick/alpine-http-python-mod_wsgi:3.6
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
pymetrick/alpine-http-python-mod_wsgi 3.6 bb2a85d0f6ea 2 minutes ago 304MB
python 3.6-alpine 6ddaac33408f 12 days ago 95MB
Comprobamos con sumo agrado que solo hemos consumido 304 Mb.
4.- TEST
Iniciamos el contenedor creado :
$ docker run --rm -ti --name http_one -p 8080:80 -p 8443:443 pymetrick/alpine-http-python-mod_wsgi:36
Nota : puede salir del entorno, sin parar el proceso, con CTRL+Q+P
Comprobar procesos en ejecución dentro del contenedor :
$ docker top http_one
Comprobar fichero que procesará los datos que devolvemos con el servidor web :
$ docker exec http_one ls -l /home/apache # sus datos de configuración
$ docker exec http_one cat /home/apache/hello.wsgi # su contenido
Y la prueba definitiva desde cualquier navegador :
http://x.x.x.x:8080/hello.wsgi
y su resultado :
Hello Dockerized World!
wsgi.errors: <_io .textiowrapper="" encoding="utf-8" name="<wsgi.errors>">
wsgi.file_wrapper:
wsgi.input:
wsgi.input_terminated: True
wsgi.multiprocess: True
wsgi.multithread: False
wsgi.run_once: False
wsgi.url_scheme: http
wsgi.version: (1, 0)
5.- Eliminar IMAGENES, VOLUMENES, REDES Y CONTENEDORES
Es fácil acumular una cantidad excesiva de imágenes no utilizadas, contenedores y volúmenes de datos que consumen espacio en disco.
Los comandos que son útiles para liberar espacio en disco y mantener su sistema organizado al eliminar imágenes, contenedores y volúmenes de Docker no utilizados, se muestran a continuación:
- limpiará todos los recursos (imágenes, contenedores, volúmenes y redes)
$ docker system prune
para eliminar adicionalmente los contenedores detenidos y todas las imágenes no utilizadas
$ docker system prune -a
eliminar una o más imágenes
$ docker rmi Image Image
- detener todos los contenedores
$ docker stop $(docker ps -a -q)
- borrar todos los contenedores
$ docker rm $(docker ps -a -q)
borrar todas las imagenes $ docker rmi $(docker images -q)