Criando nosso próprio
sistema de deployment
do zero

Sobre

Sobre

Sobre mim

Vinícius Campitelli
Vinícius
Campitelli
  • Bacharel em Ciência da Computação pela UFSCar
  • Desenvolvedor há 20 anos
  • Membro do PHPSP
  • Entusiasta em cibersegurança
  • Consultor de TI e instrutor de treinamentos

Sobre

Slides

Acesse os slides remotamente em:

QR Code
viniciuscampitelli.com/slides-criando-deployment-zero

Sobre

Repositório

Acesse os slides e os códigos das demonstrações localmente clonando o repositório:


                            $ git clone --recursive git@github.com:vcampitelli/slides-criando-deployment-zero.git
                        
github.com/vcampitelli/slides-criando-deployment-zero

Estratégias

Estratégias

Recreate deployment

Desliga a versão atual e sobe a nova em seguida. Simples, porém com indisponibilidade temporária.

Recreate deployment System Design - Estratégias de Deployment | Matheus Fidelis

Estratégias

Rolling deployment

Atualiza gradualmente instância por instância. Minimiza downtime, mas pode misturar versões durante a transição.

Rolling deployment System Design - Estratégias de Deployment | Matheus Fidelis

Estratégias

Canary deployment

Libera para uma pequena parcela dos usuários primeiro. Monitora métricas; se tudo estiver bem, expande até 100%. Ideal para reduzir risco.

Canary deployment System Design - Estratégias de Deployment | Matheus Fidelis

Estratégias

Blue/green deployment

Mantém dois ambientes idênticos (blue e green). Você publica na “nova” cor, valida e muda o tráfego em um clique, com rollback instantâneo.

Blue/green deployment System Design - Estratégias de Deployment | Matheus Fidelis

Disclaimer

Disclaimer

O intuito dessa palestra é meramente educacional! Em cenários reais, é extremamente recomendado utilizar ferramentas robustas de mercado.

Prática

Prática

Gerenciamento com Docker Compose


                            services:
                              nginx:
                                image: nginx:1-alpine
                                ports:
                                  - "80:80"
                                volumes:
                                  - "./nginx.conf.template:/etc/nginx/templates/default.conf.template"
                                environment:
                                  APP_HOST: $APP_HOST
                                healthcheck:
                                  test: ["CMD", "curl", "-f", "localhost/ping"]

                              app1:
                                image: $IMAGE1
                                restart: unless-stopped
                                env_file:
                                  - app1.env

                              app2:
                                image: $IMAGE2
                                restart: unless-stopped
                                env_file:
                                  - app2.env
                        

Arquivo compose.yaml com a configuração necessária para ter as duas aplicações possíveis

Prática

Gerenciamento com Docker Compose


                            APP_HOST=app1
                            IMAGE1=aplicacao:1.0.0
                            IMAGE2=aplicacao:1.0.1
                        

Arquivo .env com as variáveis de ambiente para o Docker Compose

Prática

Roteamento do tráfego com nginx


                            server {
                                listen 80 default_server;

                                root /var/www/html/public;
                                index index.php;

                                location / {
                                    try_files $uri $uri/ /index.php$is_args$args;
                                }

                                location ~ \.php$ {
                                    fastcgi_pass   ${APP_HOST}:9000;
                                    fastcgi_index  index.php;
                                    include        fastcgi.conf;
                                }
                            }
                        

Arquivo nginx.conf.template com o template da configuração do nginx

Prática

Script que controla tudo


                            #!/bin/bash
                            set -e

                            if [ -z "$1" ]; then
                              echo "Uso: $0 <imagem-docker>"
                              exit 1
                            fi

                            image="$1"
                            docker_compose_file="compose.yaml"
                            service1="app1"
                            service2="app2"
                            service1_env="IMAGE1"
                            service2_env="IMAGE2"
                            env_file=".env"
                            nginx_service="nginx"
                            timeout=60 # timeout em segundos para cada checagem de serviço saudável
                            sleep_interval=3 # tempo em segundos para esperar entre tentativas
                            max_retries=$((timeout / sleep_interval))

                            # Checando existência dos arquivos
                            if [ ! -f "$docker_compose_file" ]; then
                              echo "Arquivo $docker_compose_file não encontrado"
                              exit 1
                            fi
                            if [ ! -f "$env_file" ]; then
                              echo "Arquivo $env_file não encontrado"
                              exit 1
                            fi

                            #
                            # Checando se o serviço está saudável
                            # @param string $1 Nome do serviço do Docker
                            #
                            check_health() {
                              i=1
                              while [ "$i" -le $max_retries ]; do
                                status=$(docker compose -f "$docker_compose_file" ps "$1" --status running --format "{{.Health}}")
                                echo "  Healthcheck: $status"
                                if [ "$status" = "healthy" ]; then
                                  echo "$1 is healthy"
                                  return 0
                                fi

                                sleep "$sleep_interval"
                                i=$((i + 1))
                              done

                              return 1
                            }

                            # Descobrindo qual serviço (1 ou 2) está ativo
                            if [ -n "$(docker compose -f "$docker_compose_file" ps "$service1" --status running --quiet)" ]; then
                              old_service=$service1
                              new_service=$service2
                              env_name=$service2_env
                            elif [ -n "$(docker compose -f "$docker_compose_file" ps "$service2" --status running --quiet)" ]; then
                              old_service=$service2
                              new_service=$service1
                              env_name=$service1_env
                            else
                              old_service=""
                              new_service=$service2
                              env_name=$service2_env
                            fi

                            # Trocando o nome da imagem do Docker no arquivo .env
                            sed -i "/$env_name=/c\\$env_name=$image" "$env_file"

                            echo "Iniciando serviço $new_service"
                            docker compose -f "$docker_compose_file" up --build --remove-orphans --detach "$new_service"

                            echo "Aguardando até que $new_service esteja saudável.."
                            if ! check_health "$new_service"; then
                              echo "$new_service não ficou saudável em $timeout segundos"
                              docker compose -f "$docker_compose_file" stop --timeout=10 "$new_service"
                              exit 2
                            fi

                            echo "Recarregando configuração do nginx"
                            docker compose -f "$docker_compose_file" exec --env "APP_HOST=$new_service" "$nginx_service" /docker-entrypoint.d/20-envsubst-on-templates.sh
                            docker compose -f "$docker_compose_file" exec "$nginx_service" nginx -s reload
                            if ! check_health "$nginx_service"; then
                              echo "$new_service não ficou saudável em $timeout segundos"
                              docker compose -f "$docker_compose_file" stop --timeout=10 "$new_service"
                              exit 2
                            fi

                            echo "Parando container $old_service"
                            docker compose -f "$docker_compose_file" stop --timeout=10 "$old_service"

                            echo "Deploy finalizado com sucesso"
                        

