Inicializar automáticamente nodos de GKE con DaemonSets

En este tutorial se muestra cómo personalizar los nodos de un clúster de Google Kubernetes Engine (GKE) mediante DaemonSets. Un DaemonSet se encarga de que todos los nodos (o los nodos seleccionados) ejecuten una copia de un pod. Este enfoque te permite usar las mismas herramientas para orquestar tus cargas de trabajo que las que usas para modificar tus nodos de GKE.

Si las herramientas y los sistemas que usas para inicializar tus clústeres son diferentes de las herramientas y los sistemas que usas para ejecutar tus cargas de trabajo, aumentará el esfuerzo necesario para gestionar tu entorno. Por ejemplo, si usas una herramienta de gestión de la configuración para inicializar los nodos del clúster, estás usando un procedimiento que está fuera del entorno de tiempo de ejecución en el que se ejecutan el resto de tus cargas de trabajo.

El objetivo de este tutorial es ayudar a los administradores de sistemas, ingenieros de sistemas u operadores de infraestructura a agilizar la inicialización de clústeres de Kubernetes.

Antes de leer esta página, asegúrate de que conoces los siguientes conceptos:

En este tutorial, aprenderás a usar etiquetas y selectores de Kubernetes para elegir qué procedimiento de inicialización ejecutar en función de las etiquetas que se apliquen a un nodo. En estos pasos, implementará un DaemonSet para que se ejecute solo en los nodos que tengan aplicada la etiqueta default-init. Sin embargo, para demostrar la flexibilidad de este mecanismo, puedes crear otro grupo de nodos y aplicar la etiqueta alternative-init a los nodos de este nuevo grupo. En el clúster, puedes desplegar otro DaemonSet configurado para ejecutarse solo en los nodos que tengan la etiqueta alternative-init.

También puedes ejecutar varios procedimientos de inicialización en cada nodo, no solo uno. Puedes aprovechar este mecanismo para estructurar mejor tus procedimientos de inicialización y separar claramente las responsabilidades de cada uno.

En este tutorial, como ejemplo, el procedimiento de inicialización realiza las siguientes acciones en cada nodo etiquetado con la etiqueta default-init:

  1. Adjunta un disco adicional al nodo.
  2. Instala un conjunto de paquetes y bibliotecas mediante el gestor de paquetes del sistema operativo del nodo.
  3. Carga un conjunto de módulos del kernel de Linux.

Inicializar el entorno

En esta sección, debes hacer lo siguiente:

  1. Habilita las APIs de Cloud necesarias.
  2. Aprovisiona una cuenta de servicio con privilegios limitados para los nodos del clúster de GKE.
  3. Prepara el clúster de GKE.
  4. Concede al usuario privilegios de administración del clúster.

Habilitar APIs de Cloud

  1. Abre Cloud Shell.

    ABRIR Cloud Shell

  2. Selecciona el Google Cloud proyecto:

    gcloud config set project project-id
    

    Sustituye project-id por el ID delGoogle Cloud proyecto que has creado o seleccionado para este tutorial.

  3. Habilita la API de Google Kubernetes Engine:

    gcloud services enable container.googleapis.com
    

Aprovisionar una cuenta de servicio para gestionar clústeres de GKE

En esta sección, creará una cuenta de servicio asociada a los nodos del clúster. En este tutorial, los nodos de GKE usan esta cuenta de servicio en lugar de la cuenta de servicio predeterminada. Como práctica recomendada, concede a la cuenta de servicio solo los roles y permisos de acceso necesarios para ejecutar la aplicación.

