aula-11: ArgoCD + GitLab Runner para GitOps CI/CD

- ArgoCD via Helm com recursos mínimos (~1Gi)
- GitLab Runner com executor Kubernetes
- Exemplo node-bugado com Dockerfile e .gitlab-ci.yml
- Manifests K8s para repositório GitOps
- README.md da aula-03 (liveness + readiness probes)
This commit is contained in:
Allyson de Paula
2025-12-31 21:19:40 -03:00
parent 07b7ee62d3
commit 8e743f6e69
14 changed files with 1611 additions and 0 deletions

View File

@@ -18,6 +18,7 @@ This is a workshop repository for teaching Docker and Kubernetes concepts, speci
- **aula-08/**: OpenTofu - provisioning HA Talos Kubernetes cluster on Hetzner Cloud with CCM and LoadBalancer - **aula-08/**: OpenTofu - provisioning HA Talos Kubernetes cluster on Hetzner Cloud with CCM and LoadBalancer
- **aula-09/**: Kubernetes lesson - n8n deployment via Helm (Hetzner Cloud with CSI Driver and multi-tenant support) - **aula-09/**: Kubernetes lesson - n8n deployment via Helm (Hetzner Cloud with CSI Driver and multi-tenant support)
- **aula-10/**: Kubernetes lesson - GitLab deployment via Helm with Container Registry and SSH - **aula-10/**: Kubernetes lesson - GitLab deployment via Helm with Container Registry and SSH
- **aula-11/**: Kubernetes lesson - ArgoCD + GitLab Runner for GitOps CI/CD pipelines
## Running the Examples ## Running the Examples
@@ -141,6 +142,27 @@ Prerequisites:
To remove: `./cleanup.sh` To remove: `./cleanup.sh`
### Aula 11 (Kubernetes - ArgoCD + GitLab Runner)
```bash
cd aula-11
export KUBECONFIG=/path/to/aula-08/kubeconfig
./setup.sh
```
Implements GitOps CI/CD pipeline with:
- GitLab Runner (Kubernetes executor) for CI pipelines
- ArgoCD for declarative GitOps continuous deployment
- Integration with GitLab self-hosted (aula-10)
- Example application with Dockerfile and GitLab CI pipeline
Pipeline flow: git push → GitLab CI (build) → Registry (push) → GitOps repo (update) → ArgoCD (sync) → Kubernetes (deploy)
Prerequisites:
- Completed Aula 10 (GitLab)
- NGINX Ingress with LoadBalancer
- DNS configured for ArgoCD hostname
To remove: `./cleanup.sh`
## App Behavior ## App Behavior
The Node.js app (`app.js`) is intentionally designed to: The Node.js app (`app.js`) is intentionally designed to:

141
aula-03/README.md Normal file
View File

@@ -0,0 +1,141 @@
# Aula 03 - Alta Disponibilidade com Replicas e Readiness Probe
Evolução da aula-02 adicionando múltiplas réplicas e readiness probe para zero-downtime.
## Conceitos
### Liveness vs Readiness Probe
| Probe | Função | Quando Falha |
|-------|--------|--------------|
| **Liveness** | "O container está vivo?" | Reinicia o container |
| **Readiness** | "O container está pronto para receber tráfego?" | Remove do Service (sem reiniciar) |
### Por que 5 Réplicas?
```
Cenário: MAX_REQUESTS=3, 5 réplicas
Tempo 0s: [Pod1: OK] [Pod2: OK] [Pod3: OK] [Pod4: OK] [Pod5: OK]
↑ Service distribui tráfego entre todos
Tempo 5s: [Pod1: HANG] [Pod2: OK] [Pod3: OK] [Pod4: OK] [Pod5: OK]
↑ readinessProbe falha → removido do Service
↑ livenessProbe falha → reiniciando...
Tempo 7s: [Pod1: RESTART] [Pod2: OK] [Pod3: OK] [Pod4: OK] [Pod5: OK]
↑ Pod1 reiniciou, readiness OK → volta ao Service
Resultado: Usuário NUNCA vê erro 503
```
## Arquitetura
```
LoadBalancer (:3000)
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Pod 1 │ │ Pod 2 │ │ Pod 3 │ ... (5 réplicas)
│ :3000 │ │ :3000 │ │ :3000 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
readinessProbe readinessProbe readinessProbe
livenessProbe livenessProbe livenessProbe
```
## Execução
```bash
cd aula-03
# Aplicar recursos
kubectl apply -f configmap.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
# Verificar pods
kubectl get pods -w
# Acessar a aplicação
# Docker Desktop: http://localhost:3000
# Minikube: minikube service node-bugado --url
```
## Configuração das Probes
```yaml
# Readiness - remove do Service rapidamente
readinessProbe:
httpGet:
path: /health
port: 3000
periodSeconds: 1 # Verifica a cada 1 segundo
failureThreshold: 1 # 1 falha = remove do Service
# Liveness - reinicia o container
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 2 # Espera app iniciar
periodSeconds: 1 # Verifica a cada 1 segundo
failureThreshold: 2 # 2 falhas = reinicia
```
## Teste de Resiliência
```bash
# Terminal 1: Monitorar pods
kubectl get pods -w
# Terminal 2: Fazer requisições contínuas
while true; do curl -s http://localhost:3000 && echo; sleep 0.5; done
# Observar:
# 1. Pods recebem requisições e eventualmente "travam"
# 2. readinessProbe remove pod do Service (sem interromper requests)
# 3. livenessProbe reinicia o pod
# 4. Pod volta ao Service quando ready
# 5. Usuário NÃO vê erros durante todo o processo
```
## Diferença da Aula 02
| Aspecto | Aula 02 | Aula 03 |
|---------|---------|---------|
| Réplicas | 1 | 5 |
| Probes | Liveness apenas | Liveness + Readiness |
| Downtime | Sim (durante restart) | Não (outras réplicas atendem) |
| Service | NodePort | LoadBalancer |
## Fluxo de Eventos
1. **Pod recebe requisições** → contador incrementa
2. **MAX_REQUESTS atingido** → app para de responder
3. **readinessProbe falha (1s)** → pod removido do Service
4. **Tráfego redirecionado** → outros pods atendem
5. **livenessProbe falha (2s)** → Kubernetes reinicia container
6. **Pod reinicia** → contador zerado, app responde
7. **readinessProbe OK** → pod volta ao Service
## Lições
1. **Réplicas = Resiliência**: Múltiplas cópias garantem disponibilidade
2. **Readiness ≠ Liveness**: Propósitos diferentes, ambos importantes
3. **Graceful Degradation**: Sistema continua funcionando com capacidade reduzida
4. **Self-Healing**: Kubernetes detecta e corrige problemas automaticamente
## Cleanup
```bash
./cleanup.sh
# ou
kubectl delete -f .
```
## Próxima Aula
**Aula 04**: NGINX Ingress com Keep Request (Lua) para zero-downtime ainda mais robusto, segurando requisições durante a janela de restart.

328
aula-11/README.md Normal file
View File

@@ -0,0 +1,328 @@
# Aula 11 - ArgoCD + GitLab Runner (GitOps)
Deploy de ArgoCD e GitLab Runner para pipeline CI/CD completo com GitOps.
## Arquitetura
```
┌─────────────────────────────────────────────────────────────┐
│ GitLab (aula-10) │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ git push │───►│ GitLab CI │───►│ Registry │ │
│ │ (código) │ │ (Runner K8s)│ │ (imagem:tag) │ │
│ └─────────────┘ └──────┬───────┘ └───────────────┘ │
└────────────────────────────┼────────────────────────────────┘
│ atualiza manifests
┌─────────────────────────────────────────────────────────────┐
│ Repositório GitOps │
│ apps/node-bugado/ │
│ ├── deployment.yaml (image: registry.../node-bugado:sha) │
│ ├── service.yaml │
│ └── configmap.yaml │
└─────────────────────────────────────────────────────────────┘
│ sync automático
┌─────────────────────────────────────────────────────────────┐
│ ArgoCD │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ Application │───►│ Sync │───►│ Kubernetes │ │
│ │ CRD │ │ Controller │ │ (deploy) │ │
│ └─────────────┘ └──────────────┘ └───────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## Pré-requisitos
1. **Cluster Talos na Hetzner** (aula-08) com:
- NGINX Ingress Controller com LoadBalancer
- Hetzner CSI Driver
2. **GitLab instalado** (aula-10)
3. **kubectl** e **helm** instalados
## Contexto do Cluster
```bash
# Usar kubeconfig da aula-08
export KUBECONFIG=$(pwd)/../aula-08/kubeconfig
kubectl cluster-info
```
## Instalação
```bash
cd aula-11
# Executar setup interativo
chmod +x setup.sh
./setup.sh
```
O script instala:
1. **GitLab Runner** - Executor Kubernetes para pipelines CI
2. **ArgoCD** - GitOps CD para Kubernetes
3. Configura integração SSH com GitLab
## Componentes Instalados
| Componente | Namespace | Recursos | Função |
|------------|-----------|----------|--------|
| ArgoCD Server | argocd | 256Mi/512Mi | UI + API |
| ArgoCD Repo Server | argocd | 256Mi/512Mi | Git clone/sync |
| ArgoCD Controller | argocd | 256Mi/512Mi | Reconciliation |
| ArgoCD Redis | argocd | 64Mi/128Mi | Cache |
| GitLab Runner | gitlab | 128Mi/256Mi | CI jobs como pods |
| **Total** | | ~1.2Gi | |
## Acesso
### ArgoCD
```
URL: https://argocd.{domain}
Username: admin
Senha: kubectl get secret argocd-initial-admin-secret -n argocd \
-o jsonpath='{.data.password}' | base64 -d
```
### GitLab Runner
Verificar status em: `https://{gitlab-host}/admin/runners`
```bash
# Ver pods do runner
kubectl get pods -n gitlab -l app=gitlab-runner
# Ver logs
kubectl logs -n gitlab -l app=gitlab-runner -f
```
## Configurar Pipeline GitOps
### 1. Criar Repositório GitOps no GitLab
Crie um repositório `gitops-demo` com a estrutura:
```
gitops-demo/
└── apps/
└── node-bugado/
├── configmap.yaml
├── deployment.yaml
├── service.yaml
└── ingress.yaml
```
Use os arquivos de exemplo em `node-bugado/k8s/`.
### 2. Configurar Deploy Key
```bash
# Gerar par de chaves
ssh-keygen -t ed25519 -f argocd-deploy-key -N ''
# Adicionar chave pública no GitLab:
# Settings → Repository → Deploy Keys
# Marcar "Grant write permissions"
```
### 3. Conectar Repositório no ArgoCD
Via UI:
1. Acesse https://argocd.{domain}
2. Settings → Repositories → Connect Repo
3. Method: SSH
4. URL: `git@git.{domain}:{usuario}/gitops-demo.git`
5. SSH private key: (conteúdo de argocd-deploy-key)
Ou via CLI:
```bash
argocd repo add git@git.kube.quest:usuario/gitops-demo.git \
--ssh-private-key-path argocd-deploy-key
```
### 4. Criar ArgoCD Application
```bash
kubectl apply -f - << 'EOF'
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: node-bugado
namespace: argocd
spec:
project: default
source:
repoURL: git@git.kube.quest:usuario/gitops-demo.git
targetRevision: HEAD
path: apps/node-bugado
destination:
server: https://kubernetes.default.svc
namespace: demo
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
EOF
```
### 5. Configurar Pipeline no Repositório da App
No repositório `node-bugado`:
1. Copie `.gitlab-ci.yml` de `node-bugado/.gitlab-ci.yml`
2. Configure variáveis em Settings → CI/CD → Variables:
- `GITOPS_REPO`: `git@git.kube.quest:usuario/gitops-demo.git`
- `DEPLOY_KEY`: Chave SSH privada (com write access ao repo gitops)
## Fluxo de Deploy
1. **Desenvolvedor** faz push no repositório `node-bugado`
2. **GitLab CI** dispara pipeline:
- Build: Constrói imagem Docker
- Push: Envia para GitLab Registry
- Deploy: Atualiza `deployment.yaml` no repo GitOps
3. **ArgoCD** detecta mudança no repo GitOps
4. **ArgoCD** sincroniza com cluster Kubernetes
5. **Kubernetes** faz rolling update dos pods
## GitLab Runner - Executor Kubernetes
O runner usa executor `kubernetes`, onde cada job CI:
- Roda como um **pod efêmero** no cluster
- Tem acesso a **Docker-in-Docker** para builds
- É automaticamente **limpo** após conclusão
- **Escala** conforme demanda de jobs
```yaml
# gitlab-runner-values.yaml
runners:
executor: kubernetes
privileged: true # Necessário para Docker-in-Docker
namespace: gitlab
```
### Por que Docker-in-Docker?
Para construir imagens Docker dentro de um container (o job do CI), precisamos:
1. **Privileged mode**: Acesso ao kernel para criar containers
2. **Docker daemon**: Serviço Docker rodando no job
Alternativas (mais seguras, mas mais complexas):
- **Kaniko**: Build sem Docker daemon
- **Buildah**: Build rootless
## Troubleshooting
### ArgoCD não sincroniza
```bash
# Verificar status da Application
kubectl get applications -n argocd
# Ver detalhes
kubectl describe application node-bugado -n argocd
# Ver logs do controller
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-application-controller
```
### Runner não aparece no GitLab
```bash
# Verificar pod do runner
kubectl get pods -n gitlab -l app=gitlab-runner
# Ver logs
kubectl logs -n gitlab -l app=gitlab-runner
# Verificar registration token
kubectl get secret gitlab-gitlab-runner-secret -n gitlab -o yaml
```
### Jobs CI falham
```bash
# Ver pods dos jobs
kubectl get pods -n gitlab
# Ver logs de um job específico
kubectl logs -n gitlab runner-xxxxx-project-xxx-concurrent-xxx
```
### Erro SSH ao conectar repositório
```bash
# Verificar known hosts
kubectl get configmap argocd-ssh-known-hosts-cm -n argocd -o yaml
# Adicionar manualmente
ssh-keyscan git.kube.quest | kubectl create configmap argocd-ssh-known-hosts-cm \
--from-file=ssh_known_hosts=/dev/stdin -n argocd --dry-run=client -o yaml | kubectl apply -f -
```
## Comandos Úteis
```bash
# ArgoCD
kubectl get applications -n argocd
kubectl get pods -n argocd
argocd app list
argocd app sync node-bugado
# GitLab Runner
kubectl get pods -n gitlab -l app=gitlab-runner
kubectl logs -n gitlab -l app=gitlab-runner -f
# Ver todos os recursos do demo
kubectl get all -n demo
# Forçar re-sync
argocd app sync node-bugado --force
# Ver diff antes de sync
argocd app diff node-bugado
```
## Lições do Workshop
1. **GitOps**: Git como fonte única de verdade para estado do cluster
2. **Separação CI/CD**: GitLab CI = build, ArgoCD = deploy
3. **Runner Kubernetes**: Jobs como pods efêmeros e escaláveis
4. **Docker-in-Docker**: Build de imagens em containers privilegiados
5. **Auditoria**: Histórico de deploys = histórico Git
6. **Self-Heal**: ArgoCD corrige drift automaticamente
7. **Segurança**: Deploy Keys com permissão mínima
## Cleanup
```bash
./cleanup.sh
```
Remove ArgoCD e GitLab Runner. Não remove GitLab ou infraestrutura base.
## Custos
| Recurso | Custo/mês |
|---------|-----------|
| ArgoCD + Runner (~1.2Gi) | Usa workers existentes |
| **Total Adicional** | ~$0 |
## Próximos Passos
- **Aula 12**: eStargz + Lazy Pulling
- Converter imagens para formato eStargz
- Demonstrar startup mais rápido com lazy pulling
- Medir diferença de performance
## Referências
- [ArgoCD Docs](https://argo-cd.readthedocs.io/en/stable/)
- [ArgoCD Helm Chart](https://github.com/argoproj/argo-helm)
- [GitLab Runner Kubernetes Executor](https://docs.gitlab.com/runner/executors/kubernetes.html)
- [GitOps with GitLab + ArgoCD](https://medium.com/@andrew.kaczynski/gitops-in-kubernetes-argo-cd-and-gitlab-ci-cd-5828c8eb34d6)

119
aula-11/argocd-values.yaml Normal file
View File

@@ -0,0 +1,119 @@
# =============================================================================
# ArgoCD Helm Chart - Configuração para Hetzner CAX11
# =============================================================================
#
# Recursos mínimos otimizados para cluster pequeno (~1Gi total).
# Desabilita HA e componentes não essenciais.
#
# Valores dinâmicos (configurados via --set no setup.sh):
# - server.ingress.hosts[0]
# - server.ingress.tls[0].hosts[0]
# - server.ingress.annotations.cert-manager.io/cluster-issuer (se Let's Encrypt)
#
# =============================================================================
global:
# Domínio base (será sobrescrito via --set)
domain: argocd.kube.quest
# =============================================================================
# ARGOCD SERVER (UI + API)
# =============================================================================
server:
replicas: 1
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 512Mi
cpu: 500m
ingress:
enabled: true
ingressClassName: nginx
# Não usar ssl-passthrough nem backend-protocol HTTPS
# O ArgoCD roda em modo insecure (HTTP na porta 80)
# TLS é terminado no NGINX Ingress (ou CloudFlare)
# hosts e tls configurados via --set
# Modo insecure - TLS termina no ingress/proxy, não no server
extraArgs:
- --insecure
# =============================================================================
# ARGOCD REPO SERVER (Git operations)
# =============================================================================
repoServer:
replicas: 1
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 512Mi
cpu: 500m
# =============================================================================
# ARGOCD APPLICATION CONTROLLER (Reconciliation)
# =============================================================================
controller:
replicas: 1
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 512Mi
cpu: 500m
# =============================================================================
# REDIS (Cache)
# =============================================================================
redis:
resources:
requests:
memory: 64Mi
cpu: 50m
limits:
memory: 128Mi
cpu: 100m
# =============================================================================
# COMPONENTES DESABILITADOS
# =============================================================================
# Redis HA - não necessário para cluster pequeno
redis-ha:
enabled: false
# Dex (SSO) - usar autenticação local
dex:
enabled: false
# ApplicationSet Controller - pode habilitar depois se necessário
applicationSet:
enabled: false
# Notifications Controller - não essencial
notifications:
enabled: false
# =============================================================================
# CONFIGURAÇÕES GLOBAIS
# =============================================================================
configs:
params:
# Timeout para operações Git
server.repo.server.timeout.seconds: "60"
# Intervalo de reconciliação (3 minutos)
timeout.reconciliation: "180s"
cm:
# Permitir acesso via HTTP (terminação TLS no ingress)
server.insecure: "true"
# Timeout de sessão admin
admin.enabled: "true"

72
aula-11/cleanup.sh Executable file
View File

@@ -0,0 +1,72 @@
#!/bin/bash
# =============================================================================
# Aula 11 - Cleanup
# =============================================================================
#
# Remove ArgoCD e GitLab Runner instalados pelo setup.sh.
# NÃO remove a infraestrutura base (GitLab, NGINX Ingress, etc).
#
# =============================================================================
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
echo ""
echo "=========================================="
echo " Removendo ArgoCD e GitLab Runner"
echo "=========================================="
echo ""
# Remover ArgoCD Applications primeiro
log_info "Removendo ArgoCD Applications..."
kubectl delete applications --all -n argocd 2>/dev/null || true
# Remover ArgoCD
log_info "Removendo ArgoCD..."
if helm status argocd -n argocd &> /dev/null; then
helm uninstall argocd -n argocd --wait
log_success "ArgoCD removido"
else
log_warn "ArgoCD não estava instalado"
fi
# Remover namespace argocd
log_info "Removendo namespace argocd..."
kubectl delete namespace argocd --timeout=60s 2>/dev/null || true
# Remover GitLab Runner
log_info "Removendo GitLab Runner..."
if helm status gitlab-runner -n gitlab &> /dev/null; then
helm uninstall gitlab-runner -n gitlab --wait
log_success "GitLab Runner removido"
else
log_warn "GitLab Runner não estava instalado"
fi
# Limpar secrets residuais
log_info "Limpando secrets residuais..."
kubectl delete secret argocd-ssh-known-hosts-cm -n argocd 2>/dev/null || true
# Remover arquivo .env local
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ -f "${SCRIPT_DIR}/.env" ]]; then
rm "${SCRIPT_DIR}/.env"
log_info "Arquivo .env removido"
fi
echo ""
log_success "Cleanup concluído!"
echo ""
echo "Nota: GitLab, NGINX Ingress e infraestrutura base foram mantidos."
echo "Para remover o GitLab, execute: ../aula-10/cleanup.sh"
echo ""

View File

@@ -0,0 +1,132 @@
# =============================================================================
# GitLab Runner Helm Chart - Executor Kubernetes
# =============================================================================
#
# Configura GitLab Runner para executar jobs como pods no Kubernetes.
# Suporta Docker-in-Docker para build de imagens.
#
# Valores dinâmicos (configurados via --set no setup.sh):
# - gitlabUrl
# - runnerToken (novo método) ou runnerRegistrationToken (legacy)
#
# =============================================================================
# Número máximo de jobs simultâneos
concurrent: 2
# Intervalo de check por novos jobs (segundos)
checkInterval: 30
# Intervalo de heartbeat (segundos)
heartbeatInterval: 30
# =============================================================================
# CONFIGURAÇÃO DO RUNNER
# =============================================================================
runners:
# Executor: kubernetes (jobs rodam como pods)
executor: kubernetes
# Privileged mode necessário para Docker-in-Docker
privileged: true
# Namespace onde os jobs serão executados
namespace: gitlab
# Tags para identificar o runner
tags: "kubernetes,docker,hetzner"
# Rodar jobs sem tag também
runUntagged: true
# Proteger branches protegidas
protected: false
# Imagem padrão para jobs
image: alpine:latest
# Helper image (para git clone, artifacts, etc)
helper:
image: gitlab/gitlab-runner-helper:alpine-latest
# Configuração TOML adicional
config: |
[[runners]]
[runners.kubernetes]
image = "alpine:latest"
privileged = true
# Recursos para pods de job
cpu_request = "100m"
cpu_limit = "500m"
memory_request = "256Mi"
memory_limit = "512Mi"
# Timeout para pods
poll_timeout = 600
# Pull policy
pull_policy = ["if-not-present"]
# Volume para Docker certs (DinD)
[[runners.kubernetes.volumes.empty_dir]]
name = "docker-certs"
mount_path = "/certs/client"
medium = "Memory"
# Volume para cache de build
[[runners.kubernetes.volumes.empty_dir]]
name = "build-cache"
mount_path = "/cache"
medium = ""
# =============================================================================
# RECURSOS DO RUNNER (manager pod)
# =============================================================================
resources:
requests:
memory: 128Mi
cpu: 50m
limits:
memory: 256Mi
cpu: 200m
# =============================================================================
# RBAC
# =============================================================================
rbac:
create: true
# Permissões para criar pods, secrets, configmaps
rules:
- apiGroups: [""]
resources: ["pods", "pods/exec", "secrets", "configmaps"]
verbs: ["get", "list", "watch", "create", "patch", "update", "delete"]
- apiGroups: [""]
resources: ["pods/attach", "pods/log"]
verbs: ["get", "create"]
# =============================================================================
# SERVICE ACCOUNT
# =============================================================================
serviceAccount:
create: true
name: gitlab-runner
# =============================================================================
# MÉTRICAS (opcional)
# =============================================================================
metrics:
enabled: false
# =============================================================================
# POD SECURITY
# =============================================================================
podSecurityContext:
runAsNonRoot: true
runAsUser: 100
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
drop: ["ALL"]

View File

@@ -0,0 +1,116 @@
# =============================================================================
# GitLab CI/CD Pipeline - node-bugado
# =============================================================================
#
# Pipeline GitOps:
# 1. Build: Constrói imagem Docker e faz push para GitLab Registry
# 2. Deploy: Atualiza manifests no repo GitOps (ArgoCD faz sync)
#
# Variáveis necessárias (Settings → CI/CD → Variables):
# - GITOPS_REPO: URL do repositório GitOps (ex: git@git.kube.quest:user/gitops-demo.git)
# - DEPLOY_KEY: Chave SSH privada para push no repo GitOps
#
# =============================================================================
stages:
- build
- deploy
variables:
# Registry do GitLab
REGISTRY: ${CI_REGISTRY}
IMAGE_NAME: ${CI_REGISTRY_IMAGE}
# Para usar registry externo, descomente:
# REGISTRY: registry.kube.quest
# IMAGE_NAME: ${REGISTRY}/${CI_PROJECT_PATH}
# =============================================================================
# BUILD - Construir e publicar imagem Docker
# =============================================================================
build:
stage: build
image: docker:24
services:
- docker:24-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
before_script:
- docker login -u ${CI_REGISTRY_USER} -p ${CI_REGISTRY_PASSWORD} ${CI_REGISTRY}
script:
- echo "Building ${IMAGE_NAME}:${CI_COMMIT_SHA}"
- docker build -t ${IMAGE_NAME}:${CI_COMMIT_SHA} .
- docker tag ${IMAGE_NAME}:${CI_COMMIT_SHA} ${IMAGE_NAME}:latest
- docker push ${IMAGE_NAME}:${CI_COMMIT_SHA}
- docker push ${IMAGE_NAME}:latest
only:
- main
- master
tags:
- kubernetes
- docker
# =============================================================================
# DEPLOY - Atualizar manifests no repositório GitOps
# =============================================================================
deploy:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache git openssh-client
# Configurar SSH para o repo GitOps
- mkdir -p ~/.ssh
- echo "${DEPLOY_KEY}" | tr -d '\r' > ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
- ssh-keyscan -t ed25519 $(echo ${GITOPS_REPO} | sed 's/.*@\([^:]*\).*/\1/') >> ~/.ssh/known_hosts 2>/dev/null || true
# Configurar git
- git config --global user.email "ci@gitlab.local"
- git config --global user.name "GitLab CI"
script:
- echo "Updating GitOps repo with image ${IMAGE_NAME}:${CI_COMMIT_SHA}"
# Clonar repo GitOps
- git clone ${GITOPS_REPO} gitops
- cd gitops
# Atualizar tag da imagem no deployment
- |
if [ -f apps/node-bugado/deployment.yaml ]; then
sed -i "s|image:.*node-bugado.*|image: ${IMAGE_NAME}:${CI_COMMIT_SHA}|g" apps/node-bugado/deployment.yaml
git add apps/node-bugado/deployment.yaml
git commit -m "Deploy node-bugado ${CI_COMMIT_SHA:0:8}
Pipeline: ${CI_PIPELINE_URL}
Commit: ${CI_COMMIT_SHA}
Author: ${CI_COMMIT_AUTHOR}"
git push
echo "GitOps repo updated successfully"
else
echo "WARNING: apps/node-bugado/deployment.yaml not found"
echo "Please create the GitOps structure first"
exit 1
fi
only:
- main
- master
tags:
- kubernetes
when: on_success
needs:
- build
# =============================================================================
# NOTAS
# =============================================================================
#
# Para configurar as variáveis:
#
# 1. GITOPS_REPO:
# - Vá em Settings → CI/CD → Variables
# - Adicione: GITOPS_REPO = git@git.kube.quest:usuario/gitops-demo.git
#
# 2. DEPLOY_KEY:
# - Gere uma chave: ssh-keygen -t ed25519 -f deploy-key -N ''
# - Adicione a chave PÚBLICA no repo GitOps: Settings → Repository → Deploy Keys
# - Marque "Grant write permissions to this key"
# - Adicione a chave PRIVADA como variável: DEPLOY_KEY = <conteúdo de deploy-key>
# - Marque como "Protected" e "Masked"
#
# =============================================================================

