Konfiguracja serwera do wdrożenia aplikacji Rails za pomocą Ansible

Niedawno musiałem napisać kilka playbooków Ansible do przygotowania serwera do wdrożenia aplikacji Rails. Ku mojemu zaskoczeniu nie znalazłem prostego, krok po kroku przewodnika. Nie chciałem kopiować czyjegoś playbooka bez zrozumienia, co się dzieje, więc w końcu musiałem przeczytać dokumentację, zbierając wszystko samodzielnie. Być może mogę pomóc komuś przyspieszyć ten proces za pomocą tego artykułu.

Na początku warto zrozumieć, że Ansible zapewnia wygodny interfejs do wykonywania wcześniej określonej listy działań na zdalnym serwerze (serwerach) przez SSH. Nie ma w tym żadnej magii, nie można zainstalować wtyczki i uzyskać gotowego wdrożenia aplikacji z zerowym przestojem w pracy z Dockerem, monitoringiem i innymi dodatkami. Aby napisać playbook, musisz wiedzieć, co dokładnie chcesz zrobić i jak to zrobić. Dlatego nie interesują mnie gotowe playbooki z GitHuba ani artykuły typu: „Skopiuj i uruchom, będzie działać.”

Czego potrzebujemy?

Jak już mówiłem, aby napisać playbook, musisz wiedzieć, co chcesz zrobić i jak to zrobić. Zdefiniujmy, czego potrzebujemy. Dla aplikacji Rails będziemy potrzebować kilku pakietów systemowych: nginx, postgresql (redis itd.). Poza tym potrzebujemy Ruby w określonej wersji. Najlepiej zainstalować go przez rbenv (rvm, asdf…). Uruchamianie wszystkiego jako użytkownik root zawsze jest złym pomysłem, dlatego należy stworzyć osobnego użytkownika i skonfigurować jego uprawnienia. Następnie trzeba przesłać nasz kod na serwer, skopiować konfiguracje dla nginx, postgres itd. i uruchomić wszystkie te usługi.

W związku z tym kolejność działań będzie następująca:

  1. Logujemy się jako root
  2. instalujemy pakiety systemowe
  3. tworzymy nowego użytkownika, konfigurujemy uprawnienia i klucz ssh
  4. konfigurujemy pakiety systemowe (nginx itd.) i uruchamiamy je
  5. Tworzymy użytkownika w bazie danych (można również od razu stworzyć bazę)
  6. Logujemy się jako nowy użytkownik
  7. Instalujemy rbenv i Ruby
  8. Instalujemy bundler
  9. Przesyłamy kod aplikacji
  10. Uruchamiamy serwer Puma

Co więcej, ostatnie etapy można wykonać za pomocą Capistrano, przynajmniej potrafi ona domyślnie kopiować kod do katalogów wydania, przełączać dowiązanie symboliczne wydania przy udanym wdrożeniu, kopiować konfiguracje z katalogu shared, restartować Pumę itd. Można to wszystko zrobić za pomocą Ansible, ale po co?

Struktura plików

Ansible ma sztywną strukturę plików dla wszystkich swoich plików, dlatego najlepiej trzymać to w oddzielnym katalogu. Nie ma znaczenia, czy będzie on w samym aplikacji Rails, czy osobno. Pliki można przechowywać w osobnym repozytorium git. Osobiście najwygodniej mi było stworzyć katalog ansible w katalogu \/config aplikacji Rails i trzymać wszystko w jednym repozytorium.

Prosty Playbook

Playbook to plik yml, w którym za pomocą specjalnej składni opisane jest, co i jak ansible ma zrobić. Stwórzmy nasz pierwszy playbook, który nic nie robi:

---
- name: Prosty playbook
  hosts: all

Tutaj po prostu mówimy, że nasz playbook nazywa się Prosty Playbook i że jego zawartość ma być wykonana dla wszystkich hostów. Możemy go zapisać w katalogu \/ansible pod nazwą playbook.yml i spróbować go uruchomić:

ansible-playbook .\/playbook.yml

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

Ansible mówi, że nie zna hostów, które odpowiadają liście all. Trzeba je wymienić w specjalnym pliku inventory..

Stwórzmy go w tym samym katalogu ansible:

123.123.123.123

W ten sposób po prostu wskazujemy hosta (najlepiej swojego VPS do testów lub można wpisać localhost) i zapisujemy go pod nazwą inventory.
Można spróbować uruchomić ansible z plikiem inventory:

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

