Implantar no Compute Engine

Este guia explica como realizar implantações azul/verde sem inatividade em grupos gerenciados de instâncias (MIGs, na sigla em inglês) do Compute Engine usando o Cloud Build e o Terraform.

O Cloud Build permite automatizar vários processos de desenvolvedor, incluindo a criação e implantação de aplicativos em vários Google Cloud ambientes de execução como Compute Engine, Google Kubernetes Engine, GKE Enterprise e funções do Cloud Run.

Com os MIGs do Compute Engine, é possível operar aplicativos em várias máquinas virtuais (VMs) idênticas. É possível tornar as cargas de trabalho escalonáveis e altamente disponíveis aproveitando serviços de MIG automatizados, como escalonamento automático, recuperação automática, implantação regional (várias zonas) e atualização automática. Usando o modelo de implantação contínua azul/verde, você vai aprender a transferir gradualmente o tráfego de usuários de um MIG (azul) para outro (verde), ambos em execução na produção.

Antes de começar

  • Enable the Cloud Build, Cloud Run, Artifact Registry, and Resource Manager APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  • Prepare o código-fonte do aplicativo. Seu código-fonte precisa ser armazenado em um repositório como GitHub ou Bitbucket.

  • Para executar os comandos gcloud nesta página, instale a Google Cloud CLI.

Permissões de gerenciamento de identidade e acesso obrigatórias

  1. No console Google Cloud , acesse a página Permissões do Cloud Build:

    Acesse Permissões

  2. Para a conta de serviço do Cloud Build especificada ou a conta de serviço padrão do Cloud Build, defina o status dos seguintes papéis como Ativado:

    • Administrador da instância do Compute v1 (roles/compute.instanceAdmin): permite que o Cloud Build implante novas instâncias no Compute Engine.
    • Administrador do Storage (roles/storage.admin): permite ler e gravar no Cloud Storage.
    • Gravador do Artifact Registry (roles/artifactregistry.writer): permite extrair imagens do Artifact Registry e gravar nele.
    • Gravador de registros (roles/logging.logWriter): permite que entradas de registro sejam gravadas no Cloud Logging.
    • Editor do Cloud Build (roles/cloudbuild.builds.editor): permite que sua conta de serviço execute builds.

Visão geral do design

O diagrama a seguir mostra o modelo de implantação azul-verde usado no exemplo de código descrito neste documento:

Modelo azul/verde

Em geral, esse modelo inclui os seguintes componentes:

  • Dois pools de VMs do Compute Engine: azul e verde.
  • Três balanceadores de carga HTTP(S) externos:
    • Um balanceador de carga azul-verde, que encaminha o tráfego de usuários finais para o pool azul ou verde de instâncias de VM.
    • Um balanceador de carga azul que roteia o tráfego de engenheiros de controle de qualidade e desenvolvedores para o pool de instâncias de VM azul.
    • Um balanceador de carga verde que roteia o tráfego de engenheiros de controle de qualidade e desenvolvedores para o pool de instâncias verdes.
  • Dois conjuntos de usuários:
    • Usuários finais que têm acesso ao balanceador de carga azul-verde, que os direciona para o pool de instâncias azul ou verde.
    • Engenheiros de controle de qualidade e desenvolvedores que precisam de acesso aos dois conjuntos de pools para fins de desenvolvimento e teste. Eles podem acessar os balanceadores de carga azul e verde, que os encaminham para o pool de instâncias azul e o pool de instâncias verde, respectivamente.

Os pools de VMs azul e verde são implementados como MIGs do Compute Engine, e os endereços IP externo são roteados para as VMs no MIG usando balanceadores de carga HTTP(s) externos. O exemplo de código descrito neste documento usa o Terraform para configurar essa infraestrutura.

O diagrama a seguir ilustra as operações do desenvolvedor que acontecem na implantação:

Fluxo de operações de desenvolvedores

No diagrama anterior, as setas vermelhas representam o fluxo de bootstrap que ocorre quando você configura a infraestrutura de implantação pela primeira vez, e as setas azuis representam o fluxo do GitOps que ocorre durante cada implantação.

Para configurar essa infraestrutura, execute um script de configuração que inicia o processo de bootstrap e configura os componentes para o fluxo do GitOps.

O script de configuração executa um pipeline do Cloud Build que realiza as seguintes operações:

  • Cria um repositório no Cloud Source Repositories chamado copy-of-gcp-mig-simple e copia o código-fonte do repositório de amostra do GitHub para o repositório no Cloud Source Repositories.
  • Cria dois gatilhos do Cloud Build chamados apply e destroy.

O gatilho apply está anexado a um arquivo do Terraform chamado main.tfvars no Cloud Source Repositories. Esse arquivo contém as variáveis do Terraform que representam os balanceadores de carga azul e verde.

Para configurar a implantação, atualize as variáveis no arquivo main.tfvars. O gatilho apply executa um pipeline do Cloud Build que executa tf_apply e realiza as seguintes operações:

  • Cria dois MIGs do Compute Engine (um para verde e um para azul), quatro instâncias de VM do Compute Engine (duas para o MIG verde e duas para o MIG azul), os três balanceadores de carga (azul, verde e o divisor) e três endereços IP públicos.
  • Imprime os endereços IP que podem ser usados para ver os aplicativos implantados nas instâncias azul e verde.

O gatilho de exclusão é acionado manualmente para excluir todos os recursos criados pelo gatilho de aplicação.

Testar

  1. Execute o script de configuração do repositório de exemplo de código do Google:

    bash <(curl https://raw.githubusercontent.com/GoogleCloudPlatform/cloud-build-samples/main/mig-blue-green/setup.sh)
    
  2. Quando o script de configuração pedir o consentimento do usuário, digite yes.

    O script termina de ser executado em alguns segundos.

  3. No Google Cloud console, abra a página Histórico de builds do Cloud Build:

    Abra a página "Histórico de builds"

  4. Clique no build mais recente.

    A página Detalhes da build aparece, mostrando um pipeline do Cloud Build com três etapas de build: a primeira cria um repositório no Cloud Source Repositories, a segunda clona o conteúdo do repositório de amostra no GitHub para o Cloud Source Repositories, e a terceira adiciona dois gatilhos de build.

  5. Abra o Cloud Source Repositories:

    Abrir o Cloud Source Repositories

  6. Na lista de repositórios, clique em copy-of-gcp-mig-simple.

    Na guia Histórico na parte de baixo da página, você vai encontrar um commit com a descrição A copy of https://github.com/GoogleCloudPlatform/cloud-build-samples.git feito pelo Cloud Build para criar um repositório chamado copy-of-gcp-mig-simple.

  7. Abra a página Gatilhos do Cloud Build:

    Abrir a página "Gatilhos"

  8. Você vai encontrar dois gatilhos de build chamados apply e destroy. O gatilho apply está anexado ao arquivo infra/main.tfvars na ramificação main. Esse gatilho é executado sempre que o arquivo é atualizado. O acionador destroy é manual.

  9. Para iniciar o processo de implantação, atualize o arquivo infra/main.tfvars:

    1. Na janela do terminal, crie e navegue até uma pasta chamada deploy-compute-engine:

      mkdir ~/deploy-compute-engine
      cd ~/deploy-compute-engine
      
    2. Clone o repositório copy-of-gcp-mig-simple:

      gcloud source repos clone copy-of-mig-blue-green
      
    3. Navegue até o diretório clonado:

      cd ./copy-of-mig-blue-green
      
    4. Atualize infra/main.tfvars para substituir azul por verde:

      sed -i'' -e 's/blue/green/g' infra/main.tfvars
      
    5. Adicione o arquivo atualizado:

      git add .
      
    6. Faça a confirmação do arquivo:

      git commit -m "Promote green"
      
    7. Envie o arquivo:

      git push
      

      Fazer mudanças em infra/main.tfvars aciona a execução do gatilho apply, que inicia a implantação.

  10. Abra o Cloud Source Repositories:

    Abrir o Cloud Source Repositories

  11. Na lista de repositórios, clique em copy-of-gcp-mig-simple.

    O commit com a descrição Promote green vai aparecer na guia Histórico, na parte de baixo da página.

  12. Para conferir a execução do gatilho apply, abra a página Histórico de builds no console do Google Cloud :

    Abra a página "Histórico de builds"

  13. Clique no primeiro build para abrir a página Detalhes do build.

    Você vai ver o pipeline de gatilho apply com duas etapas de build. A primeira etapa de build executa "terraform apply" para criar os recursos do Compute Engine e de balanceamento de carga para a implantação. A segunda etapa de build mostra o endereço IP em que você pode ver o aplicativo em execução.

  14. Abra o endereço IP correspondente ao MIG verde em um navegador. Você vai ver uma captura de tela semelhante a esta mostrando a implantação:

    Implantação

  15. Acesse a página Grupo de instâncias do Compute Engine para ver os grupos de instâncias azul e verde:

    Abra a página "Grupo de instâncias".

  16. Abra a página Instâncias de VM para conferir as quatro instâncias de VM:

    Abra a página "Instância de VM"

  17. Abra a página Endereços IP externos para conferir os três balanceadores de carga:

    Abra a página "Endereços IP externos"

Noções básicas sobre o código

O código-fonte deste exemplo de código inclui:

  • Código-fonte relacionado ao script de configuração.
  • Código-fonte relacionado aos pipelines do Cloud Build.
  • Código-fonte relacionado aos modelos do Terraform.

Script de configuração

setup.sh é o script de configuração que executa o processo de inicialização e cria os componentes para a implantação azul-verde. O script executa as seguintes operações:

  • Ativa as APIs Cloud Build, Resource Manager, Compute Engine e Cloud Source Repositories.
  • Concede o papel roles/editor do IAM à conta de serviço do Cloud Build no seu projeto. Essa função é necessária para que o Cloud Build crie e configure os componentes do GitOps necessários para a implantação.
  • Concede o papel roles/source.admin do IAM à conta de serviço do Cloud Build no seu projeto. Essa função é necessária para que a conta de serviço do Cloud Build crie o Cloud Source Repositories no seu projeto e clone o conteúdo do exemplo de repositório do GitHub no Cloud Source Repositories.
  • Gera um pipeline do Cloud Build chamado bootstrap.cloudbuild.yaml inline, que:

    • Cria um repositório no Cloud Source Repositories.
    • Copia o código-fonte do repositório de amostra do GitHub para o novo repositório no Cloud Source Repositories.
    • Cria os gatilhos de build de aplicação e destruição.
set -e

BLUE='\033[1;34m'
RED='\033[1;31m'
GREEN='\033[1;32m'
NC='\033[0m'

echo -e "\n${GREEN}######################################################"
echo -e "#                                                    #"
echo -e "#  Zero-Downtime Blue/Green VM Deployments Using     #"
echo -e "#  Managed Instance Groups, Cloud Build & Terraform  #"
echo -e "#                                                    #"
echo -e "######################################################${NC}\n"

echo -e "\nSTARTED ${GREEN}setup.sh:${NC}"

echo -e "\nIt's ${RED}safe to re-run${NC} this script to ${RED}recreate${NC} all resources.\n"
echo "> Checking GCP CLI tool is installed"
gcloud --version > /dev/null 2>&1

readonly EXPLICIT_PROJECT_ID="$1"
readonly EXPLICIT_CONSENT="$2"

if [ -z "$EXPLICIT_PROJECT_ID" ]; then
    echo "> No explicit project id provided, trying to infer"
    PROJECT_ID="$(gcloud config get-value project)"
else
    PROJECT_ID="$EXPLICIT_PROJECT_ID"
fi

if [ -z "$PROJECT_ID" ]; then
    echo "ERROR: GCP project id was not provided as parameter and could not be inferred"
    exit 1
else
    readonly PROJECT_NUM="$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')"
    if [ -z "$PROJECT_NUM" ]; then
        echo "ERROR: GCP project number could not be determined"
        exit 1
    fi
    echo -e "\nYou are about to:"
    echo -e "  * modify project ${RED}${PROJECT_ID}/${PROJECT_NUM}${NC}"
    echo -e "  * ${RED}enable${NC} various GCP APIs"
    echo -e "  * make Cloud Build ${RED}editor${NC} of your project"
    echo -e "  * ${RED}execute${NC} Cloud Builds and Terraform plans to create"
    echo -e "  * ${RED}4 VMs${NC}, ${RED}3 load balancers${NC}, ${RED}3 public IP addresses${NC}"
    echo -e "  * incur ${RED}charges${NC} in your billing account as a result\n"
fi

if [ "$EXPLICIT_CONSENT" == "yes" ]; then
  echo "Proceeding under explicit consent"
  readonly CONSENT="$EXPLICIT_CONSENT"
else
    echo -e "Enter ${BLUE}'yes'${NC} if you want to proceed:"
    read CONSENT
fi

if [ "$CONSENT" != "yes" ]; then
    echo -e "\nERROR: Aborted by user"
    exit 1
else
    echo -e "\n......................................................"
    echo -e "\n> Received user consent"
fi

#
# Executes action with one randomly delayed retry.
#
function do_with_retry {
    COMMAND="$@"
    echo "Trying $COMMAND"
    (eval $COMMAND && echo "Success on first try") || ( \
        echo "Waiting few seconds to retry" &&
        sleep 10 && \
        echo "Retrying $COMMAND" && \
        eval $COMMAND \
    )
}

echo "> Enabling required APIs"
# Some of these can be enabled later with Terraform, but I personally
# prefer to do all API enablement in one place with gcloud.
gcloud services enable \
    --project=$PROJECT_ID \
    cloudbuild.googleapis.com \
    cloudresourcemanager.googleapis.com \
    compute.googleapis.com \
    sourcerepo.googleapis.com \
    --no-user-output-enabled \
    --quiet

echo "> Adding Cloud Build to roles/editor"
gcloud projects add-iam-policy-binding \
    "$PROJECT_ID" \
    --member="serviceAccount:$PROJECT_NUM@cloudbuild.gserviceaccount.com" \
    --role='roles/editor' \
    --condition=None \
    --no-user-output-enabled \
    --quiet

echo "> Adding Cloud Build to roles/source.admin"
gcloud projects add-iam-policy-binding \
    "$PROJECT_ID" \
    --member="serviceAccount:$PROJECT_NUM@cloudbuild.gserviceaccount.com" \
    --condition=None \
    --role='roles/source.admin' \
    --no-user-output-enabled \
    --quiet

echo "> Configuring bootstrap job"
rm -rf "./bootstrap.cloudbuild.yaml"
cat <<'EOT_BOOT' > "./bootstrap.cloudbuild.yaml"
tags:
- "mig-blue-green-bootstrapping"
steps:
- id: create_new_cloud_source_repo
  name: "gcr.io/cloud-builders/gcloud"
  script: |
    #!/bin/bash
    set -e

    echo "(Re)Creating source code repository"

    gcloud source repos delete \
        "copy-of-mig-blue-green" \
        --quiet || true

    gcloud source repos create \
        "copy-of-mig-blue-green" \
        --quiet

- id: copy_demo_source_into_new_cloud_source_repo
  name: "gcr.io/cloud-builders/gcloud"
  env:
    - "PROJECT_ID=$PROJECT_ID"
    - "PROJECT_NUMBER=$PROJECT_NUMBER"
  script: |
    #!/bin/bash
    set -e

    readonly GIT_REPO="https://github.com/GoogleCloudPlatform/cloud-build-samples.git"

    echo "Cloning demo source repo"
    mkdir /workspace/from/
    cd /workspace/from/
    git clone $GIT_REPO ./original
    cd ./original

    echo "Cloning new empty repo"
    mkdir /workspace/to/
    cd /workspace/to/
    gcloud source repos clone \
        "copy-of-mig-blue-green"
    cd ./copy-of-mig-blue-green

    echo "Making a copy"
    cp -r /workspace/from/original/mig-blue-green/* ./

    echo "Setting git identity"
    git config user.email \
        "$PROJECT_NUMBER@cloudbuild.gserviceaccount.com"
    git config user.name \
        "Cloud Build"

    echo "Commit & push"
    git add .
    git commit \
        -m "A copy of $GIT_REPO"
    git push

- id: add_pipeline_triggers
  name: "gcr.io/cloud-builders/gcloud"
  env:
    - "PROJECT_ID=$PROJECT_ID"
  script: |
    #!/bin/bash
    set -e

    echo "(Re)Creating destroy trigger"
    gcloud builds triggers delete "destroy" --quiet || true
    gcloud builds triggers create manual \
        --name="destroy" \
        --repo="https://source.developers.google.com/p/$PROJECT_ID/r/copy-of-mig-blue-green" \
        --branch="master" \
        --build-config="pipelines/destroy.cloudbuild.yaml" \
        --repo-type=CLOUD_SOURCE_REPOSITORIES \
        --quiet

    echo "(Re)Creating apply trigger"
    gcloud builds triggers delete "apply" --quiet || true
    gcloud builds triggers create cloud-source-repositories \
        --name="apply" \
        --repo="copy-of-mig-blue-green" \
        --branch-pattern="master" \
        --build-config="pipelines/apply.cloudbuild.yaml" \
        --included-files="infra/main.tfvars" \
        --quiet

EOT_BOOT

echo "> Waiting API enablement propagation"
do_with_retry "(gcloud builds list --project "$PROJECT_ID" --quiet && gcloud compute instances list --project "$PROJECT_ID" --quiet && gcloud source repos list --project "$PROJECT_ID" --quiet) > /dev/null 2>&1" > /dev/null 2>&1

echo "> Executing bootstrap job"
gcloud beta builds submit \
    --project "$PROJECT_ID" \
    --config ./bootstrap.cloudbuild.yaml \
    --no-source \
    --no-user-output-enabled \
    --quiet
rm ./bootstrap.cloudbuild.yaml

echo -e "\n${GREEN}All done. Now you can:${NC}"
echo -e "  * manually run 'apply' and 'destroy' triggers to manage deployment lifecycle"
echo -e "  * commit change to 'infra/main.tfvars' and see 'apply' pipeline trigger automatically"

echo -e "\n${GREEN}Few key links:${NC}"
echo -e "  * Dashboard: https://console.cloud.google.com/home/dashboard?project=$PROJECT_ID"
echo -e "  * Repo: https://source.cloud.google.com/$PROJECT_ID/copy-of-mig-blue-green"
echo -e "  * Cloud Build Triggers: https://console.cloud.google.com/cloud-build/triggers;region=global?project=$PROJECT_ID"
echo -e "  * Cloud Build History: https://console.cloud.google.com/cloud-build/builds?project=$PROJECT_ID"

echo -e "\n............................."

echo -e "\n${GREEN}COMPLETED!${NC}"

Pipelines do Cloud Build

apply.cloudbuild.yaml e destroy.cloudbuild.yaml são os arquivos de configuração do Cloud Build que o script de configuração usa para configurar os recursos do fluxo do GitOps. O apply.cloudbuild.yaml contém duas etapas de build:

  • Etapa de build tf_apply build que chama a função tf_install_in_cloud_build_step, que instala o Terraform. tf_apply que cria os recursos usados no fluxo do GitOps. As funções tf_install_in_cloud_build_step e tf_apply são definidas em bash_utils.sh, e a etapa de build usa o comando source para chamar essas funções.
  • describe_deployment etapa de build que chama a função describe_deployment que imprime os endereços IP dos balanceadores de carga.

destroy.cloudbuild.yaml chama tf_destroy, que exclui todos os recursos criados por tf_apply.

As funções tf_install_in_cloud_build_step, tf_apply, describe_deployment e tf_destroy são definidas no arquivo bash_utils.sh. Os arquivos de configuração de build usam o comando source para chamar as funções.

steps:
  - id: run-terraform-apply
    name: "gcr.io/cloud-builders/gcloud"
    env:
      - "PROJECT_ID=$PROJECT_ID"
    script: |
      #!/bin/bash
      set -e
      source /workspace/lib/bash_utils.sh
      tf_install_in_cloud_build_step
      tf_apply

  - id: describe-deployment
    name: "gcr.io/cloud-builders/gcloud"
    env:
      - "PROJECT_ID=$PROJECT_ID"
    script: |
      #!/bin/bash
      set -e
      source /workspace/lib/bash_utils.sh
      describe_deployment

tags:
  - "mig-blue-green-apply"
steps:
  - id: run-terraform-destroy
    name: "gcr.io/cloud-builders/gcloud"
    env:
      - "PROJECT_ID=$PROJECT_ID"
    script: |
      #!/bin/bash
      set -e
      source /workspace/lib/bash_utils.sh
      tf_install_in_cloud_build_step
      tf_destroy

tags:
  - "mig-blue-green-destroy"

O código a seguir mostra a função tf_install_in_cloud_build_step definida em bash_utils.sh. Os arquivos de configuração de build chamam essa função para instalar o Terraform de maneira dinâmica. Ele cria um bucket do Cloud Storage para registrar o status do Terraform.

function tf_install_in_cloud_build_step {
    echo "Installing deps"
    apt update
    apt install \
        unzip \
        wget \
        -y

    echo "Manually installing Terraform"
    wget https://releases.hashicorp.com/terraform/1.3.4/terraform_1.3.4_linux_386.zip
    unzip -q terraform_1.3.4_linux_386.zip
    mv ./terraform /usr/bin/
    rm -rf terraform_1.3.4_linux_386.zip

    echo "Verifying installation"
    terraform -v

    echo "Creating Terraform state storage bucket $BUCKET_NAME"
    gcloud storage buckets create \
        "gs://$BUCKET_NAME" || echo "Already exists..."

    echo "Configure Terraform provider and state bucket"
cat <<EOT_PROVIDER_TF > "/workspace/infra/provider.tf"
terraform {
  required_version = ">= 0.13"
  backend "gcs" {
    bucket = "$BUCKET_NAME"
  }
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = ">= 3.77, < 5.0"
    }
  }
}
EOT_PROVIDER_TF

    echo "$(cat /workspace/infra/provider.tf)"
}

O snippet de código a seguir mostra a função tf_apply definida em bash_utils.sh. Primeiro, ele chama terraform init, que carrega todos os módulos e bibliotecas personalizadas. Depois, executa terraform apply para carregar as variáveis do arquivo main.tfvars.

function tf_apply {
    echo "Running Terraform init"
    terraform \
        -chdir="$TF_CHDIR" \
        init

    echo "Running Terraform apply"
    terraform \
        -chdir="$TF_CHDIR" \
        apply \
        -auto-approve \
        -var project="$PROJECT_ID" \
        -var-file="main.tfvars"
}

O snippet de código a seguir mostra a função describe_deployment definida em bash_utils.sh. Ele usa gcloud compute addresses describe para buscar os endereços IP dos balanceadores de carga usando o nome e os imprime.

function describe_deployment {
    NS="ns1-"
    echo -e "Deployment configuration:\n$(cat infra/main.tfvars)"
    echo -e \
      "Here is how to connect to:" \
      "\n\t* active color MIG: http://$(gcloud compute addresses describe ${NS}splitter-address-name --region=us-west1 --format='value(address)')/" \
      "\n\t* blue color MIG: http://$(gcloud compute addresses describe ${NS}blue-address-name --region=us-west1 --format='value(address)')/" \
      "\n\t* green color MIG: http://$(gcloud compute addresses describe ${NS}green-address-name --region=us-west1 --format='value(address)')/"
    echo "Good luck!"
}

O snippet de código a seguir mostra a função tf_destroy definida em bash_utils.sh. Ele chama terraform init, que carrega todos os módulos e bibliotecas personalizadas, e executa terraform destroy, que descarrega as variáveis do Terraform.

function tf_destroy {
    echo "Running Terraform init"
    terraform \
        -chdir="$TF_CHDIR" \
        init

    echo "Running Terraform destroy"
    terraform \
        -chdir="$TF_CHDIR" \
        destroy \
        -auto-approve \
        -var project="$PROJECT_ID" \
        -var-file="main.tfvars"
}

Modelos do Terraform

Todos os arquivos e variáveis de configuração do Terraform estão na pasta copy-of-gcp-mig-simple/infra/.

  • main.tf: este é o arquivo de configuração do Terraform
  • main.tfvars: esse arquivo define as variáveis do Terraform.
  • mig/ e splitter/: essas pastas contêm os módulos que definem os balanceadores de carga. A pasta mig/ contém o arquivo de configuração do Terraform que define o MIG para os balanceadores de carga azul e verde. Os MIGs azul e verde são idênticos. Portanto, eles são definidos uma vez e instanciados para os objetos azul e verde. O arquivo de configuração do Terraform para o balanceador de carga do divisor está na pasta splitter/ .

O snippet de código a seguir mostra o conteúdo de infra/main.tfvars. Ele contém três variáveis: duas que determinam qual versão do aplicativo implantar nos pools azul e verde e uma variável para a cor ativa: azul ou verde. As mudanças nesse arquivo acionam a implantação.

MIG_VER_BLUE     = "v1"
MIG_VER_GREEN    = "v1"
MIG_ACTIVE_COLOR = "blue"

Confira a seguir um snippet de código de infra/main.tf. Neste snippet:

  • Uma variável é definida para o projeto Google Cloud .
  • O Google está definido como o provedor do Terraform.
  • Uma variável é definida para o namespace. Todos os objetos criados pelo Terraform têm essa variável como prefixo para que várias versões do aplicativo possam ser implantadas no mesmo projeto e os nomes dos objetos não entrem em conflito entre si.
  • As variáveis MIG_VER_BLUE, MIG_VER_BLUE e MIG_ACTIVE_COLOR são as vinculações das variáveis no arquivo infra/main.tfvars.
variable "project" {
  type        = string
  description = "GCP project we are working in."
}

provider "google" {
  project = var.project
  region  = "us-west1"
  zone    = "us-west1-a"
}

variable "ns" {
  type        = string
  default     = "ns1-"
  description = "The namespace used for all resources in this plan."
}

variable "MIG_VER_BLUE" {
  type        = string
  description = "Version tag for 'blue' deployment."
}

variable "MIG_VER_GREEN" {
  type        = string
  description = "Version tag for 'green' deployment."
}

variable "MIG_ACTIVE_COLOR" {
  type        = string
  description = "Active color (blue | green)."
}

O snippet de código a seguir de infra/main.tf mostra a instanciação do módulo de divisão. Esse módulo usa a cor ativa para que o balanceador de carga do divisor saiba em qual MIG implantar o aplicativo.

module "splitter-lb" {
  source               = "./splitter"
  project              = var.project
  ns                   = "${var.ns}splitter-"
  active_color         = var.MIG_ACTIVE_COLOR
  instance_group_blue  = module.blue.google_compute_instance_group_manager_default.instance_group
  instance_group_green = module.green.google_compute_instance_group_manager_default.instance_group
}

O snippet de código a seguir de infra/main.tf define dois módulos idênticos para MIGs azuis e verdes. Ele usa a cor, a rede e a sub-rede, que são definidas no módulo divisor.

module "blue" {
  source                               = "./mig"
  project                              = var.project
  app_version                          = var.MIG_VER_BLUE
  ns                                   = var.ns
  color                                = "blue"
  google_compute_network               = module.splitter-lb.google_compute_network
  google_compute_subnetwork            = module.splitter-lb.google_compute_subnetwork_default
  google_compute_subnetwork_proxy_only = module.splitter-lb.google_compute_subnetwork_proxy_only
}

module "green" {
  source                               = "./mig"
  project                              = var.project
  app_version                          = var.MIG_VER_GREEN
  ns                                   = var.ns
  color                                = "green"
  google_compute_network               = module.splitter-lb.google_compute_network
  google_compute_subnetwork            = module.splitter-lb.google_compute_subnetwork_default
  google_compute_subnetwork_proxy_only = module.splitter-lb.google_compute_subnetwork_proxy_only
}

O arquivo splitter/main.tf define os objetos criados para o MIG splitter. Confira abaixo um snippet de código de splitter/main.tf que contém a lógica para alternar entre o MIG verde e o azul. Ele é apoiado pelo serviço google_compute_region_backend_service, que pode rotear o tráfego para duas regiões de back-end: var.instance_group_blue ou var.instance_group_green. capacity_scaler define a quantidade de tráfego a ser roteada.

O código a seguir encaminha 100% do tráfego para a cor especificada, mas você pode atualizar esse código para a implantação canário e encaminhar o tráfego para um subconjunto de usuários.

resource "google_compute_region_backend_service" "default" {
  name                  = local.l7-xlb-backend-service
  region                = "us-west1"
  load_balancing_scheme = "EXTERNAL_MANAGED"
  health_checks         = [google_compute_region_health_check.default.id]
  protocol              = "HTTP"
  session_affinity      = "NONE"
  timeout_sec           = 30
  backend {
    group           = var.instance_group_blue
    balancing_mode  = "UTILIZATION"
    capacity_scaler = var.active_color == "blue" ? 1 : 0
  }
  backend {
    group           = var.instance_group_green
    balancing_mode  = "UTILIZATION"
    capacity_scaler = var.active_color == "green" ? 1 : 0
  }
}

O arquivo mig/main.tf define os objetos relacionados aos MIGs azul e verde. O snippet de código a seguir desse arquivo define o modelo de instância do Compute Engine usado para criar os pools de VMs. Observe que esse modelo de instância tem a propriedade de ciclo de vida do Terraform definida como create_before_destroy. Isso acontece porque, ao atualizar a versão do pool, não é possível usar o modelo para criar a nova versão dos pools enquanto ele ainda está sendo usado pela versão anterior. Mas se a versão mais antiga do pool for destruída antes da criação do novo modelo, haverá um período em que os pools ficarão inativos. Para evitar esse cenário, definimos o ciclo de vida do Terraform como create_before_destroy para que a versão mais recente de um pool de VMs seja criada primeiro antes que a versão mais antiga seja destruída.

resource "google_compute_instance_template" "default" {
  name = local.l7-xlb-backend-template
  disk {
    auto_delete  = true
    boot         = true
    device_name  = "persistent-disk-0"
    mode         = "READ_WRITE"
    source_image = "projects/debian-cloud/global/images/family/debian-10"
    type         = "PERSISTENT"
  }
  labels = {
    managed-by-cnrm = "true"
  }
  machine_type = "n1-standard-1"
  metadata = {
    startup-script = <<EOF
    #! /bin/bash
    sudo apt-get update
    sudo apt-get install apache2 -y
    sudo a2ensite default-ssl
    sudo a2enmod ssl
    vm_hostname="$(curl -H "Metadata-Flavor:Google" \
    http://169.254.169.254/computeMetadata/v1/instance/name)"
    sudo echo "<html><body style='font-family: Arial; margin: 64px; background-color: light${var.color};'><h3>Hello, World!<br><br>version: ${var.app_version}<br>ns: ${var.ns}<br>hostname: $vm_hostname</h3></body></html>" | \
    tee /var/www/html/index.html
    sudo systemctl restart apache2
    EOF
  }
  network_interface {
    access_config {
      network_tier = "PREMIUM"
    }
    network    = var.google_compute_network.id
    subnetwork = var.google_compute_subnetwork.id
  }
  region = "us-west1"
  scheduling {
    automatic_restart   = true
    on_host_maintenance = "MIGRATE"
    provisioning_model  = "STANDARD"
  }
  tags = ["load-balanced-backend"]

  # NOTE: the name of this resource must be unique for every update;
  #       this is wy we have a app_version in the name; this way
  #       new resource has a different name vs old one and both can
  #       exists at the same time
  lifecycle {
    create_before_destroy = true
  }
}