Ansible - Provisionando um servidor web com Nginx, PHP e MySQL

Por Aristides Neto - 24/12/2020

devops ansible

Fala pessoal tudo beleza?

Hoje vamos subir um servidor web completo, rodando os serviços Nginx, PHP 7.4 e MySQL 5.7/MySQL 8.0. Esse script foi testado e validado para funcionar corretamente nas versões 18.04 e 20.04 do Ubuntu.

Por padrão, o script irá instalar a versão do MySQL que está no repositório da distribuição, ou seja, se você usar o Ubuntu 18.04 a versão do MySQL que será instalada será a 5.7. Se você usar o Ubuntu 20.04, a versão instalada será a 8.0.

Disclaimer

O objetivo desse artigo é mostrar um script pronto de como provisionar um servidor web com os serviços Nginx, PHP e Mysql instalados e configurados. Se eu for explicar cada passo para a instalação de cada serviço, o artigo irá ficar bem extenso e cansativo de ler.

Por esse motivo, achei melhor explicar somente o que foi feito para a instalação do MySQL, explicando cada arquivo necessário para criar o mesmo. Nos próximos tópicos você verá de forma automatizada como fazer:

Como eu disse, meu objetivo é ter um script para provisionar um servidor web completo, por isso no final do artigo irei disponibilizar um link para você obter esse script por completo.

Ansible

Para nos ajudar, iremos utilizar uma ferramenta chamada Ansible. O Ansible é uma ferramenta de automação de código aberto para gerenciar, automatizar, configurar servidores e implementar serviços. O Ansible foi desenvolvido por Michael DeHaan, mas em 2015 foi adquirido pela Red Hat que o mantém até hoje.

O Ansible funciona de uma forma muito simples, ele não precisa de um agente instalado em seu alvo. Utiliza-se apenas de uma conexão via SSH podendo gerenciar múltiplos servidores de uma vez.

Instalação

Para a instalação do Ansible em sua máquina host, siga os passos abaixo conforme sua distribuição. Para uma lista completa para outras distribuições verifique a documentação do Ansible.

Ubuntu

sudo apt update
sudo apt install software-properties-common
sudo apt-add-repository --yes --update ppa:ansible/ansible
sudo apt install ansible

Debian

Adicione a linha abaixo no arquivo /etc/apt/sources.list:

deb http://ppa.launchpad.net/ansible/ansible/ubuntu trusty main

Execute os seguintes comandos:

sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 93C4A3FD7BB9C367
sudo apt update
sudo apt install ansible

Nosso cenário

Para esse tutorial, irei utilizar um droplet criado na Digital Ocean, fique a vontade para utilizar qualquer provedor de serviço como AWS, Linode, Google entre outros.

Antes de prosseguir para o próximo passo, crie sua máquina e obtenha o endereço IP que seu cloud irá disponibilizar.

Criando o arquivo de inventário

Vamos criar um diretório onde os arquivos do Ansible serão criados. Execute o comando abaixo:

mkdir ~/ansible && cd ~/ansible

Vamos criar o nosso arquivo de inventário chamado de hosts.

vim hosts

Cole o seguinte conteúdo dentro do arquivo.

[webserver]
192.168.10.100 ansible_user=root ansible_python_interpreter=/usr/bin/python3

Substitue o IP 192.168.10.100 para um IP válido, disponibilizado pelo seu cloud. É através desse arquivo de inventário que o Ansible irá obter o endereço do servidor e executar os comandos necessários.

A estrutura e a forma que iremos trabalhar com o Ansible, será seguindo as boas práticas. Iremos trabalhar com roles, variables, files e outros recursos.

Criando uma role

Uma role é usada para organizar, compartilhar a separar as responsabilidades de cada tarefa. Podemos ter várias roles, cada uma fazendo uma coisa, por exemplo: uma para instalar o MySQL, outra para criar os usuários SSH, configurar firewall, entre outras.

Vamos criar a nossa primeira role, para isso iremos utilizar o comando ansible-galaxy, digite em seu terminal:

# Cria o diretório onde as roles serão armazendas
mkdir roles

# Cria uma role chamada mysql
ansible-galaxy init roles/mysql

Ao executar o comando ansible-galaxy init roles/mysql, será criado toda a estrutura de uma role, contendo alguns diretórios e arquivos responsáveis em executar determinadas tarefas.

Podemos ver melhor a estrutura criada ao criarmos a role mysql.

