OpenStack: LoadBalancer as a Service v2

Vamos a ver como instalar y configurar LBaaSv2 en OpenStack Newton. Está montado sobre Ubuntu 16.04

Instalación

Toda la operación se desarrolla en el nodo controlador de OpenStack o donde esté configurada la red de Neutron

Puede parecer obvio, pero la documentación oficial no te dice que hay que instalar primero el plugin, para esto hacemos:

apt-get install neutron-lbaasv2-agent

Dependiendo de tu entorno instalará estas dependencias

python-pyasn1-modules:amd64 (0.0.7-0.1, automatic), haproxy:amd64 (1.6.3-1ubuntu0.1, automatic), liblua5.3-0:amd64 (5.3.1-1ubuntu2, automatic), python-neutron-lbaas:amd64 (2:9.0.0-0ubuntu1~cloud0, automatic), neutron-lbaasv2-agent:amd64 (2:9.0.0-0ubuntu1~cloud0), neutron-lbaas-common:amd64

Configuración

Vamos a editar los archivos de configuración:

En /etc/neutron/neutron.conf añadimos el plugin a la lista de plugins que haya.

service_plugins = [existing service plugins],neutron_lbaas.services.loadbalancer.plugin.LoadBalancerPluginv2

En /etc/neutron/neutron_lbaas.conf añadimos una linea en la sección [service_providers]. Si ya tenemos otros proveedores de servicios como FWaaS o VPNaaS añadimos una linea nueva, Esta directiva de configuración permite repetición.

service_provider = LOADBALANCERV2:Haproxy:neutron_lbaas.drivers.haproxy.plugin_driver.HaproxyOnHostPluginDriver:default

En /etc/neutron/lbaas_agent.ini añadimos el driver que maneja la interfaces virtuales:

interface_driver = INTERFACE_DRIVER

Yo uso linuxbridge, luego la línea quedaría así:

interface_driver = neutron.agent.linux.interface.BridgeInterfaceDriver

Finalizamos con el migrate de la base de datos.

neutron-db-manage --subproject neutron-lbaas upgrade head

Reiniciamos neutron

systemctl neutron-server restart

Reiniciamos el agente

systemctl restart Neutron-lbaasv2-agent

Y con esto termina la configuración.

Crear un LoadBalancer

Tenemos dos servidores web corriendo en nuestro Tenant y queremos balancear la carga. Los dos servidores son:

webserver-1 172.31.1.25
webserver-2 172.31.1.16

Deben estar incluidos en alguna regla que permita el trafico HTTP hacia ellos, en caso contrario podemos crearlo:

# Cargar en openrc
. openrc
# Crear el security group
neutron security-group-create http
# Crear la regla
neutron security-group-rule-create --direction ingress --protocol tcp --port-range-min 80 --port-range-max 80 --remote-ip-prefix 0.0.0.0/0 http

Vamos a crear el balanceador

neutron lbaas-loadbalancer-create --name test-lb private-subnet

private-subnet es una subred privada. Podemos consultar las redes disponibles haciendo:

openstack network list

El balanceador se crea en unos segundos, podemos verlo así:

neutron lbaas-loadbalancer-show test-lb

Que mostrará algo como esto:

+---------------------+------------------------------------------------+
| Field               | Value                                          |
+---------------------+------------------------------------------------+
| admin_state_up      | True                                           |
| description         |                                                |
| id                  | 12826d46-9854-4123-9193-37b8c2511729           |
| listeners           | {"id": "75d5218b-948e-482b-b7a9-d831d0feb476"} |
| name                | test-lb                                        |
| operating_status    | ONLINE                                         |
| pools               | {"id": "860726ba-1997-453f-9e32-7879254e3306"} |
| provider            | haproxy                                        |
| provisioning_status | ACTIVE                                         |
| tenant_id           | d245f207e6fc4e56b5a1d23e0716ad70               |
| vip_address         | 172.31.1.23                                    |
| vip_port_id         | 1e090071-2800-4468-8ab7-0493d0881e10           |
| vip_subnet_id       | a03252b6-e67e-48d9-89fc-edc3872d83ae           |
+---------------------+------------------------------------------------+

Apuntar el valor de vip_port_id porque lo necesitaremos más adelante.

Vamos a crear los grupos de seguridad necesarios para dirigir tráfico al balanceador:

$ neutron security-group-create lbaas
$ neutron security-group-rule-create \
  --direction ingress \
  --protocol tcp \
  --port-range-min 80 \
  --port-range-max 80 \
  --remote-ip-prefix 0.0.0.0/0 \
  lbaas
