Como otimizar o uso de recursos em um cluster de vários locatários do GKE usando o provisionamento automático de nós

Neste tutorial, mostramos como usar o provisionamento automático de nós para escalonar um multilocatário. Google Kubernetes Engine (GKE) cluster e como usar Identidade da carga de trabalho para controlar o acesso de locatários a recursos como os buckets do Cloud Storage. Este guia é destinado a desenvolvedores e arquitetos. é necessário ter conhecimento básico do Kubernetes e do GKE. Se você precisar de uma introdução, consulte a Visão geral do GKE.

Muitas vezes, a multilocação de cluster é implementada para reduzir custos ou padronizar operações entre locatários. Para perceber completamente a economia de custos, dimensione seu cluster para que os recursos do cluster sejam usados de maneira eficiente. É preciso minimizar o desperdício de recursos quando o cluster é escalonado automaticamente, garantindo que os nós do cluster adicionados tenham um tamanho adequado.

Neste tutorial, você usará o provisionamento automático de nós para escalonar o cluster. O provisionamento automático de nós pode ajudar a otimizar o uso de recursos do cluster e, assim, controlar os custos, adicionando nós de cluster mais adequados às cargas de trabalho pendentes.

Objetivos

  • Crie um cluster do GKE com o provisionamento automático de nós e a identidade da carga de trabalho ativados.
  • Configurar o cluster para multilocação.
  • Enviar jobs para o cluster para demonstrar como o provisionamento automático de nós cria e destrói nós de tamanhos otimizados.
  • Use taints e rótulos para instruir o provisionamento automático de nós a fim de criar pools de nós dedicados para cada locatário.
  • Use a identidade da carga de trabalho para controlar o acesso a recursos específicos do locatário, como intervalos do Cloud Storage.

Custos

Neste tutorial, usamos 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 ser qualificados para uma avaliação gratuita.

Antes de começar

  1. Faça login na sua conta do Google.

    Se você ainda não tiver uma, inscreva-se.

  2. No Console do Google Cloud, na página do seletor de projetos, selecione ou crie um projeto do Google Cloud.

    Acessar a página do seletor de projetos

  3. Verifique se o faturamento está ativado para seu projeto na nuvem. Saiba como confirmar se o faturamento está ativado para o projeto.

  4. No Console do Cloud, ative o Cloud Shell.

    Ativar o Cloud Shell

    Na parte inferior do Console do Cloud, uma sessão do Cloud Shell é iniciada e exibe um prompt de linha de comando. O Cloud Shell é um ambiente com o SDK do Cloud pré-instalado com a ferramenta de linha de comando gcloud e os valores já definidos para seu projeto atual. A inicialização da sessão pode levar alguns segundos.

  5. No Cloud Shell, ative as APIs do GKE e da API Cloud Build:
    gcloud services enable container.googleapis.com \
        cloudbuild.googleapis.com
    

    Esta operação leva alguns minutos para ser concluída:

Como preparar o ambiente

Nesta seção, você vê o código necessário para este tutorial e configura o ambiente com os valores usados no tutorial.

  1. No Cloud Shell, defina as variáveis de ambiente que você usará neste tutorial:

    export PROJECT_ID=$(gcloud config get-value project)
    
  2. Clone o repositório do GitHub que contém o código deste tutorial:

    git clone https://github.com/GoogleCloudPlatform/solutions-gke-autoprovisioning
    
  3. Altere para o diretório do repositório:

    cd solutions-gke-autoprovisioning
    
  4. Atualize o arquivo de configuração do job YAML do Kubernetes com o ID do projeto do Google:

    sed -i "s/MY_PROJECT/$PROJECT_ID/" manifests/bases/job/base-job.yaml
    
  5. Envie um job do Cloud Build para criar uma imagem do contêiner:

    gcloud builds submit pi/ --tag gcr.io/$PROJECT_ID/generate-pi
    

    A imagem é um programa Go que gera uma aproximação de pi. Use essa imagem de contêiner mais tarde.

    O Cloud Build exporta a imagem para o Container Registry do projeto.

crie um cluster do GKE;

