Skip to main content

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.

alt text

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.

alt text

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.

alt text

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.

alt text

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.

alt text

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.

alt text

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.

alt text

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.

alt text

Apertando o play vamos fazer o deploy no kubernetes.