Contenido

Kubernetes (1): conceptos básicos

Hoy en día toda web escalable es un sistema distribuido. Aunque se suele comenzar con un monolito, poco a poco se ve la necesidad de dividirlo en piezas más pequeñas o arquitecturas SOA o bien directamente a microservicios. En cualquier caso, los mecanismos de despliegue tradicionales dejan de ser eficaces. Y ahí surgen soluciones como Kubernetes y docker-swarm.

Kubernetes

Un poco de historia

Hoy en día es dificil disfrutar de un problema que otros no hayan tenido antes. Y así pasó en las grandes empresas como Google, Amazon, Facebook, …

Todos ellos tenían sus soluciones de despliegue, unas mejores y otras peores. Pero fue Google quien construyó Borg. Borg era el motor de despliegue de uso interno.

Poco a poco el equipo de trabajo dejó de ser el mismo: entra sangre nueva y se va gente a otras empresas. Esta gente llega a una empresa nueva y ve la necesidad de mejorar el sistema de despliegue y… ¿En qué fijarse? Pues en lo que conoce: Borg. Hasta que un grupo de estos ex-empleados decide comenzar un proyecto abierto y libre: nace kubernetes.

El nombre “kubernetes”

“Kubernetes” hace referencia al timonel o el patrón de un barco en Esperanto. De ahí el logo, que recuerda a un timón.

A menudo se abrevia como “K8s”, que es como decir: “una K, 8 letras más y una s”, pero nunca se pronuncia de esta manera. En inglés suena más bien como cubirnitis o quiubirnities.

Mucho más que un sistema de despliegue

Pero no es sólo un sistema de despliegue, como puede pensarse de Ansible, Puppet o Chef. Kubernetes incluye sistemas de monitorización, escalado y auto-corrección. Es decir: sus funciones se extienden más allá del despliegue hasta gestionar los recursos durante la ejecución.

Cómo funciona

Kubernetes es, en sí mismo, un sistema distribuido. Las aplicaciones se dividen en dos grupos: el plano de control y el de nodo.

Componentes de Kubernetes

En el plano de control podemos encontrar:

  • kube-apiserver, encargada de las comunicaciones. Todo pasa por ella, ya que gestiona los permisos, entre otras cosas.
  • kube-scheduler, o planificador, asegurándose de que los recursos se distribuyen de acuerdo a las especificaciones.
  • kube-control-manager, para tareas administrativas como gestión de nodos, de trabajos, endpoints, …
  • etcd, que es la base de datos donde se guarda todo.

A nivel de nodo podemos encontrar:

  • kubelet, que es un agente que debe ejecutarse en todos y cada uno de los nodos para permitir la gestión local de acuerdo a lo que diga el scheduler.
  • kube-proxy, que es otro agente que vive también en todos los nodos para realizar tareas de red.
  • container runtime, para ejecutar contenedores, como docker, containerd o CRI-O, o cualquier cosa que implemente la interfaz.

Luego también hay algunos addons muy comunes, como:

  • DNS, sin el que no se puede trabajar, ya que los Pods no se verían entre sí.
  • Web UI o dashboard, que permite la gestión desde el navegador.

Simplificando

Evidentemente, desplegar todo esto es un dolor. No ya por la cantidad de servicios y configuraciones, sino por la seguridad: cada servicio tiene sus claves privadas y debe compartir las públicas con aquellos que se comunican con ellos. Kelsey Hightower explica cómo hacerlo de forma manual en el Kubernetes the hard way, y casi todo el rato estás generando claves y compartiéndolas entre servicios.

Pero hay alternativas más sencillas:

  • kind, que te instala un cluster kubernetes en un docker. Es ligerito para ser un kubernetes y fácil de manejar. Es el que suelo utilizar yo. Puede consumir unos 500Mb de RAM.
  • minikube, que utiliza una máquina virtual tipo virtualbox. Require un consumo de recursos casi fijo con un mínimo de 2Gb de RAM.

Como digo, recomiendo kind, con lo que crear un cluster de kubernetes local puede ser tan sencillo como:

kind create cluster

Y en unos 20 segundos tendrás tu cluster listo para usar.

Herramientas básicas

La herramienta básica es kubectl, pronunciado quiubcontrol o quiubcitiel.

Necesita configuración en un archivo llamado kubeconfig, que habitualmente se encuentra en ~/.kube/config y que si hemos usado kind ya estará creado y listo para usar.

Recursos

Bueno… ahora sí… comencemos con kubernetes.

Antes hemos visto los servicios internos de Kubernetes, ahora veremos los recursos lógicos. Es decir: veremos cómo crear cosas dentro de kubernetes.

Todos los recursos se pueden mapear a formato YAML y viceversa. Eso ofrece un método muy conveniente de comunicarse con humanos :)

Hay muchos tipos de recursos, y Kubernetes admite la creación de más para uso interno:

Recursos de kubernetes

Aquí me voy a centrar solo en los más básicos.

POD

