Como otimizar um pipeline de testes para que os times não tenham problemas de concorrência ao testar suas funcionalidades e seus módulos é um assunto recorrente em vários tópicos que tratei tanto no passado quanto recentemente.
Inclusive, já fiz algumas talks sobre o assunto e tenho até um repositório de exemplo usando o Azure DevOps como ferramenta de CI. Você pode ver os slides e o vídeo abaixo!
A questão é: Como podemos fazer com que vários times de desenvolvimento consigam testar suas funcionalidades em um ambiente completamente separado dos demais de forma simples e rápida?
A resposta está, é claro, em containers. Quando utilizamos o Kubernetes com Helm juntamente a uma ferramenta de CI, podemos fazer muitas coisas dinamicamente. Neste artigo, vou atualizar minha palestra anterior e mostrar a mesma aplicação, porém rodando no pipeline do GitHub Actions. Para deixar o cenário mais real, vamos utilizar a Azure com o Azure Kubernetes Service baixando imagens de um Azure Container Registry privado já integrado de forma privada com o cluster. Todos os dados serão armazenados em um CosmosDB do tipo Mongo.
Vamos lá!
Antes de começar
Vamos ter que criar o ambiente antes de começar a mostrar como a parte dinâmica pode ser feita. Como esse não é o intuito do post, vou deixar apenas as referências de comandos do que vamos fazer por aqui, mas todas as documentações necessárias podem ser encontradas diretamente na documentação de cada serviço.
Primeiramente você precisa ter uma conta na Azure, uma vez com esta conta, instale o Azure CLI, vamos utilizar somente a linha de comando.
O primeiro comando será o comando az login
para fazer o login na sua conta e escolher qual será a subscription que será utilizada pra criar os recursos. Assim que o login estiver pronto, vamos começar criando o primeiro recurso, o resource group.
az group create -l eastus -n ship-manager-pipeline
Agora vamos criar o nosso ACR para poder armazenar nossas imagens:
az acr create -n shipmanager --sku Basic -g ship-manager-pipeline
Espere até o CR ser criado e execute o seguinte comando para habilitar o login via usuário e senha, só assim vamos poder fazer o login pelo nosso CI para poder construir as imagens:
az acr update -n shipmanager --admin-enabled true
Agora vamos partir para o CosmosDB, com um simples comando podemos criar toda a estrutura:
az cosmosdb create --kind MongoDB -n ship-manager-db -g ship-manager-pipeline
Este comando demora um pouco mais para ser executado, então seja paciente. É importante dizer que, apesar de ser incrível, o CosmosDB não é recomendado para este caso específico de criação de bancos de dados quando temos ambientes efêmeros como estes pois é mais complexo de remover futuramente. Mas, para simplificar, vamos utilizá-lo e vamos entender alternativas que podemos ter a esta abordagem mais a frente no artigo.
Por fim vamos criar o nosso AKS que vai unir todas as partes:
az aks create -n ship-manager -g ship-manager-pipeline \
--enable-addons http_application_routing \
--attach-acr shipmanager \
--vm-size Standard_B2s \
--generate-ssh-keys \
--node-count 2
Isso fará com que nosso AKS seja criado já em conjunto com o ACR, dessa forma não precisamos criar um secret para cada namespace com o nosso arquivo de login do Docker para baixar as imagens, e também não precisamos fazer um bind em uma service account.
Obtenha as credenciais do AKS com o seguinte comando:
az aks get-credentials -n ship-manager -g ship-manager-pipeline --admin
Lembrando que você precisa ter o kubectl
instalado na máquina para este comando funcionar, caso não o possua, use o comando az aks install-cli
para instalá-lo.
Criando o chart
O primeiro passo para a criação da pipeline é saber como ela vai funcionar, inicialmente vamos trabalhar somente com dois ambientes, o primeiro será o ambiente de produção e o segundo será o ambiente de testes.
O ambiente de produção será publicado sempre que um push com uma tag v*
for feito. Já o ambiente de teste será publicado em um push de qualquer outro branch que não seja o master
ou main
(dependendo do caso).
Para que você possa acompanhar, preparei este repositório de exemplo que contém tanto o código da aplicação quanto o código das actions em si.
Se você quiser acompanhar passo a passo, faça um fork do repositório, mas não se esqueça de remover a pasta .github
para que as actions sejam removidas.
Antes de criar os arquivos da pipeline, vamos criar os arquivos do Helm, para podermos criar nosso chart! Crie uma pasta chamada kubernetes
na raiz do repositório, depois crie uma segunda pasta chamada ship-manager
.
Poderíamos criar o chart do helm de forma automática via CLI, porém ele cria vários arquivos que não precisamos, então vamos criá-lo manualmente para facilitar.
Dentro da pasta ship-manager
crie mais duas pastas: templates
e charts
. Agora crie dois arquivos no mesmo nível da pasta templates
, um deles se chamará Chart.yaml
e o outro values.yaml
.
Agora, vamos para dentro da pasta charts
, nela, crie uma pasta backend
e, dentro desta última adicione um arquivo Chart.yaml
seguido de uma pasta templates
.
A estrutura final deverá ser assim:
kubernetes
└── ship-manager
├── Chart.yaml
├── charts
│ └── backend
│ ├── Chart.yaml
│ └── templates
│
├── templates
└── values.yaml
No arquivo Chart.yaml
da pasta ship-manager
vamos escrever o seguinte:
apiVersion: v2
name: ship-manager
description: Chart for the ship manager app
version: 0.1.0
E no da pasta backend
será o seguinte:
apiVersion: v2
name: backend
description: Chart for the backend part of the ship manager app
version: 0.1.0
O que fizemos foi criar o arquivo equivalente a um package.json
do Helm, ou seja, o arquivo que define o pacote que vamos instalar no nosso cluster.
O Helm funciona com base em uma hierarquia, o que acabamos de criar aqui é uma ordem de dependências, ou seja, acabamos de dizer que o frontend da aplicação, que está no ship-manager
, é dependente de um backend localizado na pasta charts
, se criássemos outra pasta charts
dentro de backend
iríamos dizer que o backend é dependente dela e assim sucessivamente. Desta forma, quando damos apenas um comando, o Helm já instala todas as dependências em ordem para nós.
Vamos criar nosso primeiro template, crie um arquivo frontend.yaml
dentro da pasta templates
localizada na pasta ship-manager
. Este template vai ser o que vai ser de fato criado dentro do cluster. Nele vamos ter todos os recursos do Kubernetes, começando pelo Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ship-manager-frontend
spec:
replicas: 1
selector:
matchLabels:
app: ship-manager-frontend
template:
metadata:
labels:
app: ship-manager-frontend
spec:
containers:
- image: {{ required "Registry is required" .Values.global.registryName }}/{{ required "Image name is required" .Values.frontend.imageName }}:{{ required "Image tag is required" .Values.global.imageTag }}
name: ship-manager-frontend
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
ports:
- containerPort: 8080
name: http
volumeMounts:
- name: config
mountPath: /usr/src/app/dist/config.js
subPath: config.js
volumes:
- name: config
configMap:
name: frontend-config
Veja que estamos utilizando placeholders do Helm para poder identificar partes que podem ser alteradas, e é isso que faz com que tudo seja possível. A criação de ambientes e alteração de variáveis em tempo de CLI e não depois de compilado faz com que possamos passar os valores que quisermos para essas variáveis ao criar o ambiente.
Depois temos as outras configurações:
apiVersion: v1
kind: Service
metadata:
name: ship-manager-frontend
spec:
selector:
app: ship-manager-frontend
ports:
- name: http
port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ship-manager-frontend
annotations:
kubernetes.io/ingress.class: addon-http-application-routing
spec:
rules:
- host: {{ default "ship-manager-frontend" .Values.frontend.ingress.hostname }}.{{ .Values.global.dnsZone }}
http:
paths:
- path: /
backend:
serviceName: ship-manager-frontend
servicePort: http
---
apiVersion: v1
kind: ConfigMap
metadata:
name: frontend-config
data:
config.js: |
const config = (() => {
return {
'VUE_APP_BACKEND_BASE_URL': 'http://{{ default "ship-manager-backend" .Values.backend.ingress.hostname }}.{{ .Values.global.dnsZone }}',
'VUE_APP_PROJECT_VERSION': '{{ .Values.global.imageTag }}'
}
})()
Perceba que estou buscando tudo de .Values
, este é o arquivo values.yaml
que vamos ver logo mais. Perceba também que a maioria das coisas que podem ser alteradas e que precisam ser alteradas, como o nome da imagem, a tag, o hostname e banco de dados, também são variáveis.
Nestes casos, utilizar configmaps e secrets ajuda muito a manter a pipeline simples.
O arquivo final será:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ship-manager-frontend
spec:
replicas: 1
selector:
matchLabels:
app: ship-manager-frontend
template:
metadata:
labels:
app: ship-manager-frontend
spec:
containers:
- image: {{ required "Registry is required" .Values.global.registryName }}/{{ required "Image name is required" .Values.frontend.imageName }}:{{ required "Image tag is required" .Values.global.imageTag }}
name: ship-manager-frontend
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
ports:
- containerPort: 8080
name: http
volumeMounts:
- name: config
mountPath: /usr/src/app/dist/config.js
subPath: config.js
volumes:
- name: config
configMap:
name: frontend-config
---
apiVersion: v1
kind: Service
metadata:
name: ship-manager-frontend
spec:
selector:
app: ship-manager-frontend
ports:
- name: http
port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ship-manager-frontend
annotations:
kubernetes.io/ingress.class: addon-http-application-routing
spec:
rules:
- host: {{ default "ship-manager-frontend" .Values.frontend.ingress.hostname }}.{{ .Values.global.dnsZone }}
http:
paths:
- path: /
backend:
serviceName: ship-manager-frontend
servicePort: http
---
apiVersion: v1
kind: ConfigMap
metadata:
name: frontend-config
data:
config.js: |
const config = (() => {
return {
'VUE_APP_BACKEND_BASE_URL': 'http://{{ default "ship-manager-backend" .Values.backend.ingress.hostname }}.{{ .Values.global.dnsZone }}',
'VUE_APP_PROJECT_VERSION': '{{ .Values.global.imageTag }}'
}
})()
Vamos fazer o mesmo com o backend, criando um arquivo backend.yaml
na pasta charts/backend/templates
:
# backend.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ship-manager-backend
spec:
replicas: 1
selector:
matchLabels:
app: ship-manager-backend
template:
metadata:
labels:
app: ship-manager-backend
spec:
containers:
- image: {{ required "Registry is required" .Values.global.registryName }}/{{ required "Image name is required" .Values.imageName }}:{{ required "Image tag is required" .Values.global.imageTag }}
name: ship-manager-backend
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
ports:
- containerPort: 3000
name: http
env:
- name: DATABASE_MONGODB_URI
valueFrom:
secretKeyRef:
key: database_mongodb_uri
name: backend-db
- name: DATABASE_MONGODB_DBNAME
value: {{ default "ship-manager" .Values.global.dbName }}
---
apiVersion: v1
kind: Service
metadata:
name: ship-manager-backend
spec:
selector:
app: ship-manager-backend
ports:
- name: http
port: 80
targetPort: 3000
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ship-manager-backend
annotations:
kubernetes.io/ingress.class: addon-http-application-routing
spec:
rules:
- host: {{ default "ship-manager-backend" .Values.ingress.hostname }}.{{ .Values.global.dnsZone }}
http:
paths:
- path: /
backend:
serviceName: ship-manager-backend
servicePort: http
---
apiVersion: v1
kind: Secret
metadata:
name: backend-db
type: Opaque
stringData:
database_mongodb_uri: {{ required "DB Connection is required" .Values.global.dbConn | quote }}
Perceba que tenho algumas funções também, como required
, default
e quote
, esta são funções nativas do Helm e quebram um bom galho quando a gente precisa de funcionalidades mais complexas.
O arquivo values
Assim como os charts, o arquivo values.yaml
é baseado em uma hierarquia de escopos, ou seja, tome como exemplo a nossa estrutura:
# values.yaml
global:
chave: # Acessível a todos os charts, tanto o frontend como o backend como `.Values.global.chave`
backend:
chave: # Acessível somente ao frontend e ao backend, porém para o frontend será `.Values.backend.chave` e o backend usará como `.Values.chave`
frontend:
chave: # Acessível pelo frontend como `.Values.frontend.chave`, mas não pelo backend
chave: # Acessível somente ao frontend como `.Values.chave`
Perceba que temos uma quebra de escopo dentro do arquivo values, as chaves que tem o mesmo nome dos seus charts dependentes serão acessadas somente por eles e pelos charts de mais alta ordem, então como o nosso frontend é o chart de maior ordem, ele tem acesso a todos os valores, enquanto o backend só tem acesso às chaves definidas sob backend:
.
Perceba também que dentro de backend
o escopo sofre um "levelling", ou seja, o escopo é removido de dentro de backend
então você não precisa acessar o valor como .Values.backend.chave
se você estiver dentro do chart backend
, mas somente como .Values.chave
.
É possível ter mais arquivos values dentro dos charts dependentes e a regra se mantém a mesma, a diferença é que o chart de maior ordem será alterado, porém este padrão torna a manutenção bastante complexa.
Nosso arquivo values precisa ter as mesmas chaves que definimos dentro dos nossos templates, então elas vão ser as seguintes:
global:
registryName:
imageTag:
dbName: ship-manager
dbConn:
dnsZone:
backend:
imageName: ship-manager-backend
ingress:
hostname:
frontend:
imageName: ship-manager-frontend
ingress:
hostname:
As chaves que estou deixando em branco são as que serão ou preenchidas pelo CLI ou pelas funções default
.
Criando a pipeline
Para criamos a pipeline, vamos utilizar o modo manual de criação, ou seja, vamos criar uma pasta .github
e dentro dela uma pasta workflows
. O primeiro workflow será o mais simples, o de produção.
Dentro da pasta workflows
vamos criar um arquivo deploy-production.yml
(pode ser qualquer nome, na verdade) e começar escrevendo o nome da nossa pipeline e quais são os gatilhos que vão fazer ela funcionar.
name: Build and push the tagged build to production
on:
push:
tags:
- 'v*'
Aqui estamos dizendo que nossa action vai rodar em todos os pushes com uma tag v*
, ou seja, v1.0.0
e até mesmo vabc
, se você quiser reduzir as possibilidades pode usar regex como v[0-9]\.[0-9]\.[0-9]
.
Depois, vamos criar nosso primeiro job e definir uma variável em comum:
name: Build and push the tagged build to production
on:
push:
tags:
- 'v*'
env:
IMAGE_NAME: ship-manager
jobs:
build_push_image:
runs-on: ubuntu-20.04
Criamos um job chamado build_push_image
que vai rodar no ubuntu 20, e uma variável compartilhada que será o nome base da imagem. Agora vamos para a ação de verdade, vamos começar a criar os passos do nosso job, começando com dois super importantes:
name: Build and push the tagged build to production
on:
push:
tags:
- 'v*'
env:
IMAGE_NAME: ship-manager
jobs:
build_push_image:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Set env
id: tags
run: echo tag=${GITHUB_REF#refs/tags/} >> $GITHUB_ENV
O primeiro passo é um checkout do nosso repositório, ele está praticamente presente em todas as actions e é sempre o primeiro passo. O segundo é a definição de uma segunda variável, o nome da tag.
Por padrão $GITHUB_REF
é ou o nome do branch ou o nome da tag como /refs/heads/main
ou /refs/tags/v1.0.0
, temos que remover o /refs/*
e ficar só com o final, por isso estamos usando uma substituição via shell para adicioná-la as variáveis globais, porém esta variável só funciona dentro deste job.
Não podemos definir a variável dentro de env
porque esta chave não executa nenhum tipo de shell, portanto não podemos usar substituição de valores e nem expansões.
Agora vamos fazer o pipeline do Docker, ou seja, build e push das imagens do backend e frontend.
name: Build and push the tagged build to production
on:
push:
tags:
- 'v*'
env:
IMAGE_NAME: ship-manager
jobs:
build_push_image:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Set env
id: tags
run: echo tag=${GITHUB_REF#refs/tags/} >> $GITHUB_ENV
- name: Set up Buildx
uses: docker/setup-buildx-action@v1
- name: Login to ACR
uses: docker/login-action@v1
with:
# Username used to log in to a Docker registry. If not set then no login will occur
username: ${{secrets.ACR_LOGIN }}
# Password or personal access token used to log in to a Docker registry. If not set then no login will occur
password: ${{secrets.ACR_PASSWORD }}
# Server address of Docker registry. If not set then will default to Docker Hub
registry: ${{ secrets.ACR_NAME }}
- name: Build and push frontend image
uses: docker/build-push-action@v2
with:
# Docker repository to tag the image with
tags: ${{secrets.ACR_NAME}}/${{ env.IMAGE_NAME }}-frontend:latest,${{secrets.ACR_NAME}}/${{ env.IMAGE_NAME }}-frontend:${{env.tag}}
labels: |
image.revision=${{github.sha}}
image.release=${{github.ref}}
file: frontend/Dockerfile
context: frontend
push: true
- name: Build and push backend image
uses: docker/build-push-action@v2
with:
# Docker repository to tag the image with
tags: ${{secrets.ACR_NAME}}/${{ env.IMAGE_NAME }}-backend:latest,${{secrets.ACR_NAME}}/${{ env.IMAGE_NAME }}-backend:${{env.tag}}
labels: |
image.revision=${{github.sha}}
image.release=${{github.ref}}
file: backend/Dockerfile
context: backend
push: true
O Docker tem 3 actions que são utilizadas, a primeira delas é o setup do buildx
o utilitário de build de imagens do Docker, a segunda é o login em um registry, no caso será nosso ACR, e aqui temos o nosso primeiro secret
que vamos criar dentro do nosso repositório, que serão os dados de login do ACR.
Por fim, estamos criando e dando um push na imagem e criando as tags latest
e o nome da tag
do GitHub, desta forma sabemos quais são as imagens "de produção" e quais serão as de teste, adicionamos também duas labels para cada imagem, uma delas tem a revisão, o sha do nosso commit, e a outra o nome da tag.
Com isso finalizamos nosso primeiro job, o segundo é a parte onde vamos fazer o deploy para o cluster. O início é o mesmo então vou omitir o conteúdo até aqui para focarmos só nessa parte:
# inicio do arquivo
jobs:
build_push_image:
# job de envio da imagem
deploy:
runs-on: ubuntu-20.04
needs: build_push_image
steps:
- uses: actions/checkout@v2
- name: Set env
id: tags
run: echo tag=${GITHUB_REF#refs/tags/} >> $GITHUB_ENV
- name: Install Helm
uses: Azure/setup-helm@v1
with:
version: v3.3.1
Perceba que estamos criando uma relação entre os dois jobs com a chave needs
, isso diz que o segundo job só será executado se o primeiro passar. Fazemos o checkout e copiamos a criação da variável, depois rodamos um step simples de instalação do Helm na máquina.
# inicio do arquivo
jobs:
build_push_image:
# job de envio da imagem
deploy:
runs-on: ubuntu-20.04
needs: build_push_image
steps:
- uses: actions/checkout@v2
- name: Set env
id: tags
run: echo tag=${GITHUB_REF#refs/tags/} >> $GITHUB_ENV
- name: Install Helm
uses: Azure/setup-helm@v1
with:
version: v3.3.1
- name: Get AKS Credentials
uses: Azure/aks-set-context@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
# Resource group name
resource-group: ship-manager-pipeline
# AKS cluster name
cluster-name: ship-manager
- name: Run Helm Deploy
run: |
helm upgrade \
ship-manager-prd \
./kubernetes/ship-manager \
--install \
--create-namespace \
--namespace production \
--set global.registryName=${{ secrets.ACR_NAME }} \
--set global.dbConn="${{ secrets.DB_CONNECTION }}" \
--set global.dnsZone=${{ secrets.DNS_NAME }} \
--set global.imageTag=${{env.tag}}
Por fim vamos ter o comando para obter as credenciais do Kubernetes e o deploy do Helm. Perceba que estamos fazendo o deploy para um namespace production
e setando os valores do values.yaml
através das flags --set
, isso torna tudo mais fácil quando precisarmos remover os dados, pois só precisamos remover o namespace e tudo é deletado.
A pipeline de testes é quase igual, a diferença é que estamos setando mais variáveis e mudamos o namespace de publicação e também o trigger. O outro arquivo, que chamei de deploy-test
ficou assim:
# deploy-test.yml
name: Build and push the tagged build to test
on:
push:
branches-ignore:
- 'main'
- 'master'
env:
IMAGE_NAME: ship-manager
jobs:
build_push_image:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Set env
id: tags
run: echo tag=${GITHUB_REF#refs/heads/} >> $GITHUB_ENV
- name: Set up Buildx
uses: docker/setup-buildx-action@v1
- name: Login to ACR
uses: docker/login-action@v1
with:
# Username used to log in to a Docker registry. If not set then no login will occur
username: ${{secrets.ACR_LOGIN }}
# Password or personal access token used to log in to a Docker registry. If not set then no login will occur
password: ${{secrets.ACR_PASSWORD }}
# Server address of Docker registry. If not set then will default to Docker Hub
registry: ${{ secrets.ACR_NAME }}
- name: Build and push frontend image
uses: docker/build-push-action@v2
with:
# Docker repository to tag the image with
tags: ${{ secrets.ACR_NAME }}/${{ env.IMAGE_NAME }}-frontend:${{env.tag}}
labels: |
image.revision=${{github.sha}}
file: frontend/Dockerfile
context: frontend
push: true
- name: Build and push backend image
uses: docker/build-push-action@v2
with:
# Docker repository to tag the image with
tags: ${{ secrets.ACR_NAME }}/${{ env.IMAGE_NAME }}-backend:${{env.tag}}
labels: |
image.revision=${{github.sha}}
file: backend/Dockerfile
context: backend
push: true
deploy:
runs-on: ubuntu-20.04
needs: build_push_image
steps:
- uses: actions/checkout@v2
- name: Set env
id: tags
run: echo tag=${GITHUB_REF#refs/tags/} >> $GITHUB_ENV
- name: Install Helm
uses: Azure/setup-helm@v1
with:
version: v3.3.1
- name: Get AKS Credentials
uses: Azure/aks-set-context@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
# Resource group name
resource-group: ship-manager-pipeline
# AKS cluster name
cluster-name: ship-manager
- name: Run Helm Deploy
run: |
helm upgrade \
ship-manager-${{env.tag}} \
./kubernetes/ship-manager \
--install \
--create-namespace \
--namespace test-${{env.tag}} \
--set global.registryName=${{ secrets.ACR_NAME }} \
--set global.dbConn="${{ secrets.DB_CONNECTION }}" \
--set global.dbName=ship-manager-test-${{env.tag}} \
--set global.dnsZone=${{ secrets.DNS_NAME }} \
--set backend.ingress.hostname=ship-manager-backend-${{env.tag}} \
--set frontend.ingress.hostname=ship-manager-frontend-${{env.tag}} \
--set global.imageTag=${{env.tag}}
Secrets
Agora que já temos as pipelines criadas, vamos ao GitHub criar nossos secrets! Abra o repositório no seu browser, navegue até a aba Settings
e depois secrets
, então clique em New repository secret
para criar um novo secret local.
Vamos criar um secret chamado ACR_LOGIN
que será o nome do nosso ACR, ou seja, shipmanager
. Outro chamado ACR_NAME
, que não é bem um segredo, porque é o DNS do nosso CR, porém assim evitamos de ter um valor fixo na nossa action, este valor é o shipmanager.azurecr.io
.
Ambas as informações podem ser obtidas no portal da Azure, sabendo o nome do ACR o login é o mesmo e o DNS é sempre <nome>.azurecr.io
A senha do ACR pode ser obtida com um comando do AZ CLI: az acr credential show -n shipmanager --query "passwords[0].value" -o tsv
e deve ser colocada em outro secret chamado ACR_PASSWORD
.
Para obtermos a chave do nosso AKS, vamos precisar de um acesso via service principal na Azure, que pode ser obtido pelo comando az ad sp create-for-rbac --sdk-auth
, este comando vai te devolver um JSON, copie todo o JSON e cole no secret chamado AZURE_CREDENTIALS
.
A conexão com o banco do secret chamado DB_CONNECTION
pode ser obtida também pelo comando az cosmosdb keys list -n ship-manager-db -g ship-manager-pipeline --type connection-strings --query "connectionStrings[0].connectionString"
.
E o secret final pode ser obtido através de uma query na lista de addons habilitados do AKS, como ligamos o HTTP Application Routing, vamos ter uma zona de DNS liberada que podemos obter com o comando az aks show -n ship-manager -g ship-manager-pipeline --query "addonProfiles.httpApplicationRouting.config.HTTPApplicationRoutingZoneName
e colocar no secret chamado DNS_NAME
.
Testando
Commitamos nossas mudanças e agora vamos criar uma tag com git tag -a v<versão> -m'nova versão
e depois git push --tags
para podermos fazer a trigger na nosso build. Teremos um pequeno delay e então uma saída como esta:
Se visualizarmos a nossa aplicação depois de alguns minutos (o DNS demora para propagar), iremos ver que temos um endereço igual ao do nosso ingress (podemos obter o endereço do frontend com kubectl get ing -n production
), ao acessar, vamos ter a nossa aplicação rodando:
Nossa aplicação está em um branch de produção e será acessível e atualizada para a versão mais nova sempre que fizermos um push com uma tag específica. O mesmo vai acontecer quando criarmos um novo branch e fizermos um push, experimente criar um branch qualquer e enviar um código!
Conclusão e melhorias
A criação de um ambiente dinâmico não é simples, porém pode ser a solução entre um time que demora a testar suas funcionalidades para um time que pode ser muito mais eficiente. Neste exemplo chegamos aos 50% do que é necessário, a outra parte importante do pipeline é também remover seus recursos sempre que não estão mais utilizados.
Por este motivo, utilizar outro banco de dados ao invés da mesma instância é muito mais preferível, o ideal seria criarmos uma dependência no chart backend para um chart do MongoDB completamente vazio, desta forma podemos ter certeza que este ambiente está totalmente isolado e podemos remover todo o ambiente sem nenhum problema.
Deixem seus comentários e quem sabe podemos continuar essa série!