Entrega contínua ao estilo GitOps com o Cloud Build

Esta página explica como criar um pipeline de integração e entrega contínuas (CI/CD) no Google Cloud usando apenas produtos alojados e a popular metodologia GitOps.

Os engenheiros da Google armazenam ficheiros de configuração e implementação no nosso repositório de código-fonte principal há muito tempo. Esta metodologia é descrita no livro Site Reliability Engineering, capítulo 8 (Beyer et al., 2016) e foi demonstrado por Kelsey Hightower durante o seu discurso de abertura do Google Cloud Next '17.

Uma parte fundamental do GitOps é a ideia de "ambientes como código": descrever as suas implementações de forma declarativa através de ficheiros (por exemplo, manifestos do Kubernetes) armazenados num repositório Git.

Neste tutorial, vai criar um pipeline de CI/CD que cria automaticamente uma imagem de contentor a partir de código comprometido, armazena a imagem no Artifact Registry, atualiza um manifesto do Kubernetes num repositório Git e implementa a aplicação no Google Kubernetes Engine (GKE) através desse manifesto.

Arquitetura da pipeline de CI/CD

Este tutorial usa dois repositórios Git:

  • repositório de apps: contém o código-fonte da própria aplicação
  • repositório env: contém os manifestos para a implementação do Kubernetes

Quando envia uma alteração para o repositório da app, o pipeline do Cloud Build executa testes, cria uma imagem de contentor e envia-a para o Artifact Registry. Depois de enviar a imagem, o Cloud Build atualiza o manifesto de implementação e envia-o para o repositório env. Isto aciona outro pipeline do Cloud Build que aplica o manifesto ao cluster do GKE e, se for bem-sucedido, armazena o manifesto noutro ramo do repositório env.

Mantemos os repositórios app e env separados porque têm ciclos de vida e utilizações diferentes. Os principais utilizadores do repositório da app são pessoas reais e este repositório é dedicado a uma aplicação específica. Os principais utilizadores do repositório env são sistemas automáticos (como o Cloud Build) e este repositório pode ser partilhado por várias aplicações. O repositório env pode ter várias ramificações que mapeiam cada uma para um ambiente específico (neste tutorial, só usa a produção) e referenciam uma imagem de contentor específica, ao passo que o repositório app não o faz.

Quando terminar este tutorial, terá um sistema no qual pode facilmente:

  • Distinguir entre implementações com falhas e bem-sucedidas consultando o histórico do Cloud Build
  • Aceda ao manifesto atualmente usado consultando o ramo production do repositório env,
  • Reverta para qualquer versão anterior reexecutando a compilação do Cloud Build correspondente.

Fluxo do pipeline de CI/CD

Acerca deste tutorial

Este tutorial usa os Cloud Source Repositories para alojar repositórios Git, mas pode alcançar os mesmos resultados com outros produtos de terceiros, como o GitHub, o Bitbucket ou o GitLab.

Este pipeline não implementa um mecanismo de validação antes da implementação. Se usar o GitHub, o Bitbucket ou o GitLab, pode modificar o pipeline para usar um pedido de envio para este fim.

Embora recomendemos o Spinnaker às equipas que querem implementar padrões de implementação avançados (azul/verde, análise de canários, multinuvem, etc.), o respetivo conjunto de funcionalidades pode não ser necessário para uma estratégia de CI/CD bem-sucedida para organizações e projetos mais pequenos. Neste tutorial, vai aprender a criar um pipeline de CI/CD adequado para aplicações alojadas no GKE com ferramentas.

Para simplificar, este tutorial usa um único ambiente, o de produção, no repositório env, mas pode expandi-lo para implementar em vários ambientes, se necessário.

Criar os repositórios Git nos Cloud Source Repositories

Nesta secção, cria os dois repositórios Git (app e env) usados neste tutorial e inicializa o app com algum código de exemplo.

  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 exemplo de código do GitHub.

    cd ~
    git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples
    cd ~/kubernetes-engine-samples/management/gitops-style-delivery/
    
  3. Configure os Cloud Source Repositories como um remoto.

    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 clonou contém uma aplicação "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 contentor com o Cloud Build

O código que clonou contém o seguinte Dockerfile.

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