El Pod es fácil de identificar en el gráfico, ya que es donde van todas las flechitas :D. Eso es porque es la unidad mínima de proceso. Dentro tendrá uno o más contenedores, pero si Kubernetes necesita crearlo, destruirlo o reemplazarlo, no se puede cambiar sólo uno de esos contenedores, sino que habrá que gestioar el Pod completo.

Su uso es, justamente, albergar contenedores. Esto incluye puntos de montaje como Secrets o ConfigMaps que veremos un poco más adelante.

Ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
    - name: nginx
      image: nginx:1.14.2
      ports:
        - containerPort: 80

ReplicaSet

El ReplicaSet es poco útil en sí mismo ya que se suele usar en un nivel superior, los Deployments que vienen justo a continuación.

Se utilizan para gestionar el número de instancias de un Pod, comprobando que haya los justos y necesarios.

Deployment

El Deployment gestiona la creación de ReplicaSets, que a su vez gestionan la creación de Pods. La gracia es que utiliza los ReplicaSets como un historial de estados, permitiendo volver atrás a un “estado” anterior.

Nadie gestiona ReplicaSets a mano… siempre mediante Deployments.

Ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.7.9
          ports:
            - containerPort: 80

DaemonSet

El DaemonSet gestiona la creación de Pods, como los Deployments, pero asegurándose de que haya un Pod por nodo. Si se crea un nodo nuevo, se encarga de crear un Pod nuevo, y si se destruye un nodo, destruirá el Pod.

Son muy útiles para herramientas de monitorización.

Ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-elasticsearch
  namespace: kube-system
  labels:
    k8s-app: fluentd-logging
spec:
  selector:
    matchLabels:
      name: fluentd-elasticsearch
  template:
    metadata:
      labels:
        name: fluentd-elasticsearch
    spec:
      tolerations:
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      containers:
      - name: fluentd-elasticsearch
        image: gcr.io/fluentd-elasticsearch/fluentd:v2.5.1
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers

StatefulSet

Como un Deployment, pero garantizando orden y unicidad de los Pods. Útil para gestionar estados, como lo que necesitan las bases de datos.

Ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx # tiene que coincidir con .spec.template.metadata.labels
  serviceName: "nginx"
  replicas: 3 # por defecto es 1
  template:
    metadata:
      labels:
        app: nginx # tiene que coincidir con .spec.selector.matchLabels
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: k8s.gcr.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "my-storage-class"
      resources:
        requests:
          storage: 1Gi

Job

Un Job es un programa que realiza trabajo. Nace, hace cosas y muere. No se espera que viva eternamente.

Se puede gestionar el paralelismo de los Jobs para evitar solapamientos o gestionarlos

Ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4

Cronjob

Un CronJob crea Jobs de forma temporizada.

Ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
apiVersion: batch/v1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            imagePullPolicy: IfNotPresent
            command:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure

Service

Al contrario que todos los anteriores, un Service no gestiona Pods, sino que es más bien un proxy que permite acceder a ellos.

El funcionamiento es el siguiente: gestiona una lista de los Pods que tienen unas labels, ofreciendo un nombre fijo para acceder a ellos, de forma que no tengamos que referirnos a una instancia concreta (que tendrá un nombre aleatorio).

Dicho de otro modo: es como un DNS a nivel interno y de grupo de Pods.

Ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx

ConfigMap

Un ConfigMap tampoco gestiona Pods, sino que es un elemento de configuración. Permite guardar claves y valores y montarlos como ficheros o variables de entorno dentro de los Pods.

Ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: ConfigMap
metadata:
  name: game-demo
data:
  # property-like keys; each key maps to a simple value
  player_initial_lives: "3"
  ui_properties_file_name: "user-interface.properties"

  # file-like keys
  game.properties: |
    enemy.types=aliens,monsters
    player.maximum-lives=5    
  user-interface.properties: |
    color.good=purple
    color.bad=yellow
    allow.textmode=true    

Secrets

Como los ConfigMaps, pero el contenido está codificado en formato base64. Ojo, porque esto no es seguro, ya que cualquiera puede decodificarlo, pero… bueno es “algo” más seguro, ya que es más dificil de recordar y permite guardar binarios.

Ejemplo:

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Secret
metadata:
  name: my-secret
type: Opaque
data:
  # You can include additional key value pairs as you do with Opaque Secrets
  extra: RXN0byBlcyB1biBzZWNyZXRv

El mismo ejemplo, pero especificado de forma legible para humanos, lo que es válido para crear un Secret, pero no se puede recuperar así:

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Secret
metadata:
  name: my-secret
type: Opaque
stringData:
  # You can include additional key value pairs as you do with Opaque Secrets
  extra: Esto es un secreto

Los hay de distintos tipos para gestionar distintas cosas: tokens, claves ssl, certificados https, …

Ingress

El Ingress es una puerta al exterior. Abre un Service fuera del cluster de kuberntes. Normalmente están gestionados por un controlador como nginx, kong, …

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: minimal-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path: /testpath
        pathType: Prefix
        backend:
          service:
            name: test
            port:
              number: 80

Saber más

Para saber más… recomiendo jugar con todo lo expuesto aquí. En próximos posts contaré cómo crear un cluster kubernetes desde cero y cómo desplegar algo útil.