La cuenta de servicio debe tener los siguientes roles:

  • Rol Lector de Monitoring (roles/monitoring.viewer). Este rol proporciona acceso de solo lectura a la consola y a la API de Cloud Monitoring.
  • Rol Editor de métricas de Monitoring (roles/monitoring.metricWriter). Este rol permite escribir datos de monitorización.
  • Rol Escritor de registros (roles/logging.logWriter): este rol proporciona los permisos suficientes para escribir registros.
  • Rol Usuario de cuenta de servicio (roles/iam.serviceAccountUser). Este rol da acceso a las cuentas de servicio de un proyecto. En este tutorial, el procedimiento de inicialización suplanta la identidad de la cuenta de servicio para ejecutar operaciones con privilegios.
  • Rol Administrador de Compute (roles/compute.admin). Este rol proporciona control total sobre todos los recursos de Compute Engine. En este tutorial, la cuenta de servicio necesita este rol para adjuntar discos adicionales a los nodos del clúster.

Para aprovisionar una cuenta de servicio, sigue estos pasos:

  1. En Cloud Shell, inicializa una variable de entorno que almacene el nombre de la cuenta de servicio:

    GKE_SERVICE_ACCOUNT_NAME=ds-init-tutorial-gke
    
  2. Crea una cuenta de servicio:

    gcloud iam service-accounts create "$GKE_SERVICE_ACCOUNT_NAME" \
      --display-name="$GKE_SERVICE_ACCOUNT_NAME"
    
  3. Inicializa una variable de entorno que almacene el nombre de la cuenta de correo de la cuenta de servicio:

    GKE_SERVICE_ACCOUNT_EMAIL="$(gcloud iam service-accounts list \
        --format='value(email)' \
        --filter=displayName:"$GKE_SERVICE_ACCOUNT_NAME")"
    
  4. Asigna los roles de Gestión de Identidades y Accesos (IAM) a la cuenta de servicio:

    gcloud projects add-iam-policy-binding \
        "$(gcloud config get-value project 2> /dev/null)" \
        --member serviceAccount:"$GKE_SERVICE_ACCOUNT_EMAIL" \
        --role roles/compute.admin
    gcloud projects add-iam-policy-binding \
        "$(gcloud config get-value project 2> /dev/null)" \
        --member serviceAccount:"$GKE_SERVICE_ACCOUNT_EMAIL" \
        --role roles/monitoring.viewer
    gcloud projects add-iam-policy-binding \
        "$(gcloud config get-value project 2> /dev/null)" \
        --member serviceAccount:"$GKE_SERVICE_ACCOUNT_EMAIL" \
        --role roles/monitoring.metricWriter
    gcloud projects add-iam-policy-binding \
        "$(gcloud config get-value project 2> /dev/null)" \
        --member serviceAccount:"$GKE_SERVICE_ACCOUNT_EMAIL" \
        --role roles/logging.logWriter
    gcloud projects add-iam-policy-binding \
        "$(gcloud config get-value project 2> /dev/null)" \
        --member serviceAccount:"$GKE_SERVICE_ACCOUNT_EMAIL" \
        --role roles/iam.serviceAccountUser
    

Preparar el clúster de GKE

En esta sección, inicia el clúster de GKE, concede permisos y completa la configuración del clúster.

En este tutorial, basta con un clúster con un número relativamente bajo de nodos pequeños de propósito general para demostrar el concepto. Crea un clúster con un grupo de nodos (el predeterminado). A continuación, etiqueta todos los nodos del grupo de nodos predeterminado con la etiqueta default-init.

  • En Cloud Shell, crea e inicia un clúster de GKE regional:

    gcloud container clusters create ds-init-tutorial \
        --enable-ip-alias \
        --image-type=ubuntu_containerd \
        --machine-type=n1-standard-2 \
        --metadata disable-legacy-endpoints=true \
        --node-labels=app=default-init \
        --node-locations us-central1-a,us-central1-b,us-central1-c \
        --no-enable-basic-auth \
        --no-issue-client-certificate \
        --num-nodes=1 \
        --location us-central1 \
        --service-account="$GKE_SERVICE_ACCOUNT_EMAIL"
    

Desplegar el DaemonSet

