Entrega contínua no estilo GitOps com o Cloud Build

Nesta página, você aprenderá como criar um pipeline de integração e entrega contínuas (CI/CD, na sigla em inglês) no Google Cloud Platform usando apenas produtos hospedados e a famosa metodologia GitOps.

Há muito tempo que os engenheiros do Google armazenam arquivos de configuração e implantação em nosso repositório de código-fonte principal. Esta metodologia é descrita no capítulo 8 do livro Engenharia de Confiabilidade do Site ( Beyer et. Al., 2016 ), e foi demonstrada por Kelsey Hightower durante sua apresentação feita no Google Cloud Next de 2017 (em inglês). O termo “GitOps” foi cunhado pela Weaveworks. Uma parte fundamental do GitOps é a ideia de “ambientes como código”: descrever as implementações de maneira declarativa, usando arquivos, como manifestos do Kubernetes, armazenados em um repositório Git.

Neste tutorial, você criará um pipeline de CI/CD que automaticamente construirá uma imagem de contêiner com base no código confirmado, armazenará a imagem no Container Registry, atualizará um manifesto do Kubernetes em um repositório Git e implantará o aplicativo no Google Kubernetes Engine usando esse manifesto.

Arquitetura do pipeline de CI/CD

Neste tutorial, usaremos dois repositórios Git:

  • Repositório do aplicativo: contém o código-fonte do próprio aplicativo.
  • Repositório do ambiente: contém os manifestos para a implantação no Kubernetes.

Quando você envia uma alteração para o repositório do aplicativo, o pipeline do Cloud Build executa testes, cria uma imagem de contêiner e a envia para o Container Registry. Depois de enviar a imagem, o Cloud Build atualiza o manifesto de implantação e o envia para o repositório do ambiente. Isso aciona outro pipeline do Cloud Build que aplica o manifesto ao cluster do GKE e, se bem-sucedido, armazena o manifesto em outra ramificação do repositório do ambiente.

Mantemos os repositórios do aplicativo e do ambiente separados porque eles têm utilidades e ciclos de vida diferentes. O repositório do aplicativo é dedicado a um aplicativo específico e é usado principalmente por pessoas reais. Os principais usuários do repositório do ambiente são sistemas automatizados, como o Cloud Build, e ele pode ser compartilhado por vários aplicativos. O repositório do ambiente pode ter várias ramificações, cada uma mapeando para um ambiente específico (você usará apenas o de produção neste tutorial). Ele cita uma imagem de contêiner específica, o que não acontece com o repositório do aplicativo.

Ao terminar este tutorial, você terá um sistema em que facilmente poderá:

  • distinguir entre implantações com falha e bem-sucedidas observando o histórico do Cloud Build;
  • acessar o manifesto usado atualmente observando o branch de produção do repositório do ambiente;
  • reverter para qualquer versão anterior bastando executar novamente a versão correspondente do Cloud Build.

Fluxo do pipeline de CI/CD

Sobre este tutorial

Neste tutorial, usamos o Cloud Source Repositories para hospedar repositórios Git. No entanto, é possível chegar aos mesmos resultados com produtos de terceiros, como GitHub, Bitbucket ou GitLab.

Este pipeline não implementa um mecanismo de validação antes da implantação. Se você usa o GitHub, o Bitbucket ou o GitLab, é possível modificar o pipeline para usar uma solicitação pull com essa finalidade.

Recomendamos o Spinnaker para as equipes que querem implementar padrões avançados de implantação (azul/verde, análise canário, várias nuvens etc.). No entanto, o conjunto de recursos dessa solução talvez não seja necessário para que organizações e projetos menores tenham uma estratégia de CI/CD bem-sucedida. Neste tutorial, você aprenderá como criar um ajuste de pipeline de CI/CD para aplicativos hospedados no GKE com ferramentas simples.

Para simplificar, este tutorial usa um único ambiente, o de produção, no repositório do ambiente, mas é possível estendê-lo à implantação em vários ambientes, se necessário.

Custos

Antes de começar

  1. Selecione ou crie um projeto do Cloud.

    ACESSE A PÁGINA "GERENCIAR RECURSOS"

  2. Ative o faturamento do projeto.

    ATIVAR FATURAMENTO

  3. Abra o Cloud Shell para executar os comandos listados neste tutorial.

    ACESSAR O CLOUD SHELL

  4. Se o comando gcloud config get-value project não retornar o ID do projeto que foi selecionado, configure o Cloud Shell para usar seu projeto.

    gcloud config set project [PROJECT_ID]
    
  5. No Cloud Shell, ative as APIs necessárias.

    gcloud services enable container.googleapis.com \
        cloudbuild.googleapis.com \
        sourcerepo.googleapis.com \
        containeranalysis.googleapis.com
    
  6. No Cloud Shell, crie um cluster do GKE que você usará para implantar o aplicativo de amostra deste tutorial.

    gcloud container clusters create hello-cloudbuild \
        --num-nodes 1 --zone us-central1-b
    
  7. Se você nunca usou o Git no Cloud Shell, configure-o com seu nome e endereço de e-mail. O Git usará essas informações para identificar você como autor das confirmações a serem criadas no Cloud Shell.

    git config --global user.email "[YOUR_EMAIL_ADDRESS]"
    git config --global user.name "[YOUR_NAME]"
    
  8. Execute o auxiliar de credenciais do gcloud. Isso conectará sua conta de usuário do GCP ao Cloud Source Repositories.

    git config credential.helper gcloud.sh
    

Ao concluir este tutorial, exclua os recursos criados para evitar o faturamento contínuo. Veja mais detalhes em Como fazer a limpeza.

Criar repositórios Git no Cloud Source Repositories

Nesta seção, você criará os dois repositórios Git, do aplicativo e do ambiente, que serão usados neste tutorial e inicializará o repositório do aplicativo com um código de amostra.

  1. No Cloud Shell, crie os dois repositórios Git.

    gcloud source repos create hello-cloudbuild-app
    gcloud source repos create hello-cloudbuild-env
    
  2. Clone o código de amostra do GitHub.

    cd ~
    git clone https://github.com/GoogleCloudPlatform/gke-gitops-tutorial-cloudbuild \
        hello-cloudbuild-app
    
  3. Configure o Cloud Source Repositories como remoto.

    cd ~/hello-cloudbuild-app
    PROJECT_ID=$(gcloud config get-value project)
    git remote add google \
        "https://source.developers.google.com/p/${PROJECT_ID}/r/hello-cloudbuild-app"
    

O código que você clonou contém um aplicativo simples "Hello World".

from flask import Flask
app = Flask('hello-cloudbuild')

@app.route('/')
def hello():
  return "Hello World!\n"

if __name__ == '__main__':
  app.run(host = '0.0.0.0', port = 8080)

Criar uma imagem de contêiner com o Cloud Build

O código que você clonou já contém o Dockerfile a seguir.

FROM python:3.7-slim
RUN pip install flask
WORKDIR /app
COPY app.py /app/app.py
ENTRYPOINT ["python"]
CMD ["/app/app.py"]

Com esse Dockerfile, crie uma imagem de contêiner com o Cloud Build e armazene-a no Container Registry.

  1. No Cloud Shell, crie uma versão do Cloud Build com base na confirmação mais recente usando o comando abaixo.

    cd ~/hello-cloudbuild-app
    COMMIT_ID="$(git rev-parse --short=7 HEAD)"
    gcloud builds submit --tag="gcr.io/${PROJECT_ID}/hello-cloudbuild:${COMMIT_ID}" .
    

    O Cloud Build faz o streaming dos registros gerados pela criação da imagem do contêiner para seu terminal quando você executa esse comando.

  2. Depois de concluir a versão, verifique se a nova imagem do contêiner está realmente disponível no Container Registry.

    ACESSAR O CONTAINER REGISTRY

    Imagem de hello-cloudbuild no Container Registry

Criar o pipeline de integração contínua

Nesta seção, você configurará o Cloud Build para executar automaticamente um teste de unidade pequeno, construirá a imagem do contêiner e, por fim, enviará essa imagem para o Container Registry. Enviar uma confirmação nova para o Cloud Source Repositories aciona automaticamente o pipeline. O arquivo cloudbuild.yaml já incluído no código é a configuração do canal.

steps:
# This step runs the unit tests on the app
- name: 'python:3.7-slim'
  id: Test
  entrypoint: /bin/sh
  args:
  - -c
  - 'pip install flask && python test_app.py -v'

# This step builds the container image.
- name: 'gcr.io/cloud-builders/docker'
  id: Build
  args:
  - 'build'
  - '-t'
  - 'gcr.io/$PROJECT_ID/hello-cloudbuild:$SHORT_SHA'
  - '.'

# This step pushes the image to Container Registry
# The PROJECT_ID and SHORT_SHA variables are automatically
# replaced by Cloud Build.
- name: 'gcr.io/cloud-builders/docker'
  id: Push
  args:
  - 'push'
  - 'gcr.io/$PROJECT_ID/hello-cloudbuild:$SHORT_SHA'
  1. Abra a página Gatilhos do Cloud Build.

    ACESSAR “ACIONADORES”

  2. Clique em Criar gatilho.

  3. Preencha as opções a seguir:

    • No campo Nome, digite hello-cloudbuild.
    • Em Evento, selecione Mover para uma ramificação.
    • Em Origem, selecione hello-cloudbuild-app como Repositório e ^master$ como Ramificação.
    • Em Configuração da compilação, selecione arquivo de configuração do Cloud Build.
    • No campo local do arquivo de configuração do Cloud Build, digite cloudbuild.yaml depois do /.
  4. Clique em Criar para salvar o gatilho de compilação.

    Dica: se você precisar criar acionadores de versão para vários projetos, use a API Build Triggers.

  5. No Cloud Shell, envie o código do aplicativo para o Cloud Source Repositories para acionar o pipeline de CI no Cloud Build.

    cd ~/hello-cloudbuild-app
    git push google master
    
  6. Abra o console do Cloud Build.

    ACESSAR O CLOUD BUILD

    Você verá uma versão em execução ou finalizada recentemente. Se quiser, clique na versão para acompanhar a execução e examinar os registros.

Criar o pipeline de entrega contínua

O Cloud Build também é usado para o pipeline de entrega contínua. Esse pipeline é executado sempre que uma confirmação é enviada para o branch candidato do repositório hello-cloudbuild-env. O pipeline aplica a versão nova do manifesto ao cluster do Kubernetes e, se bem-sucedido, copia o manifesto para o branch de produção. Esse processo tem as seguintes propriedades:

  • O branch candidato é um histórico das tentativas de implantação.
  • O branch de produção é um histórico das implantações bem-sucedidas.
  • É possível ver as implantações bem-sucedidas e com falha no Cloud Build.
  • É possível reverter para qualquer implantação anterior, bastando executar novamente a versão correspondente no Cloud Build. A reversão também atualiza o branch de produção para refletir fielmente o histórico de implantações.

Você modificará o pipeline de integração contínua para atualizar o branch candidato do repositório hello-cloudbuild-env, acionando o pipeline de entrega contínua.

Conceder ao Cloud Build acesso ao GKE

Para implantar o aplicativo no cluster do Kubernetes, o Cloud Build precisa do papel de gerenciamento de identidade e acesso do desenvolvedor do Kubernetes Engine.

Shell

No Cloud Shell, execute o comando a seguir:

PROJECT_NUMBER="$(gcloud projects describe ${PROJECT_ID} --format='get(projectNumber)')"
gcloud projects add-iam-policy-binding ${PROJECT_NUMBER} \
    --member=serviceAccount:${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com \
    --role=roles/container.developer

Console

  1. No Console do Google Cloud, abra a página "Configurações do Cloud Build":

    Abrir a página "Configurações do Cloud Build"

    Você verá a página Permissões da conta de serviço:

    Captura de tela da página

  2. Defina o status do papel Desenvolvedor do Kubernetes Engine como Ativado.

Inicializar o repositório hello-cloudbuild-env

É necessário inicializar o repositório hello-cloudbuild-env com dois branches, de produção e candidato, e um arquivo de configuração do Cloud Build que descreve o processo de implantação.

  1. No Cloud Shell, clone o repositório hello-cloudbuild-env e crie o branch de produção. Ele ainda estará vazio.

    cd ~
    gcloud source repos clone hello-cloudbuild-env
    cd ~/hello-cloudbuild-env
    git checkout -b production
    
  2. Copie o arquivo cloudbuild-delivery.yaml disponível no repositório hello-cloudbuild-app e confirme a alteração.

    cd ~/hello-cloudbuild-env
    cp ~/hello-cloudbuild-app/cloudbuild-delivery.yaml ~/hello-cloudbuild-env/cloudbuild.yaml
    git add .
    git commit -m "Create cloudbuild.yaml for deployment"
    

    O arquivo cloudbuild-delivery.yaml descreve o processo de implantação a ser executado no Cloud Build. Isso é feito em duas etapas:

    1. O Cloud Build aplica o manifesto no cluster do GKE.

    2. Se bem-sucedido, o Cloud Build copia o manifesto no branch de produção.

    steps:
    # This step deploys the new version of our container image
    # in the hello-cloudbuild Kubernetes Engine cluster.
    - name: 'gcr.io/cloud-builders/kubectl'
      id: Deploy
      args:
      - 'apply'
      - '-f'
      - 'kubernetes.yaml'
      env:
      - 'CLOUDSDK_COMPUTE_ZONE=us-central1-b'
      - 'CLOUDSDK_CONTAINER_CLUSTER=hello-cloudbuild'
    
    # This step copies the applied manifest to the production branch
    # The COMMIT_SHA variable is automatically
    # replaced by Cloud Build.
    - name: 'gcr.io/cloud-builders/git'
      id: Copy to production branch
      entrypoint: /bin/sh
      args:
      - '-c'
      - |
        set -x && \
        # Configure Git to create commits with Cloud Build's service account
        git config user.email $(gcloud auth list --filter=status:ACTIVE --format='value(account)') && \
        # Switch to the production branch and copy the kubernetes.yaml file from the candidate branch
        git fetch origin production && git checkout production && \
        git checkout $COMMIT_SHA kubernetes.yaml && \
        # Commit the kubernetes.yaml file with a descriptive commit message
        git commit -m "Manifest from commit $COMMIT_SHA
        $(git log --format=%B -n 1 $COMMIT_SHA)" && \
        # Push the changes back to Cloud Source Repository
        git push origin production

  3. Crie um branch candidato e envie os dois branches para que fiquem disponíveis no Cloud Source Repositories.

    git checkout -b candidate
    git push origin production
    git push origin candidate
    
  4. Conceda o papel de IAM de Gravador do repositório de origem à conta de serviço do Cloud Build para o repositório hello-cloudbuild-env.

    PROJECT_NUMBER="$(gcloud projects describe ${PROJECT_ID} \
        --format='get(projectNumber)')"
    cat >/tmp/hello-cloudbuild-env-policy.yaml <<EOF
    bindings:
    - members:
      - serviceAccount:${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com
      role: roles/source.writer
    EOF
    gcloud source repos set-iam-policy \
        hello-cloudbuild-env /tmp/hello-cloudbuild-env-policy.yaml
    

Criar o acionador do pipeline de entrega contínua

Nesta seção, você configurará o Cloud Build para que seja acionado por um envio no branch candidato do repositório hello-cloudbuild-env.

  1. Abra a página Acionadores do Cloud Build.

    ACESSAR “ACIONADORES”

  2. Clique em Criar gatilho.

  3. Preencha as opções a seguir:

    • No campo Nome, digite hello-cloudbuild-deploy.
    • Em Evento, selecione Mover para uma ramificação.
    • Em Origem, selecione hello-cloudbuild-env como Repositório e ^candidate$ como Ramificação.
    • Em Configuração da compilação, selecione arquivo de configuração do Cloud Build.
    • No campo local do arquivo de configuração do Cloud Build, digite cloudbuild.yaml depois do /.
  4. Clique em Criar.

Modificar o pipeline de integração contínua para acionar o pipeline de entrega contínua

Nesta seção, você adicionará algumas etapas ao pipeline de integração contínua que gerará uma nova versão do manifesto do Kubernetes e, depois, enviará esse manifesto para o repositório hello-cloudbuild-env para acionar o pipeline de entrega contínua.

  1. Copie a versão estendida do arquivo cloudbuild.yaml para o repositório app.

    cd ~/hello-cloudbuild-app
    cp cloudbuild-trigger-cd.yaml cloudbuild.yaml
    

    O cloudbuild-trigger-cd.yaml é uma versão estendida do arquivo cloudbuild.yaml. Com ele, é feita a adição das etapas abaixo para que seja gerado o novo manifesto do Kubernetes e seja acionado o pipeline de entrega contínua.

    # This step clones the hello-cloudbuild-env repository
    - name: 'gcr.io/cloud-builders/gcloud'
      id: Clone env repository
      entrypoint: /bin/sh
      args:
      - '-c'
      - |
        gcloud source repos clone hello-cloudbuild-env && \
        cd hello-cloudbuild-env && \
        git checkout candidate && \
        git config user.email $(gcloud auth list --filter=status:ACTIVE --format='value(account)')
    
    # This step generates the new manifest
    - name: 'gcr.io/cloud-builders/gcloud'
      id: Generate manifest
      entrypoint: /bin/sh
      args:
      - '-c'
      - |
         sed "s/GOOGLE_CLOUD_PROJECT/${PROJECT_ID}/g" kubernetes.yaml.tpl | \
         sed "s/COMMIT_SHA/${SHORT_SHA}/g" > hello-cloudbuild-env/kubernetes.yaml
    
    # This step pushes the manifest back to hello-cloudbuild-env
    - name: 'gcr.io/cloud-builders/gcloud'
      id: Push manifest
      entrypoint: /bin/sh
      args:
      - '-c'
      - |
        set -x && \
        cd hello-cloudbuild-env && \
        git add kubernetes.yaml && \
        git commit -m "Deploying image gcr.io/${PROJECT_ID}/hello-cloudbuild:${SHORT_SHA}
        Built from commit ${COMMIT_SHA} of repository hello-cloudbuild-app
        Author: $(git log --format='%an <%ae>' -n 1 HEAD)" && \
        git push origin candidate
    

  2. Confirme as modificações e envie-as para o Cloud Source Repositories.

    cd ~/hello-cloudbuild-app
    git add cloudbuild.yaml
    git commit -m "Trigger CD pipeline"
    git push google master
    

    Isso aciona o pipeline de integração contínua no Cloud Build.

  3. Examine a versão de integração contínua.

    ACESSAR O CLOUD BUILD

    Você verá uma versão do repositório hello-cloudbuild-app em execução ou finalizada recentemente. Se quiser, clique na versão para acompanhar a execução e examinar os registros. A última etapa desse pipeline envia o manifesto novo para o repositório hello-cloudbuild-env, que aciona o pipeline de entrega contínua.

  4. Examine a versão de entrega contínua.

    ACESSAR O CLOUD BUILD

    Você verá uma versão do repositório hello-cloudbuild-env em execução ou finalizada recentemente. Se quiser, clique na versão para acompanhar a execução e examinar os registros.

Testar o pipeline completo

O pipeline de CI/CD completo agora está configurado. Nesta seção, você testará o pipeline inteiro.

  1. Acesse a página “Serviços” do GKE.

    ACESSAR A PÁGINA “SERVIÇOS” DO GOOGLE KUBERNETES ENGINE

    Provavelmente, haverá um único serviço chamado hello-cloudbuild na lista. Ele foi criado pela versão de entrega contínua recém-executada.

  2. Clique no endpoint do serviço hello-cloudbuild. Você verá “Hello World!”. Se não houver um endpoint ou ocorrer um erro no balanceador de carga, talvez seja necessário aguardar alguns minutos até que o balanceador de carga seja totalmente inicializado. Clique em Atualizar para atualizar a página, se necessário.

  3. No Cloud Shell, substitua “Hello World” por “Hello Cloud Build” no aplicativo e no teste de unidade.

    cd ~/hello-cloudbuild-app
    sed -i 's/Hello World/Hello Cloud Build/g' app.py
    sed -i 's/Hello World/Hello Cloud Build/g' test_app.py
    
  4. Confirme e envie a alteração para o Cloud Source Repositories.

    git add app.py test_app.py
    git commit -m "Hello Cloud Build"
    git push google master
    

    Isso aciona o pipeline de CI/CD inteiro.

  5. Após alguns minutos, atualize o aplicativo no seu navegador. Agora você verá “Hello Cloud Build!”.

Testar a reversão

Nesta seção, você voltará para a versão do aplicativo que dizia "Hello World!".

  1. Abra o console do Cloud Build no repositório hello-cloudbuild-env.

    ACESSAR O CLOUD BUILD

  2. Clique na segunda versão mais recente disponível.

  3. Clique em Recriar.

  4. Quando a versão estiver concluída, atualize o aplicativo no seu navegador. Agora você verá "Hello World!" novamente.

Como fazer a limpeza

Para evitar cobranças dos recursos usados neste tutorial na conta do Google Cloud Platform:

  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.

Excluir os recursos

Se você quiser manter o projeto do GCP usado neste tutorial, exclua os recursos individuais:

  1. Exclua os repositórios Git locais.

    cd ~
    rm -rf ~/hello-cloudbuild-app
    rm -rf ~/hello-cloudbuild-env
    
  2. Exclua os repositórios Git no Cloud Source Repositories.

    gcloud source repos delete hello-cloudbuild-app --quiet
    gcloud source repos delete hello-cloudbuild-env --quiet
    
  3. Exclua os acionadores do Cloud Build.

    1. Abra a página Gatilhos do Cloud Build.

      ACESSAR “GATILHOS”

    2. Para cada gatilho, clique em Mais e, depois, em Excluir.

  4. Exclua as imagens no Container Registry.

    gcloud beta container images list-tags \
        gcr.io/${PROJECT_ID}/hello-cloudbuild \
        --format="value(tags)" | \
        xargs -I {} gcloud beta container images delete \
        --force-delete-tags --quiet \
        gcr.io/${PROJECT_ID}/hello-cloudbuild:{}
    
  5. Remova a permissão concedida ao Cloud Build para se conectar ao GKE.

    PROJECT_NUMBER="$(gcloud projects describe ${PROJECT_ID} \
        --format='get(projectNumber)')"
    gcloud projects remove-iam-policy-binding ${PROJECT_NUMBER} \
        --member=serviceAccount:${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com \
        --role=roles/container.developer
    
  6. Exclua o cluster do GKE.

    gcloud container clusters delete hello-cloudbuild \
        --zone us-central1-b
    

A seguir