Quantcast
Channel: PLEDIN 3.0
Viewing all 104 articles
Browse latest View live

Despliegue de una aplicación Python Bottle en Heroku

$
0
0

En una entrada anterior, explicamos cómo trabajar con Heroku, en concreto instalamos un CMS Drupal utilizando la herramienta heroku-cli. En este artículo vamos a desplegar una aplicación web desarrollada en python utilizando el framework bottle utilizando sólo la aplicación web Heroku (Heroku Dashboard).

Heroku es una aplicación que nos ofrece un servicio de Cloud Computing PaaS (Plataforma como servicio). Como leemos en la Wikipedia es propiedad de Salesforce.com y es una de las primeras plataformas de computación en la nube, que fue desarrollada desde junio de 2007, con el objetivo de soportar solamente el lenguaje de programación Ruby, pero posteriormente se ha extendido el soporte a Java, Node.js, Scala, Clojure y Python y PHP. La funcionalidad ofrecida por heroku esta disponible con el uso de dynos, que son una adaptación de los contenedores Linux y nos ofrecen la capacidad de computo dentro de la plataforma.

Este artículo lo escribo como apoyo para la asignatura de Lenguajes de Marcas, que imparto en el ciclo de Grado Superior de Administración de sistemas Informáticos, por lo que vamos a recordar las características de la capa gratuita de Horoku:

  • Podemos crear un dyno, que puede ejecutar un máximo de dos tipos de procesos. Para más información sobre la ejecución de los procesos ver: https://devcenter.heroku.com/articles/process-model.
  • Nuestro dyno utiliza 512 Mb de RAM
  • Tras 30 minutos de inactividad el dyno se para (sleep), además debe estar parado 6 horas cada 24 horas.
  • Podemos utilizar una base de datos postgreSQL con no más de 10.000 registros
  • Para más información de los planes ofrecido por heroku puedes visitar: https://www.heroku.com/pricing#dynos-table-modal

Veamos los pasos que tenemos que realizar para desplegar nuestra aplicación python bottle en Heroku:

Preparativos previos

  • Tenemos que crear una cuenta gratuita en Heroku (singup)
  • Hemos creado una aplicación web con python bottle siguiendo la estructura que puedes encontrar en el repositorio GiHub heroku-in-a-bottle. , de los ficheros que contiene este repositorio podemos destacar:
    • Procfile: En este fichero se define el proceso que va a ejecutar el dyno. Para más información: Process Types and the Procfile
    • requierements.txt: Fichero de texto donde guardamos el nombre los módulos python necesarios para que nuestra aplicación funcionen, y que se van a instalar en el dyno cuando despleguemos la aplicación.
  • Nuestra aplicación que queremos desplegar la tenemos guardada en un repositorio GitHub

Ejecución de nuestra aplicación en local

Mientras estemos desarrollando la aplicación la podemos probar en nuestro ordenador de la siguiente manera:

python app.py 8080
Bottle v0.12.8 server starting up (using WSGIRefServer())...
Listening on http://0.0.0.0:8080/
Hit Ctrl-C to quit.

Creamos una nueva aplicación en Heroku

Tenemos que indicar un nombre único. La URL de nuestra aplicación será: https://pruebajd.herokuapp.com

Conectar nuestro proyecto con GitHub

El contenido que vamos a desplegar en nuestro proyecto se va a copiar desde el repositorio donde tenemos nuestra aplicación, para ello desde la pestaña Deploy vamos a escoger la opción: Connect to GitHub.

A continuación desde GitHub le tenemos que dar permiso a la aplicación Heroku, para que accede a nuestros repositorios:

Ahora tenemos que conectar el repositorio donde tenemos nuestra aplicación:

Tenemos a nuestra disposición dos maneras de hacer los despliegues:

  • Automáticos: Esta opción la podemos habilitar. Cada vez que hagamos un commit en nuestro repositorio GitHub, heroku va  a desplegar la aplicación. Tenemos que elegir la rama que se va desplegar de forma automática.
  • Manual: Elegimos la rama que vamos a desplegar y pulsamos el botón Deploy Branch

Veamos un ejemplo de despliegue manual:

Si todo ha ido bien podremos acceder a nuestra aplicación:

Conclusiones

Aunque tenemos a nuestro disposición una interfaz de línea de comandos muy completa: heroku-cli, he querido explicar de forma muy simple el despliegue de aplicaciones web python bottle en Heroku Dashboard para probar las aplicaciones que van a realizar los alumnos en la asignatura.


Curso de Python3 en OpenWebinars

Curso de infraestructura Cloud con OpenStack. Universidad de Almería

$
0
0

La pasada semana, junto a mi compañero @alberto_molina hemos impartido en la Universidad de Almería, un curso sobre infraestructura Cloud, donde hemos tratado temas como la instalación automática y la administración de OpenStack, y la relación entre OpenStack y los contenedores, centrándonos principalmente en Kubernetes. Puedes acceder al curso completo en la siguiente dirección:

Curso de infraestructura Cloud con OpenStack. Universidad de Almería

Introducción

Instalación automática de OpenStack

Contenedores en OpenStack

Otros

Publicar una página web en Github Pages

$
0
0
Esté artículo lo escribí originalmente en septiembre de 2013. Como el servicio GitHub Pages ha sufrido algunos cambios en su configuración, vuelvo a publicarlo con las modificaciones oportunas.

 

Github Pages es un servicio que te ofrece Github para publicar de una manera muy sencilla páginas web. Disponemos de la opción de generar de forma automática las páginas utilizando una herramienta gráfica, pero en este artículo nos vamos a centrar en la creación y modificación de páginas web usando la línea de comandos con el comando git.

 

Tenemos dos alternativas para crear una página web con esta herramienta:

  • Páginas de usuario u organización: Es necesario crear un repositorio especial donde se va a almacenar todos el contenido del sitio web. Si por ejemplo el nombre de usuario de Github es josedom24, el nombre del repositorio debe ser josedom24.github.io. Todos los ficheros que se van a publicar deben estar en la rama “master“. Por último indicar que la URL para acceder a la ṕagina sería http://josedom24.github.io.
  • Páginas de proyecto o repositorio: A diferencia de las anteriores están asociada a cualquier repositorio que tengamos en Github (por ejemplo supongamos que el repositorio se llama “prueba“). En este caso los ficheros que se van a publicar deben estar en una rama del proyecto llamada gh-pages (Actualización 20/9/2017: Actualmente se pueden publicar páginas web en GitHub Pages desde la rama master, gh-pages o la carpeta /cod de la rama master). La URL de acceso al sitio será http://josedom24.github.io/prueba

Creación manual de páginas

Mientras que las páginas de usuario son fáciles de crear, ya que simplemente debemos crear el repositorio y clonarlo en nuestro equipo (git clone) y empezar a crear ficheros que estarán guardados en la rama master, las páginas de repositorio pueden ser un poco más complejas ya que hay que crear la nueva rama que tenemos que llamar gh-pages, siguiendo el manual de Github Pages los pasos a dar son los siguientes:

Actualización 20/9/2017: En la configuración del repositorio, podemos escoger donde vamos a guardar nuestra página web: la rama master, gh-pages (si el repositorio tiene dicha rama) o la carpeta /cod de la rama master.

En el siguiente ejemplo vamos a crear una rama gh-pages, aunque como hemos indicado anteriormente nos serviría la rama master:

$ git clone https://github.com/user/repository.git
# Clone our repository
# Cloning into 'repository'...
# remote: Counting objects: 2791, done.
# remote: Compressing objects: 100% (1225/1225), done.
# remote: Total 2791 (delta 1722), reused 2513 (delta 1493)
# Receiving objects: 100% (2791/2791), 3.77 MiB | 969 KiB/s, done.
# Resolving deltas: 100% (1722/1722), done.

A continuación tenemos que crear la nueva rama:

$ cd repository
$ git checkout --orphan gh-pages
# Creates our branch, without any parents (it's an orphan!)
# Switched to a new branch 'gh-pages'
$ git rm -rf .
# Remove all files from the old working tree
# rm '.gitignore'

Para terminar subiendo el primer fichero de nuestra página:

$ echo "My GitHub Page" > index.html
$ git add index.html
$ git commit -a -m "First pages commit"
$ git push origin gh-pages

Para publicar nuestra página, cómo indicabamos anteriormente, sólo tendríamos que ir a la configuración del repositorio y activar la opción de GitHub Pages seleccionado la rama gh-page:

Cómo podemos construir nuestras páginas web

La forma más sencilla de construir nuestro sitio es subir a nuestro repositorio todos los ficheros necesarios: ficheros html, hojas de estilos, javascript, imágenes, etc. Si sólo tuviéramos esta opción de edición de páginas no tendríamos grandes ventajas para decidirnos a escoger este servicio de hosting.

Lo que realmente hace esta herramienta una opción muy potente es que Github Pages suporta Jekyll, herramienta escrita en Ruby que nos permite generar, de una forma muy sencilla, ficheros HTML estáticos. Aunque esta herramienta esta pensada para generar blogs, nosotros vamos a utilizar algunas de sus funcionalidades para crear páginas estáticas convencionales.

Usando Jekyll para crear páginas web

La principal característica de Jekylls es la generación de html estático a partir de dos recursos muy simples:

  • Plantillas (templates): Ficheros que contienen el esqueleto de las página html que se van a generar. Estos ficheros normalmente se escriben siguiendo la sintaxis de Liquid.
  • Ficheros de contenido: Normalmente escritos en sintaxis Markdown y que contienen el contenido de la página que se va a generar.

Por lo tanto una vez que tengo definidas mis plantillas, lo único que tengo que hacer es centrarme en  el contenido escribiendo los diferentes de ficheros de contenido.

Usando plantillas

Las plantillas son ficheros de texto con extensión html. Deben estar guardados en un directorio _layouts creado en la raíz de nuestro repositorio. Además del contenido html de las páginas que se van a generar, se pueden indicar distintas etiquetas que se sustituirán por diferentes valores. Veamos algunas etiquetas:

  • La etiqueta más importante es {{ content }} que es sustituida por el contenido que definimos en los ficheros de contenido.
  • La etiqueta {{ site.path }} será sustituida por el path del repositorio.
  • Además se podrá definir en los ficheros de contenidos distintas variables que podrán ser sustituidas con etiquetas del tipo {{ page.nombredevariable }}.

Todas las referencia a ficheros de hojas de estilo, javascripts o imágenes que se definan en la plantilla deben estar guardados en nuestro repositorio.

Ejemplo de plantilla

Usando Markdown para escribir el contenido de nuestras páginas

Los distintos contenidos de nuestras páginas serán definidos en ficheros Maarkdown con extensión md. La sintaxis de este lenguaje de marcas es muy sencilla y fácilmente convertible a html. Para practicar las distintas opciones puedes usar este editor online.

Sin entrar a definir la sintaxis del lenguaje, sí nos vamos fijar en la primera parte de los ficheros donde se define la plantilla que se va a utilizar, y las distintas variables que son accesibles desde la plantilla:

---
layout: index

title: Servicios de red
tagline: CFGM SMR
---

Con la variable layout indicamos el nombre del fichero html que corresponde a la plantilla que se va a usar para generar la página. Además hemos definido dos variables cuyo valor es accesible desde la plantilla con las etiquetas {{ pages.title }} y {{ page.tagline }}.

Por último indicar como se accede a las distintas páginas, suponiendo que tenemos definido una página de usuario en la URL josedom24.github.io, si tenemos un fichero en la raíz proyecto.md, sería accesible con la URL josedom24.github.io/proyecto. Si el fichero proyecto.md esta dentro de una carpeta llamada “ejemplo”, sería accesible con la URL josedom24.github.io/ejemplo/proyecto. De forma similar a como funcionan los servidores web si tenemos un fichero index.md no será necesario indicar el nombre en la URL.

Ejemplo de fichero Markdown

Conclusiones

Este artículo presenta la experiencia que he tenido con el servicio Github Pages este fin de semana, por lo tanto soy consciente de que es sólo una introducción a todas las posibilidades que nos ofrece esta manera de mantener de una forma muy sencilla nuestra página web. No obstante espero que  sea de utilidad. Doy por hecho que el lector conoce la forma de trabajar con Git y Github, sí no es así recomiendo algún tutorial que puedes encontrar en internet.

Configurando el servidor de Pledin con IPv6

$
0
0

En estos últimos días he migrado mis páginas personales a un nuevo servidor dedicado de OVH. Anteriormente las tenía alojado en OpenShift con un plan de pago muy asequible, pero con el fin del servicio de la versión 2 de OpenShift y la llegada de la última versión, las condiciones del plan gratuito sólo sirven para pruebas y el plan de pago de la nueva versión no me lo puedo permitir. Una de las cosas que me ha gustado del servidor VPS de OVH es que te asignan una dirección ipv6 global para tu máquina y en este artículo voy a explicar cómo he configurado los diferentes servicios para que mi máquina sea accesible con ipv6.

Configuración estática con la dirección ipv6 asignada

En el panel de control de OVH podemos obtener la IPv6 que nos han asignado para nuestra máquina:

Aunque la dirección ipv4 la toma por asignación dinámica, la dirección ipv6 la tenemos que configurar de forma estática en nuestro sistema, por lo tanto editamos el fichero /etc/network/interfaces de la siguiente manera:

iface ens3 inet6 static
 address 2001:41d0:0302:2200::1c09/64
 gateway 2001:41d0:0302:2200::1

Reiniciamos el servicio de red y comprobamos las direcciones y las rutas de encaminamiento:

# systemctl restart networking

# ip -6 a
...
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
 inet6 2001:41d0:302:2200::1c09/64 scope global
 valid_lft forever preferred_lft forever
 inet6 fe80::f816:3eff:fe77:5587/64 scope link
 valid_lft forever preferred_lft forever

# ip -6 r
2001:41d0:302:2200::/64 dev ens3 proto kernel metric 256 pref medium
fe80::/64 dev ens3 proto kernel metric 256 pref medium
default via 2001:41d0:302:2200::1 dev ens3 metric 1024 pref medium

Y probamos que ya tenemos conectividad hacía el exterior:

# ping6 ipv6.google.com
PING ipv6.google.com(par21s04-in-x0e.1e100.net (2a00:1450:4007:811::200e)) 56 data bytes
64 bytes from par21s04-in-x0e.1e100.net (2a00:1450:4007:811::200e): icmp_seq=1 ttl=52 time=5.06 ms
...

De la misma manera podemos comprobar desde el exterior que tenemos acceso a nuestra ipv6:

$ ping6 2001:41d0:0302:2200::1c09
PING 2001:41d0:0302:2200::1c09(2001:41d0:302:2200::1c09) 56 data bytes
64 bytes from 2001:41d0:302:2200::1c09: icmp_seq=1 ttl=53 time=69.5 ms

Dando a conocer nuestra ipv6 al mundo

Es la hora de configurar nuestro servidor DNS para que podamos acceder a nuestra dirección ipv6 con el nombre de nuestra máquina, para ello hemos añadido un registro AAAA, que podemos consultar de la siguiente manera:

$ host playerone.josedomingo.org
playerone.josedomingo.org has address 137.74.161.90
playerone.josedomingo.org has IPv6 address 2001:41d0:302:2200::1c09

Por lo tanto cómo el nombre de nuestra página web es un alias (registro CNAME) del anterior:

$ host www.josedomingo.org
www.josedomingo.org is an alias for playerone.josedomingo.org.
playerone.josedomingo.org has address 137.74.161.90
playerone.josedomingo.org has IPv6 address 2001:41d0:302:2200::1c09

Si necesito enviar correos electrónicos desde mi servidor es muy recomendable tener definido los registros inversos de nuestras direcciones ip, eso lo podemos hacer desde el panel de control de OVH:

Por lo tanto podemos consultar dicho registro inverso para la dirección ipv6:

$ host 2001:41d0:302:2200::1c09
9.0.c.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.2.2.0.3.0.0.d.1.4.1.0.0.2.ip6.arpa domain name pointer playerone.josedomingo.org.

¿Podemos acceder a nuestra página web desde un cliente ipv6?

La última versión del servidor web apache2 está configurada por defecto para responder peticiones en el puerto 80 o en el 443 en ipv4 y en ipv6. Lo podemos comprobar de la siguiente manera:

# netstat -putan
...
tcp6 0 0 :::80 :::* LISTEN 17981/apache2
tcp6 0 0 :::443 :::* LISTEN 17981/apache2

Si accedemos desde un equipo con ipv6 a nuestra página podemos comprobar que el acceso se está haciendo por ipv6:

# tailf /var/log/access.log
...
2001:470:1f12:aac::2 - - [24/Sep/2017:19:58:01 +0200] "GET / HTTP/1.0" 301 549 "-" "Lynx/2.8.9dev.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/3.3.8"

Conclusiones

De una manera muy sencilla hemos configurado nuestro servidor VPS de OVH con una dirección global ipv6 que permite acceder a servicios del exterior. También hemos configurado nuestros servicios de forma adecuada para poder acceder a nuestro servicios, actualmente sólo el servidor web) desde el exterior usando ipv6.

 

Curso de Flask microframework python en OpenWebinars

Introducción a PHP-FPM

$
0
0

FPM (FastCGI Process Manager) es una implementación alternativa al PHP FastCGI. FPM se encarga de interpretar código PHP. Aunque normalmente se utiliza junto a un servidor web (Apache2 o ngnix), en este artículo vamos a hacer una introducción a PHP-FPM de manera aislada, vamos a estudiar algunos parámetros de configuración y estudiar su funcionamiento.

Para instalarlo en Debian 9:

apt install php7.0-fpm php7.0

Configuración de php-fpm

Con esto hemos instalado php 7.0 y php-fpm. Veamos primeros algunos ficheros de configuración de php:

La configuración de php está dividida según desde se use:

  • /etc/php/7.0/cli: Configuración de php para php7.0-cli, cuando se utiliza php desde la línea de comandos.
  • /etc/php/7.0/fpm: Configuración de php para php-fpm
  • /etc/php/7.0/mods-available: Módulos disponibles de php que puedes estar configurados en cualquiera de los escenarios anteriores.

Si nos fijamos en la configuración de php para php-fpm:

  • /etc/php/7.0/fpm/conf.d: Módulos instalados en esta configuración de php (enlaces simbólicos a /etc/php/7.0/mods-available).
  • /etc/php/7.0/fpm/php-fpm.conf: Configuración general de php-fpm.
  • /etc/php/7.0/fpm/php.ini: Configuración de php para este escenario.
  • /etc/php/7.0/fpm/pool.d: Directorio con distintos pool de configuración. Cada aplicación puede tener una configuración distinta (procesos distintos) de php-fpm.

Por defecto tenemos un pool cuya configuración la encontramos en /etc/php/7.0/fpm/pool.d/www.conf, en este fichero podemos configurar muchos parámetros, los más importantes son:

  • [www]: Es el nombre del pool, si tenemos varios, cada uno tiene que tener un nombre.
  • user y grorup: Usuario y grupo con el que se va ejecutar los procesos.
  • listen: Se indica el socket unix o el socket TCP donde van a escuchar los procesos:

    • Por defecto, escucha por un socket unix: listen = /run/php/php7.0-fpm.sock
    • Si queremos que escuche por un socket TCP: listen = 127.0.0.1:9000
    • En el caso en que queramos que escuche en cualquier dirección: listen = 9000
  • Directivas de procesamiento, gestión de procesos:

    • pm: Por defecto igual a dynamic (el número de procesos se crean y destruyen de forma dinámica). Otros valores: static o ondemand.
    • Otras directivas: pm.max_children, pm.start_servers, pm.min_spare_servers,…
  • pm.status_path = /status: No es necesaria, pero vamos a activar la URL de status para comprobar el estado del proceso.

Por último reiniciamos el servicio:

systemctl restart php7.0-fpm

Pruebas de funcionamiento

  1. Suponemos que tenemos configurado por defecto, por lo tanto los procesos están escuchando en un socket UNIX:

    listen = /run/php/php7.0-fpm.sock
    

    Para enviar ficheros php a los procesos para su interpretación vamos a utilizar el programa cgi-fcgi:

    apt-get install libfcgi0ldbl
    

    Y a continuación accedemos a la URL /status, para ello:

    SCRIPT_NAME=/status SCRIPT_FILENAME=/status REQUEST_METHOD=GET cgi-fcgi -bind -connect /run/php/php7.0-fpm.sock
    
    Expires: Thu, 01 Jan 1970 00:00:00 GMT
    Cache-Control: no-cache, no-store, must-revalidate, max-age=0
    Content-type: text/plain;charset=UTF-8
    
    pool:                 www
    process manager:      dynamic
    start time:           13/Nov/2017:19:32:50 +0000
    start since:          38
    accepted conn:        6
    listen queue:         0
    max listen queue:     0
    listen queue len:     0
    idle processes:       1
    active processes:     1
    total processes:      2
    max active processes: 1
    max children reached: 0
    slow requests:        0
    

    Si queremos ejecutar un fichero php, vamos a crear un directorio /var/www y vamos a guardar un fichero holamundo.php con el siguiente contenido:

    <?php echo "Hola Mundo!!!";?>
    

    A continuación vamos a indicar el directorio de trabajo en el fichero /etc/php/7.0/fpm/pool.d/www.conf:

    chroot = /var/www
    

    Inicializamos el servicio:

    systemctl restart php7.0-fpm
    

    Y podríamos ejecutar el fichero de la siguiente manera:

    SCRIPT_NAME=/holamundo.php SCRIPT_FILENAME=/holamundo.php REQUEST_METHOD=GET cgi-fcgi -bind -connect /run/php/php7.0-fpm.sock
    
    Content-type: text/html; charset=UTF-8
    
    Hola Mundo!!!
    
  2. Si suponemos que hemos configurado php-fpm para que escuche en un socket TCP:

    listen = 127.0.0.1:9000
    

    Para realizar las pruebas que hemos probado anteriormente:

    SCRIPT_NAME=/status SCRIPT_FILENAME=/status REQUEST_METHOD=GET cgi-fcgi -bind -connect 127.0.0.1:9000
    
    SCRIPT_NAME=/holamundo.php SCRIPT_FILENAME=/holamundo.php REQUEST_METHOD=GET cgi-fcgi -bind -connect 127.0.0.1:9000
    

Conclusiones

En muchas ocasiones cuando se instala apchache2 o ngninx junto a php-fpm nos cuesta entender como funciona la interpretación del código PHP por medio de php-fpm. en este artículo he tratado de hacer una introducción a este servicio de manera independiente al servidor web para que se entienda un poco mejor. Espero que haya sido de utilidad.

Vistas (views) en el servidor DNS Bind9

$
0
0


En alguna circunstancia nos puede interesar que un mismo nombre que resuelve nuestro DNS devuelva direcciones IP distintas según en que red este conectada el cliente que realiza la consulta. Por ejemplo:

  • Si tenemos un servidor DNS que da servicio a internet y a intranet. Si se realiza la consulta desde internet, por ejemplo al nombre www.example.org debe devolver a la IP pública, sin embargo si se hace la misma consulta desde la intranet la resolución deberá ser a una IP privada.
  • Otro ejemplo es en la resolución de nombres de instancias de OpenStack. En este caso una instancia tiene asignada una IP privada en el rango en el que hemos configurado la red interna, además puede tener asignada una IP flotante en el rango de la configuración que hayamos hecho de la red externa. En este caso sería deseable que cuando hago una consulta desde el exterior me resuelva con la IP flotante, y cuando haga una consulta desde otra instancia conecta da a la red interna me devuelva la dirección fija asignada a la instancia.

Vamos a ver un ejemplo del uso de vistas (views) en bind9 para configurar dos zonas diferenciadas para el mismo nombre de dominio y que se utilicen según desde donde se solicite la resolución.

Escenario de nuestro ejemplo

Hemos creado una instancia en OpenStack con el siguiente esquema de red:

La red interna tiene como direccionamiento 10.0.0.0/24 y la red externa 172.22.0.0/16. Vamos a configurar bind9 para que cuando se consulte el nombre del servidor desde la red externa devuelva la ip flotante (172.22.0.129) y cuando la consulta se realice desde la red interna se devuelva la ip fija (10.0.0.13).

Uso de vistas en bind9

Al utilizar “views” en bind9 vamos a tener zonas diferencias según el origen de la consulta. Por lo tanto vamos a crear dos “acl” par filtrar las redes desde las que se hacen las consultas y posteriormente dentro de cada vista definiremos las zonas con autoridad del servidor. De esta manera el fichero /etc/bind/named.conf.local quedaría de la siguiente manera:

acl interna { 10.0.0.0/24; localhost; };
acl externa { 172.22.0.0/16; };
view todas {
    match-clients { interna; externa;};


};

view interna {
    match-clients { interna; };
    allow-recursion { any; };

        zone "example.org"
        {
                type master;
                file "db.interna.example.org";
        };
        zone "0.0.10.in-addr.arpa"
        {
                type master;
                file "db.0.0.10";
        };
        include "/etc/bind/zones.rfc1918";
        include "/etc/bind/named.conf.default-zones";
};

view externa {
    match-clients { externa; };
    allow-recursion { any; };

        zone "example.org"
        {
                type master;
                file "db.externa.example.org";
        };
        zone "22.172.in-addr.arpa"
        {
                type master;
                file "db.22.172";
        };
        include "/etc/bind/zones.rfc1918";
        include "/etc/bind/named.conf.default-zones";
};

Hemos creado las dos “acl” para diferenciar las redes por las que vamos a consultar:

acl interna { 10.0.0.0/24; localhost; };
acl externa { 172.22.0.0/16; };

La zona que se va a utilizar será la definida en la vista donde hayamos definidos las IP desde las que se hace las consultas:

match-clients { interna; };

Otro detalle que tenemos que tener en cuanta al utilizar vistas, es que todas las zonas definidas deben estar dentro de una zona, por lo tanto las zonas de resolución inversa definidas en el RFC1918 y las zonas por defecto, la hemos incluido en cada una de las vistas:

Y no se nos puede olvidar eliminar las zonas por defecto del fichero named.conf:

include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";
//include "/etc/bind/named.conf.default-zones";

La configuración de la zona directa para la vista interna quedaría el fichero /var/cache/bind/db.interna.example.org:

$TTL    86400
@   IN  SOA instancia1.example.org. root.example.org. (
                  1     ; Serial
             604800     ; Refresh
              86400     ; Retry
            2419200     ; Expire
              86400 )   ; Negative Cache TTL
;
@   IN  NS  instancia1.example.org.
instancia1.example.org. IN  A   10.0.0.13

De forma similar la vista externa de la zona de resolución directa en el fichero /var/cache/bind/db.externa.example.org:

$TTL    86400
@   IN  SOA instancia1.example.org. root.example.org. (
                  1     ; Serial
             604800     ; Refresh
              86400     ; Retry
            2419200     ; Expire
              86400 )   ; Negative Cache TTL
;
@   IN  NS  instancia1.example.org.
instancia1.example.org. IN  A   172.22.200.129

Y las zonas de resolución inversa para las dos vistas serían:

En le fichero /var/cache/bind/db.0.0.10:

$TTL    86400
@   IN  SOA instancia1.example.org. root.example.org. (
                  1     ; Serial
             604800     ; Refresh
              86400     ; Retry
            2419200     ; Expire
              86400 )   ; Negative Cache TTL
;
@   IN  NS  instancia1.example.org.

13.0.0.10.in-addr.arpa. IN  PTR instancia1.example.org.

Y en el fichero /var/cache/bind/db.22.172:

$TTL    86400
@   IN  SOA instancia1.example.org. root.example.org. (
                  1     ; Serial
             604800     ; Refresh
              86400     ; Retry
            2419200     ; Expire
              86400 )   ; Negative Cache TTL
;
@   IN  NS  instancia1.example.org.

129.200.22.172.in-addr.arpa.    IN  PTR instancia1.example.org.

Pruebas de funcionamiento

Si hacemos una consulta a nuestro servidor desde la red 10.0.0.0/24:

dig @10.0.0.13 instancia1.example.org

; <<>> DiG 9.10.3-P4-Debian <<>> @10.0.0.13 instancia1.example.org
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30042
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;instancia1.example.org.        IN  A

;; ANSWER SECTION:
instancia1.example.org. 86400   IN  A   10.0.0.13

;; Query time: 0 msec
;; SERVER: 10.0.0.13#53(10.0.0.13)
;; WHEN: Thu Dec 28 12:45:48 UTC 2017
;; MSG SIZE  rcvd: 67

Si por el contrario hacemos la consulta desde la red 172.22.0.0/16:

dig @172.22.200.129 instancia1.example.org

; <<>> DiG 9.10.3-P4-Debian <<>> @172.22.200.129 instancia1.example.org
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 42980
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;instancia1.example.org.        IN  A

;; ANSWER SECTION:
instancia1.example.org. 86400   IN  A   172.22.200.129

;; Query time: 1 msec
;; SERVER: 172.22.200.129#53(172.22.200.129)
;; WHEN: Thu Dec 28 12:44:52 UTC 2017
;; MSG SIZE  rcvd: 67

Y las consultas inversa de la misma manera, desde la red interna:

dig @10.0.0.13 -x 10.0.0.13

; <<>> DiG 9.10.3-P4-Debian <<>> @10.0.0.13 -x 10.0.0.13
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19579
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;13.0.0.10.in-addr.arpa.        IN  PTR

;; ANSWER SECTION:
13.0.0.10.in-addr.arpa. 86400   IN  PTR instancia1.example.org.

;; Query time: 0 msec
;; SERVER: 10.0.0.13#53(10.0.0.13)
;; WHEN: Thu Dec 28 12:48:21 UTC 2017
;; MSG SIZE  rcvd: 87

Y desde la red externa:

dig @172.22.200.129 -x 172.22.200.129

; <<>> DiG 9.10.3-P4-Debian <<>> @172.22.200.129 -x 172.22.200.129
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 52169
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;129.200.22.172.in-addr.arpa.   IN  PTR

;; ANSWER SECTION:
129.200.22.172.in-addr.arpa. 86400 IN   PTR instancia1.example.org.

;; Query time: 1 msec
;; SERVER: 172.22.200.129#53(172.22.200.129)
;; WHEN: Thu Dec 28 12:46:55 UTC 2017
;; MSG SIZE  rcvd: 92

Conclusiones

Si ofrecemos servicios a distintas redes (distinto direccionamiento), usar las vistas en el servidor DN bind9 nos puede ayudar a utilizar un mismo nombre para cada servicio independientemente desde donde se haga la consulta. La resolución de cada nombre dependerá del origen de la consulta.


Configuración de un proxy inverso con Apache 2.4

$
0
0

Un proxy inverso es un tipo de servidor proxy que recupera recursos en nombre de un cliente desde uno o más servidores. Por lo tanto el cliente hace la petición al puerto 80 del proxy, y éste es el que hace la petición al servidor web que normalmente está en una red interna no accesible desde el cliente.

Apache como proxy inverso

Apache2.4 puede funcionar como proxy inverso usando el módulo proxy junto a otros módulos, por ejemplo:

  • proxy_http: Para trabajar con el protocolo HTTP.
  • proxy_ftp: Para trabajar con el protocolo FTP.
  • proxy_html: Permite reescribir los enlaces HTML en el espacio de direcciones de un proxy.
  • proxy_ajp: Para trabajar con el protocolo AJP para Tomcat.

Por lo tanto, para empezar, vamos activar los módulos que necesitamos:

# a2enmod proxy proxy_http

Ejemplo de utilización de proxy inverso

Tenemos a nuestra disposición un servidor interno (no accesible desde el cliente) en la dirección privada, con el nombre de interno.example.org. Tenemos un servidor que va a funcionar de proxy, llamado proxy.example.org con dos interfaces de red: una pública conectada a la red donde se encuentra el cliente, y otra interna conectada a la red donde se encuentra el servidor interno.

Sirviendo una página estática

En nuestro servidor interno hemos creado un virtual host para servir una página estática, index.html, con este contenido:

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Probando proxy inverso con Apache2</title>
</head>
<body>
        <h1>Probando proxy inverso con Apache2</h1>
        <img src="img.jpg"/>
        <img src="/img.jpg"/>
        <br/>
        <a href="http://10.0.0.6/carpeta/index.html">Enlace tipo 1</a><br/>
        <a href="/carpeta/index.html">Enlace tipo 2</a><br/>
        <a href="carpeta/index.html">Enlace tipo 3</a>
</body>

Vamos a utilizar la directiva ProvyPass en el fichero de configuración del virtual host, de la siguiente forma:

ProxyPass "/web/" "http://interno.example.org/"

También lo podemos configurar de forma similar con:

<Location "/web/">
    ProxyPass "http://interno.example.org/"
</Location>

Evidentemente debe funcionar la resolución de nombre para que el proxy pueda acceder al servidor interno.

De esta manera al acceder desde el cliente la URL http://proxy.example.org/web/ se mostraría la página que se encuentra en el servidor interno.

Como vemos una imagen no se ha cargado, además no todos los enlaces funcionan, pero antés vamos a solucionar el problema de las redirecciones.

El probelma de las redirecciones

Cuando creamos una redirección en un servidor web y el cliente intenta acceder al recurso, el servidor manda una respuesta con código de estado 301 o 302, e indica la URL de la nueva ubicación del recurso en una cabecera HTTP llamada Location.

Si hemos configurado una redirección en el servidor interno, cuando se accede al recurso a través del proxy, la redirección se realiza pero la cabecera Location viene referencia la dirección del servidor interno, por lo que el cliente es incapaz de acceder a la nueva ubicación. Al acceder a ´http://proxy.example.org/web/directorio´ se produce una redirección pero como vemos la nueva url hace referencia al servidor interno por lo que no funciona:

Para solucionarlo utilizamos la directiva ProxyPassReverse que se encarga de reescribir la URL de la cabecera Location.

La configuración quedaría:

ProxyPass "/web/" "http://interno.example.org/"
ProxyPassReverse "/web/" "http://interno.example.org/"

O de esta otra forma:

<Location "/web/">
    ProxyPass "http://interno.example.org/"
    ProxyPassReverse "http://interno.example.org/"
</Location>

Por lo que ya podemos hacer la redirección de forma correcta:

El problema de las rutas HTML

La página que servimos a través del proxy que se guarda en el servidor interno puede tener declarada rutas, por ejemplo en imágenes o enlaces. Nos podemos encontrar con diferentes tipos de rutas:

  • http://10.0.0.6/carpeta/index.html: Una ruta absoluta donde aparece la dirección del servidor interno y que evidentemente el cliente no va a poder seguir.
  • /carpeta/index.html: Una ruta absoluta, referenciada a la raíz del DocumentRoot.
  • carpeta/index.html: Una ruta relativa.

Si tenemos una ruta relativa, el cliente la va a poder seguir sin problema cuando accede a través del proxy, pero si tenemos una ruta como la segunda no lo va a poder hacer, porque en el DocumentRoot del proxy no existe este recurso. Al acceder al segundo enlace:

Para solucionar este problema debemos reescribir el HTML para cambiar la referencia del enlace. Para ello necesitamos activar un nuevo módulo:

# a2enmod proxy_html

Y realizar la siguiente configuración:

ProxyPass "/web/"  "http://interno.example.org/"
ProxyPassReverse "/web/"  "http://interno.example.org/"
ProxyHTMLURLMap http://interno.example.org /web
<Location /web/>
    ProxyPassReverse /
    ProxyHTMLEnable On
    ProxyHTMLURLMap / /web/
</Location>

Como vemos hemos configurado un proxy para HTML, que será responsable de reescribir todos las rutas que contiene el HYML, utilizando la directiva ProxyHTMLURLMap:

ProxyHTMLURLMap http://interno.example.org /web

Es importante no poner la barra final, cuando se encuentra una ruta que coincide con el primer patrón se reescribe con el segundo, esta regla reescribe las ruta del tipo de la primera opción que hemos visto anteriormente. Para arreglar la rutas de la segunda opción, utilizamos dentro de la sección Location:

ProxyHTMLURLMap / /web/

Después de iniciar comprobamos que al intentar acceder al proxy obtenemos un error en el navegador del cliente “Error de codificación de contenido”.

Sirviendo contenido multimedia

Acabamos de configurar un proxy que examina y reescribe el HTML de nuestro sitio web, pero evidentemente existe más contenido en nuestro sitio que no es HTML y no debería ser procesado por proxy_html. Esto se soluciona verificando la cabecera del contenido y rechazando todos los contenidos que no tengan el tipo MIME adecuado.

Pero tenemos un problema: normalmente se comprime el contenido HTML, y encontramos cabeceras de este tipo:

Content-Type: text/html
Content-Encoding: gzip

Este contenido no debería pasar por el analizador de proxy_html. Para solucionar esto podemos negarnos a admitir la compresión. La eliminación de cualquier cabecera de petición Accept-Encoding hace el trabajo. Para ello podemos utilizar la directiva RequestHeader del módulos headers, por lo tanto activamos el módulo:

# a2enmod headers

Y usamos la directiva RequestHeader dentro del la sección Location:

ProxyPass "/web/"  "http://interno.example.org/"
ProxyPassReverse "/web/"  "http://interno.example.org/"
ProxyHTMLURLMap http://interno.example.org /web
<Location /web/>
    ProxyPassReverse /
    ProxyHTMLEnable On
    ProxyHTMLURLMap / /web/
    RequestHeader unset Accept-Encoding
</Location>

Ahora si podemos acceder a la página completa a través del proxy.

Sirviendo contenido con HTTPS

Una situación similar surge en el caso del contenido encriptado (https). En este caso, usando el módulo ssl y un certificado en el proxy, de modo que la sesión segura real se encuentre entre el navegador y el proxy, no al servidor interno.

flask: Miniframework python para desarrollar páginas web (1ª parte)

$
0
0

Flask es un “micro” framework escrito en Python y concebido para facilitar el desarrollo de aplicaciones Web bajo el patrón MVC.

¿Por qué usar flask?

  • Flask es un “micro” framework: se enfoca en proporcionar lo mínimo necesario para que puedas poner a funcionar una aplicación básica en cuestión de minutos. Se necesitamos más funcionalidades podemos extenderlo con las Flask extensions.
  • Incluye un servidor web de desarrollo para que puedas probar tus aplicaciones sin tener que instalar un servidor web.
  • También trae un depurador y soporte integrado para pruebas unitarias.
  • Es compatible con python3, por lo tanto podemos usar la codificación de caracteres unicode, y 100% compatible con el estándar WSGI.
  • Buen manejo de rutas: Con el uso de un decorador python podemos hacer que nuestra aplicación con URL simples y limpias.
  • Flask soporta el uso de cookies seguras y el uso de sesiones.
  • Flask se apoya en el motor de plantillas Jinja2, que nos permite de forma sencilla renderizar vistas y respuestas.
  • Flask no tiene ORMs, wrappers o configuraciones complejas, eso lo convierte en un candidato ideal para aplicaciones ágiles o que no necesiten manejar ninguna dependencia. Si necesitas trabajar con base de datos sólo tenemos que utilizar una extensión.
  • Este framework resulta ideal para construir servicios web (como APIs REST) o aplicaciones de contenido estático.
  • Flask es Open Source y está amparado bajo una licencia BSD.
  • Puedes ver el código en Github, la documentación es muy completa y te puedes suscribir a su lista de correos para mantenerte al día de las actualizaciones.

Instalación de flask

Vamos a realizar la instalación de Flask utilizando la herramienta pip en un entorno virtual creado con virtualenv. La instalación de Flask depende de dos paquetes: Werkzeug, una librería WSGI para Python y jinja2 como motor de plantillas.

Creando el entorno virtual

Como Flask es compatible con python3 vamos a crear un entorno virtual compatible con la versión 3 del interprete python. Para ello nos aseguremos que tenemos la utilidad instalada:

# apt-get install python-virtualenv

Y creamos el entorno virtual:

$ virtualenv -p /usr/bin/python3 flask

Para activar nuestro entorno virtual:

$ source flask/bin/activate
(flask)$ 

Y a continuación instalamos Flask:

(flask)$ pip install Flask

Si nos aparece el siguiente aviso durante la instalación:

WARNING: The C extension could not be compiled, speedups are not enabled.
Failure information, if any, is above.
Retrying the build without the C extension now.

La instalación se realiza bien, pero no se habilita el aumento de rendimiento de jinja2.

Puedes volver a realizar la instalación después de instalar el siguiente paquete:

# apt-get install python3-dev

Al finalizar podemos comprobar los paquetes python instalados:

(flask)$ pip freeze
Flask==0.12.2
Jinja2==2.9.6
MarkupSafe==1.0
Werkzeug==0.12.2
click==6.7
itsdangerous==0.24

Podemos guardar las dependencias en un fichero requirements.txt:

# pip freeze > requirements.txt

La utilización del fichero ˋrequirements.txtˋ, donde vamos a ir guardando los paquetes python (y sus versiones) de nuestra instalación, nos va a posibilitar posteriormente poder crear otro entrono virtual con los mismos paquetes:

# pip install -r requirements.txt

Y finalmente comprobamos la versión de flask que tenemos instalada:

(flask)$ flask --version
Flask 0.12.2
Python 3.4.2 (default, Oct  8 2014, 10:45:20) 
[GCC 4.9.1]

Corriendo una aplicación sencilla

Escribimos nuestra primera aplicación flask, en un fichero app.py:

from flask import Flask
app = Flask(__name__)   

@app.route('/')
def hello_world():
    return 'Hello, World!'

if __name__ == '__main__':
    app.run()
  1. El objeto app de la clase Flask es nuestra aplicación WSGI, que nos permitirá posteriormente desplegar nuestra aplicación en un servidor Web. Se le pasa como parámetro el módulo actual (__name__).
  2. El decorador router nos permite filtrar la petición HTTP recibida, de tal forma que si la petición se realiza a la URL / se ejecutará la función vista hello_word.
  3. La función vista que se ejecuta devuelve una respuesta HTTP. En este caso devuelve una cadena de caracteres que se será los datos de la respuesta.
  4. Finalmente si ejecutamos este módulo se ejecuta el método run que ejecuta un servidor web para que podamos probar la aplicación.

De esta forma podemos ejecutar nuestra primera aplicación:

$ python3 app.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Y podemos acceder a la URL http://127.0.0.1:5000/ desde nuestro navegador y ver el resultado.

O podemos ejecutar:

$ curl http://127.0.0.1:5000
Hello, World!

Configuración del servidor web de desarrollo

Podemos cambiar la dirección y el puerto desde donde nuestro servidor web va a responder. Por ejemplo si queremos acceder a nuestra aplicación desde cualquier dirección en el puerto 8080:

...
app.run('0.0.0.0',8080)

$ python3 app.py
* Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)

Modo “debug”

Si activamos este modo durante el proceso de desarrollo de nuestra aplicación tendremos a nuestra disposición una herramienta de depuración que nos permitirá estudiar los posibles errores cometidos, además se activa el modo “reload” que inicia automáticamente el servidor de desarrollo cuando sea necesario. Para activar este modo:

...
app.run(debug=True)

$ python3 app.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 106-669-497

El Debugger PIN lo utilizaremos para utilizar la herramienta de depuración.

En la proxíma entrada seguiremos trabajando con flask y aprenderemos a trabajar con las rutas que nuestra aplicación va a responder.

flask: Enrutamiento (2ª parte)

$
0
0

Enrutamiento: rutas

El objeto Flask app nos proporciona un decorador router que es capaz de filtrar la función vista que se va ejecutar analizando la petición HTTP, fundamentalmente por la ruta y el método que se hace la petición.

Trabajando con rutas

Veamos un ejemplo:

...
@app.route('/')
def inicio():
    return 'Página principal'   

@app.route('/articulos/')
def articulos():
    return 'Lista de artículos' 

@app.route('/acercade')
def acercade():
    return 'Página acerca de...'

En este caso se comprueba la ruta de la petición HTTP, y cuando coincide con alguna indicada en las rutas se ejecuta la función correspondiente devolviendo una respuesta HTTP.

Si declaramos rutas terminando en / son consideradas como un directorio de un sistema de fichero, en este caso si se accede a la ruta sin la barra final se producirá una redirección a la ruta correcta.

Si declaramos la rutas sin / final, se consideran un fichero del sistema de fichero, si accedemos a la ruta con el / nos devolverá una respuesta con código 404.

Si la ruta de la petición HTTP no corresponde con ninguna que hayamos indicado se devolverá una respuesta con código de estado 404 indicando que no se ha encontrado el recurso.

Rutas dinámicas

Podemos gestionar rutas variables, es decir que correspondan a un determinado patrón o expresión regular, por ejemplo:

@app.route("/articulos/<int:id>")
def mostrar_ariculo(id):
    return 'Vamos a mostrar el artículo con id:{}'.format(id)

Otro ejemplo:

@app.route("/hello/")
@app.route("/hello/<string:nombre>")
@app.route("/hello/<string:nombre>/<int:edad>")
def hola(nombre=None,edad=None):
    if nombre and edad:
        return 'Hola, {0} tienes {1} años.'.format(nombre,edad)
    elif nombre:
        return 'Hola, {0}'.format(nombre)
    else:
        return 'Hola mundo'

La parte dinámica de la ruta la podemos obtener como variable que recibe la función correspondiente. En el segundo ejemplo, además observamos que varias rutas pueden ejecutar una misma función. Aunque no es obligatorio podemos especificar el tipo de la variable capturada:

  • string: Acepta cualquier texto sin barras (por defecto)
  • int: Acepta enteros
  • float: Acepta valores reales
  • path: Acepta cadena de caracteres con barras

Construcción de rutas

Podemos importar la función url_for que nos permite construir rutas a partir del nombre de la función asociada. De esta manera en la siguiente ruta voy a generar tres enlaces a las rutas dinámicas que hemos visto anteriormente:

from flask import Flask, url_for
...
@app.route("/enlaces")
def enlaces():
        cad='<a href="{0}">Decir hola</a>({0})<br/>'.format(url_for("hola"))
        cad=cad+'<a href="{0}">Decir hola a pepe</a>({0})<br/>'.format(url_for("hola",nombre="pepe"))
        cad=cad+'<a href="{0}">Decir hola a pepe de 16 años</a>({0})'.format(url_for("hola",nombre="pepe",edad=16))
        return cad
...

Si accedemos a la URL observamos los enlaces y las URL a las que se enlazan:

Enrutamiento: Métodos

Para acceder a las distintas URLs podemos utilizar varios métodos en nuestra petición HTTP. En nuestros ejemplos vamos a trabajar con el método GET y POST, que son los métodos que normalmente podemos utilizar desde un navegador web.

  • GET: Se realiza una petición para obtener un recurso del servidor web. Es el método más utilizado.
  • POST: Aunque con el método GET también podemos mandar información al servidor (por medio de parámetros escritas en la URL), utilizamos el método POST para enviar información a una determinada URL. Normalmente utilizamos los formularios HTML para enviar información al servidor por medio del método POST.

Por defecto las rutas indicadas en la funciones route sólo son accesibles utilizando el método GET. Si una URL recibe información por medio del método POST y no queremos que se acceda a ella con un método GET, se definirá de la siguiente manera:

@app.route('/articulos/new',methods=["POST"])
def articulos_new():
    return 'Está URL recibe información de un formulario con el método POST'

También en muchas ocasiones es deseable acceder a una URL con los dos métodos, de tal manera que haremos una cosa cuando acedemos con GET y haremos otra cuando se acceda con POST. Ejemplo:

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        return 'Hemos accedido con POST'
    else:
        return 'Hemos accedido con GET'

En la próxima entrada estudiaremos como Flask gestiona las peticiones HTTP y genera las respuestas.

flask: Trabajando con peticiones y respuestas (3ª parte)

$
0
0

Trabajando con peticiones HTTP

Hemos indicado que nuestra aplicación Flask recibe una petición HTTP, cuando la URL a la que accedemos se corresponde con una ruta y un método indicada en una determinada route se ejecuta la función correspondiente. Desde esta función se puede acceder al objeto request que posee toda la información de la petición HTTP.

El objeto request

Veamos los atributos más importante que nos ofrece el objeto request:

from flask import Flask, request
...
@app.route('/info',methods=["GET","POST"])
def inicio():
    cad=""
    cad+="URL:"+request.url+"<br/>"
    cad+="Método:"+request.method+"<br/>"
    cad+="header:<br/>"
    for item,value in request.headers.items():
        cad+="{}:{}<br/>".format(item,value)    
    cad+="información en formularios (POST):<br/>"
    for item,value in request.form.items():
        cad+="{}:{}<br/>".format(item,value)
    cad+="información en URL (GET):<br/>"
    for item,value in request.args.items():
        cad+="{}:{}<br/>".format(item,value)    
    cad+="Ficheros:<br/>"
    for item,value in request.files.items():
        cad+="{}:{}<br/>".format(item,value)
    return cad
  • request.url: La URL a la que accedemos.
  • request.path: La ruta de la URL, quitamos el servidor y los parámetros con información.
  • request.method: El método HTTP con el qué hemos accedido.
  • request.headers: Las cabeceras de la petición HTTP. Tenemos atributos para acceder a cabeceras en concreto, por ejemplo, request.user_agent.
  • request.form: Información recibida en el cuerpo de la petición cuando se utiliza el método POST, normalmente se utiliza un formulario HTML para enviar esta información.
  • request.args: Parámetros con información indicado en la URL en las peticiones GET.
  • request.files: Ficheros para subir al servidor en una petición PUT o POST.

Ejemplo: sumar dos números

@app.route("/suma",methods=["GET","POST"])
def sumar():
    if request.method=="POST":
        num1=request.form.get("num1")
        num2=request.form.get("num2")
        return "<h1>El resultado de la suma es {}</h1>".format(str(int(num1)+int(num2)))
    else:
        return '''<form action="/suma" method="POST">
                <label>N1:</label>
                <input type="text" name="num1"/>
                <label>N2:</label>
                <input type="text" name="num2"/><br/><br/>
                <input type="submit"/>
                </form>'''

Generando respuestas HTTP, respuestas de error y redirecciones

El decorador router gestiona la petición HTTP recibida y crea un objeto reponse con la respuesta HTTP: el código de estado, las cabaceras y los datos devueltos. Esta respuesta la prepara a partir de lo que devuelve la función vista ejecutada con cada route. Estas funciones pueden devolver tres tipos de datos:

  • Una cadena, o la generación de una plantilla (que veremos posteriormente). Por defecto se indica un código 200 y las cabeceras por defecto.
  • Un objeto de la clase response generado con la función make_repsonse, que recibe los datos devueltos, el código de estado y las cabeceras.
  • Una tupla con los mismos datos: datos, cabeceras y código de respuesta.

Ejemplo de respuestas

Veamos el siguiente código:

@app.route('/string/')
def return_string():
    return 'Hello, world!'  

@app.route('/object/')
def return_object():
    headers = {'Content-Type': 'text/plain'}
    return make_response('Hello, world!', 200,headers)  

@app.route('/tuple/')
def return_tuple():
    return 'Hello, world!', 200, {'Content-Type':'text/plain'}

Puedes comprobar que devuelve cada una de las rutas.

Respuestas de error

Si queremos que en cualquier momento devolver una respuesta HTTP de error podemos utilizar la función abort:

@app.route('/login')
def login():
    abort(401)
    # Esta línea no se ejecuta

Redirecciones

Si queremos realizar una redicirección HTTP a otra URL utilizamos la función redirect:

@app.route('/')
def index():
    return redirect(url_for('return_string'))

Contenido estático

Nuestra página web necesita tener contenido estático: hoja de estilo, ficheros javascript, imágenes, documentos pdf, etc. Para acceder a ellos vamos a utilizar la función url_for.

¿Dónde guardamos el contenido estático?

Dentro de nuestro directorio vamos a crear un directorio llamado static, donde podemos crear la estructura de directorios adecuada para guardas nuestro contenido estático. Por ejemplo para guardar el CSS, el java script y las imágenes podríamos crear una estrucutra como la siguiente:

aplicacion
    static
        css
        js
        img

Acceder al contenido estático

Por ejemplo:

url_for('static', filename='css/style.css')

Estariamos creando la ruta para acceder al fichero style.css que se encuentra en static/css.

Otro ejemplo:

url_for('static', filename='img/tux.png')

Estaríamos creando la ruta para acceder al fichero tux.png que se encuentra en static/img.

Mostrar una imagen

@app.route('/')
def inicio():
    return '<img src="'+url_for('static', filename='img/tux.png')+'"/>'

Y comprobamos que se muestra al acceder a la página:

flask: Plantillas con jinja2 (4ª parte)

$
0
0

Plantillas con jinja2

Jinja2 es un motor de plantilla desarrollado en Python. Flask utiliza jinja2 para generar documentos HTML válidos de una manera muy sencilla y eficiente.

Por dependencias al instalar Flask instalamos jinja2. En esta unidad vamos a estudiar los elementos principales de jinja2, para más información accede a la documentación oficial de jinja2.

Una plantilla simple

Veamos un ejemplo para entender como funciona jinja2:

from jinja2 import Template

temp1="Hola {{nombre}}"
print(Template(temp1).render(nombre="Pepe"))

La salida es Hola Pepe. La plantilla se compone de una variable {{nombre}} que es sustituida por el valor de la variable nombre al renderizar o generar la plantilla.

Elementos de una plantilla

Una plantilla puede estar formada por texto, y algunos de los siguientes elementos:

  • Variables, se indican con {{ ... }}
  • Instrucciones, se indican con {% ... %}
  • Comentarios, se indican con {# ... #}

Variables en las plantillas

Las variables en la plantillas se sustituyen por los valores que se pasan a la plantilla al renderizarlas. Si enviamos una lista o un diccionario puedo acceder los valores de dos maneras:

{{ foo.bar }}
{{ foo['bar'] }}

Veamos algunos ejemplos:

temp2='<a href="{{ url }}"> {{ enlace }}</a>'
print(Template(temp2).render(url="http://www.flask.com",enlace="Flask"))    

temp3='<a href="{{ datos[0] }}"> {{ datos[1] }}</a>'
print(Template(temp3).render(datos=["http://www.flask.com","Flask"]))   

temp4='<a href="{{ datos.url }}"> {{ datos.enlace }}</a>'
print(Template(temp4).render(datos={"url":"http://www.flask.com","enlace":"Flask"}))

El resultado de las tres plantillas es:

<a href="http://www.flask.com"> Flask</a>

Filtros de variables

Un filtro me permite modificar una variable. Son distintas funciones que me modifican o calculan valores a partir de las variables, se indican separadas de las variables por | y si tienen parámetros se indican entre paréntesis. Veamos algunos ejemplos:

temp5='Hola {{nombre|striptags|title}}'
print(Template(temp5).render(nombre="   pepe  "))   

temp6="los datos son {{ lista|join(', ') }}"
print(Template(temp6).render(lista=["amarillo","verde","rojo"]))    

temp6="El ultimo elemento tiene {{ lista|last|length}} caracteres"
print(Template(temp6).render(lista=["amarillo","verde","rojo"]))

Por defecto los caracteres (>, <, &, ") no se escapan, si queremos mostrarlo en nuestra página HTML tenemos que escapar los caracteres:

temp7="La siguiente cadena muestra todos los caracteres: {{ info|e }}"
print(Template(temp7).render(info="<hola&que&tal>"))

Y por tanto la salida es:

La siguiente cadena muestra todos los caracteres: &lt;hola&amp;que&amp;tal&gt;

Para ver todos los filtros accede a la lista de filtros en la documentación.

Instrucciones en las plantillas

for

Nos permite recorrer una secuencia, veamos un ejemplo sencillo. Es compatible con la sentencia for de python.

temp7='''
<ul>
{% for elem in elems -%}
<li>{{loop.index}} - {{ elem }}</li>
{% endfor -%}
</ul>
'''
print(Template(temp7).render(elems=["amarillo","verde","rojo"]))

La salida es:

<ul>
<li>1 - amarillo</li>
<li>2 - verde</li>
<li>3 - rojo</li>
</ul>

El - detrás del bloque for evita que se añada una línea en blanco.

En un bloque for tenemos acceso a varias variables, veamos las más interesantes:

  • loop.index: La iteración actual del bucle (empieza a contar desde 1).
  • loop.index0: La iteración actual del bucle (empieza a contar desde 0).
  • loop.first: True si estamos en la primera iteración.
  • loop.last: True si estamos en la última iteración.
  • loop.length: Número de iteraciones del bucle.

if

Nos permite preguntar por el valor de una variable o si una variable existe. Es compatible con la sentencia if de python.

Ejemplo:

temp9='''
{% if elems %}
<ul>
{% for elem in elems -%}
    {% if elem is divisibleby 2 -%}
        <li>{{elem}} es divisible por 2.</li>
    {% else -%}
        <li>{{elem}} no es divisible por 2.</li>
    {% endif -%}
{% endfor -%}
</ul>
{% endif %}
'''
print(Template(temp9).render(elems=[1,2,3,4]))

Y la salida será:

<ul>
    <li>1 no es divisible por 2.</li>
    <li>2 es divisible por 2.</li>
    <li>3 no es divisible por 2.</li>
    <li>4 es divisible por 2.</li>
</ul>

Tenemos un conjunto de tests para realizar comprobaciones, por ejemplo divisibleby devuelve True si un número es divible por el que indiquemos. Hay más tests que podemos utilizar. Para ver todos los tests accede a la lista de tests en la documentación.

Generando páginas HTML con Flask y Jinja2

Flask utiliza por defecto jinja2 para generar documentos HTML, para generar una plantilla utilizamos la función render_template que recibe como parámetro el fichero donde guardamos la plantilla y las variables que se pasan a esta.

Las plantillas las vamos a guardar en ficheros en el directorio templates (dentro del directorio aplicacion).

Plantilla simple

Veamos un ejemplo de cómo podemos generar HTML a partir de una plantilla en Flask, el programa será el siguiente:

...
@app.route('/hola/')
@app.route('/hola/<nombre>')
def saluda(nombre=None):
    return render_template("template1.html",nombre=nombre)

La plantilla:

<!DOCTYPE html>
<html lang="es">
<head>
<title>Hola, que tal {{nombre}}</title>
<meta charset="utf-8" />
</head>

<body>
    <header>
       <h1>Mi sitio web</h1>
       <p>Mi sitio web creado en html5</p>
    </header>
    <h2>Vamos a saludar</h2>
    {% if nombre %}
      <h1>Hola {{nombre|title}}</h1>
      <p>¿Cómo estás?</p>
    {%else%}
      <p>No has indicado un nombre</p>
    {% endif %}
</body>
</html>

Y la salida:

Envío de varias variables a una plantilla

En este caso veremos un ejemplo donde mandamos varias variables a la plantilla:

@app.route('/suma/<num1>/<num2>')
def suma(num1,num2):
    try:
        resultado=int(num1)+int(num2)
    except:
        abort(404)
    return render_template("template2.html",num1=num1,num2=num2,resultado=resultado)

La plantilla:

...
    <h2>Suma</h2>
    {% if resultado>0 %}
      <p>El resultado es positivo</p>
    {%else%}
      <p>El resultado es negativo</p>
    {% endif %}
    <h3>{{resultado}}</h3>
...

Y la salida:

Generando páginas de error con plantillas

Como vemos en el ejemplo anterior, si los números no se pueden sumar se generara una respuesta 404, podemos también generar esta página a partir de una plantilla:

@app.errorhandler(404)
def page_not_found(error):
    return render_template("error.html",error="Página no encontrada..."), 404

La plantilla:

...
    <header>
       <h1>{{error}}</h1>
       <img src="{{ url_for('static', filename='img/tux.png')}}"/>
    </header>   
...

Uso de for en una plantilla

En este caso vamos a mostrar la tabla de multiplicar de un número, en la plantilla vamos a generar un bucle con 10 iteraciones usando el tipo de datos range:

@app.route('/tabla/<numero>')
def tabla(numero):
    try:
        numero=int(numero)
    except:
        abort(404)
    return render_template("template3.html",num=numero)

La plantilla:

...
    <h2>Tabla de multiplicar</h2>
    {% for i in range(1,11) -%}
      <p>{{num}} * {{i}} = {{num*i}}</p>
    {% endfor -%}
...

Y la salida:

Envío de diccionario a una plantilla

En realidad vamos a mandar una lista de diccionarios, donde tenemos información para construir un enlace:

@app.route('/enlaces')
def enlaces():
    enlaces=[{"url":"http://www.google.es","texto":"Google"},
            {"url":"http://www.twitter.com","texto":"Twitter"},
            {"url":"http://www.facbook.com","texto":"Facebook"},
            ]
    return render_template("template4.html",enlaces=enlaces)

La plantilla:

...
    <h2>Enlaces</h2>
    {% if enlaces %}
    <ul>
    {% for enlace in enlaces -%}
      <li><a href="{{ enlace.url }}">{{ enlace.texto }}</a></li>
    {% endfor -%}
    </ul>
    {% else %}
        <p>No hay enlaces></p>
    {% endif %}
...

Y la salida:

Herencia de plantillas

La herencia de plantillas nos permite hacer un esqueleto de plantilla, para que todas las páginas de nuestro sitio web sean similares. En la unidad anterior hicimos una plantilla independiente para cada página, eso tiene un problema: si queremos cambiar algo que es común a todas las páginas hay que cambiarlo en todos los ficheros.

En nuestro caso vamos a crear una plantilla base de donde se van a heredar todas las demás, e indicaremos los bloques que las plantillas hijas pueden sobreescribir.

La plantilla base

Vamos a crear una plantilla base.html donde indicaremos las partes comunes de todas nuestras páginas, e indicaremos los bloques que las otras plantillas pueden reescribir.

<!DOCTYPE html>
<html lang="es">
<head>
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="{{url_for("static", filename='css/style.css')}}">
<meta charset="utf-8" />
</head>

<body>
    <header>
       <h1>Mi sitio web</h1>
       <p>Mi sitio web creado en html5</p>
    </header>
    {% block content %}{% endblock %}
</body>
</html>

Algunas consideraciones:

  1. Hemos creado dos bloques (title y content) en las plantillas hijas vamos a poder rescribir esos dos bloque para poner el título de la página y el contenido. Podríamos indicar todos los bloques que necesitamos.
  2. Hemos incluido una hoja de estilo que está en nuestro contenido estático (directorio static)

Herencia de plantillas

A continuación, veamos la primera plantilla (tample1.html) utilizando la técnica de herencia:

{% extends "base.html" %}
{% block title %}Hola, que tal {{nombre}}{% endblock %}
{% block content %}
    <h2>Vamos a saludar</h2>
    {% if nombre %}
      <h1>Hola {{nombre|title}}</h1>
      <p>¿Cómo estás?</p>
    {%else%}
      <p>No has indicado un nombre</p>
    {% endif %}
{% endblock %}

Observamos cómo hemos reescrito los dos bloques.

Ejecuta el programa y comprueba que se genera el documento HTML completo, comprueba también que se está usando una hoja de estilo.

Curso de Apache 2.4 en OpenWebinars

Instalación de kubernetes con kubeadm

$
0
0


Kubernetes es un sistema de código abierto que nos permite despliegues automáticos, escabilidad y gestión de contenedores de aplicaiones. kubeadm es una herramienta que nos permite el despliegue de un cluster de kubernetes de manera sencilla. El cluster lo podemos crear en máquinas físicas o virtuales, en nuestro caso, vamos a usar Debian 9 en 3 máquinas virtuales para realizar la instalación.

Instalación de los paquetes necesarios

Instalación de Docker

Lo primero que hacemos, siguiendo las instrucciones de instalación de página oficial, es instalar la última versión de docker, en los tres nodos:

$ sudo apt-get update

Instalamos los paquetes que nos permiten usar repositorios apt con https:

$ sudo apt-get install \
     apt-transport-https \
     ca-certificates \
     curl \
     gnupg2 \
     software-properties-common

Añadimos las clves GPG oficales de Docker:

$ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -

Añadimos el repositorio para nuestra versión de Debian:

$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/debian \
   $(lsb_release -cs) \
   stable"

Y por último instalamos docker:

$ sudo apt-get update
$ sudo apt-get install docker-ce

Finalmente comprobamos la versión instalada:

$ docker --version
Docker version 18.03.1-ce, build 9ee9f40

Instalación de kubeadm, kubelet and kubectl

Vamos a instalar los siguientes pauqetes en nuestras máquinas:

  • kubeadm: Instrucción que nos permite crear el cluster.
  • kubelet: Es el componente de kubernetes que se ejecuta en todos los nodos y es responsable de ejecutar los pods y los contenedores.
  • kubectl: La utilidad de línea de comandos que nos permite controlar el cluster.

Para la instalación, seguimos los pasos indicados en la documentación:

apt-get update && apt-get install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
EOF
apt-get update
apt-get install -y kubelet kubeadm kubectl

Inicializando el nodo master

En el nodo que vamos a usar como master, ejecutamos la siguiente instrucción como superusuario:

$ kubeadm init --pod-network-cidr=192.168.0.0/16 --apiserver-cert-extra-sans=172.22.201.15

Este comando inicializa el cluster, hemos indicado le CIDR de la red por donde se comunican los nodos del cluster.

Estoy utilizando como instraestructura tres instancias de OpenStack, es necesario indicar el parámetro --apiserver-cert-extra-sans con la IP flotante del master para que el certificado que se genera sea válido para esta ip, y se pueda controlar el cluster desde el exterior.

Cuando termina muestra un mensaje similar a este:

Your Kubernetes master has initialized successfully!    

To start using your cluster, you need to run (as a regular  user):

  sudo cp /etc/kubernetes/admin.conf $HOME/
  sudo chown $(id -u):$(id -g) $HOME/admin.conf
  export KUBECONFIG=$HOME/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the    options listed at:
  http://kubernetes.io/docs/admin/addons/

You can now join any number of machines by running the  following on each node
as root:    

  kubeadm join --token <token> <master-ip>:<master-port>

Nos indica tres cosas:

  1. Las instrucciones que tenemos que ejecutar en el master, con un usuario sin privilegios para usar el cliente kubectl y manejar el claster.
  2. La necesidad de instalar un pod para la gestión de la red.
  3. Y la instrucción que tenemos que ejecutar en los nodos para añadirlos al cluster. Utilizaremos un token para ello.

Instalación del pod para gestionar la red

Antes de ello, en el master con un usuario con privilegios podemos usar el cliente kubectl ejecutando:

export KUBECONFIG=/etc/kubernetes/admin.conf

A continuación tenemos que instalar un pod que nos permita la comunicación por red de los distintos pods que vamos a correr en el cluster. kubeadm solo soporta plugins de red CNI (Container Network Interface), que es un proyecto que consiste en crear especificaciones y librerías para configure las redes que interconectan los contenedores. De las distintas alternativas vamos a instalar Calico, para ello:

$ kubectl apply -f https://docs.projectcalico.org/v3.0/getting-started/kubernetes/installation/hosted/kubeadm/1.7/calico.yaml

Y comprobamos que todos los pods del espacio de nombres kube-system están funcionando con normalidad:

$ kubectl get pods -n kube-system
NAME                                       READY        STATUS    RESTARTS   AGE
calico-etcd-kxfp2                          1/1          Running   0          1d
calico-kube-controllers-5d74847676-fb9nl   1/1          Running   0          1d
calico-node-c8pjr                          2/2          Running   0          1d
calico-node-g8xpt                          2/2          Running   0          1d
calico-node-q5ls4                          2/2          Running   0          1d
etcd-k8s-1                                 1/1          Running   0          1d
kube-apiserver-k8s-1                       1/1          Running   0          1d
kube-controller-manager-k8s-1              1/1          Running   0          1d
kube-dns-86f4d74b45-828lm                  3/3          Running   0          1d
kube-proxy-9jmdn                           1/1          Running   0          1d
kube-proxy-pjr2b                           1/1          Running   0          1d
kube-proxy-xqsmg                           1/1          Running   0          1d
kube-scheduler-k8s-1                       1/1          Running   0          1d

Uniendo los nodos al cluster

En cada nodo que va a formar parte del cluster tenemos que ejecutar, como superusuario, el comando que nos ofreció el comando kubeadm al iniciar el cluster en el master:

kubeadm join --token <token> <master-ip>:<master-port>
...
Node join complete:
* Certificate signing request sent to master and response
  received.
* Kubelet informed of new secure connection details.    

Run 'kubectl get nodes' on the master to see this machine join.

Y finalmente desde el master podemos obtener los nodos que forman el cluster:

# kubectl get nodes
NAME      STATUS    ROLES     AGE       VERSION
k8s-1     Ready     master    1d        v1.10.2
k8s-2     Ready     <none>    1d        v1.10.2
k8s-3     Ready     <none>    1d        v1.10.2

Acceso desde un cliente externo

Normalmente vamos a interactuar con el cluster desde un clinete externo donde tengamos instaldo kubectl. Para instalar kubectl, siguiendo las instrucciones oficiales, ejecutamos:

apt-get update && apt-get install -y apt-transport-https
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
EOF
apt-get update
apt-get install -y kubectl

Y para configurar el acceso al cluster:

  1. Desde el nodo master damos permisos de lectura al fichero /etc/kubernetes/admin.conf:

    chmod 644 /etc/kubernetes/admin.conf
    
  2. Desde el cliente:

    export IP_MASTER=172.22.201.15
    sftp debian@${IP_MASTER}
    sftp> get /etc/kubernetes/admin.conf
    sftp> exit
    
    mv admin.conf ~/.kube/mycluster.conf
    sed -i -e "s#server: https://.*:6443#server: https://${IP_MASTER}:6443#g" ~/.kube/mycluster.conf
    export KUBECONFIG=~/.kube/mycluster.conf
    

Y comprobamos que tenemos acceso al cluster:

$ kubectl cluster-info
Kubernetes master is running at https://172.22.201.15:6443

Puertos necesarios para acceder al cluster de Kubernetes

Si instalamos el cluster en instancias de un servicio cloud de infraestuctura hay que tener en cuanta que los siguientes puertos deben estar accesibles:

  • 80: Para acceder a los servicios con el controlador Ingress.
  • 443: Para acceder a los servicios con el controlador Ingress y HTTPS.
  • 6443: Para acceder a la API de Kubernetes.
  • 30000-40000: Para acceder a las aplicaciones con el servicio NodePort.

Desplegando una aplicación en Kubernetes

$
0
0

Un escenario común cuando desplegamos una aplicación web puede ser el siguiente:

En este escenario tenemos los siguientes elementos:

  • Un conjunto de máquinas (normalmente virtuales) que sirven la aplicación web (frontend).
  • Un balanceador de carga externo que reparte el tráfico entre las diferentes máquinas.
  • Un número de servidores de bases de datos (backend).
  • Un balanceador de carga interno que reparte el acceso a las bases de datos.

El escenario anterior se podría montar en Kubernetes de la siguiente forma:

Los distintos recursos de Kubernetes nos proporcionan distintas características muy deseadas:

  • Pods: La unidad mínima de computación en Kubernetes, permite ejecutar contenedores. Representa un conjunto de contenedores y almacenamiento compartido que comparte una única IP.
  • ReplicaSet: Recurso de un cluster Kubernetes que asegura que siempre se ejecute un número de replicas de un pod determinado. Nos proporciona las siguientes características:
    • Que no haya caída del servicio
    • Tolerancia a errores
    • Escabilidad dinámica
  • Deployment: Recurso del cluster Kubernetes que nos permite manejar los ReplicaSets. Nos proporciona las siguientes características:
    • Actualizaciones continúas
    • Despliegues automáticos
  • Service: Nos permite el acceso a los pod.
  • Ingress: Nos permite implementar un proxy inverso para el acceso a los distintos servicios establecidos. Estos dos elementos nos proporcionan la siguiente funcionalidad:
    • Balanceo de carga
  • Otros recursos de un cluster Kubernetes nos pueden proporcional características adicionales:
    • Migraciones sencillas
    • Monitorización
    • Control de acceso basada en Roles
    • Integración y despliegue continuo

En las siguientes entradas vamos a ir estudiando cada uno de estos recursos.

Recursos de Kubernetes: Pods

$
0
0

La unidad más pequeña de kubernetes son los Pods, con los que podemos correr contenedores. Un pod representa un conjunto de contenedores que comparten almacenamiento y una única IP. Los pods son efímeros, cuando se destruyen se pierde toda la información que contenía. Si queremos desarrollar aplicaciones persistentes tenemos que utilizar volúmenes.

Por lo tanto, aunque Kubernetes es un orquestador de contenedores, la unidad mínima de ejecución son los pods:

  • Si seguimos el principio de un proceso por contenedor, nos evitamos tener sistemas (como máquinas virtuales) ejecutando docenas de procesos,
  • pero en determinadas circunstancias necesito más de un proceso para que se ejecute mi servicio.

Por lo tanto parece razonable que podamos tener más de un contenedor compartiendo almacenamiento y direccionamiento, que llamamos Pod. Además existen mas razones:

  • Kubernetes puede trabajar con distintos contenedores (Docker, Rocket, cri-o,…) por lo tanto es necesario añadir una capa de abstracción que maneje las distintas clases de contenedores.
  • Además esta capa de abstracción añade información adicional necesaria en Kubernetes como por ejemplo, políticas de reinicio, comprobación de que la aplicación esté inicializada (readiness probe), comprobación de que la aplicación haya realizado alguna acción especificada (liveness probe), …

Ejemplos de implementación en pods

  1. Un servidor web nginx con un servidor de aplicaciones PHP-FPM, lo podemos implementar en un pod, y cada servicio en un contenedor.
  2. Una aplicación WordPress con una base de datos mariadb, lo implementamos en dos pods diferenciados, uno para cada servicio.

Esqueleto YAML de un pod

Podemos describir la estructura de un pod en un fichero con formato Yaml, por ejemplo el fichero nginx.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: default
  labels:
    app: nginx
spec:
  containers:
    - image:  nginx
      name:  nginx

Donde indicamos:

  • apiVersion: v1: La versión de la API que vamos a usar.
  • kind: Pod: La clase de recurso que estamos definiendo.
  • metadata: Información que nos permite identificar unívocamente al recurso.
  • spec: Definimos las características del recurso. En el caso de un pod indicamos los contenedores que van a formar el pod, en este caso sólo uno.

Para más información acerca de la estructura de la definición de los objetos de Kubernetes: Understanding Kubernetes Objects.

Creación y gestión de un pod

Para crear el pod desde el fichero yaml anterior, ejecutamos:

kubectl create -f nginx.yaml
pod "nginx" created

Y podemos ver que el pod se ha creado:

kubectl get pods
NAME      READY     STATUS    RESTARTS   AGE
nginx     1/1       Running   0          19s

Si queremos saber en que nodo del cluster se está ejecutando:

kubectl get pod -o wide
NAME      READY     STATUS    RESTARTS   AGE       IP                   NODE
nginx     1/1       Running   0          1m       192.168.13.129    k8s-3

Para obtener información más detallada del pod:

kubectl describe pod nginx
Name:         nginx
Namespace:    default
Node:         k8s-3/10.0.0.3
Start Time:   Sun, 13 May 2018 21:17:34 +0200
Labels:       app=nginx
Annotations:  <none>
Status:       Running
IP:           192.168.13.129
Containers:
...

Para eliminar el pod:

kubectl delete pod nginx
pod "nginx" deleted

Accediendo al pod con kubectl

Para obtener los logs del pod:

$ kubectl logs nginx
127.0.0.1 - - [13/May/2018:19:23:57 +0000] "GET / HTTP/1.1" 200 612     "-" "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101    Firefox/60.0" "-"
...

Si quiero conectarme al contenedor:

kubectl exec -it nginx -- /bin/bash
root@nginx:/# 

Podemos acceder a la aplicación, redirigiendo un puerto de localhost al puerto de la aplicación:

kubectl port-forward nginx 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

Y accedemos al servidor web en la url http://localhost:8080.

Labels

Las Labels nos permiten etiquetar los recursos de kubernetes (por ejemplo un pod) con información del tipo clave/valor.

Para obtener las labels de los pods que hemos creado:

kubectl get pods --show-labels
NAME      READY     STATUS    RESTARTS   AGE       LABELS
nginx     1/1       Running   0          10m       app=nginx

Los Labels lo hemos definido en la sección metada del fichero yaml, pero también podemos añadirlos a los pods ya creados:

kubectl label pods nginx service=web
pod "nginx" labeled

kubectl get pods --show-labels
NAME      READY     STATUS    RESTARTS   AGE       LABELS
nginx     1/1       Running   0          12m       app=nginx,service=web

Los Labels me van a permitir seleccionar un recurso determinado, por ejemplo para visualizar los pods que tienen un label con un determinado valor:

kubectl get pods -l service=web
NAME      READY     STATUS    RESTARTS   AGE
nginx     1/1       Running   0          13m

También podemos visualizar los valores delos labels como una nueva columna:

kubectl get pods -Lservice
NAME      READY     STATUS    RESTARTS   AGE       SERVICE
nginx     1/1       Running   0          15m       web

Modificando las características de un pod creado

Podemos modificar las características de cualquier recurso de kubernetes una vez creado, por ejemplo podemos modificar la definición del pod de la siguiente manera:

kubectl edit pod nginx

se abrirá un editor de texto donde veremos el fichero Yaml que define el recurso, podemos ver todos los parámetros que se han definido con valores por defecto, al no tenerlo definidos en el fichero de creación del pod nginx.yaml.

Recursos de Kubernetes: ReplicaSet

$
0
0

ReplicaSet es un recurso de Kubernetes que asegura que siempre se ejecute un número de replicas de un pod determinado. Por lo tanto, nos asegura que un conjunto de pods siempre están funcionando y disponibles. Nos proporciona las siguientes características:

  • Que no haya caída del servicio
  • Tolerancia a errores
  • Escabilidad dinámica

Definición yaml de un ReplicaSet

Vamos a ver un ejemplo de definición de ReplicaSet en el fichero nginx-rs.yaml:

apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
  name: nginx
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - image:  nginx
          name:  nginx
  • replicas: Indicamos el número de pos que siempre se van a estar ejecutando.
  • selector: Indicamos el pods que vamos a replicar y vamos a controlar con el ReplicaSet. En este caso va a controlar pods que tenga un label app cuyo valor sea nginx. Si no se indica el campo selector se seleccionar or defecto los pods cuyos labels sean iguales a los que hemos declarado en la sección siguiente.
  • template: El recurso ReplicaSet contiene la definición de un pod.

Al crear el ReplicaSet se crearán los pods que hemos indicado como número de replicas:

kubectl create -f nginx-rs.yaml
replicaset.extensions "nginx" created

Veamos el ReplicaSet creado y los pods que ha levantado.

kubectl get rs
NAME      DESIRED   CURRENT   READY     AGE
nginx     2         2         2         44s

kubectl get pods
NAME          READY     STATUS    RESTARTS   AGE
nginx-5b2rn   1/1       Running   0          1m
nginx-6kfzg   1/1       Running   0          1m

¿Qué pasaría si borro uno de los pods que se han creado? Inmediatamente se creará uno nuevo para que siempre estén ejecutándose los pods deseados, en este caso 2:

kubectl delete pod nginx-5b2rn
pod "nginx-5b2rn" deleted

kubectl get pods
NAME          READY     STATUS              RESTARTS   AGE
nginx-6kfzg   1/1       Running             0          2m
nginx-lkvzj   0/1       ContainerCreating   0          4s

kubectl get pods
NAME          READY     STATUS    RESTARTS   AGE
nginx-6kfzg   1/1       Running   0          2m
nginx-lkvzj   1/1       Running   0          8s

En cualquier momento puedo escalar el número de pods que queremos que se ejecuten:

kubectl scale rs nginx --replicas=5
replicaset.extensions "nginx" scaled

kubectl get pods --watch
NAME          READY     STATUS    RESTARTS   AGE
nginx-6kfzg   1/1       Running   0          5m
nginx-bz2gs   1/1       Running   0          46s
nginx-lkvzj   1/1       Running   0          3m
nginx-ssblp   1/1       Running   0          46s
nginx-xxg4j   1/1       Running   0          46s

Como anteriormente vimos podemos modificar las características de un ReplicaSet con la siguiente instrucción:

kubectl edit rs nginx

Por último si borramos un ReplicaSet se borraran todos los pods asociados:

kubectl delete rs nginx
replicaset.extensions "nginx" deleted

kubectl get rs
No resources found.

kubectl get pods 
No resources found.

El uso del recurso ReplicaSet sustituye al uso del recurso ReplicaController, más concretamente el uso de Deployment que define un ReplicaSet.

Recursos de Kubernetes: Deployment

$
0
0

Deployment es la unidad de más alto nivel que podemos gestionar en Kubernetes. Nos permite definir diferentes funciones:

  • Control de replicas
  • Escabilidad de pods
  • Actualizaciones continúas
  • Despliegues automáticos
  • Rollback a versiones anteriores

deoployment

Definición yaml de un Deployment

Vamos a ver un ejemplo de definición de un Deployment en el fichero nginx-deployment.yaml:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx
  namespace: default
  labels:
    app: nginx
spec:
  revisionHistoryLimit: 2
  strategy:
    type: RollingUpdate
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        ports:
        - name: http
          containerPort: 80

El despliegue de un Deployment crea un ReplicaSet y los Pods correspondientes. Por lo tanto en la definición de un Deployment se define también el replicaSet asociado. En la práctica siempre vamos a trabajar con Deployment. Los atributos relacionados con el Deployment que hemos indicado en la definición son:

  • revisionHistoryLimit: Indicamos cuántos ReplicaSets antiguos deseamos conservar, para poder realizar rollback a estados anteriores. Por defecto, es 10.
  • strategy: Indica el modo en que se realiza una actualización del Deployment: recreate: elimina los Pods antiguos y crea los nuevos; RollingUpdate: actualiza los Pods a la nueva versión.

Puedes encontrar más parámetros en la documentación.

Cuando creamos un Deployment, se crea el ReplicaSet asociado y todos los pods que hayamos indicado.

kubectl create -f nginx-deployment.yaml 
deployment.extensions "nginx" created

kubectl get pods
NAME                     READY     STATUS    RESTARTS   AGE
nginx-84f79f5cdd-b2bn5   1/1       Running   0          44s
nginx-84f79f5cdd-cz474   1/1       Running   0          44s

kubectl get deploy
NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx     2         2         2            2           49s

kubectl get rs
NAME               DESIRED   CURRENT   READY     AGE
nginx-84f79f5cdd   2         2         2         53s

kubectl get pods
NAME                     READY     STATUS    RESTARTS   AGE
nginx-84f79f5cdd-b2bn5   1/1       Running   0          56s
nginx-84f79f5cdd-cz474   1/1       Running   0          56s

Como ocurría con los replicaSets los Deployment también se pueden escalar, aumentando o disminuyendo el número de pods asociados:

kubectl scale deployment nginx --replicas=4
deployment.extensions "nginx" scaled

kubectl get rs
NAME               DESIRED   CURRENT   READY     AGE
nginx-84f79f5cdd   4         4         4         2m

kubectl get pods
NAME                     READY     STATUS    RESTARTS   AGE
nginx-84f79f5cdd-7kzls   1/1       Running   0          2m
nginx-84f79f5cdd-8zrcb   1/1       Running   0          2m
nginx-84f79f5cdd-b2bn5   1/1       Running   0          2m
nginx-84f79f5cdd-cz474   1/1       Running   0          2m

Actualización del deployment

Podemos cambiar en cualquier momentos las propiedades del deployment, por ejemplo para hacer una actualización de la aplicación:

kubectl set image deployment nginx nginx=nginx:1.13 --all
deployment.apps "nginx" image updated

También podríamos haber cambiado la versión de la imagen modificando la definición Yaml del Deployment con la siguiente instrucción:

kubectl edit deployment nginx

Y comprobamos que se ha creado un nuevo RecordSet, y unos nuevos pods con la nueva versión de la imagen.

kubectl get rs
NAME               DESIRED   CURRENT   READY     AGE
nginx-75f9c7b459   4         4         3         5s
nginx-84f79f5cdd   0         0         0         6m

kubectl get pods
NAME                     READY     STATUS    RESTARTS   AGE
nginx-75f9c7b459-87l5j   1/1       Running   0          5s
nginx-75f9c7b459-92x8s   1/1       Running   0          5s
nginx-75f9c7b459-fm6g5   1/1       Running   0          5s
nginx-75f9c7b459-pz78j   1/1       Running   0          5s

La opción --all fuerza a actualizar todos los pods aunque no estén inicializados.

Rollback de nuestra aplicación

Si queremos volver a la versión anterior de nuestro despliegue, tenemos que ejecutar:

kubectl rollout undo deployment/nginx
deployment.apps "nginx" 

T comprobamos como se activa el antiguo RecordSet y se crean nuevos pods con la versión anterior de nuestra aplicación:

kubectl get rs
NAME               DESIRED   CURRENT   READY     AGE
nginx-75f9c7b459   0         0         0         1h
nginx-84f79f5cdd   4         4         4         22h

kubectl get pods
NAME                     READY     STATUS    RESTARTS   AGE
nginx-84f79f5cdd-cwgzx   1/1       Running   0          5s
nginx-84f79f5cdd-hmd2l   1/1       Running   0          5s
nginx-84f79f5cdd-rhnkg   1/1       Running   0          5s
nginx-84f79f5cdd-vn7kd   1/1       Running   0          5s

Eliminando el despliegue

Si eliminamos el Deployment se eliminarán el RecordSet asociado y los pods que se estaban gestionando.

kubectl delete deployment nginx
deployment.extensions "nginx" deleted

kubectl get deploy
No resources found.

kubectl get rs
No resources found.

kubectl get pods
No resources found.

flask: Trabajando con peticiones y respuestas (3ª parte)

$
0
0

<img src="https://www.josedomingo.org/pledin/assets/wp-content/uploads/2018/03/flask.png" alt="" width="460" height="180" class="aligncenter size-full wp-image-1919" />

Trabajando con peticiones HTTP

Hemos indicado que nuestra aplicación Flask recibe una petición HTTP, cuando la URL a la que accedemos se corresponde con una ruta y un método indicada en una determinada route se ejecuta la función correspondiente. Desde esta función se puede acceder al objeto request que posee toda la información de la petición HTTP.

El objeto request

Veamos los atributos más importante que nos ofrece el objeto request:

from flask import Flask, request
...
@app.route('/info',methods=["GET","POST"])
def inicio():
    cad=""
    cad+="URL:"+request.url+"<br/>"
    cad+="Método:"+request.method+"<br/>"
    cad+="header:<br/>"
    for item,value in request.headers.items():
        cad+="{}:{}<br/>".format(item,value)    
    cad+="información en formularios (POST):<br/>"
    for item,value in request.form.items():
        cad+="{}:{}<br/>".format(item,value)
    cad+="información en URL (GET):<br/>"
    for item,value in request.args.items():
        cad+="{}:{}<br/>".format(item,value)    
    cad+="Ficheros:<br/>"
    for item,value in request.files.items():
        cad+="{}:{}<br/>".format(item,value)
    return cad
  • request.url: La URL a la que accedemos.
  • request.path: La ruta de la URL, quitamos el servidor y los parámetros con información.
  • request.method: El método HTTP con el qué hemos accedido.
  • request.headers: Las cabeceras de la petición HTTP. Tenemos atributos para acceder a cabeceras en concreto, por ejemplo, request.user_agent.
  • request.form: Información recibida en el cuerpo de la petición cuando se utiliza el método POST, normalmente se utiliza un formulario HTML para enviar esta información.
  • request.args: Parámetros con información indicado en la URL en las peticiones GET.
  • request.files: Ficheros para subir al servidor en una petición PUT o POST.

<img src="https://www.josedomingo.org/pledin/assets/wp-content/uploads/2018/03/flask3.png" alt="" width="1442" height="361" class="aligncenter size-full wp-image-1954" />

Ejemplo: sumar dos números

@app.route("/suma",methods=["GET","POST"])
def sumar():
    if request.method=="POST":
        num1=request.form.get("num1")
        num2=request.form.get("num2")
        return "<h1>El resultado de la suma es {}</h1>".format(str(int(num1)+int(num2)))
    else:
        return '''<form action="/suma" method="POST">
                <label>N1:</label>
                <input type="text" name="num1"/>
                <label>N2:</label>
                <input type="text" name="num2"/><br/><br/>
                <input type="submit"/>
                </form>'''

<img src="https://www.josedomingo.org/pledin/assets/wp-content/uploads/2018/03/flask4.png" alt="" width="481" height="155" class="aligncenter size-full wp-image-1955" />

Generando respuestas HTTP, respuestas de error y redirecciones

El decorador router gestiona la petición HTTP recibida y crea un objeto reponse con la respuesta HTTP: el código de estado, las cabaceras y los datos devueltos. Esta respuesta la prepara a partir de lo que devuelve la función vista ejecutada con cada route. Estas funciones pueden devolver tres tipos de datos:

  • Una cadena, o la generación de una plantilla (que veremos posteriormente). Por defecto se indica un código 200 y las cabeceras por defecto.
  • Un objeto de la clase response generado con la función make_repsonse, que recibe los datos devueltos, el código de estado y las cabeceras.
  • Una tupla con los mismos datos: datos, cabeceras y código de respuesta.

Ejemplo de respuestas

Veamos el siguiente código:

@app.route('/string/')
def return_string():
    return 'Hello, world!'  

@app.route('/object/')
def return_object():
    headers = {'Content-Type': 'text/plain'}
    return make_response('Hello, world!', 200,headers)  

@app.route('/tuple/')
def return_tuple():
    return 'Hello, world!', 200, {'Content-Type':'text/plain'}

Puedes comprobar que devuelve cada una de las rutas.

Respuestas de error

Si queremos que en cualquier momento devolver una respuesta HTTP de error podemos utilizar la función abort:

@app.route('/login')
def login():
    abort(401)
    # Esta línea no se ejecuta

Redirecciones

Si queremos realizar una redicirección HTTP a otra URL utilizamos la función redirect:

@app.route('/')
def index():
    return redirect(url_for('return_string'))

Contenido estático

Nuestra página web necesita tener contenido estático: hoja de estilo, ficheros javascript, imágenes, documentos pdf, etc. Para acceder a ellos vamos a utilizar la función url_for.

¿Dónde guardamos el contenido estático?

Dentro de nuestro directorio vamos a crear un directorio llamado static, donde podemos crear la estructura de directorios adecuada para guardas nuestro contenido estático. Por ejemplo para guardar el CSS, el java script y las imágenes podríamos crear una estructura como la siguiente:

aplicacion
    static
        css
        js
        img

Acceder al contenido estático

Por ejemplo:

url_for('static', filename='css/style.css')

Estaríamos creando la ruta para acceder al fichero style.css que se encuentra en static/css.

Otro ejemplo:

url_for('static', filename='img/tux.png')

Estaríamos creando la ruta para acceder al fichero tux.png que se encuentra en static/img.

Mostrar una imagen

@app.route('/')
def inicio():
    return '<img src="'+url_for('static', filename='img/tux.png')+'"/>'

Y comprobamos que se muestra al acceder a la página:

<img src="https://www.josedomingo.org/pledin/assets/wp-content/uploads/2018/03/img1.png" alt="" width="288" height="370" class="aligncenter size-full wp-image-1958" />

Viewing all 104 articles
Browse latest View live