Como executar aplicativos da Web no GKE usando PVMs com custo otimizado

Neste tutorial, mostramos como lidar com preempções enquanto você executa VMs preemptivas (PVMs) no Google Kubernetes Engine (GKE) que está sendo exibido. um aplicativo da Web. As PVMs são instâncias de computação acessíveis e de curta duração adequadas para cargas de trabalho tolerantes a falhas. Eles oferecem os mesmos tipos e opções de máquina que as instâncias de computação comuns e duram até 24 horas.

Este tutorial é destinado a desenvolvedores de aplicativos, arquitetos de sistemas e engenheiros de DevOps que definem, implementam e implantam aplicativos voltados para a Web e querem usar PVMs em implantações de produção. Neste tutorial, você precisa entender os conceitos fundamentais do Kubernetes e vários componentes no balanceamento de carga HTTP(S).

Contexto

Um PVM é limitado a um tempo de execução de 24 horas e recebe um aviso de 30 segundos de encerramento quando a instância está prestes a ser interrompida. Inicialmente, o PVM envia um aviso de preempção para a instância na forma de um sinal ACPI G2 Soft Off (SIGTERM). Depois de 30 segundos, um sinal ACPI G3 Mechanical Off (SIGKILL) é enviado para o sistema operacional da instância. Em seguida, a VM passa a instância para um estado TERMINATED.

As PVMs são uma boa opção para cargas de trabalho distribuídas e tolerantes a falhas que não exigem disponibilidade contínua de uma única instância. Exemplos desse tipo de carga de trabalho incluem codificação de vídeo, renderização de efeitos visuais, análise de dados, simulação e genômica. No entanto, devido a limitações de disponibilidade e possíveis interrupções frequentes resultantes de preempçãos, as PVMs geralmente não são recomendadas para aplicativos da Web e voltados para o usuário.

Neste tutorial, descrevemos como configurar uma implantação que usa uma combinação de PVMs e VMs padrão no GKE para ajudar a disponibilizar tráfego de aplicativos da Web de maneira confiável sem interrupções.

Desafios no uso de PVMs

O maior desafio de usar PVMs na exibição de tráfego voltado ao usuário é garantir que as solicitações dos usuários não sejam interrompidas. Na preempção, você precisa abordar o seguinte:

  • Como você garante a disponibilidade de um aplicativo quando ele estiver sendo executado em PVMs? As PVMs não têm disponibilidade garantida e são explicitamente excluídas dos contratos de nível de serviço do Compute Engine.
  • Como você lida com o encerramento normal do aplicativo de maneira que as afirmações a seguir sejam verdadeiras:
    • O balanceador de carga interrompe solicitações de encaminhamento para os pods em execução em uma instância que está sendo interrompida.
    • As solicitações em andamento são processadas de maneira adequada, elas sejam concluídas ou encerradas.
    • As conexões com os bancos de dados e o aplicativo são fechadas ou esvaziadas antes da instância ser encerrada.
  • Como você lida com solicitações, como transações críticas para os negócios, que podem exigir garantias de tempo de atividade ou que não são tolerantes a falhas?

Considere os desafios na desativação de contêineres que são executados em PVMs. Do ponto de vista da implementação, a maneira mais fácil de escrever a lógica de limpeza quando uma instância é encerrada é por meio de um script de desligamento. No entanto, esses scripts não serão compatíveis se você estiver executando cargas de trabalho em contêineres no GKE.

Como alternativa, você pode usar um gerenciador SIGTERM no seu aplicativo para escrever a lógica de limpeza. Os contêineres também fornecem ganchos de ciclo de vida, como preStop, que é acionado imediatamente antes do contêiner ser encerrado. No Kubernetes, o Kubelet é responsável por executar eventos de ciclo de vida do contêiner. Em um cluster do Kubernetes, o Kubelet é executado na VM e observa as especificações do pod por meio do servidor da API Kubernetes (em inglês).