View File

@@ -0,0 +1,35 @@
# =============================================================================
# Dockerfile - node-bugado
# =============================================================================
#
# Imagem simples para demonstrar CI/CD com GitLab + ArgoCD.
# A aplicação "trava" após MAX_REQUESTS requisições para simular
# falhas e demonstrar auto-healing do Kubernetes.
#
# Build:
# docker build -t registry.kube.quest/<usuario>/node-bugado:v1 .
#
# =============================================================================
FROM node:24-alpine
LABEL maintainer="workshop"
LABEL description="App que trava para demonstrar liveness probes"
# Metadados OCI
LABEL org.opencontainers.image.source="https://git.kube.quest"
LABEL org.opencontainers.image.title="node-bugado"
WORKDIR /app
# Copiar código da aplicação
COPY app.js .
# Porta da aplicação
EXPOSE 3000
# Usuário não-root
USER node
# Comando de inicialização
CMD ["node", "app.js"]

View File

@@ -0,0 +1,35 @@
const http = require("http");
const MAX_REQUESTS = Number(process.env.MAX_REQUESTS || 3);
let requestCount = 0;
console.log("MAX_REQUESTS =", MAX_REQUESTS);
const server = http.createServer((req, res) => {
if (req.url === "/health") {
if (requestCount > MAX_REQUESTS) {
// app travado, mas processo vivo
return;
}
res.writeHead(200);
res.end(`ok`);
return;
}
requestCount++;
console.log("request", requestCount);
if (requestCount > MAX_REQUESTS) {
console.log(`App travado apos ${MAX_REQUESTS} requests`);
return;
}
res.writeHead(200);
res.end(`Req -> ${requestCount}/${MAX_REQUESTS}`);
});
server.listen(3000, () => {
console.log("App rodando na porta 3000");
});

