Como gerenciar a infraestrutura como código com o Terraform, o Cloud Build e o GitOps

Neste tutorial, explicamos como gerenciar a infraestrutura como código com o Terraform (em inglês) e o Cloud Build usando a conhecida metodologia GitOps. O termo GitOps foi cunhado pela primeira vez pela Weaveworks (em inglês), e o conceito principal dele é usar um repositório Git para armazenar o estado do ambiente que você quer. O Terraform é uma ferramenta de código aberto da HashiCorp (em inglês) que permite criar, alterar e melhorar de maneira previsível sua infraestrutura de nuvem por meio de código. Neste tutorial, você usa o Cloud Build, um serviço de integração contínua do Google Cloud, para aplicar automaticamente os manifestos do Terraform ao seu ambiente.

Este tutorial é destinado para desenvolvedores e operadores que procuram uma estratégia elegante para fazer alterações previsíveis na infraestrutura. O artigo considera que você já conhece o Google Cloud, o Linux e o GitHub.

Arquitetura

Para demonstrar como este tutorial aplica as práticas do GitOps para gerenciar execuções do Terraform, considere o diagrama de arquitetura a seguir. Ele usa ramificações GitHub (dev e prod) para representar ambientes reais. Esses ambientes são definidos por redes VPC virtuais: dev e prod, respectivamente, em um projeto do Google Cloud.

Infraestrutura com ambientes de desenvolvimento e produção.

O processo começa quando você envia o código do Terraform para a ramificação dev ou prod. Nesse cenário, o Cloud Build aciona e aplica manifestos do Terraform para atingir o estado desejado no respectivo ambiente. Por outro lado, quando você aplica o código do Terraform a qualquer outra ramificação, por exemplo, a uma ramificação de recurso, o Cloud Build é executado para terraform plan,, mas nada é aplicado a ambiente algum.

O ideal é que desenvolvedores ou operadores precisem fazer propostas de infraestrutura para branches não protegidos e enviá-las por meio de solicitações de envio (links em inglês). O app GitHub do Cloud Build, discutido posteriormente neste tutorial, aciona automaticamente os jobs de criação e vincula os relatórios terraform plan a essas solicitações de envio. Dessa forma, é possível discutir e analisar as possíveis alterações com os colaboradores e adicionar confirmações de acompanhamento antes que as alterações sejam mescladas no branch básico.

Se não houver preocupações, mescle as alterações no branch dev. Essa mescla aciona uma implantação de infraestrutura para o ambiente dev, o que permite testá-lo. Depois de testar e ter certeza do que foi implantado, mescle o branch dev no branch prod para acionar a instalação da infraestrutura no ambiente de produção.

Objetivos

  • Configurar seu repositório GitHub.
  • Configurar o Terraform para armazenar o estado em um bucket do Cloud Storage.
  • Conceder permissões à conta de serviço do Cloud Build.
  • Conectar o Cloud Build ao seu repositório GitHub.
  • Alterar a configuração do ambiente em uma ramificação de recurso.
  • Promover mudanças no ambiente de desenvolvimento.
  • Promover mudanças no ambiente de produção.

Custos

Neste tutorial, usamos os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços. Novos usuários do Google Cloud podem ser qualificados para uma avaliação gratuita.

Antes de começar

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

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

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

    Acessar a página do seletor de projetos

  3. Verifique se a cobrança está ativada para o seu projeto do Google Cloud. Saiba como confirmar se a cobrança está ativada para o seu projeto.

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

    Ativar o Cloud Shell

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

  5. No Cloud Shell, consiga o código do projeto que você acabou de selecionar:
    gcloud config get-value project
    Se este comando não retornar o código, configure o Cloud Shell para usar seu projeto. Substitua PROJECT_ID pelo código do seu projeto.
    gcloud config set project PROJECT_ID
  6. Ative as APIs necessárias:
    gcloud services enable cloudbuild.googleapis.com compute.googleapis.com
    Esta etapa pode levar alguns minutos.
  7. Se você nunca usou o Git no Cloud Shell, configure-o com seu nome e endereço de e-mail:
    git config --global user.email "your-email-address"
    git config --global user.name "your-name"
    
    O Git usa essas informações para identificar você como o autor das confirmações que você cria no Cloud Shell.

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