Ao remover um pod usando uma linha de comando ou uma API, o Kubelet percebe que o pod foi marcado como encerrado e inicia o processo de desligamento. A duração do processo é limitada pelo "período de carência", que é definido como um número definido de segundos após o qual o Kubelet envia um sinal SIGKILL para os contêineres. Como parte do encerramento normal, se algum contêiner em execução no pod tiver definido um hook preStop, o Kubelet executará o gancho dentro do contêiner. Em seguida, o Kubelet aciona um sinal SIGTERM para process-ID 1 dentro de cada contêiner. Se o aplicativo estiver executando um gerenciador SIGTERM, o gerenciador será executado. Quando o gerenciador é concluído, o Kubelet envia um sinal SIGKILL para todos os processos ainda em execução no pod.

Imagine que um nó do Kubernetes está passando por preempção. É preciso ter um mecanismo para identificar o aviso de preempção e iniciar o processo de remoção do pod. Suponha que você esteja executando um programa que detecta um aviso de preempção e remove os pods em execução ao receber um evento. Após a remoção, a sequência de encerramento do pod descrita anteriormente é acionada. No entanto, nesse caso, o nó também está passando por uma desativação, que é processada pelo sistema operacional (SO) do nó. Esse encerramento pode interferir no processamento do ciclo de vida do contêiner do Kubelet, o que significa que o contêiner pode ser encerrado abruptamente, mesmo que ele esteja no meio da execução de um gancho preStop.

Além disso, no ponto de disponibilidade de disponibilidade e gerenciamento de tráfego, a execução do aplicativo voltado para a Web exclusivamente em PVMs também pode causar vários desafios. Antes de usar as PVMs, considere as seguintes perguntas:

  • O que acontece se a maioria das PVMs for interrompida de uma só vez? Como os aplicativos atendem a milhares de solicitações por segundo, como são as failovers sem interrupção?
  • O que acontecerá se a capacidade do PVM não estiver disponível? Como fazer o escalonamento horizontal ou manter um estado estável de implantação do aplicativo nesse caso?

Arquitetura

Para resolver os desafios do uso de PVMs, faça o seguinte:

  • Execute um cluster do GKE com dois pools de nós: um em PVMs de execução e o outro com VMs padrão em execução. Isso permite que você divida o tráfego e tenha um failover ativo para lidar com solicitações novas e em andamento em caso de preempção. Essa abordagem também permite dividir o tráfego entre VMs padrão e PVMs com base nos requisitos de garantias de tempo de atividade e tolerância a falhas. Para mais informações sobre como decidir o tamanho dos pools de nós, consulte o que considerar.
  • Detecte avisos de preempção nas PVMs e remova os pods em execução no nó.
  • Use um gancho preStop ou um gerenciador SIGTERM para executar a lógica de limpeza.
  • Certifique-se de que o Kubelet tenha permissão para lidar com o ciclo de vida do encerramento do pod e não esteja encerrado abruptamente.
  • Liga o nó para que nenhum novo pod seja programado nele enquanto estiver sendo interrompido.

No diagrama a seguir, veja uma visualização de alto nível da arquitetura que você implantará neste tutorial.

Arquitetura de alto nível

Como o diagrama mostra, crie dois pools de nós: default-pool é executado em VMs padrão e pvm-pool é executado em PVMs. O programador padrão do GKE tenta distribuir os pods de maneira uniforme por todas as instâncias nos pools de nós. Por exemplo, se você estiver implantando quatro réplicas e tiver dois nós em execução em cada pool de nós, o programador provisiona um pod em todos os quatro nós. No entanto, ao usar PVMs, convém direcionar mais tráfego para pvm-pool a fim de conseguir uma utilização de PVM maior e, portanto, uma economia. Por exemplo, talvez você queira provisionar três pods em pvm-pool e um pod em default-pool para failover, reduzindo o número de VMs padrão no cluster.

Para alcançar esse tipo de controle sobre a programação, você pode escrever seu próprio programador ou dividir o aplicativo em duas implantações. Cada implantação será fixada a um pool de nós com base nas regras de afinidade de nó. Neste exemplo, você cria duas implantações, web-std e web-pvm, em que web-std é fixado em default-pool e web-pvm é fixado em pvm-pool.

Para nós pvm-pool, é preciso detectar um aviso de preempção e, ao recebê-lo, começar a remover pods. É possível gravar sua própria lógica para ouvir o aviso de preempção e remover os pods em execução no nó. Como alternativa, neste tutorial, é possível criar um agente usando o manipulador de eventos k8s-node-termination-handler.

