Konfigurieren eines Servers zum Bereitstellen einer Rails-Anwendung mit Ansible

Vor nicht allzu langer Zeit musste ich mehrere Ansible-Playbooks schreiben, um den Server für die Bereitstellung einer Rails-Anwendung vorzubereiten. Und überraschenderweise habe ich keine einfache Schritt-für-Schritt-Anleitung gefunden. Ich wollte nicht das Spielbuch eines anderen kopieren, ohne zu verstehen, was geschah, und am Ende musste ich die Dokumentation lesen und alles selbst sammeln. Vielleicht kann ich mit Hilfe dieses Artikels jemandem helfen, diesen Prozess zu beschleunigen.

Das erste, was Sie verstehen müssen, ist, dass Ansible Ihnen eine praktische Schnittstelle bietet, um eine vordefinierte Liste von Aktionen auf einem oder mehreren Remote-Servern über SSH auszuführen. Hier gibt es keine Zauberei, Sie können kein Plugin installieren und Ihre Anwendung ohne Ausfallzeiten mit Docker, Überwachung und anderen Extras sofort bereitstellen. Um ein Playbook zu schreiben, müssen Sie wissen, was genau Sie tun möchten und wie Sie es tun. Deshalb gebe ich mich nicht mit vorgefertigten Playbooks von GitHub zufrieden oder mit Artikeln wie: „Kopieren und ausführen, es wird funktionieren.“

Was brauchen wir?

Wie ich bereits sagte, müssen Sie zum Schreiben eines Playbooks wissen, was Sie tun möchten und wie Sie es tun. Lassen Sie uns entscheiden, was wir brauchen. Für eine Rails-Anwendung benötigen wir mehrere Systempakete: Nginx, Postgresql (Redis usw.). Darüber hinaus benötigen wir eine bestimmte Ruby-Version. Am besten installieren Sie es über rbenv (rvm, asdf...). All dies als Root-Benutzer auszuführen ist immer eine schlechte Idee, daher müssen Sie einen separaten Benutzer erstellen und seine Rechte konfigurieren. Danach müssen Sie unseren Code auf den Server hochladen, die Konfigurationen für Nginx, Postgres usw. kopieren und alle diese Dienste starten.

Daraus ergibt sich folgender Handlungsablauf:

  1. Melden Sie sich als Root an
  2. Systempakete installieren
  3. Erstellen Sie einen neuen Benutzer, konfigurieren Sie Rechte und SSH-Schlüssel
  4. Konfigurieren Sie Systempakete (Nginx usw.) und führen Sie sie aus
  5. Wir erstellen einen Benutzer in der Datenbank (Sie können sofort eine Datenbank erstellen)
  6. Melden Sie sich als neuer Benutzer an
  7. Installieren Sie rbenv und Ruby
  8. Installieren des Bundlers
  9. Hochladen des Anwendungscodes
  10. Starten des Puma-Servers

Darüber hinaus können die letzten Schritte mit capistrano durchgeführt werden. Zumindest kann es sofort Code in Release-Verzeichnisse kopieren, das Release bei erfolgreicher Bereitstellung mit einem Symlink wechseln, Konfigurationen aus einem freigegebenen Verzeichnis kopieren, Puma neu starten usw. All dies ist mit Ansible möglich, aber warum?

Dateistruktur

Ansible hat streng Dateistruktur für alle Ihre Dateien, daher ist es am besten, alles in einem separaten Verzeichnis aufzubewahren. Darüber hinaus ist es nicht so wichtig, ob es sich um die Schienenanwendung selbst oder separat handelt. Sie können Dateien in einem separaten Git-Repository speichern. Persönlich fand ich es am praktischsten, ein Ansible-Verzeichnis im /config-Verzeichnis der Rails-Anwendung zu erstellen und alles in einem Repository zu speichern.

Einfaches Playbook

Playbook ist eine YML-Datei, die mithilfe einer speziellen Syntax beschreibt, was Ansible tun soll und wie. Lassen Sie uns das erste Playbook erstellen, das nichts tut:

---
- name: Simple playbook
  hosts: all

Hier sagen wir einfach, dass unser Playbook aufgerufen wird Simple Playbook und dass sein Inhalt für alle Hosts ausgeführt werden sollte. Wir können es im Verzeichnis /ansible unter dem Namen speichern playbook.yml und versuchen Sie Folgendes auszuführen:

ansible-playbook ./playbook.yml

PLAY [Simple Playbook] ************************************************************************************************************************************
skipping: no hosts matched

Ansible sagt, dass es keine Hosts kennt, die mit der All-Liste übereinstimmen. Sie müssen in einem Sonderangebot aufgeführt sein Inventardatei.

Erstellen wir es im selben Ansible-Verzeichnis:

123.123.123.123

So geben wir einfach den Host an (idealerweise den Host unseres VPS zum Testen, Sie können auch localhost registrieren) und speichern ihn unter dem Namen inventory.
Sie können versuchen, Ansible mit einer Inventardatei auszuführen:

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

TASK [Gathering Facts] ************************************************************************************************************************************

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

Wenn Sie SSH-Zugriff auf den angegebenen Host haben, stellt Ansible eine Verbindung her und sammelt Informationen über das Remote-System. (Standard-TASK [Fakten sammeln]) und anschließend einen kurzen Bericht über die Ausführung (PLAY RECAP).

Standardmäßig verwendet die Verbindung den Benutzernamen, unter dem Sie im System angemeldet sind. Es wird höchstwahrscheinlich nicht auf dem Host liegen. In der Playbook-Datei können Sie mithilfe der remote_user-Direktive angeben, welcher Benutzer für die Verbindung verwendet werden soll. Außerdem sind Informationen über ein Remote-System für Sie oft unnötig und Sie sollten keine Zeit damit verschwenden, sie zu sammeln. Diese Aufgabe kann auch deaktiviert werden:

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

Versuchen Sie erneut, das Playbook auszuführen, und stellen Sie sicher, dass die Verbindung funktioniert. (Wenn Sie den Root-Benutzer angegeben haben, müssen Sie auch die Direktive „bewerbe: true“ angeben, um erhöhte Rechte zu erhalten. Wie in der Dokumentation geschrieben: become set to ‘true’/’yes’ to activate privilege escalation. obwohl nicht ganz klar ist, warum).

Möglicherweise erhalten Sie eine Fehlermeldung, die darauf zurückzuführen ist, dass Ansible den Python-Interpreter nicht ermitteln kann. Sie können ihn dann manuell angeben:

ansible_python_interpreter: /usr/bin/python3 

Mit dem Befehl können Sie herausfinden, wo Sie Python haben whereis python.

Systempakete installieren

Die Standarddistribution von Ansible enthält viele Module für die Arbeit mit verschiedenen Systempaketen, sodass wir aus keinem Grund Bash-Skripte schreiben müssen. Jetzt benötigen wir eines dieser Module, um das System zu aktualisieren und Systempakete zu installieren. Ich habe Ubuntu Linux auf meinem VPS, also verwende ich zum Installieren von Paketen apt-get и Modul dafür. Wenn Sie ein anderes Betriebssystem verwenden, benötigen Sie möglicherweise ein anderes Modul (denken Sie daran, ich habe am Anfang gesagt, dass wir im Voraus wissen müssen, was und wie wir tun werden). Die Syntax wird jedoch höchstwahrscheinlich ähnlich sein.

Ergänzen wir unser Playbook mit den ersten Aufgaben:

---
- 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

