Isolar as cargas de trabalho em pools de nós dedicados

Nesta página, mostramos como isolar suas cargas de trabalho de contêiner em pools de nós dedicados no Google Distributed Cloud (GDC) isolados por air-gap para oferecer mais controle dos seus pods. O isolamento da carga de trabalho oferece alguns benefícios, como:

  • Risco reduzido de ataques de escalonamento de privilégios no cluster do Kubernetes.
  • Mais controle sobre pods que exigem recursos extras.

Nesses casos, considere isolar as cargas de trabalho de contêiner para ter mais controle e otimização.

Por que devo isolar minhas cargas de trabalho?

Isolar as cargas de trabalho em pools de nós dedicados não é obrigatório, mas pode ser uma ação prudente para evitar possíveis problemas. No entanto, gerenciar pools de nós dedicados exige mais supervisão e muitas vezes é desnecessário.

Os clusters do Kubernetes usam cargas de trabalho privilegiadas gerenciadas pelo GDC para ativar recursos e funcionalidades específicas do cluster, como a coleta de métricas. Essas cargas de trabalho recebem permissões especiais para serem executadas corretamente no cluster.

As cargas de trabalho que você implanta nos nós podem ser comprometidas por uma entidade maliciosa. A execução dessas cargas de trabalho com as cargas de trabalho privilegiadas gerenciadas pelo GDC significa que um invasor que viola um contêiner comprometido pode usar as credenciais da carga de trabalho privilegiada no nó para escalonar privilégios no cluster.

Os pools de nós dedicados também são úteis quando é necessário agendar pods que exigem mais recursos que outros, como mais memória ou mais espaço em disco local.

É possível usar os seguintes mecanismos para programar suas cargas de trabalho em um pool de nós dedicado:

Um taint de nó informa ao cluster do Kubernetes para evitar a programação de cargas de trabalho sem uma tolerância correspondente, como cargas de trabalho gerenciadas pelo GDC, nesses nós. A afinidade de nó nas cargas de trabalho instrui o cluster a programar os pods nos nós dedicados.

Limitações do isolamento de nós

  • Os invasores ainda podem iniciar ataques de negação de serviço (DoS) no nó comprometido.

  • Os nós comprometidos ainda podem ler muitos recursos, incluindo todos os pods e namespaces no cluster.

  • Os nós comprometidos podem acessar secrets e credenciais usadas por todos os pods em execução nesse nó.

  • O uso de um pool de nós separado para isolar as cargas de trabalho pode afetar a economia, o escalonamento automático e a utilização de recursos.

  • Os nós comprometidos ainda podem ignorar as políticas de saída de rede.

  • Algumas cargas de trabalho gerenciadas pelo GDC precisam ser executadas em todos os nós do cluster e são configuradas para tolerar todos os taints.

  • Se você implantar recursos DaemonSet que tenham permissões elevadas e possam tolerar qualquer taint, esses pods poderão ser um caminho para o escalonamento de privilégios de um nó comprometido.

Como funciona o isolamento de nós

Para implementar o isolamento de nós para as cargas de trabalho, faça isto:

  1. Atribua um taint e um identificador a um pool de nós para as cargas de trabalho.

  2. Atualize as cargas de trabalho com a tolerância e a regra de afinidade de nó correspondente.

Este guia pressupõe que você começará com um pool de nós no cluster. O uso da afinidade de nó, além dos taints de nó, não é obrigatório, mas isso é recomendável porque você se beneficia de um maior controle sobre a programação.

Antes de começar

Antes de começar, veja se você realizou as seguintes tarefas:

  • Escolha um nome específico para o taint e o identificador do nó que você quer usar nos pools de nós dedicados. Por exemplo, workloadType=untrusted

  • Se necessário, peça ao administrador do IAM da organização para conceder a você o papel de desenvolvedor de cluster de usuário (user-cluster-developer), que não está vinculado a um namespace.

Atribuir um taint e um identificador a um novo pool de nós

Quando você aplica um taint ou um identificador a um novo pool de nós, todos os nós, incluindo os adicionados posteriormente, recebem automaticamente os taints e identificadores especificados.

Para adicionar um taint e um rótulo a um novo pool de nós, siga estas etapas:

  1. Edite diretamente a seção nodePools do recurso personalizado Cluster ao criar o pool de nós:

    nodePools:
      ...
      - machineTypeName: n2-standard-2-gdc
        name: nodepool-1
        nodeCount: 3
        taints: TAINT_KEY=TAINT_VALUE:TAINT_EFFECT
        labels: LABEL_KEY=LABEL_VALUE
    

    Substitua:

    • TAINT_KEY=TAINT_VALUE: um par de chave-valor associado a um TAINT_EFFECT de programação. Exemplo: workloadType=untrusted
    • TAINT_EFFECT: um dos seguintes valores de efeito:
      • NoSchedule: pods que não toleram esse taint não são programados no nó. Os pods atuais não são removidos do nó.
      • PreferNoSchedule: o Kubernetes evita a programação de pods que não toleram esse taint no nó.
      • NoExecute: o pod é removido do nó quando já está em execução nele e não é programado no nó quando ainda não está em execução nele.
    • LABEL_KEY=LABEL_VALUE: os pares de chave-valor para os rótulos de nó, que correspondem aos seletores especificados nos manifestos das cargas de trabalho.
  2. Aplique o recurso Cluster para criar o novo pool de nós:

    kubectl apply -f cluster.yaml \
        --kubeconfig MANAGEMENT_API_SERVER
    

    Substitua MANAGEMENT_API_SERVER pelo caminho do kubeconfig do servidor da API zonal em que o cluster do Kubernetes está hospedado. Se você ainda não gerou um arquivo kubeconfig para o servidor da API na zona de destino, consulte Fazer login para mais detalhes.

Atribuir um taint e um identificador a um pool de nós atual

Para aplicar uma restrição ou um rótulo a um pool de nós, é necessário aplicar as mudanças a cada nó. Não é possível atualizar dinamicamente as configurações do pool de nós.

Para adicionar um taint e um rótulo a um pool de nós atual, siga estas etapas:

  1. Liste os nós no pool de nós dedicado:

    kubectl get node --kubeconfig KUBERNETES_CLUSTER_KUBECONFIG \
        -l baremetal.cluster.gke.io/node-pool=NODE_POOL_NAME
    

    Substitua as seguintes variáveis:

    • KUBERNETES_CLUSTER_KUBECONFIG: o caminho kubeconfig do cluster do Kubernetes.
    • NODE_POOL_NAME: o nome do pool de nós dedicados.

    Anote o ID de cada nó no pool de nós da saída.

  2. Para cada nó no pool de nós, aplique as restrições:

    kubectl taint nodes NODE_ID \
        TAINT_KEY=TAINT_VALUE:TAINT_EFFECT \
        --kubeconfig KUBERNETES_CLUSTER_KUBECONFIG
    

    Substitua as seguintes variáveis:

    • NODE_ID: o ID do nó de trabalho no pool de nós dedicado.
    • TAINT_KEY=TAINT_VALUE: um par de chave-valor associado a um TAINT_EFFECT de programação. Por exemplo, workloadType=untrusted.
    • TAINT_EFFECT: um dos seguintes valores de efeito:
      • NoSchedule: pods que não toleram esse taint não são programados no nó. Os pods atuais não são removidos do nó.
      • PreferNoSchedule: o Kubernetes evita a programação de pods que não toleram esse taint no nó.
      • NoExecute: o pod é removido do nó quando já está em execução nele e não é programado no nó quando ainda não está em execução nele.
    • KUBERNETES_CLUSTER_KUBECONFIG: o caminho kubeconfig do cluster do Kubernetes.
  3. Para cada nó no pool de nós, aplique os rótulos que correspondem aos seletores que você vai definir nas cargas de trabalho de contêiner:

    kubectl label NODE_ID \
        LABEL_KEY:LABEL_VALUE \
        --kubeconfig KUBERNETES_CLUSTER_KUBECONFIG
    

    Substitua as seguintes variáveis:

    • NODE_ID: o ID do nó de trabalho no pool de nós dedicado.
    • LABEL_KEY:LABEL_VALUE: os pares de chave-valor para os rótulos de nó, que correspondem aos seletores especificados nos manifestos das cargas de trabalho.
    • KUBERNETES_CLUSTER_KUBECONFIG: o caminho kubeconfig do cluster do Kubernetes.

Adicionar uma tolerância e uma regra de afinidade de nó

Depois que você atribui um taint ao pool de nós dedicado, nenhuma carga de trabalho poderá ser programada nele, a menos que tenha uma tolerância correspondente ao taint adicionado. Adicione a tolerância à especificação das cargas de trabalho para permitir que esses pods sejam programados no pool de nós com taint.

Se você atribuiu um rótulo ao pool de nós dedicado, também é possível adicionar uma regra de afinidade de nó para instruir o GDC a programar apenas as cargas de trabalho nesse pool de nós.

Para configurar sua carga de trabalho de contêiner para ser executada no pool de nós dedicado, siga estas etapas:

  1. Adicione as seções a seguir à seção .spec.template.spec da carga de trabalho do contêiner:

    kind: Deployment
    apiVersion: apps/v1
        ...
        spec:
        ...
          template:
            spec:
              tolerations:
              - key: TAINT_KEY
                operator: Equal
                value: TAINT_VALUE
                effect: TAINT_EFFECT
              affinity:
                nodeAffinity:
                  requiredDuringSchedulingIgnoredDuringExecution:
                    nodeSelectorTerms:
                    - matchExpressions:
                      - key: LABEL_KEY
                        operator: In
                        values:
                        - "LABEL_VALUE"
              ...
    

    Substitua:

    • TAINT_KEY: a chave do taint que você aplicou ao pool de nós dedicado.
    • TAINT_VALUE: o valor do taint que você aplicou ao pool de nós dedicado.
    • TAINT_EFFECT: um dos seguintes valores de efeito:
      • NoSchedule: pods que não toleram esse taint não são programados no nó. Os pods atuais não são removidos do nó.
      • PreferNoSchedule: o Kubernetes evita a programação de pods que não toleram esse taint no nó.
      • NoExecute: o pod é removido do nó quando já está em execução nele e não é programado no nó quando ainda não está em execução nele.
    • LABEL_KEY: a chave do rótulo do nó que você aplicou ao pool de nós dedicado.
    • LABEL_VALUE: o valor do rótulo do nó que você aplicou ao pool de nós dedicado.

    Por exemplo, o recurso Deployment a seguir adiciona uma tolerância ao taint workloadType=untrusted:NoExecute e uma regra de afinidade de nó para o identificador de nó workloadType=untrusted:

    kind: Deployment
    apiVersion: apps/v1
    metadata:
      name: my-app
      namespace: default
      labels:
        app: my-app
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: my-app
      template:
        metadata:
          labels:
            app: my-app
        spec:
          tolerations:
          - key: workloadType
            operator: Equal
            value: untrusted
            effect: NoExecute
          affinity:
            nodeAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
                nodeSelectorTerms:
                - matchExpressions:
                  - key: workloadType
                    operator: In
                    values:
                    - "untrusted"
          containers:
          - name: my-app
            image: harbor-1.org-1.zone1.google.gdc.test/harborproject/my-app
            ports:
            - containerPort: 80
          imagePullSecrets:
          - name: SECRET
    
  2. Atualize a implantação:

    kubectl apply -f deployment.yaml -n NAMESPACE \
        --kubeconfig KUBERNETES_CLUSTER_KUBECONFIG
    

    Substitua as seguintes variáveis:

    • NAMESPACE: o namespace do projeto da sua carga de trabalho de contêiner.
    • KUBERNETES_CLUSTER_KUBECONFIG: o caminho kubeconfig do cluster do Kubernetes.

O GDC recria os pods afetados. A regra de afinidade de nó força os pods no pool de nós dedicado que você criou. A tolerância permite que apenas esses pods sejam posicionados nos nós.

Verificar se a separação funciona

Para verificar se a programação funciona corretamente, execute o comando a seguir e verifique se as cargas de trabalho estão no pool de nós dedicado:

kubectl get pods -o=wide -n NAMESPACE \
    --kubeconfig KUBERNETES_CLUSTER_KUBECONFIG

Conselhos e práticas recomendadas

Após configurar o isolamento de nós, recomendamos que você faça isto:

  • Ao criar novos pools de nós, impeça que a maioria das cargas de trabalho gerenciadas pelo GDC seja executada nesses nós adicionando o próprio taint a esses pools de nós.
  • Sempre que você implantar novas cargas de trabalho no cluster, como ao instalar ferramentas de terceiros, faça a auditoria das permissões exigidas pelos pods. Sempre que possível, evite implantar cargas de trabalho que usam permissões elevadas para nós compartilhados.