k8s-node-termination-handler usa um daemonset do Kubernetes para criar um agente em cada instância em pvm-pool. O agente monitora um evento de encerramento de nó usando APIs de metadados do Compute Engine. Sempre que um evento de encerramento é observado, o agente inicia o processo de remoção do pod. Neste exemplo, 20 segundos são alocados como um período de carência para pods regulares e 10 segundos para pods do sistema. Isso significa que, se houver um gancho preStop ou um gerenciador SIGTERM configurado para o pod, ele poderá ser executado até 20 segundos antes de o pod ser encerrado por SIGKILL. O período de carência é um parâmetro configurável em k8s-node-termination-handler. O período de carência total para pods normais e do sistema não pode exceder a notificação de preempção: 30 segundos.

O agente também mantém o nó para impedir que novos pods sejam programados.

Quando você remove pods, o gancho preStop é acionado. Neste exemplo, preStop é configurado para fazer duas tarefas:

  • Falha na verificação de integridade do aplicativo. Isso é feito para sinalizar ao balanceador de carga que ele precisa remover o pod do caminho de exibição da solicitação.
  • Suspender pela duração do período de carência (20 segundos) alocado por k8s-node-termination-handler. O pod permanece ativo por 20 segundos para processar as solicitações em andamento.

Enquanto o gancho preStop estiver em execução, para garantir que o Kubelet não seja encerrado repentinamente pelo SO do nó, você criará um serviço systemd que bloqueia o encerramento. Essa abordagem ajuda a garantir que o Kubelet possa gerenciar o ciclo de vida dos pods durante o encerramento sem a interferência do SO do nó. Use um daemonset do Kubernetes para criar o serviço. Esse daemonset será executado em todas as instâncias em pvm-pool.

Neste exemplo, usamos o Traffic Director para gerenciar o tráfego. É possível usar qualquer solução de proxy, como OSS Envoy, Istio, Nginx ou HAProxy, para gerenciar o tráfego, desde que você siga as diretrizes do subsistema usadas neste exemplo, em que o Traffic Director está configurado. para fazer o seguinte:

  • Ativa o roteamento ponderado das solicitações. Neste exemplo, você cria três réplicas de aplicativos em pvm-pool e uma em default-pool. O Traffic Director está configurado para dividir o tráfego de 75 a 25% entre pvm-pool e default-pool. No caso de preempção, todas as solicitações são automaticamente encaminhadas para default-pool.

    Neste tutorial, apresentamos um exemplo simples de divisão de tráfego. Também é possível definir condições de correspondência em portas de tráfego, campos de cabeçalho, URIs e muito mais para rotear a solicitação para um pool de nós específico. Para mais informações, consulte técnicas avançadas de roteamento de tráfego usando o Traffic Director.

  • No caso de preempção, se a solicitação resultar em um código de status 5xx (por exemplo, devido a um gateway não estar disponível ou por uma conexão upstream expirar), o Traffic Director fará novas tentativas de até três solicitações. vezes.

  • Use um disjuntor para limitar o número máximo de novas tentativas que podem ocorrer em um determinado momento.

  • Use a detecção de outliers para eliminar endpoints não íntegros do caminho de exibição do balanceador de carga.

O Traffic Director usa o modelo de proxy sidecar. Neste modelo, ocorrem os seguintes eventos:

  1. Os clientes enviam solicitações para um balanceador de carga gerenciado pelo Google Cloud.
  2. O balanceador de carga envia o tráfego para um proxy de borda configurado pelo Traffic Director.
  3. O proxy de borda aplica políticas predefinidas (como a distribuição de solicitações entre endpoints diferentes) e políticas de nova tentativa e disjuntores, além de balancear a carga das solicitações para serviços no cluster do GKE.
  4. O proxy secundário intercepta o tráfego e o encaminha para o aplicativo.

Para mais informações, veja como a interceptação e o encaminhamento de tráfego funcionam com o Traffic Director.

Objetivos

  • Implante um cluster do GKE com dois pools de nós usando VMs padrão e PVMs.
  • Implante um aplicativo de amostra.
  • Configure os recursos do cluster para processar a preempção.
  • Configurar o Traffic Director para controlar o tráfego para os serviços do GKE.
  • Simula a preempção de uma PVM.
  • Verifique se os pods estão sendo encerrados corretamente.
  • Verifique se as novas solicitações não são encaminhadas para pods que estão passando por preempção.
  • Verifique se as solicitações em andamento e novas são veiculadas sem interrupções.

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.

Ao concluir este tutorial, exclua os recursos criados para evitar o faturamento contínuo. Para mais informações, consulte Como fazer a limpeza.

Antes de começar

  1. No Console do Google Cloud, acesse a página do seletor de projetos.

    Acessar o seletor de projetos

  2. Selecione ou crie um projeto do Google Cloud.

  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. Ative a API Compute Engine, GKE, Container Analysis, Cloud Build, Container Registry, and Traffic Director.

    Ative a API

  6. Encontre o ID do projeto e configure-o no Cloud Shell. Substitua YOUR_PROJECT_ID pelo ID do projeto:
    gcloud config set project YOUR_PROJECT_ID
    
  7. Exporte as seguintes variáveis de ambiente:
    export PROJECT=$(gcloud config get-value project)
    export CLUSTER=$PROJECT-gke
    export REGION="us-central1"
    export ZONE="us-central1-c"
    export TERMINATION_HANDLER="https://github.com/GoogleCloudPlatform/k8s-node-termination-handler"
    

crie um cluster do GKE;

  1. No Cloud Shell, crie um cluster do GKE que tenha um pool de nós padrão, default-pool, com instâncias padrão e um pool de nós personalizado, pvm-pool, com PVMs:

    gcloud beta container clusters create $CLUSTER \
       --zone=$ZONE \
       --num-nodes="1" \
       --enable-ip-alias \
       --machine-type="n1-standard-4" \
       --scopes=https://www.googleapis.com/auth/cloud-platform
    gcloud beta container node-pools create "pvm-pool" \
       --cluster=$CLUSTER \
       --zone=$ZONE \
       --preemptible \
       --machine-type="n1-standard-4" \
       --scopes=https://www.googleapis.com/auth/cloud-platform \
       --num-nodes="1"
    
  2. Clone o repositório de código solutions-gke-pvm-preemption-handler usado neste tutorial:

    git clone https://github.com/GoogleCloudPlatform/solutions-gke-pvm-preemption-handler && \
    cd solutions-gke-pvm-preemption-handler
    
  3. Crie um daemonset que seja executado em instâncias PVM no cluster do GKE e crie um serviço systemd que bloqueie o encerramento do processo do Kubelet:

    kubectl apply -f daemonset.yaml
    
  4. Consiga o nome da instância do PVM implantada como parte do pool de nós pvm-pool:

    PVM=$(kubectl get no \
        -o=jsonpath='{range .items[*]} \
        {.metadata.name}{"\n"}{end}' | grep pvm)
    
  5. Verifique se o serviço está implantado corretamente:

    1. Crie uma regra de firewall a fim de usar o SSH para se conectar ao PVM por meio do encaminhamento do IAP:

      gcloud compute firewall-rules create allow-ssh-ingress-from-iap \
          --direction=INGRESS \
          --action=allow \
          --rules=tcp:22 \
          --source-ranges=35.235.240.0/20
      
    2. Use SSH para se conectar à PVM:

      gcloud compute ssh $PVM --tunnel-through-iap --zone=$ZONE
      
    3. No terminal PVM, verifique o status do serviço implantado:

      systemctl status delay.service
      

      Você vê o estado do serviço como Active (exited)..

      ...
      delay.service - Delay GKE shutdown
         Loaded: loaded (/etc/systemd/system/delay.service; enabled; vendor preset: disabled)
         Active: active (exited) since Tue 2020-07-21 04:48:33 UTC; 1h 17min ago
      ...
      
    4. Saia do terminal PVM digitando exit.

  6. Deploy k8s-node-termination-handler:

    1. Clone o repositório:

      git clone $TERMINATION_HANDLER
      
    2. Em um editor de texto, abra o arquivo k8s-node-termination-handler/deploy/k8s.yaml e procure a seguinte linha:

      args: ["--logtostderr", "--exclude-pods=$(POD_NAME):$(POD_NAMESPACE)", "-v=10", "--taint=cloud.google.com/impending-node-termination::NoSchedule"]
      
    3. Substitua a linha anterior pela linha a seguir, que aloca um período de carência de 10 segundos para encerrar os pods do sistema. Os 20 segundos restantes no período de carência são alocados automaticamente para pods regulares.

      args: ["--logtostderr", "--exclude-pods=$(POD_NAME):$(POD_NAMESPACE)", "-v=10", "--taint=cloud.google.com/impending-node-termination::NoSchedule", "--system-pod-grace-period=10s"]
      
    4. Implante o gerenciador:

      kubectl apply \
          -f k8s-node-termination-handler/deploy/k8s.yaml \
          -f k8s-node-termination-handler/deploy/rbac.yaml
      
  7. Verifique se o gerenciador de encerramento do nó foi implantado corretamente:

    kubectl get ds node-termination-handler -n kube-system
    

    A resposta será semelhante a:

    NAME                       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
    node-termination-handler   1         1         1       1            1           <none>          101s
    