Como configurar seu repositório GitHub

Neste tutorial, você usa um único repositório Git para definir a infraestrutura em nuvem. Você orquestra essa infraestrutura ao ter branches diferentes correspondentes a ambientes diferentes:

  • A ramificação dev contém as alterações mais recentes aplicadas ao ambiente de desenvolvimento.
  • A ramificação prod contém as últimas alterações que são aplicadas ao ambiente de produção.

Com essa infraestrutura, sempre é possível fazer referência ao repositório para saber qual configuração é esperada em cada ambiente e propor novas alterações, primeiro fundindo-as ao ambiente dev. Em seguida, você promove as alterações mesclando a ramificação dev na ramificação prod subsequente.

Para começar, você bifurca o repositório solutions-terraform-cloudbuild-gitops (em inglês).

  1. No GitHub, acesse https://github.com/GoogleCloudPlatform/solutions-terraform-cloudbuild-gitops.git (em inglês).
  2. No canto superior direito da página, clique em Bifurcar.

    Bifurcando um repositório.

    Agora você tem uma cópia do repositório solutions-terraform-cloudbuild-gitops com arquivos de origem.

  3. No Cloud Shell, clone este repositório bifurcado, substituindo your-github-username pelo seu nome de usuário do GitHub:

    cd ~
    git clone https://github.com/your-github-username/solutions-terraform-cloudbuild-gitops.git
    cd ~/solutions-terraform-cloudbuild-gitops
    

O código nesse repositório está estruturado da seguinte maneira:

  • A pasta environments/ contém subpastas que representam ambientes, como dev e prod, que fornecem separação lógica entre cargas de trabalho em diferentes estágios de maturidade, desenvolvimento e produção, respectivamente. É uma boa prática que esses ambientes sejam os mais semelhantes possíveis, mas cada subpasta tem a própria configuração do Terraform para garantir que possam ter configurações únicas, conforme necessário.

  • A pasta modules/ contém módulos in-line do Terraform. Esses módulos representam agrupamentos lógicos de recursos relacionados e são usados para compartilhar código em diferentes ambientes.

  • O cloudbuild.yaml é um arquivo de configuração de versão que contém instruções para o Cloud Build, como executar tarefas com base em um conjunto de etapas. Esse arquivo especifica uma execução condicional, dependendo do branch em que o Cloud Build está buscando o código. Por exemplo:

    • Para ramificações dev e prod, as seguintes etapas são executadas:

      1. terraform init
      2. terraform plan
      3. terraform apply
    • Para qualquer outra ramificação, as etapas a seguir são executadas:

      1. terraform init para todas as subpastas environments
      2. terraform plan para todas as subpastas environments

A razão pela qual terraform init e terraform plan são executados para todas as subpastas environments é garantir que as alterações propostas sejam mantidas para cada ambiente. Dessa maneira, antes de mesclar a solicitação de envio, é possível analisar os planos para garantir que o acesso não seja concedido a uma entidade não autorizada, por exemplo.

Como configurar o Terraform para armazenar o estado em um bucket do Cloud Storage

Por padrão, o Terraform armazena o estado localmente em um arquivo chamado terraform.tfstate. Essa configuração padrão pode dificultar o uso do Terraform para as equipes, especialmente quando muitos usuários o executam ao mesmo tempo e cada máquina tem o próprio entendimento da infraestrutura atual.

Para ajudar a evitar esses problemas, esta seção configura um estado remoto (em inglês) que aponta para um bucket do Cloud Storage. O estado remoto é um recurso de back-ends e, neste tutorial, é configurado nos arquivos backend.tf, por exemplo:

# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

terraform {
  backend "gcs" {
    bucket = "PROJECT_ID-tfstate"
    prefix = "env/dev"
  }
}

Nas etapas a seguir, você cria um bucket do Cloud Storage e altera alguns arquivos para apontar para seu novo bucket e seu projeto do Google Cloud.

  1. No Cloud Shell, crie o bucket do Cloud Storage:

    PROJECT_ID=$(gcloud config get-value project)
    gsutil mb gs://${PROJECT_ID}-tfstate
    
  2. Ative o Controle de versões do objeto para manter o histórico das implantações:

    gsutil versioning set on gs://${PROJECT_ID}-tfstate
    

    Ativar o controle de versões do objeto aumenta os custos de armazenamento, que podem ser reduzidos com a configuração do Gerenciamento do ciclo de vida de objetos para excluir versões mais antigas.

  3. Substitua o marcador PROJECT_ID pelo código do projeto nos arquivos terraform.tfvars e backend.tf:

    cd ~/solutions-terraform-cloudbuild-gitops
    sed -i s/PROJECT_ID/$PROJECT_ID/g environments/*/terraform.tfvars
    sed -i s/PROJECT_ID/$PROJECT_ID/g environments/*/backend.tf
    
  4. Verifique se todos os arquivos foram atualizados:

    git status
    

    O resultado será semelhante ao seguinte:

    On branch dev
    Your branch is up-to-date with 'origin/dev'.
    Changes not staged for commit:
     (use "git add <file>..." to update what will be committed)
     (use "git checkout -- <file>..." to discard changes in working directory)
           modified:   environments/dev/backend.tf
           modified:   environments/dev/terraform.tfvars
           modified:   environments/prod/backend.tf
           modified:   environments/prod/terraform.tfvars
    no changes added to commit (use "git add" and/or "git commit -a")
    
  5. Confirme e envie suas alterações por push:

    git add --all
    git commit -m "Update project IDs and buckets"
    git push origin dev
    

    Dependendo da sua configuração do GitHub, será preciso se autenticar para enviar as alterações anteriores.

Como conceder permissões à sua conta de serviço do Cloud Build

Para permitir que a conta de serviço do Cloud Build execute scripts do Terraform com o objetivo de gerenciar recursos do Google Cloud, você precisa conceder acesso adequado ao projeto. Para simplificar, o acesso ao editor do projeto é concedido neste tutorial. Porém, quando o papel de editor do projeto tem uma permissão ampla, em ambientes de produção, siga as práticas recomendadas de segurança de TI da sua empresa, geralmente fornecendo acesso com menos privilégios.

  1. No Cloud Shell, recupere o e-mail da conta do serviço do Cloud Build do seu projeto:

    CLOUDBUILD_SA="$(gcloud projects describe $PROJECT_ID \
        --format 'value(projectNumber)')@cloudbuild.gserviceaccount.com"
    
  2. Conceda o acesso necessário à sua conta de serviço do Cloud Build:

    gcloud projects add-iam-policy-binding $PROJECT_ID \
        --member serviceAccount:$CLOUDBUILD_SA --role roles/editor
    

Como conectar diretamente o Cloud Build ao seu repositório GitHub

Esta seção mostra como instalar o app GitHub do Cloud Build. Essa instalação permite que você conecte seu repositório do GitHub ao projeto do Google Cloud para que o Cloud Build possa aplicar automaticamente seus manifestos do Terraform toda vez que você criar uma nova ramificação ou um código push no GitHub.

As etapas a seguir fornecem instruções para instalar o app apenas para o repositório solutions-terraform-cloudbuild-gitops, mas você pode optar por instalar o app para mais ou todos os seus repositórios.

  1. Acesse a página do Marketplace do GitHub para o aplicativo Cloud Build:

    Abrir a página do aplicativo Cloud Build (em inglês)

  2. Se esta for a primeira vez que você configura um aplicativo no GitHub, clique em Configurar com o Google Cloud Build. Caso contrário, clique em Editar seu plano, selecione suas informações de faturamento e, na página Editar seu plano, clique em Conceder acesso a este aplicativo.

  3. Na página Install Google Cloud Build, selecione Only select repositories e digite your-user/solutions-terraform-cloudbuild-gitops para se conectar seu repositório bifurcado.

  4. Clique em Instalar.

  5. Faça login no Google Cloud.

    A página Autorização é exibida. É solicitado que você autorize o app GitHub do Cloud Build a se conectar ao Google Cloud.

    Faça login no Google Cloud

  6. Clique em Authorize Google Cloud Build by GoogleCloudBuild.

    Você será redirecionado para o Console do Cloud.

  7. Selecione o projeto do Google Cloud em que você está trabalhando.

  8. Se você concordar com os termos e condições, marque a caixa de seleção e clique em Avançar.

  9. Na etapa Seleção de repositório, selecione your-user/solutions-terraform-cloudbuild-gitops para se conectar ao seu projeto do Google Cloud e, em seguida, clique em Conectar.

  10. Clique em Concluído e depois em Conectar.

O app GitHub do Cloud Build agora está configurado, e seu repositório do GitHub está vinculado ao seu projeto do Google Cloud. A partir de agora, as alterações no repositório GitHub acionam execuções do Cloud Build, que informam os resultados de volta ao GitHub usando as Verificações do GitHub (em inglês).

Como alterar a configuração do ambiente em um novo branch de recurso

Até agora, você configurou a maior parte do seu ambiente. Portanto, é hora de fazer algumas alterações no código do seu ambiente de desenvolvimento.

  1. No GitHub, acesse a página principal do seu repositório bifurcado.
  2. Verifique se você está na ramificação dev.

    Verifique se você está no branch de desenvolvimento.

  3. Para abrir o arquivo para edição, acesse o arquivo modules/firewall/main.tf e clique no ícone de lápis.

  4. Na linha 15, corrija o erro de digitação "http-server**2**" no campo target_tags.

    O valor precisa ser "http-server".

  5. Adicione uma mensagem de confirmação na parte inferior da página, como "Corrigindo o destino do firewall http", e selecione Criar um novo branch para esta confirmação.

  6. Clique em Propor alteração de arquivo.

  7. Na página seguinte, clique em Criar solicitação de envio para abrir uma nova solicitação com sua alteração.

    Depois que a solicitação de envio for aberta, um job do Cloud Build será iniciado automaticamente.

  8. Clique em Mostrar todas as verificações e aguarde a verificação ficar verde.

    Mostre todas as verificações em uma solicitação de envio.

  9. Clique em Detalhes para ver mais informações, incluindo a saída do terraform plan em Ver mais detalhes no Google Cloud Build.

Observe que o job Cloud Build executou o pipeline definido no arquivo cloudbuild.yaml. Como discutido anteriormente, esse pipeline tem comportamentos diferentes, dependendo do branch que está sendo buscado. A construção verifica se a variável $BRANCH_NAME corresponde a alguma pasta do ambiente. Nesse caso, o Cloud Build executa terraform plan para esse ambiente. Caso contrário, o Cloud Build executa terraform plan em todos os ambientes para garantir que a alteração proposta seja válida para todos eles. Se a execução de algum desses planos falhar, a compilação também falhará.

- id: 'tf plan'
  name: 'hashicorp/terraform:0.11.14'
  entrypoint: 'sh'
  args:
  - '-c'
  - |
      if [ -d "environments/$BRANCH_NAME/" ]; then
        cd environments/$BRANCH_NAME
        terraform plan
      else
        for dir in environments/*/
        do
          cd ${dir}
          env=${dir%*/}
          env=${env#*/}
          echo ""
          echo "*************** TERRAFOM PLAN ******************"
          echo "******* At environment: ${env} ********"
          echo "*************************************************"
          terraform plan || exit 1
          cd ../../
        done
      fi 

Da mesma forma, o comando terraform apply é executado para ramificações do ambiente, mas é completamente ignorado em qualquer outro caso. Nesta seção, você enviou uma alteração de código para uma nova filial, portanto, nenhuma implantação de infraestrutura foi aplicada ao seu projeto do Google Cloud.

- id: 'tf apply'
  name: 'hashicorp/terraform:0.11.14'
  entrypoint: 'sh'
  args:
  - '-c'
  - |
      if [ -d "environments/$BRANCH_NAME/" ]; then
        cd environments/$BRANCH_NAME
        terraform apply -auto-approve
      else
        echo "***************************** SKIPPING APPLYING *******************************"
        echo "Branch '$BRANCH_NAME' does not represent an oficial environment."
        echo "*******************************************************************************"
      fi

Como aplicar o sucesso da execução do Cloud Build antes de mesclar branches

Para garantir que as mesclagens possam ser aplicadas apenas quando as respectivas execuções do Cloud Build forem bem-sucedidas, continue com as seguintes etapas:

  1. No GitHub, acesse a página principal do seu repositório bifurcado.
  2. No nome do repositório, clique em Settings.
  3. No menu esquerdo, clique em Branches.
  4. Em Branch protection rules, clique em Add rule.
  5. Em Padrão de nome do branch, selecione dev.
  6. Em Configurações de regras, selecione Exigir que as verificações de status sejam aprovadas antes da mesclagem e, em Verificações de status encontradas na última semana para este repositório, clique em Compilar.
  7. Clique em Criar.
  8. Repita as etapas 5 a 8, configurando Branch name pattern como prod.

Essa configuração é importante para proteger as ramificações dev e prod. Ou seja, as confirmações precisam primeiro ser enviadas para outro branch e somente então podem ser mescladas ao branch protegido. Neste tutorial, a proteção exige que a execução do Cloud Build seja bem-sucedida para que a mesclagem seja permitida.

Como promover alterações no ambiente de desenvolvimento

Você tem uma solicitação de envio aguardando para ser mesclada. É hora de aplicar o estado que você quer ao seu ambiente dev.

  1. No GitHub, acesse a página principal do seu repositório bifurcado.
  2. No nome do seu repositório, clique em Pull requests.
  3. Clique na solicitação de envio que você acabou de criar.
  4. Clique em Merge pull request e depois em Confirm merge.

    Confirme a mesclagem.

  5. Verifique se um novo Cloud Build foi acionado:

    Acessar a página do Cloud Build

  6. Abra a compilação e verifique os registros.

    Quando a compilação terminar, você verá algo assim:

    Step #3 - "tf apply": external_ip = external-ip-value
    Step #3 - "tf apply": firewall_rule = dev-allow-http
    Step #3 - "tf apply": instance_name = dev-apache2-instance
    Step #3 - "tf apply": network = dev
    Step #3 - "tf apply": subnet = dev-subnet-01
    
  7. Copie external-ip-value e abra o endereço em um navegador da web.

    Esse provisionamento pode levar alguns segundos para inicializar a VM e propagar a regra do firewall, mas, no final, você verá Environment: dev no navegador da Web.

Como promover alterações no ambiente de produção

Agora que seu ambiente de desenvolvimento foi totalmente testado, promova seu código de infraestrutura para produção.

  1. No GitHub, acesse a página principal do seu repositório bifurcado.
  2. Clique em New pull request.
  3. Para base repository, selecione seu repositório recém-bifurcado.
  4. Para base, selecione prod e para compare, selecione dev.
  5. Clique em Criar solicitação de envio
  6. Para title, digite um título como Promoting networking changes e clique em Criar solicitação de envio.
  7. Revise as alterações propostas, incluindo os detalhes de terraform plan do Cloud Build e clique em Mesclar solicitação de envio.
  8. Clique em Confirmar mesclagem.
  9. No Cloud Console, abra a página Histórico de versões para ver suas alterações sendo aplicadas ao ambiente de produção:

    Acessar a página do Cloud Build

  10. Aguarde o término da compilação e verifique os registros.

    No final dos registros, você verá algo assim:

    Step #3 - "tf apply": external_ip = external-ip-value
    Step #3 - "tf apply": firewall_rule = prod-allow-http
    Step #3 - "tf apply": instance_name = prod-apache2-instance
    Step #3 - "tf apply": network = prod
    Step #3 - "tf apply": subnet = prod-subnet-01
    
  11. Copie external-ip-value e abra o endereço em um navegador da web.

    Esse provisionamento pode levar alguns segundos para inicializar a VM e propagar a regra do firewall, mas, no final, você vê Environment: prod no navegador da Web.

Você configurou com êxito um pipeline de infraestrutura como código sem servidor no Cloud Build. No futuro, tente realizar as ações a seguir:

  • Adicione implantações para casos de uso separados.
  • Crie ambientes adicionais para refletir suas necessidades.
  • Use um projeto por ambiente em vez de uma VPC por ambiente.

Limpar

Depois de concluir o tutorial, limpe os recursos que você criou no Google Cloud para não ser cobrado por eles no futuro.

Excluir o projeto

  1. No Console do Cloud, acesse a página Gerenciar recursos:

    Acessar a página "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.

A seguir