Nesta seção, você cria um cluster do GKE com provisionamento automático de nós e identidade de carga de trabalho ativados. Observe os seguintes detalhes do processo de criação do cluster:

  • Especifique limites de CPU e memória para o cluster. O provisionamento automático de nós respeita esses limites quando adiciona ou remove nós do cluster. Para mais informações, consulte Como ativar o provisionamento automático de nós na documentação do GKE.
  • Você especifica a conta de serviço padrão e os escopos usados pelos nós nos pools de nós provisionados automaticamente. Usando essas configurações, é possível controlar as permissões de acesso do nó provisionado. Para mais informações, consulte Como definir padrões de identidade para nós provisionados automaticamente na documentação do GKE.
  • Você define um perfil de escalonamento automático que prioriza a utilização. Esse perfil instrui o autoescalador de cluster a reduzir rapidamente o cluster para minimizar os recursos não utilizados. Isso ajuda na eficiência do recurso para cargas de trabalho em lote ou no trabalho. A configuração se aplica a todos os pools de nós no cluster.
  • Você ativa a Identidade da carga de trabalho especificando o pool de carga de trabalho.

Para criar o cluster:

  1. Crie uma conta de serviço:

    gcloud iam service-accounts create nap-sa
    

    Essa conta de serviço é usada pelos nós provisionados automaticamente.

  2. Conceda as novas permissões da conta de serviço para extrair imagens do bucket do Cloud Storage usado pelo Container Registry:

    gsutil iam ch \
        serviceAccount:nap-sa@$PROJECT_ID.iam.gserviceaccount.com:objectViewer \
        gs://artifacts.$PROJECT_ID.appspot.com
    
  3. Crie um cluster do GKE que tenha o provisionamento automático de nós e a identidade da carga de trabalho ativados:

    gcloud beta container clusters create multitenant \
        --release-channel=regular \
        --zone=us-central1-c \
        --num-nodes=2 \
        --machine-type=n1-standard-2 \
        --workload-pool=${PROJECT_ID}.svc.id.goog \
        --autoscaling-profile=optimize-utilization \
        --enable-autoprovisioning \
        --autoprovisioning-service-account=nap-sa@${PROJECT_ID}.iam.gserviceaccount.com \
        --autoprovisioning-scopes=\
    https://www.googleapis.com/auth/devstorage.read_write,\
    https://www.googleapis.com/auth/cloud-platform \
        --min-cpu 1 \
        --min-memory 1 \
        --max-cpu 50 \
        --max-memory 256 \
        --enable-network-policy \
        --enable-ip-alias
    
  4. Defina o nome do cluster padrão e a zona de computação:

    gcloud config set container/cluster multitenant
    gcloud config set compute/zone us-central1-c
    

Como configurar o cluster para multilocação

Ao operar um aplicativo de software como serviço (SaaS) multilocatário, você normalmente deve separar seus locatários. Separar os locatários pode ajudar a minimizar os danos de um locatário comprometido. Ele também pode ajudar você a alocar recursos de cluster uniformemente entre os locatários e rastrear quantos recursos cada locatário está consumindo. O Kubernetes não garante o isolamento perfeito entre locatários, mas oferece recursos que podem ser suficientes para casos de uso específicos. Para mais informações sobre os recursos de multilocação do GKE, consulte os guias de visão geral e práticas recomendadas na documentação do GKE.

No aplicativo de exemplo, você cria dois locatários, tenant1 e tenant2. Cada locatário e os recursos do Kubernetes são separados no próprio namespace. Crie uma política de rede simples que imponha o isolamento de locatários impedindo a comunicação de outros namespaces. Posteriormente, você usará os campos taints de nó e nodeSelector para evitar que pods de locatários diferentes sejam programados no mesmo horário. nó. É possível fornecer mais grau de separação, executando cargas de trabalho de locatários em nós dedicados.

Use o Kustomize para gerenciar os manifestos do Kubernetes enviados ao cluster. O Kustomize permite combinar e personalizar arquivos YAML para várias finalidades.

  1. Crie um namespace, uma conta de serviço e um recurso de política de rede para tenant1:

    kubectl apply -k manifests/setup/tenant1
    

    A saída será assim:

    namespace/tenant1-ns created
    serviceaccount/tenant1-ksa created
    networkpolicy.networking.k8s.io/tenant1-deny-from-other-namespaces created
    
  2. Crie os recursos do cluster para tenant2:

    kubectl apply -k manifests/setup/tenant2
    

