Desplegar una carga de trabajo con estado con Filestore

En este tutorial se explica cómo desplegar una carga de trabajo con estado de lector/escritor sencilla mediante un volumen persistente (PV) y una reclamación de volumen persistente (PVC) en Google Kubernetes Engine (GKE). Sigue este tutorial para aprender a diseñar con escalabilidad mediante Filestore, el sistema de archivos de red gestionado de Google Cloud.

Fondo

Por naturaleza, los Pods son efímeros. Esto significa que GKE destruye el estado y el valor almacenados en un pod cuando se elimina, se expulsa o se vuelve a programar.

Como operador de aplicaciones, puede que quieras mantener cargas de trabajo con reconocimiento del estado. Algunos ejemplos de estas cargas de trabajo son las aplicaciones que procesan artículos de WordPress, las aplicaciones de mensajería y las aplicaciones que procesan operaciones de aprendizaje automático.

Si usas Filestore en GKE, puedes realizar las siguientes operaciones:

  • Despliega cargas de trabajo con estado que sean escalables.
  • Permite que varios pods tengan ReadWriteMany como accessMode, de forma que varios pods puedan leer y escribir al mismo tiempo en el mismo almacenamiento.
  • Configura GKE para montar volúmenes en varios pods simultáneamente.
  • Conserva el almacenamiento cuando se eliminen los pods.
  • Permite que los pods compartan datos y se escalen fácilmente.

Configurar el almacenamiento de archivos gestionado con Filestore mediante CSI

GKE ofrece una forma de desplegar y gestionar automáticamente el controlador de CSI de Filestore para Kubernetes en tus clústeres. Con el controlador de CSI para Filestore, puedes crear o eliminar instancias de Filestore de forma dinámica y usarlas en cargas de trabajo de Kubernetes con un StorageClass o un Deployment.

Puedes crear una instancia de Filestore creando un PVC que aprovisione dinámicamente una instancia de Filestore y el PV, o bien acceder a instancias de Filestore aprovisionadas previamente en cargas de trabajo de Kubernetes.

Nueva instancia

Crear la clase de almacenamiento

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: filestore-sc
provisioner: filestore.csi.storage.gke.io
volumeBindingMode: Immediate
allowVolumeExpansion: true
parameters:
  tier: standard
  network: default
  • volumeBindingMode se ha definido como Immediate, lo que permite que el aprovisionamiento del volumen comience inmediatamente.
  • tier se define como standard para que las instancias de Filestore se creen más rápido. Si necesitas un almacenamiento NFS con mayor disponibilidad, instantáneas para copias de seguridad de datos, replicación de datos en varias zonas y otras funciones de nivel empresarial, define tier como enterprise. Nota: La política de reclamación de los PV creados dinámicamente tiene el valor predeterminado Delete si no se define reclaimPolicy en StorageClass.
  1. Crea el recurso StorageClass:

    kubectl create -f filestore-storageclass.yaml
    
  2. Verifica que se haya creado la clase de almacenamiento:

    kubectl get sc
    

    El resultado debería ser similar al siguiente:

    NAME                     PROVISIONER                    RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
    filestore-sc             filestore.csi.storage.gke.io   Delete          Immediate              true                   94m
    

Instancia preaprovisionada

Crear la clase de almacenamiento

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: filestore-sc
provisioner: filestore.csi.storage.gke.io
volumeBindingMode: Immediate
allowVolumeExpansion: true

Si volumeBindingMode tiene el valor Immediate, el aprovisionamiento del volumen puede empezar inmediatamente.

  1. Crea el recurso StorageClass:

      kubectl create -f preprov-storageclass.yaml
    
  2. Verifica que se haya creado la clase de almacenamiento:

      kubectl get sc
    

    El resultado debería ser similar al siguiente:

      NAME                     PROVISIONER                    RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
      filestore-sc             filestore.csi.storage.gke.io   Delete          Immediate              true                   94m
    

Crear un volumen persistente para la instancia de Filestore

