Translate

domingo, 3 de noviembre de 2019

Crear un contenedor DOCKER (2) de forma eficiente

Desplegar contenedores DOCKER en servidores CLOUD, con recursos limitados (
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 :
  1. RUN (shell form, se ejecutará en un entorno que será, por defecto /bin/sh -c en Linux o cmd /S /C en Windows)
  2. 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 :
  1. CMD ["executable","param1","param2"] (es el formato preferido)
  2. CMD ["param1","param2"] (como default parameters a ENTRYPOINT)
  3. CMD command param1 param2 (entorno form)
Las variables de entorno están soportadas con las instrucciones :
  • 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
Y los comentarios con :  # 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\
\n\
    Options ExecCGI FollowSymLinks\n\
    AllowOverride All\n\
    Require all granted\n\
    AddHandler wsgi-script .wsgi\n\
" >> /etc/apache2/httpd.conf

# "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

Crear variables con ENV   ENV PYTHON_VERSION=3.6  que podremos utilizar en otras acciones como ${PYTHON_VERSION}

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

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]

 

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: .

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)
          
Y hasta aquí todo lo que necesitará para profundizar en el mundo de los contenedores.  La próxima entrega hablamos de los ajustes necesarios para un sistema en producción, no te lo pierdas.

lunes, 28 de octubre de 2019

Crear un contenedor DOCKER (1) con APACHE+PYTHON36+MOD_WSGI

Cuando desarrollas aplicaciones, es muy importante complementar los desarrollos con test de pruebas. A menudo, ante las quejas de un usuario sobre el funcionamiento de una aplicación, podemos oir al soporte responder: "pues a mí me funciona correctamente".

Las prisas por entregar un proyecto NO permiten realizar extensas pruebas en distintas plataformas o versiones de S.O. y tampoco es posible plataformar un servidor o equipo en producción siempre que lo necesitemos.

Para proyectos como :
  • Sitios web
  • Servicios
  • Procesos
  • Aplicaciones

Los contenedores son una nueva forma de implementar aplicaciones, donde es posible ejecutar servicios y procesos en un entorno protegido. Los principales beneficios de los contenedores, si se comparan con las máquinas virtuales clásicas, son los siguientes: la implementación es mucho más rápida, la administración es mínima, no es necesario aplicar parches.

Mi objetivo es crear un CONTENEDOR que contenga un S.O. ( CENTOS 7 ) + APACHE + PYTHON3.6 + MOD_WSGI, y todo ello con el fín de empaquetar un sistema completo, incluyendo su propio entorno, donde se hayan realizado todos los test de prueba y carga.

DOCKER

Cuando hablamos de contenedores, no podemos evitar hablar de DOCKER, quizás la plataforma de contenedores más conocida. DOCKER es una herramienta que está diseñada para beneficiar tanto a los desarrolladores como a los administradores de sistemas. Docker está disponible en dos ediciones: Enterprise Edition y Community Edition. Esta última es la ideal para desarrolladores y pequeños equipos que desean experimentar con aplicaciones basadas en contenedores.


Está disponible para estos sistemas operativos:
  • Windows 10
  • MAC
  • Fedora
  • Debian
  • Ubuntu
  • CentOS
Existen dos canales de actualización:
  • Edge: nuevas características cada mes
  • Stable: actualizaciones confiables cada trimestre
El soporte se proporciona a través del Foro de la Comunidad de Docker.

Docker tiene el mismo motor en Linux y Windows.


INSTALAR DOCKER


 

Como plataforma de pruebas, estoy utilizando una RASPBERRY PI 3B+ con S.O. UBUNTU SERVER 16.04 y ahora comienzo a instalar el servicio DOCKER.

$ sudo su
$ sudo curl -sSL https://get.docker.com/ | sh
# Executing docker install script, commit: f45d7c11389849ff46a6b4d94e0dd1ffebca32c1
+ sudo -E sh -c apt-get update -qq >/dev/null 


Debemos agregar el usuario 'ubuntu' al grupo docker. Esto sirve para poder ejecutar Docker sin anteponer la opción sudo.

$ sudo usermod -aG docker ubuntu

Reiniciar..

$ sudo reboot

Una vez completado el reinicio

$ docker -v

Instalar python 3 :

$ sudo apt-get install python-pip python3-pip
$ sudo apt-get install python3-dev python3-cffi libssl-dev libxml2-dev libxslt1-dev libjpeg8-dev zlib1g-dev