En esta sección, debes hacer lo siguiente:

  1. Crea el ConfigMap que almacena el procedimiento de inicialización.
  2. Despliega el DaemonSet que programa y ejecuta el procedimiento de inicialización.

DaemonSet hace lo siguiente:

  1. Configura un volumen que hace que el contenido de ConfigMap esté disponible para los contenedores que gestiona DaemonSet.
  2. Configura los volúmenes de las áreas del sistema de archivos privilegiadas del nodo de clúster subyacente. Estas áreas permiten que los contenedores que programa DaemonSet interactúen directamente con el nodo que los ejecuta.
  3. Programa y ejecuta un contenedor init que ejecuta el procedimiento de inicialización y, a continuación, se termina al completarse.
  4. Programa y ejecuta un contenedor que permanece inactivo y no consume recursos.

El contenedor inactivo asegura que un nodo se inicialice solo una vez. Los DaemonSets se han diseñado para que todos los nodos aptos ejecuten una copia de un pod. Si usas un contenedor normal, este ejecuta el procedimiento de inicialización y, a continuación, se cierra cuando se completa. Por diseño, el DaemonSet vuelve a programar el pod. Para evitar la reprogramación continua, DaemonSet primero ejecuta el procedimiento de inicialización en un contenedor init y, a continuación, deja un contenedor en ejecución.

El siguiente procedimiento de inicialización contiene operaciones con y sin privilegios. Con chroot, puedes ejecutar comandos como si los estuvieras ejecutando directamente en el nodo, no solo dentro de un contenedor.

# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: entrypoint
  labels:
    app: default-init
data:
  entrypoint.sh: |
    #!/usr/bin/env bash

    set -euo pipefail

    DEBIAN_FRONTEND=noninteractive
    ROOT_MOUNT_DIR="${ROOT_MOUNT_DIR:-/root}"

    echo "Installing dependencies"
    apt-get update
    apt-get install -y apt-transport-https curl gnupg lsb-release

    echo "Installing gcloud SDK"
    export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)"
    echo "deb https://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
    curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
    apt-get update
    apt-get install -y google-cloud-sdk

    echo "Getting node metadata"
    NODE_NAME="$(curl -sS http://metadata.google.internal/computeMetadata/v1/instance/name -H 'Metadata-Flavor: Google')"
    ZONE="$(curl -sS http://metadata.google.internal/computeMetadata/v1/instance/zone -H 'Metadata-Flavor: Google' | awk -F  "/" '{print $4}')"

    echo "Setting up disks"
    DISK_NAME="$NODE_NAME-additional"

    if ! gcloud compute disks list --filter="name:$DISK_NAME" | grep "$DISK_NAME" > /dev/null; then
        echo "Creating $DISK_NAME"
        gcloud compute disks create "$DISK_NAME" --size=1024 --zone="$ZONE"
    else
        echo "$DISK_NAME already exists"
    fi

    if ! gcloud compute instances describe "$NODE_NAME" --zone "$ZONE" --format '(disks[].source)' | grep "$DISK_NAME" > /dev/null; then
        echo "Attaching $DISK_NAME to $NODE_NAME"
        gcloud compute instances attach-disk "$NODE_NAME" --device-name=sdb --disk "$DISK_NAME" --zone "$ZONE"
    else
        echo "$DISK_NAME is already attached to $NODE_NAME"
    fi

    # We use chroot to run the following commands in the host root (mounted as the /root volume in the container)
    echo "Installing nano"
    chroot "${ROOT_MOUNT_DIR}" apt-get update
    chroot "${ROOT_MOUNT_DIR}" apt-get install -y nano

    echo "Loading Kernel modules"
    # Load the bridge kernel module as an example
    chroot "${ROOT_MOUNT_DIR}" modprobe bridge
...