# Diretório: ~/ansible
.
├── hosts
└── roles
    └── mysql
        ├── defaults
        │   └── main.yml
        ├── files
        ├── handlers
        │   └── main.yml
        ├── meta
        │   └── main.yml
        ├── README.md
        ├── tasks
        │   └── main.yml
        ├── templates
        ├── tests
        │   ├── inventory
        │   └── test.yml
        └── vars
            └── main.yml

Trabalhando na role MySQL

Iremos agora criar as tarefas que a role MySQL executará. Vou começar pelos arquivos de variavéis para quando chegarmos na tarefa em si, você possa entender de onde as variavéis foram obtidas.

Os próximos tópicos estão nomeados em inglês para manter os mesmos nomes utilizados pelo Ansible.

Vars

Podemos definir variáveis para o nosso script utilizando o arquivo vars/main.yml ou o defaults/main.yml. No diretório default, utilizamos quando queremos que uma determinada variável seja usada caso não for declarada em algum lugar do script, ou seja, seria onde colocaríamos as variavéis padrões para evitar algum erro durante o provisionamento, caso não declarada.

No diretório vars podemos declarar as variáveis que deverão ser usadas durante a execução do script.

Para a criação dos usuários e dos bancos de dados MySQL, iremos utilizar o arquivo vars/main.yml. Cole o seguinte conteúdo.

---
# vars file for roles/mysql
db_password: password

mysql_users:
  - user: aristides
    password: [email protected]
    host: '%'
    priv: '*.*:ALL,GRANT'

  - user: example
    password: [email protected]
    host: '%'
    priv: 'example.*:SELECT,INSERT,UPDATE,DELETE,LOCK TABLES,EXECUTE,SHOW VIEW'

  - user: sakila
    password: [email protected]
    host: '%'
    priv: 'sakila.*:SELECT,INSERT,UPDATE,DELETE,LOCK TABLES,EXECUTE,SHOW VIEW'

mysql_databases:
  - database: example
    encoding: utf8mb4
    name: example.sql
    dest: /tmp

  - database: sakila
    encoding: utf8mb4
    name: sakila.sql
    dest: /tmp

Tasks

Vamos criar nossa primeira tarefa, para isso vamos editar o arquivo tasks/main.yml. Cole o seguinte conteúdo dentro do arquivo, logo abaixo explico o que será feito.

---
# tasks file for roles/mysql
- name: Instalando MySQL Server
  apt: 
    name: "{{ packages }}"
    state: latest
    update_cache: yes
  vars:
    packages:
      - mysql-server
      - python3-pymysql
      - python3-mysqldb
  notify: restart mysql

- name: Certifica que o serviço do MySQL está rodando
  service:
    name: mysql
    state: started
    enabled: true

- import_tasks: config.yml
- import_tasks: import.yml

Crie o seguinte arquivo mysql/tasks/config.yml e cole o conteúdo abaixo:

---
# tasks file for roles/mysql
- name: Altera plugin de autenticação para mysql_native_password
  shell: mysql -u root -e 'UPDATE mysql.user SET plugin="mysql_native_password" WHERE user="root" AND host="localhost"'
  # Caso queira executar o mesmo script na segunda vez, descomente a linha abaixo e comente a de cima.
  # Na primeira vez que o script é executado é criado uma senha para o usuário root, por isso a necessidade de informar a senha no comando abaixo
  # shell: mysql -u root -p{{ db_password }} -e 'UPDATE mysql.user SET plugin="mysql_native_password" WHERE user="root" AND host="localhost"'

- name: Recarrega privilégios
  shell: mysql -u root -e 'FLUSH PRIVILEGES'
  # shell: mysql -u root -p{{ db_password }} -e 'FLUSH PRIVILEGES'

- name: Configura a senha do root
  mysql_user:
    login_host: 'localhost'
    login_user: 'root'
    login_password: ''
    name: 'root'
    password: '{{ db_password }}'
    state: present

- name: Criando usuario Mysql
  mysql_user:
    login_user: 'root'
    login_password: '{{ db_password }}'
    name: '{{ item.user }}'
    password: '{{ item.password }}'
    priv: "{{ item.priv }}"
    host: "{{ item.host }}"
    state: present
  with_items: "{{ mysql_users }}"

- name: Criando bancos de dados
  mysql_db:
    name: '{{ item.database }}'
    login_user: root
    login_password: "{{ db_password }}"
    state: present
    encoding: "{{ item.encoding }}"
  with_items: "{{ mysql_databases }}"

Crie o seguinte arquivo mysql/tasks/import.yml e cole o conteúdo abaixo:

---
# tasks file for roles/mysql
- name: Copiando arquivos SQL para o servidor
  copy:
    src: "{{ item.name }}"
    dest: "{{ item.dest }}/{{ item.name }}"
  with_items: "{{ mysql_databases }}"