Instalar docker-compose
$ sudo su
$ sudo pip3 install docker-compose
ó si da error...
$ python3 -m easy_install docker-compose

Comprobar la version instalada

$ docker-compose --version

Creamos nuestro primer container de prueba, para lo que descargamos una IMAGEN de Apache:

$ docker pull httpd
Using default tag: latest
latest: Pulling from library/httpd
d852f02e2b4c: Pull complete
ab75f5d475d0: Pull complete
9647edb2ed9b: Pull complete
eeced0d4397e: Pull complete
a394083bd2d5: Pull complete
Digest: sha256:649bd29cc9284f06cf1a99726c4e747a83679e04eea3311b55022dd247026138
Status: Downloaded newer image for httpd:latest
docker.io/library/httpd:latest

y creamos el contenedor :

$ docker run -p 81:80 httpd

solicito crear un contenedor con la imagen que acabo de descargar (pull), con apache ya configurado, y exponer el puerto 81 del contenedor que corresponde al puerto 80 interno de apache. La razón por la que no puedo usar el puerto 80 es porque solo hay un puerto 80 para cada Host de Contenedor, por lo que el NAT es fundamental cuando planeamos utilizar contenedores.

Desde un navegador podrá http://*.*.*.*:81 (sustituya los * por su propia ip o localhost si fuera su caso)


It works!

...será la respuesta.

Estos son algunos comandos útiles para administrar su máquina Docker, 'id_container' es el nombre que podemos proporcionar al contenedor en su creación o por defecto, el que DOCKER le dará al crearlo sin indicarle ninguno. 'id_immagine' es la imagen que previamente hemos descargado con 'pull' :
  • Descargar Imagen - docker pull id_immagine
  • Start Container – docker start id_container
  • Stop Container – docker stop id_container
  • Abrir una sesión – docker exec -it id_container /bin/bash
  • Abrir una sesión como root -- docker exec -u 0 -it id_container /bin/bash
  • Lista de Imagenes – docker images
  • Lista de contenedores – docker ps -a
  • Eliminar Imagen – docker rmi id_immagine
  • Eliminar Container – docker rm id_container
Para gestionar CONTENEDORES

En los comandos siguientes ID_CONTAINER es el nombre del contenedor, que podemos elegir al crearlo, y RED es el nombre de una red ya existente.

Para crear un contenedor (y ponerlo en marcha):
sudo docker run --name=
ID_CONTAINER  IMAGEN
El problema de este comando es que dejamos de tener acceso a la shell y sólo se puede parar el proceso desde otro terminal.
  • Para crear un contenedor (y ponerlo en marcha):
    docker run --name=
    ID_CONTAINER  IMAGEN
  • Para crear un contenedor (y ponerlo en marcha):
    docker run --name=
    ID_CONTAINER  IMAGEN
    El problema de este comando es que dejamos de tener acceso a la shell y sólo se puede parar el proceso desde otro terminal.
  • Lo habitual es poner en marcha el contenedor en modo separado (detached), es decir, en segundo plano, y así podemos seguir utilizando la shell:
    docker run -d --name=
    ID_CONTAINER IMAGEN
  • Si queremos ver la secuencia de arranque del contenedor, podemos poner en marcha el contenedor en modo pseudo-tty, que trabaja en primer plano, pero del que podemos salir con Ctrl+C.
    docker run -t --name=
    ID_CONTAINER IMAGEN
  • Al crear el contenedor se pueden añadir diversas opciones:
    Para incluir el contenedor en una red privada virtual (y que se pueda comunicar con el resto de contenedores incluidos en esa red):
    docker run --name=
    ID_CONTAINER --net=RED IMAGEN
     
  • Para que el contenedor atienda a un puerto determinado, aunque internamente atienda un puerto distinto:
    docker run --name=
    ID_CONTAINER -p PUERTO_EXTERNO:PUERTO_INTERNO
    IMAGEN 
  • Para establecer variables de configuración del contenedor:                                                      docker run --name=ID_CONTAINER -e VARIABLE=VALOR IMAGEN

Salir de la shell del contenedor con 'exit'


Para copiar (o mover) archivos entre el contenedor y el sistema anfitrión o viceversa:
docker cp
ID_CONTAINER:ORIGEN DESTINO
docker cp ORIGEN
ID_CONTAINER:DESTINO


Cosas importantes a saber antes de crear un contenedor DOCKER:

    *