Com este Dockerfile, pode criar uma imagem de contentor com o Cloud Build e armazená-la no Artifact Registry.

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

    cd ~/kubernetes-engine-samples/management/gitops-style-delivery/
    COMMIT_ID="$(git rev-parse --short=7 HEAD)"
    gcloud builds submit --tag="us-central1-docker.pkg.dev/${PROJECT_ID}/my-repository/hello-cloudbuild:${COMMIT_ID}" .
    

    O Cloud Build transmite os registos gerados pela criação da imagem do contentor para o seu terminal quando executa este comando.

  2. Após a conclusão da compilação, verifique se a nova imagem do contentor está disponível no Artifact Registry.

    Aceda ao Artifact Registry

    Imagem hello-cloudbuild no Artifact Registry

Criar o pipeline de integração contínua

Nesta secção, configura o Cloud Build para executar automaticamente um pequeno teste de unidade, compilar a imagem do contentor e, em seguida, enviá-la para o Artifact Registry. O envio de uma nova consolidação para os Cloud Source Repositories aciona automaticamente este pipeline. O ficheiro cloudbuild.yaml incluído no código é a configuração do pipeline.

steps:
# This step runs the unit tests on the app
- name: 'python:3.13-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'
  - 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repository/hello-cloudbuild:$SHORT_SHA'
  - '.'

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

    Aceda a Acionadores

  2. Clique em Criar acionador.

  3. Preencha as seguintes opções:

    • No campo Nome, escreva hello-cloudbuild.
    • Em Evento, selecione Enviar para uma ramificação.
    • Em Origem, selecione hello-cloudbuild-app como repositório e ^master$ como ramo.
    • Em Configuração de compilação, selecione Ficheiro de configuração do Cloud Build.
    • No campo Localização do ficheiro de configuração do Cloud Build, escreva cloudbuild.yaml após /.
  4. Clique em Criar para guardar o acionador de compilação.

    Sugestão: se precisar de criar acionadores de versão para muitos projetos, pode usar a API Build Triggers.

  5. No Cloud Shell, envie o código da aplicação para os Cloud Source Repositories para acionar o pipeline de CI no Cloud Build.

    cd ~/kubernetes-engine-samples/management/gitops-style-delivery/
    git push google master
    
  6. Abra a consola do Cloud Build.

    Aceda ao Cloud Build

    São apresentadas as compilações executadas e concluídas recentemente. Pode clicar numa compilação para acompanhar a respetiva execução e examinar os respetivos registos.

Criar o pipeline de entrega contínua

O Cloud Build também é usado para o pipeline de entrega contínua. O pipeline é executado sempre que um commit é enviado para o ramo candidate do repositório hello-cloudbuild-env. O pipeline aplica a nova versão do manifesto ao cluster do Kubernetes e, se for bem-sucedido, copia o manifesto para o ramo de produção. Este processo tem as seguintes propriedades:

  • O ramo candidato é um histórico das tentativas de implementação.
  • O ramo production é um histórico das implementações bem-sucedidas.
  • Tem uma vista das implementações bem-sucedidas e falhadas no Cloud Build.
  • Pode reverter para qualquer implementação anterior executando novamente a compilação correspondente no Cloud Build. Uma reversão também atualiza o ramo de produção para refletir fielmente o histórico de implementações.

Vai modificar o pipeline de integração contínua para atualizar a ramificação candidate do repositório hello-cloudbuild-env, acionando o pipeline de entrega contínua.

Conceder acesso do Cloud Build ao GKE

Para implementar a aplicação no seu cluster do Kubernetes, o Cloud Build precisa da função de gestão de identidades e acessos do Kubernetes Engine Developer.

Shell

No Cloud Shell, execute o seguinte comando:

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

Consola

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

    Aceda a Autorizações

  2. Defina o estado da função Kubernetes Engine Developer como Ativar.

A inicializar o repositório hello-cloudbuild-env

Tem de inicializar o repositório hello-cloudbuild-env com dois ramos (production e candidate) e um ficheiro de configuração do Cloud Build que descreva o processo de implementação.

  1. No Cloud Shell, clone o repositório hello-cloudbuild-env e crie o ramo production.

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

    cd ~/kubernetes-engine-samples/management/gitops-style-delivery/
    cp ~/hello-cloudbuild-app/cloudbuild-delivery.yaml ~/kubernetes-engine-samples/management/gitops-style-delivery/cloudbuild.yaml
    git add .
    git commit -m "Create cloudbuild.yaml for deployment"
    

    O ficheiro cloudbuild-delivery.yaml descreve o processo de implementação a ser executado no Cloud Build. Tem dois passos:

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

    2. Se tiver êxito, o Cloud Build copia o manifesto para o ramo production.

    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_REGION=us-central1'
      - '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 ramo candidato e envie ambos os ramos para que fiquem disponíveis nos Cloud Source Repositories.

    git checkout -b candidate
    git push origin production
    git push origin candidate
    
  4. Conceda a função do IAM Source Repository Writer à 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 para o pipeline de entrega contínua