Aufgabe ist genau die Aufgabe, die Ansible auf Remote-Servern ausführt. Wir geben der Aufgabe einen Namen, damit wir ihre Ausführung im Protokoll verfolgen können. Und wir beschreiben anhand der Syntax eines bestimmten Moduls, was es tun muss. In diesem Fall apt: update_cache=yes - sagt, dass Systempakete mit dem apt-Modul aktualisiert werden sollen. Der zweite Befehl ist etwas komplizierter. Wir übergeben eine Liste von Paketen an das apt-Modul und sagen, dass dies der Fall ist state soll werden present, das heißt, wir sagen, diese Pakete installieren. Auf ähnliche Weise können wir sie anweisen, sie zu löschen oder sie durch einfache Änderung zu aktualisieren state. Bitte beachten Sie, dass wir das Paket postgresql-contrib benötigen, damit Rails mit Postgresql funktionieren, das wir jetzt installieren. Auch hier müssen Sie dies wissen und tun; Ansible allein wird dies nicht tun.

Versuchen Sie erneut, das Playbook auszuführen, und überprüfen Sie, ob die Pakete installiert sind.

Neue Benutzer erstellen.

Um mit Benutzern zu arbeiten, verfügt Ansible auch über ein Modul – Benutzer. Fügen wir noch eine Aufgabe hinzu (ich habe die bereits bekannten Teile des Playbooks hinter den Kommentaren versteckt, um es nicht jedes Mal komplett zu kopieren):

---
- name: Simple playbook
  # ...
  tasks:
    # ...
    - name: Add a new user
      user:
        name: my_user
        shell: /bin/bash
        password: "{{ 123qweasd | password_hash('sha512') }}"

Wir erstellen einen neuen Benutzer, legen eine Shell und ein Passwort dafür fest. Und dann stoßen wir auf mehrere Probleme. Was ist, wenn Benutzernamen für verschiedene Hosts unterschiedlich sein müssen? Und das Passwort im Klartext im Playbook zu speichern, ist eine sehr schlechte Idee. Lassen Sie uns zunächst den Benutzernamen und das Passwort in Variablen einfügen, und gegen Ende des Artikels werde ich zeigen, wie man das Passwort verschlüsselt.

---
- name: Simple playbook
  # ...
  tasks:
    # ...
    - name: Add a new user
      user:
        name: "{{ user }}"
        shell: /bin/bash
        password: "{{ user_password | password_hash('sha512') }}"

Variablen werden in Playbooks mithilfe doppelter geschweifter Klammern festgelegt.

Wir geben die Werte der Variablen in der Inventardatei an:

123.123.123.123

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

Bitte beachten Sie die Richtlinie [all:vars] - Es heißt, dass der nächste Textblock aus Variablen (vars) besteht und diese auf alle Hosts (all) anwendbar sind.

Auch das Design ist interessant "{{ user_password | password_hash('sha512') }}". Die Sache ist, dass Ansible den Benutzer nicht über installiert user_add als ob Sie es manuell tun würden. Und es speichert alle Daten direkt, weshalb wir das Passwort auch vorher in einen Hash umwandeln müssen, was dieser Befehl macht.

Fügen wir unseren Benutzer zur Sudo-Gruppe hinzu. Zuvor müssen wir jedoch sicherstellen, dass eine solche Gruppe existiert, da dies niemand für uns tun wird:

---
- name: Simple playbook
  # ...
  tasks:
    # ...
    - 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"

Alles ist ganz einfach, wir haben auch ein Gruppenmodul zum Erstellen von Gruppen, mit einer Syntax, die apt sehr ähnlich ist. Dann reicht es aus, diese Gruppe beim Benutzer zu registrieren (groups: "sudo").
Es ist auch nützlich, diesem Benutzer einen SSH-Schlüssel hinzuzufügen, damit wir uns damit ohne Passwort anmelden können:

---
- name: Simple playbook
  # ...
  tasks:
    # ...
    - 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

In diesem Fall ist das Design interessant "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" – Es kopiert den Inhalt der Datei id_rsa.pub (Ihr Name kann anders sein), also den öffentlichen Teil des SSH-Schlüssels, und lädt ihn in die Liste der autorisierten Schlüssel für den Benutzer auf dem Server hoch.

Rollen

