Introducción a Kubernetes (I): Clusterizando enmilocalfunciona.io

Publicado por Manuel Valle el

DockerKubernetesInfraestructuraGetting Started

Uno de los conceptos o tecnologías de moda y con más éxito alrededor de dockers/contenedores es sin duda Kubernetes. Kubernetes es una tecnología creada por Google y sirve para la gestión y orquestación de contenedores Docker. Google utiliza Dockers para muchos de sus servicios como Gmail o Maps.

Lo que te permite Kubernetes es que te olvides de la infraestructura y pienses tan solo en aplicaciones y en cómo empaquetarlas. Basado en este principio todos los conceptos que usa están relacionados con este propósito.

Los conceptos clave de Kubernetes son:

  • Pods: Conjunto de contenedores y volúmenes.
  • Replication Controllers: Gestor de Pods que asegura que están levantadas las réplicas y permite escalar de forma fácil. Una réplica es una copia exacta de un Pod. Levanta Pods en caso de fallos o reinicios.
  • Service: Define como acceder a un grupo de Pods.

Para nuestro artículo vamos a montar un cluster de Kubernetes sobre Vagrant y VirtualBox con 2 nodos, y vamos a instalar un blog basado en Ghost.

Cómo empezar

La forma mas fácil de comenzar es montándote un entorno en local utilizando VirtualBox y Vagrant. Las instrucciones están basadas en Ubuntu pero será sencillo replicarlo para cualquier otra distribución.

Paso 1 - Instalación de requerimientos previos

Como ya hemos comentado necesitamos:

  • Virtualbox, que es un paquete de virtualización desarrollado por Oracle;
  • Vagrant que es una herramienta que facilita la creación de entornos virtuales portables, reproducibles y ligeros.

El tooling que nos da Kubernetes está preparado para trabajar con estos dos paquetes. Para instalarlo hay que ejecutar el siguiente comando :

sudo apt-get install vagrant virtualbox wget  

También podremos necesitar algún paquete más si no los tememos ya instalados:

sudo apt-get install wget tar  

Una vez instalados todos los requerimientos podemos continuar con la instalación de Kubernetes en nuestra máquina.

Paso 2 - Descargar y Arrancar un entorno Kubernetes

Hay dos formas de hacerlo:

  1. Descargando la release del repositorio de Github y ejecutándolo manualmente (más control).
  2. Ejecutando un script que descarga la ultima versión.

Ambos dan el mismo resultado. Si queremos probar una versión en concreto iremos al primer método si solo queremos probar la ultima pues iremos por el segundo. Para nuestro entorno de ejemplo vamos a instalar, ademas del master, 2 nodos adicionales. Por ello deberemos exportar la variable NUM_NODES con valor '2'. La variable KUBERNETES_PROVIDER tambien la definiremos con valor 'vagrant' para que los scripts, que también valen para otros tipos de virtualización e IaaS, sepan que nuestro entorno será sobre vagrant.

Método 1 - Desde releases de Github

Descargamos y descomprimimos la versión que queramos, por ejemplo, de la siguiente manera:

wget https://github.com/kubernetes/kubernetes/releases/download/v1.3.0/kubernetes.tar.gz  

(La versión 1.3.0 ocupa ~1.4GB)

  • Descomprimimos:
tar xvfz kubernetes.tar.gz  

Este último comando nos creará una carpeta llamada kubernetes con todo lo necesario. No situamos en la carpeta kubernetes y ejecutamos:

export KUBERNETES_PROVIDER=vagrant  
export NUM_NODES=2  
./cluster/kube-up.sh

Este comando tarda un poco, porque provisiona las máquinas con Vagrant en VirtualBox y tiene que descargar las imágenes, y configurar el entorno.

Método 2 - Con scripts automáticos de Kubernetes

  • Simplemente abrimos un terminal y ejecutamos los siguientes comandos.
export KUBERNETES_PROVIDER=vagrant  
export NUM_NODES=2  
wget -q -O - https://get.k8s.io | bash  

