Inicializar automaticamente os nós do GKE com o DaemonSets.

Neste tutorial, mostramos como personalizar os nós de um cluster do Google Kubernetes Engine (GKE) usando DaemonSets. Um DaemonSet ajuda a garantir que todos os nós (ou os nós selecionados) executem uma cópia de um pod. Quando novos nós são adicionados a um cluster, eles também executam um pod do DaemonSet.

Se as ferramentas e os sistemas usados para inicializar os clusters forem diferentes das ferramentas e dos sistemas usados para executar as cargas de trabalho, você aumentará o esforço necessário para gerenciar o ambiente. Por exemplo, se você usa uma ferramenta de gerenciamento de configuração para inicializar os nós do cluster, você depende de um procedimento fora do ambiente de execução em que as outras cargas de trabalho são executadas. Usar um DaemonSet permite usar as mesmas ferramentas para orquestrar as cargas de trabalho que você usa para modificar os nós do GKE.

O objetivo deste tutorial é ajudar administradores de sistemas, engenheiros de sistemas ou operadores de infraestrutura a simplificar a inicialização de clusters do Kubernetes.

Antes de ler esta página, você precisa conhecer:

Neste tutorial, você vai aprender a usar taints e tolerâncias do Kubernetes para garantir que os nós sejam configurados por um DaemonSet antes que as cargas de trabalho de aplicativos possam ser programadas neles.

Objetivos

Neste tutorial, você realizará as seguintes ações:

  • Veja como provisionar um cluster do GKE.
  • Adicione um taint a um pool de nós para evitar o agendamento de cargas de trabalho antes de aplicar a configuração do nó.
  • Implante um DaemonSet que configure os nós e remova a restrição.
  • Verifique se os nós do cluster estão configurados e se a taint foi removida.

Custos

Neste documento, você vai usar os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços.

Novos usuários do Google Cloud podem estar qualificados para um teste sem custo financeiro.

Ao concluir as tarefas descritas neste documento, é possível evitar o faturamento contínuo excluindo os recursos criados. Para mais informações, consulte Limpeza.

Antes de começar

  1. Faça login na sua conta do Google Cloud . Se você começou a usar o Google Cloud, crie uma conta para avaliar o desempenho de nossos produtos em situações reais. Clientes novos também recebem US$ 300 em créditos para executar, testar e implantar cargas de trabalho.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  3. Verify that billing is enabled for your Google Cloud project.

  4. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  5. Verify that billing is enabled for your Google Cloud project.

Implicações de segurança do DaemonSet privilegiado

Usar a configuração securityContext: privileged: true em um DaemonSet (ou qualquer pod) é poderoso, mas tem implicações de segurança significativas porque desativa a maioria dos limites de isolamento de contêineres para esse pod. Você precisa estar ciente das seguintes restrições e riscos de segurança que ele apresenta:

  • Escape de contêiner ou comprometimento do host:uma vulnerabilidade no aplicativo ou na imagem do contêiner privilegiado pode levar diretamente ao acesso root no nó do host.
  • Violação do princípio de privilégio mínimo:o modo privilegiado concede todas as capacidades, provavelmente muito mais do que o necessário para uma tarefa específica. Esse acesso amplo aumenta o potencial de danos se o contêiner for comprometido.
  • Desestabilização do nó:comandos acidentais ou maliciosos podem ser executados no contêiner privilegiado, por exemplo, valores sysctl incorretos ou comandos como rm -rf /host/boot. Esses tipos de comandos podem falhar ou corromper o sistema operacional do nó host.
  • Movimentação lateral:comprometer um nó usando um DaemonSet privilegiado dá a um invasor uma base sólida para atacar outros nós, o plano de controle do Kubernetes ou sistemas conectados.
  • Exposição de dados:o acesso irrestrito ao sistema de arquivos do host (/) pode expor dados sensíveis armazenados no nó, incluindo credenciais, chaves ou dados pertencentes a outros pods se eles usarem volumes hostPath.
  • Maior superfície de ataque:o modo privilegiado expõe mais chamadas e recursos do sistema do kernel do host a possíveis exploits no contêiner.

