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

flask: Plantillas con jinja2 (4ª 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" />

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><htmllang="es"><head><title>Hola, que tal {{nombre}}</title><metacharset="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:

<img src="https://www.josedomingo.org/pledin/assets/wp-content/uploads/2018/03/template1.png" alt="" width="264" height="340" class="aligncenter size-full wp-image-1963" />

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:

<img src="https://www.josedomingo.org/pledin/assets/wp-content/uploads/2018/03/template2.png" alt="" width="283" height="323" class="aligncenter size-full wp-image-1964" />

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:

<img src="https://www.josedomingo.org/pledin/assets/wp-content/uploads/2018/03/template3.png" alt="" width="266" height="605" class="aligncenter size-full wp-image-1965" />

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:

<img src="https://www.josedomingo.org/pledin/assets/wp-content/uploads/2018/03/template4.png" alt="" width="261" height="318" class="aligncenter size-full wp-image-1966" />

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><htmllang="es"><head><title>{% block title %}{% endblock %}</title><linkrel="stylesheet"href="{{url_for("static",filename='css/style.css')}}"><metacharset="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

<img src="https://www.josedomingo.org/pledin/assets/wp-content/uploads/2018/05/name_blue.png" alt="" width="1600" height="237" class="aligncenter size-full wp-image-1988" />

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:

<img src="https://www.josedomingo.org/pledin/assets/wp-content/uploads/2018/05/deploy1.png" alt="" width="685" height="387" class="aligncenter size-full wp-image-1997" />

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.

<img src="https://www.josedomingo.org/pledin/assets/wp-content/uploads/2018/06/pod.png" alt="" width="960" height="384" class="aligncenter size-full wp-image-2002" />

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 de los 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

rs

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

Bienvenidos a PLEDIN 3.0

$
0
0

En esta entrada os presento la nueva versión de mi página personal: PLEDIN 3.0. Desde hace 8 años he estado trabajando con Wordpress y la verdad es que aunque la experiencia ha sido muy positiva, sí he experimentado que las ventajas que ofrece una herramienta como Wordpress no las estoy aprovechando, no necesito una web dinámica para escribir contenido estático. Además el mantenimiento de la página puede llegar a ser muy pesado (copias de seguridad, mantenimiento de la base de datos, etc.), todo estos aspectos son un poco más complicado con la página Moodle donde mantengo los cursos que he ido impartiendo a lo largo de estos años (utilizo una moodle con contenido estático, sin aprovechar todas las funcionalidades que nos ofrece).

Por todas estas razones os presento la nueva página de PLEDIN desarrollada con Jekyll, esta herramienta escrita en Ruby es un generador de páginas estáticas, que me permite de forma sencilla escribir el contenido estático con Markdown y automáticamente generar el html qué posteriormente despliego en mi servidor. He estado mirando diferentes herramientas que trabajan de forma similar: Pelican escrita en Python y Hugo escrita en Go, pero finalmente me decidí a usar Jekyll ya que he encontrado más soporte en la red y una comunidad de usuarios más amplia. Las ventajas que me ofrece utilizar un generador de páginas estáticas son las siguientes (y seguro que se me olvida alguna):

  • Trabajo con fichero Markdown, la sintaxis es muy sencilla y puedo utilizar mi editor de textos favorito para escribir.
  • Todos los ficheros que forman parte de mi página se pueden guardar en un sistema de control de versiones, trabajar con git aporta muchas ventajas.
  • El resultado final son páginas web html estáticas, con lo que la velocidad de acceso a la página es muy elevada.
  • Me olvido de la base de datos que necesitan todos los gestores de contenido para funcionar, no necesito hacer copias de seguridad ni administrar la base de datos.
  • La copia de seguridad ya la tengo con el uso del sistema de control de versiones, pero si quiero hacer la copia de la página sólo tengo que guardar los ficheros del directorio donde se aloja.
  • La puesta en producción es muy sencilla y se puede automatizar de una forma simple.

Un poco de historia

Empecé a escribir en la página en octubre de 2005. Al principio decidí elegir una moodle para realizar la página, y comencé a escribir artículos y a colgar los materiales y cursos que iba encontrando o generando. En esta primera etapa la página estaba alojada en mi ordenador personal de mi casa y recuerdo que lo pasaba muy mal cada vez que se iba la luz o tenía cualquier problema con el ordenador. Podéis ver una captura de pantalla de la página en abril de 2008:

2008

Ver pantalla completa

Al poco tiempo decidí que tener la página en un servidor personal me daba muchos dolores de cabeza, y en noviembre de 2008 migre la página a un servicio de hosting compartido con la empresa CDMON. Y en abril de 2010 decidí el cambio de la Moodle al Wordpress, en realidad mantuve las dos páginas:

  • www.josedomingo.org: Estaría creada con el Wordpress y serviría de blog personal donde podía escribir mis artículos.
  • plataforma.josedomingo.org: Seguiría siendo la moodle pero sólo para guardar los cursos que iba impartiendo.

De esta manera en 2010 y 2011 las páginas se veían de esta manera:

blog

Ver pantalla completa

plataforma

Ver pantalla completa

La plataforma se podía ver en 2012 de la siguiente manera:

plataforma

Ver pantalla completa

Y el blog en el año 2014 se veía así:

blog

Ver pantalla completa

En diciembre de 2014 hice otro cambio importante: la migración desde un servidor hosting compartido a un servicio de cloud computing PaaS: OpenShift. La versión anterior de OpenShift nos ofrecía una capa de servicio gratuita muy adecuada para tener alojadas páginas pequeñas, sólo me hizo falta contratar más almacenamiento. Además decidí migrar los cursos más antiguos de la plataforma a la página: pledin.gnomio.com. Durante estos años cambié otra vez los temas de las páginas, que son los que han tenido hasta la actualidad:

blog

Ver pantalla completa

plataforma

Ver pantalla completa

A mediados de 2016 se anunció la nueva versión de OpenShift y por consiguiente el cierre de los servicios ofrecidos por la versión anterior. La nueva versión no ofrecía una capa gratuita tan interesante como la anterior y por lo tanto había que buscar un nuevo alojamiento para las páginas. En noviembre de 2016 me decidí por contratar un servidor dedicado que es donde actualmente se encuentran alojadas las páginas.

El nuevo blog

como comentaba anteriormente he desarrollado las nuevas páginas con Jekyll, he usado el tema Minimal Mistake que nos da muchas opciones de configuración y que a mí me gusta mucho.

Al acceder a la nueva web encontramos una página principal desde donde podemos acceder a los distintos sitios: al blog PLEDIN (www.josedomingo.org/pledin/blog/), a la plataforma con los curso (plataforma.josedomingo.org) y a los contenidos de los módulos de Formación Profesional que voy a impartir este año (este apartado lo estrenaré en los próximos días).

Para realizar la migración de los posts he usado el plugin de WordPress: WordPress2Jekyll que me ha permitido generar de forma automática los fichero Markdown con el contenido de los diferentes posts.

Además he configurado Jekyll para que las URL de acceso a las distintas páginas e imágenes sean los mismos, con lo que no voy a tener enlaces rotos, y los posibles enlaces que haya en otras páginas sigan funcionando.

Aunque mi blog no tenía muchos comentarios, los he migrado también a ficheros Markdown y he hecho una modificación en el layout de las páginas donde se muestran los posts para añadir los comentarios.

La nueva plataforma de cursos

También he migrado los contenidos de la moodle, a la nueva plataforma Pledin. Esta migración ha costado un poco más ya que no encontraba herramientas que lo hicieran de forma automática.

De esta manera me he creado un pequeño programa: moodle2md que me permite coger una copia de seguridad de un curso de moodle y convertir sus contenidos en fichero Markdown, además de extraer los ficheros e imágenes del curso. Posteriormente he tenido que editar los ficheros para adaptarlos un poco, pero los resultados han sido muy satisfactorios.

Otro problema que he solucionado es la redirección de las URL de la moodle a la nueva página. En el repositorio podéis encontrar el fichero rewrite.py que genera las reglas de reescrituras para el módulo rewrite de Apache2 para que todos los recursos de un curso tengan una regla que me lleve a las nuevas URL. Por ejemplo si accedemos a la URL de la moodle: https://plataforma.josedomingo.org/pledin/mod/resource/view.php?id=1635 accederemos al documento pdf que se encuentra en nuestra nueva plataforma.

Bueno ha sido un trabajo un poco complejo pero espero que el resultado os guste. Un saludo a todos.


Nuevo curso de python3

$
0
0

python3

Python es un lenguaje de programación interpretado cuya filosofía hace hincapié en una sintaxis que favorezca un código legible.

Se trata de un lenguaje de programación multiparadigma, ya que soporta orientación a objetos, programación imperativa y, en menor medida, programación funcional. Es un lenguaje interpretado, usa tipado dinámico y es multiplataforma.

Los contenidos del curso al que podrás acceder en la Plataforma Pledin forman parte de un curso que he impartido para OpenWebinars en mayo de 2017.

Puedes obtener todo el contenido del curso en el repositorio GitHub. Todas las observaciones, mejoras y sugerencias son bienvenidas.

Accede al curso de Python3

Nuevo curso de python flask

$
0
0

flask

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

Los siguientes contenidos forman parte de un curso que he impartido para OpenWebinars en septiembre de 2017.

Puedes obtener todo el contenido del curso en el repositorio GitHub. Todas las observaciones, mejoras y sugerencias son bienvenidas.

Accede al curso de Python Flask

Nuevo curso de Apache 2.4

$
0
0

apache24

El servidor HTTP Apache es un servidor web HTTP de código abierto, para plataformas Unix (BSD, GNU/Linux, etc.), Microsoft Windows, Macintosh y otras, que implementa el protocolo HTTP/1.12​ y la noción de sitio virtual.

Los siguientes contenidos forman parte de un curso que he impartido para OpenWebinars en febrero de 2018.

Puedes obtener todo el contenido del curso en el repositorio GitHub.

Accede al curso de Apache 2.4

Recursos de Kubernetes: Namespaces

$
0
0

Los Namespaces nos permiten aislar recursos para el uso por los distintos usuarios del cluster, para trabajar en distintos proyectos. A cada namespace se le puede asignar una cuota y definirle reglas y políticas de acceso.

Trabajando con Namespaces

Para obtener la lista de Namespaces ejecutamos:

kubectl get namespaces
NAME          STATUS    AGE
default       Active    1d
kube-public   Active    1d
kube-system   Active    1d
  • default: Espacio de nombres por defecto.
  • kube-system: Espacio de nombres creado y gestionado por Kubernetes.
  • kube-public: Espacio de nombres accesible por todos los usuarios, reservado para uso interno del cluster.

Para crear un nuevo Namespace:

kubectl create ns proyecto1
namespace "proyecto1" created

Otra forma de crear un Namespace es a partir de un fichero yaml con su definición:

apiVersion: v1
kind: Namespace
metadata:
  name: proyecto1

Podemos ver las características del nuevo espacio de nombres:

kubectl describe ns proyecto1
Name:         proyecto1
Labels:       <none>
Annotations:  <none>
Status:       Active

No resource quota.

No resource limits.

Y su definición yaml:

kubectl get ns proyecto1 -o yaml
apiVersion: v1
kind: Namespace
metadata:
  creationTimestamp: 2018-05-23T16:19:58Z
  name: proyecto1
  resourceVersion: "152566"
  selfLink: /api/v1/namespaces/proyecto1
  uid: 2306825c-5ea5-11e8-ab66-fa163e99cb75
spec:
  finalizers:
  - kubernetes
status:
  phase: Active

Crear recursos en un namespace

Para crear un recurso en un namespace debemos indicar el nombre del espacio de nombres en la etiqueta namespace en su definición:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nginx
  namespace: proyecto1
  ...

También podemos crearlos sin el fichero yaml:

kubectl run nginx --image=nginx -n proyecto1
deployment.apps "nginx" created

kubectl get deploy -n proyecto1
NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx     1         1         1            1           15s

Y creamos el servicio asociado:

kubectl expose deployment/nginx --port=80 --type=NodePort -n proyecto1
service "nginx" exposed

kubectl get services -n proyecto1
NAME      TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
nginx     NodePort   10.107.121.169   <none>        80:30352/TCP   10s

Configurando un namespace por defecto

Podemos indicar en un determinado contexto (un contexto determina el cluter y el usuario que podemos utilizar) un namespace, de tal manera que cuando utilicemos dicho contexto se va a utilizar el namespace indicado, y no será necesario indicarlo con la opción -n. Para ello es necesario determinar el contexto en el que estamos trabajando:

kubectl config current-context
kubernetes-admin@kubernetes

Y a continuación modifico el contexto añadiendo el namespace que quiero usar por defecto.

kubectl config set-context kubernetes-admin@kubernetes --namespace=proyecto1
Context "kubernetes-admin@kubernetes" modified.

Eliminando un namespace

Al eliminar un namespace se borran todos los recursos que hemos creado en él.

kubectl delete ns proyecto1
namespace "proyecto1" deleted

Índice de entradas sobre Kubernetes

Este artículo corresponde a una serie de entradas donde he hablado sobre Kubernetes:

Kubernetes. Configurando nuestras aplicaciones: variables de entornos, ConfigMap, Secrets

$
0
0

Configurando nuestras aplicaciones con variables de entorno

Para configurar las aplicaciones que vamos a desplegar usamos variables de entorno, por ejemplo podemos ver las variables de entorno que podemos definir para configurar la imagen docker de MariaDB.

Podemos definir un Deployment que defina un contenedor configurado por medio de variables de entorno, mariadb-deployment.yaml:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: mariadb-deployment
  labels:
    app: mariadb
    type: database
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: mariadb
        type: database
    spec:
      containers:
        - name: mariadb
          image: mariadb
          ports:
            - containerPort: 3306
              name: db-port
          env:
            - name: MYSQL_ROOT_PASSWORD
              value: my-password

Y creamos el despliegue:

kubectl create -f mariadb-deployment.yaml
deployment.apps "mariadb-deployment" created

O directamente ejecutando:

kubectl run mariadb --image=mariadb --env MYSQL_ROOT_PASSWORD=my-password

Veamos el pod creado:

kubectl get pods -l app=mariadb
NAME                                READY     STATUS    RESTARTS   AGE
mariadb-deployment-fc75f956-f5zlt   1/1       Running   0          15s

Y probamos si podemos acceder, introduciendo la contraseña configurada:

kubectl exec -it mariadb-deployment-fc75f956-f5zlt -- mysql -u root -p
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 8
Server version: 10.2.15-MariaDB-10.2.15+maria~jessie mariadb.org binary distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> 

Configurando nuestras aplicaciones: ConfigMaps

ConfigMap te permite definir un diccionario (clave,valor) para guardar información que puedes utilizar para configurar una aplicación.

Al crear un ConfigMap los valores se pueden indicar desde un directorio, un fichero o un literal.

kubectl create cm mariadb --from-literal=root_password=my-password \
                          --from-literal=mysql_usuario=usuario     \
                          --from-literal=mysql_password=password-user \
                          --from-literal=basededatos=test
configmap "mariadb" created

kubectl get cm
NAME      DATA      AGE
mariadb   4         15s

kubectl describe cm mariadb
Name:         mariadb
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
mysql_usuario:
----
usuario
root_password:
----
my-password
basededatos:
----
test
mysql_password:
----
password-user
Events:  <none>

Ahora podemos configurar el fichero yaml que define el despliegue, mariadb-deployment-configmap.yaml:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: mariadb-deploy-cm
  labels:
    app: mariadb
    type: database
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: mariadb
        type: database
    spec:
      containers:
        - name: mariadb
          image: mariadb
          ports:
            - containerPort: 3306
              name: db-port
          env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                configMapKeyRef:
                  name: mariadb
                  key: root_password
            - name: MYSQL_USER
              valueFrom:
                configMapKeyRef:
                  name: mariadb
                  key: mysql_usuario
            - name: MYSQL_PASSWORD
              valueFrom:
                configMapKeyRef:
                  name: mariadb
                  key: mysql_password
            - name: MYSQL_DATABASE
              valueFrom:
                configMapKeyRef:
                  name: mariadb
                  key: basededatos

Creamos el despliegue y probamos el acceso:

kubectl create -f mariadb-deployment-configmap.yaml
deployment.apps "mariadb-deploy-cm" created

kubectl get pods -l app=mariadb
NAME                                 READY     STATUS    RESTARTS   AGE
mariadb-deploy-cm-57f7b9c7d7-ll6pv   1/1       Running   0          15s

kubectl exec -it mariadb-deploy-cm-57f7b9c7d7-ll6pv -- mysql -u usuario -p
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 8
Server version: 10.2.15-MariaDB-10.2.15+maria~jessie mariadb.org binary distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
   
MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| test               |
+--------------------+
2 rows in set (0.00 sec)

Configurando nuestras aplicaciones: Secrets

Los Secrets nos permiten guardar información sensible que será codificada. Por ejemplo,nos permite guarda contraseñas, claves ssh, …

Al crear un Secret los valores se pueden indicar desde un directorio, un fichero o un literal.

kubectl create secret generic mariadb --from-literal=password=root
secret "mariadb" created

kubectl get secret
NAME                  TYPE                                  DATA      AGE
...
mariadb               Opaque                                1         15s

kubectl describe secret mariadb
Name:         mariadb
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
password:  4 bytes

Los Secrets no son seguro, no están encriptados.

kubectl get secret mariadb -o yaml
apiVersion: v1
data:
  password: cm9vdA==
kind: Secret
metadata:
  creationTimestamp: 2018-05-23T18:22:27Z
  name: mariadb
  namespace: default
  resourceVersion: "162405"
  selfLink: /api/v1/namespaces/default/secrets/mariadb
  uid: 3fa5e1ad-5eb6-11e8-ab66-fa163e99cb75
type: Opaque

echo 'cm9vdA==' | base64 --decode
root

Podemos definir un Deployment que defina un contenedor configurado por medio de variables de entorno, mariadb-deployment-secret.yaml:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: mariadb-deploy-secret
  labels:
    app: mariadb
    type: database
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: mariadb
        type: database
    spec:
      containers:
        - name: mariadb
          image: mariadb
          ports:
            - containerPort: 3306
              name: db-port
          env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mariadb
                  key: password

Creamos el despliegue y probamos el acceso:

kubectl create -f mariadb-deployment-secret.yaml
deployment.apps "mariadb-deploy-secret" created

kubectl get pods -l app=mariadb
NAME                                    READY     STATUS    RESTARTS   AGE
mariadb-deploy-secret-f946dddfd-kkmlb   1/1       Running   0          15s
    
kubectl exec -it mariadb-deploy-secret-f946dddfd-kkmlb -- mysql -u root -p
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 8
Server version: 10.2.15-MariaDB-10.2.15+maria~jessie mariadb.org binary distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> 

La empresa Bitnami ha desarrollado otro recurso de Kubernete llamado SealedSecrets que permite que un controlador gestione la encriptación de los datos sensibles.

Índice de entradas sobre Kubernetes

Este artículo corresponde a una serie de entradas donde he hablado sobre Kubernetes:

Cursos de introducción a la programación en OpenWebinars

$
0
0

Los dos últimos cursos que he impartido en la plataforma OpenWebinars están relacionados con la programación, más concretamente aprender a programar desde 0 con pseudocódigo y con python3, os dejo los vídeos promocionales de los mismos:

Introducción a la programación con pseudocódigo

En este curso aprenderás los fundamentos a la programación estructurada. Estudiaremos el ciclo de desarrollo de una aplicación: análisis, diseño y codificación mediante pseudocódigo. Los vídeos que se han publicado en YouTube para promocionar el curso han sido:

Curso de Python 3 desde cero

En este curso aprenderás los fundamentos del lenguaje de programación Python 3 desde cero, pensado para no programadores que se quieran iniciar en este mundo. mLos vídeos que se han publicado son los siguientes:

Kubernetes. Desplegando WordPress con MariaDB

$
0
0

Antes de introducir el concepto de almacenamiento persistente, en esta entrada vamos a realizar un despliegue de una aplicación Wordpress y una base de datos MariaDB, para concluir con las consecuencias que tiene que nuestros pods sean efímeros, es decir, cuando se eliminan se pierde la información almacenada. Puedes encontrar todos los ficheros con los que vamos a trabajar en el directorio wordpress.

Vamos a trabajar en un namespace llamado wordpress:

kubectl create -f wordpress-ns.yaml 
namespace "wordpress" created

Desplegando la base de datos MariaDB

A continuación vamos a crear los secrets necesarios para la configuración de la base de datos, vamos a guardarlo en el fichero mariadb-secret.yaml:

kubectl create secret generic mariadb-secret --namespace=wordpress \
                            --from-literal=dbuser=user_wordpress \
                            --from-literal=dbname=wordpress \
                            --from-literal=dbpassword=password1234 \
                            --from-literal=dbrootpassword=root1234 \
                            -o yaml --dry-run > mariadb-secret.yaml


kubectl create -f mariadb-secret.yaml 
secret "mariadb-secret" created

Creamos el servicio, que será de tipo ClusterIP:

kubectl create -f mariadb-srv.yaml 
service "mariadb-service" created

Y desplegamos la aplicación:

kubectl create -f mariadb-deployment.yaml 
deployment.apps "mariadb-deployment" created

Comprobamos los recursos que hemos creado hasta ahora:

kubectl get deploy,service,pods -n wordpress
NAME                                       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/mariadb-deployment   1         1         1            1           20s

NAME                      TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
service/mariadb-service   ClusterIP   10.98.24.76   <none>        3306/TCP   20s

NAME                                     READY     STATUS    RESTARTS   AGE
pod/mariadb-deployment-844c98579-cgp84   1/1       Running   0          20s

Desplegando la aplicación Wordpress

Lo primero creamos el servicio:

kubectl create -f wordpress-srv.yaml 
service "wordpress-service" created

Y realizamos el despliegue:

kubectl create -f wordpress-deployment.yaml 

Y vemos los recursos creados:

kubectl get deploy,service,pods -n wordpress
NAME                                         DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/mariadb-deployment     1         1         1            1           6m
deployment.extensions/wordpress-deployment   1         1         1            1           25s

NAME                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
service/mariadb-service     ClusterIP   10.98.24.76      <none>        3306/TCP                     6m
service/wordpress-service   NodePort    10.111.158.165   <none>        80:30331/TCP,443:30015/TCP   25s

NAME                                        READY     STATUS    RESTARTS   AGE
pod/mariadb-deployment-844c98579-cgp84      1/1       Running   0          6m
pod/wordpress-deployment-866b7d9fd8-wf5t4   1/1       Running   0          25s

Por último creamos el recurso ingress que nos va a permitir el acceso a la aplicación utilizando un nombre:

kubectl create -f wordpress-ingress.yaml 
ingress.extensions "wordpress-ingress" created

kubectl get ingress -n wordpress
NAME                HOSTS                      ADDRESS   PORTS     AGE
wordpress-ingress   wp.172.22.200.178.nip.io             80        20s

Y accedemos:

wp

Problemas que nos encontramos

En realidad no son problemas, son la consecuencia de que los pods son efímeros, cuando se elimina un pod su información se pierde. Por lo tanto nos podemos encontrar con algunas circunstancias:

  1. ¿Qué pasa si eliminamos el despliegue de mariadb?, o, ¿se elimina el pod de mariadb y se crea uno nuevo?. En estas circunstancias se pierde la información de la base de datos y el proceso de instalación comenzará de nuevo.
  2. ¿Qué pasa si escalamos el despliegue de la base de datos y tenemos dos pods ofreciendo la base de datos?. En cada acceso a la aplicación se va a balancear la consulta a la base de datos entre los dos pods (uno que tiene la información de la instalación y otro que que no tiene información), por lo que en los accesos consecutivos nos va a ir mostrando la aplicación y en el siguiente acceso nos va a decir que hay que instalar el wordpress.
  3. Si escribimos un post en el wordpress y subimos una imagen, ese fichero se va a guardar en el pod que está corriendo la aplicación, por lo tanto si se borra, se perderá el contenido estático.
  4. En el caso que tengamos un pods con contenido estático (por ejemplo imágenes) y escalamos el despliegue de wordpress a dos pods, en uno se encontrará la imagen pero en el otro no, por lo tanto en los distintos accesos consecutivos que se hagan a la aplicación se ira mostrando o no la imagen según el pod que este respondiendo.

Para solucionar estos problemas veremos en las siguientes entradas la utilización de volúmenes en Kubernetes.

Índice de entradas sobre Kubernetes

Este artículo corresponde a una serie de entradas donde he hablado sobre Kubernetes:


Almacenamiento en Kubernetes. PersistentVolumen. PersistentVolumenClaims

$
0
0

Como hemos comentado anteriormente los pods son efímero, la información guardada en ellos no es persistente, pero es evidentemente que necesitamos que nuestras aplicaciones tengan la posibilidad de que su información no se pierda. La solución es añadir volúmenes (almacenamiento persistente) a los pods para que lo puedan utilizar los contenedores. Los volúmenes son considerados otro recurso de Kubernetes.

Definiendo volúmenes en un pod

En la definición de un pod, además de especificar los contenedores que lo van a formar, también podemos indicar los volúmenes que tendrá. Además la definición de cada contenedor tendremos que indicar los puntos de montajes de los diferentes volúmenes. Por ejemplo, el el fichero pod-nginx.yaml podemos ver la definición de tres tipos de volúmenes en un pod:

apiVersion: v1
kind: Pod
metadata:
  name: www
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - mountPath: /home
      name: home
    - mountPath: /git
      name: git
      readOnly: true
    - mountPath: /temp
      name: temp
  volumes:
  - name: home
    hostPath:
      path: /home/debian
  - name: git
    gitRepo:
      repository: https://github.com/josedom24/kubernetes.git
  - name: temp
    emptyDir: {}

En la sección volumes definimos los volúmenes disponibles y en la definición del contenedor, con la etiqueta volumeMounts, indicamos los puntos de montajes.

Hemos definido tres volúmenes de diferente tipo:

  • hostPath: Este volumen corresponde a un directorio o fichero del nodo donde se crea el pod. Como vemos se monta en el directorio /home del contenedor. En cluster multinodos este tipo de volúmenes no son efectivos, ya que no tenemos duplicada la información en los distintos nodos y su contenido puede depender del nodo donde se cree el pod.
  • gitRepo: El contenido del volumen corresponde a un repositorio de github, lo vamos a montar en el el directorio /git y como vemos lo hemos configurado de sólo lectura.
  • emptyDir: El contenido de este volumen, que hemos montado en el directorio /temp se borrará al eliminar el pod. Lo utilizamos para compartir información entre los contenedores de un mismo pod.

En la documentación de Kubernetes puedes encontrar todos los tipos de volúmenes que podemos utilizar.

Veamos los volúmenes que hemos creado:

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

kubectl describe pod www
...
Volumes:
  home:
    Type:          HostPath (bare host directory volume)
    Path:          /home/debian
    HostPathType:  
  git:
    Type:        GitRepo (a volume that is pulled from git when the pod is created)
    Repository:  https://github.com/josedom24/kubernetes.git
    Revision:    
  temp:
    Type:    EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:  

Accedemos al pod y vemos los contenidos de cada directorio:

kubectl exec -it www -- bash

root@www:/# cd /home
root@www:/home# ls
fichero.txt
root@www:/home# cd /git
root@www:/git# ls
kubernetes
root@www:/git# cd /temp
root@www:/temp# ls
root@www:/temp# ^C
root@www:/temp# 
  • El directorio /home tiene un fichero que esta en el directorio /home/debian del nodo donde se ha ejecutado el pod.
  • El directorio /git tiene el contenido del repositorio github que hemos indicado.
  • El directorio /temp corresponde a un directorio vacío que podemos utilizar para compartir información entre los contenedores del pod,la información de este directorio se perderá al eliminarlo.

Compartiendo información en un pod

Veamos con un ejemplo la posibilidad de compartir información entre contenedores de un pod. En el fichero pod2-nginx.yaml creamos un pod con dos contenedores y un volumen:

apiVersion: v1
kind: Pod
metadata:
  name: two-containers
spec:

  volumes:
  - name: shared-data
    emptyDir: {}

  containers:
  - name: nginx-container
    image: nginx
    volumeMounts:
    - name: shared-data
      mountPath: /usr/share/nginx/html

  - name: busybox-container
    image: busybox
    command:
      - sleep
      - "3600"
    volumeMounts:
    - name: shared-data
      mountPath: /pod-data

Vamos a crear el pod, vamos acceder al contenedor busybox-cotainer y vamos a escribir un index.html, que al estar compartido por el contenedor nginx-container podremos ver ala acceder a él.

kubectl create -f pod2-nginx.yaml 
pod "two-containers" created
    
kubectl get pods 
NAME             READY     STATUS    RESTARTS   AGE
two-containers   2/2       Running   0          10s
www              1/1       Running   0          1h

kubectl exec -it two-containers -c busybox-container -- sh
/ # cd /pod-data/
/pod-data # echo "Prueba de compartir información entre contenedores">index.html
/pod-data # exit

kubectl port-forward two-containers 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
Handling connection for 8080

nginx

Almacenamiento disponible en Kubernetes: PersistentVolumen

Ya hemos visto que podemos añadir almacenamiento a un pod, sin embargo habría que distinguir dos conceptos:

  • El desarrollador de aplicaciones no debería conocer con profundidad las características de almacenamiento que le ofrece el cluster. Desde este punto de vista, al desarrollador le puede dar igual que tipo de volumen puede utilizar (aunque en algún caso puede ser interesante indicarlo), lo que le interesa es, por ejemplo, el tamaño y las operaciones (lectura, lectura y escritura) del almacenamiento que necesita, y obtener del cluster un almacenamiento que se ajuste a esas características. La solicitud de almacenamiento se realiza con un elemento del cluster llamado PersistentVolumenCliams.
  • El administrador será el responsable de dar de alta en el cluster los distintos tipos de almacenamientos que hay disponibles, y que se representa con un recurso llamado PersistentVolumen.

Definiendo un PersistentVolumen

Un PersistentVolumen es un objeto que representa los volúmenes disponibles en el cluster. En él se van a definir los detalles del backend de almacenamiento que vamos a utilizar, el tamaño disponible, los modos de acceso, las políticas de reciclaje, etc.

Tenemos tres modos de acceso, que depende del backend que vamos a utilizar:

  • ReadWriteOnce: read-write solo para un nodo (RWO)
  • ReadOnlyMany: read-only para muchos nodos (ROX)
  • ReadWriteMany: read-write para muchos nodos (RWX)

Las políticas de reciclaje de volúmenes también depende del backend y son:

  • Retain: Reclamación manual
  • Recycle: Reutilizar contenido
  • Delete: Borrar contenido

Creando un PersistentVolumen con NFS

Vamos a instalar en el master del cluster (lo podríamos tener en cualquier otro servidor) un servidor NFS para compartir directorios en los nodos del cluster.

Configuración en el master

En el master como root, ejecutamos:

apt install nfs-kernel-server
mkdir -p /var/shared

Y en el fichero /etc/exports declaramos el directorio que vamos a exportar:

/var/shared 10.0.0.0/24(rw,sync,no_root_squash,no_all_squash)

Nota: La red 10.0.0.0/24 es la red interna donde se encuentra el master y los nodos del cluster.

Por último reiniciamos el servicio:

systemctl restart nfs-kernel-server.service

Y comprobamos los directorios exportados:

showmount -e 127.0.0.1
Export list for 127.0.0.1:
/var/shared 10.0.0.0/24

Configuración en los nodos

En cada uno de los nodos del cluster vamos a montar el directorio compartido, para ello:

apt install nfs-common

Y comprobamos los directorios exportados en el master:

showmount -e 10.0.0.4
Export list for 10.0.0.4:
/var/shared 10.0.0.0/24

Y ya podemos montarlo:

mount -t nfs4 10.0.0.4:/var/shared /var/data

Creación del volumen en Kubernetes

Ya podemos crear el volumen utilizando el objeto PersistentVolumen. Lo definimos en el fichero nfs-pv.yaml:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    path: /var/shared
    server: 10.0.0.4

Y lo creamos y vemos el recurso:

kubectl create -f nfs-pv.yaml 
persistentvolume "nfs-pv" created

kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM     STORAGECLASS   REASON    AGE
nfs-pv    5Gi        RWX            Recycle          Available                                      10s

El tipo de volumen disponible lo vamos a referenciar con su nombre (nfs-pv), tiene 5Gb de capacidad, estamos utilizando NFS, el modo de acceso es RWX y su política de reciclaje es de reutilización del contenido.

Solicitud de almacenamiento en Kubernetes: PersistentVolumenClaims

A continuación si nuestro pod necesita un volumen, necesitamos hacer una solicitud de almacenamiento usando un obketo del tipo PersistentVolumenCliams.

Cuando creamos un PersistentVolumenCliams, se asignará un PersistentVolumen que se ajuste a la petición. Está asignación se puede configurar de dos maneras distintas:

  • Estática: Primero se crea todos los PersistentVolumenCliams por parte del administrador, que se irán asignando conforme se vayan creando los PersistentVolumen.
  • Dinámica: En este caso necesitamos un “provisionador” de almacenamiento (para cada uno de los backend), de tal manera que cada vez que se cree un PersistentVolumenClaim, se creará bajo demanda un PersistentVolumen que se ajuste a las características seleccionadas.

Creación de PersistentVolumenClaim

Siguiendo con el ejercicio anterior vamos a crear una solicitud de almacenamiento del volumen creado anteriormente con NFS. Definimos el objeto en el fichero nfs-pvc.yaml:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi

Creamos el recurso y obtenemos los recursos que tenemos a nuestra disposición:

kubectl create -f nfs-pvc.yaml
persistentvolumeclaim "nfs-pvc" created

kubectl get pv,pvc
NAME                      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM             STORAGECLASS   REASON    AGE
persistentvolume/nfs-pv   5Gi        RWX            Recycle          Bound     default/nfs-pvc                            14m

NAME                            STATUS    VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/nfs-pvc   Bound     nfs-pv    5Gi        RWX                           15s

Como podemos observar al crear el pvc se busca del conjunto de pv uno que cumpla sus requerimientos, y se asocian (status bound) por lo tanto el tamaño indicado en el pvc es el valor mínimo de tamaño que se necesita, pero el tamaño real será el mismo que el del pv asociado.

Si queremos añadir un volumen a un pod a partir de esta solicitud, podemos usar la definición del fichero pod-nginx-pvc:

apiVersion: v1
kind: Pod
metadata:
  name: www-vol
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
      - mountPath: /usr/share/nginx/html
        name: nfs-vol
  volumes:
    - name: nfs-vol
      persistentVolumeClaim:
        claimName: nfs-pvc

Lo creamos y accedemos a él:

kubectl create -f pod-nginx-pvc.yaml
pod "www-vol" created

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

nginx

Evidentemente al montar el directorio DocumentRoot del servidor (/usr/share/nginx/html) en el volumen NFS, no tiene index.html, podemos crear uno en el directorio compartido del master y estará disponible en todos los nodos:

echo "It works..." | ssh debian@172.22.201.15 'cat >> /var/shared/index.html'
 
kubectl port-forward www-vol 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

nginx

Si escalamos el pod no tendríamos ningún problema ya que todos los nodos del cluster comparten el mismo directorio referenciado por el volumen. Además el contenido del volumen es persistente, y aunque eliminemos el pod, la información no se pierde:

kubectl delete pod www-vol
pod "www-vol" deleted

kubectl create -f pod-nginx-pvc.yaml
pod "www-vol" created

kubectl port-forward www-vol 8080:80
Frwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

Y desde otro terminal:

curl http://localhost:8080
It works...

Además podemos comprobar cómo se ha montado el volumen en el contenedor:

kubectl exec -it www-vol -- bash

root@www-vol:/# df -h
...
10.0.0.4:/var/shared   20G  4.8G   15G  26% /usr/share/nginx/html
...

Índice de entradas sobre Kubernetes

Este artículo corresponde a una serie de entradas donde he hablado sobre Kubernetes:

Kubernetes. Desplegando WordPress con MariaDB con almacenamiento persistente

$
0
0

Puedes encontrar todos los ficheros con los que vamos a trabajar en el directorio wordpress2.

Configuración del servidor NFS

Para este ejercicio hemos creado dos directorio que hemos exportado en el servidor NFS, en el fichero /etc/exports encontramos:

/var/shared/vol1 10.0.0.0/24(rw,sync,no_root_squash,no_all_squash)
/var/shared/vol2 10.0.0.0/24(rw,sync,no_root_squash,no_all_squash)

Y en los clientes montamos dichos directorios:

mount -t nfs4 10.0.0.4:/var/shared/vol1 /var/data/vol1
mount -t nfs4 10.0.0.4:/var/shared/vol2 /var/data/vol2

Gestión del almacenamiento para nuestro despliegue

Lo primero que hacemos es crear los dos pv que podemos encontrar definidos en el fichero wordpress-pv.yaml:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: volumen1
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    path: /var/shared/vol1
    server: 10.0.0.4
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: volumen2
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    path: /var/shared/vol2
    server: 10.0.0.4

Lo creamos:

kubectl create -f wordpress-pv.yaml
persistentvolume "volumen1" created
persistentvolume "volumen2" created

A continuación vamos a trabajar con un namespace, por lo tanto lo creamos:

kubectl create -f wordpress-ns.yaml 
namespace "wordpress" created

Vamos a realizar la solicitud de almacenamiento para la base de datos, que tenemos definido en el fichero mariadb-pvc.yaml:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mariadb-pvc
  namespace: wordpress
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi

De forma similar solicitamos el almacenamiento para nuestra aplicación. Esta solicitud la tenemos definida en el fichero wordpress-pvc.yaml.

Creamos las solicitudes:

kubectl create -f wordpress-pvc.yaml 
persistentvolumeclaim "wordpress-pvc" created

kubectl create -f mariadb-pvc.yaml  
persistentvolumeclaim "mariadb-pvc" created

Y lo comprobamos:

kubectl get pv,pvc -n wordpress     
NAME                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM                     STORAGECLASS   REASON    AGE
persistentvolume/volumen1   5Gi        RWX            Recycle          Bound     wordpress/wordpress-pvc                            50s
persistentvolume/volumen2   5Gi        RWX            Recycle          Bound     wordpress/mariadb-pvc                              49s

NAME                                  STATUS    VOLUME     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/mariadb-pvc     Bound     volumen2   5Gi        RWX                           10s
persistentvolumeclaim/wordpress-pvc   Bound     volumen1   5Gi        RWX                           23s

Desplegando nuestra aplicación

El proceso a partir de aquí es muy parecido al ejercicio anterior, a excepción de que tenemos que modificar los deployments para indicar los volumenes de los pods: el fichero mariadb-depoyment.yaml y el fichero wordpress-depoyment.yaml.

Y creamos los distintos recursos:

kubectl create -f mariadb-srv.yaml 
service "mariadb-service" created

kubectl create -f wordpress-srv.yaml 
service "wordpress-service" created

kubectl create -f wordpress-ingress.yaml 
ingress.extensions "wordpress-ingress" created

kubectl create -f mariadb-secret.yaml 
secret "mariadb-secret" created

kubectl create -f mariadb-deployment.yaml
deployment.apps "mariadb-deployment" created

kubectl create -f wordpress-deployment.yaml
deployment.apps "wordpress-deployment" created

Y comprobamos todos los recursos creados:

kubectl get pvc,services,deploy,ingress -n wordpress
NAME                                  STATUS    VOLUME     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/mariadb-pvc     Bound     volumen2   5Gi        RWX                           22s
persistentvolumeclaim/wordpress-pvc   Bound     volumen1   5Gi        RWX                           23s

NAME                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
service/mariadb-service     ClusterIP   10.103.198.179   <none>        3306/TCP                     21s
service/wordpress-service   NodePort    10.105.13.172    <none>        80:32084/TCP,443:31262/TCP   21s

NAME                                         DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/mariadb-deployment     1         1         1            1           20s
deployment.extensions/wordpress-deployment   1         1         1            1           20s

NAME                                   HOSTS                      ADDRESS   PORTS     AGE
ingress.extensions/wordpress-ingress   wp.172.22.200.178.nip.io             80        21s

Ya podemos acceder al wordpress, incializarlo, entrar en la administración y crear el primer post:

wp

Conclusiones

En este ejercicio hemos conseguido que nuestros pods no sean efímeros, por lo tanto:

  1. ¿Qué pasa si eliminamos el despliegue de mariadb?, o, ¿se elimina el pod de mariadb y se crea uno nuevo?. En nuestro nuevo escenario, no se perdería la información de la base de datos, ya que está guardad en un volumen compartido por todos los nodos.
  2. ¿Qué pasa si escalamos el despliegue de la base de datos y tenemos dos pods ofreciendo la base de datos?. En cada acceso a la aplicación se va a balancear la consulta a la base de datos entre los dos pods, y en nuestro escenario puede provocar que se corrompa la información de la base de datos al escribir y leer al mismo tiempo, una posible solución sería usar un cluster de mysql con galera.
  3. Si escribimos un post en el wordpress y subimos una imagen, ese fichero se va a guardar en el volumen compartido por todos los nodos, por lo tanto si se borra, no se perderá el contenido estático.
  4. En el caso que tengamos un pods con contenido estático (por ejemplo imágenes) y escalamos el despliegue de wordpress a dos pods, los dos pods tienen el contenido estático, ya que comparten información en el columen compartido.

Índice de entradas sobre Kubernetes

Este artículo corresponde a una serie de entradas donde he hablado sobre Kubernetes:

Integración de Kubernetes con OpenStack

$
0
0

ikos

Vamos a realizar una instalación de Kubernetes y los vamos a configurar para que utilice los recursos ofrecidos por OpenStack: en concreto, vamos a poder crear de forma dinámica volúmenes (PersistentVolumen) ofrecidos por el componente de OpenStack encargado de la gestión de volúmenes: Cinder, y vamos a poder crear Services del tipo LoadBalancer creando un balanceador en el componente de redes de OpenStack: Neutron.

Tenemos dos opciones de configuración para conseguir comunicarnos con un proveedor de cloud:

  • Configurar el componente Kube Controller Manager, que entre otras funciones, se encargará de conectar con el proveedor cloud (en nuestro caso OpenStack).
  • Desde la versión 1.6 de Kubernetes se ha introducido un nuevo componente Cloud Controller Manager que específicamente es el encargado de gestionar el proveedor cloud, de tal manera que la arquitectura de componentes de Kuberentes se hace más modular.

En esta unidad vamos a configurar el Kube Controller Manager para comunicarse con OpenSatck. Para la segunda opción utilizando Cloud Controller Manager para comunicarse con OpenStack puede ser muy interesante el repositorio de openstack-cloud-controller-manager.

Integración de Kubernetes y OpenStack con Kubeadm

Vamos a partir de una instalación de kubernetes con kubeadm (puedes seguir el apartado Instalación de kubernetes con kubeadm). En esta instalación he usado el plugin CNI wave:

sysctl net.bridge.bridge-nf-call-iptables=1
kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"

Si necesitas reinstalar kubeadm debes ejecutar kubeadm reset en todos los nodos del cluster.

Configuración del acceso a OpenStack

Lo primero, vamos a crear un fichero cloud.conf donde vamos a guardar las credenciales de acceso a OpenStack y los recursos que vamos a utilizar:

[Global]
auth-url=https://<openstack_endpoint>:5000/v3
domain-name=Nombre del dominio
tenant-name=Nombre del proyecto
username=usuario
password=contraseña
ca-file=/etc/kubernetes/ca.crt

[LoadBalancer]
subnet-id=bf7be908-51a4-45d1-8403-391cfe1a73aa
floating-network-id=49812d85-8e7a-4c31-baa2-d427692f6568

Se pueden configurar más opciones que puedes encontrar en este enlace.

En mi caso el acceso a OpenStack se hace de forma cifrada (con https) por lo que necesito el certificado de la Autoridad Certificadora. Los ficheros cloud.conf y ca.crt los guardo en el directorio /etc/kubernetes del master y los nodos del cluster.

Configurando kube-controller-manager

Tenemos que modificar la configuración del pod kube-controller-manager indicado el proveedor cloud que vamos autilizar y el fichero de configuración que debe utilizar (cloud.conf). Además debemos asegurarnos que el pod tiene acceso al fichero de configuración cloud.conf y al certificado de la CA. Para realizar la configuración debemos modificar el fichero /etc/kubernetes/manifests/kube-controller-manager.yaml en el nodo master de la siguiente forma:

... 
spec:
  containers:
  - command:
    - kube-controller-manager
    ...
    - --cloud-provider=openstack- --cloud-config=/etc/kubernetes/cloud.conf
...
    volumeMounts:
    ...
    - mountPath: /etc/kubernetes/cloud.conf
      name: cloud-config
      readOnly: true 
    - mountPath: /etc/kubernetes/ca.crt
      name: cloud-ca
      readOnly: true
...
volumes:
  ...
  - hostPath:
      path: /etc/kubernetes/cloud.conf
      type: FileOrCreate
    name: cloud-config
  - hostPath:
      path: /etc/kubernetes/ca.crt
      type: FileOrCreate
    name: cloud-ca

Una vez modificado correctamente el fichero, el pod kube-controller-manager se reiniciará con la nueva configuración, podemos comprobar que la modificación ha sido realizada con la siguiente instrucción:

kubectl describe pod kube-controller-manager -n kube-system | grep '/etc/kubernetes/cloud.conf'

      --cloud-config=/etc/kubernetes/cloud.conf
      /etc/kubernetes/cloud.conf from cloud-config (ro)
    Path:          /etc/kubernetes/cloud.conf

Configurando kubelet

A continuación debemos reiniciar el componente kubelet en el master y en los nodos del cluster modificando su configuración para indicarles el proveedor cloud que vamos a utilizar y el fichero de configuración que debe utilizar (cloud.conf). Para ello modificamos el fichero /etc/systemd/system/kubelet.service.d/10-kubeadm.conf añadiendo la siguiente línea:

Environment="KUBELET_EXTRA_ARGS=--cloud-provider=openstack --cloud-config=/etc/kubernetes/cloud.conf"

Y reiniciamos el servicio:

systemctl daemon-reload
systemctl restart kubelet

Y comprobamos que el servicio está ejecutándose:

ps xau | grep /usr/bin/kubelet

root      5323 14.0  3.6 354508 74652 ?        Ssl  17:52   0:01 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --pod-manifest-path=/etc/kubernetes/manifests --allow-privileged=true --network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin --cluster-dns=10.96.0.10 --cluster-domain=cluster.local --authorization-mode=Webhook --client-ca-file=/etc/kubernetes/pki/ca.crt --cadvisor-port=0 --rotate-certificates=true --cert-dir=/var/lib/kubelet/pki --cloud-provider=openstack --cloud-config=/etc/kubernetes/cloud.conf

Probando la creación dinámica volúmenes por Cinder

Lo primero es crear un recurso del tipo StorageClass Que nos permite configurar un origen o clase de almacenamiento disponible en el cluster. Para ello creamos el fichero cinder-sc.yaml:

apiVersion: storage.k8s.io/v1beta1
kind: StorageClass
metadata:
  name: standard
  annotations:
    storageclass.beta.kubernetes.io/is-default-class: "true"
  labels:
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: EnsureExists
provisioner: kubernetes.io/cinder

Y creamos el recurso:

kubectl create -f cinder-sc.yaml 
storageclass.storage.k8s.io "standard" created

kubectl get sc
NAME                 PROVISIONER            AGE
standard (default)   kubernetes.io/cinder   40s

A continuación vamos a crear un recurso PersistentVolumeClaim con el fichero demo-cinder-pvc.yaml:

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: cinder-claim
  annotations:
    volume.beta.kubernetes.io/storage-class: "standard"
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

Y lo creamos:

kubectl create -f demo-cinder-pvc.yaml 
persistentvolumeclaim "cinder-claim" created

Y podemos comprobar como de forma dinámica se ha creado un recurso PersistentVolumen:

kubectl get pv,pvc

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY    STATUS    CLAIM                  STORAGECLASS   REASON    AGE
persistentvolume/pvc-569773b8-682a-11e8-931d-fa163e99cb75   1Gi        RWO            Delete           Bound        default/cinder-claim   standard                 19s

NAME                                 STATUS    VOLUME                                     CAPACITY   ACCESS     MODES   STORAGECLASS   AGE
persistentvolumeclaim/cinder-claim   Bound     pvc-569773b8-682a-11e8-931d-fa163e99cb75   1Gi        RWO            standard       19s

Como podemos comprobar al crear un volumen en Kubernetes desde Cinder el modo de acceso es RWO: Escritura y lectura para un sólo nodo.

Además podemos comprobar como realmente se ha creado un volumen en OpenStack:

openstack volume list                   
+--------------------------------------+-------------------------------------------------------------+-----------+------+-------------+
| ID                                   | Name                                                        | Status    |  Size |Attached to |
+--------------------------------------+-------------------------------------------------------------+-----------+------+-------------+
| e952c9c8-b423-451f-b6a0-32521e0a6fe6 | kubernetes-dynamic-pvc-569773b8-682a-11e8-931d-fa163e99cb75 | in-use    |    1 | available   | 
+--------------------------------------+-------------------------------------------------------------+-----------+------+-------------+

Utilizando el volumen en un despliegue

A continuación vamos a crear un deployment que cree un servidor nginx cuyo DocumentRoot va a estar montado en el volumen que hemos creado, para ello creamos el fichero nginx-deployment.yaml:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx
  namespace: default
  labels:
    app: nginx
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        volumeMounts:
          - mountPath: /usr/share/nginx/html
            name: vol
        ports:
        - name: http
          containerPort: 80
      volumes:
        - name: vol
          persistentVolumeClaim:
            claimName: cinder-claim

Creamos el deployment y comprobamos que el volumen de OpenStack se ha conectado con el nodo donde se ha creado el pod:

openstack volume list      

+--------------------------------------+-------------------------------------------------------------+--------+------+--------------------------------+
| ID                                   | Name                                                        |  Status| Size | Attached to                    |
+--------------------------------------+-------------------------------------------------------------+--------+------+--------------------------------+
| e952c9c8-b423-451f-b6a0-32521e0a6fe6 | kubernetes-dynamic-pvc-569773b8-682a-11e8-931d-fa163e99cb75 |  in-use|    1 | Attached to k8s-3 on /dev/vdb  |
+--------------------------------------+-------------------------------------------------------------+--------+------+--------------------------------+

Evidentemente el volumen que acabamos de crear está vacío, por lo tanto vamos a crear un fichero index.html:

kubectl exec -it nginx-695ffcfd59-js75s -- bash -c "echo '<h1>Kubernetes and Openstack</h1>'>/usr/share/nginx/html/index.html"

Uso de servicio tipo LoadBalancer

Ya hemos comprobado como podemos crear de forma dinámica volúmenes en cinder desde Kubernetes, en este apartado vamos a crear un servicio de tipo LoadBalancer, que va a crear en el componente neutron de OpenStack un balanceador de carga con una IP flotante asignada que nos permitirá acceder al servidor nginx. Para ello creamos un fichero nginx-srv.yaml de la siguiente forma:

apiVersion: v1
kind: Service
metadata:
  name: nginx
  namespace: default
spec:
  type: LoadBalancer
  ports:
  - name: http
    port: 80
    targetPort: http
  selector:
    app: nginx

Creamos el servicio, y al cabo de unos segundos comprobamos la IP flotante asignada al balanceador de carga de OpenStack:

kubectl create -f nginx-srv.yaml
service "nginx" created

kubectl get services
NAME         TYPE           CLUSTER-IP    EXTERNAL-IP      PORT(S)        AGE
...
nginx        LoadBalancer   10.98.19.43   172.22.201.196   80:32415/TCP   18s

Podemos comprobar en OpenStack que le balanceador se ha creado:

neutron lbaas-loadbalancer-list                                                                                               

+--------------------------------------+----------------------------------+-------------+---------------------+----------+
| id                                   | name                             | vip_address | provisioning_status | provider |
+--------------------------------------+----------------------------------+-------------+---------------------+----------+
| 6b4d7557-4762-4d77-bdb4-b26f77b4d471 | a56a875a9683111e8931dfa163e99cb7 | 10.0.0.8    | ACTIVE              | haproxy  |
+--------------------------------------+----------------------------------+-------------+---------------------+----------+

Y comprobamos la ip flotante asignada:

openstack floating ip list

+--------------------------------------+---------------------+------------------+--------------------------------------+--------------------------------------  +----------------------------------+
| ID                                   | Floating IP Address | Fixed IP Address | Port                                 | Floating Network                     |     Project                          |
+--------------------------------------+---------------------+------------------+--------------------------------------+--------------------------------------  +----------------------------------+
...
| b0cfa51e-2cf8-40bf-9e8e-d37430883889 | 172.22.201.196      | 10.0.0.8         | 17c66af7-8783-45a4-8d69-e1cf7723894f | 49812d85-8e7a-4c31-baa2-d427692f6568 |     d8799b3f93124dd7b79cde85730dff6d |
...

Por último podemos acceder al servidor web utilizando la IP flotante del balanceador que hemos creado:

nginx

Enlaces de referencia

Índice de entradas sobre Kubernetes

Este artículo corresponde a una serie de entradas donde he hablado sobre Kubernetes:

El truco era el Software Libre

$
0
0

El pasado 25 de mayo, junto a mi compañero Alberto Molina, participamos en las jornadas OpenSouthCode19 presentando la charla: El truco era el Softare Libre. Formando administradores de sistemas en el IES Gonzalo Nazareno, donde contamos nuestra experiencia en estos años de como el uso de Sotfware Libre ha revolucionado nuestra manera de dar clases y como ha ayudado a los alumnos a obtener una formación más completa en la administración de sistemas. La experiencia de participar en estas jornadas ha sido muy positiva. Os dejo aquí la presentación que utilizamos para la charla:

Curso de introducción a la programación con pseudocódigo

$
0
0

Curso de introducción a la programación con pseudocódigo

Un algoritmo es un conjunto de acciones que especifican la secuencia de operaciones realizar, en orden, para resolver un problema. El pseudocódigo, nos permite una aproximación del algoritmo al lenguaje natural y por tanto un a redacción rápida del mismo. En este curso se presenta los fundamentos para analizar problemas y resolverlos a través de pseudocódigo.

Los siguientes contenidos forman parte de un curso que he impartido para OpenWebinars en mayo de 2018.

Puedes obtener todo el contenido del curso en la Plataforma PLEDIN. Todas las observaciones, mejoras y sugerencias son bienvenidas.

Contenido del curso

  1. Introducción al curso

    Introducción a la programación

  2. Resolución de problemas
  3. Análisis del problema
  4. Diseño de algoritmos

Entorno de trabajo: PSeInt

  1. Introducción a PSeInt

    Pseudocódigo: Introducción

  2. Estructura del algoritmo
  3. Tipos de datos simples
  4. Variables
  5. Operadores y expresiones
  6. Asignación de variables
  7. Entrada y salida de información
  8. Otras instrucciones
  9. Funciones matemáticas
  10. Funciones de cadenas de texto
  11. Nuestro primer pseudocódigo completo
  12. Ejecución paso a paso
  13. Ejercicios estructura secuencial

    Pseudocódigo: Estructuras alternativas

  14. Estructuras alternativas: Si
  15. Estructuras alternativas: Segun
  16. Ejercicios estructuras alternativas

    Pseudocódigo: Estructuras repetitivas

  17. Estructuras repetitivas: Mientras
  18. Estructuras repetitivas: Repetir-Hasta Que
  19. Estructuras repetitivas: Para
  20. Uso específico de variables: contadores, acumuladores e indicadores
  21. Ejercicios estructuras repetitivas
  22. Ejercicios cadenas de caracteres

    Pseudocódigo: Arreglos

  23. Estructuras de datos: Arreglos (array)
  24. Arreglos unidimensionales: Vectores
  25. Arreglos multidimensionales: Tablas
  26. Ejercicios de arreglos

    Pseudocódigo: Programación estructurada

  27. Programación estructurada
  28. Funciones y procedimientos
  29. Funciones recursivas
  30. Ejercicios de funciones
  31. Más ejercicios

    Lenguajes de Programación

  32. Introducción a los lenguajes de programación
  33. Programas traductores
  34. Compilación y ejecución de un lenguaje compilado: C++
  35. Compilación e interpretación de un programa Java
  36. Ejecución de programas interpretados con Python
Viewing all 104 articles
Browse latest View live