En la url https://get.k8s.io está el script que busca la última versión, la descarga y ejecuta la instalación del entorno.

Paso 3 - Comprobando que el cluster está correctamente levantado

Si habéis llegado a este punto seguramente habéis tenido que esperar un rato un poco largo :) . Para empezar a interactuar con Kubernetes el comando básico es kubectl. En esta versión lo encontrareis en la ruta ./cluster/kubectl.sh.

Para comprobar que nuestro cluster esta correctamente levantado podéis ejecutar los siguientes comandos:

  • Comprobar que los nodos están Ready:
$ ./cluster/kubectl.sh get nodes
NAME                STATUS    AGE  
kubernetes-node-1   Ready     1h  
kubernetes-node-2   Ready     1h  
  • Comprobar que los Pods de sistema (namespace kube-system) están ok:
$ ./cluster/kubectl.sh get --namespace=kube-system pods
NAME                                   READY     STATUS    RESTARTS   AGE  
heapster-v1.1.0-2101778418-dsjz0       4/4       Running   0          19m  
kube-dns-v17-jz2t4                     3/3       Running   0          1h  
kube-proxy-kubernetes-node-1           1/1       Running   0          1h  
kube-proxy-kubernetes-node-2           1/1       Running   0          18m  
kubernetes-dashboard-v1.1.0-buseh      1/1       Running   0          1h  
monitoring-influxdb-grafana-v3-a21ln   2/2       Running   0          1h  
  • Ver los logs de todos los namespaces en tiempo real:
$ ./cluster/kubectl.sh  get --all-namespaces events -w
NAMESPACE     LASTSEEN                    FIRSTSEEN                   COUNT     NAME                               KIND         SUBOBJECT   TYPE      REASON                   SOURCE                     MESSAGE

kube-system   2016-07-07T10:25:22+02:00   2016-07-07T10:25:22+02:00   1         heapster-v1.1.0-2101778418-dsjz0   Pod       spec.containers{heapster-nanny}   Normal    Created                  {kubelet kubernetes-node-2}   Created container with docker id 194f461e3f9f  
kube-system   2016-07-07T10:25:24+02:00   2016-07-07T10:25:24+02:00   1         heapster-v1.1.0-2101778418-dsjz0   Pod       spec.containers{heapster-nanny}   Normal    Started                  {kubelet kubernetes-node-2}   Started container with docker id 194f461e3f9f  
kube-system   2016-07-07T10:25:24+02:00   2016-07-07T10:25:24+02:00   1         heapster-v1.1.0-2101778418-dsjz0   Pod       spec.containers{eventer-nanny}    Normal    Created                  {kubelet kubernetes-node-2}   Created container with docker id fc3f52da9e6f  
kube-system   2016-07-07T10:25:24+02:00   2016-07-07T10:25:22+02:00   2         heapster-v1.1.0-2101778418-dsjz0   Pod       spec.containers{heapster-nanny}   Normal    Pulled                   {kubelet kubernetes-node-2}   Container image "gcr.io/google_containers/addon-resizer:1.3" already present on machine  
kube-system   2016-07-07T10:25:26+02:00   2016-07-07T10:25:26+02:00   1         heapster-v1.1.0-2101778418-dsjz0   Pod       spec.containers{eventer-nanny}    Normal    Started                  {kubelet kubernetes-node-2}   Started container with docker id fc3f52da9e6f  
..........
  • Ver información del cluster:
$ kubectl cluster-info
Kubernetes master is running at https://10.245.1.2  
Heapster is running at https://10.245.1.2/api/v1/proxy/namespaces/kube-system/services/heapster  
KubeDNS is running at https://10.245.1.2/api/v1/proxy/namespaces/kube-system/services/kube-dns  
kubernetes-dashboard is running at https://10.245.1.2/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard  
Grafana is running at https://10.245.1.2/api/v1/proxy/namespaces/kube-system/services/monitoring-grafana  
InfluxDB is running at https://10.245.1.2/api/v1/proxy/namespaces/kube-system/services/monitoring-influxdb