Para evitar riscos de segurança, siga estas práticas recomendadas e mitigações:

  • Evite usar o modo privilegiado:a abordagem mais segura é evitar completamente a configuração privileged: true.
  • Usar recursos do Linux:se forem necessários direitos elevados, conceda recursos específicos do Linux, como NET_ADMIN, SYS_ADMIN, SYS_MODULE no campo securityContext.capabilities.add em vez de privilégio total. Essa abordagem segue o princípio de privilégio mínimo, que recomendamos em vez de conceder permissões amplas.
  • Limitar o escopo:execute DaemonSets privilegiados apenas em pools de nós dedicados, possivelmente corrompidos, para conter o impacto potencial se um contêiner for comprometido.
  • Aplicar políticas:use ferramentas como o Controlador de Políticas ou o Gatekeeper para criar políticas que restringem, auditam ou exigem justificativa para a implantação de contêineres privilegiados.
  • Verificar e usar imagens confiáveis:use a autorização binária e a verificação rigorosa de imagens para garantir que apenas imagens de contêiner confiáveis e verificadas sejam executadas com privilégios elevados.
  • Minimize as montagens de host:monte apenas os caminhos de host específicos necessários e use readOnly: true sempre que possível. Evite montar todo o sistema de arquivos raiz (/).
  • Faça auditorias regulares:revise periodicamente todas as cargas de trabalho em execução com a configuração privileged: true.

Inicializar o ambiente

Nesta seção, você realizará as ações a seguir:

  1. Ativar as APIs do Cloud necessárias.
  2. Provisionar uma conta de serviço com privilégios limitados para os nós no cluster do GKE.
  3. Preparar o cluster do GKE.
  4. Conceder privilégios de administração do cluster ao usuário.

Ativar APIs do Cloud

  1. Abra o Cloud Shell.

    ABRIR Cloud Shell

  2. Selecione o projeto Google Cloud :

    gcloud config set project project-id
    

    Substitua project-id pelo ID do projetoGoogle Cloud que você criou ou selecionou para este tutorial.

  3. Ative a API Kubernetes Engine.

    gcloud services enable container.googleapis.com
    

Provisionar uma conta de serviço para gerenciar clusters do GKE

Nesta seção, você criará uma conta de serviço associada aos nós no cluster. Neste tutorial, os nós do GKE usam essa conta de serviço em vez da conta de serviço padrão. Como prática recomendada, conceda à conta de serviço apenas os papéis e as permissões de acesso necessários para executar o aplicativo.

Os papéis necessários para a conta de serviço são os seguintes:

  • Leitor do Monitoring (roles/monitoring.viewer). Esse papel fornece acesso somente leitura aos dados de monitoramento.
  • Gravador de métricas do Monitoring (roles/monitoring.metricWriter). Esse papel permite gravar dados de monitoramento.
  • Gravador de registros (roles/logging.logWriter). Esse papel concede permissões para gravar registros.

Para provisionar uma conta de serviço, siga estas etapas:

  1. No Cloud Shell, inicialize uma variável de ambiente que armazene o nome da conta de serviço:

    GKE_SERVICE_ACCOUNT_NAME=ds-init-tutorial-gke
    
  2. Crie uma conta de serviço:

    gcloud iam service-accounts create "$GKE_SERVICE_ACCOUNT_NAME" \
      --display-name="$GKE_SERVICE_ACCOUNT_NAME"
    
  3. Inicialize uma variável de ambiente que armazene o nome da conta de e-mail da conta de serviço:

    GKE_SERVICE_ACCOUNT_EMAIL="$(gcloud iam service-accounts list \
        --format='value(email)' \
        --filter=displayName:"$GKE_SERVICE_ACCOUNT_NAME")"
    
  4. Vincule os papéis de gerenciamento de identidade e acesso (IAM, na sigla em inglês) à conta de serviço:

    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
    

