---
tags: web,day3
robots: noindex, nofollow
lang: pt-br
---
# 29 Automatizando o Deploy
Vamos usar o **Ansible** para criar a automatização do deploy,
todos os passos que fizemos nas aulas passadas iremos agora colocar em um **playbook** no Ansible.
## Ansible
Ansible é um projeto open-source para automatização de tarefas sequenciais e utiliza o protocolo SSH para se comunicar com um ou mais servidores e executar as **tasks** definidas em um arquivo no formato **yaml**.
Além de podermos descrever o passo a passo do deploy em um playbook ainda podemos reaproveitar automatizações criadas por outras pessoas e disponíveis no https://galaxy.ansible.com
### Instalando
```bash
pip install ansible
```
### Play e Playbook
**Play** é um conjunto de fatos, variavéis e tarefas (facts, vars, tasks) e um **playbook** é o arquivo no formato **.yaml** onde colocamos um ou mais plays, em cada play descrevemos o estado que desejamos que o servidor esteja assim que o playbook for executado.
### Inventário
O playbook é aplicado em todos os **hosts** de um inventário, o inventário é o local onde todos os IPs são agrupados e normalmente colocamos nosso inventário de hosts em um arquivo chamado `inventory` ou passamos os IPs via linha de comando.
## Automatizando o Deploy
Vamos trabalhar na pasta `exemplos/day3/ansible` e para isso precisamos de uma VM no seu estado inicial, se você fez um snapshot pode agora salvar o estado atual da VM e então restaurar para o estado inicial.
Se não fez o snapshot pode baixar novamente o arquivo .vdi em osboxes.org e iniciar uma nova VM limpa.
### 1 Inventory
Criamos o arquivo `inventory` contendo o nosso host.
> **NOTA** confirme o endereço de IP com o comando `ip address` dentro da VM.
`nano exemplos/day3/ansible/inventory`
```ini
[webservers]
192.168.1.101
```
Caso queira fazer o deploy em mais servidores ao mesmo tempo poderá listar quantos IPS quiser e também pode criar quantos grupos quiser.
### 2 testando o Ansible
`exemplos/day3/ansible/django-deploy.yaml`
```yaml
---
- hosts: webservers
remote_user: osboxes
vars:
- msg: Hello
tasks:
- name: print hello
debug:
msg: "{{ msg }} World"
- name: Ping the server
ping:
```
Execute com o comando
```bash
ansible-playbook -i inventory django-deploy.yaml
```
```
PLAY [webservers] ***************************************************************
TASK [Gathering Facts] **********************************************************
ok: [192.168.1.101]
TASK [print hello] **************************************************************
ok: [192.168.1.101] => {
"msg": "Hello World"
}
TASK [Ping the server] **********************************************************
ok: [192.168.1.101]
PLAY RECAP **********************************************************************
192.168.1.101 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
```
> **NOTAS** Substitua o `remote_user` caso seja necessário e se não tiver uma chave SSH configurada verá um erro de autenticação, pode passar `--ask-pass` para o Ansible pedir para você digitar a sua senha.
Repare que o Ansible executou 3 **TASKS** a primeira task **gathering facts** é default do próprio ansible, nesta tarefa ele coleta informações a respeito ma máquina remota e depois executa as nossa próprias tasks em sequência.
Agora que já temos a resposta do teste podemos alterar o playbook para fazer de fato o deploy.
### 3 Deploy
Agora vamos criar o playbook e nele descrever as variáveis e tarefas.
> **DICA** se usar o VSCode instale as extensões [YAML](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml) e [Ansible](https://marketplace.visualstudio.com/items?itemName=redhat.ansible)
Os passos que vamos colocar em cada uma das tasks do playbook são:

1. Pacotes
2. Virtual Env
3. APP (pastas, permissões, repo)
4. Configurações
5. App Server
6. Web Server
`exemplos/day3/ansible/django-deploy.yaml`
```yaml
---
- hosts: webservers
remote_user: osboxes
vars:
system_user: osboxes
base_path: /app
repo_url: 'https://github.com/rochacbruno/python-web-api'
repo_branch: day3
repo_path: "{{ base_path }}/python-web-api"
app_path: "{{ repo_path }}/exemplos/day2/django"
app_name: djblog
settings_path: "{{ app_name }}.settings"
database_path: "{{ base_path }}/db.sqlite3"
venv_path: /home/{{ system_user }}/.venv
django_admin: "{{ venv_path }}/bin/django-admin"
environment:
PYTHONPATH: "{{ app_path }}"
DJANGO_SETTINGS_MODULE: "{{ settings_path }}"
BLOG_ENV: production
tasks:
- name: Instalação de pacotes
become: true
apt:
update_cache: true
state: present
pkg:
- python3-dev
- python-is-python3
- nginx
- git
- sqlite3
- python3.10-venv
- name: Virtual Env Python
pip:
virtualenv: "{{ venv_path }}"
virtualenv_command: python3.10 -m venv
name: pip
state: latest
- name: Adding user to www-data group
become: true
user:
name: "{{ system_user }}"
groups:
- www-data
append: true
- name: Base APP Folder
become: true
file:
path: "{{ base_path }}"
state: directory
owner: "{{ system_user }}"
group: www-data
mode: 0775
recurse: true
- name: Respository Codebase
git:
repo: "{{ repo_url }}"
dest: "{{ repo_path }}"
single_branch: true
version: "{{ repo_branch }}"
force: true
- name: Installing the project
pip:
virtualenv: "{{ venv_path }}"
name: file://{{app_path}}
state: present
- name: Place the app settings file
template:
src: blog_settings.toml.j2
dest: "{{ base_path }}/blog_settings.toml"
- name: Django - Get the database name
command:
chdir: "{{ base_path }}"
cmd: >-
{{ django_admin }} shell -c
"from django.conf import settings;print(settings.DATABASES.default.NAME)"
register: django_db_name
- name: Django - Assert App is correctly set
assert:
that: django_db_name.stdout == database_path
- name: Django - DB Migration
command:
chdir: "{{ base_path }}"
cmd: "{{ django_admin }} migrate --noinput"
- name: Django - Check if admin user exists
command:
chdir: "{{ base_path }}"
cmd: >-
{{ django_admin }} shell -c
"from django.contrib.auth.models import User;print(User.objects.count())"
register: django_admin_user_count
- name: Django - Create admin user
command:
chdir: "{{ base_path }}"
cmd: >-
{{ django_admin }} shell -c
"from django.contrib.auth.models import User;
User.objects.create_superuser('admin', 'admin@example.com', 'admin')"
when: django_admin_user_count.stdout == "0"
- name: Django - Collect static files
command:
chdir: "{{ base_path }}"
cmd: "{{ django_admin }} collectstatic --noinput"
- name: Gunicorn
become: true
block:
- name: Gunicorn - Install
pip:
name: gunicorn
virtualenv: "{{ venv_path }}"
state: present
- name: Gunicorn - socket file
template:
src: templates/gunicorn.socket.j2
dest: /etc/systemd/system/gunicorn.socket
- name: Gunicorn - service file
template:
src: templates/gunicorn.service.j2
dest: /etc/systemd/system/gunicorn.service
- name: Gunicorn - Reload systemd
systemd:
daemon_reload: yes
- name: Gunicorn - Start service
systemd:
state: started
enabled: true
name: gunicorn.socket
- name: NGINX
become: true
block:
- name: NGINX - Copy site config
template:
src: templates/nginx_blog.j2
dest: "/etc/nginx/sites-available/nginx_blog"
- name: NGINX - Enable site config
file:
src: "/etc/nginx/sites-available/nginx_blog"
dest: /etc/nginx/sites-enabled/default
state: link
- name: NGINX - Reload service
systemd:
state: restarted
name: nginx
```
> **NOTA** como em algumas tasks usamos `become: true` para executar com `sudo` teremos que informar o password e por isso executamos com `--ask-become-pass`
```bash
ansible-playbook -i inventory django-deploy.yaml --ask-become-pass
```
```
BECOME password:
PLAY [webservers] ***************************************************************
TASK [Gathering Facts] **********************************************************
ok: [192.168.1.101]
TASK [Instalação de pacotes] ****************************************************
ok: [192.168.1.101]
TASK [Virtual Env Python] *******************************************************
ok: [192.168.1.101]
TASK [Adding user to www-data group] ********************************************
ok: [192.168.1.101]
TASK [Base APP Folder] **********************************************************
changed: [192.168.1.101]
TASK [Respository Codebase] *****************************************************
changed: [192.168.1.101]
TASK [Installing the project] ***************************************************
changed: [192.168.1.101]
TASK [Place the app settings file] **********************************************
ok: [192.168.1.101]
TASK [Django - Get the database name] *******************************************
changed: [192.168.1.101]
TASK [Django - Assert App is correctly set] *************************************
ok: [192.168.1.101] => {
"changed": false,
"msg": "All assertions passed"
}
TASK [Django - DB Migration] ****************************************************
changed: [192.168.1.101]
TASK [Django - Check if admin user exists] **************************************
changed: [192.168.1.101]
TASK [Django - Create admin user] ***********************************************
changed: [192.168.1.101]
TASK [Django - Collect static files] ********************************************
changed: [192.168.1.101]
TASK [Gunicorn - Install] *******************************************************
changed: [192.168.1.101]
TASK [Gunicorn - socket file] ***************************************************
changed: [192.168.1.101]
TASK [Gunicorn - service file] **************************************************
changed: [192.168.1.101]
TASK [Gunicorn - Reload systemd] ************************************************
ok: [192.168.1.101]
TASK [Gunicorn - Start service] *************************************************
changed: [192.168.1.101]
TASK [NGINX - Copy site config] *************************************************
changed: [192.168.1.101]
TASK [NGINX - Enable site config] ***********************************************
changed: [192.168.1.101]
TASK [NGINX - Reload service] ***************************************************
changed: [192.168.1.101]
PLAY RECAP **********************************************************************
192.168.1.101 : ok=22 changed=15 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
```
## Templates
Na pasta `templates`
`blog_settings.toml.j2`
```html
[production]
allowed_hosts = ["*"]
debug = false
DATABASES__default__NAME="{{ database_path }}"
STATIC_ROOT="{{ base_path }}/static/"
STATICFILES_DIR="{{base_path }}/static/"
STATIC_URL="/static/"
```
`gunicorn.service.j2`
```ini
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target
[Service]
Environment=PYTHONPATH={{ app_path }}
Environment=DJANGO_SETTINGS_MODULE={{ settings_path }}
Environment=BLOG_ENV=production
User={{ system_user }}
Group=www-data
WorkingDirectory={{ base_path }}
ExecStart={{ venv_path }}/bin/gunicorn \
--access-logfile - \
--workers 3 \
--bind unix:/run/gunicorn.sock \
{{ app_name }}.wsgi
[Install]
WantedBy=multi-user.target
```
`gunicorn.socket.j2`
```ini
[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/gunicorn.sock
[Install]
WantedBy=sockets.target
```
`nginx_blog.j2`
```nginx
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
autoindex on;
alias {{ base_path }}/static/;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
}
```