Para poder acceder a estas URLs hay que utilizar algún método de los explicados en la documentación. El método más sencillo es el de levantar un 'proxy' con el comando:

$ ./cluster/kubectl.sh proxy --port=8080
Starting to serve on 127.0.0.1:8080

De este modo, accediendo a localhost:8080 en nuestro navegador, podemos acceder a las URLs importantes de nuestro cluster, como el Dashboard:

http://localhost:8080/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard

Con este proxy podremos luego acceder también a nuestros servicios.

Otra de las URLs importantes es la de Cockpit (un dashboard para gestionar clusters de Kubernetes u Openshift). Para acceder podemos hacerlo en la URL https://10.245.1.2:9090 (usuario vagrant, password vagrant)

Para más información del comando kubectl podéis ir al link

Paso 4 - Lanzar nuestra primera aplicación sobre Kubernetes (enmilocalfunciona.io)

Nuestro blog enmilocalfunciona.io esta basado en el gestor de blog  Ghost. Nuestro ejemplo se va a basar en este software desarrollado en Node.js.

Para montar nuestra app, vamos a montar un Replication Controller para poder poner más instancias balanceadas. Un Replication Controller es un mecanismo de Kubernetes que asegura que un Pod tiene levantado un número determinado de replicas. Si necesitamos más replicas el Replication Controller levanta más réplicas, si necesitamos menos, las mata, si alguna de ellas falla y muere entonces levanta nuevas réplicas para mantener el número definido. Es conveniente que incluso para pequeñas aplicaciones con un solo Pod lo definamos con un replication controller ya que en caso de fallo nos asegura que nuestra app se levanta automáticamente.

La imagen docker de ghost utiliza por defecto almacenamiento local pero como queremos que todas las instancias sirvan el mismo contenido vamos a definir en Kubernetes un volumen. El volumen no va a ser mas que el uso de un NFS que tendremos que haber montado al efecto en la propia máquina.

¡Empecemos!

Creación de Persistence Volumen

Este es el YAML que tenemos que crear para poder crear en Kubernetes un Persistent Volume y un Persistent Volume Claim para poder usarlo en nuestros pods:

volume.yml:

apiVersion: v1  
kind: PersistentVolume  
metadata:  
  name: nfs-cluster
spec:  
  capacity:
    storage: 200Mi
  accessModes:
    - ReadWriteMany
  nfs:
    server: 10.245.1.1
    path: "/var/nfsshare/ghost"
  persistentVolumeReclaimPolicy: Retain

---
kind: PersistentVolumeClaim  
apiVersion: v1  
metadata:  
  name: nfs-cluster
spec:  
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 100Mi

Ahora tendremos que crearlo en nuestro cluster:

$ ./cluster/kubectl.sh create -f volume.yaml 
persistentvolume "nfs-cluster" created  
persistentvolumeclaim "nfs-cluster" created

$ ./cluster/kubectl.sh get persistentvolume
NAME          CAPACITY   ACCESSMODES   STATUS    CLAIM                 REASON    AGE  
nfs-cluster   200Mi      RWX           Bound     default/nfs-cluster             39s

$ ./cluster/kubectl.sh get persistentvolumeclaim
NAME          STATUS    VOLUME        CAPACITY   ACCESSMODES   AGE  
nfs-cluster   Bound     nfs-cluster   0                        43s

Vemos como ya esta creado y listo para usar

Creación del Replication Controller

Ahora vamos a crear nuestra aplicación definiendo como se puede ver el número de replicas y el uso de nuestro volumen compartido.

ghost.yml:

apiVersion: v1  
kind: ReplicationController  
metadata:  
  name: ghost
  labels:
    purpose: enmilocalfunciona.io
spec:  
  replicas: 1
  template:
    metadata:
      labels:
        app: ghost
        purpose: enmilocalfunciona.io
    spec:
      containers:
      - name: ghost
        image: ghost:latest
        resources:
          limits:
            cpu: 200m
            memory: 50Mi
        env:
        - name: GET_HOSTS_FROM
          value: dns
        ports:
        - containerPort: 2368
        volumeMounts:
          # name must match the volume name below
          - name: ghost-data
            mountPath: "/var/lib/ghost"
      volumes:
        - name: ghost-data
          persistentVolumeClaim:
            claimName: nfs-cluster

Ahora lo creamos en nuestro cluster:

$ ./cluster/kubectl.sh create -fghost.yml
replicationcontroller "ghost" created

$ ./cluster/kubectl.sh get rc
NAME      DESIRED   CURRENT   AGE  
ghost     1         1         34s

$ ./cluster/kubectl.sh get pods -l app=ghost
NAME          READY     STATUS    RESTARTS   AGE  
ghost-7a6pz   1/1       Running   0          2m

Una vez creada nuestra aplicación podemos crear el servicio para poder acceder desde fuera:

Creación del Service

Definimos un Service de tipo LoadBalancer y le refenciamos a nuestra app (app: ghost). Aquí puedes ver los tipos de servicios que puedes publicar en Kubernetes:

service.yml:

apiVersion: v1  
kind: Service  
metadata:  
  labels:
    name: ghost-lb
    purpose: enmilocalfunciona.io
  name: ghost-lb
spec:  
  ports:
    - port: 2368
  selector:
    app: ghost
  type: LoadBalancer

Lo ejecutamos:

$ ./cluster/kubectl.sh create -f service.yml
service "ghost-lb" created

Pues ya tenemos lista nuestra aplicación. Si tenemos levantado el proxy que hicimos anteriormente podremos acceder a nuestra app con la URL:

http://localhost:8080/api/v1/proxy/namespaces/default/services/ghost-lb/

Vamos a jugar con las réplicas

Ahora vamos a ir aumentando nuestro número de réplicas con el siguiente comando:

$ cluster/kubectl.sh scale rc ghost --replicas=2
replicationcontroller "ghost" scaled

$ ./cluster/kubectl.sh get pods -l app=ghost
NAME          READY     STATUS              RESTARTS   AGE  
ghost-7a6pz   1/1       Running             0          8m  
ghost-fqx0c   0/1       ContainerCreating   0          5s

$ ./cluster/kubectl.sh get pods -l app=ghost
NAME          READY     STATUS        RESTARTS   AGE  
ghost-7a6pz   1/1       Running       0          9m  
ghost-fqx0c   1/1       Running       0          30s

Ahí tenemos nuestras 2 réplicas balanceadas. Podemos probar con ApacheBench a ver si mejoramos nuestra disponibilidad o no :)

$ ab -n 100 -c 10 http://localhost:8080/api/v1/proxy/namespaces/default/services/ghost-lb/
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>  
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/  
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient).....done


Server Software:  
Server Hostname:        localhost  
Server Port:            8080

Document Path:          /api/v1/proxy/namespaces/default/services/ghost-lb/  
Document Length:        4948 bytes

Concurrency Level:      10  
Time taken for tests:   21.311 seconds  
Complete requests:      100  
Failed requests:        3  
   (Connect: 0, Receive: 0, Length: 3, Exceptions: 0)
Total transferred:      515466 bytes  
HTML transferred:       493966 bytes  
Requests per second:    4.69 [#/sec] (mean)  
Time per request:       2131.100 [ms] (mean)  
Time per request:       213.110 [ms] (mean, across all concurrent requests)  
Transfer rate:          23.62 [Kbytes/sec] received

Y esta es una tabla con los resultados según he ido aumentando instancias:

Instancias Total Time Requests per second Time per request Transfer rate
1 47.357 2.11 4735.691 10.59
2 21.311 4.69 2131.100 23.62
4 12.201 8.20 1220.063 41.26

Pues hemos terminado. En siguientes artículos de esta serie iremos viendo más aspectos o funcionalidades de Kubernetes. ¡Síguenos en Twitter para estar al día de los siguientes artículos!

Autor

Manuel Valle

Cloud Architect en Red Hat.
Implantador y "contagiador" de filosofía DevOps y metodologías Ágiles. Especialista en IaaS y PaaS.
Twitter: @manuvaldi