Configuração do servidor para implantar um aplicativo Rails usando Ansible

Recentemente, eu precisei escrever alguns playbooks do Ansible para preparar o servidor para a implantação de um aplicativo Rails. E, para minha surpresa, não encontrei um manual passo a passo simples. Eu não queria copiar o playbook de outra pessoa sem entender o que estava acontecendo e, no final, tive que ler a documentação, reunindo tudo sozinho. Talvez eu possa ajudar alguém a acelerar esse processo com este artigo.

Primeiramente, é importante entender que o Ansible oferece uma interface conveniente para executar uma lista predefinida de ações em um servidor remoto (ou servidores) via SSH. Não há nenhuma mágica aqui, não é possível instalar um plugin e obter automaticamente a implantação da sua aplicação com zero downtime usando Docker, monitoramento e outros recursos. Para escrever um playbook, você deve saber exatamente o que deseja fazer e como fazê-lo. Portanto, não estou satisfeito com playbooks prontos do GitHub, ou artigos do tipo: “Copie e execute, - vai funcionar.”

O que precisamos?

Como já mencionei, para escrever um playbook você precisa saber o que quer fazer e como fazê-lo. Vamos esclarecer o que precisamos. Para um aplicativo Rails, precisaremos de alguns pacotes de sistema: nginx, postgresql (redis, etc.). Além disso, precisamos de uma versão específica do Ruby. O melhor é instalá-lo via rbenv (rvm, asdf…). Executar tudo isso como usuário root é sempre uma má ideia, então precisamos criar um usuário separado e configurar suas permissões. Depois disso, é necessário enviar nosso código para o servidor, copiar as configurações para nginx, postgres, etc. e iniciar todos esses serviços.

Assim, a sequência de ações é a seguinte:

  1. Fazemos login como root
  2. instalamos os pacotes de sistema
  3. criamos um novo usuário, configuramos permissões e chave SSH
  4. configuramos os pacotes de sistema (nginx, etc.) e os iniciamos
  5. Criamos um usuário no banco de dados (podemos criar o banco de dados ao mesmo tempo)
  6. Fazemos login com o novo usuário
  7. Instalamos rbenv e Ruby
  8. Instalamos o Bundler
  9. Carregamos o código do aplicativo
  10. Iniciamos o servidor Puma

Além disso, as etapas finais podem ser feitas usando capistrano, pois ela já vem com a capacidade de copiar código para diretórios de release, alternar o release via symlink após um deploy bem-sucedido, copiar as configurações do diretório compartilhado, reiniciar o puma, etc. Tudo isso também pode ser feito com Ansible, mas por que fazer?

Estrutura de Arquivos

Ansible tem uma estrutura de arquivos rigorosa para todos os seus arquivos, portanto, é melhor manter tudo em um diretório separado. E não importa muito se ele está dentro do aplicativo rails ou separado. Você pode armazenar os arquivos em um repositório git separado. Pessoalmente, achei mais conveniente criar um diretório ansible na pasta /config do aplicativo rails e guardar tudo em um único repositório. Playbook Simples

Um playbook é um arquivo yml que descreve, usando uma sintaxe especial, o que e como o ansible deve fazer. Vamos criar nosso primeiro playbook que não faz nada:

--- - name: Playbook Simples hosts: all

Aqui nós simplesmente dizemos que nosso playbook se chama

e que seu conteúdo deve ser executado para todos os hosts. Podemos salvá-lo no diretório /ansible com o nome Um playbook é um arquivo yml que descreve, usando uma sintaxe especial, o que e como o ansible deve fazer. Vamos criar nosso primeiro playbook que não faz nada: playbook.yml playbook.yml e tentar executar:

ansible-playbook ./playbook.yml

PLAY [Playbook Simples] ************************************************************************************************************************************
skipping: nenhum host correspondeu

Ansible informa que não conhece hosts que correspondam à lista all. Eles devem ser listados em um arquivo de inventário..

Vamos criar um neste mesmo diretório ansible:

123.123.123.123

Dessa forma, basta indicar o host (de preferência o host do seu VPS para testes, ou você pode escrever localhost) e salvá-lo com o nome inventory.
Você pode tentar executar o ansible com o arquivo de inventário:

ansible-playbook ./playbook.yml -i inventory
PLAY [Playbook Simples] ************************************************************************************************************************************