TASK [Zbieranie faktów] ************************************************************************************************************************************

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

Jeśli masz dostęp przez ssh do wskazanego hosta, to ansible połączy się i zbierze informacje o zdalnym systemie. (domyślne TASK [Zbieranie faktów]), po czym dostaniesz krótki raport o wykonaniu (PLAY RECAP).

Domyślnie do połączenia używane jest imię użytkownika, pod którym jesteś zalogowany w systemie. Na hoście prawdopodobnie nie będzie go. W pliku playbook można określić, jakiego użytkownika użyć do połączenia za pomocą dyrektywy remote_user. Informacje o zdalnym systemie mogą być również często zbędne i nie warto tracić czasu na ich zbieranie. Tę czynność można również wyłączyć:

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

Spróbuj ponownie uruchomić playbook i upewnij się, że połączenie działa. (Jeśli podałeś użytkownika root, musisz również wskazać dyrektywę become: true, aby uzyskać podwyższone uprawnienia, jak opisano w dokumentacji: become ustawione na ‘true’/’yes’, aby aktywować eskalację uprawnień. choć nie jest do końca jasne, po co to robić).

Możesz otrzymać błąd spowodowany tym, że ansible nie może określić interpretera Pythona; w takim przypadku możesz go podać ręcznie:

ansible_python_interpreter: /usr/bin/python3 

gdzie znajduje się Python, możesz dowiedzieć się za pomocą polecenia whereis python.

Instalacja pakietów systemowych

W standardowej wersji Ansible znajduje się wiele modułów do pracy z różnymi pakietami systemowymi, dzięki czemu nie musimy pisać skryptów bash w każdej sytuacji. Teraz potrzebujemy jednego z takich modułów do aktualizacji systemu i instalacji pakietów. Na moim VPS działa Ubuntu Linux, dlatego do instalacji pakietów używam apt-get i modułu dla niego. Jeśli używasz innego systemu operacyjnego, może być potrzebny inny moduł (pamiętaj, że na początku mówiłem, że musimy z góry wiedzieć, co i jak będziemy robić). Jednak składnia prawdopodobnie będzie podobna.

Uzupełnijmy nasz playbook o pierwsze zadania:

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

  tasks:
    - name: Zaktualizuj system
      apt: update_cache=yes
    - name: Zainstaluj zależności systemowe
      apt:
        name: git,nginx,redis,postgresql,postgresql-contrib
        state: present

Zadanie — to właśnie zadanie, które ansible będzie wykonywać na zdalnych serwerach. Dajemy zadaniu nazwę, aby śledzić jego wykonanie w logach. Opisujemy, za pomocą składni konkretnego modułu, co należy zrobić. W tym przypadku apt: update_cache=yes — mówi, aby zaktualizować pakiety systemowe za pomocą modułu apt. Druga komenda jest nieco bardziej skomplikowana. Przekazujemy do modułu apt listę pakietów i mówimy, że ich stan powinno być obecny, czyli mówimy, aby zainstalować te pakiety. W podobny sposób możemy powiedzieć, aby je usunąć lub zaktualizować, po prostu zmieniając stan. Zauważ, że do pracy rails z postgresql potrzebujemy pakietu postgresql-contrib, który teraz instalujemy. O tym również musimy wiedzieć i zrobić, ansible sam tego nie zrobi.

Spróbuj uruchomić playbook jeszcze raz i sprawdzić, czy pakiety zostaną zainstalowane.

Tworzenie nowych użytkowników.

Aby pracować z użytkownikami, Ansible ma również moduł — user. Dodajmy jeszcze jedno zadanie (ukryłem już znane części playbooka w komentarzach, aby nie kopiować go w całości za każdym razem):

---
- name: Prosty playbook
  # ...
  tasks:
    # ...
    - name: Dodaj nowego użytkownika
      user:
        name: my_user
        shell: /bin/bash
        password: "{{ 123qweasd | password_hash('sha512') }}"

Tworzymy nowego użytkownika, ustawiamy mu powłokę i hasło. I tutaj natrafiamy na kilka problemów. Co jeśli nazwy użytkowników muszą być różne dla różnych hostów? Przechowywanie hasła w otwartej postaci w playbooku to bardzo zły pomysł. Na początek przeniesiemy nazwę użytkownika i hasło do zmiennych, a pod koniec artykułu pokażę, jak hasło zaszyfrować.