El contenedor no tendrá nada instalado por defecto, lo primero que se hará es un 

      apt-get update  o yum update para descargar los repositorios del sistema

    * Crear un contenedor desde una imagen con -i permite acceder al contenedor inmediatamente despues de su creación :

      docker run -i -t --name=id_container centos:7 /bin/bash

    * Una vez dentro del contenedor se puede escribir 'exit' y se saldrá del contenedor, pero se perderá toda la  información del contenedor, archivos, aplicaciones, instalaciones… para salir sin cerrar el contenedor 'CTRL+Q+P'


    * Una vez configurado un contenedor con todos los elementos instalados, puede crear una nueva imagen del contenedor :

    $ docker commit ID_CONTAINER ID_NUEVA_IMAGEN
A partir de ahora, podrá crear nuevos contenedores a partir de ID_NUEVA_IMAGEN

CREAR CONTENEDOR DOCKER CON CENTOS 7+ APACHE + PYTHON3 + MOD_WSGI


$ docker pull centos:7

$ docker run -ti --name centos_one centos:7 /bin/bash 
[root@50b7b40cfd61 /]# 

...estará en la shell por crear el contenedor con -i ...
# yum update

Instalar PYTHON 3.6 desde los fuentes :
# yum groupinstall -y "Development Tools"

# yum install -y zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel expat-devel

# yum install -y wget


# mkdir /home/tmp

# cd /home/tmp 




# wget https://www.python.org/ftp/python/3.6.9/Python-3.6.9.tar.xz
# tar -xJf Python-3.6.9.tar.xz
# cd Python-3.6.9
# ./configure    o  

   ./configure --prefix=/usr/local --enable-shared LDFLAGS="-Wl,-rpath /usr/local/lib"
# make
# make altinstall


Instalar drivers para MySQL (mysql-connector) y PostgreSQL (Psycopg2)

# pip3.6 install mysql-connector-python 

# pip3.6 install Psycopg2

Instalar apache :


# yum install httpd httpd-devel mod_ssl

Instalar mod_wsgi desde fuentes:

# cd ..
# yum -y install python36-devel
# wget "https://github.com/GrahamDumpleton/mod_wsgi/archive/4.6.8.tar.gz"
# tar -xzf '4.6.8.tar.gz'
# cd ./mod_wsgi-4.6.8
#./configure --with-python=/usr/bin/python3.6

#make
#make install


Instalaciones varias y opcionales


# yum install zip unzip
# yum install ImageMagick ImageMagick-devel

Incorporar este software python es opcional, pero necesario para instalar PYMETRICK

# pip3.6 install datetime
# pip3.6 install pillow==4.0.0             
Tratamiento de imagenes
# pip3.6 install qrcode                          Generar cógidos QR
# pip3.6 install requests
# pip3.6 install openpyxl                     
Crear y leer ficheros EXCEL xlsx

# pip3.6 install fpdf                              Crear fichero PDF
# pip3.6 install certifi





Ya he instalado todo el software necesario para ejecutar un contenedor CENTOS7+APACHE+PYTHON3+MOD_WSGI. Si te preguntas por qué no incluir MySQL o PostgreSQL, la recomendación es no incluir más de una aplicación o servicio por cada contenedor. Siempre es posible crear redes entre contenedores para que se puedan intercomunicar o bien, comunicarse con los propios servicios del host, o de un cloud.

Salimos con CTRL+Q+P y paramos el contenedor con
$ docker stop centos_one

Crear una nueva imagen a partir del contenedor creado:

$ docker commit centos_one centos7_httpd_py3_mod_wsgi

Verá el resultado con :

$ docker images
REPOSITORY                           TAG                 IMAGE ID            CREATED             SIZE
centos7_httpd_py3_mod_wsgi   latest              3ae42078c5f2        34 seconds ago      1.13GB


Hasta aquí la instalación de hoy, pero continuamos hasta crear una aplicación web completa basada en nuestro librería python 3 PYMETRICK

Nota: Todo el tutorial realizado solo tiene un fin teórico de aprendizaje sobre Docker, priorizando un s.o. y librerias python muy conocidas por todos. Los siguientes tutoriales priorizan las soluciones más profesionales para desplegar servicios en proveedores de la nube (Cloud) así como la creación de contenedores con fichero de configuración o dockerfiles. 






jueves, 27 de junio de 2019

Cuando logging no es correcto

Por defecto,  cuando hacemos uso de la libreria en Python, registrará cualquier mensaje que sea de advertencia o superior a stderr. 

La función basicConfig() se puede usar para modificar el formato, dónde enviar los registros y qué nivel registrar. Pero una vez que se ha registrado algo, llamar a basicConfig() no registrará ningún cambio de formato, o no hará nada.

