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

Aprenda a provisionar um servidor web com Nginx, PHP e Mysql usando o Ansible. Importe seu banco de dados de forma automatizada e efificente

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

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:

  • Instalação do MySQL
  • Configurar senha do usuário root
  • Criação de usuários
  • Criação dos bancos de dados
  • Envio de um arquivo .sql para o servidor
  • Importação do arquivo .sql
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: user@123
    host: '%'
    priv: '*.*:ALL,GRANT'

  - user: example
    password: example@123
    host: '%'
    priv: 'example.*:SELECT,INSERT,UPDATE,DELETE,LOCK TABLES,EXECUTE,SHOW VIEW'

  - user: sakila
    password: sakila@123
    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
  • Definimos a senha que será configurada para o usuário root do Mysql em db_password.
  • Definimos uma variável chamada mysql_users que recebe um array de usuários, nela informamos o nome e senha do usuário, o host e seus privilégios.
  • Na variável mysql_databases, informamos os bancos de dados que serão copiados da máquina host para a máquina remota (servidor).

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
  • Na task do MySQL, iremos instalar o pacote mysql-server e extensões do python para que o Ansible possa executar comandos do MySQL.
  • Repare na linha notify: restart mysql, ela será explicada mais a diante.
  • Após a instalação será feita uma checagem para certificar que o serviço do MySQL está ativo.
  • Logo após é feito a importação de duas tasks config.yml e import.yml que foi criada para executar outros comandos. Essa é uma boa prática para dividir as responsabilidades dos arquivos.

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 }}"
  • O arquivo config.yml é responsável em realizar algumas configurações no banco de dados e setar uma senha para o usuário root.
  • Veja que existe algumas variáveis, por exemplo a {{ db_password }}, declarada no arquivo vars/main.yml.
  • Um ponto importante e crucial para entender como a funcionalidade de loop funciona no Ansible é analisar a linha with_items: "{{ mysql_users }}" e with_items: "{{ mysql_databases }}". O Ansible irá percorrer esse array acessando os valores através da palavra reservada item e a chave do array declarado, por exemplo: item.name. Como informado as variáveis foram declaradas no arquivo vars/main.yml.

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 }}"
  • O arquivo import.yml é responsável em copiar para o servidor os arquivos .sql de backup e realizar a importação do mesmo.
  • Veja que a importação é feita utilizando o recurso de loop e pode ser utilizado para importar vários bancos de dados de maneira rápida e eficiente.
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
  • Nesse arquivo criamos um handler chamado de restart mysql que como o próprio nome diz, ao finalizar a execução do script o Ansible irá restartar o serviço do MySQL para que entre em vigor as alterações realizadas.

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
  • Informamos ao Ansible que ao ler esse arquivo utilize o host informado no arquivo hosts que contenha a chave webserver. Esse arquivo hosts criamos lá no início, o arquivo de inventário.
  • Na linha gather_facts deixei como no para ganhar um tempo na execução do script. Caso for yes, o Ansible irá obter várias informações do servidor como por exemplo, sistema operacional, memória ram, hd e várias outras. Essas informações podem ser usadas para outros fins, mas para o nosso caso não é necessário.
  • Em roles definimos que será executada a role mysql. Você pode adicionar quantas roles que desejar.

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:

  • Será feito a instalação do MySQL
  • A senha do root será alterada conforme definimos no arquivo vars/main.yml
  • Serão criados usuários para o acesso ao MySQL
  • Serão criados bando de dados
  • Serão importados dois arquivos .sql para os bancos recém criados

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.

Você pode encontrar o código no meu repositório no github.

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

Até a próxima!