---
- name: Prosty playbook
  # ...
  tasks:
    # ...
    - name: Dodaj nowego użytkownika
      user:
        name: "{{ user }}"
        shell: /bin/bash
        password: "{{ user_password | password_hash('sha512') }}"

W playbookach zmienne są ustalane za pomocą podwójnych klamerek.

Wartości zmiennych określimy w pliku inventory:

123.123.123.123

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

Zwróć uwagę na dyrektywę [all:vars] — oznacza, że następny blok tekstu to zmienne (vars) i będą one stosowane do wszystkich hostów (all).

Również interesująca jest konstrukcja "{{ user_password | password_hash('sha512') }}". Rzecz w tym, że ansible nie tworzy użytkowników za pomocą user_add jak byś to zrobił ręcznie. Zapisuje wszystkie dane bezpośrednio, dlatego hasło również musimy wcześniej przekształcić w hasz, co robi ta komenda.

Dodajmy naszego użytkownika do grupy sudo. Jednak przed tym należy upewnić się, że taka grupa istnieje, ponieważ nikt za nas tego nie zrobi:

---
- name: Prosty playbook
  # ...
  tasks:
    # ...
    - name: Upewnij się, że grupa 'sudo' istnieje
      group:
        name: sudo
        state: present
    - name: Dodaj nowego użytkownika
      user:
        name: "{{ user }}"
        shell: /bin/bash
        password: "{{ user_password | password_hash('sha512') }}"
        groups: "sudo"

Wszystko jest dość proste, mamy również moduł group do tworzenia grup, z syntaksą bardzo podobną do apt. Po tym wystarczy przypisać tę grupę użytkownikowi (groups: "sudo").
Również warto dodać temu użytkownikowi klucz ssh, abyśmy mogli się pod nim logować bez hasła:

---
- name: Prosty playbook
  # ...
  tasks:
    # ...
    - name: Upewnij się, że grupa 'sudo' istnieje
      group:
      name: sudo
        state: present
    - name: Dodaj nowego użytkownika
      user:
        name: "{{ user }}"
        shell: /bin/bash
        password: "{{ user_password | password_hash('sha512') }}"
        groups: "sudo"
    - name: Wdróż klucz SSH
      authorized_key:
        user: "{{ user }}"
        key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
        state: present

W tym przypadku interesująca jest konstrukcja "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" — kopiuje zawartość pliku id_rsa.pub (może on mieć inną nazwę), czyli publiczną część klucza SSH i dodaje go do listy autoryzowanych kluczy dla użytkownika na serwerze.

Role

Wszystkie trzy zadania związane z tworzeniem użytkownika można łatwo przyporządkować do jednej grupy zadań i dobrze byłoby przechowywać tę grupę oddzielnie od głównego playbooka, aby nie stał się zbyt rozbudowany. W tym celu w Ansible istnieją role.
Zgodnie z podaną na początku strukturą plików, role należy umieścić w osobnym katalogu roles, dla każdej roli — osobny katalog o tej samej nazwie, w obrębie katalogu tasks, files, templates, itp.
Stworzymy strukturę plików: ./ansible/roles/user/tasks/main.yml (main — to główny plik, który będzie ładowany i wykonywany przy podłączaniu roli do playbooka, w nim można podłączać inne pliki roli). Teraz można przenieść do tego pliku wszystkie zadania związane z użytkownikiem:

# 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

W głównym playbooku należy również wskazać, aby używać roli user:

---
- name: Prosty playbook
  hosts: all
  remote_user: root
  gather_facts: no

  tasks:
    - name: Zaktualizuj system
      apt: update_cache=yes
    - name: Zainstaluj zależności systemowe
      apt:
        name: git,nginx,redis,postgresql,postgresql-contrib
        state: present

  roles:
    - user

Możliwe, że warto także wykonać aktualizację systemu przed wszystkimi innymi zadaniami, dlatego można zmienić nazwę bloku tasks w którym są one zdefiniowane na pre_tasks.

Konfiguracja nginx

Nginx powinien być już zainstalowany, należy go skonfigurować i uruchomić. Zróbmy to od razu w roli. Tworzymy strukturę plików:

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

Teraz będą nam potrzebne pliki i szablony. Różnica między nimi polega na tym, że pliki Ansible kopiują bezpośrednio, tak jak są. A szablony powinny mieć rozszerzenie j2 i można w nich używać wartości zmiennych przy pomocy tych samych podwójnych nawiasów klamrowych.