Esto puede confundirnos cuando estamos realizando pruebas para modificar un registro de salida.
 
Una forma rápida de restablecer el registro es verificar si logging.root está definido, y eliminar cualquier controlador de la siguiente forma:


import logging
import logging.handlers

if logging.root:
    del logging.root.handlers [:]

 
Después de eliminar los controladores, podrá volver a llamar a basicConfig().

lunes, 17 de junio de 2019

Python 3.x y tipos de cadenas: Unicode, Byte y Bytearray

Nos hemos acostumbrado a usar frameworks para todo y hemos perdido las ventajas de lo artesanal.

La codificación por defecto de Python 3.x es Unicode, lo que sería equivalente a cadena de caracteres (str) en Python 2.x.

Unicode es un estándar de codificación de caracteres para facilitar el tratamiento de textos de múltiples lenguajes, incluido los basados en ideogramas o aquellos usados en textos de lenguas muertas. El término Unicode proviene de los objetivos perseguidos durante el desarrollo del proyecto: universalidad, uniformidad y unicidad.

En Unicode los caracteres alfabéticos, los ideogramas y los símbolos se tratan de forma equivalente y se pueden mezclar entre sí en un mismo texto, es decir, es posible representar en un mismo párrafo caracteres del alfabeto árabe, cirílico, latino, ideogramas japoneses y símbolos musicales.

One-character Unicode strings can also be created with the chr() built-in function, which takes integers and returns a Unicode string of length 1 that contains the corresponding code point. The reverse operation is the built-in ord() function that takes a one-character Unicode string and returns the code point value:
>>>
>>> chr(57344)
'\ue000'
>>> ord('\ue000')
57344


The unicode_escape encoding will convert all the various ways of entering unicode characters. The '\x00' syntax, the '\u0000' and even the '\N{name}' syntax:
The \x takes two hex digits \xDF. The lower-case \u takes four hex digits \u00DF. The upper-case \U takes eight hex digits \U000000DF.
>>> from makeunicode import u
>>> print(u('\u00dcnic\u00f6de'))
Ünicöde
>>> print(u('\xdcnic\N{Latin Small Letter O with diaeresis}de'))
Ünicöde 
>>>u"\N{EURO SIGN}"

Escape Characters

The recognized escape sequences are:
\newline
Ignored
\
Backslash (\)
\’
Single quote (‘)
\”
Double quote (”)
\a
ASCII Bell (BEL)
\b
ASCII Backspace (BS)
\f
ASCII Formfeed (FF)
\n
ASCII Linefeed (LF)
\N{name}
Character named NAME in the Unicode database (Unicode only)
\r
ASCII Carriage Return (CR)
\t
ASCII Horizontal Tab (TAB)
\uxxxx
Character with 16-bit hex value XXXX (Unicode only) (1)
\Uxxxxxxxx
Character with 32-bit hex value XXXXXXXX (Unicode only) (2)
\v
ASCII Vertical Tab (VT)
\ooo
Character with octal value OOO (3,5)
\xhh
Character with hex value HH (4,5)


En Python 3 no existen cadenas codificadas en UTF-8, cuando hablamos de codificación es porque convertimos cadenas de caracteres Unicode en una secuencia de bytes con una codificación determinada ( por ejemplo UTF-8). Por tanto los bytes no son caracteres (Unicode), los bytes son bytes y un carácter es una abstracción, siendo una cadena una sucesión de abstracciones.

In Python 3, all strings are sequences of Unicode characters

UTF-8 is a way of encoding characters as a sequence of bytes

>>> b'\x80abc'.decode("utf-8", "strict")  
Traceback (most recent call last):
    ...
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0:
  invalid start byte
>>> b'\x80abc'.decode("utf-8", "replace")
'\ufffdabc'
>>> b'\x80abc'.decode("utf-8", "backslashreplace")
'\\x80abc'
>>> b'\x80abc'.decode("utf-8", "ignore")
'abc'


The opposite method of bytes.decode() is str.encode(), which returns a bytes representation of the Unicode string, encoded in the requested encoding.

The errors parameter is the same as the parameter of the decode() method but supports a few more possible handlers. As well as 'strict', 'ignore', and 'replace' (which in this case inserts a question mark instead of the unencodable character), there is also 'xmlcharrefreplace' (inserts an XML character reference), backslashreplace (inserts a \uNNNN escape sequence) and namereplace (inserts a \N{...} escape sequence).
The following example shows the different results:
>>>
>>> u = chr(40960) + 'abcd' + chr(1972)
>>> u.encode('utf-8')
b'\xea\x80\x80abcd\xde\xb4'
>>> u.encode('ascii')  
Traceback (most recent call last):
    ...
UnicodeEncodeError: 'ascii' codec can't encode character '\ua000' in
  position 0: ordinal not in range(128)
>>> u.encode('ascii', 'ignore')
b'abcd'
>>> u.encode('ascii', 'replace')
b'?abcd?'
>>> u.encode('ascii', 'xmlcharrefreplace')
b'ꀀabcd޴'
>>> u.encode('ascii', 'backslashreplace')
b'\\ua000abcd\\u07b4'
>>> u.encode('ascii', 'namereplace')
b'\\N{YI SYLLABLE IT}abcd\\u07b4'



Bytes are not characters; bytes are bytes. Characters are an abstraction. A string is a sequence of those abstractions.

You can’t concatenate bytes and strings. They are two different data types.
You can’t count the occurrences of bytes in a string, because there are no bytes in a string. A string is a sequence of characters. Perhaps you meant “count the occurrences of the string that you would get after decoding this sequence of bytes in a particular character encoding”? Well then, you’ll need to say that explicitly. Python 3 won’t implicitly convert bytes to strings or strings to bytes.
By an amazing coincidence, this line of code says “count the occurrences of the string that you would get after decoding this sequence of bytes in this particular character encoding.”

And here is the link between strings and bytes: bytes objects have a decode() method that takes a character encoding and returns a string, and strings have an encode() method that takes a character encoding and returns a bytes object.

u'something'.encode('utf-8') will generate b'bytes',
but so does bytes(u'something', 'utf-8').
And b'bytes'.decode('utf-8') seems to do same thing as str(b'', 'utf-8')

Por ejemplo :
>>> n = 'NIÑO'
>>> m = bytes(n,'utf-8')  ----->  b'N\x3c\xb1O'  -----> class 'byte'
equivalente a : 
>>> m = n.encode('utf-8')  --->  b'N\x3c\xb1O'  -----> class 'byte'
para convertir una cadena Byte a Unicode podemos utilizar la función decode():
>>> l = m.decode('utf-8')  ----> 'NIÑO'  ---------------> class 'str'

Comprobando la codificación por defecto del sistema sys.getdefaultencoding() comprobará que es 'utf-8', por tanto, n.encode() y n.decode() pueden sustituir n.encode('utf-8') y n.decode('utf-8')

In Python 3.x, the prefix 'b' indicates the string is a bytes object which differs from the normal string (which as we know is by default a Unicode string), and even the 'b' prefix is preserved:
 b'prefix in Python 3.x'

Para declarar una cadena ByteArray utilizaremos la función bytearray():

>>> entidad = bytearray('niño', 'utf-8')
>>> type(entidad)

Syntax:
bytes([source[, encoding[, errors]]])
 
Return a new "bytes" object, which is an immutable sequence of small 
integers in the range 0 <= x < 256, print as ASCII characters when
 displayed. bytes is an immutable version of bytearray – it has the same
 non-mutating methods and the same indexing and slicing behavior. 
 
Syntax:
bytearray([source[, encoding[, errors]]])
 
Return a new array of bytes. The bytearray type is a mutable sequence of
 integers in the range 0 <= x < 256. It has most of the usual 
methods of mutable sequences, described in Mutable Sequence Types, as 
well as most methods that the bytes type has, see Bytes and Byte Array 
Methods. 

Difference between bytes and bytearray object in Python

>>> #bytearray objects are a mutable counterpart to bytes objects 
>>> x = bytearray("Python bytearray", "utf8")  
>>> print(x) bytearray(b'Python bytearray')  
>>> #can remove items from the bytes 
>>> del x[11:15]  
>>> print(x) bytearray(b'Python bytey') 
>>> #can add items from the bytes 
>>> x[11:15] = b" object" 
>>> print(x) bytearray(b'Python byte object') 
>>> #can use the methods of mutable type iterable objects as the lists  
>>> x.append(45) 
>>> print(x) bytearray(b'Python byte object-')

Convert a bytes to bytearray

>>> #create a bytes object from a list of integers in the range 0 through 255
>>> x = bytes([105, 100, 107, 112, 132, 118, 107, 112, 200])
>>> print(x)
b'idkp\x84vkp\xc8'
>>> #generates a new array of bytes from a bytes object
>>> x1 = bytearray(x)
>>> print(x1)
bytearray(b'idkp\x84vkp\xc8')

bs = 'niño'
print(type(bs))
 
bb = bs.encode('utf-8')
print(type(bb))
bb
b'ni\xc3\xb1o'
bss = bb.decode('utf-8')
print(type(bss))
bss
niño 
 
 
mod_wsgi

Apache/2.4.27 (Unix) mod_wsgi/4.5.18 Python/3.6
:QUERY_STRING: searchPhrase=NI%C3%91OS
DEBUG:wsgi.wsgateway:wsgi.errors: <_io .textiowrapper="" encoding="utf-8" name="<wsgi.errors>">
DEBUG:wsgi.wsgateway:self.request searchPhrase=NI%C3%91OS
DEBUG:parse_qs:qs parametros searchPhrase NI\xc3\x83\xc2\x91OS
 
  1. WSGI environ keys are unicode
  2. WSGI environ values that contain incoming request data are bytes
  3. headers, chunks in the response iterable as well as status code are bytes as well
1.La aplicación pasa una instancia de un diccionario de Python que contiene lo que se conoce como el entorno WSGI. Todas las claves en este diccionario son cadenas nativas. Para las variables CGI, todos los nombres van a ser ISO-8859-1 y, por lo tanto, donde las cadenas nativas son cadenas unicode, esa codificación se usa para los nombres de las variables CGI.

2.Para la variable WSGI 'wsgi.url_scheme' contenida en el entorno WSGI, el valor de la variable debe ser una cadena nativa.

3.Para las variables CGI contenidas en el entorno WSGI, los valores de las variables son cadenas nativas. Donde las cadenas nativas son cadenas unicode, la codificación ISO-8859-1 se usaría de tal manera que los datos de los caracteres originales se conserven y, como sea necesario, la cadena unicode pueda volver a convertirse en bytes y, a continuación, descodificada para volver a codificar usando una codificación diferente.

4.La secuencia de entrada WSGI 'wsgi.input' contenida en el entorno WSGI y desde donde se lee el contenido de la solicitud, debe generar cadenas de bytes.

5.La línea de estado especificada por la aplicación WSGI debe ser una cadena de bytes. Donde las cadenas nativas son cadenas unicode, el tipo de cadena nativa también se puede devolver, en cuyo caso se codificaría como ISO-8859-1.

6.La lista de encabezados de respuesta especificados por la aplicación WSGI debe contener tuplas que consisten en dos valores, donde cada valor es una cadena de bytes. Donde las cadenas nativas son cadenas unicode, el tipo de cadena nativa también se puede devolver, en cuyo caso se codificaría como ISO-8859-1.

7.El iterable devuelto por la aplicación y del que se deriva el contenido de la respuesta, debe producir cadenas de bytes. Donde las cadenas nativas son cadenas unicode, el tipo de cadena nativa también se puede devolver, en cuyo caso se codificaría como ISO-8859-1.

8.El valor que se pasa a la devolución de llamada 'write ()' devuelto por 'start_response ()' debe ser una cadena de bytes. Donde las cadenas nativas son cadenas unicode, también se puede suministrar un tipo de cadena nativo, en cuyo caso se codificaría como ISO-8859-1.       
  
Con POSTGRESQL

Reading data from database is similar to reading from file. Decode when reading, process it, encode when writing. However, some python database libraries do this for you automatically. sqlite3, MySQLdb, psycopg2 all allow you to pass unicode string directly to INSERT or SELECT statement. When you specify the string encoding when creating connection, the returned string is also decoded to unicode string automatically.

Here is a psycopg2 example:
#!/usr/bin/env python
# coding=utf-8

"""
postgres database read/write example
"""

import psycopg2


def get_conn():
    return psycopg2.connect(host="localhost",
                            database="t1",
                            user="t1",
                            password="fNfwREMqO69TB9YqE+/OzF5/k+s=")


def write():
    with get_conn() as conn:
        cur = conn.cursor()
        cur.execute(u"""\
CREATE TABLE IF NOT EXISTS t1
(id integer,
 data text);
""")
        cur.execute(u"""\
DELETE FROM t1
""")
        cur.execute(u"""\
INSERT INTO t1 VALUES (%s, %s)
""", (1, u"✓"))


def read():
    with get_conn() as conn:
        cur = conn.cursor()
        cur.execute(u"""\
SELECT id, data FROM t1
""")
        for row in cur:
            data = row[1].decode('utf-8')
            print(type(data), data)


def main():
    write()
    read()


if __name__ == '__main__':
    main()