Preparar o cluster do GKE

Nesta seção, você iniciará o cluster do GKE, concederá permissões e finalizará a configuração do cluster.

Para demonstrar o conceito deste tutorial, um cluster com um número relativamente baixo de nós pequenos e de uso geral é suficiente. Crie um cluster com um pool de nós (o padrão).

  • No Cloud Shell, crie e inicie um cluster do GKE regional:

    gcloud container clusters create ds-init-tutorial \
        --enable-ip-alias \
        --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"
    

Aplicar configurações de nós usando um DaemonSet

Nesta seção, você vai impedir que as cargas de trabalho sejam executadas em nós antes da conclusão da configuração aplicando um taint ao pool de nós. Em seguida, implante um DaemonSet que faz o seguinte:

  1. Programa pods em nós com taint usando uma tolerância para o taint.
  2. Executa um contêiner init privilegiado que primeiro aplica a configuração do nó usando sysctl e depois remove a restrição do nó usando kubectl. A remoção do taint permite que o nó seja programado para cargas de trabalho.
  3. Programa e executa um contêiner de pausa que permanece inativo e não consome recursos para evitar que o DaemonSet reprograme o pod usado para configuração.

Este tutorial aplica o parâmetro de kernel vm.max_map_count=262144 como uma configuração de exemplo.

  1. Aplique um taint ao pool de nós padrão:

    gcloud container node-pools update default-pool \
      --cluster=ds-init-tutorial \
      --node-taints=node.config.status/stage=configuring:NoSchedule \
      --region=us-central1
    

    Com esse taint, apenas os pods que o toleram, como o pod DaemonSet, podem ser programados nesse pool de nós.

  2. Verifique se a restrição foi aplicada:

    kubectl describe nodes -l cloud.google.com/gke-nodepool=default-pool | grep Taints
    

    O status do nó precisa mostrar node.config.status/stage=configuring:NoSchedule.

  3. Salve o seguinte manifesto como auto-untaint-daemonset.yaml:

    # WARNING: This DaemonSet runs as privileged, which has significant
    # security implications. Only use this on clusters where you have
    # strict controls over what is deployed.
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: node-config-sa
      namespace: default
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: node-patcher-role
    rules:
    - apiGroups: [""]
      resources: ["nodes"]
      # Permissions needed to read and remove a taint from the node.
      verbs: ["get", "patch", "update"]
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: node-config-binding
    subjects:
    - kind: ServiceAccount
      name: node-config-sa
      namespace: default
    roleRef:
      kind: ClusterRole
      name: node-patcher-role
      apiGroup: rbac.authorization.k8s.io
    ---
    apiVersion: apps/v1
    kind: DaemonSet
    metadata:
      name: auto-untaint-daemonset
      labels:
        app: auto-untaint-configurator
    spec:
      selector:
        matchLabels:
          app: auto-untaint-configurator
      updateStrategy:
        type: RollingUpdate
      template:
        metadata:
          labels:
            app: auto-untaint-configurator
        spec:
          serviceAccountName: node-config-sa
          hostPID: true
          # Toleration now matches the taint on your node.
          tolerations:
          - key: "node.config.status/stage"
            operator: "Equal"
            value: "configuring"
            effect: "NoSchedule"
          volumes:
          - name: host-root-fs
            hostPath:
              path: /
          initContainers:
          - name: configure-and-untaint
            image: ubuntu:22.04 # Using a standard container image.
            securityContext:
              privileged: true # Required for chroot and sysctl.
            env:
            - name: NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
            volumeMounts:
            - name: host-root-fs
              mountPath: /host
            command: ["/bin/bash", "-c"]
            args:
            - |
              # Using explicit error checking for each critical command.
    
              # Define the configuration and taint details.
              SYSCTL_PARAM="vm.max_map_count"
              SYSCTL_VALUE="262144"
              TAINT_KEY="node.config.status/stage"
    
              echo "Running configuration on node: ${NODE_NAME}"
    
              # 1. APPLY CONFIGURATION
              echo "--> Applying ${SYSCTL_PARAM}=${SYSCTL_VALUE}..."
              if ! chroot /host sysctl -w "${SYSCTL_PARAM}=${SYSCTL_VALUE}"; then
                echo "ERROR: Failed to apply sysctl parameter." >&2
                exit 1
              fi
              echo "--> Configuration applied successfully."
    
              # 2. UNTAINT THE NODE
              # This command removes the taint from the node this Pod is running on.
              echo "--> Untainting node ${NODE_NAME} by removing taint ${TAINT_KEY}..."
              if ! /host/home/kubernetes/bin/kubectl taint node "${NODE_NAME}" "${TAINT_KEY}:NoSchedule-"; then
                echo "ERROR: Failed to untaint the node." >&2
                exit 1
              fi
              echo "--> Node has been untainted and is now schedulable."
          # The main container is minimal; it just keeps the Pod running.
          containers:
          - name: pause-container
            image: registry.k8s.io/pause:3.9
    

    Esse manifesto cria uma ServiceAccount, um ClusterRole e um ClusterRoleBinding para conceder ao DaemonSet permissão para remover taints de nós. O DaemonSet implanta um pod em cada nó que tolera a restrição configuring:NoSchedule. Esse pod executa um contêiner init privilegiado que aplica a configuração sysctl (vm.max_map_count=262144) e remove a restrição do nó, o que torna o nó programável. Um contêiner de pausa é iniciado para manter o pod em execução.

    O contêiner de inicialização é executado no modo privilegiado, o que tem implicações de segurança. Para mais detalhes, consulte Compensações e restrições de segurança do DaemonSet privilegiado.

  4. Aplique o manifesto:

    kubectl apply -f auto-untaint-daemonset.yaml
    
  5. Verifique se os pods do DaemonSet foram criados e aguarde até que eles alcancem o estado Running:

    kubectl get pods -l app=auto-untaint-configurator -o wide
    

    O estado Running indica que o contêiner de inicialização foi concluído. Anote o nome do pod para usá-lo na verificação da inicialização na próxima seção.