Alle drei Aufgaben zur Nutzungserstellung lassen sich problemlos in eine Aufgabengruppe einteilen, und es wäre sinnvoll, diese Gruppe getrennt vom Haupt-Playbook zu speichern, damit sie nicht zu groß wird. Zu diesem Zweck hat Ansible Rollen.
Gemäß der eingangs angegebenen Dateistruktur müssen Rollen in einem separaten Rollenverzeichnis abgelegt werden. Für jede Rolle gibt es ein separates Verzeichnis mit demselben Namen, innerhalb des Verzeichnisses Aufgaben, Dateien, Vorlagen usw
Lassen Sie uns eine Dateistruktur erstellen: ./ansible/roles/user/tasks/main.yml (main ist die Hauptdatei, die geladen und ausgeführt wird, wenn eine Rolle mit dem Playbook verbunden wird; andere Rollendateien können damit verbunden werden). Nun können Sie alle Aufgaben rund um den Benutzer in diese Datei übertragen:

# 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

Im Haupt-Playbook müssen Sie Folgendes angeben, um die Benutzerrolle zu verwenden:

---
- name: Simple playbook
  hosts: all
  remote_user: root
  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

  roles:
    - user

Außerdem kann es sinnvoll sein, das System vor allen anderen Aufgaben zu aktualisieren; dazu können Sie den Block umbenennen tasks in dem sie definiert sind pre_tasks.

Nginx einrichten

Wir sollten Nginx bereits installiert haben; wir müssen es konfigurieren und ausführen. Machen wir es gleich in der Rolle. Lassen Sie uns eine Dateistruktur erstellen:

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

Jetzt brauchen wir Dateien und Vorlagen. Der Unterschied besteht darin, dass Ansible die Dateien direkt kopiert, so wie sie sind. Und Vorlagen müssen die Erweiterung j2 haben und können variable Werte mit denselben doppelten geschweiften Klammern verwenden.

Lassen Sie uns Nginx aktivieren main.yml Datei. Dafür haben wir ein systemd-Modul:

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

Hier sagen wir nicht nur, dass Nginx gestartet werden muss (das heißt, wir starten es), sondern wir sagen auch sofort, dass es aktiviert werden muss.
Nun kopieren wir die Konfigurationsdateien:

# 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'

Wir erstellen die Haupt-Nginx-Konfigurationsdatei (Sie können sie direkt vom Server übernehmen oder selbst schreiben). Und auch die Konfigurationsdatei für unsere Anwendung im Verzeichnis „sites_available“ (dies ist nicht notwendig, aber nützlich). Im ersten Fall verwenden wir das Kopiermodul zum Kopieren von Dateien (die Datei muss in /ansible/roles/nginx/files/nginx.conf). Im zweiten Schritt kopieren wir die Vorlage und ersetzen die Werte der Variablen. Die Vorlage sollte vorhanden sein /ansible/roles/nginx/templates/my_app.j2). Und es könnte in etwa so aussehen:

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 }};
  ....
}

Achten Sie auf die Einlagen {{ app_name }}, {{ app_path }}, {{ server_name }}, {{ inventory_hostname }} – Dies sind alle Variablen, deren Werte Ansible vor dem Kopieren in die Vorlage einfügt. Dies ist nützlich, wenn Sie ein Playbook für verschiedene Hostgruppen verwenden. Beispielsweise können wir unsere Inventardatei hinzufügen:

[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

Wenn wir nun unser Playbook starten, führt es die angegebenen Aufgaben für beide Hosts aus. Gleichzeitig unterscheiden sich die Variablen für einen Staging-Host von denen für die Produktion, und zwar nicht nur in Rollen und Playbooks, sondern auch in Nginx-Konfigurationen. {{ inventory_hostname }} müssen nicht in der Inventardatei angegeben werden - dies spezielle Ansible-Variable und der Host, für den das Playbook gerade läuft, wird dort gespeichert.
Wenn Sie eine Inventardatei für mehrere Hosts haben möchten, diese aber nur für eine Gruppe ausführen möchten, können Sie dies mit dem folgenden Befehl tun:

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

Eine weitere Möglichkeit besteht darin, separate Inventardateien für verschiedene Gruppen zu erstellen. Oder Sie können die beiden Ansätze kombinieren, wenn Sie viele verschiedene Hosts haben.

Kehren wir zum Einrichten von Nginx zurück. Nach dem Kopieren der Konfigurationsdateien müssen wir in sitest_enabled einen symbolischen Link zu my_app.conf von „sites_available“ erstellen. Und starten Sie Nginx neu.

... # old code in mail.yml

- 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

Hier ist alles einfach – wieder Ansible-Module mit einer ziemlich standardmäßigen Syntax. Aber es gibt einen Punkt. Es macht keinen Sinn, Nginx jedes Mal neu zu starten. Ist Ihnen aufgefallen, dass wir keine Befehle wie „mach das so“ schreiben, die Syntax sieht eher so aus wie „dies sollte diesen Zustand haben“. Und meistens funktioniert Ansible genau so. Wenn die Gruppe bereits existiert oder das Systempaket bereits installiert ist, prüft Ansible dies und überspringt die Aufgabe. Außerdem werden Dateien nicht kopiert, wenn sie vollständig mit denen übereinstimmen, die sich bereits auf dem Server befinden. Wir können dies ausnutzen und Nginx nur dann neu starten, wenn die Konfigurationsdateien geändert wurden. Dafür gibt es eine Registeranweisung:

# 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

Wenn sich eine der Konfigurationsdateien ändert, wird eine Kopie erstellt und die Variable registriert restart_nginx. Und erst wenn diese Variable registriert wurde, wird der Dienst neu gestartet.

Und natürlich müssen Sie die Nginx-Rolle zum Haupt-Playbook hinzufügen.

Postgresql einrichten

Wir müssen Postgresql mit systemd auf die gleiche Weise aktivieren wie mit nginx und außerdem einen Benutzer erstellen, den wir für den Zugriff auf die Datenbank und die Datenbank selbst verwenden.
Lassen Sie uns eine Rolle erstellen /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 }}"

Ich werde nicht beschreiben, wie man Variablen zum Inventar hinzufügt, dies wurde bereits oft gemacht, ebenso wie die Syntax der Module postgresql_db und postgresql_user. Weitere Informationen finden Sie in der Dokumentation. Die interessanteste Richtlinie hier ist become_user: postgres. Tatsache ist, dass standardmäßig nur der Postgres-Benutzer Zugriff auf die Postgresql-Datenbank hat und zwar nur lokal. Diese Anweisung ermöglicht es uns, Befehle im Namen dieses Benutzers auszuführen (natürlich nur, wenn wir Zugriff darauf haben).
Außerdem müssen Sie möglicherweise eine Zeile zu pg_hba.conf hinzufügen, um einem neuen Benutzer Zugriff auf die Datenbank zu ermöglichen. Dies kann auf die gleiche Weise erfolgen, wie wir die Nginx-Konfiguration geändert haben.

Und natürlich müssen Sie die Postgresql-Rolle zum Haupt-Playbook hinzufügen.

Ruby über rbenv installieren

Ansible verfügt nicht über Module für die Arbeit mit rbenv, wird aber durch Klonen eines Git-Repositorys installiert. Daher ist dieses Problem das ungewöhnlichste. Lassen Sie uns eine Rolle für sie erstellen /ansible/roles/ruby_rbenv/main.yml und fangen wir an, es auszufüllen:

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

Wir verwenden erneut die Direktive „bewerbe_user“, um unter dem Benutzer zu arbeiten, den wir für diese Zwecke erstellt haben. Da rbenv in seinem Home-Verzeichnis und nicht global installiert ist. Und wir verwenden auch das Git-Modul, um das Repository zu klonen, indem wir repo und dest angeben.

Als nächstes müssen wir rbenv init in bashrc registrieren und dort rbenv zu PATH hinzufügen. Dafür haben wir das lineinfile-Modul:

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

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

Dann müssen Sie ruby_build installieren:

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

Und schließlich Ruby installieren. Dies geschieht über rbenv, also einfach mit dem Bash-Befehl:

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

Wir sagen, welcher Befehl womit ausgeführt werden soll. Allerdings stoßen wir hier auf die Tatsache, dass Ansible den in bashrc enthaltenen Code nicht ausführt, bevor die Befehle ausgeführt werden. Das bedeutet, dass rbenv direkt im selben Skript definiert werden muss.

Das nächste Problem ergibt sich aus der Tatsache, dass der Shell-Befehl aus Ansible-Sicht keinen Status hat. Das heißt, es wird nicht automatisch überprüft, ob diese Ruby-Version installiert ist oder nicht. Das können wir selbst machen:

- name: Install 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

Jetzt müssen Sie nur noch den Bundler installieren:

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

Und noch einmal: Fügen Sie unsere Rolle ruby_rbenv zum Haupt-Playbook hinzu.

Geteilte Dateien.

Im Allgemeinen könnte die Einrichtung hier abgeschlossen werden. Als nächstes müssen Sie nur noch capistrano ausführen und es kopiert den Code selbst, erstellt die erforderlichen Verzeichnisse und startet die Anwendung (sofern alles richtig konfiguriert ist). Capistrano erfordert jedoch häufig zusätzliche Konfigurationsdateien, wie z database.yml oder .env Sie können genau wie Dateien und Vorlagen für Nginx kopiert werden. Es gibt nur eine Feinheit. Bevor Sie Dateien kopieren, müssen Sie eine Verzeichnisstruktur für sie erstellen, etwa so:

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

Wir geben nur ein Verzeichnis an und Ansible erstellt bei Bedarf automatisch übergeordnete Verzeichnisse.

Ansible-Tresor

Wir haben bereits festgestellt, dass Variablen geheime Daten wie das Passwort des Benutzers enthalten können. Wenn Sie erstellt haben .env Datei für den Antrag und database.yml dann muss es noch mehr solcher kritischen Daten geben. Es wäre gut, sie vor neugierigen Blicken zu verbergen. Zu diesem Zweck wird es verwendet Ansible-Tresor.

Lassen Sie uns eine Datei für Variablen erstellen /ansible/vars/all.yml (Hier können Sie verschiedene Dateien für verschiedene Hostgruppen erstellen, genau wie in der Inventardatei: Production.yml, Staging.yml usw.).
Alle Variablen, die verschlüsselt werden müssen, müssen mithilfe der Standard-YML-Syntax in diese Datei übertragen werden:

# 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

Anschließend kann diese Datei mit dem Befehl verschlüsselt werden:

ansible-vault encrypt ./vars/all.yml

Natürlich müssen Sie beim Verschlüsseln ein Passwort für die Entschlüsselung festlegen. Sie können sehen, was sich in der Datei befindet, nachdem Sie diesen Befehl aufgerufen haben.

Durch ansible-vault decrypt Die Datei kann entschlüsselt, geändert und anschließend erneut verschlüsselt werden.

Sie müssen die Datei nicht entschlüsseln, damit sie funktioniert. Sie speichern es verschlüsselt und führen das Playbook mit dem Argument aus --ask-vault-pass. Ansible fragt nach dem Passwort, ruft die Variablen ab und führt die Aufgaben aus. Alle Daten bleiben verschlüsselt.

Der vollständige Befehl für mehrere Gruppen von Hosts und Ansible Vault sieht etwa so aus:

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

Aber ich werde Ihnen nicht den vollständigen Text der Spielbücher und Rollen geben, sondern schreiben Sie ihn selbst. Denn Ansible ist so: Wenn Sie nicht verstehen, was zu tun ist, wird es es nicht für Sie erledigen.

Source: habr.com

Kommentar hinzufügen