Nesta secção, configura o Cloud Build para ser acionado por um push para o ramo candidate do repositório hello-cloudbuild-env.

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

    Aceda a Acionadores

  2. Clique em Criar acionador.

  3. Preencha as seguintes opções:

    • No campo Nome, escreva hello-cloudbuild-deploy.
    • Em Evento, selecione Enviar para uma ramificação.
    • Em Origem, selecione hello-cloudbuild-env como repositório e ^candidate$ como ramo.
    • Em Configuração, selecione Ficheiro de configuração do Cloud Build (YAML ou JSON).
    • No campo Localização do ficheiro de configuração do Cloud Build, escreva cloudbuild.yaml após /.
  4. Clique em Criar.

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

Nesta secção, adiciona alguns passos ao pipeline de integração contínua que gera uma nova versão do manifesto do Kubernetes e a envia para o repositório hello-cloudbuild-env para acionar o pipeline de entrega contínua.

  1. Substitua o ficheiro cloudbuild.yaml pelo exemplo expandido no ficheiro cloudbuild-trigger-cd.yaml.

    cd ~/kubernetes-engine-samples/management/gitops-style-delivery/
    cp cloudbuild-trigger-cd.yaml cloudbuild.yaml
    

    O ficheiro cloudbuild-trigger-cd.yaml é uma versão expandida do ficheiro cloudbuild.yaml. Adiciona passos para gerar o novo manifesto do Kubernetes e acionar 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 us-central1-docker.pkg.dev/$PROJECT_ID/my-repository/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 os Cloud Source Repositories.

    cd ~/kubernetes-engine-samples/management/gitops-style-delivery/
    git add cloudbuild.yaml
    git commit -m "Trigger CD pipeline"
    git push google master
    

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

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

    Aceda ao Cloud Build

    São apresentadas as compilações executadas e concluídas recentemente para o repositório hello-cloudbuild-app. Pode clicar numa compilação para acompanhar a respetiva execução e examinar os respetivos registos. O último passo desta conduta envia o novo manifesto para o repositório hello-cloudbuild-env, o que aciona a conduta de entrega contínua.

  4. Examine a compilação de entrega contínua.

    Aceda ao Cloud Build

    São apresentadas as compilações executadas e concluídas recentemente para o repositório hello-cloudbuild-env. Pode clicar numa compilação para acompanhar a respetiva execução e examinar os respetivos registos.

Testar a pipeline completa

O pipeline de CI/CD completo está agora configurado. Nesta secção, testa-o de forma integral.

  1. Aceda à página Serviços do GKE.

    Aceda aos serviços do Google Kubernetes Engine

    A lista contém um único serviço denominado hello-cloudbuild criado pela compilação de entrega contínua concluída recentemente.

  2. Clique no ponto final do serviço hello-cloudbuild. "Hello World!" é apresentado. Se não existir um ponto final ou se vir um erro do balanceador de carga, pode ter de aguardar alguns minutos para que o balanceador de carga seja completamente inicializado. Clique em Atualizar para atualizar a página, se necessário.

  3. No Cloud Shell, substitua "Hello World" por "Hello Cloud Build", tanto na aplicação como no teste de unidade.

    cd ~/kubernetes-engine-samples/management/gitops-style-delivery/
    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 os Cloud Source Repositories.

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

    Isto aciona a pipeline de CI/CD completa.

  5. Após alguns minutos, atualize a aplicação no navegador. É apresentada a mensagem "Hello Cloud Build!".

Testar a reversão

Nesta secção, reverte para a versão da aplicação que dizia "Hello World!".

  1. Abra a consola do Cloud Build para o repositório hello-cloudbuild-env.

    Aceda ao Cloud Build

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

  3. Clique em Reconstruir.

  4. Quando a compilação estiver concluída, recarregue a aplicação no navegador. "Hello World!" volta a aparecer.