Como verificar o comportamento do provisionamento automático de nós

Um cluster do GKE consiste em um ou mais pools de nós. Todos os nós em um pool têm o mesmo tipo de máquina, o que significa que têm a mesma quantidade de CPU e memória. Se as demandas de recursos da carga de trabalho forem variáveis, você poderá se beneficiar de ter vários pools de nós com diferentes tipos de máquinas no cluster. Dessa forma, o autoescalador de cluster pode adicionar nós do tipo mais adequado, o que pode melhorar a eficiência do recurso e, portanto, reduzir custos. No entanto, manter muitos pools de nós aumenta a sobrecarga de gerenciamento. Poderá também ser prático em um cluster de vários locatários se você quiser executar cargas de trabalho de locatário em pools de nós dedicados.

Em vez disso, use o provisionamento automático de nós para ampliar o autoescalador do cluster. Quando o provisionamento automático de nós está ativado, o escalonador automático de cluster pode criar novos pools de nós automaticamente com base nas especificações dos pods pendentes. Como resultado, o autoescalador de clusters pode criar nós do tipo mais adequado, mas você não precisa criar ou gerenciar os pools de nós por conta própria. Com o provisionamento automático de nós, o cluster pode escalonar de maneira eficiente sem provisionamento em excesso, o que ajuda a reduzir os custos.

Além disso, se os pods pendentes tiverem restrições de separação de carga de trabalho, o provisionamento automático de nós poderá criar nós que atendam às restrições. Dessa maneira, use o provisionamento automático de nós para criar automaticamente pools de nós que serão usados por apenas um locatário.

Nesta seção, você envia vários jobs ao cluster para verificar o comportamento do provisionamento automático de nós. Os jobs usam a imagem generate-pi que você criou anteriormente.

Envie um job simples

Primeiro, você envia um job simples para o cluster. O job não especifica restrições específicas do locatário. Há capacidade suficiente no cluster para lidar com as solicitações de CPU e memória do job. Portanto, você espera que o job seja programado em um dos nós atuais no pool de nós padrão. Nenhum nó adicional é provisionado.

  1. Liste os pools de nós no cluster:

    gcloud container node-pools list
    

    Você verá um único pool padrão.

  2. Imprima a configuração do job no console:

    kubectl kustomize manifests/jobs/simple-job/
    

    A saída será assim:

    apiVersion: batch/v1
    kind: Job
    metadata:
    name: pi-job
    spec:
    ...
    

    A configuração não especifica taints ou seletores de nó.

  3. Envie o job:

    kubectl apply -k manifests/jobs/simple-job/
    
  4. Assista aos pools de nós no cluster:

    watch -n 5 gcloud container node-pools list
    

    Você ainda verá um único pool padrão. Nenhum novo pool de nós é criado.

  5. Após cerca de 30 segundos, pressione Control+C para parar de assistir aos pools de nós.

  6. Assista aos nós do cluster:

    kubectl get nodes -w
    

    Nenhum nó novo está sendo criado.

  7. Depois de assistir por 1 minuto, pressione Control+C para parar de assistir.

  8. Liste os jobs no cluster:

    kubectl get jobs --all-namespaces
    

    A saída será assim:

    NAMESPACE   NAME     COMPLETIONS   DURATION   AGE
    default     pi-job   1/1           14s        21m
    

    O valor 1/1 na coluna Completions indica que um job de um total de um job foi concluído.

Enviar um job com restrições específicas de locatário

Nesta seção, você envia outro job para confirmar que o provisionamento automático de nós usa restrições da separação de cargas de trabalho. A configuração do job inclui um seletor de nós específico do locatário e uma tolerância específica do locatário. O job pode ser programado apenas em um nó que tenha rótulos que correspondam aos pares de chave-valor do seletor. Uma tolerância funciona em conjunto com taints de node, que também limitam quais jobs podem ser programados em um node. Uma prática recomendada com o provisionamento automático de nós é incluir um seletor de nó e uma tolerância para a separação da carga de trabalho.