View File

@@ -0,0 +1,17 @@
# =============================================================================
# ConfigMap - node-bugado
# =============================================================================
#
# Configuração da aplicação.
# MAX_REQUESTS define quantas requisições antes de "travar".
#
# =============================================================================
apiVersion: v1
kind: ConfigMap
metadata:
name: node-bugado-config
labels:
app: node-bugado
data:
MAX_REQUESTS: "5"

View File

@@ -0,0 +1,76 @@
# =============================================================================
# Deployment - node-bugado
# =============================================================================
#
# Deployment com liveness e readiness probes.
# A imagem é atualizada automaticamente pelo pipeline GitLab CI.
#
# =============================================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: node-bugado
labels:
app: node-bugado
spec:
replicas: 2
selector:
matchLabels:
app: node-bugado
template:
metadata:
labels:
app: node-bugado
spec:
terminationGracePeriodSeconds: 5
containers:
- name: node-bugado
# IMPORTANTE: Esta linha é atualizada automaticamente pelo GitLab CI
image: registry.kube.quest/workshop/node-bugado:latest
ports:
- containerPort: 3000
name: http
# Variáveis de ambiente via ConfigMap
env:
- name: MAX_REQUESTS
valueFrom:
configMapKeyRef:
name: node-bugado-config
key: MAX_REQUESTS
# Recursos
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
# Liveness probe - detecta quando a app trava
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 3
failureThreshold: 2
timeoutSeconds: 2
# Readiness probe - remove do service enquanto não está pronta
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 2
periodSeconds: 2
failureThreshold: 1
timeoutSeconds: 1
# Pod security context
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000

