Esta entrada es un registro de las diferentes acciones que realicé para conseguir que los pods asociados al StatefulSet del tutorial StatefulSet Basics se crearan correctamente.

Lo publico como lo que es, un log de todos los pasos que fui dando, en modo ensayo y error, hasta que conseguí que los pods se crearan con éxito. Mi intención al publicarlo no es tanto que sirva como referencia sino como archivo. Y si alguien se encuentra con un problema similar, que pueda consultar los pasos que he dado durante el troubleshooting.

Como indicaba en el artículo anterior, quiero publicar un tutorial paso a paso con el proceso correcto para provisionar los PersistentVolumes necesarios para el tutorial StatefulSet Basics del sitio de Kubernetes.

Creación de un StatefulSet

StatefulSet Basics

Creación del PersistentVolume

  1. Creamos la carpeta que contiene el PersistentVolume:

    mkdir /tmp/data/pv001 -p
    
  2. Definimos el PersistentVolume (vi pv001.yaml):

    kind: PersistentVolume
    apiVersion: v1
    metadata:
        name: pv001
        labels:
            type: local
    spec:
        storageClassName: manual
        capacity:
            storage: 1Gi
        accessModes:
        - ReadWriteOnce
        hostPath:
            path: "/tmp/data/pv001"
    
  3. Creamos el PersistentVolume

    $ kubectl apply -f pv001.yaml
    persistentvolume "pv001" created
    $ kubectl get pv
    NAME      CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS      CLAIM     STORAGECLASS   REASON    AGE
    pv001     1Gi        RWO           Retain          Available             manual                   10s
    
  4. Definimos un PersistentVolumeClaim (pv001claim.yaml)

    $ vi pv001-claim.yaml
    kind: PersistentVolumeClaim
    apiVersion: v1
    metadata:
        name: pv001claim
    spec:
        storageClassName: manual
        accessModes:
        - ReadWriteOnce
        resources:
            requests:
                storage: 1Gi
    
  5. Creamos el PersistentVolumeClaim

    $ kubectl apply -f pv001-claim.yaml
    persistentvolumeclaim "pv001claim" created
    
  6. Verificamos que el PVClaim ha quedado ligado al PV:

    $ kubectl get pvc
    NAME         STATUS    VOLUME    CAPACITY   ACCESSMODES   STORAGECLASS   AGE
    pv001claim   Bound     pv001     1Gi        RWO           manual         43s
    

Creación del StatefulSet

Creación del headless service

  1. Definimos el headless service nginx
kind: Service
apiVersion: v1
metadata:
    name: nginx
    labels:
        app: nginx
spec:
    ports:
- port: 80
name: web
clusterIP: None # Headless Service
selector:
    app: nginx
  1. Creamos el servicio:
     $ kubectl apply -f nginx_svc.yaml
     service "nginx" created
    

Definición del StatefulSet

  1. Definimos el StatefulSet

    kind: StatefulSet
    apiVersion: apps/v1beta1
    metadata:
        name: web
    spec:
        serviceName: "nginx"
        replicas: 2
        template:
            metadata:
                labels:
                    app: nginx
            spec:
                containers:
                - name: nginx
                image: gcr.io/google_containers/nginx-slim:0.8
                ports:
                - containerPort: 80
                    name: web
                volumeMounts:
                - name: www
                mountPath: /usr/shere/nginx/html
        volumeClaimTemplates:
        - metadata:
            name: www
        spec:
            accessModes: [ "ReadWriteOnce" ]
            resources:
                requests:
                    storage: 1Gi
    
  2. Creamos el StatefulSet:

     $ kubectl apply -f statefulset.yaml
     statefulset "web" created
    
  3. Verificamos:

     $ kubectl get statefulset
     NAME      DESIRED   CURRENT   AGE
     web       2         1         53s
     $ kubectl describe statefulset web
     Name:                   web
     Namespace:              default
     CreationTimestamp:      Thu, 17 Aug 2017 09:55:10 +0000
     Selector:               app=nginx
     Labels:                 app=nginx
     Annotations:            kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"apps/v1beta1","kind":"StatefulSet","metadata":{"annotations":{},"name":"web","namespace":"default"},"spec":{"replicas":2,"serviceName":"...
     Replicas:               2 desired | 1 total
     Pods Status:            0 Running / 1 Waiting / 0 Succeeded / 0 Failed
     Pod Template:
     Labels:       app=nginx
     Containers:
     nginx:
         Image:              gcr.io/google_containers/nginx-slim:0.8
         Port:               80/TCP
         Environment:        <none>
         Mounts:
         /usr/shere/nginx/html from www (rw)
     Volumes:      <none>
     Volume Claims:
     Name:         www
     StorageClass:
     Labels:       <none>
     Annotations:  <none>
     Capacity:     1Gi
     Access Modes: [ReadWriteOnce]
     Events:
     FirstSeen     LastSeen        Count   From            SubObjectPath   Type            Reason                  Message
     ---------     --------        -----   ----            -------------   --------        ------                  -------
     2m            2m              1       statefulset                     Normal          SuccessfulCreate        create Claim www-web-0 Pod web-0 in StatefulSet web success
     2m            2m              1       statefulset                     Normal          SuccessfulCreate        create Pod web-0 in StatefulSet web successful
     $
    

    Observamos que hay 1 pod en waiting.

    Revisamos los pods

    kubectl get pods
    NAME      READY     STATUS    RESTARTS   AGE
    web-0     0/1       Pending   0          5m
    $ kubectl describe pod web-0
    Name:           web-0
    Namespace:      default
    Node:           <none>
    Labels:         app=nginx
                    controller-revision-hash=web-3274782773
    Annotations:    kubernetes.io/created-by={"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"StatefulSet","namespace":"default","name":"web","uid":"27fe7496-8332-11e7-9fc4-024216ac27e1","apiVersion":...
    Status:         Pending
    IP:
    Created By:     StatefulSet/web
    Controlled By:  StatefulSet/web
    Containers:
    nginx:
        Image:              gcr.io/google_containers/nginx-slim:0.8
        Port:               80/TCP
        Environment:        <none>
        Mounts:
        /usr/shere/nginx/html from www (rw)
        /var/run/secrets/kubernetes.io/serviceaccount from default-token-klj21 (ro)
    Conditions:
    Type          Status
    PodScheduled  False
    Volumes:
    www:
        Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
        ClaimName:  www-web-0
        ReadOnly:   false
    default-token-klj21:
        Type:       Secret (a volume populated by a Secret)
        SecretName: default-token-klj21
        Optional:   false
    QoS Class:      BestEffort
    Node-Selectors: <none>
    Tolerations:    node.alpha.kubernetes.io/notReady:NoExecute for 300s
                    node.alpha.kubernetes.io/unreachable:NoExecute for 300s
    Events:
    FirstSeen     LastSeen        Count   From                    SubObjectPath   Type            Reason                  Message
    ---------     --------        -----   ----                    -------------   --------        ------                  -------
    7m            7s              29      default-scheduler                       Warning         FailedScheduling        PersistentVolumeClaim is not bound:"www-web-0"
    $
    

    El problema está en que no consigue montar el PersistentVolumeClaim. En la salida del comando el ClaimName es www-web-0.

  4. Troubleshooting del primer pod

    1. Cambio de nombre del PVClaim

      No parece ser posible actualizar un PVClaim, por lo que borramos y creamos uno con el nombre www. No hay ninguna diferencia.

      Eliminamos el PVClaim ligado al PV, pero tampoco cambia nada.

      Eliminamos el PV y lo creamos de nuevo, para ver si el PVClaim lo enlaza. Tampoco funciona.

    2. Eliminar StorageClassName del PV

      El PVClaim creado automáticamente con el StatefulSet no especifica una StorageClassName. Vamos a crear un nuevo PV sin StorageClassName. Tras unos segundos, el PVClaim enlaza con el nuevo PV automáticamente.

      $ kubectl apply -f pv001.yaml
      persistentvolume "pv001" created
      $ kubectl get pvc
      NAME        STATUS    VOLUME    CAPACITY   ACCESSMODES   STORAGECLASS   AGE
      www         Pending                                      manual         8m
      www-web-0   Pending                                                     21m
      $ kubectl get pvc
      NAME        STATUS    VOLUME    CAPACITY   ACCESSMODES   STORAGECLASS   AGE
      www         Pending                                      manual         8m
      www-web-0   Pending                                                     22m
      $ kubectl get pvc
      NAME        STATUS    VOLUME    CAPACITY   ACCESSMODES   STORAGECLASS   AGE
      www         Pending                                      manual         8m
      www-web-0   Bound     pv001     1Gi        RWO                          22m
      

      Eliminamos el PVClaim creado manualmente.

      Revisamos qué ha pasado con los pods pendientes de creación.

      $ kubectl get pods
      NAME      READY     STATUS    RESTARTS   AGE
      web-0     0/1       Pending   0          24m
      $ kubectl describe pod web-0
      ...
      Events:
      FirstSeen     LastSeen        Count   From                    SubObjectPath   Type            Reason                  Message
      ---------     --------        -----   ----                    -------------   --------        ------                  -------
      25m           3m              78      default-scheduler                       Warning         FailedScheduling        PersistentVolumeClaim is not bound:"www-web-0"
      2m            11s             14      default-scheduler                       Warning         FailedScheduling        No nodes are available that match all of the following predicates:: PodToleratesNodeTaints (1).
      

      Parece que el mensaje de error se debe a que no hay nodos disponibles donde planificar el pod. Aunque estoy haciendo las pruebas con un clúster de un solo nodo, he aplicado el taint para poder planificar pods en el nodo master. Por si acaso, aplicamos de nuevo:

      $ kubectl taint nodes --all node-role.kubernetes.io/master-
      node "node1" untainted
      

      Revisamos los pods de nuevo:

      $ kubectl describe pod web-0
      ...
      Events:
      FirstSeen     LastSeen        Count   From                    SubObjectPath           Type            Reason                  Message
      ---------     --------        -----   ----                    -------------           --------        ------                  -------
      33m           11m             78      default-scheduler                               Warning         FailedScheduling        PersistentVolumeClaim is not bound: "www-web-0"
      10m           1m              36      default-scheduler                               Warning         FailedScheduling        No nodes are available that match all of the following predicates:: PodToleratesNodeTaints (1).
      33s           33s             1       default-scheduler                               Normal          Scheduled               Successfully assigned web-0 to node1
      33s           33s             1       kubelet, node1                                  Normal          SuccessfulMountVolume   MountVolume.SetUp succeeded for volume "pv001"
      33s           33s             1       kubelet, node1                                  Normal          SuccessfulMountVolume   MountVolume.SetUp succeeded for volume "default-token-klj21"
      32s           32s             1       kubelet, node1          spec.containers{nginx}  Normal          Pulling                 pulling image "gcr.io/google_containers/nginx-slim:0.8"
      26s           26s             1       kubelet, node1          spec.containers{nginx}  Normal          Pulled                  Successfully pulled image"gcr.io/google_containers/nginx-slim:0.8"
      26s           26s             1       kubelet, node1          spec.containers{nginx}  Normal          Created                 Created container
      26s           26s             1       kubelet, node1          spec.containers{nginx}  Normal          Started                 Started container
      $
      
  5. Troubleshooting del segundo pod

    Ya tenemos un pod corriendo… Pero todavía nos falta otro :(

    $ kubectl get pods
    NAME      READY     STATUS    RESTARTS   AGE
    web-0     1/1       Running   0          34m
    web-1     0/1       Pending   0          1m
    $ kubectl describe pod web-1
    ...
                    node.alpha.kubernetes.io/unreachable:NoExecute for 300s
    Events:
    FirstSeen     LastSeen        Count   From                    SubObjectPath   Type            Reason                  Message
    ---------     --------        -----   ----                    -------------   --------        ------                  -------
    2m            1s              9       default-scheduler                       Warning         FailedScheduling        PersistentVolumeClaim is not bound: "www-web-1"
    

    Seguimos teniendo el problema de que no tenemos el PVClaim enlazado con un PV. Parece claro que el la causa es que sólo hemos creado un PV y necesitamos otro para el segundo pod.

    Creamos un segundo PV (sin especificar StorageClassName):

    mkdir /tmp/data/pv002 -p
    cp pv001.yaml pv002.yaml
    vi pv002.yaml
    

    Modificamos el fichero de definición del pv002 para cambiar el nombre y la ruta a la carpeta donde se almacenarán los ficheros.

    Creamos el pv002:

    $ kubectl apply -f pv002.yaml
    persistentvolume "pv002" created
    

    Revisamos de nuevo el estado del segundo pod:

    $ kubectl get pods
    NAME      READY     STATUS    RESTARTS   AGE
    web-0     1/1       Running   0          42m
    web-1     1/1       Running   0          9m
    

Resumen

Después de estar peleando con los diferentes problemas que he encontrado, queda claro que:

  1. Es necesario crear los PersistentVolumes sin StorageClassName o especificar en el PVClaim el mismo StorageClassName indicado en el PV. Este caso no tengo claro cómo debe hacerse.
  2. Hay que crear tantos PersistentVolumes como pods existan en el StatefulSet.

Queda pendiente crear una guía de creación de los PersistentVolumes para ser consumidos por los pods.