Pipeline no Azure DevOps para uma aplicação ASP.NET Core

Pipeline no Azure DevOps para uma aplicação ASP.NET Core

Neste artigo vamos te guiar passo a passo na configuração de um pipeline usando a estratégia de uso dos arquivos YAMLs dentro do Azure Devops para criar sua primeira pipeline, bora por a mão na massa!?

Todo exemplo usado neste artigo estará disponível neste projeto público aqui: https://dev.azure.com/thihenos0063/thihenos

Azure DevOps

O Azure DevOps (https://dev.azure.com/) é uma plataforma abrangente desenvolvida pela Microsoft que oferece um conjunto completo de ferramentas para gerenciamento do ciclo de vida de desenvolvimento de software. Nosso primeiro passo será criar uma conta dentro do Azure DevOps para criar nosso primeiro projeto e em seguida criar nosso primeiro repositório para colocar o exemplo do nosso código.

Para esse exemplo, criamos um projeto bem simples em dotnet Core apenas para simular a criação container, checks de segurança e o deploy em produção deste código.

Dentro desse repositório, o arquivo mais importante para a criação da nossa pipeline estará dentro do caminho dotnet/.pipelines/azure-pipelines.yml. Como boa prática, sempre crie uma pasta com o nome .pipelines e coloque todos os arquivos necessários ou que fazem parte desse contexto dentro da pasta, assim você mantém seu repositório organizado.

Por que YAML ?

Utilizar YAML para definir pipelines tem várias vantagens. Primeiro, ele permite versionar a configuração do pipeline junto com o código fonte, garantindo que você tenha um histórico de alteração de sua pipeline. Além disso, arquivos YAML são fáceis de ler e escrever, o que facilita a manutenção e a colaboração.

Para aprender mais sobre YAMLS e todas as propriedades que esse tipo de configuração permite, a Azure tem seu próprio wiki disponível.

Configurando nossa pipeline

Antes de iniciar, você precisa ter um repositório no Azure Repos ou em outra plataforma Git compatível. Caso ainda não tenha, crie um repositório e adicione seu código.

No menu lateral, selecione Pipelines e depois clique em New Pipeline.

Escolha o repositório onde está seu código.

Neste exemplo, vamos selecionar o projeto dotnet-example citado anteriormente.

Agora, vamos selecionar um arquivo de pipeline para criar nossa pipeline.

Selecione o local onde o arquivo YAML será armazenado. Para este exemplo, vamos criar um novo arquivo no repositório.

E por último, iremos salvar nossa pipeline.

 

O que nossa pipeline irá executar?

<p”>Agora, nesta sessão, iremos passar por cada passo de nossa pipeline e entenderemos cada detalhe. Primeiro, a visão geral de nossa pipeline será essa:

Cada caixinha apresentada é chamada de Stage. Cada um desses Stages contém um conjunto de instruções para nossa pipeline executar.

Começaremos pelo Stage de Build. Dentro desse Stage, temos um Job que se chama Build App. 

Esse job é responsável por pegar a versão atual do projeto, baixar todas as suas dependências e criar a imagem Docker do projeto.

Sua representação em linguagem de YAML seria representada pelo arquivo dotnet/.pipelines/jobs/build-job.yml. Em linhas gerais, estamos solicitando uma máquina Ubuntu para executar scripts, como se fossem comandos lançados por nós no terminal. A ideia aqui seria simular exatamente o que um desenvolvedor faria para ter seu código compilado. Após o build ocorrer com sucesso, iremos salvar nosso artefato gerado para ser utilizado em nossa estratégia de CD, para esse cenário, criei um perfil no DockerHub para subir essa imagem de teste.

				
					jobs:
- job: jobbuild
  displayName: Building App
  pool:
    vmImage: 'ubuntu-latest'
  steps:


  - script: |
            CS_PROJ_FILE="dotnet.csproj"
            echo "Verificando conteúdo do arquivo $CS_PROJ_FILE..."
            cat $CS_PROJ_FILE
            
            echo "Extraindo a versão do projeto..."
            version=$(grep '<Version>' $CS_PROJ_FILE | sed -E 's/.*<Version>(.*)<\/Version>.*/\1/')
            
            if [ -z "$version" ]; then
              echo "Erro: não foi possível encontrar a versão no arquivo $CS_PROJ_FILE."
            else
              echo "Versão do projeto: $version"
              echo "##vso[task.setvariable variable=packageVersion]$version"
            fi
    displayName: 'Read version from main manifest'
    workingDirectory: 'dotnet/'


  - script: |
            dotnet restore
            dotnet build -c Release --no-restore
    displayName: 'Install dotnet dependencies'
    workingDirectory: 'dotnet/'


  - script: |
          docker login -u toolboxconsultoria@gmail.com -p $(docker-pass)
    displayName: 'Docker login'


  - ${{ if eq(variables['Build.SourceBranchName'], 'master') }}:
    - script: |
              docker build -t toolboxdevopsplayground/${{ parameters.containerName }}:latest .
              docker push toolboxdevopsplayground/${{ parameters.containerName }}:latest
      displayName: 'Build & Push Docker Image (latest)'
      workingDirectory: 'dotnet/'


  - ${{ if ne(variables['Build.SourceBranchName'], 'master') }}:
    - script: |
              docker build -t toolboxdevopsplayground/${{ parameters.containerName }}:$(packageVersion) .
              docker push toolboxdevopsplayground/${{ parameters.containerName }}:$(packageVersion)
      displayName: 'Build & Push Docker Image versioned'
      workingDirectory: 'dotnet/'
				
			

Seguindo adiante, temos agora a validação de segurança que será a representação do arquivo dotnet/.pipelines/jobs/security-job.yml. Nesse Stage, temos o job Security Check, no qual colocamos duas ferramentas para analisar nosso código. 

Temos primeiro a execução do GitLeaks, que valida se há algum padrão de chave ou senha dentro do nosso código. Como nosso repositório não tem esse tipo de falha, a execução apresenta que nada foi encontrado.

 

Logo em seguida, baixamos a imagem gerada no passo anterior. Como comentamos, após o build da imagem Docker, enviamos ela para o DockerHub, apenas para fins didáticos para simular como salvaremos um artefato gerado no processo de CI.

Logo em seguida, baixamos a imagem gerada no passo anterior para utilizar a ferramenta Trivy, que irá investigar se a imagem em questão tem alguma falha de segurança.

 

				
					- script: |
      echo "Running gitleaks to verify keys..."
      docker pull zricethezav/gitleaks:latest
      docker run -v ./:/path zricethezav/gitleaks:latest detect --source="/path" 
    displayName: 'Run Gitleaks'


  - script: |
            CS_PROJ_FILE="dotnet.csproj"
            echo "Verificando conteúdo do arquivo $CS_PROJ_FILE..."
            cat $CS_PROJ_FILE
            
            echo "Extraindo a versão do projeto..."
            version=$(grep '<Version>' $CS_PROJ_FILE | sed -E 's/.*<Version>(.*)<\/Version>.*/\1/')
            
            if [ -z "$version" ]; then
              echo "Erro: não foi possível encontrar a versão no arquivo $CS_PROJ_FILE."
            else
              echo "Versão do projeto: $version"
              echo "##vso[task.setvariable variable=packageVersion]$version"
            fi
    displayName: 'Read version from main manifest'
    workingDirectory: 'dotnet/'


  - script: |
      echo "Running Trivy to scan the Docker image..."
      docker pull toolboxdevopsplayground/${{ parameters.containerName }}:$(packageVersion)
      docker run aquasec/trivy
      docker run aquasec/trivy image toolboxdevopsplayground/${{ parameters.containerName }}:$(packageVersion)
    displayName: 'Run Trivy'

				
			

Por último, iremos simular um estágio de CD, onde o deploy seria efetuado.

 

Neste código, o intuito não é fazer um deploy oficial, até porque um deploy pode acontecer de várias formas, como:

  • Atualização de uma máquina virtual através de conexão FTP

  • Deploy do container em questão em algum serviço de cloud como Azure Function, AWS Lambda ou Google Cloud Run

  • Ou até mesmo sistemas mais complexos como um cluster Kubernetes.

A ideia aqui seria mostrar para como podemos criar Stages de forma dinâmica para simular deploy em ambientes de desenvolvimento, qualidade e produção.


Primeiro, ao executar a pipeline, percebemos que temos um atributo que recebe valores em lista.

 

Esse parâmetro foi configurado no nosso arquivo de pipeline e seu intuito é saber em quais ambientes iremos fazer deploy. Neste caso, estamos declarando que o valor default será em todos os ambientes.

 
				
					parameters:
  - name: ambientesDeploy
    type: object
    default: ['dev','qa','prod']


				
			

Em seguida, iremos solicitar que nosso template efetue uma interação nessa array de valores e que dinamicamente, a cada execução, criaremos esse Stage de deploy.

				
					- ${{ each value in parameters.ambientesDeploy }}:
  - template: stages/deploy-stage.yml
    parameters:
      containerName: 'example-dotnet'
      name: ${{ value }}


				
			

Feito isso, agora passamos o dado de cada iteração do loop para dentro do template de deploy-stage, assim podemos trocar as mensagens de exibição de forma dinâmica.

				
					parameters:
  containerName: 'comments'
  name: ''


stages:
- stage: DeployStage${{ parameters.name }}
  displayName: Deploy Stage ${{ parameters.name }}
  jobs:
  - template: ../jobs/deploy-job.yml
    parameters:
      containerName: '${{ parameters.containerName }}'
      name: '${{ parameters.name }}'

				
			

Acelere a sua carreira conosco!

A Mentoria DevOps é um programa de mentoria de 12 meses com encontros semanais ao vivo, com um grupo seleto e restrito, onde estaremos do seu lado para mantê-lo relevante e atualizado no mercado de tecnologia, aprendendo e implementando as melhores práticas e ferramentas de DevOps.

Clique aqui para entrar na prioridade pela melhor oferta de lançamento

Conclusão

Agora você tem na mão um perfeito esqueleto para você desenhar suas pipelines em .NET! A ideia desse artigo prático era trazer de uma forma simples e objetiva os primeiros passos para a criação de sua primeira pipeline e também deixar um exemplo que fosse funcional para ser utilizado em casos reais! Nos vemos nos próximos artigos.