Implemente no Compute Engine

Este guia explica como fazer implementações azul/verde sem tempo de inatividade em grupos de instâncias geridos (MIGs) do Compute Engine através do Cloud Build e do Terraform.

O Cloud Build permite-lhe automatizar uma variedade de processos de programadores, incluindo a criação e a implementação de aplicações em vários Google Cloud tempos de execução como o Compute Engine, Google Kubernetes Engine, GKE Enterprise, e funções do Cloud Run.

Os GIGs do Compute Engine permitem-lhe operar aplicações em várias máquinas virtuais (VMs) idênticas. Pode tornar as suas cargas de trabalho escaláveis e altamente disponíveis tirando partido dos serviços de MIG automatizados, incluindo: escalamento automático, autocura, implementação regional (várias zonas) e atualização automática. Com o modelo de implementação contínua azul/verde, vai aprender a transferir gradualmente o tráfego de utilizadores de um MIG (azul) para outro MIG (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

  • Tenha o código-fonte da aplicação pronto. O código fonte tem de estar armazenado num repositório, como o GitHub ou o Bitbucket.

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

Autorizações de gestão de identidade e de acesso necessárias

  1. Na Google Cloud consola, aceda à página Autorizações do Cloud Build:

    Aceda a Autorizações

  2. Para a conta de serviço do Cloud Build especificada ou a conta de serviço do Cloud Build predefinida, defina o estado das seguintes funções como Ativado:

    • Administrador de instâncias do Compute v1 (roles/compute.instanceAdmin) | Permite que o Cloud Build implemente novas instâncias no Compute Engine.
    • Administrador de armazenamento (roles/storage.admin) | Permite a leitura e a escrita a partir do Cloud Storage.
    • Escritor do Artifact Registry (roles/artifactregistry.writer) | Permite extrair imagens do Artifact Registry e escrever no mesmo.
    • Escritor de registos (roles/logging.logWriter) | Permite que as entradas de registo sejam escritas no Cloud Logging.
    • Editor do Cloud Build (roles/cloudbuild.builds.editor) | Permite que a sua conta de serviço execute compilações.

Vista geral do design

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

Modelo azul/verde

A um nível elevado, este modelo inclui os seguintes componentes:

  • Dois conjuntos de VMs do Compute Engine: azul e verde.
  • Três balanceadores de carga HTTP(S) externos:
    • Um equilibrador de carga azul/verde que encaminha o tráfego dos utilizadores finais para o conjunto azul ou verde de instâncias de VM.
    • Um equilibrador de carga azul que encaminha o tráfego de engenheiros de CQ e programadores para o conjunto de instâncias de VM azul.
    • Um equilibrador de carga verde que encaminha o tráfego de engenheiros de CQ e programadores para o conjunto de instâncias verde.
  • Dois conjuntos de utilizadores:
    • Utilizadores finais que têm acesso ao equilibrador de carga azul/verde, que os direciona para o conjunto de instâncias azul ou verde.
    • Engenheiros de controlo de qualidade e programadores que precisam de acesso a ambos os conjuntos de pools para fins de desenvolvimento e testes. Podem aceder aos balanceadores de carga azuis e verdes, que os encaminham para o conjunto de instâncias azuis e o conjunto de instâncias verdes, respetivamente.

Os conjuntos de VMs azuis e verdes são implementados como MIGs do Compute Engine, e os endereços IP externos são encaminhados para as VMs no MIG através de equilibradores de carga HTTP(s) externos. O exemplo de código descrito neste documento usa o Terraform para configurar esta infraestrutura.

O diagrama seguinte ilustra as operações do programador que ocorrem na implementação:

Fluxo de operações do programador

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

Para configurar esta infraestrutura, executa um script de configuração que inicia o processo de arranque 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 nos Cloud Source Repositories denominado copy-of-gcp-mig-simple e copia o código fonte do repositório de exemplo do GitHub para o repositório nos Cloud Source Repositories.
  • Cria dois acionadores do Cloud Build com os nomes apply e destroy.

O acionador apply está anexado a um ficheiro do Terraform denominado main.tfvars nos Cloud Source Repositories. Este ficheiro contém as variáveis do Terraform que representam os balanceadores de carga azuis e verdes.

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

  • Cria dois GIGs do Compute Engine (um para o verde e outro para o azul), quatro instâncias de VM do Compute Engine (duas para o GIG verde e duas para o GIG 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 pode usar para ver as aplicações implementadas nas instâncias azuis e verdes.

O acionador de destruição é acionado manualmente para eliminar todos os recursos criados pelo acionador de aplicação.

Experimentar

  1. Execute o script de configuração a partir do repositório de exemplos de código da 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 utilizador, introduza yes.

    O script termina a execução em alguns segundos.

  3. Na Google Cloud consola, abra a página Histórico de compilações do Cloud Build:

    Abra a página Criar histórico

  4. Clique na compilação mais recente.

    É apresentada a página Detalhes da compilação, que mostra um pipeline do Cloud Build com três passos de compilação: o primeiro passo de compilação cria um repositório nos Cloud Source Repositories, o segundo passo clona o conteúdo do repositório de exemplo no GitHub para os Cloud Source Repositories e o terceiro passo adiciona dois acionadores de compilação.

  5. Abra os Cloud Source Repositories:

    Abrir Cloud Source Repositories

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

    No separador Histórico na parte inferior da página, é apresentado um commit com a descrição A copy of https://github.com/GoogleCloudPlatform/cloud-build-samples.git criado pelo Cloud Build para criar um repositório com o nome copy-of-gcp-mig-simple.

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

    Abra a página Acionadores

  8. São apresentados dois acionadores de compilação com os nomes apply e destroy. O acionador apply está anexado ao ficheiro infra/main.tfvars no ramo main. Este acionador é executado sempre que o ficheiro é atualizado. O acionador destroy é um acionador manual.

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

    1. Na janela do terminal, crie e navegue para uma pasta com o nome 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 para o diretório clonado:

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

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

      git add .
      
    6. Consolide o ficheiro:

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

      git push
      

      Fazer alterações a infra/main.tfvars aciona a execução do apply acionador, que inicia a implementação.

  10. Abra os Cloud Source Repositories:

    Abrir Cloud Source Repositories

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

    Verá a confirmação com a descrição Promote green no separador Histórico na parte inferior da página.

  12. Para ver a execução do acionador apply, abra a página Histórico de compilação na consola Google Cloud :

    Abra a página Criar histórico

  13. Abra a página Detalhes da compilação clicando na primeira compilação.

    É apresentada a pipeline de acionamento apply com dois passos de compilação. O primeiro passo de compilação executa o comando Terraform apply para criar os recursos do Compute Engine e de balanceamento de carga para a implementação. O segundo passo de compilação imprime o endereço IP onde pode ver a aplicação em execução.

  14. Abra o endereço IP correspondente ao MIG verde num navegador. É apresentada uma captura de ecrã semelhante à seguinte que mostra a implementação:

    Implementação

  15. Aceda à 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 ver as quatro instâncias de VM:

    Abra a página da instância de VM

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

    Abra a página Endereços IP externos

Compreender o código

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

  • Código fonte relacionado com o script de configuração.
  • Código fonte relacionado com os pipelines do Cloud Build.
  • Código fonte relacionado com os modelos do Terraform.

Script de configuração

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

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

    • Cria um novo repositório nos Cloud Source Repositories.
    • Copia o código fonte do repositório GitHub de exemplo para o novo repositório nos Cloud Source Repositories.
    • Cria os acionadores de compilação 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 ficheiros de configuração do Cloud Build que o script de configuração usa para configurar os recursos para o fluxo GitOps. apply.cloudbuild.yaml contém dois passos de compilação:

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

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

As funções tf_install_in_cloud_build_step, tf_apply, describe_deployment e tf_destroy estão definidas no ficheiro bash_utils.sh. Os ficheiros de configuração de compilação 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 seguinte mostra a função tf_install_in_cloud_build_step definida em bash_utils.sh. Os ficheiros de configuração da compilação chamam esta função para instalar o Terraform rapidamente. Cria um contentor do Cloud Storage para registar o estado 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 seguinte fragmento do código mostra a função tf_apply definida em bash_utils.sh. Primeiro, chama terraform init que carrega todos os módulos e bibliotecas personalizadas e, em seguida, executa terraform apply para carregar as variáveis do ficheiro 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 seguinte fragmento do código mostra a função describe_deployment definida em bash_utils.sh. Usa o gcloud compute addresses describe para obter os endereços IP dos balanceadores de carga através do nome e imprimi-los.

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 seguinte fragmento do código mostra a função tf_destroy definida em bash_utils.sh. Chama terraform init que carrega todos os módulos e bibliotecas personalizadas e, em seguida, 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

Vai encontrar todos os ficheiros de configuração e variáveis do Terraform na pasta copy-of-gcp-mig-simple/infra/.

  • main.tf: este é o ficheiro de configuração do Terraform
  • main.tfvars: este ficheiro define as variáveis do Terraform.
  • mig/ e splitter/: estas pastas contêm os módulos que definem os balanceadores de carga. A pasta mig/ contém o ficheiro de configuração do Terraform que define o MIG para os balanceadores de carga azuis e verdes. Os MIGs azuis e verdes são idênticos, pelo que são definidos uma vez e instanciados para os objetos azuis e verdes. O ficheiro de configuração do Terraform para o equilibrador de carga do divisor encontra-se na pasta splitter/ .

O seguinte fragmento do código mostra o conteúdo de infra/main.tfvars. Contém três variáveis: duas que determinam que versão da aplicação implementar nos conjuntos azul e verde, e uma variável para a cor ativa: azul ou verde. As alterações a este ficheiro acionam a implementação.

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

Segue-se um fragmento do código de infra/main.tf. Neste fragmento:

  • Uma variável está definida para o projeto Google Cloud .
  • A Google está definida como fornecedor do Terraform.
  • Uma variável é definida para o espaço de nomes. Todos os objetos criados pelo Terraform têm o prefixo desta variável para que seja possível implementar várias versões da aplicação 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 associações para as variáveis no ficheiro 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 seguinte fragmento do código de infra/main.tf mostra a instanciação do módulo de divisão. Este módulo recebe a cor ativa para que o balanceador de carga do divisor saiba em que MIG implementar a aplicação.

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 seguinte fragmento do código de infra/main.tf define dois módulos idênticos para MIGs azuis e verdes. Recebe 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 ficheiro splitter/main.tf define os objetos criados para o MIG do separador. Segue-se um fragmento de código de splitter/main.tf que contém a lógica para alternar entre o MIG verde e o azul. É suportado pelo serviço google_compute_region_backend_service, que pode encaminhar 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 encaminhar.

O código seguinte encaminha 100% do tráfego para a cor especificada, mas pode atualizar este código para a implementação canary para encaminhar o tráfego para um subconjunto dos utilizadores.

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 ficheiro mig/main.tf define os objetos relativos aos MIGs azuis e verdes. O seguinte fragmento do código deste ficheiro define o modelo de instância do Compute Engine que é usado para criar os conjuntos de VMs. Tenha em atenção que este modelo de instância tem a propriedade do ciclo de vida do Terraform definida como create_before_destroy. Isto acontece porque, quando atualiza a versão do conjunto, não pode usar o modelo para criar a nova versão dos conjuntos quando ainda está a ser usado pela versão anterior do conjunto. No entanto, se a versão mais antiga do conjunto for destruída antes de criar o novo modelo, haverá um período em que os conjuntos estarão inativos. Para evitar este cenário, definimos o ciclo de vida do Terraform como create_before_destroy para que a versão mais recente de um conjunto de VMs seja criada primeiro antes de a versão mais antiga ser 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
  }
}