Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/infra upgrades #21

Merged
merged 4 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/deploy_to_production.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Deploy to Production

on:
release:
types: [released]

jobs:
test_and_lint:
uses: ./.github/workflows/tests.yml

deploy_to_production:
needs: test_and_lint
name: Deploy to Production
runs-on: ubuntu-latest

steps:
- name: Dump event
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: |
echo "$GITHUB_CONTEXT"

- name: Deploy new changes
uses: appleboy/[email protected]
env:
TAG_NAME: ${{github.event.release.tag_name}}
PROJECT_ROOT: ${{github.event.repository.name}}
with:
host: ${{secrets.PRODUCTION_SSH_HOSTNAME}}
username: ${{secrets.PRODUCTION_USER_NAME}}
key: ${{ secrets.PRODUCTION_SSH_PRIVATE_KEY }}
envs: TAG_NAME,PROJECT_ROOT
script_stop: true
script: |
cd $PROJECT_ROOT
echo "Deploying branch $TAG_NAME"
git fetch origin && git checkout $TAG_NAME
echo "Building images..."
docker compose -f production.yml --env-file .env build --build-arg="PDM_INSTALL_ARGS=--prod --no-lock --no-editable"
echo "Running database migrations"
docker compose -f production.yml --env-file .env up db_migration --no-deps -d
echo "Restarting services"
docker compose -f production.yml --env-file .env up celery-worker celery-scheduler django --no-deps -d
39 changes: 21 additions & 18 deletions .github/workflows/deploy_to_staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,27 @@ jobs:

steps:
- uses: actions/checkout@v2
- name: Build & Deploy
- name: Dump event
env:
PRIVATE_KEY: ${{ secrets.STAGING_SSH_PRIVATE_KEY }}
HOSTNAME: ${{secrets.STAGING_SSH_HOSTNAME}}
USERNAME: ${{secrets.STAGING_USER_NAME}}
PROJECT_ROOT: ${{github.event.repository.name}}

GITHUB_CONTEXT: ${{ toJson(github) }}
run: |
echo "$PRIVATE_KEY" > private_key && chmod 600 private_key
ssh -o StrictHostKeyChecking=no -i private_key ${USERNAME}@${HOSTNAME} '
echo "$GITHUB_CONTEXT"

# Now we have got the access of EC2 and we will start the deploy .
cd ${PROJECT_ROOT} &&
git checkout dev &&
git fetch --all &&
git reset --hard origin/dev &&
git pull origin dev &&
docker compose -f staging.yml --env-file .env build &&
docker compose -f staging.yml --env-file .env up db_migration --no-deps -d &&
docker compose -f staging.yml --env-file .env up django celery-scheduler celery-worker --no-deps -d
'
- name: Deploy new changes
uses: appleboy/[email protected]
env:
PROJECT_ROOT: ${{github.event.repository.name}}
with:
host: ${{secrets.STAGING_SSH_HOSTNAME}}
username: ${{secrets.STAGING_USER_NAME}}
key: ${{ secrets.STAGING_SSH_PRIVATE_KEY }}
envs: PROJECT_ROOT,
script_stop: true
script: |
cd $PROJECT_ROOT
git fetch --all
git reset --hard origin/dev
git pull origin dev
docker compose -f staging.yml --env-file .env build --build-arg="PDM_INSTALL_ARGS=--prod --no-lock --no-editable"
docker compose -f staging.yml --env-file .env up db_migration --no-deps -d
docker compose -f staging.yml --env-file .env up django celery-scheduler celery-worker --no-deps -d
36 changes: 12 additions & 24 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -1,41 +1,29 @@
name: Tests

on:
- push
- pull_request
push:
branches-ignore:
- main
- dev
pull_request:
branches-ignore:
- main
- dev
workflow_call:

jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-20.04]
python-version: ["3.11"]
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
- name: Set up Python 3.11
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
python-version: 3.11
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools wheel
pip install tox==4.6.0 tox-gh-actions
- name: Test with tox
run: tox

build:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
- name: Build Image
id: docker_build
uses: docker/build-push-action@v2
with:
context: ./
file: ./infra/app/Dockerfile
push: false
58 changes: 54 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -811,23 +811,73 @@ Após alguns segundos você terá duas instâncias da aplicação Django rodando

## Deploy

Normalmente o deploy das aplicações são realizados em uma VPS, como a Amazon EC2. O Boilerplate já vem configurado com CI/CD para o ambiente de Staging, porém é simples copiar e criar um outro worfklow para o ambiente de produção.
Normalmente o deploy das aplicações são realizados em uma VPS, como a Amazon EC2. O Boilerplate já vem configurado com CI/CD para o ambiente de Staging e Produção. Porém o primeiro deploy é necessário a configuração pelo usuário.

> Apesar de apenas copiar e colar o workflow seja o suficiente para o ambiente de produção, perceba que o deploy atual possui downtime de cerca de 30 segundos.
> O formato de deploy atual possui downtime de cerca de 5 segundos.

Antes do primeiro deploy por CI/CD será necessário:
- Criar a máquina virtual;
- Atribuir um domínio para o endereço da máquina;
- Instalar as dependências (git, docker compose) na máquina;
- Configurar o acesso via SSH ao Github (quando criar sua chave SSH não coloque um passphrase, caso contrário o processo de CD irá falhar);
- Configurar as variáveis de ambiente;
- Subir a aplicação manualmente.
- [Subir a aplicação manualmente](#subir-a-aplicação-pela-primeira-vez).

Após estes passos, o fluxo de CI/CD fará deploys automaticamente, após a configuração das Secrets no Github (Dentro do repositório -> `Settings` -> `Secrets`).
Vá até o arquivo [deploy_to_staging.yml](./.github/workflows/deploy_to_staging.yml) e verifique as variáveis de ambiente necessárias. Elas provavelmente são:
Vá até o arquivo [deploy_to_staging.yml](./.github/workflows/deploy_to_staging.yml) e verifique as variáveis de ambiente necessárias. Elas são:

- `STAGING_SSH_PRIVATE_KEY`: Chave SSH Privada utilizada para conectar na máquina
- `STAGING_SSH_HOSTNAME`: Nome do HOST para acesso SSH
- `STAGING_USER_NAME`: Nome do usuário da máquina para acesso SSH

> Perceba que os valores das variáveis de ambiente são prefixados para o ambiente de deploy, caso esteja criando um novo arquivo de deploy para outro ambiente, garanta que as variáveis estarão prefixadas com o nome do ambiente. Exemplo: `PRODUCTION_SSH_PRIVATE_KEY`.

Para o ambiente de produção, o fluxo é praticamente o mesmo, porém as variáveis de ambiente são:
- `PRODUCTION_SSH_PRIVATE_KEY`: Chave SSH Privada utilizada para conectar na máquina
- `PRODUCTION_SSH_HOSTNAME`: Nome do HOST para acesso SSH
- `PRODUCTION_USER_NAME`: Nome do usuário da máquina para acesso SSH


### Subir a aplicação pela primeira vez

Para subir a aplicação pela primeira vez será necessário subir alguns serviços na máquina, porém já existem scripts que fazem a maior parte do trabalho.
Primeiro passo é rodar o script [start_nginx_acme.sh](./infra/server/start_nginx_acme.sh) a partir do root do repositório:
```sh
./infra/server/start_nginx_acme.sh
```
Esse comando irá iniciar dois containers: o [nginx-proxy](https://github.com/nginx-proxy/nginx-proxy) e [nginx-proxy-acme](https://github.com/nginx-proxy/acme-companion). Que tem como objetivo:
- nginx-proxy: Reverse-proxy que ficará na frente da aplicação Django e demais serviços;
- nginx-proxy-acme: Configura automaticamente HTTPs;

Após isso, será necessário subir a infraestrutura de serviços de terceiros, dependendo do ambiente.

Para o ambiente de staging, utilize o compose [infra/server/3rd_party/staging.yml](./infra/server/3rd_party/staging.yml), com o comando:
```sh
docker compose -f ./infra/server/3rd_party/staging.yml --env-file .env up -d
```

Para o ambiente de produção, utilize o compose [infra/server/3rd_party/production.yml](./infra/server/3rd_party/production.yml), com o comando:
```sh
docker compose -f ./infra/server/3rd_party/production.yml --env-file .env up -d
```

Após isso é possível subir o compose da aplicação de acordo com o ambiente.

Para o ambiente de staging, utilize o compose [staging.yml](./staging.yml), com o comando:
```sh
docker compose -f staging.yml --env-file .env build --no-cache
docker compose -f staging.yml --env-file .env up -d
```

Para o ambiente de produção, utilize o compose [production.yml](./production.yml), com o comando:
```sh
docker compose -f production.yml --env-file .env build --no-cache
docker compose -f production.yml --env-file .env up -d
```

Feito! A aplicação estará no ar. Após isso o processo de CI/CD irá fazer deploys automaticamente na máquina.


### Detalhes sobre os ambientes de staging/produção

Os arquivos estáticos são servidos pelo nginx em qualquer domínio! Não encontramos um jeito melhor de servir os arquivos estáticos sem ser configurando um fallback para quando não é possível se conectar com um container. Essa configuração está definida no arquivo [infra/nginx/conf.d/fallback_server.conf](./infra/nginx/conf.d/fallback_server.conf).
7 changes: 3 additions & 4 deletions infra/app/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@ RUN pip install pip setuptools wheel
RUN pip install pdm==2.7.4

COPY pyproject.toml pdm.lock /pkgs/
RUN pdm install --prod --no-lock --no-editable
ARG PDM_INSTALL_ARGS=""
RUN pdm install ${PDM_INSTALL_ARGS}

WORKDIR /app

ENV PYTHONPATH=/app/src:/pkgs/.venv/lib/python3.11/site-packages
COPY src /app/src
COPY locale /app/locale
COPY manage.py gunicorn.conf.py /app/
RUN python manage.py collectstatic --noinput
RUN python manage.py compilemessages
COPY manage.py gunicorn.conf.py infra/app/before_migrate.sh /app/
12 changes: 12 additions & 0 deletions infra/app/before_migrate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/sh
set -e

# This script is ran only by the db_migration container. This speeds up the startup of the application

echo "Collecting static files.."
python manage.py collectstatic --noinput

echo "Compiling messages..."
python manage.py compilemessages

exec "$@"
1 change: 1 addition & 0 deletions infra/nginx/conf.d/custom_proxy.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
client_max_body_size 100m;
8 changes: 8 additions & 0 deletions infra/nginx/conf.d/fallback_server.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
server {
listen 80 default_server;
root /usr/share/nginx/static;

location /static {
alias /usr/share/nginx/static;
}
}
51 changes: 0 additions & 51 deletions infra/nginx/nginx.conf.template

This file was deleted.

19 changes: 19 additions & 0 deletions infra/server/3rd_party/production.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
version: "3.3"

services:
redis:
image: "redis"
ports:
- "${REDIS_PORT}:${REDIS_PORT}"
command: redis-server --requirepass ${REDIS_PASSWORD} --replicaof no one --replica-read-only no
volumes:
- cache:/data
networks:
- shared

volumes:
cache:

networks:
shared:
external: true
34 changes: 34 additions & 0 deletions infra/server/3rd_party/staging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
version: "3.3"

services:
redis:
image: "redis"
ports:
- "${REDIS_PORT}:${REDIS_PORT}"
command: redis-server --requirepass ${REDIS_PASSWORD} --replicaof no one --replica-read-only no
volumes:
- cache:/data
networks:
- shared

db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "${SQL_PORT}:${SQL_PORT}"
command: -p ${SQL_PORT}
environment:
POSTGRES_PASSWORD: ${SQL_PASSWORD}
POSTGRES_USER: ${SQL_USER}
POSTGRES_DB: ${SQL_DATABASE}
networks:
- shared

volumes:
cache:
postgres_data:

networks:
shared:
external: true
Loading
Loading