├── app ├── requirements.txt ├── Dockerfile └── src │ ├── app.py │ └── templates │ └── index.html ├── assets ├── banner.png ├── backend-model ├── tfvars-model └── diagram.drawio ├── infra └── terraform │ ├── network.tf │ ├── registry.tf │ ├── provider.tf │ ├── doks.tf │ ├── outputs.tf │ ├── variables.tf │ └── README.md ├── k8s ├── app │ ├── service.yaml │ ├── deployment.yaml │ └── ingress.yaml ├── setup │ ├── cluster-issuer.yaml │ └── argo-ingress.yaml └── argocd │ └── application.yaml ├── .gitignore ├── LICENSE ├── .github └── workflows │ └── build-deploy.yaml └── README.md /app/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==3.0.0 2 | Werkzeug==3.0.1 -------------------------------------------------------------------------------- /assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellabeneta/project-argocd/HEAD/assets/banner.png -------------------------------------------------------------------------------- /infra/terraform/network.tf: -------------------------------------------------------------------------------- 1 | resource "digitalocean_vpc" "doks_vpc" { 2 | name = var.vpc_name 3 | region = var.region 4 | ip_range = var.vpc_ip_range 5 | } -------------------------------------------------------------------------------- /app/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM python:3.9-slim 3 | 4 | WORKDIR /app 5 | 6 | COPY requirements.txt . 7 | COPY src/ . 8 | 9 | RUN pip install -r requirements.txt 10 | 11 | EXPOSE 8080 12 | 13 | CMD ["python", "app.py"] 14 | -------------------------------------------------------------------------------- /infra/terraform/registry.tf: -------------------------------------------------------------------------------- 1 | resource "digitalocean_container_registry" "docregistry" { 2 | name = var.registry_name 3 | region = var.region 4 | subscription_tier_slug = var.subscription_tier_slug 5 | } -------------------------------------------------------------------------------- /k8s/app/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: podname-service 5 | spec: 6 | selector: 7 | app: podname 8 | ports: 9 | - protocol: TCP 10 | port: 80 11 | targetPort: 8080 12 | type: ClusterIP 13 | -------------------------------------------------------------------------------- /infra/terraform/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | digitalocean = { 4 | source = "digitalocean/digitalocean" 5 | version = "~> 2.0" 6 | } 7 | } 8 | 9 | required_version = ">= 0.14" 10 | } 11 | 12 | provider "digitalocean" { 13 | token = var.do_token 14 | } -------------------------------------------------------------------------------- /assets/backend-model: -------------------------------------------------------------------------------- 1 | terraform { 2 | backend "s3" { 3 | bucket = "" 4 | key = "" 5 | region = "" 6 | encrypt = "" or <"false"> 7 | dynamodb_table = "" 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/app.py: -------------------------------------------------------------------------------- 1 | 2 | from flask import Flask, render_template 3 | import socket 4 | 5 | app = Flask(__name__) 6 | 7 | @app.route('/') 8 | def index(): 9 | hostname = socket.gethostname() 10 | return render_template('index.html', hostname=hostname) 11 | 12 | if __name__ == '__main__': 13 | app.run(host='0.0.0.0', port=8080) 14 | -------------------------------------------------------------------------------- /k8s/setup/cluster-issuer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: ClusterIssuer 3 | metadata: 4 | name: letsencrypt-prod 5 | spec: 6 | acme: 7 | server: https://acme-v02.api.letsencrypt.org/directory 8 | email: 9 | privateKeySecretRef: 10 | name: letsencrypt-prod 11 | solvers: 12 | - http01: 13 | ingress: 14 | class: nginx 15 | -------------------------------------------------------------------------------- /k8s/app/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: podname 5 | labels: 6 | app: podname 7 | spec: 8 | replicas: 10 9 | selector: 10 | matchLabels: 11 | app: podname 12 | template: 13 | metadata: 14 | labels: 15 | app: podname 16 | spec: 17 | containers: 18 | - name: podname 19 | image: registry.digitalocean.com/project-argocd-registry/podname:v29 20 | ports: 21 | - containerPort: 8080 22 | -------------------------------------------------------------------------------- /k8s/argocd/application.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: project-argocd 5 | namespace: argocd 6 | spec: 7 | project: default 8 | source: 9 | repoURL: https://github.com/dellabeneta/project-argocd.git 10 | targetRevision: HEAD 11 | path: k8s/app 12 | destination: 13 | server: https://kubernetes.default.svc 14 | namespace: default 15 | syncPolicy: 16 | automated: 17 | prune: true 18 | selfHeal: true 19 | syncOptions: 20 | - CreateNamespace=true -------------------------------------------------------------------------------- /k8s/app/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: podname-ingress 5 | annotations: 6 | nginx.ingress.kubernetes.io/ssl-redirect: "true" 7 | cert-manager.io/cluster-issuer: "letsencrypt-prod" 8 | spec: 9 | ingressClassName: nginx 10 | tls: 11 | - hosts: 12 | - app. 13 | secretName: podname-tls 14 | rules: 15 | - host: app. 16 | http: 17 | paths: 18 | - path: / 19 | pathType: Prefix 20 | backend: 21 | service: 22 | name: podname-service 23 | port: 24 | number: 80 25 | -------------------------------------------------------------------------------- /assets/tfvars-model: -------------------------------------------------------------------------------- 1 | do_token = "" 2 | cluster_name = "" 3 | region = "" 4 | k8s_version = "" 5 | node_size = "" 6 | vpc_name = "" 7 | vpc_ip_range = "" 8 | cluster_tags = [""] 9 | node_pool_tags = [""] 10 | node_pool_name = "" 11 | auto_scale = 12 | min_nodes = 13 | max_nodes = 14 | registry_name = "" 15 | subscription_tier_slug = "" 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Terraform specific 2 | infra/terraform/terraform.tfvars 3 | infra/terraform/backend.tf 4 | *.tfstate 5 | *.tfstate.* 6 | *.tfvars 7 | .terraform/ 8 | .terraform.lock.hcl 9 | crash.log 10 | 11 | # IDE and Editor files 12 | .idea/ 13 | .vscode/ 14 | *.swp 15 | *.swo 16 | *~ 17 | 18 | # OS generated files 19 | .DS_Store 20 | .DS_Store? 21 | ._* 22 | .Spotlight-V100 23 | .Trashes 24 | ehthumbs.db 25 | Thumbs.db 26 | 27 | # Python 28 | __pycache__/ 29 | *.py[cod] 30 | *$py.class 31 | *.so 32 | .Python 33 | build/ 34 | develop-eggs/ 35 | dist/ 36 | downloads/ 37 | eggs/ 38 | .eggs/ 39 | lib/ 40 | lib64/ 41 | parts/ 42 | sdist/ 43 | var/ 44 | wheels/ 45 | *.egg-info/ 46 | .installed.cfg 47 | *.egg 48 | venv/ 49 | .env 50 | 51 | # Logs 52 | *.log 53 | logs/ -------------------------------------------------------------------------------- /k8s/setup/argo-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: argocd-server-ingress 5 | namespace: argocd 6 | annotations: 7 | nginx.ingress.kubernetes.io/force-ssl-redirect: "true" 8 | nginx.ingress.kubernetes.io/ssl-passthrough: "true" 9 | nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" 10 | cert-manager.io/cluster-issuer: "letsencrypt-prod" 11 | spec: 12 | ingressClassName: nginx 13 | tls: 14 | - hosts: 15 | - argocd. 16 | secretName: argocd-server-tls 17 | rules: 18 | - host: argocd. 19 | http: 20 | paths: 21 | - path: / 22 | pathType: Prefix 23 | backend: 24 | service: 25 | name: argocd-server 26 | port: 27 | number: 443 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Michel Dellabeneta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /infra/terraform/doks.tf: -------------------------------------------------------------------------------- 1 | resource "digitalocean_kubernetes_cluster" "doks" { 2 | name = var.cluster_name 3 | region = var.region 4 | vpc_uuid = digitalocean_vpc.doks_vpc.id 5 | version = var.k8s_version 6 | tags = var.cluster_tags 7 | registry_integration = true 8 | 9 | node_pool { 10 | name = var.node_pool_name 11 | size = var.node_size 12 | tags = var.node_pool_tags 13 | auto_scale = var.auto_scale 14 | min_nodes = var.min_nodes 15 | max_nodes = var.max_nodes 16 | } 17 | 18 | depends_on = [digitalocean_vpc.doks_vpc] 19 | } 20 | 21 | resource "digitalocean_container_registry_docker_credentials" "docr_credentials" { 22 | registry_name = digitalocean_container_registry.docregistry.name 23 | expiry_seconds = 3600 24 | write = true 25 | } 26 | 27 | resource "local_file" "kubeconfig" { 28 | content = digitalocean_kubernetes_cluster.doks.kube_config[0].raw_config 29 | filename = pathexpand("~/.kube/config") 30 | directory_permission = "0755" 31 | file_permission = "0600" 32 | } -------------------------------------------------------------------------------- /app/src/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pod Hostname 6 | 36 | 37 | 38 |
39 |

Pod Information

40 |
41 | Current Pod: {{ hostname }} 42 |
43 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /infra/terraform/outputs.tf: -------------------------------------------------------------------------------- 1 | # Cluster Outputs 2 | output "cluster_id" { 3 | description = "ID do cluster Kubernetes" 4 | value = digitalocean_kubernetes_cluster.doks.id 5 | } 6 | 7 | output "cluster_endpoint" { 8 | description = "Endpoint do cluster Kubernetes" 9 | value = digitalocean_kubernetes_cluster.doks.endpoint 10 | sensitive = true 11 | } 12 | 13 | output "cluster_status" { 14 | description = "Status atual do cluster" 15 | value = digitalocean_kubernetes_cluster.doks.status 16 | } 17 | 18 | output "kubernetes_version" { 19 | description = "Versão do Kubernetes em uso" 20 | value = digitalocean_kubernetes_cluster.doks.version 21 | } 22 | 23 | # Node Pool Outputs 24 | output "node_pool_id" { 25 | description = "ID do node pool" 26 | value = digitalocean_kubernetes_cluster.doks.node_pool[0].id 27 | } 28 | 29 | output "node_count" { 30 | description = "Número atual de nodes no cluster" 31 | value = digitalocean_kubernetes_cluster.doks.node_pool[0].actual_node_count 32 | } 33 | 34 | # VPC Outputs 35 | output "vpc_id" { 36 | description = "ID da VPC" 37 | value = digitalocean_vpc.doks_vpc.id 38 | } 39 | 40 | output "vpc_ip_range" { 41 | description = "Range de IP da VPC" 42 | value = digitalocean_vpc.doks_vpc.ip_range 43 | } 44 | 45 | # Registry Outputs 46 | output "registry_endpoint" { 47 | description = "Endpoint do registry de containers" 48 | value = digitalocean_container_registry.docregistry.server_url 49 | } 50 | 51 | output "registry_name" { 52 | description = "Nome do registry" 53 | value = digitalocean_container_registry.docregistry.name 54 | } 55 | 56 | # Kubeconfig 57 | output "kubeconfig_path" { 58 | description = "Caminho do arquivo kubeconfig" 59 | value = local_file.kubeconfig.filename 60 | } -------------------------------------------------------------------------------- /infra/terraform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "do_token" { 2 | description = "DigitalOcean API token" 3 | type = string 4 | } 5 | 6 | variable "cluster_name" { 7 | description = "Name of the Kubernetes cluster" 8 | type = string 9 | } 10 | 11 | variable "region" { 12 | description = "Region where the cluster will be created" 13 | type = string 14 | } 15 | 16 | variable "k8s_version" { 17 | description = "Kubernetes version" 18 | type = string 19 | } 20 | 21 | variable "node_size" { 22 | description = "Size of the nodes" 23 | type = string 24 | } 25 | 26 | variable "vpc_name" { 27 | description = "Name of the VPC" 28 | type = string 29 | } 30 | 31 | variable "vpc_ip_range" { 32 | description = "IP range for the VPC" 33 | type = string 34 | } 35 | 36 | variable "cluster_tags" { 37 | description = "Tags for the Kubernetes cluster" 38 | type = list(string) 39 | } 40 | 41 | variable "node_pool_tags" { 42 | description = "Tags for the node pool" 43 | type = list(string) 44 | } 45 | 46 | variable "node_pool_name" { 47 | description = "Name for the node pool" 48 | type = string 49 | } 50 | 51 | variable "auto_scale" { 52 | description = "Enable auto-scaling for the node pool" 53 | type = bool 54 | } 55 | 56 | variable "min_nodes" { 57 | description = "Minimum number of nodes in the node pool" 58 | type = number 59 | } 60 | 61 | variable "max_nodes" { 62 | description = "Maximum number of nodes in the node pool" 63 | type = number 64 | } 65 | 66 | variable "registry_name" { 67 | description = "Name of the container registry" 68 | type = string 69 | } 70 | 71 | variable "subscription_tier_slug" { 72 | description = "Subscription tier slug for the container registry" 73 | type = string 74 | } -------------------------------------------------------------------------------- /.github/workflows/build-deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | on: 3 | push: 4 | paths: 5 | - 'app/**' 6 | branches: 7 | - main 8 | workflow_dispatch: 9 | inputs: 10 | reason: 11 | description: 'Reason for manual workflow run' 12 | required: false 13 | default: 'Manual trigger' 14 | 15 | permissions: 16 | contents: write 17 | 18 | jobs: 19 | build: 20 | runs-on: ubuntu-latest 21 | outputs: 22 | image_tag: ${{ steps.image_tag.outputs.tag }} 23 | steps: 24 | - name: Checkout code 25 | uses: actions/checkout@v3 26 | 27 | - name: Login to Digital Ocean Registry 28 | uses: docker/login-action@v2 29 | with: 30 | registry: registry.digitalocean.com 31 | username: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} 32 | password: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} 33 | 34 | - name: Generate Image Tag 35 | id: image_tag 36 | run: echo "tag=v${{ github.run_number }}" >> $GITHUB_OUTPUT 37 | 38 | - name: Build and push 39 | uses: docker/build-push-action@v4 40 | with: 41 | context: ./app 42 | push: true 43 | tags: | 44 | registry.digitalocean.com/${{ secrets.DIGITALOCEAN_REGISTRY_NAME }}/${{ secrets.IMAGE_NAME }}:latest 45 | registry.digitalocean.com/${{ secrets.DIGITALOCEAN_REGISTRY_NAME }}/${{ secrets.IMAGE_NAME }}:${{ steps.image_tag.outputs.tag }} 46 | 47 | deploy: 48 | needs: build 49 | runs-on: ubuntu-latest 50 | steps: 51 | - name: Checkout code 52 | uses: actions/checkout@v3 53 | 54 | - name: Update deployment file 55 | run: | 56 | NEW_IMAGE="registry.digitalocean.com/${{ secrets.DIGITALOCEAN_REGISTRY_NAME }}/${{ secrets.IMAGE_NAME }}:${{ needs.build.outputs.image_tag }}" 57 | sed -i "s|image: registry.digitalocean.com/.*/podname:.*|image: ${NEW_IMAGE}|" k8s/app/deployment.yaml 58 | 59 | - name: Auto Commit 60 | uses: stefanzweifel/git-auto-commit-action@v4 61 | with: 62 | commit_message: "chore: update image tag to ${{ needs.build.outputs.image_tag }}" 63 | file_pattern: 'k8s/app/deployment.yaml' 64 | -------------------------------------------------------------------------------- /infra/terraform/README.md: -------------------------------------------------------------------------------- 1 | ## Requirements 2 | 3 | | Name | Version | 4 | |------|---------| 5 | | [terraform](#requirement\_terraform) | >= 0.14 | 6 | | [digitalocean](#requirement\_digitalocean) | ~> 2.0 | 7 | 8 | ## Providers 9 | 10 | | Name | Version | 11 | |------|---------| 12 | | [digitalocean](#provider\_digitalocean) | 2.47.0 | 13 | | [local](#provider\_local) | 2.5.2 | 14 | 15 | ## Modules 16 | 17 | No modules. 18 | 19 | ## Resources 20 | 21 | | Name | Type | 22 | |------|------| 23 | | [digitalocean_container_registry.docregistry](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/container_registry) | resource | 24 | | [digitalocean_container_registry_docker_credentials.docr_credentials](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/container_registry_docker_credentials) | resource | 25 | | [digitalocean_kubernetes_cluster.doks](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/kubernetes_cluster) | resource | 26 | | [digitalocean_vpc.doks_vpc](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/vpc) | resource | 27 | | [local_file.kubeconfig](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource | 28 | 29 | ## Inputs 30 | 31 | | Name | Description | Type | Default | Required | 32 | |------|-------------|------|---------|:--------:| 33 | | [auto\_scale](#input\_auto\_scale) | Enable auto-scaling for the node pool | `bool` | n/a | yes | 34 | | [cluster\_name](#input\_cluster\_name) | Name of the Kubernetes cluster | `string` | n/a | yes | 35 | | [cluster\_tags](#input\_cluster\_tags) | Tags for the Kubernetes cluster | `list(string)` | n/a | yes | 36 | | [do\_token](#input\_do\_token) | DigitalOcean API token | `string` | n/a | yes | 37 | | [k8s\_version](#input\_k8s\_version) | Kubernetes version | `string` | n/a | yes | 38 | | [max\_nodes](#input\_max\_nodes) | Maximum number of nodes in the node pool | `number` | n/a | yes | 39 | | [min\_nodes](#input\_min\_nodes) | Minimum number of nodes in the node pool | `number` | n/a | yes | 40 | | [node\_pool\_name](#input\_node\_pool\_name) | Name for the node pool | `string` | n/a | yes | 41 | | [node\_pool\_tags](#input\_node\_pool\_tags) | Tags for the node pool | `list(string)` | n/a | yes | 42 | | [node\_size](#input\_node\_size) | Size of the nodes | `string` | n/a | yes | 43 | | [region](#input\_region) | Region where the cluster will be created | `string` | n/a | yes | 44 | | [registry\_name](#input\_registry\_name) | Name of the container registry | `string` | n/a | yes | 45 | | [subscription\_tier\_slug](#input\_subscription\_tier\_slug) | Subscription tier slug for the container registry | `string` | n/a | yes | 46 | | [vpc\_ip\_range](#input\_vpc\_ip\_range) | IP range for the VPC | `string` | n/a | yes | 47 | | [vpc\_name](#input\_vpc\_name) | Name of the VPC | `string` | n/a | yes | 48 | 49 | ## Outputs 50 | 51 | | Name | Description | 52 | |------|-------------| 53 | | [cluster\_endpoint](#output\_cluster\_endpoint) | Endpoint do cluster Kubernetes | 54 | | [cluster\_id](#output\_cluster\_id) | ID do cluster Kubernetes | 55 | | [cluster\_status](#output\_cluster\_status) | Status atual do cluster | 56 | | [kubeconfig\_path](#output\_kubeconfig\_path) | Caminho do arquivo kubeconfig | 57 | | [kubernetes\_version](#output\_kubernetes\_version) | Versão do Kubernetes em uso | 58 | | [node\_count](#output\_node\_count) | Número atual de nodes no cluster | 59 | | [node\_pool\_id](#output\_node\_pool\_id) | ID do node pool | 60 | | [registry\_endpoint](#output\_registry\_endpoint) | Endpoint do registry de containers | 61 | | [registry\_name](#output\_registry\_name) | Nome do registry | 62 | | [vpc\_id](#output\_vpc\_id) | ID da VPC | 63 | | [vpc\_ip\_range](#output\_vpc\_ip\_range) | Range de IP da VPC | 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Projeto de fluxo contínuo com Argo CD

2 | 3 |
4 | 5 |
6 | 7 |
8 | 9 |
10 | 11 | [![Workflow](https://img.shields.io/github/actions/workflow/status/dellabeneta/project-argocd/build-deploy.yaml?color=success&label=Workflow&logo=githubactions)](https://github.com/dellabeneta/project-argocd/actions) 12 | [![Version](https://img.shields.io/badge/Version-1.1.0-blue?logo=github)](https://github.com/dellabeneta/project-argocd/releases) [![License](https://img.shields.io/github/license/dellabeneta/project-argocd?label=License&color=blue&logo=opensourceinitiative)](https://github.com/dellabeneta/project-argocd/blob/main/LICENSE) [![Infrastructure](https://img.shields.io/badge/Infrastructure-Terraform-blue?logo=terraform)](https://github.com/dellabeneta/project-argocd/tree/main/infra/terraform) 13 | [![Kubernetes](https://img.shields.io/badge/Kubernetes-ready-brightgreen?logo=kubernetes)](https://kubernetes.io/docs/) [![Argo CD](https://img.shields.io/badge/Argo--CD-ready-brightgreen?logo=argo)](https://argo-cd.readthedocs.io/) 14 |
15 | 16 |
17 | 18 |
19 | Com um repositório GitHub bem estruturado, contendo pastas específicas para a aplicação, arquivos Terraform para provisionamento de infraestrutura e manifests Kubernetes, é possível implementar um fluxo totalmente automatizado: do commit no código-fonte ao deploy da aplicação em um cluster Kubernetes. 20 |

21 | O processo funciona conectando diferentes tecnologias. Sempre que houver alterações no código-fonte, um workflow gera automaticamente uma nova imagem Docker da aplicação. Em seguida, esse mesmo workflow atualiza o manifesto Kubernetes correspondente, armazenado na pasta designada. O ArgoCD, por sua vez, detecta essa atualização no manifesto e sincroniza a nova versão da aplicação no cluster, criando um fluxo contínuo e confiável de deploy automatizado. 22 |

23 | 24 |

25 | 26 | DigitalOcean Referral Badge 27 | 28 |

29 | 30 |
31 | 32 |
33 | 34 |

Desenho da arquitetura

35 | 36 |
37 | 38 | 39 |
40 | 41 |
42 |

Vamos à prática

43 |
44 | 45 |
46 | 47 | #### Pré-requisitos: 48 | 49 | - Terraform instalado 50 | - Kubectl instalado 51 | - Conta na Digital Ocean e Token de acesso 52 | - Um domínio válido com zona de DNS ativa 53 | 54 | #### Observações importantes: 55 | 56 | - É necessário um arquivo `terraform.tfvars` seu, em `/infra/terraform`. [Clique aqui para copiar um modelo!](https://github.com/dellabeneta/project-argocd/blob/main/assets/tfvars-model) 57 | - Eu usei o State do Terraform de maneira remota. É opcional mas, [clique aqui se quiser seguir essa boa prática!](https://github.com/dellabeneta/project-argocd/blob/main/assets/backend-model) 58 |
59 | 60 | #### 1. Configure o Cluster Kubernetes 61 | 62 | Abra seu terminal no path raiz do `projeto-argocd` e execute esses dois comandos para o terraform provisionar o DOKS (serviço gerenciado de Kubernetes da Digital Ocean): 63 | 64 | ``` 65 | terraform -chdir=infra/terraform init 66 | ``` 67 | ``` 68 | terraform -chdir=infra/terraform apply --auto-approve 69 | ``` 70 | Após uma considerável espera, vamos checar o estado do componentes: 71 | ``` 72 | kubectl get all -A 73 | ``` 74 |
75 | 76 | #### 2. Instalar o Ingress Controller (Nginx) 77 | 78 | Seguindo em nosso terminal, vamos aos passos para criarmos nosso Nginx Ingress Controller: 79 | ``` 80 | kubectl apply \ 81 | -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml 82 | ``` 83 | Cheque o estado de tudo com: 84 | ``` 85 | kubectl get all -A 86 | ``` 87 | O nosso IP Público estará como `pending` por uns 3 minutos. Após isso, já crie os subdomínios para seu ArgoCD e a aplicação. Eu tenho feito o meu dessa forma: 88 | - argocd.seudominio.com ---> IP_DO_LOADBALANCER 89 | - app.seudominio.com ---> IP_DO_LOADBALANCER 90 | 91 | Aguarde as propagações. Monitore com https://www.whatsmydns.net/ ou `nslookup` e `dig` no terminal. 92 | 93 |
94 | 95 | #### 3. Instale o Cert-Manager 96 | 97 | Vamos preparar o Cert-Manager que será o principal elemento, responsável pelos nossos certificados de forma geral dentro do nosso Cluster K8S: 98 | ``` 99 | kubectl apply \ 100 | -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.2/cert-manager.yaml 101 | ``` 102 | Aguarde os pods ficarem prontos: 103 | ``` 104 | kubectl wait --for=condition=Ready pods --all -n cert-manager --timeout=300s 105 | ``` 106 | Verifique se os CRDs foram aplicados corretamente: 107 | ``` 108 | kubectl get crds | grep cert-manager 109 | ``` 110 | 111 |
112 | 113 | #### 4. Crie um Cluster-Issuer para Let's Encrypt 114 | 115 | Salve este YAML como `cluster-issuer.yaml` e aplique-o com `kubectl apply -f k8s/setup/cluster-issuer.yaml`. 116 | ``` 117 | # cluster-issuer.yaml 118 | apiVersion: cert-manager.io/v1 119 | kind: ClusterIssuer 120 | metadata: 121 | name: letsencrypt-prod 122 | spec: 123 | acme: 124 | server: https://acme-v02.api.letsencrypt.org/directory 125 | email: email@dominio.com 126 | privateKeySecretRef: 127 | name: letsencrypt-prod 128 | server: https://acme-v02.api.letsencrypt.org/directory 129 | solvers: 130 | - http01: 131 | ingress: 132 | class: nginx 133 | ``` 134 | 135 |
136 | 137 | #### 5. Ingress para o ArgoCD 138 | 139 | Crie o namespace `argocd`: 140 | ``` 141 | kubectl create namespace argocd 142 | ``` 143 | Confira com: 144 | ``` 145 | kubectl get namespaces 146 | ``` 147 | Mesmo processo, salve o YAML como `argo-ingress.yaml` e aplique com `kubectl apply -f k8s/setup/argo-ingress.yaml`. 148 | ``` 149 | apiVersion: networking.k8s.io/v1 150 | kind: Ingress 151 | metadata: 152 | name: argocd-ingress 153 | annotations: 154 | nginx.ingress.kubernetes.io/force-ssl-redirect: "true" 155 | nginx.ingress.kubernetes.io/ssl-passthrough: "true" 156 | nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" 157 | cert-manager.io/cluster-issuer: "letsencrypt-prod" 158 | spec: 159 | ingressClassName: nginx 160 | tls: 161 | - hosts: 162 | - argocd.seudominio.com 163 | secretName: argocd-server-tls 164 | rules: 165 | - host: argocd.seudominio.com 166 | http: 167 | paths: 168 | - path: / 169 | pathType: Prefix 170 | backend: 171 | service: 172 | name: argocd-server 173 | port: 174 | name: 443 175 | ``` 176 | 177 |
178 | 179 | #### 6. Setup do ArgoCD 180 | 181 | Aplique o manifesto OFICIAL para o ArgoCD, nós já temos uma namespace com nome `argocd` e vamos utilizá-lo: 182 | ``` 183 | kubectl apply \ 184 | -n argocd \ 185 | -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml 186 | ``` 187 | Após alguns poucos minutos, ele estará no ar com seu certificado já emito pelo Cert-Manager + Ingress que criamos. 188 | 189 | Você agora pode acessar pelo https://argocd.dominio.com. 190 | 191 | Busque sua senha do `admin`, gerada automaticamente durante o provisionamento, com o comando: 192 | ``` 193 | kubectl get secret argocd-initial-admin-secret \ 194 | -n argocd \ 195 | -o jsonpath="{.data.password}" | base64 -d; echo 196 | ``` 197 | 198 |
199 | 200 | #### 7. Exemplo de YAML para "Application". Afinal de contas, agora você vai usar repositórios Git como fonte única de verdade. 201 | 202 | Este projeto, que é um REPOSITÓRIO PÚBLICO, já possui uma aplicação de exemplo em `/app`, inclusive com Github Actions, que vai sempre fazer o CI da aplicação, ajustando a versão da imagem Docker no arquivo `deployment.yaml`, na linha 19 em `/k8s/app/`, que será consumido pelo ArgoCD. Ajuste conforme sua demanda. 203 | 204 | Segue exemplo de YAML baseado **neste projeto**: 205 | ``` 206 | apiVersion: argoproj.io/v1alpha1 207 | kind: Application 208 | metadata: 209 | name: project-argocd 210 | namespace: argocd 211 | spec: 212 | project: default 213 | source: 214 | repoURL: https://github.com/dellabeneta/project-argocd.git 215 | targetRevision: HEAD 216 | path: k8s/app 217 | destination: 218 | server: https://kubernetes.default.svc 219 | namespace: default 220 | syncPolicy: 221 | automated: 222 | prune: true 223 | selfHeal: true 224 | syncOptions: 225 | - CreateNamespace=true 226 | ``` 227 | Salve-o e aplique com, é claro, `kubectl apply -f k8s/argocd/application.yaml` 228 | 229 |
230 | 231 |
232 |

Referências

233 |
234 | 235 |
236 | 237 | - https://argo-cd.readthedocs.io/en/stable/ 238 | - https://kubernetes.io/docs/ 239 | - https://kubernetes.io/docs/setup/ 240 | - https://kubernetes.io/docs/concepts/ 241 | - https://docs.docker.com/ 242 | - https://docs.docker.com/get-started/ 243 | - https://docs.docker.com/engine/reference/commandline/ 244 | - https://www.terraform.io/docs 245 | - https://www.terraform.io/docs/providers/ 246 | - https://learn.hashicorp.com/collections/terraform/aws-get-started 247 | - https://docs.github.com/en/actions 248 | - https://docs.github.com/en/actions/using-workflows 249 | - https://docs.github.com/en/actions/learn-github-actions 250 |
251 | -------------------------------------------------------------------------------- /assets/diagram.drawio: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | --------------------------------------------------------------------------------