Te recomendamos que revises detenidamente cada procedimiento de inicialización, ya que podría alterar el estado de los nodos de tu clúster. Solo un pequeño grupo de personas debería tener derecho a modificar esos procedimientos, ya que pueden afectar en gran medida a la disponibilidad y la seguridad de tus clústeres.

Para desplegar el ConfigMap y el DaemonSet, haz lo siguiente:

  1. En Cloud Shell, cambia el directorio de trabajo a $HOME:

    cd "$HOME"
    
  2. Clona el repositorio de Git que contiene las secuencias de comandos y los archivos de manifiesto para implementar y configurar el procedimiento de inicialización:

    git clone https://github.com/GoogleCloudPlatform/solutions-gke-init-daemonsets-tutorial
    
  3. Cambia el directorio de trabajo por el directorio del repositorio recién clonado:

    cd "$HOME"/solutions-gke-init-daemonsets-tutorial
    
  4. Crea un ConfigMap para contener la secuencia de comandos de inicialización del nodo:

    kubectl apply -f cm-entrypoint.yaml
    
  5. Despliega el DaemonSet:

    kubectl apply -f daemon-set.yaml
    
  6. Verifica que se haya completado la inicialización del nodo:

    kubectl get ds --watch
    

    Espera a que se indique que el DaemonSet está listo y actualizado, como se muestra en un resultado similar al siguiente:

    NAME              DESIRED   CURRENT   READY     UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
    node-initializer   3         3         3         3            3          <none>  2h
    

Validar y verificar el procedimiento de inicialización

Después de que cada nodo del clúster marcado con la etiqueta default-init ejecute el procedimiento de inicialización, puede verificar los resultados.

En cada nodo, el procedimiento de verificación comprueba lo siguiente:

  1. Se ha conectado un disco adicional y está listo para usarse.
  2. Gestor de paquetes del sistema operativo del nodo que ha instalado los paquetes y las bibliotecas.
  3. Se cargan los módulos del kernel.

Ejecuta el procedimiento de verificación:

  • En Cloud Shell, ejecuta la secuencia de comandos de verificación:

    kubectl get nodes -o=jsonpath='{range .items[?(@.metadata.labels.app=="default-init")]}{.metadata.name}{" "}{.metadata.labels.failure-domain\.beta\.kubernetes\.io/zone}{"\n"}{end}' | while IFS= read -r line ; do ./verify-init.sh $line < /dev/null; done
    

    Espera a que se ejecute la secuencia de comandos y comprueba que cada nodo se ha inicializado correctamente, tal como se indica en un resultado como el siguiente:

    Verifying gke-ds-init-tutorial-default-pool-5464b7e3-nzjm (us-central1-c) configuration
    Disk configured successfully on gke-ds-init-tutorial-default-pool-5464b7e3-nzjm (us-central1-c)
    Packages installed successfully in gke-ds-init-tutorial-default-pool-5464b7e3-nzjm (us-central1-c)
    Kernel modules loaded successfully on gke-ds-init-tutorial-default-pool-5464b7e3-nzjm (us-central1-c)
    Verifying gke-ds-init-tutorial-default-pool-65baf745-0gwt (us-central1-a) configuration
    Disk configured successfully on gke-ds-init-tutorial-default-pool-65baf745-0gwt (us-central1-a)
    Packages installed successfully in gke-ds-init-tutorial-default-pool-65baf745-0gwt (us-central1-a)
    Kernel modules loaded successfully on gke-ds-init-tutorial-default-pool-65baf745-0gwt (us-central1-a)
    Verifying gke-ds-init-tutorial-default-pool-6b125c50-3xvl (us-central1-b) configuration
    Disk configured successfully on gke-ds-init-tutorial-default-pool-6b125c50-3xvl (us-central1-b)
    Packages installed successfully in gke-ds-init-tutorial-default-pool-6b125c50-3xvl (us-central1-b)
    Kernel modules loaded successfully on gke-ds-init-tutorial-default-pool-6b125c50-3xvl (us-central1-b)