Gerenciamento de infraestrutura como código com Terraform, Jenkins e GitOps

Neste tutorial, explicamos como gerenciar a infraestrutura como código com o Terraform e o Jenkins usando o GitOps conhecido. O tutorial é destinado a desenvolvedores e operadores que procuram práticas recomendadas para gerenciar a infraestrutura da maneira como gerenciam aplicativos de software. Para acompanhar o artigo, é necessário ter familiaridade com o Terraform, Jenkins, GitHub, Google Kubernetes Engine (GKE) e Google Cloud.

Arquitetura

A arquitetura usada neste tutorial usa branches do GitHub (dev e prod) para representar ambientes reais de desenvolvimento e produção. Esses ambientes são definidos por redes Virtual Private Cloud (VPC), dev e prod, em um projeto do Google Cloud.

Proposta de infraestrutura

Como mostra o diagrama de arquitetura a seguir, o processo começa quando um desenvolvedor ou operador faz uma proposta de infraestrutura para um branch não protegido do GitHub, geralmente um branch de recursos. Quando apropriado, essa ramificação de recurso pode ser promovida para o ambiente de desenvolvimento por meio de uma solicitação de envio (PR) para a ramificação dev. O Jenkins aciona automaticamente um job para executar o pipeline de validação. Esse job executa o comando terraform plan e informa o resultado da validação de volta ao GitHub e inclui um link para um relatório detalhado de alteração da infraestrutura. Essa etapa é essencial porque permite analisar possíveis alterações com colaboradores e adicionar confirmações de acompanhamento antes que as alterações sejam mescladas no branch dev.

Arquitetura mostrando as práticas do GitOps para gerenciar execuções do Terraform.

Implantação Dev

Se o processo de validação for bem-sucedido e você aprovar as alterações de infraestrutura propostas, poderá mesclar a solicitação de envio na ramificação dev. O diagrama a seguir mostra esse processo.

Mescle a solicitação de envio para a ramificação

Quando a mesclagem é concluída, o Jenkins aciona outro job para executar o pipeline de implantação. Neste cenário, o job aplica os manifestos do Terraform no ambiente de desenvolvimento para alcançar o estado desejado. Essa etapa é importante porque permite testar o código do Terraform antes de promovê-lo para produção.

Implantação Prod

Depois de testar e estar pronto para promover as alterações na produção, mescle o branch dev no branch prod para acionar a instalação da infraestrutura no ambiente de produção. O diagrama a seguir mostra esse processo.

Como mesclar o branch

O mesmo processo de validação é executado quando você cria a solicitação de envio. Esse processo permite que a equipe de operações revise e aprove as alterações propostas para produção.

Objetivos

  • Configurar seu repositório GitHub.
  • Criar armazenamento de estado remoto do Terraform
  • Criar um cluster do GKE e instalar o Jenkins.
  • 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.

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. 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, configure o ID do projeto e defina o nome de usuário e o endereço de e-mail do GitHub:
    PROJECT_ID=PROJECT_ID
    GITHUB_USER=YOUR_GITHUB_USER
    GITHUB_EMAIL=YOUR_EMAIL_ADDRESS
    gcloud config set project $PROJECT_ID
    

    Se você nunca acessou o GitHub a partir do Cloud Shell, configure-o com seu nome de usuário e endereço de e-mail:

    git config --global user.email "$GITHUB_EMAIL"
    git config --global user.name "$GITHUB_USER"
    

    O GitHub usa essas informações para identificar você como o autor das confirmações que você cria no Cloud Shell.

Como configurar seu repositório GitHub

Neste tutorial, você usa um único repositório do GitHub para definir a infraestrutura em nuvem. Você orquestra essa infraestrutura ao ter branches diferentes que correspondem 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.