View File

@@ -0,0 +1,38 @@
# =============================================================================
# Ingress - node-bugado
# =============================================================================
#
# Ingress NGINX para expor a aplicação externamente.
# Configure o hostname de acordo com seu domínio.
#
# =============================================================================
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: node-bugado
labels:
app: node-bugado
annotations:
nginx.ingress.kubernetes.io/proxy-connect-timeout: "30"
nginx.ingress.kubernetes.io/proxy-read-timeout: "30"
# Descomente para Let's Encrypt:
# cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
rules:
- host: bugado.kube.quest
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: node-bugado
port:
number: 80
# Descomente para TLS:
# tls:
# - hosts:
# - bugado.kube.quest
# secretName: node-bugado-tls

View File

@@ -0,0 +1,24 @@
# =============================================================================
# Service - node-bugado
# =============================================================================
#
# Service ClusterIP para expor a aplicação internamente.
# Use com Ingress para acesso externo.
#
# =============================================================================
apiVersion: v1
kind: Service
metadata:
name: node-bugado
labels:
app: node-bugado
spec:
type: ClusterIP
selector:
app: node-bugado
ports:
- name: http
port: 80
targetPort: 3000
protocol: TCP

456
aula-11/setup.sh Executable file
View File

@@ -0,0 +1,456 @@
#!/bin/bash
# =============================================================================
# Aula 11 - ArgoCD + GitLab Runner (GitOps)
# =============================================================================
#
# Este script instala:
# 1. GitLab Runner (executor kubernetes) para CI
# 2. ArgoCD para CD declarativo (GitOps)
# 3. Integração com GitLab self-hosted (aula-10)
#
# Pré-requisitos:
# - Cluster Kubernetes (aula-08)
# - GitLab instalado (aula-10)
# - NGINX Ingress Controller
# - kubectl e helm instalados
#
# =============================================================================
set -e
# Cores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Funções de log
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Diretório do script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="${SCRIPT_DIR}/.env"
# =============================================================================
# VERIFICAR PRÉ-REQUISITOS
# =============================================================================
log_info "Verificando pré-requisitos..."
# Verificar kubectl
if ! command -v kubectl &> /dev/null; then
log_error "kubectl não encontrado. Instale com: brew install kubectl"
exit 1
fi
# Verificar helm
if ! command -v helm &> /dev/null; then
log_error "helm não encontrado. Instale com: brew install helm"
exit 1
fi
# Verificar conexão com cluster
if ! kubectl cluster-info &> /dev/null; then
log_error "Não foi possível conectar ao cluster Kubernetes"
log_info "Verifique se KUBECONFIG está configurado corretamente"
log_info "Exemplo: export KUBECONFIG=\$(pwd)/../aula-08/kubeconfig"
exit 1
fi
# Verificar se GitLab está instalado
if ! kubectl get namespace gitlab &> /dev/null; then
log_error "Namespace 'gitlab' não encontrado"
log_info "Execute primeiro a aula-10 para instalar o GitLab"
exit 1
fi
# Verificar NGINX Ingress
if ! kubectl get ingressclass nginx &> /dev/null; then
log_error "NGINX Ingress Controller não encontrado"
log_info "Execute o script de instalação do NGINX Ingress na aula-08"
exit 1
fi
log_success "Pré-requisitos verificados"
# =============================================================================
# CARREGAR CONFIGURAÇÃO EXISTENTE
# =============================================================================
# Carregar configuração local PRIMEIRO (se existir)
if [[ -f "$ENV_FILE" ]]; then
log_info "Carregando configuração local..."
source "$ENV_FILE"
fi
# Se não tiver configuração local, tentar herdar da aula-10
if [[ -z "$GITLAB_HOST" ]]; then
AULA10_ENV="${SCRIPT_DIR}/../aula-10/.env"
if [[ -f "$AULA10_ENV" ]]; then
log_info "Herdando configuração da aula-10..."
source "$AULA10_ENV"
GITLAB_HOST="${GITLAB_HOST:-}"
DOMAIN="${DOMAIN:-}"
USE_CLOUDFLARE="${USE_CLOUDFLARE:-false}"
USE_LETSENCRYPT="${USE_LETSENCRYPT:-false}"
LETSENCRYPT_EMAIL="${LETSENCRYPT_EMAIL:-}"
fi
fi
# =============================================================================
# COLETAR CONFIGURAÇÃO
# =============================================================================
echo ""
echo "=========================================="
echo " Configuração do ArgoCD + GitLab CI"
echo "=========================================="
echo ""
# GitLab Host
if [[ -z "$GITLAB_HOST" ]]; then
read -p "Hostname do GitLab (ex: git.kube.quest): " GITLAB_HOST
fi
log_info "GitLab: https://${GITLAB_HOST}"
# Extrair domínio base
if [[ -z "$DOMAIN" ]]; then
DOMAIN=$(echo "$GITLAB_HOST" | sed 's/^[^.]*\.//')
fi
# ArgoCD Host
if [[ -z "$ARGOCD_HOST" ]]; then
DEFAULT_ARGOCD="argocd.${DOMAIN}"
read -p "Hostname do ArgoCD [${DEFAULT_ARGOCD}]: " ARGOCD_HOST
ARGOCD_HOST="${ARGOCD_HOST:-$DEFAULT_ARGOCD}"
fi
log_info "ArgoCD: https://${ARGOCD_HOST}"
# TLS (herdar da aula-10 ou perguntar)
if [[ "$USE_CLOUDFLARE" != "true" && "$USE_LETSENCRYPT" != "true" ]]; then
echo ""
echo "Configuração de TLS:"
echo " 1) CloudFlare (proxy ativo - TLS na borda)"
echo " 2) Let's Encrypt (cert-manager)"
echo " 3) HTTP apenas (desenvolvimento)"
read -p "Escolha [1-3]: " TLS_CHOICE
case $TLS_CHOICE in
1)
USE_CLOUDFLARE=true
USE_LETSENCRYPT=false
;;
2)
USE_CLOUDFLARE=false
USE_LETSENCRYPT=true
if [[ -z "$LETSENCRYPT_EMAIL" ]]; then
read -p "Email para Let's Encrypt: " LETSENCRYPT_EMAIL
fi
;;
*)
USE_CLOUDFLARE=false
USE_LETSENCRYPT=false
;;
esac
fi
# Salvar configuração
cat > "$ENV_FILE" << EOF
# Configuração gerada pelo setup.sh
# $(date)
GITLAB_HOST=${GITLAB_HOST}
ARGOCD_HOST=${ARGOCD_HOST}
DOMAIN=${DOMAIN}
USE_CLOUDFLARE=${USE_CLOUDFLARE}
USE_LETSENCRYPT=${USE_LETSENCRYPT}
LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL}
EOF
log_success "Configuração salva em ${ENV_FILE}"
# =============================================================================
# INSTALAR GITLAB RUNNER
# =============================================================================
echo ""
log_info "=== Instalando GitLab Runner ==="
# Adicionar repositório Helm
helm repo add gitlab https://charts.gitlab.io 2>/dev/null || true
helm repo update
# Verificar se já está instalado
if helm status gitlab-runner -n gitlab &> /dev/null; then
log_warn "GitLab Runner já instalado, fazendo upgrade..."
HELM_CMD="upgrade"
else
HELM_CMD="install"
fi
# Obter runner registration token
log_info "Obtendo token de registro do GitLab Runner..."
# Tentar obter o token do secret
RUNNER_TOKEN=""
if kubectl get secret gitlab-gitlab-runner-secret -n gitlab &> /dev/null; then
RUNNER_TOKEN=$(kubectl get secret gitlab-gitlab-runner-secret -n gitlab -o jsonpath='{.data.runner-registration-token}' 2>/dev/null | base64 -d 2>/dev/null || echo "")
fi
if [[ -z "$RUNNER_TOKEN" ]]; then
log_warn "Token de registro não encontrado automaticamente"
echo ""
echo "Para obter o token:"
echo " 1. Acesse https://${GITLAB_HOST}/admin/runners"
echo " 2. Clique em 'New instance runner'"
echo " 3. Copie o registration token"
echo ""
read -p "Cole o registration token: " RUNNER_TOKEN
fi
if [[ -z "$RUNNER_TOKEN" ]]; then
log_error "Token de registro é obrigatório"
exit 1
fi
# Instalar GitLab Runner
log_info "Instalando GitLab Runner via Helm..."
helm ${HELM_CMD} gitlab-runner gitlab/gitlab-runner \
--namespace gitlab \
-f "${SCRIPT_DIR}/gitlab-runner-values.yaml" \
--set gitlabUrl="https://${GITLAB_HOST}" \
--set runnerRegistrationToken="${RUNNER_TOKEN}" \
--wait --timeout 5m
log_success "GitLab Runner instalado"
# Verificar pods
log_info "Verificando pods do GitLab Runner..."
kubectl get pods -n gitlab -l app=gitlab-runner
# =============================================================================
# INSTALAR CERT-MANAGER (se Let's Encrypt)
# =============================================================================
if [[ "$USE_LETSENCRYPT" == "true" ]]; then
echo ""
log_info "=== Verificando cert-manager ==="
if ! kubectl get namespace cert-manager &> /dev/null; then
log_info "Instalando cert-manager..."
helm repo add jetstack https://charts.jetstack.io 2>/dev/null || true
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true \
--wait --timeout 5m
log_success "cert-manager instalado"
else
log_success "cert-manager já instalado"
fi
# Criar ClusterIssuer se não existir
if ! kubectl get clusterissuer letsencrypt-prod &> /dev/null; then
log_info "Criando ClusterIssuer letsencrypt-prod..."
kubectl apply -f - << EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ${LETSENCRYPT_EMAIL}
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
EOF
log_success "ClusterIssuer criado"
fi
fi
# =============================================================================
# INSTALAR ARGOCD
# =============================================================================
echo ""
log_info "=== Instalando ArgoCD ==="
# Adicionar repositório Helm
helm repo add argo https://argoproj.github.io/argo-helm 2>/dev/null || true
helm repo update
# Criar namespace
kubectl create namespace argocd 2>/dev/null || true
# Verificar se já está instalado
if helm status argocd -n argocd &> /dev/null; then
log_warn "ArgoCD já instalado, fazendo upgrade..."
HELM_CMD="upgrade"
else
HELM_CMD="install"
fi
# Construir argumentos Helm
HELM_ARGS=""
HELM_ARGS="$HELM_ARGS --set global.domain=${ARGOCD_HOST}"
HELM_ARGS="$HELM_ARGS --set server.ingress.hosts[0]=${ARGOCD_HOST}"
if [[ "$USE_LETSENCRYPT" == "true" ]]; then
HELM_ARGS="$HELM_ARGS --set server.ingress.tls[0].secretName=argocd-server-tls"
HELM_ARGS="$HELM_ARGS --set server.ingress.tls[0].hosts[0]=${ARGOCD_HOST}"
HELM_ARGS="$HELM_ARGS --set 'server.ingress.annotations.cert-manager\.io/cluster-issuer=letsencrypt-prod'"
elif [[ "$USE_CLOUDFLARE" == "true" ]]; then
# CloudFlare faz TLS na borda
HELM_ARGS="$HELM_ARGS --set server.ingress.tls="
fi
# Instalar ArgoCD
log_info "Instalando ArgoCD via Helm..."
eval helm ${HELM_CMD} argocd argo/argo-cd \
--namespace argocd \
-f "${SCRIPT_DIR}/argocd-values.yaml" \
${HELM_ARGS} \
--wait --timeout 10m
log_success "ArgoCD instalado"
# =============================================================================
# OBTER SENHA DO ADMIN
# =============================================================================
echo ""
log_info "=== Credenciais do ArgoCD ==="
# Aguardar secret ser criado
log_info "Aguardando secret de credenciais..."
for i in {1..30}; do
if kubectl get secret argocd-initial-admin-secret -n argocd &> /dev/null; then
break
fi
sleep 2
done
ARGOCD_PASSWORD=$(kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath='{.data.password}' 2>/dev/null | base64 -d 2>/dev/null || echo "")
if [[ -z "$ARGOCD_PASSWORD" ]]; then
log_warn "Não foi possível obter a senha inicial"
log_info "Tente: kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath='{.data.password}' | base64 -d"
else
echo ""
echo "=========================================="
echo " ArgoCD Credenciais"
echo "=========================================="
echo " URL: https://${ARGOCD_HOST}"
echo " Username: admin"
echo " Password: ${ARGOCD_PASSWORD}"
echo "=========================================="
fi
# =============================================================================
# CONFIGURAR INTEGRAÇÃO GITLAB
# =============================================================================
echo ""
log_info "=== Configurando Integração GitLab ==="
# Obter host key do GitLab
log_info "Obtendo SSH host key do GitLab..."
SSH_HOST_KEY=$(ssh-keyscan -t ed25519 ${GITLAB_HOST} 2>/dev/null || ssh-keyscan ${GITLAB_HOST} 2>/dev/null || echo "")
if [[ -n "$SSH_HOST_KEY" ]]; then
# Criar ConfigMap com known hosts
kubectl create configmap argocd-ssh-known-hosts-cm \
--from-literal=ssh_known_hosts="${SSH_HOST_KEY}" \
-n argocd \
--dry-run=client -o yaml | kubectl apply -f -
log_success "SSH host key configurado"
else
log_warn "Não foi possível obter SSH host key"
log_info "Configure manualmente: argocd cert add-ssh --batch < known_hosts"
fi
# =============================================================================
# INSTRUÇÕES FINAIS
# =============================================================================
echo ""
echo "=========================================="
echo " Instalação Concluída!"
echo "=========================================="
echo ""
echo "ArgoCD:"
echo " URL: https://${ARGOCD_HOST}"
echo " Username: admin"
if [[ -n "$ARGOCD_PASSWORD" ]]; then
echo " Password: ${ARGOCD_PASSWORD}"
else
echo " Password: kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath='{.data.password}' | base64 -d"
fi
echo ""
echo "GitLab Runner:"
echo " Namespace: gitlab"
echo " Verificar: kubectl get pods -n gitlab -l app=gitlab-runner"
echo " Status no GitLab: https://${GITLAB_HOST}/admin/runners"
echo ""
echo "Próximos passos:"
echo ""
echo "1. Configure DNS:"
echo " Adicione registro A para ${ARGOCD_HOST} apontando para o LoadBalancer"
echo ""
echo "2. Crie um repositório GitOps no GitLab:"
echo " - Nome: gitops-demo"
echo " - Estrutura: apps/node-bugado/{deployment,service,configmap}.yaml"
echo ""
echo "3. Configure repositório no ArgoCD:"
echo " a) Gere uma deploy key:"
echo " ssh-keygen -t ed25519 -f argocd-deploy-key -N ''"
echo ""
echo " b) Adicione a chave pública no GitLab:"
echo " Settings → Repository → Deploy Keys"
echo ""
echo " c) Conecte o repositório no ArgoCD:"
echo " - Acesse https://${ARGOCD_HOST}"
echo " - Settings → Repositories → Connect Repo"
echo " - Method: SSH"
echo " - URL: git@${GITLAB_HOST}:<usuario>/gitops-demo.git"
echo " - SSH private key: (conteúdo de argocd-deploy-key)"
echo ""
echo "4. Crie uma Application no ArgoCD:"
echo " kubectl apply -f - << 'EOF'"
echo " apiVersion: argoproj.io/v1alpha1"
echo " kind: Application"
echo " metadata:"
echo " name: node-bugado"
echo " namespace: argocd"
echo " spec:"
echo " project: default"
echo " source:"
echo " repoURL: git@${GITLAB_HOST}:<usuario>/gitops-demo.git"
echo " targetRevision: HEAD"
echo " path: apps/node-bugado"
echo " destination:"
echo " server: https://kubernetes.default.svc"
echo " namespace: demo"
echo " syncPolicy:"
echo " automated:"
echo " prune: true"
echo " selfHeal: true"
echo " syncOptions:"
echo " - CreateNamespace=true"
echo " EOF"
echo ""
echo "Comandos úteis:"
echo " kubectl get pods -n argocd"
echo " kubectl get applications -n argocd"
echo " kubectl logs -n gitlab -l app=gitlab-runner -f"
echo ""