TASK [Coletando Fatos] ************************************************************************************************************************************

PLAY RECAP ************************************************************************************************************************************

Se você tiver acesso por ssh ao host indicado, o ansible se conectará e coletará informações sobre o sistema remoto. (Tarefa padrão [Coletando Fatos]) e em seguida fornecerá um resumo da execução (PLAY RECAP).

Por padrão, o nome de usuário usado para a conexão é aquele com o qual você está logado no sistema. No host, provavelmente, ele não estará disponível. No arquivo do playbook, você pode especificar qual usuário usar para a conexão através da diretiva remote_user. Assim, as informações sobre o sistema remoto podem não ser necessárias e não vale a pena gastar tempo coletando-as. Essa tarefa também pode ser desativada:

---
- name: Simple playbook
  hosts: all
  remote_user: root
  become: true
  gather_facts: no

Tente executar o playbook novamente e certifique-se de que a conexão está funcionando. (Se você indicou o usuário root, também deve adicionar a diretiva become: true para obter permissões elevadas. Como está escrito na documentação: become set to 'true'/'yes' to activate privilege escalation. embora não esteja muito claro por quê).

É possível que você receba um erro causado pelo fato de que o ansible não consegue determinar o interpretador Python; você pode especificá-lo manualmente:

ansible_python_interpreter: /usr/bin/python3 

onde você pode descobrir onde está o Python com o comando whereis python.

Instalação de pacotes do sistema

Na instalação padrão do Ansible, há muitos módulos para trabalhar com vários pacotes de sistema, o que nos impede de escrever scripts em bash para qualquer situação. Agora, precisaremos de um desses módulos para atualizar o sistema e instalar pacotes do sistema. No meu VPS, tenho Ubuntu Linux, portanto, para instalar pacotes, uso apt-get e módulo para ele. Se você estiver usando um sistema operacional diferente, pode ser que precise de outro módulo (lembre-se de que no início eu disse que você deve saber com antecedência o que e como faremos). No entanto, a sintaxe provavelmente será semelhante.

Vamos complementar nosso playbook com as primeiras tarefas:

---
- name: Simple playbook
  hosts: all
  remote_user: root
  become: true
  gather_facts: no

  tasks:
    - name: Update system
      apt: update_cache=yes
    - name: Install system dependencies
      apt:
        name: git,nginx,redis,postgresql,postgresql-contrib
        state: present

A tarefa — é exatamente essa tarefa que o ansible executará em servidores remotos. Damos um nome à tarefa para rastrear sua execução no log. E descrevemos, utilizando a sintaxe de um módulo específico, o que ele precisa fazer. Neste caso apt: update_cache=yes — diz para atualizar os pacotes do sistema usando o módulo apt. O segundo comando é um pouco mais complexo. Passamos para o módulo apt uma lista de pacotes e dizemos que eles state devem se tornar present, ou seja, estamos dizendo para instalar esses pacotes. Da mesma forma, podemos dizer para removê-los ou atualizá-los, basta alterar. state. Observe que, para o funcionamento do rails com postgresql, precisamos do pacote postgresql-contrib, que estamos instalando agora. É importante saber disso e fazer, o ansible não fará isso automaticamente.

Tente executar o playbook novamente e verifique se os pacotes serão instalados.

Criação de novos usuários.

Para trabalhar com usuários, o Ansible também possui um módulo — user. Vamos adicionar mais uma task (já escondi partes conhecidas do playbook em comentários, para não precisar copiá-lo inteiro todas as vezes):

---
- nome: Playbook simples
  # ...
  tasks:
    # ...
    - nome: Adicionar um novo usuário
      user:
        name: my_user
        shell: /bin/bash
        password: "{{ 123qweasd | password_hash('sha512') }}"

Estamos criando um novo usuário, definindo seu shell e senha. E logo encontramos alguns problemas. E se os nomes dos usuários precisarem ser diferentes para diferentes hosts? Além disso, armazenar a senha em texto claro no playbook é uma ideia muito ruim. Primeiro, vamos mover o nome de usuário e a senha para variáveis, e mais para o final do artigo, mostrarei como criptografar a senha.

---
- nome: Playbook simples
  # ...
  tasks:
    # ...
    - nome: Adicionar um novo usuário
      user:
        name: "{{ user }}"
        shell: /bin/bash
        password: "{{ user_password | password_hash('sha512') }}"

Usando chaves duplas nos playbooks para definir variáveis.

Os valores das variáveis serão especificados no arquivo de inventory:

123.123.123.123

[all:vars]
user=my_user
user_password=123qweasd

Observe a diretiva [all:vars] — ela indica que o próximo bloco de texto é de variáveis (vars) e são aplicáveis a todos os hosts (all).

A construção também é interessante "{{ user_password | password_hash('sha512') }}". A questão é que o ansible não adiciona o usuário através do user_add como você faria manualmente. Ele salva todos os dados diretamente, razão pela qual devemos converter a senha em hash previamente, e é isso que esse comando faz.

Vamos adicionar nosso usuário ao grupo sudo. No entanto, antes disso, precisamos garantir que tal grupo existe, pois ninguém fará isso por nós:

---
- nome: Playbook simples
  # ...
  tasks:
    # ...
    - nome: Garantir que um grupo 'sudo' exista
      group:
        name: sudo
        state: present
    - nome: Adicionar um novo usuário
      user:
        name: "{{ user }}"
        shell: /bin/bash
        password: "{{ user_password | password_hash('sha512') }}"
        groups: "sudo"

Tudo é bastante simples, também temos um módulo group para criar grupos, com uma sintaxe muito parecida com a do apt. Depois disso, basta atribuir esse grupo ao usuário (groups: "sudo").
Também é útil adicionar uma chave ssh a esse usuário, para que possamos fazer login como ele sem senha:

---
- name: Playbook simples
  # ...
  tasks:
    # ...
    - name: Garantir um grupo 'sudo'
      group:
      name: sudo
        state: present
    - name: Adicionar um novo usuário
      user:
        name: "{{ user }}"
        shell: /bin/bash
        password: "{{ user_password | password_hash('sha512') }}"
        groups: "sudo"
    - name: Implantar chave SSH
      authorized_key:
        user: "{{ user }}"
        key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
        state: present

Neste caso, a construção que é interessante "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" — ela copia o conteúdo do arquivo id_rsa.pub (o nome pode ser diferente), ou seja, a parte pública da chave ssh e a carrega na lista de chaves autorizadas para o usuário no servidor.

Funções

As três tarefas para criar o usuário podem ser facilmente agrupadas em um único grupo de tarefas, e seria bom armazenar esse grupo separadamente do playbook principal, para que ele não cresça demais. Para isso, o Ansible possui roles.
De acordo com a estrutura de arquivos mencionada no início, as roles devem ser colocadas em um diretório separado roles, com um diretório separado para cada role com o mesmo nome, dentro do diretório tarefas, arquivos, templates, etc.
Vamos criar a estrutura de arquivos: ./ansible/roles/user/tasks/main.yml (main — este é o arquivo principal que será carregado e executado ao conectar a role ao playbook, nele você pode incluir outros arquivos da role). Agora podemos transferir para este arquivo todas as tarefas relacionadas ao usuário:

# Create user and add him to groups
- name: Ensure a 'sudo' group
  group:
    name: sudo
    state: present

- name: Add a new user
  user:
    name: "{{ user }}"
    shell: /bin/bash
    password: "{{ user_password | password_hash('sha512') }}"
    groups: "sudo"

- name: Deploy SSH Key
  authorized_key:
    user: "{{ user }}"
    key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
    state: present

No playbook principal, é necessário indicar que se deve usar a role user:

---
- name: Playbook simples
  hosts: all
  remote_user: root
  gather_facts: no

  tasks:
    - name: Atualizar sistema
      apt: update_cache=yes
    - name: Instalar dependências do sistema
      apt:
        name: git,nginx,redis,postgresql,postgresql-contrib
        state: present

  roles:
    - user

Além disso, talvez faça sentido realizar a atualização do sistema antes de todas as outras tarefas, para isso você pode renomear o bloco tasks no qual elas são definidas em pre_tasks.

Configuração do nginx

O Nginx já deve estar instalado, é necessário configurá-lo e iniciá-lo. Vamos fazer isso diretamente na role. Criamos a estrutura de arquivos:

- ansible
  - roles
    - nginx
      - files
      - tasks
        - main.yml
      - templates

Agora precisamos dos arquivos e dos templates. A diferença entre eles é que os arquivos são copiados diretamente pelo Ansible, conforme estão. Já os templates devem ter a extensão j2 e podem usar os valores das variáveis através das mesmas chaves duplas.

Vamos incluir o nginx no main.yml arquivo. Para isso, temos o módulo systemd:

# Copy nginx configs and start it
- name: enable service nginx and start
  systemd:
    name: nginx
    state: started
    enabled: yes

Aqui, não apenas dizemos que o nginx deve ser iniciado (ou seja, o estamos iniciando), mas também informamos que ele deve ser habilitado.
Agora vamos copiar os arquivos de configuração:

# Copy nginx configs and start it
- name: enable service nginx and start
  systemd:
    name: nginx
    state: started
    enabled: yes

- name: Copy the nginx.conf
  copy:
    src: nginx.conf
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: '0644'
    backup: yes

- name: Copy template my_app.conf
  template:
    src: my_app_conf.j2
    dest: /etc/nginx/sites-available/my_app.conf
    owner: root
    group: root
    mode: '0644'

Estamos criando o arquivo de configuração principal do nginx (podemos pegá-lo diretamente do servidor ou escrever um novo). E também o arquivo de configuração para nosso aplicativo na diretório sites_available (isso não é obrigatório, mas é útil). No primeiro caso, usamos o módulo copy para copiar arquivos (o arquivo deve estar em /ansible/roles/nginx/files/nginx.conf). No segundo - copiamos o template, substituindo os valores das variáveis. O template deve estar em /ansible/roles/nginx/templates/my_app.j2). E ele pode ser parecido com isto:

upstream {{ app_name }} {
  server unix:{{ app_path }}\/shared\/tmp\/sockets\/puma.sock;
}

server {
  listen 80;
  server_name {{ server_name }} {{ inventory_hostname }};
  root {{ app_path }}\/current\/public;

  try_files $uri\/index.html $uri.html $uri @{{ app_name }};
  ....
}

Note as inserções {{ app_name }}, {{ app_path }}, {{ server_name }}, {{ inventory_hostname }} — todas essas são variáveis, cujos valores o Ansible irá substituir no template antes da cópia. Isso é útil quando se utiliza um playbook para diferentes grupos de hosts. Por exemplo, podemos complementar nosso arquivo de inventory:

[production]
123.123.123.123

[staging]
231.231.231.231

[all:vars]
user=my_user
user_password=123qweasd

[production:vars]
server_name=production
app_path=\/home\/www\/my_app
app_name=my_app

[staging:vars]
server_name=staging
app_path=\/home\/www\/my_stage
app_name=my_stage_app

Se agora executarmos nosso playbook, ele irá realizar as tarefas especificadas para ambos os hosts. Mas as variáveis para o host de staging serão diferentes da de production, não apenas nas roles e playbooks, mas também nas configurações do nginx. {{ inventory_hostname }} não é necessário especificar no arquivo inventory — isso é uma variável especial do ansible e ela armazena o host para o qual o playbook está sendo executado no momento.
Se você quiser ter um arquivo inventory para vários hosts, mas executar apenas para um único grupo, isso pode ser feito com o seguinte comando:

ansible-playbook -i inventory .\/playbook.yml -l "staging"

outra opção — ter arquivos inventory separados para diferentes grupos. Ou você pode combinar os dois métodos, se tiver muitos hosts diferentes.

Vamos voltar à configuração do nginx. Após copiar os arquivos de configuração, precisamos criar um symlink em sites_enabled para my_app.conf a partir de sites_available. E reiniciar o nginx.

... # código antigo em mail.yml

- name: Criar symlink para sites-enabled
  file:
    src: /etc/nginx/sites-available/my_app.conf
    dest: /etc/nginx/sites-enabled/my_app.conf
    state: link

- name: reiniciar nginx
  service:
    name: nginx
    state: restarted

Aqui tudo é simples — são novamente os módulos do ansible com uma sintaxe bastante padrão. Mas há um ponto. Não faz sentido reiniciar o nginx toda vez. Você notou que não escrevemos comandos do tipo: "faça isso assim", a sintaxe é mais como "isso deve ter este estado aqui". E na maioria das vezes, é assim que o ansible funciona. Se o grupo já existir, ou se o pacote do sistema já estiver instalado, o ansible verificará isso e ignorará a tarefa. Da mesma forma, os arquivos não serão copiados se coincidirem completamente com o que já existe no servidor. Podemos tirar proveito disso e reiniciar o nginx apenas se os arquivos de configuração forem modificados. Para isso, existe a diretiva register:

# Copy nginx configs and start it
- name: enable service nginx and start
  systemd:
    name: nginx
    state: started
    enabled: yes

- name: Copy the nginx.conf
  copy:
    src: nginx.conf
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: '0644'
    backup: yes
  register: restart_nginx

- name: Copy template my_app.conf
  template:
    src: my_app_conf.j2
    dest: /etc/nginx/sites-available/my_app.conf
    owner: root
    group: root
    mode: '0644'
  register: restart_nginx

- name: Create symlink to sites-enabled
  file:
    src: /etc/nginx/sites-available/my_app.conf
    dest: /etc/nginx/sites-enabled/my_app.conf
    state: link

- name: restart nginx
  service:
    name: nginx
    state: restarted
  when: restart_nginx.changed

Se um dos arquivos de configuração mudar, a cópia será realizada e a variável será registrada. restart_nginx. E somente se essa variável estiver registrada, o reinício do serviço será realizado.

E, claro, é necessário adicionar o papel do nginx ao playbook principal.

Configuração do postgresql

Precisamos ativar o postgresql através do systemd da mesma forma que fizemos com o nginx, bem como criar um usuário que utilizaremos para acessar o banco de dados e o próprio banco de dados.
Vamos criar um papel. /ansible/roles/postgresql/tasks/main.yml:

# Create user in postgresql
- name: enable postgresql and start
  systemd:
    name: postgresql
    state: started
    enabled: yes

- name: Create database user
  become_user: postgres
  postgresql_user:
    name: "{{ db_user }}"
    password: "{{ db_password }}"
    role_attr_flags: SUPERUSER

- name: Create database
  become_user: postgres
  postgresql_db:
    name: "{{ db_name }}"
    encoding: UTF-8
    owner: "{{ db_user }}"

Não vou detalhar como adicionar variáveis ao inventory, isso já foi feito muitas vezes, assim como a sintaxe dos módulos postgresql_db e postgresql_user. Mais informações podem ser encontradas na documentação. Aqui, o mais interessante é a diretiva become_user: postgres. O fato é que, por padrão, o acesso ao banco de dados postgresql é restrito apenas ao usuário postgres e apenas localmente. Esta diretiva nos permite executar comandos em nome desse usuário (desde que tenhamos acesso).
Além disso, pode ser necessário adicionar uma linha no pg_hba.conf para abrir o acesso ao novo usuário no banco. Isso pode ser feito da mesma forma que alteramos a configuração do nginx.

E, claro, é necessário adicionar o papel do postgresql ao playbook principal.

Instalação do ruby via rbenv

No ansible não há módulos para trabalhar com rbenv, e ele é instalado clonando o repositório do git. Portanto, essa tarefa se torna a mais não padrão. Vamos criar um papel para isso /ansible/roles/ruby_rbenv/main.yml e começar a preenchê-lo:

# Install rbenv and ruby
- name: Install rbenv
  become_user: "{{ user }}"
  git: repo=https://github.com/rbenv/rbenv.git dest=~/.rbenv

Novamente usamos a diretiva become_user para trabalhar sob o usuário que criamos para esse propósito. Como o rbenv é instalado em seu diretório home, e não globalmente. Também usamos o módulo git para clonar o repositório, especificando repo e dest.

Em seguida, precisamos adicionar rbenv init no bashrc e também adicionar rbenv no PATH. Para isso, temos o módulo lineinfile:

- name: Adicionar rbenv ao PATH
  become_user: "{{ user }}"
  lineinfile:
    path: ~\/ .bashrc
    state: present
    line: 'export PATH="${HOME}\/ .rbenv\/bin:${PATH}"'

- name: Adicionar rbenv init ao bashrc
  become_user: "{{ user }}"
  lineinfile:
    path: ~\/ .bashrc
    state: present
    line: 'eval "$(rbenv init -)"'

Depois, precisamos instalar o ruby_build:

- name: Instalar ruby-build
  become_user: "{{ user }}"
  git: repo=https:\/\/github.com\/rbenv\/ruby-build.git dest=~\/ .rbenv\/plugins\/ruby-build

E, finalmente, instalar o ruby. Isso é feito através do rbenv, ou seja, apenas com um comando bash:

- name: Instalar ruby
  become_user: "{{ user }}"
  shell: |
    export PATH="${HOME}\/ .rbenv\/bin:${PATH}"
    eval "$(rbenv init -)"
    rbenv install {{ ruby_version }}
  args:
    executable: \/bin\/bash

Dizemos qual comando executar e como. No entanto, aqui encontramos que o ansible não executa o código contido no bashrc antes de executar os comandos. Portanto, teremos que definir rbenv diretamente neste mesmo script.

O próximo problema está relacionado ao fato de que o comando shell não possui estado do ponto de vista do ansible. Ou seja, não haverá verificação automática se essa versão do ruby está instalada ou não — teremos que fazer isso nós mesmos:

- name: Instalar ruby
  become_user: "{{ user }}"
  shell: |
    export PATH="${HOME}\/ .rbenv\/bin:${PATH}"
    eval "$(rbenv init -)"
    if ! rbenv versions | grep -q {{ ruby_version }}
      then rbenv install {{ ruby_version }} && rbenv global {{ ruby_version }}
    fi
  args:
    executable: \/bin\/bash

E resta instalar o bundler:

- name: Instalar bundler
  become_user: "{{ user }}"
  shell: |
    export PATH="${HOME}\/ .rbenv\/bin:${PATH}"
    eval "$(rbenv init -)"
    gem install bundler

E novamente adicionar nosso papel ruby_rbenv ao playbook principal.

Arquivos compartilhados.

No geral, nessa configuração poderíamos encerrar. A seguir, basta iniciar o capistrano e ele mesmo copiará o código, criará os diretórios necessários e iniciará o aplicativo (se tudo estiver configurado corretamente). No entanto, muitas vezes o capistrano precisa de arquivos de configuração adicionais, como database.yml ou .env Eles podem ser copiados exatamente como arquivos e modelos para nginx. Há apenas um pequeno detalhe. Antes de copiar os arquivos, é necessário criar a estrutura de diretórios para eles, algo como isto:

# Copy shared files for deploy
- name: Ensure shared dir
  become_user: "{{ user }}"
  file:
    path: "{{ app_path }}/shared/config"
    state: directory

Nós especificamos apenas um diretório e o Ansible automaticamente criará os pais, se necessário.

Ansible Vault

Já encontramos situações em que variáveis podem conter dados secretos, como senhas de usuário. Se você criou .env um arquivo para o aplicativo, e database.yml lá, deve haver ainda mais dados críticos. Seria bom ocultá-los de olhos curiosos. Para isso, usamos ansible vault.

Vamos criar um arquivo para variáveis /ansible/vars/all.yml (aqui você pode criar arquivos diferentes para diferentes grupos de hosts, assim como no arquivo inventory: production.yml, staging.yml, etc.).
Neste arquivo, é necessário transferir todas as variáveis que devem ser criptografadas, usando a sintaxe padrão do yml:

# System vars
user_password: 123qweasd
db_password: 123qweasd

# ENV vars
aws_access_key_id: xxxxx
aws_secret_access_key: xxxxxx
aws_bucket: bucket_name
rails_secret_key_base: very_secret_key_base

Depois disso, este arquivo pode ser criptografado com o comando:

ansible-vault encrypt ./vars/all.yml

Naturalmente, ao criptografar, você precisará estabelecer uma senha para descriptografar. Você pode ver o que haverá dentro do arquivo após executar este comando.

Com o ansible-vault decrypt o arquivo pode ser descriptografado, alterado e, em seguida, criptografado novamente.

Para funcionar, o arquivo não precisa ser descriptografado. Você o mantém em forma criptografada e executa o playbook com o argumento --ask-vault-pass. O Ansible pedirá a senha, recuperará as variáveis e executará as tarefas. Todos os dados permanecerão criptografados.

O comando completo para vários grupos de hosts e ansible vault ficará assim:

ansible-playbook -i inventory ./playbook.yml -l "staging" --ask-vault-pass

E eu não fornecerei o texto completo dos playbooks e funções, escreva você mesmo. Porque o Ansible é uma ferramenta assim — se você não entende o que deve fazer, ele também não fará por você.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster