Deploy
Onde poderíamos colocar esse projeto para rodar? Nem dá para listar a quantidade método que poderíamos utilizar para disponibilizar esse projeto.
Já construímos a imagem, agora é só pegá-la e colocar para rodar no lugar certo no último stage (deploy) do pipeline.
Para fazer isso com dois métodos diferentes vamos fazer o seguinte:
- Em develop (branch develop) vamos hospedar o nosso código no Netlify.
- Em production (branch main) vamos deployar um container no Kubernetes.
No caso do Netlify podemos usar o netflify-cli para interagir com a ferramenta. Se observar bem a documentação temos todo o processo de instalação e vamos utilizá-lo no job de deploy.
Se não tiver uma, crie uma conta e adicione um novo projeto de forma MANUAL. Faça o upload da pasta build/
do projeto só para gerar um novo site. Se não tiver a pasta build só rodar os mesmos comando do job build (npm run build) que a pasta irá aparecer ou pegue um build de algum artefato do pipeline.
Ao criar o projeto, edite o nome para que a URL gerada pelo Netlify fique mais amigável. O Netlify usa o nome do projeto para criar o link com o domínio .netlify.app.
No meu caso, alterei para curso-gitlab-ci.
Aqui já temos o site manualmente deployado. O que nos interessa em gerar esse projeto é ter algumas informações para que possamos fazer login com a ferramenta netlify-cli.
Precisamos do Project ID, que conseguimos em configurações do projeto, e de um token. Na sua conta em https://app.netlify.com/user/applications#content gere um token e ponha o tempo de expiração de acordo com a sua necessidade.
Vamos criar as varíaveis no repositório para o environment develop. O netlify-cli irá buscar as varíaveis de ambient NETFLIFY_AUTH_TOKEN e NETLIFY_SITE_ID para o comando neflify status
.
Não definimos nada ainda em production, só development fará o deploy desta maneira usando o netflify.
Vou fazendo uma pequena alteração no código só para ver alguma diferença.
<p className="read-the-docs">
Created by Valentin Despa and modified by David Puziol.
</p>
Nosso job de deploy será da seguinte maneira.
deploy-dev:
stage: deploy
needs: [build] # Dependemos do build pois ele irá gerar o diretório build
environment: # As variável de develop estarão disponíveis nesse job.
name: develop
variables:
GIT_STRATEGY: none # Não precisamos fazer o clone do repositório. Vamos usar o código para alguma coisa?
cache:
key: netlify-cli-cache
paths:
- ~/.npm # Cache global do npm #
before_script:
- echo "Instalando netlify-cli"
- npm install -g netlify-[email protected] # Sempre bom fixar a versão
- netlify --version
- apk add curl
script: | # Tudo isso abaixo é considerado um único item da lista. Poderíamos ter feito vários itens. Fiz dessa maneira para mostrar como fica no log.
echo "Conferindo o status usando as credênciais definidas na variável de ambiente."
echo $NETFLIFY_AUTH_TOKEN
echo $NETLIFY_SITE_ID
netlify status
echo "Subindo o diretório build para o projeto $NETLIFY_SITE_ID"
netlify deploy --prod --dir build
curl 'https://curso-gitlab-ci.netflify.app'
rules:
- if: '$CI_COMMIT_BRANCH == "develop"' # Vamos fazer esse tipo de deploy só em develop.
A imagem do job acima é a mesma usada em default (node:22-alpine ) e não utilizando extends para nada. Nem precisamos que esse runner tenha o código do repositório, só o artefato com a pastas build/. Definido a variável GIT_STRATEGY como none
já ganhamos um pouco de performance.
Outro detalhe é que quando passamos --prod no comando netflify vamos alterar o site público. Como aqui estamos fazendo deploys de develop em uma ferramenta e production em outra, não vamos nos importar com isso.
Vamos criar um merge com as modificações e aceitar. O deploy em dev irá correr junto com a criação da imagem, pois não estamos usando a imagem apesar dela esta sendo gerada no pipeline. A única dependência que colocamos é o processo de build e não o da imagem.
Se você usou o cache corretamente, mesmo que tenhamos mudado o código não mudamos as depedências então ele deveria ser utilizado no job do build. Só não irá ser se não foi rodado nenhuma vez o build anteriormente para esse job.
O curl no final é só para ver site o site esta respondendo e mais nada. Não conseguimos garantir a mudança de um projeto inteiro só analizando a primeira página.
Existem estágios de pipeline que chamamos de smoke test que serve para verificar as mudanças.
A pipeline é montada de acordo com a estratégia. A estratégia aqui foi que desenvolvimento e produção estão em branchs separadas, mas é possível ter somente a branch main e colocar diferentes jobs para que continuem deployando em outros ambientes (Deploy dev >>>> deploy prod). Geralmente quando esta estratégia é montada o job que faz o deploy em produção costumamos colocá-lo MANUAL, ou seja, somente se alguém apertar o play irá executar.
Para produção vamo deployar no kubernetes para mostrar uma funcionalidade do GitLab. Alguns detalhes antes de começar. Se você esta fazendo esse curso e não tem um cluster kubernetes local para brincar recomendo usar o kind. Se não tem o docker também então saiba que o kind depende do docker.
Script rapido para instalar as ferramentas no ubuntu.
# Instala Docker e habilita para não precisar usar o sudo
curl -fsSL https://get.docker.com | sudo sh && sudo usermod -aG docker $USER && newgrp docker
# Instala Kind
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.22.0/kind-linux-amd64 && chmod +x ./kind && sudo mv ./kind /usr/local/bin/kind
# Cria o cluster com um só node master para economizar recurso
kind create cluster
# Instala o kubectl para acessar o cluster
curl -LO "https://dl.k8s.io/release/$(curl -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \
&& chmod +x kubectl \
&& sudo mv kubectl /usr/local/bin/kubectl
# Testa se o kubectl esta se comunicando.
kubectl get nodes
# Instalando o helm que vamos precisar
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
- O cluster que eu vou deployar não tem o kube-api exposto e não tem load balancer.
- É um cluster local na minha máquina pessoal.
- A tag da imagem sempre será a latest e não estamos focando em gerar tags diferentes aqui.
- Não vamos criar manifestos kubernetes, apenas aplicar direto o que não é muito correto de se fazer.
- Vamos instalar o Gitlab kubernetes Agent no cluster. Se a API do Kubernetes não está exposta, então o GitLab CI não consegue acessar o cluster diretamente com kubeconfig, mas o agent dentro do cluster consegue e é mais seguro. Se não for utilizar GitOps essa é uma das melhores maneiras isso.
Instalando o GitLab K8S Agent
Na página do projeto, em Operater >>> Kubernetes clusters podemos conectar o GitLab a um cluster.
Vamos criar gitlab-agent aqui para o nosso cluster. Vou chamar de localcluster. Esse cluster será usado só para nossa branch main simulando nosso ambiente produtivo.
Ele nos dará um comando para executarmos para fazer a instalação do agente no cluster via helm, inclusive com o token preenchido.
helm repo add gitlab https://charts.gitlab.io
helm repo update
helm upgrade --install localcluster gitlab/gitlab-agent \
--namespace gitlab-agent-localcluster \
--create-namespace \
--set config.token=glagent-tnRaoRCrXxkK726CDB7zX-XXXXXXXXXXXXXXXXXXXXXX \
--set config.kasAddress=wss://kas.gitlab.com
Conferindo o que temos de pods no cluster no namespace que e
❯ k get pods -n gitlab-agent-localcluster
NAME READY STATUS RESTARTS AGE
localcluster-gitlab-agent-v2-676cb8cfb9-mjpwn 1/1 Running 0 3m39s
localcluster-gitlab-agent-v2-676cb8cfb9-ntv4t 1/1 Running 0 3m39s
Agora vamos criar o job em cicd/deploy/deploy.yaml para executar um deploy nesse cluster quando chegar na main.
deploy-prod:
stage: deploy
when: manual # coloquei só para aprimorar os conceitos, mas poderia ser tudo automatizado.
variables:
GIT_STRATEGY: none # Não vamos precisar do código então vamos ganhar velocidade
KUBE_CONTEXT: puziol/learn-gitlab-app:localcluster # Criamos essa variável que poderia estar setada dentro de um ambiente production
image:
name: bitnami/kubectl:latest
entrypoint: [""]
script:
- |
echo "Criando deployment com a imagem davidpuziol/learn-gitlab-app:latest"
kubectl config get-contexts
kubectl config use-context $KUBE_CONTEXT
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: learn-app-deployment
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: learn-app
template:
metadata:
labels:
app: learn-app
spec:
containers:
- name: learn-app
image: davidpuziol/learn-gitlab-app:latest
imagePullPolicy: Always
ports:
- containerPort: 3000
EOF
echo "Criando service para expor a aplicação na porta 3000"
kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: learn-app-service
namespace: default
spec:
selector:
app: learn-app
ports:
- protocol: TCP
port: 3000
targetPort: 3000
type: ClusterIP
EOF
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
Se esse código estivessem no repositório.
deploy-prod:
stage: deploy
when: manual
## GIT_STRATEGY: none # Precisaríamos fazer o clone
KUBE_CONTEXT: puziol/learn-gitlab-app:localcluster
image:
name: bitnami/kubectl:latest
entrypoint: [""]
script:
- |
echo "Criando deployment com a imagem davidpuziol/learn-gitlab-app:latest"
kubectl config get-contexts
kubectl config use-context $KUBE_CONTEXT
kubectl apply k8s/deployment.yaml
ubectl apply k8s/service.yaml
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
Para que funcione é necessário criar um merge request para main. Esse mesmo repositório podería se conectar em vários cluster e por isso precisamos avisar qual o context o kubectl deve usar passando o grupo/projeto/nome_da_conexão
.
Outro ponto importante é que o stage deploy virá depois do stage build e como o ultimo job do stage build que irá fazer o push então precisaremos esperar todo o processo de build acontecer e usar needs para ganhar velocidade não é possível.
Ao aceitar o merge de develop para main temos então deploy-prod em espera manual.
Apertando o play vamos fazer o deploy no kubernetes.