GitHub Actions

GitHub Actions

Automatize, personalize e execute seus fluxos de trabalho de desenvolvimento do software diretamente no seu repositório com o GitHub Actions. Você pode descobrir, criar e compartilhar ações para realizar qualquer trabalho que desejar, incluindo CI/CD, bem como combinar ações em um fluxo de trabalho completamente personalizado.
GitHub Actions

GitHub Actions

Um Fluxo de trabalho ("workflow") é configurado através de um arquivo YAML na pasta .github/workflows do seu repositório

Meus slides: Criando esteiras de CI/CD performáticas e seguras

Como automatizar?

Como automatizar?

Webhook

Ao mesclarmos uma PR, invocamos um endpoint protegido na máquina de destino com o hash do commit

Prós

  • O sistema só será avisado quando realmente houver algo a ser feito

Contras

  • Precisamos expor um endpoint para a Internet, protegendo-o bem

Como automatizar?

Long polling

Instalamos um agente na máquina de destino que fica consultando a API do GitHub a cada x minutos para verificar se há um novo commit

Prós

  • Não precisamos expor nada do nosso ambiente para fora

Contras

  • Serão feitas muitas requisições por dia para o GitHub — e muitas delas à toa

Como automatizar?

SSH

Fazemos o GitHub acessar a máquina via SSH e executar um comando que irá iniciar o processo

Prós

  • O sistema só será avisado quando realmente houver algo a ser feito

Contras

  • Precisamos expor o acesso via SSH para a Internet (ou invocar a API do serviço de hospedagem para liberar o acesso temporariamente para o IP atual do GitHub Actions)

Automatizando com GitHub Actions

Automatizando com GitHub Actions


                            name: Deploy do Backend

                            on:
                              push:
                                branches: [ "main" ]

                            jobs:
                              deploy:
                                name: Deploy
                                runs-on: ubuntu-latest
                                permissions:
                                  contents: read
                                  packages: write
                                env:
                                  REGISTRY: ghcr.io
                                  IMAGE_NAME: ${{ github.repository }}
                                steps:
                                  - name: Checkout
                                    uses: actions/checkout@v4

                                  - name: Fazendo login no Registry de Containers
                                    uses: docker/login-action@v3
                                    with:
                                      registry: ${{ env.REGISTRY }}
                                      username: ${{ github.actor }}
                                      password: ${{ secrets.GITHUB_TOKEN }}

                                  - name: Configurando Docker Buildx
                                    uses: docker/setup-buildx-action@v3

                                  - name: Extract metadata (tags, labels) for Docker
                                    id: meta
                                    uses: docker/metadata-action@v5
                                    with:
                                      images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
                                      tags: type=sha

                                  - name: Faz o build da imagem do Docker
                                    uses: docker/build-push-action@v6
                                    id: build
                                    with:
                                      push: true
                                      tags: ${{ steps.meta.outputs.tags }}
                                      labels: ${{ steps.meta.outputs.labels }}

                                  - name: Executando comandos via SSH
                                    uses: appleboy/ssh-action@v1.2.2
                                    with:
                                      host: ${{ secrets.SSH_HOST }}
                                      port: ${{ secrets.SSH_PORT }}
                                      username: ${{ secrets.SSH_USERNAME }}
                                      key: ${{ secrets.SSH_KEY }}
                                      passphrase: ${{ secrets.SSH_KEY_PASSPHRASE }}
                                      script: docker-compose-update ${{ steps.meta.outputs.tags }}
                        

Referências

Referências

Treinamentos in company

Workshops
Gostou? Então conheça meus treinamentos corporativos e sob demanda sobre Desenvolvimento, Segurança da Informação, DevOps, Arquitetura de Sistemas e diversos outros assuntos em viniciuscampitelli.com

Obrigado!