Esse job não pode ser programado no pool de nós padrão, porque ele não tem nós que satisfaçam a restrição do seletor. Portanto, o provisionamento automático de nós cria um novo pool de nós com rótulos de nós que satisfazem o requisito do seletor. O provisionamento automático de nós também adiciona um taint específico de locatário aos nós que correspondem à tolerância na configuração do job. Somente os pods que têm uma tolerância correspondente podem ser programados nos nós do pool, o que permite separar ainda mais as cargas de trabalho do locatário.

  1. Liste os pools de nós no cluster:

    gcloud container node-pools list
    

    Você verá um único pool padrão.

  2. Imprima a configuração da tarefa no console:

    kubectl kustomize manifests/jobs/one-tenant/
    

    A configuração inclui um requisito de seletor de nós específico do locatário e uma tolerância. A saída será assim:

    apiVersion: batch/v1
    kind: Job
    metadata:
    name: tenant1-pi-job
    spec:
    ...
    
  3. Envie o job:

    kubectl apply -k manifests/jobs/one-tenant/
    
  4. Observe os pools de nós no cluster:

    watch -n 5 gcloud container node-pools list
    

    Após algum tempo, você verá um novo pool de nós. A saída será assim:

    NAME                            MACHINE_TYPE       DISK_SIZE_GB
    default-pool                    n1-standard-2      100
    nap-n1-standard-1-15jwludl      n1-standard-1      100
    

    O nome do pool de nós é prefixado com nap-, o que indica que ele foi criado pelo provisionamento automático de nós. O nome do pool de nós também inclui o tipo de máquina dos nós no pool, por exemplo, n1-standard-1.

  5. Assista aos nós do cluster:

    kubectl get nodes -w
    

    Após cerca de um minuto, você verá um novo nó na lista. O nome do nó inclui o nome do pool de nós nap-. O novo nó inicialmente tem um status Not Ready. Após algum tempo, o status do novo nó muda para Ready, o que significa que o nó agora pode aceitar trabalhos pendentes.

  6. Para parar de assistir aos nós, pressione Control+C.

  7. Liste os taints de nó:

    kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
    

    Você vê que o novo nó tem um taint NoSchedule para o par de chave-valor tenant: tenant1. Portanto, apenas pods que tenham uma tolerância correspondente a tenant: tenant1 podem ser programados no nó.

  8. Assista aos jobs no cluster:

    kubectl get jobs -w --all-namespaces
    

    Após algum tempo, você vê que tenant1-pi-job tem uma conclusão de 1/1, que indica que ele foi concluído com sucesso.

  9. Para parar de assistir ao job, pressione Control+C.

  10. Observe os pools de nós no cluster:

    watch -n 5 gcloud container node-pools list
    

    Após algum tempo, você vê que o pool de nap- é excluído e o cluster novamente tem apenas o pool de nós padrão. O provisionamento automático de nós excluiu o pool de nós nap- porque não há mais trabalho pendente que corresponda às restrições do pool.

  11. Para parar de assistir aos pools de nós, pressione Control+C.

Enviar dois jobs maiores com restrições de locatário

Nesta seção, você envia dois jobs com restrições específicas de locatário e também aumenta as solicitações de recursos de cada job. Mais uma vez, esses jobs não podem ser programados no pool de nós padrão devido às restrições do seletor de nós. Como cada job tem sua própria restrição de seletor, o provisionamento automático de nós cria dois novos pools de nós. Dessa forma, é possível usar o provisionamento automático de nós para manter os jobs de locatário separados. Como os jobs têm um número maior de solicitações de recursos em comparação com o job anterior, o provisionamento automático de nós cria pools de nós com tipos de máquinas maiores do que na última vez.

  1. Liste os pools de nós no cluster:

    gcloud container node-pools list
    

    Você verá um único pool padrão.

  2. Imprima a configuração combinada:

    kubectl kustomize manifests/jobs/two-tenants/
    

    A configuração inclui dois jobs separados, cada um com um seletor de nós e tolerância específicos do locatário, e com mais solicitações de recursos.

    A saída será assim:

    apiVersion: batch/v1
    kind: Job
    metadata:
    name: tenant1-larger-pi-job
    spec:
    ...
    
  3. Envie os jobs:

    kubectl apply -k manifests/jobs/two-tenants/
    
  4. Observe os pools de nós no cluster:

    watch -n 5 gcloud container node-pools list
    

    Depois de algum tempo, você verá dois pools de nós adicionais. A saída será assim:

    NAME                            MACHINE_TYPE       DISK_SIZE_GB
    default-pool                    n1-standard-2      100
    nap-n1-standard-2-6jxjqobt      n1-standard-2      100
    nap-n1-standard-2-z3s06luj      n1-standard-2      100
    

    Os nomes do pool de nós são prefixados com nap-, o que indica que eles foram criados pelo provisionamento automático de nós. Os nomes de pool de nós também incluem o tipo de máquina dos nós no pool, por exemplo, n1-standard-2.

  5. Para parar de assistir aos nós, pressione Control+C.

  6. Assista aos nós do cluster:

    kubectl get nodes -w
    

    Após cerca de um minuto, dois novos nós serão exibidos na lista. Os nomes do nó incluem o nome do pool de nós nap- associado. Os novos nós inicialmente têm um status Not Ready. Após algum tempo, o status dos novos nós muda para Ready, o que significa que os nós agora podem aceitar trabalhos pendentes.

  7. Para parar de assistir aos nós, pressione Control+C.

  8. Liste os taints de nó:

    kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
    

    Você vê que os novos nós têm taints NoSchedule, um com par de chave-valor tenant: tenant1 e outro com tenant: tenant2. Somente os pods que têm tolerâncias de locatário correspondentes podem ser programados nos nós.

  9. Observe os jobs no cluster:

    kubectl get jobs -w --all-namespaces
    

    Após algum tempo, você vê que tenant1-larger-pi-job e tenant2-larger-pi-job mudam para ter a conclusão de 1/1 cada, o que indica que os jobs foram concluídos com êxito.

  10. Para parar de assistir aos jobs, pressione Control+C.

  11. Veja os pools de nós no cluster:

    watch -n 5 gcloud container node-pools list
    

    Após algum tempo, você verá que os dois pools nap- são excluídos e o cluster novamente tem apenas um único pool de nós padrão. O provisionamento automático de nós excluiu os pools de nós nap- porque não há mais trabalho pendente que corresponda às restrições dos pools.

  12. Para parar de assistir aos pools de nós, pressione Control+C.

Como controlar o acesso aos recursos do Google Cloud

Além de manter a separação de locatários dentro do cluster, você normalmente quer controlar o acesso de locatários aos recursos do Google Cloud, como buckets do Cloud Storage ou tópicos do Pub/Sub. Por exemplo, cada locatário pode exigir um bucket do Cloud Storage que não pode ser acessado por outros locatários.

Usando a Identidade da carga de trabalho, é possível criar um mapeamento entre contas de serviço do Kubernetes e contas de serviço do Google Cloud. Em seguida, atribua os papéis apropriados do gerenciamento de identidade e acesso (IAM, na sigla em inglês) à conta de serviço do Google Cloud. Assim, é possível aplicar o princípio do menor privilégio para que os jobs de locatário possam acessar os recursos atribuídos, mas não podem acessar os recursos que pertencem a outros locatários.

Configurar a identidade da carga de trabalho do GKE

Configure o mapeamento entre sua conta de serviço do Kubernetes e uma conta de serviço do Google Cloud criada por você.

  1. Crie uma conta de serviço do Google Cloud para tenant1:

    gcloud iam service-accounts create tenant1-gsa
    
  2. Conceda à conta de serviço do Kubernetes as permissões tenant1 do IAM para usar a conta de serviço do Google Cloud correspondente para tenant1:

    gcloud iam service-accounts add-iam-policy-binding \
        tenant1-gsa@${PROJECT_ID}.iam.gserviceaccount.com \
        --role roles/iam.workloadIdentityUser \
        --member "serviceAccount:${PROJECT_ID}.svc.id.goog[tenant1-ns/tenant1-ksa]"
    
  3. Preencha o mapeamento entre as contas de serviço anotando a conta do serviço do Kubernetes com a conta de serviço do Google Cloud:

    kubectl annotate serviceaccount tenant1-ksa -n tenant1-ns \
        iam.gke.io/gcp-service-account=tenant1-gsa@${PROJECT_ID}.iam.gserviceaccount.com
    

Enviar um job que grava em um intervalo do Cloud Storage

Nesta seção, você confirma que um job em execução como uma conta de serviço específica do Kubernetes pode usar as permissões do IAM da conta de serviço mapeada do Google Cloud.

  1. Crie um novo bucket do Cloud Storage para tenant1:

    export BUCKET=tenant1-$PROJECT_ID
    gsutil mb -b on -l us-central1 gs://$BUCKET
    

    Você usa o código do projeto como um sufixo no nome do intervalo para tornar o nome exclusivo.

  2. Atualize o arquivo de configuração do job para usar o intervalo do Cloud Storage:

    sed -i "s/MY_BUCKET/$BUCKET/" \
        manifests/jobs/write-gcs/bucket-write.yaml
    
  3. Conceda permissões à conta de serviço tenant1 para ler e gravar objetos no intervalo:

    gsutil iam ch \
        serviceAccount:tenant1-gsa@$PROJECT_ID.iam.gserviceaccount.com:objectAdmin \
        gs://$BUCKET
    
  4. Imprima a configuração do job:

    kubectl kustomize manifests/jobs/write-gcs/
    

    A saída será assim:

    apiVersion: batch/v1
    kind: Job
    metadata:
    name: tenant1-pi-job-gcs
    spec:
    ...
    

    O novo nome do bucket é transmitido como um argumento para o contêiner generate-pi, e o job especifica a conta de serviço tenant1-ksa do Kubernetes apropriada.

  5. Envie o job:

    kubectl apply -k manifests/jobs/write-gcs/
    

    Como na seção anterior, o provisionamento automático de nós cria um novo pool de nós e um novo nó para executar o job.

  6. Assista ao pod do job:

    kubectl get pods -n tenant1-ns -w
    

    Nesse caso, você assiste ao pod em vez de assistir ao pool de nós. A transição do pod é feita por status diferentes. Depois de alguns minutos, o status muda para Completed. Esse status indica que o job foi concluído.

  7. Para parar de assistir, pressione Control+C.

  8. Confirme se um arquivo foi gravado no intervalo do Cloud Storage:

    gsutil ls -l gs://$BUCKET
    

    Você verá um único arquivo.

  9. Para limpar, exclua o job:

    kubectl delete job tenant1-pi-job-gcs -n tenant1-ns
    

    Você reenviará o job na próxima seção.

Revogar permissões do IAM

Por fim, você confirma que a revogação das permissões do IAM da conta de serviço do Google Cloud impede que a conta de serviço do Kubernetes mapeada acesse o bucket do Cloud Storage.

  1. Revogue as permissões da conta de serviço do Google Cloud para gravar no intervalo do Cloud Storage:

    gsutil iam ch -d \
        serviceAccount:tenant1-gsa@$PROJECT_ID.iam.gserviceaccount.com:objectAdmin \
        gs://$BUCKET
    
  2. Envie o mesmo job que anteriormente:

    kubectl apply -k manifests/jobs/write-gcs/
    
  3. Mais uma vez, observe o status do pod do job:

    kubectl get pods -n tenant1-ns -w
    

    Após alguns minutos, o status muda para Error, o que indica que o job falhou. Esse erro é esperado porque o job está sendo executado como uma conta de serviço do Kubernetes que mapeia para uma conta de serviço do Google Cloud que, por sua vez, não tem mais permissões de gravação para o bucket do Cloud Storage.

  4. Para parar de assistir ao pod, pressione Control+C.

  5. Liste os arquivos no intervalo:

    gsutil ls -l gs://$BUCKET
    

    Você verá um único arquivo no intervalo. um novo arquivo não foi gravado.

Como fazer a limpeza

A maneira mais fácil de eliminar o faturamento é excluir o projeto do Cloud que você criou para o tutorial.

Exclua o projeto

  1. No Console do 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.

Excluir o cluster do GKE

Se você não quiser excluir o projeto, exclua o cluster do GKE:

gcloud container clusters delete multitenant

A seguir