apiVersion: v1
kind: PersistentVolume
metadata:
  name: fileserver
  annotations:
    pv.kubernetes.io/provisioned-by: filestore.csi.storage.gke.io
spec:
  storageClassName: filestore-sc
  capacity:
    storage: 1Ti
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Delete
  volumeMode: Filesystem
  csi:
    driver: filestore.csi.storage.gke.io
    # Modify this to use the zone, filestore instance and share name.
    volumeHandle: "modeInstance/<LOCATION>/<INSTANCE_NAME>/<FILE_SHARE_NAME>"
    volumeAttributes:
      ip: <IP_ADDRESS> # Modify this to Pre-provisioned Filestore instance IP
      volume: <FILE_SHARE_NAME> # Modify this to Pre-provisioned Filestore instance share name
  1. Verifica que la instancia de Filestore preexistente esté lista:

      gcloud filestore instances list
    

    El resultado es similar al siguiente, donde el valor de STATE es READY:

      INSTANCE_NAME: stateful-filestore
      LOCATION: us-central1-a
      TIER: ENTERPRISE
      CAPACITY_GB: 1024
      FILE_SHARE_NAME: statefulpath
      IP_ADDRESS: 10.109.38.98
      STATE: READY
      CREATE_TIME: 2022-04-05T18:58:28
    

    Anota el INSTANCE_NAME, LOCATION, FILE_SHARE_NAME y IP_ADDRESS de la instancia de Filestore.

  2. Rellena las variables de la consola de la instancia de Filestore:

      INSTANCE_NAME=INSTANCE_NAME
      LOCATION=LOCATION
      FILE_SHARE_NAME=FILE_SHARE_NAME
      IP_ADDRESS=IP_ADDRESS
    
  3. Sustituye las variables de marcador de posición por las variables de consola obtenidas anteriormente en el archivo preprov-pv.yaml:

      sed "s/<INSTANCE_NAME>/$INSTANCE_NAME/" preprov-pv.yaml > changed.yaml && mv changed.yaml preprov-pv.yaml
      sed "s/<LOCATION>/$LOCATION/" preprov-pv.yaml > changed.yaml && mv changed.yaml preprov-pv.yaml
      sed "s/<FILE_SHARE_NAME>/$FILE_SHARE_NAME/" preprov-pv.yaml > changed.yaml && mv changed.yaml preprov-pv.yaml
      sed "s/<IP_ADDRESS>/$IP_ADDRESS/" preprov-pv.yaml > changed.yaml && mv changed.yaml preprov-pv.yaml
    
  4. Crea el PV

      kubectl apply -f preprov-pv.yaml
    
  5. Comprueba que el valor de STATUS de la PV sea Bound:

      kubectl get pv
    

    El resultado debería ser similar al siguiente:

      NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS    REASON   AGE
      fileserver  1Ti        RWX            Delete           Bound    default/fileserver   filestore-sc             46m
    

Usar un PersistentVolumeClaim para acceder al volumen

El siguiente manifiesto de pvc.yaml hace referencia al StorageClass del controlador CSI de Filestore llamado filestore-sc.

Para que varios pods puedan leer y escribir en el volumen, el valor de accessMode se define como ReadWriteMany.

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: fileserver
spec:
  accessModes:
  - ReadWriteMany
  storageClassName: filestore-sc
  resources:
    requests:
      storage: 1Ti
  1. Despliega el PVC:

    kubectl create -f pvc.yaml
    
  2. Verifica que se haya creado el PVC:

    kubectl get pvc
    

    El resultado debería ser similar al siguiente:

    NAME         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS        AGE
    fileserver   Bound    pvc-aadc7546-78dd-4f12-a909-7f02aaedf0c3   1Ti        RWX            filestore-sc        92m
    
  3. Comprueba que la instancia de Filestore recién creada esté lista:

    gcloud filestore instances list
    

    El resultado debería ser similar al siguiente:

    INSTANCE_NAME: pvc-5bc55493-9e58-4ca5-8cd2-0739e0a7b68c
    LOCATION: northamerica-northeast2-a
    TIER: STANDARD
    CAPACITY_GB: 1024
    FILE_SHARE_NAME: vol1
    IP_ADDRESS: 10.29.174.90
    STATE: READY
    CREATE_TIME: 2022-06-24T18:29:19
    