Dodajmy nginx do main.yml pliku. W tym celu mamy moduł systemd:

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

Tutaj nie tylko mówimy, że nginx ma być started (czyli uruchamiamy go), ale również od razu informujemy, że powinien być enabled.
Teraz skopiujemy pliki konfiguracyjne:

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

Tworzymy główny plik konfiguracyjny nginx (można go pobrać bezpośrednio z serwera lub napisać samodzielnie). Również plik konfiguracyjny dla naszej aplikacji w katalogu sites_available (to nie jest konieczne, ale przydatne). W pierwszym przypadku używamy modułu copy do kopiowania plików (plik musi znajdować się w /ansible/roles/nginx/files/nginx.conf). W drugim przypadku kopiujemy szablon, podstawiając wartości zmiennych. Szablon musi znajdować się w /ansible/roles/nginx/templates/my_app.j2). I może wyglądać mniej więcej tak:

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

Zauważ, że wstawki {{ app_name }}, {{ app_path }}, {{ server_name }}, {{ inventory_hostname }} — to wszystkie zmienne, których wartości ansible podstawia do szablonu przed skopiowaniem. Jest to przydatne, jeśli korzystamy z playbooka dla różnych grup hostów. Na przykład możemy uzupełnić nasz plik 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

Jeśli teraz uruchomimy nasz playbook, to wykona podane zadania dla obu hostów. Jednak dla hosta staging zmienne będą różne od production, i to nie tylko w rolach i playbookach, ale także w konfiguracjach nginx. {{ inventory_hostname }} nie trzeba podawać w pliku inventory — to jest specjalna zmienna ansible i tam przechowywany jest host, dla którego w danej chwili wykonywany jest playbook.
Jeśli chcesz mieć plik inventory dla kilku hostów, a uruchomić tylko dla jednej grupy, można to zrobić następującą komendą:

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

inną opcją jest posiadanie oddzielnych plików inventory dla różnych grup. Lub można połączyć dwa podejścia, jeśli masz wiele różnych hostów.

Wróćmy do konfiguracji nginx. Po skopiowaniu plików konfiguracyjnych musimy utworzyć symlink w sites_enabled na my_app.conf z sites_available. I zrestartować nginx.

... # stary kod w mail.yml

- name: Utwórz symlink do 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

Tutaj wszystko jest proste — ponownie moduły ansible z dość standardową składnią. Ale jest jeden ważny punkt. Nie ma sensu restartować nginx za każdym razem. Zauważyłeś, że nie piszemy komend w stylu: „zrób to tak”, składnia wygląda raczej jak „to powinno mieć taki a nie inny stan”. I najczęściej właśnie tak działa ansible. Jeśli grupa już istnieje lub pakiet systemowy jest już zainstalowany, to ansible to sprawdzi i pominie zadanie. Podobnie pliki nie będą kopiowane, jeśli w pełni pokrywają się z tym, co już jest na serwerze. Możemy z tego skorzystać i restartować nginx tylko wtedy, gdy pliki konfiguracyjne zostały zmienione. W tym celu istnieje dyrektywa 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

Jeśli jeden z plików konfiguracyjnych ulegnie zmianie, to zostanie wykonana operacja kopiowania i zarejestrowana zmienna restart_nginx. I tylko wtedy, gdy ta zmienna została zarejestrowana, zostanie wykonany restart usługi.

No i oczywiście, trzeba dodać rolę nginx do głównego playbooka.

Konfiguracja postgresql

Musimy włączyć postgresql za pomocą systemd w taki sam sposób, jak robiliśmy to z nginx, a także stworzyć użytkownika, którego będziemy używać do uzyskania dostępu do bazy danych oraz samą bazę danych.
Stwórzmy rolę /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 }}"

Nie będę szczegółowo opisywać, jak dodawać zmienne do inventory, to już było robione wielokrotnie, podobnie jak składnia modułów postgresql_db i postgresql_user. Więcej informacji można znaleźć w dokumentacji. Tutaj najbardziej interesująca jest dyrektywa become_user: postgres. Fakt jest taki, że domyślnie dostęp do bazy postgresql ma tylko użytkownik postgres i to tylko lokalnie. Ta dyrektywa pozwala nam wykonywać komendy w imieniu tego użytkownika (o ile mamy do niego dostęp).
Również być może będziesz musiał dodać linię w pg_hba.conf, aby otworzyć dostęp nowemu użytkownikowi do bazy. Można to zrobić tak, jak zmienialiśmy konfigurację nginx.