$ neutron security-group-rule-create \
  --direction ingress \
  --protocol tcp \
  --port-range-min 443 \
  --port-range-max 443 \
  --remote-ip-prefix 0.0.0.0/0 \
  lbaas
$ neutron security-group-rule-create \
  --direction ingress \
  --protocol icmp \
  lbaas

Hemos permitido trafico HTTP, HTTPS y el ping. Ahora aplicamos las reglas al puerto del balanceador. El vip_port_id que apuntamos antes.

$ neutron port-update \
  --security-group lbaas \
  1e090071-2800-4468-8ab7-0493d0881e10

Podemos comprobar que está activo al hacer ping.

ping -c3 172.31.1.23
PING 172.31.1.23 (172.31.1.23) 56(84) bytes of data.
64 bytes from 172.31.1.23: icmp_seq=1 ttl=62 time=19.2 ms
64 bytes from 172.31.1.23: icmp_seq=2 ttl=62 time=0.491 ms
64 bytes from 172.31.1.23: icmp_seq=3 ttl=62 time=0.312 ms

--- 172.31.1.23 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.312/6.678/19.232/8.877 ms

Creamos los listeners para el balanceador:

$ neutron lbaas-listener-create \
  --name test-lb-http \
  --loadbalancer test-lb \
  --protocol HTTP \
  --protocol-port 80

Y por último el pool de servidores a los que se balancea:

$ neutron lbaas-pool-create \
  --name test-lb-pool-http \
  --lb-algorithm ROUND_ROBIN \
  --listener test-lb-http \
  --protocol HTTP
$ neutron lbaas-member-create \
  --subnet private-subnet \
  --address 172.31.1.16 \
  --protocol-port 80 \
  test-lb-pool-http
$ neutron lbaas-member-create \
  --subnet private-subnet \
  --address 172.31.1.25  \
  --protocol-port 80 \
  test-lb-pool-http

Ya sólo nos queda comprobar que funciona:

$ for i in $(seq 1 4); do curl 172.31.1.23; done
webserver-1
webserver-2
webserver-1
webserver-2
Posted in OpenStack | Tagged , , | Comments Off on OpenStack: LoadBalancer as a Service v2

Empaquetando Aplicaciones para Kubernetes

En la entrada anterior hemos visto como utilizar las herramientas que nos proporciona Kubernetes para construir nuestra aplicación. Ahora vamos a ver lo fácil que es construir un paquete con toda la información que va a necesitar nuestra aplicación para funcionar.

El gestor de paquetes de Kubernetes

El gestor de paquete de Kubernetes se llama Helm. También es el nombre del comando.

Para instalar Helm simplemente nos descargamos el binario y lo colocamos en el PATH.

$ wget http://storage.googleapis.com/kubernetes-helm/helm-v2.1.3-linux-amd64.tar.gz
$ mv linux-amd64/helm /usr/local/bin/helm

Podemos ver que está instalado con:

$ helm version
Client: &version.Version{SemVer:"v2.1.2", GitCommit:"58e545f47002e36ca71ac5d1f7a987b56e1937b3", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.1.2", GitCommit:"58e545f47002e36ca71ac5d1f7a987b56e1937b3", GitTreeState:"clean"}

Helm cuenta con otro componente que se llama Tiller, es un pod que actua como un agente dentro de Kubernetes. La forma más rápida de desplegar el agente es así:

$ helm init

Esto tomará los valores por defecto para conectar con el clúster y desplegar el agente. Vemos el agente desplegado como pod

$ kubectl --namespace=kube-system get pods --selector='name=tiller'
NAME                             READY     STATUS    RESTARTS   AGE
tiller-deploy-3161388333-0lqd4   1/1       Running   8          6d

Estructura de un paquete para Kubernetes/Helm

Al igual que otros paquetes como los deb de Debian los paquetes de Helm se llaman Charts y tienen la siguiente estructura:

wordpress/
  Chart.yaml          # A YAML file containing information about the chart
  LICENSE             # OPTIONAL: A plain text file containing the license for the chart
  README.md           # OPTIONAL: A human-readable README file
  values.yaml         # The default configuration values for this chart
  charts/             # OPTIONAL: A directory containing any charts upon which this chart depends.
  templates/          # OPTIONAL: A directory of templates that, when combined with values,
                      # will generate valid Kubernetes manifest files.
  templates/NOTES.txt # OPTIONAL: A plain text file containing short usage notes

Todos nos interesan ahora mismo excepto el de charts/ porque nuestro Chart (de wordpress) no depende de ningún otro. Vamos a ir viendo cada uno al tiempo que vemos como lo hemos adaptado a nuestros yaml para wordpress

Nuestro Chart de WordPress

Chart.yaml

name: wp-helm
description: An example of Helm and WordPress
version: 0.1.0
keywords:
  - http
  - wordpress
  - www
  - web
  - cms
home: "https://www.wordpress.org"
sources:
  - "https://github.com/nordri/helm-wordpress"
maintainers:
  - name: nordri
    email: nordri@gmail.com
engine: gotpl

No hay mucho que contar, está bastante claro. La opción engine hace referencia al parseador que usaremos para las plantillas, la opción por defecto si no se indica es gotpl que es el que usa Golang, como Jinja2 para Python.

LICENSE

Es el texto de la Licencia Apache 2

README.md

La información relativa a nuestro Chart, como funciona y demás

Un ejemplo de WordPress con Helm y Kubernetes.........

values.yaml

Este fichero contiene los datos de nuestra instalación. Como por ejemplo el nombre del sitio. Puertos a usar, usuarios y contraseñas.

image: wordpress
db_server: 192.168.1.43
root_db_password: root01
nfs_path: srvnfs
nfs_server: 192.168.1.43
pv_size: 20Gi
pvc_size: 2Gi
hostname: wordpress
resources:
  requests:
    memory: 128Mi
    cpu: 500m

Para este ejemplo hemos introducido dos nuevos parámetros que van a controlar el uso de los recursos por parte de nuestra aplicación. La memoria que hemos establecido a 128Mi y la cpu a 500m (500 milicores)

charts

Si usáramos dependencias de otros charts los incluiríamos en este directorio. Seguiría la misma estructura de directorios del padre, por ejemplo si nuestro chart depende del chart Apache y MySQL sería:

wordpress:
  Chart.yaml
  requirements.yaml
  # ...
  charts/
    apache/
      Chart.yaml
      # ...
    mysql/
      Chart.yaml
      # ...

templates

Aquí es donde vamos a definir cada uno de los componentes que conforman nuestra aplicación. Cada fichero yaml que utilizamos en la entrada anterior aparece aquí parametrizado para que coja los valores del fichero de values.yaml. Por no alargar mucho la entrada vamos a ver los detalles más significativos:

Hay dos ficheros clave que son _helpers.tpl y NOTES.txt. El primero es donde definimos funciones que serán ejecutadas por el parseador y el segundo es el mensaje que presentaremos cuando la aplicación se instale, como la URL de acceso.

Una vez tenemos las funciones definidas podemos hacer uso de ellas dentro de nuestras plantillas. En nuestro caso usamos la función template con el parámetro “fullname” para obtener el nombre de la instalación. Esto es porque, al igual que con Docker, cada instalación recibe un nombre aleatorio dentro del clúster, así podemos definir por ejemplo para nuestro servicio la siguiente línea:

name: {{template "fullname" .}}-svc

Ese será el nombre del servicio y lo usaremos para relacionar cada componente de la aplicación dentro del cluster.

Lo siguiente será sacar valores del fichero values.yaml y usaríamos la siguiente sintaxis:

# Obtener el nombre del host que le pasaremos a ingress
- host: {{ .Values.hostname }}

También podemos tener en la plantilla un nombre por defecto en caso que no se rellene ese valor en values.yaml

- host: {{ default "localhost" .Values.hostname }}

Los valores pueden hacer uso de pipelines al estilo Bash por ejemplo para cifrar una password y entrecomillarla. Lo vemos en el fichero de mysql-secrets.yaml.

mysql-root-password: {{.Values.root_db_password | b64enc | quote}}

Vemos que la contraseña está en plano en el fichero de values, la tomamos, la pasamos a base64 y la ponemos entre comillas.

Si queremos incluir trozos de values sin tener que referenciarlos uno a uno podemos hacer uso de toYaml que es una función para llamar a plantillas, tenemos que definir una plantilla dentro de values, nosotros hemos llamado resources y luego pasarla por la función de indentado, así si queremos incluir los resources que puede hacer uso el pod hacemos así:

{{ toYaml .Values.resources | indent 10 }}

Y tomará de values el trozo de plantilla que corresponda con resources y lo indentará 10 posiciones.

También se usa una lista de variables ya definidas que podemos consultar aquí

Instalando el paquete

Una vez tenemos todo instalamos el paquete. Si vemos la ayuda de Helm tenemos varias formas de instalar el paquete:

[...]
1. By chart reference: helm install stable/mariadb
2. By path to a packaged chart: helm install ./nginx-1.2.3.tgz
3. By path to an unpacked chart directory: helm install ./nginx
4. By absolute URL: helm install https://example.com/charts/nginx-1.2.3.tgz
[...]

Si lo instalamos usando la opción 3 veremos algo parecido a esto:

NAME:   winsome-tortoise
LAST DEPLOYED: Thu Dec 29 12:19:26 2016
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/Secret
NAME                                TYPE      DATA      AGE
winsome-tortoise-wordpress-secret   Opaque    1         1s

==> v1/PersistentVolume
NAME                            CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM                                    REASON    AGE
winsome-tortoise-wordpress-pv   20Gi       RWX           Retain          Bound     default/winsome-tortoise-wordpress-pvc             1s

==> v1/Service
NAME                             CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
winsome-tortoise-wordpress-svc   10.111.214.123          80:32590/TCP   1s

==> extensions/Deployment
NAME                         DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
winsome-tortoise-wordpress   1         1         1            0           1s

==> extensions/Ingress
NAME                             HOSTS       ADDRESS   PORTS     AGE
winsome-tortoise-wordpress-ing   wordpress             80        1s

==> v1/PersistentVolumeClaim
NAME                             STATUS    VOLUME                          CAPACITY   ACCESSMODES   AGE
winsome-tortoise-wordpress-pvc   Bound     winsome-tortoise-wordpress-pv   20Gi       RWX           1s


NOTES:
Los datos de acceso son los siguientes:

Host: wordpress

Donde vemos un resumen de los componentes que han sido lanzados y la información que nos da el fichero NOTES.txt. Vemos las aplicaciones instaladas a través de helm, asi

$ helm list
NAME               REVISION    UPDATED                     STATUS      CHART        
winsome-tortoise    1           Thu Dec 29 12:19:26 2016    DEPLOYED    wp-helm-0.1.0

Si quisiéramos eliminar la aplicación hariamos:

$ helm delete winsome-tortoise

Repositorio

Aquí el repositorio en GitHub: aquí

Posted in Kubernetes | Tagged , , | Comments Off on Empaquetando Aplicaciones para Kubernetes

WordPress Sobre Kubernetes

En la siguiente entrada vamos a explicar como podemos desplegar una legacy application, como WordPress, en un entorno de cloud como es Kubernetes.

Kubernetes se basan en una unidad mínima funcional llamada Pod, un Pod es uno o más contenedores, los contenedores pueden ser Docker o más recientemente, LXC. ¿Qué ocurre? que los contenedores son volátiles. Es decir, están condenados a desaparecer y volver a recrearse para siempre. Así, ¿qué pasa con la información que se va creando dentro de los Pods? Desaparece. Para evitar esta situación introducimos el concepto de persistencia. Básicamente consiste en configurarar el contenedor para que la información sensible que se va generando se guarde en un sitio inmune a la muerte y renacimiento de los contenedores. Para el caso que vamos a explicar aquí trataremos el sistema de ficheros y la base de datos. Por un lado, necesitamos que el WordPress siempre tenga acceso a su código PHP (aunque lo ideal sería conservar simplemente el wp-content) y por otro la base de datos, que es donde se guarda las entradas y demás.

La base de datos

Vamos a crear una máquina que funcionara como servidor de base de datos de MariaDB, configuramos para que acepte conexiones remotas.

Volvemos a nuestro master de Kubernetes y vamos a crear un secret

Un secret es un trozo de configuración para nuestras aplicaciones que introducimos en el fichero YAML. De está forma podemos pasar credenciales e información sensible en nuestro clúster sin que esté expuesta. En nuestro ejemplo vamos a crear un secret para pasar las credenciales de nuestra base de datos.

Primero codificamos la contraseña en base64, para este ejemplo, la contraseña de root es root01

$ echo -n root01 | base64
cm9vdDAx

Y escribimos el contenido el fichero de secretes

apiVersion: v1  
kind: Secret  
metadata:  
  name: mysql-secrets
type: Opaque  
data:  
  mysql-root-password: cm9vdDAx 

Añadimos esta configuración a Kubernetes

$ kubectl create -f mysql-secrets.yaml
secret "mysql-secrets" created

Vemos los secrets haciendo:

kubectl get secrets
NAME                  TYPE                                  DATA      AGE
default-token-thlxa   kubernetes.io/service-account-token   3         1d
mysql-secrets         Opaque                                3         23s

Ahora, cada vez que necesitemos de una aplicación que haga uso de la base de datos, podemos pasar las credenciales de root utilizando este endpoint.

Persistencia de Sistema de Ficheros

Para este ejemplo hemos montado un servidor NFS que ofrecemos a la red de los nodos de Kubernetes, así en nuestro fichero /etc/exports tendremos algo así:

/srvnfs    192.168.1.0/24(rw)

Ahora tenemos que presentar este volumen a Kubernetes, para ello hacemos uso de dos nuevos recursos, Persistent Volume y Persistent Volume Claim. La definición de la documentación de Kubernetes es muy buena, así que aquí lo explicaremos rápidamente:

  • Persistent Volume Es el pastel que entregamos al clúster, entero, sin partir, en bruto, tal cual.
  • Persistent Volume Claim Es cada porción de pastel que vamos a partir con cada pod que necesite persistencia.

Para crear estos recursos tenemos los siguientes YAML.

 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-wp-content
spec:
  capacity:
    storage: 20Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  nfs:
    path: /srvnfs
    server: 192.168.1.43

Y para el claim

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: pvc-wp-content
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 2Gi

Nota: Hay que instalar nfs-client en todos los nodos.

Se pueden usar otros drivers para persistencia como Ceph o Gluster, el procedimiento es el mismo.

Los creamos:

$ kubectl create -f pv-persistent.yaml
persistentvolume "pv-wp-content" created
$ kubectl create -f pvc-persistent.yaml
persistentvolumeclaim "pvc-wp-content" created

Lo verificamos:

$ kubectl get pv,pvc
NAME               CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM                    REASON    AGE
pv/pv-wp-content   20Gi       RWX           Retain          Bound     default/pvc-wp-content             3m
NAME                 STATUS    VOLUME          CAPACITY   ACCESSMODES   AGE
pvc/pvc-wp-content   Bound     pv-wp-content   20Gi       RWX           2m

Crear un deployment para WordPress

Ahora que está todo listo para que nuestro WordPress pueda desplegarse vamos a escribir el YAML para hacer uso de estos recursos. Partimos del ejemplo que nos da el propio Kubernetes aquí pero adaptado al clúster que nos hemos construido:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: wordpress-deployment
  labels:
    app: wordpress
spec:
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: frontend
    spec:
      containers:
      - image: wordpress
        name: wordpress
        env:
          - name: WORDPRESS_DB_PASSWORD
            valueFrom:
              secretKeyRef:
                name: mysql-secrets
                key: mysql-root-password
          - name: WORDPRESS_DB_HOST
            value: 192.168.1.44
        ports:
        - containerPort: 80
          name: wordpress
        volumeMounts:
        - name: wordpress-persistent-storage
          mountPath: /var/www/html
      volumes:
        - name: wordpress-persistent-storage
          persistentVolumeClaim:
            claimName: pvc-wp-content

Vemos como le pasamos la contraseña de root en una variable de entorno, pero se carga a través de un secret, de forma que no está expuesta a simple vista. También le indicamos donde va a montar /var/www/html para escribir el código PHP de WordPress.

Ahora simplemente exponemos el servicio, recordando usar NodePort.

apiVersion: v1
kind: Service
metadata:
  name: wordpress-svc
  labels:
    app: wordpress
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: wordpress
    tier: frontend
  type: NodePort

Y construimos el ingress.

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
 name: wordpress-ingress
spec:
 rules:
   - host: wordpress
     http:
       paths:
         - path: /
           backend:
             serviceName: wordpress-svc
             servicePort: 80

Podemos comprobar que funciona:

$ curl -I --resolve wordpress:80:192.168.1.41 http://wordpress
HTTP/1.1 302 Found
Cache-Control: no-cache, must-revalidate, max-age=0
Content-Type: text/html; charset=UTF-8
Date: Sat, 10 Dec 2016 11:43:28 GMT
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Location: http://wordpress/wp-admin/install.php
Server: Apache/2.4.10 (Debian)
X-Powered-By: PHP/5.6.28

Conclusiones

  1. Hemos montado una legacy app sobre kuberntes teniendo en cuenta la persistencia de la información.
  2. Hay que tener en cuenta que este ejemplo coloca todos los ficheros de wordpress en el directorio compartido, por lo que si desplegamos otro sobrescribe lo anterior, si queremos tener varios WP corriendo en este entorno tendremos que configurara para que cada uno utilice una carpeta.
  3. También como dije al principio lo ideal es poner en persistencia el directorio de wp-contents que es el que conserva los datos. Esto implica cambiar el Docker y complica el ejemplo.
  4. El traefik a veces se queda pillado, de momento lo que hago es destruir el controlador para que el deplyment lo cree de nuevo.
  5. Si el traefik cambia de nodo hay que cambiar los dns para que apunten a la IP nueva. Puedes usar tier para crear afinidad con el nodo, de forma que traefik siempre funcione en el mismo nodo.
  6. También puedes tener un traefik por nodo para hacer balanceo o HA.
Posted in Kubernetes, WordPress | Tagged , , , | Comments Off on WordPress Sobre Kubernetes

Kubernetes sobre 3 nodos con Traefik Ingress Controller

En la siguiente entrada vamos a configurar 3 máquinas Ubuntu Xenial para correr Kuberntes 1.4 y configuraremos el Traefik Ingress Controller para tener acceso a las aplicaciones.

Configuración inicial

Como decimos necesitamos tres máquinas para construir el clúster, una como master y las otras dos como workers. Las direcciones IP que usaré serán:

192.168.1.40    master
192.168.1.41    minion1
192.168.1.42    minion2

Esta configuración es importante ya que los certificados y las entradas DNS se generaran a partir de estos.

Hay que instalar estos paquetes en todas las máquinas del clúster

Instalamos la llave pública para el repositorio de Kubernetes

curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -

Y añadimos el repositorio a nuestros sources

echo deb http://apt.kubernetes.io/ kubernetes-xenial main | tee /etc/apt/sources.list.d/kubernetes.list

Actualizamos

apt-get update

Instalamos los paquetes necesarios

apt-get install -y kubelet kubeadm kubectl kubernetes-cni docker.io

En este punto ya estamos listo para desplegar Kubernetes

Desplegar Kubernetes

Desde la máquina master ejecutamos el siguiente comando:

kubeadm init --pod-network-cidr=172.30.0.0/16

Este comando inicializará el master con todos los contenedores necesarios para que Kubernetes funcione. Le estamos pasando el parámetro pod-network-cidr con la red que usaran los contenedores. Esto es porque voy a usar Flannel como networking overlay. Todos los contenedores que se desplieguen bajo este Kubernetes tendrán una IP de este rango. Este parámetro sólo es necesario para la red de Flannel, si queremos usar Calico, Weave, u otra no es necesario. Yo uso Flannel porque me ha dado buenos resultados y encuentro que es fácil de configurar.

Debemos ver la siguiente salida al terminar de crear contenedores:

Running pre-flight checks
 generated token: "cfbebe.3e2eab74675b5426"
 generated Certificate Authority key and certificate:
Issuer: CN=kubernetes | Subject: CN=kubernetes | CA: true
Not before: 2016-12-07 15:46:29 +0000 UTC Not After: 2026-12-05 15:46:29 +0000 UTC
Public: /etc/kubernetes/pki/ca-pub.pem
Private: /etc/kubernetes/pki/ca-key.pem
Cert: /etc/kubernetes/pki/ca.pem
 generated API Server key and certificate:
Issuer: CN=kubernetes | Subject: CN=kube-apiserver | CA: false
Not before: 2016-12-07 15:46:29 +0000 UTC Not After: 2017-12-07 15:46:29 +0000 UTC
Alternate Names: [192.168.1.40 10.96.0.1 kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local]
Public: /etc/kubernetes/pki/apiserver-pub.pem
Private: /etc/kubernetes/pki/apiserver-key.pem
Cert: /etc/kubernetes/pki/apiserver.pem
 generated Service Account Signing keys:
Public: /etc/kubernetes/pki/sa-pub.pem
Private: /etc/kubernetes/pki/sa-key.pem
 created keys and certificates in "/etc/kubernetes/pki"
 created "/etc/kubernetes/kubelet.conf"
 created "/etc/kubernetes/admin.conf"
 created API client configuration
 created API client, waiting for the control plane to become ready
 all control plane components are healthy after 36.174970 seconds
 waiting for at least one node to register and become ready
 first node is ready after 4.009025 seconds
 attempting a test deployment
 test deployment succeeded
 created essential addon: kube-discovery, waiting for it to become ready
 kube-discovery is ready after 19.002880 seconds
 created essential addon: kube-proxy
 created essential addon: kube-dns

Kubernetes master initialised successfully!

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

kubeadm join --token=cfbebe.3e2eab74675b5426 192.168.1.40

La última línea muestra el token que usaremos para unir nuevos workers al clúster, hay que guardarla en un lugar seguro.

Si nos preguntamos qué contenedores ha creado podemos verlos así:

$ kubectl get pods --namespace=kube-system
NAME                              READY     STATUS              RESTARTS   AGE
dummy-2088944543-ojfjy            1/1       Running             0          5m
etcd-master                       1/1       Running             0          5m
kube-apiserver-master             1/1       Running             0          6m
kube-controller-manager-master    1/1       Running             0          6m
kube-discovery-1150918428-78wag   1/1       Running             0          5m
kube-dns-654381707-v4gsp          0/3       ContainerCreating   0          5m
kube-proxy-2ebl5                  1/1       Running             0          5m
kube-scheduler-master             1/1       Running             0          5m

Construir el Networking Overlay

Antes de unir los nodos al cluster debemos configurar la red. Como hemos dicho vamos a usar Flannel, así nos descargamos el fichero YAML para configurarlo:

curl -o kube-flannel.yml https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

Solo tenemos que modificar la línea

"Network": "10.244.0.0/16",

por

"Network": "172.30.0.0/16",

Y añadirlo a la configuración

kubectl apply -f kube-flannel.yml

Si ahora preguntamos por los contenedores, veremos que aparece uno nuevo que es el de Flannel, también observamos que el contenedor de DNS termina de crearse. Es necesaria la red para que funcione el DNS.

Añadiendo nuevos nuevos nodos al clúster

Ahora es el momento de usar el comando que guardamos cuando ejecutamos kubeadm init en cada uno de los workers que queramos usar en el clúster. En cada nodo:

kubeadm join --token=cfbebe.3e2eab74675b5426 192.168.1.40

Cuando haya terminado veremos los siguientes contenedores:

NAMESPACE     NAME                              READY     STATUS    RESTARTS   AGE       IP             NODE
kube-system   dummy-2088944543-ojfjy            1/1       Running   0          21m       192.168.1.40   master
kube-system   etcd-master                       1/1       Running   0          20m       192.168.1.40   master
kube-system   kube-apiserver-master             1/1       Running   0          21m       192.168.1.40   master
kube-system   kube-controller-manager-master    1/1       Running   0          21m       192.168.1.40   master
kube-system   kube-discovery-1150918428-78wag   1/1       Running   0          21m       192.168.1.40   master
kube-system   kube-dns-654381707-v4gsp          3/3       Running   0          20m       172.30.0.2     master
kube-system   kube-flannel-ds-4iuct             2/2       Running   0          5m        192.168.1.40   master
kube-system   kube-flannel-ds-hy10z             2/2       Running   1          1m        192.168.1.42   minion2
kube-system   kube-flannel-ds-lf7a8             2/2       Running   1          1m        192.168.1.41   minion1
kube-system   kube-proxy-2ebl5                  1/1       Running   0          20m       192.168.1.40   master
kube-system   kube-proxy-labsi                  1/1       Running   0          1m        192.168.1.41   minion1
kube-system   kube-proxy-lf770                  1/1       Running   0          1m        192.168.1.42   minion2
kube-system   kube-scheduler-master             1/1       Running   0          20m       192.168.1.40   master

Observar que existen pods (el de Flannel y el Kube-Proxy) por nodo, por cada nodo que se añade al clúster tendremos un pod que gobernara ese trabajo en ese nodo.

Llegados a este punto, ya tenemos un clúster de Kubernetes funcionando, pero nos interesa poder ofrecer servicios al exterior, así que ahora vamos a montar un Ingress Controller.

Accediendo a los servicios de Kubernetes: Ingress Controller

Para esta tarea necesitamos un pod especial, en realidad no es más que un proxy inverso que conectará el mundo exterior con lo que expongamos mediante los servicios. Existe una limitación conocida y hay abierto un issue en el GitHub de Kubernetes por la falta de transparencia y documentación para construir el Ingress Controller sobre Bare Metal. Aun así es posible usar un workaround para hacer que funcione. El truco consiste en usar NodePort para exponer el servicio de forma que Ingress conecte a este puerto en el nodo al no funcionar bien con ClusterIP o LoadBalancer. Esto no sucede si usamos un proveedor de cloud como AWS o GCE.

Lo primero que hacemos es descargar el YAML con la configuración de Traefik

curl -o traefik.yaml https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik.yaml

Y aplicamos

kubectl apply -f traefik.yaml

Y ya está. Podemos verlo en la lista de pods:

kube-system   traefik-ingress-controller-2249976834-saoj1   1/1       Running   0          15s       192.168.1.41   minion1

Es súper sencillo. Como vemos se ha construido en el minion1, ahora debemos dirigir el tráfico HTTP a este worker al puerto 80 que es donde está escuchando Traefik, cada petición que reciba la enviará al servicio adecuado.

Vamos a probar que funciona lanzando un pod con NGiNX

Construimos el deployment de Nginx.

kubectl run nginx --image=nginx --port=80
NAMESPACE     NAME                                          READY     STATUS    RESTARTS   AGE       IP             NODE
default       nginx-3449338310-xg88a                        1/1       Running   0          30s       172.30.2.2     minion2

Ahora exponemos el servicio

kubectl expose deployment nginx --port=80 --target-port=80 --type=NodePort
NAME         CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
nginx        10.110.8.199          80/TCP    15s

Y creamos el ingress con el siguiente contenido:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx-ingress
spec:
  rules:
    - host: nginx
      http:
        paths:
          - path: /
            backend:
              serviceName: nginx
              servicePort: 80
$ kubectl create -f nginx-ingress.yaml

Comprobamos que funciona:

$ curl -L --resolve nginx:80:192.168.1.41 http://nginx
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Y listo, en la siguiente entrada veremos como construir una aplicación algo más compleja utilizando las herramientas que nos proporciona Kubernetes.

Referencias

  • http://kubernetes.io/docs/getting-started-guides/kubeadm/
  • https://docs.traefik.io/user-guide/kubernetes/
  • https://medium.com/@rothgar/exposing-services-using-ingress-with-on-prem-kubernetes-clusters-f413d87b6d34#.o7o65fwhf
  • https://github.com/kubernetes/ingress/issues/17
Posted in Kubernetes | Tagged , , | Comments Off on Kubernetes sobre 3 nodos con Traefik Ingress Controller

Figlet as a Service

Todo comenzó cuando quise poner un ASCII Art a las máquinas para el fichero /etc/motd. Para hacerlo quería usar un playbook de Ansible que me permitiera generar el código ASCII utilizando como entrada en nombre del host.

Me puse a investigar y a sopesar las opciones:

  1. Podía instalar figlet en cada máquina y generar el ASCII Art.
  2. Podía usar alguna API que proporcionara algún método para obtener los ASCII Art en remoto

La primera opción no me convence porque tengo que instalar un paquete que solo se va a usar una vez y la segunda menos porque las opciones que encontré eran de pago y algunas máquinas no tienen salida a Internet.

Así pensé utilizar una combinación de ambas y crearme mi propio servicio de figlet. No ha sido demasiado complicado crearme un par de contenedores docker que para ofrecer el servicio a la infraestructura.

Dockerizando Figlet

Lo primero que hemos hecho ha sido buscar en Docker Hub el contenedor para PHP que ejecute FPM. Delante de este contenedor colocamos otro contenedor NGINX que actúa como frontend de la aplicación.

Esto no es complicado, porque ya está hecho, el hub oficial de PHP tiene un tag para FPM. A este contenedor hay que instalarle el paquete de figlet. Con apt se hace. Se escribe este cambio en el Dockerfile y se construye la imagen.

El contenedor de NGINX hay que configurarlo para que haga de proxy al php-fpm

Escribimos el código para leer la URL de la que sacaremos el nombre del host. Tenemos en cuenta si viene vacía, o si es mayor a 32 caracteres, ya que la RFC considera que el tamaño máximo de nombre de host es de 32 caracteres. También para este caso se pasa un ancho de columna a figlet de 160 caracteres, para que no salga cortado, en cualquier caso es raro que un nombre de host sobrepase los 15 caracteres, al menos en las infraestructuras que yo administro.

Construida la imagen, se escribe el docker-compose.yaml para vincular el nginx con el php-fpm. Se establece la política de restart, se exponen los puertos y se habilitan los volumenes.

Publicado

Está publicado aquí

Fuentes

Y las fuentes en mi github por si alguien quiere investigar o montarlo en su infraestructura.

Posted in Docker, Linux, Linux Script, PHP | Tagged , , | 1 Comment