Crear un pod de lectura y otro de escritura

En esta sección, crearás un pod de lectura y un pod de escritura. En este tutorial se usan implementaciones de Kubernetes para crear los pods. Un Deployment es un objeto de la API de Kubernetes que te permite ejecutar varias réplicas de pods distribuidas entre los nodos de un clúster.

Crear el Pod de lectura

El pod de lectura leerá el archivo que escriban los pods de escritura. Los pods de lectura verán a qué hora y qué réplica del pod de escritura ha escrito en el archivo.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: reader
spec:
  replicas: 1
  selector:
    matchLabels:
      app: reader
  template:
    metadata:
      labels:
        app: reader
    spec:
      containers:
      - name: nginx
        image: nginx:stable-alpine
        ports:
        - containerPort: 80
        volumeMounts:
        - name: fileserver
          mountPath: /usr/share/nginx/html # the shared directory 
          readOnly: true
      volumes:
      - name: fileserver
        persistentVolumeClaim:
          claimName: fileserver

El pod de lectura leerá de la ruta /usr/share/nginx/html, que se comparte entre todos los pods.

  1. Implementa el pod de lector:

    kubectl apply -f reader-fs.yaml
    
  2. Comprueba que las réplicas de lectores se están ejecutando. Para ello, haz una consulta a la lista de pods:

    kubectl get pods
    

    El resultado debería ser similar al siguiente:

    NAME                      READY   STATUS    RESTARTS   AGE
    reader-66b8fff8fd-jb9p4   1/1     Running   0          3m30s
    

Crea el Pod de redacción

El pod de escritura escribirá periódicamente en un archivo compartido al que podrán acceder otros pods de escritura y lectura. El pod de escritura registra su presencia escribiendo su nombre de host en el archivo compartido.

La imagen utilizada para el pod del escritor es una imagen personalizada de Alpine Linux, que se usa para utilidades y aplicaciones de producción. Incluye una secuencia de comandos indexInfo.html que obtendrá los metadatos del escritor más reciente y llevará la cuenta de todos los escritores únicos y las escrituras totales.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: writer
spec:
  replicas: 2 # start with 2 replicas
  selector:
    matchLabels:
      app: writer
  template:
    metadata:
      labels:
        app: writer
    spec:
      containers:
      - name: content
        image: us-docker.pkg.dev/google-samples/containers/gke/stateful-workload:latest
        volumeMounts:
        - name: fileserver
          mountPath: /html # the shared directory
        command: ["/bin/sh", "-c"]
        args:
        - cp /htmlTemp/indexInfo.html /html/index.html;
          while true; do
          echo "<b> Date :</b> <text>$(date)</text> <b> Writer :</b> <text2> ${HOSTNAME} </text2> <br>  " >> /html/indexData.html;
          sleep 30;  
          done
      volumes:
      - name: fileserver
        persistentVolumeClaim:
          claimName: fileserver

En este tutorial, el pod de escritura escribe cada 30 segundos en la ruta /html/index.html. Modifica el valor del número sleep para que tenga una frecuencia de escritura diferente.

  1. Despliega el pod de escritura:

    kubectl apply -f writer-fs.yaml
    
  2. Comprueba que los pods de escritura se están ejecutando. Para ello, haz una consulta a la lista de pods:

    kubectl get pods
    

    El resultado debería ser similar al siguiente:

    NAME                      READY   STATUS    RESTARTS   AGE
    reader-66b8fff8fd-jb9p4   1/1     Running   0          3m30s
    writer-855565fbc6-8gh2k   1/1     Running   0          2m31s
    writer-855565fbc6-lls4r   1/1     Running   0          2m31s
    

Exponer y acceder a la carga de trabajo de lector a un balanceador de carga de servicio

Para exponer una carga de trabajo fuera del clúster, crea un servicio de tipo LoadBalancer. Este tipo de servicio crea un balanceador de carga externo con una dirección IP accesible a través de Internet.

  1. Crea un servicio de tipo LoadBalancer llamado reader-lb:

    kubectl create -f loadbalancer.yaml
    
  2. Supervisa la implementación para ver que GKE asigna un EXTERNAL-IP al servicio reader-lb:

    kubectl get svc --watch
    

    Cuando el Service esté listo, la columna EXTERNAL-IP mostrará la dirección IP pública del balanceador de carga:

      NAME         TYPE           CLUSTER-IP    EXTERNAL-IP     PORT(S)        AGE
      kubernetes   ClusterIP      10.8.128.1    <none>          443/TCP        2d21h
      reader-lb    LoadBalancer   10.8.131.79   34.71.232.122   80:32672/TCP   2d20h
    
  3. Pulsa Ctrl+C para finalizar el proceso de monitorización.

  4. Usa un navegador web para ir a la EXTERNAL-IP asignada al balanceador de carga. La página se actualiza cada 30 segundos. Cuantos más escritores de pódcasts y menor sea la frecuencia, más entradas se mostrarán.

Para ver más detalles sobre el servicio de balanceador de carga, consulta loadbalancer.yaml.

Ampliar el escritor

Como el PV accessMode se ha definido como ReadWriteMany, GKE puede aumentar el número de pods para que más pods de escritura puedan escribir en este volumen compartido (o más lectores puedan leerlos).

  1. Amplía writer a cinco réplicas:

    kubectl scale deployment writer --replicas=5
    

    El resultado debería ser similar al siguiente:

    deployment.extensions/writer scaled
    
  2. Verifica el número de réplicas en ejecución:

    kubectl get pods
    

    El resultado debería ser similar al siguiente:

    NAME                      READY   STATUS    RESTARTS   AGE
    reader-66b8fff8fd-jb9p4   1/1     Running   0          11m
    writer-855565fbc6-8dfkj   1/1     Running   0          4m
    writer-855565fbc6-8gh2k   1/1     Running   0          10m
    writer-855565fbc6-gv5rs   1/1     Running   0          4m
    writer-855565fbc6-lls4r   1/1     Running   0          10m
    writer-855565fbc6-tqwxc   1/1     Running   0          4m
    
  3. Usa un navegador web para volver a la EXTERNAL-IP asignada al balanceador de carga.

En este punto, has configurado y escalado tu clúster para que admita cinco pods de escritura con estado. Cuando varios pods de escritura escriben en el mismo archivo simultáneamente. Los lectores de Pods también se pueden ampliar fácilmente.

Opcional: Acceder a los datos del Pod de escritura

En esta sección se muestra cómo usar una interfaz de línea de comandos para acceder a un pod de lectura o escritura. Puedes ver el componente compartido en el que escribe el escritor y del que lee el lector.

  1. Obtén el nombre del pod de escritura:

    kubectl get pods
    

    El resultado debería ser similar al siguiente:

    NAME                      READY   STATUS    RESTARTS   AGE
    writer-5465d65b46-7hxv4   1/1     Running   0          20d
    

    Anota el nombre de host de un pod de escritor (por ejemplo, writer-5465d65b46-7hxv4).

  2. Ejecuta el siguiente comando para acceder al pod de escritura:

    kubectl exec -it WRITER_HOSTNAME -- /bin/sh
    
  3. Consulta el componente compartido en el archivo indexData.html:

    cd /html
    cat indexData.html
    
  4. Borra el archivo indexData.html:

    echo '' > indexData.html
    

    Actualiza el navegador web que aloja la dirección EXTERNAL-IP para ver el cambio.

  5. Salir del entorno:

    exit