No i oczywiście trzeba dodać rolę postgresql do głównego playbooka.

Instalacja ruby przez rbenv

W ansible nie ma modułów do pracy z rbenv, a instaluje się go przez klonowanie repozytorium gita. Dlatego to zadanie staje się najbardziej niestandardowe. Stwórzmy dla niego rolę /ansible/roles/ruby_rbenv/main.yml i zaczniemy ją wypełniać:

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

Ponownie używamy dyrektywy become_user, aby działać z utworzonego przez nas na ten cel użytkownika. Ponieważ rbenv jest instalowany w jego katalogu domowym, a nie globalnie. Używamy także modułu git, aby sklonować repozytorium, podając repo i dest.

Następnie musimy dodać rbenv init do bashrc i tam też umieścić rbenv w PATH. W tym celu mamy moduł lineinfile:

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

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

Po tym należy zainstalować ruby_build:

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

I wreszcie zainstalować ruby. Można to zrobić za pomocą rbenv, to znaczy po prostu za pomocą komendy bash:

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

Mówimy, jaką komendę wykonać i czym. Jednak napotykamy problem, że ansible nie uruchamia kodu zawartego w bashrc przed uruchomieniem komend. Oznacza to, że rbenv będzie musiał być definiowany bezpośrednio w tym samym skrypcie.

Następny problem polega na tym, że komenda shell nie ma stanu z perspektywy ansible. Oznacza to, że nie będzie automatycznej weryfikacji, czy ta wersja ruby jest zainstalowana, czy nie — musimy to zrobić ręcznie:

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

A teraz pozostaje zainstalować bundler:

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

I ponownie dodać naszą rolę ruby_rbenv do głównego playbooka.

Pliki udostępnione.

Ogólnie rzecz biorąc, można by na tym zakończyć konfigurację. Następnie pozostaje uruchomić capistrano, a ono samo skopiuje kod, utworzy potrzebne katalogi i uruchomi aplikację (jeśli wszystko jest poprawnie skonfigurowane). Jednak często capistrano potrzebuje dodatkowych plików konfiguracyjnych, takich jak database.yml lub .env Można je skopiować na dokładnie takiej samej zasadzie jak pliki i szablony dla nginx. Jest tylko jedna drobność. Przed skopiowaniem plików należy utworzyć dla nich strukturę katalogów, coś w rodzaju:

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

podajemy tylko jeden katalog, a ansible automatycznie utworzy rodzicielskie, jeśli to konieczne.

Ansible Vault

Już napotkaliśmy na to, że zmienne mogą zawierać dane wrażliwe, takie jak hasło użytkownika. Jeśli stworzyłeś .env plik dla aplikacji, a database.yml to powinno być tam jeszcze więcej takich krytycznych danych. Dobrze byłoby je ukryć przed obcymi oczami. Do tego służy ansible vault.

Stworzymy plik dla zmiennych /ansible/vars/all.yml (można tworzyć różne pliki dla różnych grup hostów, tak jak w pliku inventory: production.yml, staging.yml itd.).
Do tego pliku należy przenieść wszystkie zmienne, które powinny być zaszyfrowane, używając standardowej składni 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

Następnie plik ten można zaszyfrować poleceniem:

ansible-vault encrypt ./vars/all.yml

Oczywiście podczas szyfrowania trzeba ustawić hasło do deszyfrowania. Możesz zobaczyć, co znajdzie się w pliku po wywołaniu tego polecenia.

Za pomocą ansible-vault decrypt plik można deszyfrować, zmienić i potem ponownie zaszyfrować.

Do pracy nie musisz deszyfrować pliku. Przechowujesz go w zaszyfrowanej formie i uruchamiasz playbook z argumentem --ask-vault-pass. Ansible zapyta o hasło, pobierze zmienne i wykona zadania. Wszystkie dane pozostaną zaszyfrowane.

Komenda dla kilku grup hostów i ansible vault będzie wyglądać mniej więcej tak:

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

A pełny tekst playbooków i ról Ci nie dam, pisz sam. Bo ansible jest taki — jeśli nie rozumiesz, co trzeba zrobić, to on również nic nie zrobi.

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster