25 feb. 2016

Crear un registro de imagenes Docker privado.

Por ahí he leído toda la infraestructura que es necesaria insalar para tener un sistema de registro privado de imagenes de Docker.

Pero no, usemos Docker!!!


Con esta idea se puede bajar una imagen del registro de Docker del siguiente enlace.

Antes de continuar les dejaré la lista de artículos sobre Docker que he tocado en el blog:

  1. Instalar Docker en Debian Jessie
  2. Uso de Docker en Debian Jessie (parte 1)
  3. Uso de Docker en Debian Jessie (parte 2)
  4. Crear una imagen Docker a partir de un archivo Dockerfile
  5. Iniciando Django usando Docker
  6. Instalar Gitlab por medio de Docker
  7. Ejecutando microservicios con docker usando docker-compose
  8. Docker en Docker (DinD)
  9. Iniciando Django con docker usando docker-compose con postgresql como microservicio.
  10. Importar un contenedor Docker en Python.
  11. Compartir imagenes Docker por medio de archivos tar.
Lo primero que se hará es bajar la imagen Docker del registro, se tiene la versión oficial que es la 2 o la última.

$docker pull registry:2
ó
$docker pull registry

Se inicia el contenedor en el puerto 5000:

$ docker run -d -p 5000:5000 registry:2 
3c0719746d75f46220328c08209b3e716c8e883ad6f3df7bbae0d70e50656a45

Para verificar que el registro está corriendo se prueba con curl conectarse a localhost al puerto 5000 a v2/:
$ curl -i http://localhost:5000/v2/
HTTP/1.1 200 OK
Content-Length: 2
Content-Type: application/json; charset=utf-8
Docker-Distribution-Api-Version: registry/2.0
X-Content-Type-Options: nosniff
Date: Thu, 25 Feb 2016 18:40:25 GMT


Ahora se puede probar subir una imagen al sistema privado de registro, se prueba con busybox:

Se crea un tag:
$docker tag busybox localhost:5000/busy

Se sube la imagen:

$ docker push localhost:5000/busy
The push refers to a repository [localhost:5000/busy]
5f70bf18a086: Pushed 
9508eff2c687: Pushed 
latest: digest: sha256:b58c00d636b25f40fc7605b1b807af81c1a79a7bbb2179b4753b9dc7e0d1dcd7 size: 711

Para poder acceder de manera insegura al registro privado se tiene que modificar el archivo /etc/default/docker y agregar la siguiente línea (tomar en cuenta que el contenedor está corriendo en localhost):

DOCKER_OPTS="--insecure-registry localhost:5000"

Se reinicia el servicio de docker:

# service docker restart

Para más información de las opciones del registro de Docker pueden revisar el siguiente enlace.

Para revisar las imagenes almacenadas en el sistema de registro privado se ejecuta:
curl http://localhost:5000/v2/_catalog
{"repositories":["busy"]}



El catalogo muestra que se tiene la imagen busy.

Se puede crear otra tag de busybox con nombre busy1 y se sube al servidor:

$docker tag busybox localhost:5000/busy1

$ docker push localhost:5000/busy1
The push refers to a repository [localhost:5000/busy1]
5f70bf18a086: Mounted from busy 
9508eff2c687: Mounted from busy 
latest: digest: sha256:b58c00d636b25f40fc7605b1b807af81c1a79a7bbb2179b4753b9dc7e0d1dcd7 size: 711

Al volver a consultar el catalogo se tiene dos imagenes:
$curl http://localhost:5000/v2/_catalog
{"repositories":["busy","busy1"]}

Cada imagen tiene un manifiesto:

$curl http://localhost:5000/v2/busy1/manifests/latest

$ curl http://localhost:5000/v2/busy1/manifests/latest
{
   "schemaVersion": 1,
   "name": "busy1",
   "tag": "latest",
   "architecture": "amd64",
   "fsLayers": [
      {
         "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
      },
      {
         "blobSum": "sha256:d7e8ec85c5abc60edf74bd4b8d68049350127e4102a084f22060f7321eac3586"
      }
   ],
   "history": [
      {
         "v1Compatibility": "{\"architecture\":\"amd64\",\"config\":{\"Hostname\":\"ea7fe68f39fd\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"sh\"],\"Image\":\"5c5fb281b01ee091a0fffa5b4a4c7fb7d358e7fb7c49c263d6d7a4e35d199fd0\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"container\":\"f63d86f7f85b3207532327b6e484bf09d8a0d1a0979cf7bdce1bd5268666fdd3\",\"container_config\":{\"Hostname\":\"ea7fe68f39fd\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"sh\\\"]\"],\"Image\":\"5c5fb281b01ee091a0fffa5b4a4c7fb7d358e7fb7c49c263d6d7a4e35d199fd0\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"created\":\"2015-12-08T18:31:51.481948133Z\",\"docker_version\":\"1.8.3\",\"id\":\"a997905216262e309de0dccc4c8ed39ee475a9d0e6b3f3c3a40b4ccf28af9b15\",\"os\":\"linux\",\"parent\":\"3d030bd4e34f5ed0b05de21a56503d80881fb1464afdde1c06a2b39c59260a22\"}"
      },
      {
         "v1Compatibility": "{\"id\":\"3d030bd4e34f5ed0b05de21a56503d80881fb1464afdde1c06a2b39c59260a22\",\"created\":\"2015-12-08T18:31:50.979824705Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ADD file:c295b0748bf05d4527f500b62ff269bfd0037f7515f1375d2ee474b830bad382 in /\"]}}"
      }
   ],
   "signatures": [
      {
         "header": {
            "jwk": {
               "crv": "P-256",
               "kid": "FO3A:MIWL:JVJW:5O62:N7RK:GZXL:ACGC:WRUQ:PM4P:FVDL:PBAQ:X3IG",
               "kty": "EC",
               "x": "8eTT88WGU5NVLCrp5qGa4cO_nCo00e1L-eNLKF_0eeE",
               "y": "QwgEYEkJDxz4vihT3Mc6OitZ9uraF_TwbfpvZm72dJ8"
            },
            "alg": "ES256"
         },
         "signature": "LkmuupCii3CgtJKkKbasvkKc1jo7V0rZP9k4EFXuVMQL08-7n8s8TWzNB_Wx8oTv4qMb-Bpqhv3A2jdemu6eSA",
         "protected": "eyJmb3JtYXRMZW5ndGgiOjIyMTQsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNi0wMi0yNVQxOToxNjoxNFoifQ"
      }
   ]
}


Para listar todos los tags de una imagen se tiene:
$ curl http://localhost:5000/v2/busy1/tags/list
{"name":"busy1","tags":["latest"]}
$ curl http://localhost:5000/v2/busy/tags/list
{"name":"busy","tags":["latest"]}


Se puede hacer un pull del sistema de registro local para verificar que la imagen está actualizada:

$ docker pull localhost:5000/busy1
Using default tag: latest
latest: Pulling from busy1
Digest: sha256:b58c00d636b25f40fc7605b1b807af81c1a79a7bbb2179b4753b9dc7e0d1dcd7
Status: Image is up to date for localhost:5000/busy1:latest


Para más información del despliegue del sistema de registro pueden revisar el siguiente enlace.

24 feb. 2016

Compartir imagenes Docker por medio de archivos tar

Uno de los puntos más importantes a la hora de trabajar con Docker es el compartir  imagenes Docker aparte de usar Docker Hub.

Se tiene la exportación e importación de imagenes como opción almacenando y extrayendo a partir de archios tar.

Se tienen los artículos anteriores sobre Docker:

  1. Instalar Docker en Debian Jessie
  2. Uso de Docker en Debian Jessie (parte 1)
  3. Uso de Docker en Debian Jessie (parte 2)
  4. Crear una imagen Docker a partir de un archivo Dockerfile
  5. Iniciando Django usando Docker
  6. Instalar Gitlab por medio de Docker
  7. Ejecutando microservicios con docker usando docker-compose
  8. Docker en Docker (DinD)
  9. Iniciando Django con docker usando docker-compose con postgresql como microservicio.
  10. Importar un contenedor Docker en Python.

Lo primero es ver que contenedores se tienen ejecutandose en el equipo:

$docker ps 
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS              PORTS                                                                    NAMES
b0c1163d1668        gitlab/gitlab-ce:latest   "/assets/wrapper"        4 weeks ago         Up 4 hours          0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, 0.0.0.0:8022->22/tcp           gitlab
b4d3c96f6dc9        jenkins                   "/bin/tini -- /usr/lo"   4 weeks ago         Up 4 hours          0.0.0.0:8080->8080/tcp, 0.0.0.0:50000->50000/tcp, 0.0.0.0:8122->22/tcp   jenkins


Se tienen dos contenedores ejecutandose, uno de gitlab y otro de jenkins.

Se realizará respaldo del contenedor con gitlab usando el comando docker export, pasando el id del contenedor de gitlab que es b0c1163d1668:

$docker export b0c1163d1668 > gitlab.tar

Al ejecutar ls se tiene el archivo gitlab.tar:

ls -l
total 2201820
-rw-r--r-- 1 ernesto ernesto 1409662976 feb 24 16:15 gitlab.tar


Para usar la imagen a partir del archivo tar se importa la imagen: 

$ docker import - gitlab2 < gitlab.tar
sha256:020ea830b267080ce092f42b1b0f1a2d520b21b8a3393696f5271f42e4bd8d79

El nombre de la imagen es gitlab2, ahora se lista las imagenes que se tienen en el equipo:

$ docker images | grep gitlab
gitlab2                        latest              020ea830b267        About a minute ago   1.344 GB
gitlab/gitlab-ce               latest              db1c29be1030        6 weeks ago          1.326 GB
sameersbn/gitlab               latest              47d53c4a820a        10 weeks ago         675.7 MB

Si se quiere compartir la imagen con alguien,  se puede subir el archivo tar a un servidor web y de ahí se puede bajar .  

Importar un contenedor Docker en Python.



En los artículos anteriores sobre Docker se ha tocado el manejo de imagenes, ahora se explicará como importar un contenedor desde Python, este artículo se basa en el siguiente artículo en inglés.

Los artículos anteriores de Docker son:
  1. Instalar Docker en Debian Jessie
  2. Uso de Docker en Debian Jessie (parte 1)
  3. Uso de Docker en Debian Jessie (parte 2)
  4. Crear una imagen Docker a partir de un archivo Dockerfile
  5. Iniciando Django usando Docker
  6. Instalar Gitlab por medio de Docker
  7. Ejecutando microservicios con docker usando docker-compose
  8. Docker en Docker (DinD)
  9. Iniciando Django con docker usando docker-compose con postgresql como microservicio.

Se tiene el módulo Sidomo que permite manejar contenedores.

Para instalar sidomo se ejecuta el comando pip:

pip install -e git+https://github.com/deepgram/sidomo.git#egg=sidomo

Se baja la imagen Docker de Ubuntu:
docker pull ubuntu

El código de ejemplo del sitio de sidomo se encuentra en el siguiente enlace.


La modificación del código es el siguiente:

#!/usr/bin/env python
from sidomo import Container
def say_hello(to):
    """Just say it."""
    with Container(
        'ubuntu',
        stderr=False
    ) as c:
        for line in c.run(
            'echo Hola Mundo  %s' % to
        ):
            yield line
if __name__ == '__main__':
    for line in say_hello("desde un contenedor Docker"):
        print line
Al ejecutar el código se tiene:

python ejemplo.py 
Hola Mundo desde un contenedor Docker



12 feb. 2016

PyDay: Evento #PyTatuy en Mérida

El día de hoy se realizará un PyDay en la Ciudad de Mérida.

A continuación les coloco la info del evento:


En mi caso me toca dar la charla de Usar Django con docker.

Les dejo la presentación colocada en slideshare:



11 feb. 2016

Iniciando Django con docker usando docker-compose con postgresql como microservicio.

En este artículo se tomará lo visto en el artículo de microservicios con docker donde se uso flask y redis, en este caso será con postgresql y Django.

Dejo a continuación la lista de artículos sobre docker que se ha tocado en el blog:

  1. Instalar Docker en Debian Jessie
  2. Uso de Docker en Debian Jessie (parte 1)
  3. Uso de Docker en Debian Jessie (parte 2)
  4. Crear una imagen Docker a partir de un archivo Dockerfile
  5. Iniciando Django usando Docker
  6. Instalar Gitlab por medio de Docker
  7. Ejecutando microservicios con docker usando docker-compose
  8. Docker en Docker (DinD)


El artículo se basa en un artículo en inglés sobre docker-compose y django.


La idea es tener dos contenedores uno ejecutando postgresql y otro Django.

En un directorio se crea el archivo Dockerfile con el siguiente contenido:

FROM python:2.7
ENV PYTHONUNBUFFERED 1
RUN mkdir /codigo
WORKDIR /codigo
ADD requerimientos.txt /codigo/
RUN pip install --upgrade pip
RUN pip install -r requermientos.txt
ADD . /codigo/

El archivo requerimientos.txt contendrá la instalación de Django y de la librería psycopg2 para conectarse a postgresql desde  django:

Django
psycopg2

El último archivo a crear es el que usará docker-compose con el nombre de docker-compose.yml y su contenido iniciará la base de datos postgresql y luego a Django:

bd:
  image: postgres
web:
  build: .
  command: python manage.py runserver 0.0.0.0:8000
  volumes:
    - .:/codigo
  ports:
    - "8000:8000"
  links:
    - bd


Se tendrán dos contenedores uno llamado bd y otro web. El comando a iniciar DJango es el que se conoce y usa el puerto 8000 que será visto de manera pública.


A continuación se crea el proyecto Django con el siguiente comando:

docker-compose run web django-admin.py startproject djangoapp .
Building web
Step 1 : FROM python:2.7
 ---> 19ab33b86bc6
Step 2 : ENV PYTHONUNBUFFERED 1
 ---> Running in eb54e8bab43f
 ---> a4206823aac3
Removing intermediate container eb54e8bab43f
Step 3 : RUN mkdir /app
 ---> Running in bf221dbca163
 ---> 44eb31969545
Removing intermediate container bf221dbca163
Step 4 : WORKDIR /app
 ---> Running in 6209e18e7e87
 ---> 6f7d50fc9b12
Removing intermediate container 6209e18e7e87
Step 5 : ADD requerimientos.txt /app/
 ---> 293c5424f664
Removing intermediate container c072ad8ca2ae
Step 6 : RUN pip install --upgrade pip
 ---> Running in 48f862804290
Collecting pip
  Downloading pip-8.0.2-py2.py3-none-any.whl (1.2MB)
Installing collected packages: pip
  Found existing installation: pip 7.1.2
    Uninstalling pip-7.1.2:
      Successfully uninstalled pip-7.1.2
Successfully installed pip-8.0.2
 ---> 6126a41cd55b
Removing intermediate container 48f862804290
Step 7 : RUN pip install -r requerimientos.txt
 ---> Running in 7e6c9ff9fefa
Collecting Django (from -r requerimientos.txt (line 1))
  Downloading Django-1.9.2-py2.py3-none-any.whl (6.6MB)
Collecting psycopg2 (from -r requerimientos.txt (line 2))
  Downloading psycopg2-2.6.1.tar.gz (371kB)
Building wheels for collected packages: psycopg2
  Running setup.py bdist_wheel for psycopg2: started
  Running setup.py bdist_wheel for psycopg2: finished with status 'done'
  Stored in directory: /root/.cache/pip/wheels/e2/9a/5e/7b620848bbc7cfb9084aafea077be11618c2b5067bd532f329
Successfully built psycopg2
Installing collected packages: Django, psycopg2
Successfully installed Django-1.9.2 psycopg2-2.6.1
 ---> 971bcfbe34ec
Removing intermediate container 7e6c9ff9fefa
Step 8 : ADD . /app/
 ---> 7a7faf87cf21
Removing intermediate container 6c246df080b3
Successfully built 7a7faf87cf21




Esto generará un directorio y unos archivos como se muestra a continuación:
ls -l
total 20
drwxr-xr-x 2 root    root    4096 feb 10 21:35 djangoapp
-rw-r--r-- 1 ernesto ernesto  174 feb 10 21:14 docker-compose.yml
-rw-r--r-- 1 ernesto ernesto  177 feb 10 21:14 Dockerfile
-rwxr-xr-x 1 root    root     252 feb 10 21:35 manage.py
-rw-r--r-- 1 ernesto ernesto   17 feb 10 20:10 requerimientos.txt


Es necesario cambiar de usuario al directorio djangoapp y a manage.py.
sudo chown -R $USER:$USER .

Se revisa las imagenes creadas:
docker images
REPOSITORY                  TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
docker5_web              latest              7a7faf87cf21        4 minutes ago       715.2 MB
postgres                    latest              54fa18d9f3b6        2 weeks ago         263.8 MB


Modificar el archivo djangoapp/settings.py para dar soporte a la conexión al contenedor de postgresql:

Modificar la sección database con el siguiente contenido:


DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'postgres',
        'USER': 'postgres',
        'HOST': 'db',
        'PORT': 5432,
    }
}

El usuario y clave son los que tiene predefinido la imagen de docker para postgresql.

Se ejecuta migrate del manage.py para crear las tablas:
docker-compose run web python manage.py migrate

Operations to perform:
  Apply all migrations: admin, contenttypes, auth, sessions
Running migrations:
  Rendering model states... DONE
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying sessions.0001_initial... OK


Se crea la cuenta de administrador de django:
docker-compose run web python manage.py createsuperuser
Username (leave blank to use 'root'): admin
Email address: 
Password: 
Password (again): 
Superuser created successfully.


Para levantar el servidor web de django se inicia docker-compose:

$docker-compose up
docker5_db_1 is up-to-date
Creating docker5_web_1
Attaching to docker5_db_1, docker5_web_1
db_1  | The files belonging to this database system will be owned by user "postgres".
db_1  | This user must also own the server process.
db_1  | 
db_1  | The database cluster will be initialized with locale "en_US.utf8".
db_1  | The default database encoding has accordingly been set to "UTF8".
db_1  | The default text search configuration will be set to "english".
db_1  | 
db_1  | Data page checksums are disabled.
db_1  | 
db_1  | fixing permissions on existing directory /var/lib/postgresql/data ... ok
db_1  | creating subdirectories ... ok
db_1  | selecting default max_connections ... 100
db_1  | selecting default shared_buffers ... 128MB
db_1  | selecting dynamic shared memory implementation ... posix
db_1  | creating configuration files ... ok
db_1  | creating template1 database in /var/lib/postgresql/data/base/1 ... ok
db_1  | initializing pg_authid ... ok
db_1  | initializing dependencies ... ok
db_1  | creating system views ... ok
db_1  | loading system objects' descriptions ... ok
db_1  | creating collations ... ok
db_1  | creating conversions ... ok
db_1  | creating dictionaries ... ok
db_1  | setting privileges on built-in objects ... ok
db_1  | creating information schema ... ok
db_1  | loading PL/pgSQL server-side language ... ok
db_1  | vacuuming database template1 ... ok
db_1  | copying template1 to template0 ... ok
db_1  | copying template1 to postgres ... ok
db_1  | syncing data to disk ... ok
db_1  | 
db_1  | WARNING: enabling "trust" authentication for local connections
db_1  | You can change this by editing pg_hba.conf or using the option -A, or
db_1  | --auth-local and --auth-host, the next time you run initdb.
db_1  | 
db_1  | Success. You can now start the database server using:
db_1  | 
db_1  |     pg_ctl -D /var/lib/postgresql/data -l logfile start
db_1  | 
db_1  | ****************************************************
db_1  | WARNING: No password has been set for the database.
db_1  |          This will allow anyone with access to the
db_1  |          Postgres port to access your database. In
db_1  |          Docker's default configuration, this is
db_1  |          effectively any other container on the same
db_1  |          system.
db_1  | 
db_1  |          Use "-e POSTGRES_PASSWORD=password" to set
db_1  |          it in "docker run".
db_1  | ****************************************************
db_1  | waiting for server to start....LOG:  database system was shut down at 2016-02-11 04:34:33 UTC
db_1  | LOG:  MultiXact member wraparound protections are now enabled
db_1  | LOG:  database system is ready to accept connections
db_1  | LOG:  autovacuum launcher started
db_1  |  done
db_1  | server started
db_1  | ALTER ROLE
db_1  | 
db_1  | 
db_1  | /docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*
db_1  | 
db_1  | LOG:  received fast shutdown request
db_1  | LOG:  aborting any active transactions
db_1  | waiting for server to shut down...LOG:  autovacuum launcher shutting down
db_1  | .LOG:  shutting down
db_1  | LOG:  database system is shut down
db_1  |  done
db_1  | server stopped
db_1  | 
db_1  | PostgreSQL init process complete; ready for start up.
db_1  | 
db_1  | LOG:  database system was shut down at 2016-02-11 04:34:38 UTC
db_1  | LOG:  MultiXact member wraparound protections are now enabled
db_1  | LOG:  database system is ready to accept connections
db_1  | LOG:  autovacuum launcher started
db_1  | LOG:  database system was interrupted; last known up at 2016-02-11 04:34:39 UTC
db_1  | LOG:  database system was not properly shut down; automatic recovery in progress
db_1  | LOG:  invalid record length at 0/1707848
db_1  | LOG:  redo is not required
db_1  | LOG:  MultiXact member wraparound protections are now enabled
db_1  | LOG:  database system is ready to accept connections
db_1  | LOG:  autovacuum launcher started
db_1  | ERROR:  relation "auth_user" does not exist at character 280
db_1  | STATEMENT:  SELECT "auth_user"."id", "auth_user"."password", "auth_user"."last_login", "auth_user"."is_superuser", "auth_user"."username", "auth_user"."first_name", "auth_user"."last_name", "auth_user"."email", "auth_user"."is_staff", "auth_user"."is_active", "auth_user"."date_joined" FROM "auth_user" WHERE "auth_user"."username" = 'root'
web_1 | Performing system checks...
web_1 | 
web_1 | System check identified no issues (0 silenced).
web_1 | February 11, 2016 - 12:38:56
web_1 | Django version 1.9.2, using settings 'djangoapp.settings'
web_1 | Starting development server at http://0.0.0.0:8000/
web_1 | Quit the server with CONTROL-C.


Sólo queda abrir el navegador en la siguiente URL http://127.0.0.1:8000/admin.

A continuación la imagen de la sesión de admin de Django:



Y la figura de los logs de la ejecución de Django:


Como se vió en el artículo se puede ejecutar los comandos del manage.py desde fuera del contenedor que estos comandos se ejecutarán en el contenedor.

Ya con esto se pueden realizar configuraciones más complejas como incorporar redis o rabbitMQ (con Django Celery, explicado en un artículo anterior) y lo mejor es que la configuración es simplemente adaptar lo que existe a las necesidades puntuales del proyecto.

Referencias:

  1. Quickstart: Compose and Django.
  2. Packaging Django applications into Docker container images.
  3. Deploy de Django sobre Docker.
  4. Docker Explicado: Cómo crear Contenedores de Docker corriendo en Memcached.
  5. Docker en la ejecución de test de integración en NodeJS.

8 feb. 2016

Extracción de Datos del PDF de reporte de salud de Cencoex

Este artículo es la continuación de los artículos de extracción de datos de un PDF, enfocado al reporte de Cencoex del área de salud.


La siguiente figura muestra el pdf antes mencionado:



Los artículos anteriores de extracción de pdf son:
  1. Extracción de información de PDFs con Python (parte 1).
  2. Extracción de información de PDFs con Python (parte 2).
  3. Extracción de Información de PDFs con Python (parte 3).


En el caso del Pdf del reporte de Cencoex se tiene el script ya explicado anteriormente que guarda la información en una base de datos mongodb llamada cencoex en una colección llamada salud. 

La siguiente figura muestra los documentos de la colección:

Con está información almacenada en mongodb se creará un API Restful con Eve como se explico en los artículos anteriores.

La imagen anterior muestra que los documentos tienen los siguientes campos:
  • _id: En el identificador del objeto
  • rif: Es el RIF de la empresa a la cual se le asignaron los dolares, el campo es un string.
  • monto: Monto en Dolares de la asignación de divisas a dicha empresa, el campo es un string.
  • empresa: Es el nombre de la empresa a la cual se le asignaron los dolares, el campo es un string. 
  • numero: es el número asignado que aparece en el pdf, es un entero. 


Con esta información es que se construirá el esquema para acceder por medio de Eve.

A continuación el archivo settings.py:

#Configuracion de mongodb
MONGO_HOST = 'localhost'
MONGO_PORT = 27017
#MONGO_USERNAME = 'user'
#MONGO_PASSWORD = 'user'
MONGO_DBNAME = 'cencoex'



RESOURCE_METHODS = ['GET', 'POST', 'DELETE']

ITEM_METHODS = ['GET', 'PATCH', 'PUT', 'DELETE']
#Esquema
schema = {
    'numero': {
        'type': 'integer',
    },
    'rif': {
        'type': 'string',
        'maxlength': 11,
    },
    'empresa': {
        'type': 'string',
    },
    'monto': {
        'type': 'string',
    },
}



empresa = {
    'titulo': 'numero',
    'additional_lookup': {
        'url': 'regex("[\w]+")',
        'field': 'numero'
    },

    'cache_control': 'max-age=10,must-revalidate',
    'cache_expires': 10,

    'resource_methods': ['GET', 'POST'],

    'schema': schema
}

DOMAIN = {
    'salud': empresa,
}


Como se ve, el dominio es salud que toma la información de la empresa donde el campo a manejar es el número. Se definió el esquema que maneja los mismos campos de la colección. 


El archivo run.py es el cual permite iniciar el API RestFul:

#!/usr/bin/env python

from eve import Eve
app = Eve()

if __name__ == '__main__':
    app.run()


Para iniciar el API RestFul se ejecuta el run.py:
python run.py


Al acceder a http://127.0.0.1:5000/ por medio del cliente restclient se tiene lo siguiente:

Para acceder a la lista de elementos del API RestFul se coloca en el cliente http://127.0.0.1:5000/salud/, la siguientes figuran muestran el resultado:


Como se ve en la figura se trae un JSON con los documentos de la base de datos mongodb.

Ahora se quiere acceder al primer número del reporte de la siguiente forma:
http://127.0.0.1:5000/salud/1

El resultado se muestra en las siguientes dos figuras:


Claro, esto es una demostración, pero se puede buscar realizar busquedas con otros campos. 

Se puede usar Django con el framework de API RestFul a fin  de mejorar la forma de consultar dichos documentos de la base de datos mongodb.

La idea era mostrar lo fácil que es publicar los datos de un Reporte de Cencoex, en realidad lo más complicado es extraer los datos de un PDF, incluso se pierde información dependiendo del formato de documento utilizado, como se mostró en artículo anterior el documento es una hoja de cálculo en Excel y si se perdió información en la extracción. 

Las instituciones que tienen dicha información en base de datos pueden hacer públicos los campos que se requieran de sólo lectura en un API RestFul, así se elimina el proceso de buscar información de PDFs. 


A continuación dejo la propuesta que está trabajando la comunidad de software libre en github por parte de la comunidad de Software Libre de Venezuela.


A continuación unos  vídeos de youtube:
Gobierno Abierto:


Alianza para el Gobierno Abierto:




7 feb. 2016

API rest ful con Eve (parte 2)

En el primer artículo se tocó el tema del api rest ful con eve de manera sencilla.

En este artículo se toca el uso de mongodb para majejo del api rest con mongodb, el artículo se basa en la guía rápida de eve que se encuentra en su sitio.

Los artículos relacionados con mongodb del blog son:

  1. Instalar un servidor mongodb en Debian.
  2. Habilitar la autenticación en un servidor mongodb.


Para ver la base de datos en mongodb se usará el cliente robomongo que lo pueden descargar en el siguiente enlace.

El archivo run.py contiene lo mismo que en el artículo anterior:

#!/usr/bin/env python

from eve import Eve
app = Eve()

if __name__ == '__main__':
    app.run()

El archivo settings.py contendrá algunas directivas.

Configuración de la base de datos mongodb:

MONGO_HOST = 'localhost'
MONGO_PORT = 27017
#MONGO_USERNAME = 'user'
#MONGO_PASSWORD = 'user'
MONGO_DBNAME = 'eve'

Como a mongodb no se le ha configurado un usuario y clave se comentan esas líneas, la base de datos a usar se llama eve.

Configurar todas las opciones del CRUD (leer, editar, update y borrar):

# Enable reads (GET), inserts (POST) and DELETE for resources/collections
# (if you omit this line, the API will default to ['GET'] and provide
# read-only access to the endpoint).
RESOURCE_METHODS = ['GET', 'POST', 'DELETE']

# Enable reads (GET), edits (PATCH), replacements (PUT) and deletes of
# individual items  (defaults to read-only item access).
ITEM_METHODS = ['GET', 'PATCH', 'PUT', 'DELETE']

Se define un esquema el cual permite validar los datos:

schema = {
    # Schema definition, based on Cerberus grammar. Check the Cerberus project
    # (https://github.com/nicolaiarocci/cerberus) for details.
    'nombre': {
        'type': 'string',
        'minlength': 1,
        'maxlength': 10,
    },
    'apellido': {
        'type': 'string',
        'minlength': 1,
        'maxlength': 15,
        'required': True,
        # talk about hard constraints! For the purpose of the demo
        # 'lastname' is an API entry-point, so we need it to be unique.
        'unique': True,
    },
    # 'role' is a list, and can only contain values from 'allowed'.
    'role': {
        'type': 'list',
        'allowed': ["author", "contributor", "copy"],
    },
    # An embedded 'strongly-typed' dictionary.
    'localizacion': {
        'type': 'dict',
        'schema': {
            'direccion': {'type': 'string'},
            'ciudad': {'type': 'string'}
        },
    },
    'nacimiento: {
        'type': 'datetime',
    },
}

Definición de persona:

persona = {
    # 'title' tag used in item links. Defaults to the resource title minus
    # the final, plural 's' (works fine in most cases but not for 'people')
    'titulo': 'persona',

    # by default the standard item entry point is defined as
    # '/people/<ObjectId>'. We leave it untouched, and we also enable an
    # additional read-only entry point. This way consumers can also perform
    # GET requests at '/people/<lastname>'.
    'additional_lookup': {
        'url': 'regex("[\w]+")',
        'field': 'apellido'
    },

    # We choose to override global cache-control directives for this resource.
    'cache_control': 'max-age=10,must-revalidate',
    'cache_expires': 10,

    # most global settings can be overridden at resource level
    'resource_methods': ['GET', 'POST'],

    'schema': schema
}

Por último se define el recurso como un dominio, el recurso persona toma la definición dada anteriormente:

DOMAIN = {
    'persona': persona,
}


Con robomongo se puede observar que ya se tiene dos documentos agregados a la colección persona de la base de datos eve:



Se consulta vía GET a http://127.0.0.1:500 por medio de la extensión de google chrome restclient:


Al hacer una consulta GET a http://127.0.0.1:5000/persona se tiene lo siguiente:




Desde la línea de comandos se agregará dos documentos, se usará curl para ello:
curl -d '[{"nombre": "Ernesto", "apellido": "Crespo"}, {"nombre": "Argimiro", "apellido": "Crespo"}]' -H 'Content-Type: application/json'  http://127.0.0.1:5000/persona


Al ejecutar devuelve lo siguiente:
{
   "_status": "OK",
   "_items": [
      {
         "_updated": "Mon, 08 Feb 2016 03:34:41 GMT",
         "_links": {
            "self": {
               "href": "persona/56b80cd146b4402633320fd5",
               "title": "Persona"
            }
         },
         "_created": "Mon, 08 Feb 2016 03:34:41 GMT",
         "_status": "OK",
         "_id": "56b80cd146b4402633320fd5",
         "_etag": "cb32857db339920319adeb23b29359936c92680f"
      },
      {
         "_updated": "Mon, 08 Feb 2016 03:34:41 GMT",
         "_links": {
            "self": {
               "href": "persona/56b80cd146b4402633320fd6",
               "title": "Persona"
            }
         },
         "_created": "Mon, 08 Feb 2016 03:34:41 GMT",
         "_status": "OK",
         "_id": "56b80cd146b4402633320fd6",
         "_etag": "c14c62e94c88dd227ccffaf5c248f18492040ee2"
      }
   ]
}


Al revisar la base de datos mongodb se tiene que se insertaron dos documentos:





Crear un paquete python del módulo pywrapper_config

Hace unos años escribí un artículo que explicaba como crear un paquete python (Crear un paquete python de un programa).

En este artículo se empaquetará el módulo python pywrapper_config el cual fue tema del siguiente artículo (Módulo que permite acceder a archivos de configuración).

La diferencia con el primer artículo será que en este caso se tocará como subir el paquete a los repositorios de paquetes de Python, donde se podrá usar el comando pip para instalar dicho módulo.

El módulo pywrapper_config está alojado en github en el siguiente enlace.

El archivo setup.py contiene lo siguiente:

#!/usr/bin/env python

from distutils.core import setup

data_files = [('share/pywrapper_config',['pywrapper_config.py','CHANGELOG','README.md','LICENSE'])]

setup(name='pywrapper_config',
      version='0.3.2',
      description='Wrapper to ConfigParser',
      author='Ernesto Crespo',
      author_email='ecrespo@gmail.com',
      url='https://github.com/ecrespo/pywrapper-config',
      license = "GPLv3",
      platforms=['i386','AMD64'],
      py_modules = ['pywrapper_config'],
      data_files =data_files,
      requires = ['ConfigParser'],
     )

Acá se define el nombre de la aplicación, su versión, la descripción de la aplicación, el autor con su correo, el url donde se aloja la aplicación, la licenica que usa, las pltaformas que soporta, el módulo que proveee, otros archivos adicionales y que se requiere para que el módulo funcione.

El archivo MANIFEST.in contiene:

include conf-examples/*.conf LICENSE README.md CHANGELOG


El siguiente paso es crear una cuenta en el sitio de pypi.

Crear el archivo .pypirc con el siguiente contenido:

[pypirc]
servers = pypi
[server-login]
username:<miusuario>
password:<miclave>

Este archivo define que el programa se subirá al repositorio pypi usando el usuario y clave ya definidos.

Para crear los paquetes fuentes se usará el siguiente comando:

python setup.py sdist --format=zip,gztar

Este comando generará dos archivos en el directorio dist:

dist
├── pywrapper_config-0.3.2.tar.gz
└── pywrapper_config-0.3.2.zip

Se crearon los paquetes tar.gz y .zip.

Lo siguiente es registrar la aplicación:

python setup.py sdist register -r pywrapper_config


Por último para subir los paquetes se ejecuta el siguiente comando:

python setup.py sdist --format=zip,gztar upload -r pywrapper_config

Al buscar en la lista de paquetes de python se tiene lo que muestra la siguiente figura:

Al darle clic se tiene la siguiente información:


La información que se muestra al empaquetador es la siguiente:


Lo que quedaría a continuación es el proceso para crear un paquete Debian y un rpm.


Referencias:

  1. Distribir aplicaciones python
  2. The Python package index
  3. Distribuyendo programas Python en el Pypi
  4. How to submit a package to Pypi