- name: Import arquivo de dump do banco de dados
  mysql_db:
    name: '{{ item.database }}'
    login_user: root
    login_password: "{{ db_password }}"
    state: import
    target: "{{ item.dest }}/{{ item.name }}"
  with_items: "{{ mysql_databases }}"

Veja que iremos importar dois bancos de dados, você pode fazer o download clicando aqui. Descompacte o arquivo .zip e salve os dois arquivos .sql no diretório roles/mysql/files. Os dois são os mesmos bancos só que com nomes diferentes, isso apenas para entender que podemos importar vários bancos de dados.

Handlers

Os handlers são gatilhos que irá acionar uma task. No arquivo vars/main.yml durante a instalação do MySQL, existe uma chamada para o handler restart mysql através da linha notify: restart mysql.

Os handlers quando acionados são executados somente no final do script, independentemente de onde são chamados, sendo essa a diferença de uma task normal.

O arquivo que será utilizado é o handlers/main.yml. Copie e cole o conteúdo abaixo:

---
# handlers file for roles/mysql
- name: restart mysql
  service:
    name: mysql
    state: restarted
    enabled: yes

Playbook

Para que tudo isso funcione, temos que criar um arquivo responsável em executar nossas tarefas, esse arquivo é chamado de playbook.

Crie um arquivo chamado my-server.yml na raiz do seu projeto e cole o seguinte conteúdo nele.

---
- hosts: webserver
  gather_facts: no

  roles:
    - mysql

Arquivo de configuração (opcional)

A criação de um arquivo de configuração na raiz do projeto é opcional, pois o arquivo principal se encontra em /etc/ansible/ansible.cfg e você pode realizar qualquer configuração necessária.

Existe uma configuração que eu tenho preferência em alterar que se chama host_key_checking. Setando essa chave para false quer dizer que no primeiro acesso ao servidor não será feita a verificação da chave do host, ou seja, aquela pergunta se o host é confiável e sendo necessário digitar yes para continuar.

Por padrão o Ansible deixa essa opção como true por motivos de segurança, ele protege contra spoofing de servidor. Caso queira saber mais clique aqui.

Para desativar, crie na raiz do projeto o arquivo ansible.cfg e cole o seguinte contéudo:

[defaults]
host_key_checking = False

Vamos ver como está nossa estrutura de diretórios depois da criação dos nossos arquivos:

# Diretório: ~/ansible
.
├── ansible.cfg
├── hosts
├── my-server.yml
└── roles
    ├── mysql
        ├── defaults
        │   └── main.yml
        ├── files
        │   ├── example.sql
        │   └── sakila.sql
        ├── handlers
        │   └── main.yml
        ├── meta
        │   └── main.yml
        ├── README.md
        ├── tasks
        │   ├── config.yml
        │   ├── import.yml
        │   └── main.yml
        ├── templates
        ├── tests
        │   ├── inventory
        │   └── test.yml
        └── vars
            └── main.yml

Execução do script

Vamos entender o que será feito com esse script:

Agora precisamos executar o nosso playbook, para isso digite o comando abaixo informando o arquivo de inventário utilizado e o nosso playbook. O comando time no início do comando para a execução do playbook, é um comando do Linux, utilizo ele para que no final ele informe o tempo que levou na execução do comando.

time ansible-playbook -i hosts my-server.yml

Após a finalização do script, você verá a informação:

PLAY RECAP ******************************************************************************************
192.168.10.100  : ok=10   changed=9    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   


real    2m46,517s     <-- tempo de execução do script
user    0m20,500s
sys 0m2,520s

A partir desse momento você terá o MySQL rodando com todas as configurações devidas de uma forma automatizada, lindo né? E isso tudo levou apenas 2 min e 46 segundos. Se fosse fazer isso na mão, quanto tempo seria?

Conclusão

Para finalizarmos esse artigo, como disse, meu objetivo é mostrar como subir um servidor web completo com Nginx, PHP e MySQL, se você seguir exatamente os passos aqui citados você terá seu servidor MySQL pronto.

Mas você pode clonar meu repositório no github com o script completo, execute no seu terminal:

cd ~/ && git clone https://github.com/aristidesneto/ansible-webserver.git && cd ansible-webserver

Se você encontrar algum erro, sugestões de melhorias ou se tiver alguma dúvida, deixe seu comentário abaixo.

Até a próxima!

Aristides Neto

Programador web focado no back-end, mas esforçado no front. Entusiasta em gerenciamento de servidores Linux, impressionado com a automatização de processos e provisionamento de máquinas.