Validar e verificar o procedimento de inicialização

Depois que a configuração do nó for concluída, verifique os resultados conferindo os registros.

  1. Verifique os registros do contêiner de inicialização de um dos pods para conferir a saída:

    kubectl logs POD_NAME -c configure-and-untaint
    

    Substitua POD_NAME pelo nome do pod.

    Você vai ver uma saída indicando a configuração bem-sucedida e a remoção da restrição do nó.

  2. Verifique se a restrição foi removida:

    kubectl describe nodes -l cloud.google.com/gke-nodepool=default-pool | grep Taints
    

    O status do nó deve mostrar Taints: <none> ou mostrar restrições com a chave node.config.status/stage.

Limpar

Para evitar cobranças na sua conta do Google Cloud pelos recursos usados neste tutorial, exclua o projeto criado para ele. Se você criou um projeto dedicado para este tutorial, é possível excluí-lo totalmente. Se você usou um projeto existente, mas não quer excluí-lo, siga as etapas a seguir para limpar o projeto.

Limpar o projeto

Para limpar um projeto sem excluí-lo, remova os recursos criados neste tutorial.

  1. No Cloud Shell, exclua o cluster do GKE:

    gcloud container clusters delete ds-init-tutorial --quiet --region us-central1
    
  2. Exclua a conta de serviço:

    gcloud iam service-accounts delete "$GKE_SERVICE_ACCOUNT_EMAIL" --quiet
    

Excluir o projeto

A maneira mais fácil de eliminar o faturamento é excluir o projeto criado para o tutorial.

  1. No console Google Cloud , acesse a página Gerenciar recursos.

    Acessar "Gerenciar recursos"

  2. Na lista de projetos, selecione o projeto que você quer excluir e clique em Excluir .
  3. Na caixa de diálogo, digite o ID do projeto e clique em Encerrar para excluí-lo.

A seguir