Para começar, você precisa bifurcar o repositório solutions-terraform-jenkins-gitops.

  1. No GitHub, acesse o repositório solutions-terraform-jenkins-gitops.
  2. Clique em Bifurcar.

    Como bifurcar um repositório no GitHub.

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

  3. No Cloud Shell, clone esse repositório bifurcado:

    cd ~
    git clone https://github.com/$GITHUB_USER/solutions-terraform-jenkins-gitops.git
    cd ~/solutions-terraform-jenkins-gitops
    

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

    • example-pipelines/: contém subpastas com o pipeline de exemplo usado neste tutorial.
    • example-create/: contém o código do Terraform para criar uma máquina virtual no ambiente.
    • environments/: contém as pastas de ambiente dev e prod com configurações de back-end e links para os arquivos da pasta example-create/.
    • Pasta jenkins-gke/: contém os scripts necessários para implantar o Jenkins em um novo cluster do GKE.
    • tf-gke/: contém o código do Terraform para implantação no GKE e a instalação do Jenkins e dos recursos dependentes.

Como criar armazenamento de estado remoto do Terraform

O estado do Terraform é armazenado localmente por padrão. No entanto, recomendamos que você armazene o estado no armazenamento central remoto que pode ser acessado de qualquer sistema. Essa abordagem ajuda a evitar a criação de várias cópias em diferentes sistemas, o que pode levar a configurações e estados de infraestrutura incompatíveis.

Nesta seção, você configura um bucket do Cloud Storage que armazena o estado remoto do Terraform.

  1. No Cloud Shell, crie um bucket do Cloud Storage.

    gsutil mb gs://${PROJECT_ID}-tfstate
    
  2. Ative o controle de versão de objetos para manter o histórico dos estados:

    gsutil versioning set on gs://${PROJECT_ID}-tfstate
    
  3. Substitua o marcador PROJECT_ID pelo código do projeto nos arquivos terraform.tfvars e backend.tf:

    sed -i.bak "s/PROJECT_ID/${PROJECT_ID}/g" ./example-pipelines/environments/*/terraform.tfvars
    sed -i.bak "s/PROJECT_ID/${PROJECT_ID}/g" ./example-pipelines/environments/*/backend.tf
    
    sed -i.bak "s/PROJECT_ID/${PROJECT_ID}/g" ./jenkins-gke/tf-gke/terraform.tfvars
    sed -i.bak "s/PROJECT_ID/${PROJECT_ID}/g" ./jenkins-gke/tf-gke/backend.tf
    
  4. Verifique se todos os arquivos foram atualizados:

    git status
    

    A saída será assim:

    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:   example-pipelines/environments/dev/backend.tf
            modified:   example-pipelines/environments/dev/terraform.tfvars
            modified:   example-pipelines/environments/prod/backend.tf
            modified:   example-pipelines/environments/prod/terraform.tfvars
            modified:   jenkins-gke/tf-gke/backend.tf
            modified:   jenkins-gke/tf-gke/terraform.tfvars
    
  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, talvez seja necessário realizar a autenticação para enviar as alterações anteriores.

Como criar um cluster do GKE e instalar o Jenkins

Nesta seção, você usará o Terraform e o Helm para configurar o ambiente para gerenciar a infraestrutura como código. Primeiro, use o Terraform e o Cloud Foundations Toolkit para configurar uma nuvem privada virtual, um cluster do GKE e uma Identidade da carga de trabalho. Em seguida, use o helm para instalar o Jenkins sobre esse ambiente.

Antes de começar a executar comandos do Terraform, é necessário criar um token de acesso pessoal do GitHub. Esse token é necessário para permitir que o Jenkins acesse o repositório bifurcado.

Criar um token de acesso pessoal do GitHub

  1. Faça login no GitHub.
  2. Clique na foto do perfil e em Configurações.
  3. Clique em Configurações do desenvolvedor e em Tokens de acesso pessoal.
  4. Clique em Gerar novo token, insira uma descrição no campo Nota e selecione o escopo repo.
  5. Clique em Gerar token e copie o token recém-criado para a área de transferência.

    Como gerar e copiar um token para a área de transferência.

  6. No Cloud Shell, salve o token na variável GITHUB_TOKEN. Esse conteúdo variável é armazenado depois como um secret no cluster do GKE.

    GITHUB_TOKEN="NEWLY_CREATED_TOKEN"
    

Criar um cluster do GKE e instalar o Jenkins

Agora crie o cluster do GKE. Esse cluster inclui uma Identidade da carga de trabalho (jenkins-wi-jenkins@PROJECT_ID.iam.gserviceaccount.com), que permite dar ao Jenkins a permissão necessária no Contas de serviço no Console do Cloud. Devido às propriedades de segurança e maleabilidade dela, é recomendável usar a identidade da carga de trabalho para acessar os serviços do Google Cloud no GKE.

Para gerenciar a infraestrutura do Google Cloud como código, o Jenkins precisa ser autenticado para usar as APIs do Google Cloud. Nas etapas a seguir, o Terraform configura a conta de serviço do Kubernetes (KSA, na sigla em inglês) usada pelo Jenkins para atuar como uma conta de serviço do Google. (GSA). Essa configuração permite que o Jenkins se autentique automaticamente como o GSA ao acessar as APIs do Google Cloud.

Para simplificar, conceda acesso ao editor do projeto para este tutorial. No entanto, como esse papel tem um conjunto amplo de permissões, em ambientes de produção, você precisa seguir as práticas recomendadas de segurança de TI da sua empresa, o que geralmente significa fornecer acesso com menos privilégios.

  1. No Cloud Shell, instale o Terraform:

    wget https://releases.hashicorp.com/terraform/0.12.24/terraform_0.12.24_linux_amd64.zip
    unzip terraform_0.12.24_linux_amd64.zip
    sudo mv terraform /usr/local/bin/
    rm terraform_0.12.24_linux_amd64.zip
    
  2. Crie o cluster do GKE e instale o Jenkins:

    cd jenkins-gke/tf-gke/
    terraform init
    terraform plan --var "github_username=$GITHUB_USER" --var "github_token=$GITHUB_TOKEN"
    terraform apply --auto-approve --var "github_username=$GITHUB_USER" --var "github_token=$GITHUB_TOKEN"
    

    Esse processo pode levar alguns minutos para ser concluído. A resposta será semelhante a:

    Apply complete! Resources: 28 added, 0 changed, 0 destroyed.
    
    Outputs:
    
    ca_certificate = LS0tLS1CRU..
    client_token = <sensitive>
    cluster_name = jenkins
    gcp_service_account_email = jenkins-wi-jenkins@PROJECT_ID.iam.gserviceaccount.com
    jenkins_k8s_config_secrets = jenkins-k8s-config
    jenkins_project_id = PROJECT_ID
    k8s_service_account_name = jenkins-wi-jenkins
    kubernetes_endpoint = <sensitive>
    service_account = tf-gke-jenkins-k253@PROJECT_ID.iam.gserviceaccount.com
    zone = us-east4-a
    

    Quando o Jenkins é implantado no cluster do GKE recém-criado, o diretório inicial do Jenkins é armazenado em um volume permanente de acordo com a documentação do gráfico do Helm (em inglês). Essa implantação também vem com um pipeline de várias ramificações pré-configurado com example-pipelines/environments/Jenkinsfile, que é acionado por solicitações de envio. e mescla as ramificações de dev e prod.

  3. Volte para a pasta principal:

    cd ../..
    
  4. Recupere as credenciais do cluster que você acabou de criar:

    gcloud container clusters get-credentials jenkins --zone=us-east4-a --project=${PROJECT_ID}
    
  5. Recupere o URL e as credenciais do Jenkins:

    JENKINS_IP=$(kubectl get service jenkins -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    JENKINS_PASSWORD=$(kubectl get secret jenkins -o jsonpath="{.data.jenkins-admin-password}" | base64 --decode);echo
    printf "Jenkins url: http://$JENKINS_IP\nJenkins user: admin\nJenkins password: $JENKINS_PASSWORD\n"
    
  6. Faça login no Jenkins usando as informações de saída da etapa anterior.

  7. Configure o local do Jenkins para que o GitHub possa criar links diretos para suas versões. Clique em Manage Jenkins > Configure System e, no campo Jenkins URL, configure o URL do Jenkins.

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

A maior parte do seu ambiente está configurada agora. Portanto, é hora de fazer algumas alterações no código.

  1. No Cloud Shell, crie um novo branch de recursos em que é possível trabalhar sem afetar os outros membros da equipe:

    git checkout -b change-vm-name
    
  2. Altere o nome da máquina virtual:

    cd example-pipelines/example-create
    sed -i.bak "s/\${var.environment}-001/\${var.environment}-new/g" main.tf
    

    Você está alterando o arquivo main.tf na pasta example-create. Este arquivo é vinculado pelas pastas de ambiente dev e prod, o que significa que sua alteração é propagada para os dois ambientes.

  3. Envie a alteração do código para o branch de recurso do GitHub:

    git commit -am "change vm name"
    git push --set-upstream origin change-vm-name
    
  4. No GitHub, acesse a página principal do seu repositório bifurcado.

  5. Clique na guia Solicitações de envio do seu repositório e, em seguida, clique em Nova solicitação de envio.

  6. Para o repositório base, selecione o repositório bifurcado.

    Como criar uma solicitação de envio para o repositório base.

  7. Para base, selecione dev e para compare, selecione change-vm-name.

    Como selecionar a base e comparar garfos.

  8. Clique em Criar solicitação de envio.

  9. Quando sua solicitação de envio é aberta, um job do Jenkins é iniciado automaticamente (o Jenkins pode levar um minuto ou mais para confirmar a nova solicitação pull). Clique em Mostrar todas as verificações e aguarde a verificação ficar verde.

    Aguarde a verificação ficar verde.

  10. Clique em Detalhes para ver mais informações, incluindo a saída do comando terraform plan.

    Mais detalhes sobre os resultados, incluindo a saída de

O job do Jenkins executou o pipeline definido em Jenkinsfile. Esse pipeline tem comportamentos diferentes, dependendo do branch que está sendo buscado. A construção verifica se a variável TARGET_ENV corresponde a alguma pasta do ambiente. Se houver correspondência, o Jenkins executará terraform plan para esse ambiente. Caso contrário, o Jenkins 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 falhará.

A razão pela qual terraform plan é executado para todas as subpastas example-pipelines/environments é garantir que as alterações propostas sejam aplicadas a todos os ambientes. Dessa maneira, antes de mesclar a solicitação de envio, é possível analisar os planos para garantir que você não esteja concedendo acesso a uma entidade não autorizada, por exemplo.

Este é o código terraform plan em JENKINSFILE:

stage('TF plan') {
  when { anyOf {branch "prod";branch "dev";changeRequest() } }
  steps {
    container('terraform') {
      sh '''
      if [[ $CHANGE_TARGET ]]; then
        TARGET_ENV=$CHANGE_TARGET
      else
        TARGET_ENV=$BRANCH_NAME
      fi

      if [ -d "example-pipelines/environments/${TARGET_ENV}/" ]; then
        cd example-pipelines/environments/${TARGET_ENV}
        terraform plan
      else
        for dir in example-pipelines/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 é ignorado em qualquer outro caso. Nesta seção, você enviou uma alteração de código para um novo branch. Portanto, nenhuma implantação de infraestrutura foi aplicada ao seu projeto do Cloud.

Este é o código terraform apply em JENKINSFILE:

stage('TF Apply') {
  when { anyOf {branch "prod";branch "dev" } }
  steps {
    container('terraform') {
      sh '''
      TARGET_ENV=$BRANCH_NAME

      if [ -d "example-pipelines/environments/${TARGET_ENV}/" ]; then
        cd example-pipelines/environments/${TARGET_ENV}
        terraform apply -input=false -auto-approve
      else
        echo "*************** SKIPPING APPLY ******************"
        echo "Branch '$TARGET_ENV' does not represent an official environment."
        echo "*************************************************"
      fi'''
    }
  }
}

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

Você pode garantir que as mesclagens sejam aplicadas somente quando as execuções de job do Jenkins forem bem-sucedidas.

  1. No GitHub, acesse a página principal do seu repositório bifurcado.
  2. Para o nome do repositório, clique em Configurações.
  3. Clique em Ramificações.
  4. Em Regras de proteção do branch, clique em Adicionar regra.
  5. Em padrão do nome da ramificação, digite dev.
  6. Em Proteger ramificações correspondentes, selecione Exigir que as verificações de status sejam aprovadas antes da mesclagem e, em seguida, selecione continuous-integration/jenkins/pr-merge.

    (Opcional) Ative as opções Solicitar análises de solicitação de pull antes de mesclar e Incluir administradores para evitar que solicitações de envio não analisadas e não autorizadas sejam mescladas na produção.

  7. Clique em Criar

  8. Repita as etapas 4 a 7, definindo Padrão de nome da ramificação como prod.

Essa configuração é importante para proteger as ramificações dev e prod, o que significa que você precisa primeiro enviar confirmações para outra ramificação e, em seguida, mesclar somente à ramificação protegida. Neste tutorial, a proteção exige que a execução do job do Jenkins seja bem-sucedida para que a mesclagem seja permitida. É possível ver se sua configuração foi aplicada na solicitação de envio recém-criada. Procure as marcas de seleção verdes.

Como verificar se sua configuração foi aplicada

Como promover alterações no ambiente de desenvolvimento

Você tem uma solicitação de envio aguardando para ser mesclada. Agora é possível aplicar o estado que você quer para seu ambiente dev.

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

    Mesclar e confirmar uma solicitação de envio.

  5. No Jenkins, clique em Open Blue Ocean. Em seguida, no projeto de várias ramificações terraform-jenkins-create-demo, na guia Ramificações, verifique o ícone de status para ver se um novo job dev foi acionado. Pode levar alguns minutos para começar.

    Verifique se um novo job **dev** foi acionado.

  6. No Console do Cloud, acesse a página Instâncias de VM e verifique se você tem a VM com o novo nome.

    Acessar instâncias de VM

    Verifique se você tem a VM com o novo nome.

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

Agora que você testou seu ambiente de desenvolvimento, pode promover 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 o repositório base, selecione o repositório que você bifurcar.

    Selecione o repositório que você bifurcar.

  4. Para base, selecione prod e para compare, selecione dev.

    Repositórios bifurcados para base e comparação.

  5. Clique em Criar solicitação de envio

  6. Insira um título, como Promoting vm name change, e clique em Criar solicitação de envio.

  7. Aguarde até que o verificador fique verde (pode levar um ou dois minutos) e clique no link Detalhes ao lado de continuous-integration/jenkins/pr-merge.

    Aguardando o verificador ficar verde.

  8. No Jenkins, selecione TF Plan e revise as alterações propostas nos registros.

    Analisar as alterações propostas nos registros.

  9. Se as alterações propostas estiverem corretas, no GitHub, clique em Merge pull request e em Confirm merge.

  10. No Console do Cloud, abra a página Instâncias de VM e verifique se a VM de produção foi implantada.

    Instâncias de VM

    Verifique se a VM de produção foi implantada.

Você configurou um pipeline de infraestrutura como código no Jenkins. No futuro, tente realizar as ações a seguir:

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

Limpeza

Para evitar que os recursos usados neste tutorial sejam cobrados na conta do Google Cloud Platform, faça o seguinte:

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