Como implantar o aplicativo

  1. No Cloud Shell, configure as regras de firewall para verificações de integridade:

    gcloud compute firewall-rules create fw-allow-health-checks \
        --action=ALLOW \
        --direction=INGRESS \
        --source-ranges=35.191.0.0/16,130.211.0.0/22 \
        --rules tcp
    
  2. Implante o aplicativo:

    kubectl apply -f deploy.yaml
    
  3. Verifique se há duas implantações diferentes em execução em default-pool e pvm-pool:

    1. Para default-pool, execute o seguinte:

      kubectl get po -l app=web-std \
          -o=custom-columns=NAME:.metadata.name,Node:.spec.nodeName
      

      A resposta será semelhante a:

      NAME                       Node
      web-std-695b5fb6c4-55gcc   gke-vital-octagon-109612-default-pool-dcdb8fe5-2tc7
      
    2. Para pvm-pool, execute o seguinte:

      kubectl get po -l app=web-pvm \
          -o=custom-columns=NAME:.metadata.name,Node:.spec.nodeName
      

      A resposta será semelhante a esta:

      NAME                       Node
      web-pvm-6f867bfc54-nm6fb   gke-vital-octagon-109612-gke-pvm-pool-664ec4ff-2cgc
      

      Para cada serviço, um NEG autônomo é criado. Ele contém endpoints que são as portas e os endereços IP do pod. Para mais informações e exemplos, consulte Grupos de endpoints de rede autônomos.

  4. Confirme se o NEG independente foi criado:

    gcloud beta compute network-endpoint-groups list
    

    A resposta será semelhante a esta:

    NAME                                       LOCATION       ENDPOINT_TYPE   SIZE
    k8s1-be35f81e-default-web-pvm-80-7c99357f  us-central1-c  GCE_VM_IP_PORT  1
    k8s1-be35f81e-default-web-std-80-f16dfcec  us-central1-c  GCE_VM_IP_PORT  1
    

    Para gerenciar o aplicativo usando o Traffic Director, implante as implantações como grupos de endpoints da rede (NEGs, na sigla em inglês). Conforme discutido na seção "Arquitetura", você usa um proxy secundário para criar as implantações.

  5. Verifique se as implantações foram criadas com um proxy sidecar:

    kubectl get pods -l app=web-std \
        -o jsonpath={.items[*].spec.containers[*].name}
    

    A resposta será semelhante a esta:

    hello-app istio-proxy
    

    Para ver resultados semelhantes para pods em execução em pvm-pool, execute o seguinte:

    kubectl get pods -l app=web-pvm \
        -o jsonpath={.items[*].spec.containers[*].name}
    

Como criar o serviço Traffic Director

O Traffic Director usa uma configuração semelhante à de outros produtos do Cloud Load Balancing. Em outras palavras, é necessário configurar os seguintes componentes para o Traffic Director:

  1. No Cloud Shell, encontre os NEGs que você criou anteriormente e armazene os nomes deles em uma variável:

    1. Para o serviço default-pool, use o seguinte:

      NEG_NAME_STD=$(gcloud beta compute network-endpoint-groups list \
                     | grep web-std | awk '{print $1}')
      
    2. Para o serviço pvm-pool, use o seguinte:

      NEG_NAME_PVM=$(gcloud beta compute network-endpoint-groups list \
                     | grep web-pvm | awk '{print $1}')
      
  2. Crie a verificação de integridade:

      gcloud compute health-checks create http td-gke-health-check \
          --request-path=/health \
          --use-serving-port \
          --healthy-threshold=1 \
          --unhealthy-threshold=2 \
          --check-interval=2s \
          --timeout=2s
    
  3. Substitua os marcadores de posição nos arquivos de manifesto:

    sed -i -e "s/\[PROJECT_ID\]/$PROJECT/g" td-gke-service-config.yaml
    sed -i -e "s/\[ZONE\]/$ZONE/g" td-gke-service-config.yaml
    sed -i -e "s/\[NEG_NAME_STD\]/$NEG_NAME_STD/g" td-gke-service-config.yaml
    sed -i -e "s/\[NEG_NAME_PVM\]/$NEG_NAME_PVM/g" td-gke-service-config.yaml
    sed -i -e "s/\[PROJECT_ID\]/$PROJECT/g" td-urlmap.yaml
    
  4. Crie um serviço do Traffic Director:

    gcloud compute backend-services import td-gke-service \
        --source=td-gke-service-config.yaml --global
    

    O serviço é configurado para dividir o tráfego usando o escalonador de capacidade entre os dois NEGs criados anteriormente. O serviço também é configurado com a detecção de outliers:

    • Para detecção de outliers, defina Erros consecutivos (ou falhas de gateway antes de um host ser removido do serviço) como 2.
    • Para quebras de circuito, defina Máx. de novas tentativas como 3.
  5. Verifique se o serviço Traffic Director está implantado corretamente:

    gcloud compute backend-services get-health td-gke-service --global
    

    A resposta será semelhante a:

    ‐‐‐
    backend: default-pool-service-NEG
    status:
      healthStatus:
      ‐ healthState: HEALTHY
    ...
    ‐‐‐
    backend: pvm-pool-service-NEG
    status:
      healthStatus:
      ‐ healthState: HEALTHY
    ...
    

    Talvez seja necessário aguardar alguns minutos e executar o comando várias vezes, antes que o back-end seja mostrado como HEALTHY.

  6. Crie um mapa de URLs que use o serviço criado:

    gcloud compute url-maps import web-service-urlmap \
        --source=td-urlmap.yaml
    

    O mapa de URLs configura o roteamento de tráfego. Todas as solicitações para o caminho "/*" são redirecionadas para o serviço do Traffic Director que você criou. Além disso, o mapa também configura uma política para repetir solicitações (máximo de três vezes) que resultaram em um código de status 5xx.

  7. Crie o proxy HTTP de destino.

    gcloud compute target-http-proxies create td-gke-proxy \
        --url-map=web-service-urlmap
    
  8. Crie a regra de encaminhamento que usa o endereço IP virtual (VIP) 0.0.0.0:

    gcloud compute forwarding-rules create td-gke-forwarding-rule \
        --global \
        --load-balancing-scheme=INTERNAL_SELF_MANAGED \
        --address=0.0.0.0 \
        --target-http-proxy=td-gke-proxy \
        --ports=80
    

    Neste ponto, os serviços do GKE em default-pool e pvm-pool estão acessíveis no VIP do serviço com balanceamento de carga do Traffic Director.

Criar o balanceador de carga

Nesta seção, você configura um balanceador de carga e um proxy de borda para o tráfego de usuários. O balanceador de carga atua como um gateway para a configuração que você acabou de criar.

  1. No Cloud Shell, crie um proxy de borda gerenciado pelo Traffic Director:

    kubectl apply -f edge-proxy.yaml
    
  2. Verifique se o balanceador de carga está íntegro e pronto para veicular o tráfego:

    kubectl describe ingress gateway-proxy-ingress
    

    A resposta será semelhante a:

    ...
      Host        Path  Backends
      ‐‐‐‐        ‐‐‐‐  ‐‐‐‐‐‐‐‐
      *           *     gateway-proxy-svc:80 (10.20.0.14:80)
    
    Annotations:  ingress.kubernetes.io/backends: {"k8s1-da0dd12b-default-gateway-proxy-svc-80-b3b7b808":"HEALTHY"}
    ...
    

    O back-end precisa estar no estado HEALTHY. Pode levar vários minutos e várias tentativas do comando para que o balanceador de carga esteja pronto para aceitar o tráfego.

  3. Registre o endereço IP para uso posterior:

    IPAddress=$(kubectl get ingress gateway-proxy-ingress \
                -o jsonpath="{.status.loadBalancer.ingress[*].ip}")
    

Como gerar tráfego

Agora que a configuração está completa, é hora de testá-la.

  1. No Cloud Shell, clique em Abrir uma nova guia para iniciar uma nova sessão do Cloud Shell.

  2. No novo shell, defina o ID do projeto:

    gcloud config set project YOUR_PROJECT_ID
    
  3. Instale o Kubetail para observar vários registros de pods de aplicativos juntos:

    sudo apt-get update
    sudo apt-get install kubetail
    kubetail web
    
  4. No shell original, simule o tráfego:

    seq 1 100 | xargs -I{} -n 1 -P 10 curl -I http://$IPAddress
    

    Esse comando gera 100 solicitações, 10 solicitações paralelas por vez.

    No novo shell, você verá registros semelhantes ao seguinte:

    ---
    [web-pvm-6f867bfc54-nm6fb hello-app] Received request at: 2020-07-20 20:26:23.393
    [web-pvm-6f867bfc54-nm6fb hello-app] Received request at: 2020-07-20 20:26:23.399
    [web-std-6f867bfc54-55gcc hello-app] Received request at: 2020-07-20 20:26:24.001
    ...
    

    Verifique se as solicitações são distribuídas entre os pools de nós com base na divisão de 75% a 25% que você configurou anteriormente.

  5. De volta ao shell original, gere tráfego usando httperf:

    sudo apt-get install httperf && \
    httperf --server=$IPAddress --port=80 --uri=/ \
            --num-conns=5000 --rate=20 --num-calls=1 \
            --print-reply=header
    

    Esse comando instala httperf e gera 5.000 solicitações no aplicativo de exemplo com a taxa de 20 solicitações por segundo. Esse teste é executado por aproximadamente 250 segundos.

Como simular a preempção

  1. No novo shell, saia do comando Kubetail pressionando Ctrl+C.
  2. Consiga o nome da instância do PVM implantada como parte de pvm-pool:

    PVM=$(kubectl get no \
          -o=jsonpath='{range .items[*]} \
          {.metadata.name}{"\n"}{end}' | grep pvm)
    
  3. Enquanto o teste httperf estiver em andamento, acione um evento de manutenção na instância do PVM para simular a preempção:

    gcloud compute instances simulate-maintenance-event $PVM \
        --zone=us-central1-c
    
  4. Observe os registros do aplicativo no novo shell:

    kubectl logs -f deploy/web-pvm -c hello-app
    

    Quando o pod recebe um sinal SIGTERM, o status de verificação de integridade do aplicativo informa explicitamente fail:

    ...
    Health status fail at: 2020-07-21 04:45:43.742
    ...
    

    O pod continua a receber solicitações até que seja removido do caminho de exibição da solicitação devido a falhas nas verificações de integridade. Essa remoção pode levar alguns segundos para ser propagada.

    ...
    Received request at: 2020-07-21 04:45:45.735
    Received request at: 2020-07-21 04:45:45.743
    Health status fail at: 2020-07-21 04:45:45.766
    ...
    

    Após alguns segundos, o pod deixa de receber novas solicitações. O pod continua ativo por 20 segundos. O manipulador preStop é feito para dormir por 20 segundos para permitir atividades de limpeza, incluindo solicitações em andamento. Em seguida, o pod é encerrado.

    ...
    Health status fail at: 2020-07-21 04:46:01.796
    2020-07-21 04:46:02.303  INFO 1 --- [       Thread-3] ConfigServletWebServerApplicationContext : Closing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@27ddd392: startup date [Tue Jul 21 04:39:44 UTC 2020]; root of context hierarchy
    Exiting PreStop hook
    ...
    
  5. Se quiser, execute o comando kubetail web para filtrar registros de todos os pods do aplicativo em execução nos dois pools de nós. A saída mostra as solicitações sendo roteadas para default-pool conforme a PVM está em preempção:

    ...
    [web-pvm-6f867bfc54-nm6fb] Received request at: 2020-07-20 20:45:45.743
    [web-pvm-6f867bfc54-nm6fb] Health status fail at: 2020-07-21 04:45:45.766
    [web-std-6f867bfc54-55gcc] Received request at: 2020-07-20 04:45:45.780
    [web-std-6f867bfc54-55gcc] Received request at: 2020-07-20 04:45:45.782
    ...
    

Validações pós-preempção

  1. No shell original, aguarde a conclusão do teste Httperf. Após a conclusão, o resultado será semelhante ao seguinte.

    ...
    Reply status: 1xx=0 2xx=5000 3xx=0 4xx=0 5xx=0
    ...
    Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
    ...
    

    A resposta indica que a preempção é atendida por default-pool e pvm-pool foi encerrado normalmente.

  2. Verifique se o PVM está ativo novamente e se o estado original do cluster foi restaurado:

    kubectl get po -l app=web-pvm \
        -o=custom-columns=NAME:.metadata.name,Node:.spec.nodeName
    

    Duas réplicas são provisionadas em pvm-pool:

    NAME                     Node
    web-pvm-6f867bfc54-9z2cp gke-vital-octagon-109612-gke-pvm-pool-664ec4ff-49lx
    

Considerações

Antes de executar esta solução na produção, considere estas qualificações:

  1. Neste tutorial, não usamos políticas de escalonamento automático de pods ou clusters. Em ambientes de produção, verifique se você tem o escalonamento automático correto para lidar com os picos de tráfego.
  2. Considere cuidadosamente a divisão entre VMs padrão e PVMs no cluster. Por exemplo, suponha que você esteja executando 100 PVMs e apenas uma VM padrão e 50% das PVMs passam por uma preempção. Nesse caso, o pool de VMs padrão levará algum tempo para ser redimensionado para recursos preemptivos. Enquanto isso, o tráfego dos usuários é afetado. Para atenuar grandes preempçãos, use aplicativos de terceiros, como Spot e estafette-gke-preemptible-killer (ambos em inglês). para distribuir as preempçãos e evitar que várias instâncias sejam desativadas juntas.
  3. Com base no seu caso de uso, teste cuidadosamente o período de carência alocado para pods regulares e do sistema usando k8s-node-termination-handler. Devido à crítica do aplicativo, talvez seja necessário alocar mais de 20 segundos para os pods regulares. Uma desvantagem possível dessa abordagem é que talvez não haja tempo suficiente para que os pods do sistema sejam encerrados de maneira limpa. Isso pode resultar em possível perda de registros e métricas de monitoramento que são gerenciadas por pods do sistema.

Limpeza

Para evitar cobranças na sua conta do Google Cloud pelos recursos usados neste tutorial, exclua o projeto do Cloud criado para este tutorial ou exclua os recursos associados a ele.

Excluir o projeto do Cloud

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

  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 os recursos

Se você quiser manter o projeto usado neste tutorial, exclua os recursos individuais.

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

    gcloud container clusters delete $CLUSTER --zone=$ZONE --async
    
  2. Excluir as regras de firewall

    gcloud compute firewall-rules delete allow-ssh-ingress-from-iap && \
    gcloud compute firewall-rules delete fw-allow-health-checks
    
  3. Exclua o serviço Traffic Director:

    gcloud compute forwarding-rules delete td-gke-forwarding-rule \
        --global && \
    gcloud compute target-http-proxies delete td-gke-proxy \
        --global && \
    gcloud compute url-maps delete web-service-urlmap \
        --global && \
    gcloud compute backend-services delete td-gke-service \
        --global && \
    gcloud compute health-checks delete td-gke-health-check \
        --global
    
  4. Excluir todos os NEGs:

    NEG_NAME_EDGE=$(gcloud beta compute network-endpoint-groups list \
                    | grep gateway-proxy | awk '{print $1}') && \
    gcloud beta compute network-endpoint-groups delete $NEG_NAME_EDGE \
        --zone=$ZONE && \
    gcloud beta compute network-endpoint-groups delete $NEG_NAME_STD \
        --zone=$ZONE && \
    gcloud beta compute network-endpoint-groups delete $NEG_NAME_PVM \
        --zone=$ZONE
    
  5. Exclua o código baixado, os artefatos e outras dependências:

    cd .. && rm -rf solutions-gke-pvm-preemption-handler
    

A seguir