Konfigurowanie serwera do wdrażania aplikacji Railsowych przy użyciu Ansible

Niedawno musiałem napisać kilka podręczników Ansible, aby przygotować serwer do wdrożenia aplikacji Railsowej. I, co zaskakujące, nie znalazłem prostej instrukcji krok po kroku. Nie chciałem kopiować cudzego poradnika bez zrozumienia co się dzieje i w końcu musiałem zapoznać się z dokumentacją, zbierając wszystko sam. Być może dzięki temu artykułowi pomogę komuś przyspieszyć ten proces.

Pierwszą rzeczą, którą należy zrozumieć, jest to, że ansible zapewnia wygodny interfejs do wykonywania predefiniowanej listy działań na zdalnych serwerach za pośrednictwem SSH. Nie ma tu żadnej magii, nie można zainstalować wtyczki i wdrożyć aplikację bez przestojów z dokiem, monitorowaniem i innymi dodatkami od razu po wyjęciu z pudełka. Aby napisać poradnik, musisz wiedzieć, co dokładnie chcesz zrobić i jak to zrobić. Dlatego nie zadowalają mnie gotowe poradniki z GitHuba, czy artykuły typu: „Skopiuj i uruchom, zadziała”.

Czego potrzebujemy?

Jak już mówiłem, aby napisać poradnik, musisz wiedzieć, co chcesz zrobić i jak to zrobić. Zdecydujmy, czego potrzebujemy. Do aplikacji Railsowej będziemy potrzebować kilku pakietów systemowych: nginx, postgresql (redis itp.). Ponadto potrzebujemy konkretnej wersji Ruby. Najlepiej zainstalować go poprzez rbenv (rvm, asdf...). Uruchamianie tego wszystkiego jako użytkownik root jest zawsze złym pomysłem, dlatego musisz utworzyć osobnego użytkownika i skonfigurować jego uprawnienia. Następnie musisz wgrać nasz kod na serwer, skopiować konfiguracje dla nginx, postgres itp. i uruchomić wszystkie te usługi.

W rezultacie sekwencja działań jest następująca:

  1. Zaloguj się jako root
  2. zainstaluj pakiety systemowe
  3. utwórz nowego użytkownika, skonfiguruj uprawnienia, klucz ssh
  4. skonfiguruj pakiety systemowe (nginx itp.) i uruchom je
  5. Tworzymy użytkownika w bazie danych (można od razu stworzyć bazę danych)
  6. Zaloguj się jako nowy użytkownik
  7. Zainstaluj rbenv i Ruby
  8. Instalowanie pakietu
  9. Przesyłanie kodu aplikacji
  10. Uruchomienie serwera Puma

Co więcej, ostatnie etapy można wykonać za pomocą Capistrano, przynajmniej od razu po wyjęciu z pudełka może skopiować kod do katalogów wydań, przełączyć wydanie za pomocą dowiązania symbolicznego po pomyślnym wdrożeniu, skopiować konfiguracje ze współdzielonego katalogu, zrestartować pumę itp. Wszystko to można zrobić za pomocą Ansible, ale po co?

Struktura pliku

Ansible ma ścisłe struktura plików dla wszystkich plików, dlatego najlepiej trzymać je wszystkie w oddzielnym katalogu. Co więcej, nie jest aż tak istotne, czy będzie to znajdowało się w samej aplikacji Railsowej, czy też osobno. Możesz przechowywać pliki w oddzielnym repozytorium git. Osobiście najwygodniej było utworzyć katalog ansible w katalogu /config aplikacji Rails i przechowywać wszystko w jednym repozytorium.

Prosty podręcznik

Playbook to plik yml, który przy użyciu specjalnej składni opisuje, co i jak powinien zrobić Ansible. Stwórzmy pierwszy podręcznik, który nic nie robi:

---
- name: Simple playbook
  hosts: all

Tutaj po prostu mówimy, że nasz podręcznik nazywa się Simple Playbook i że jego zawartość powinna zostać wykonana dla wszystkich hostów. Możemy zapisać go w katalogu /ansible pod nazwą playbook.yml i spróbuj uruchomić:

ansible-playbook ./playbook.yml

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

Ansible twierdzi, że nie zna żadnych hostów pasujących do listy wszystkich. Muszą być wymienione w specjalnym plik inwentarza.

Utwórzmy go w tym samym katalogu ansible:

123.123.123.123

W ten sposób po prostu określamy hosta (najlepiej hosta naszego VPS do testów lub możesz zarejestrować hosta lokalnego) i zapisujemy go pod nazwą inventory.
Możesz spróbować uruchomić ansible z plikiem invetory:

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

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

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

Jeśli masz dostęp SSH do określonego hosta, ansible połączy się i zbierze informacje o systemie zdalnym. (domyślne ZADANIE [Zbieranie faktów]), po czym zda krótki raport z wykonania (PODSTAWIENIE GRY).

Domyślnie połączenie wykorzystuje nazwę użytkownika, pod którą jesteś zalogowany do systemu. Najprawdopodobniej nie będzie go na hoście. W pliku playbook możesz określić, którego użytkownika chcesz użyć do połączenia, używając dyrektywy Remote_user. Ponadto informacje o zdalnym systemie często mogą być dla Ciebie niepotrzebne i nie powinieneś tracić czasu na ich zbieranie. To zadanie można również wyłączyć:

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

Spróbuj ponownie uruchomić podręcznik i upewnij się, że połączenie działa. (Jeśli określiłeś użytkownika root, musisz także określić dyrektywę get: true, aby uzyskać podwyższone uprawnienia. Jak napisano w dokumentacji: become set to ‘true’/’yes’ to activate privilege escalation. choć nie do końca wiadomo dlaczego).

Być może pojawi się błąd spowodowany faktem, że ansible nie może określić interpretera Pythona, wtedy możesz określić go ręcznie:

ansible_python_interpreter: /usr/bin/python3 

Możesz dowiedzieć się, gdzie masz Pythona za pomocą polecenia whereis python.

Instalowanie pakietów systemowych

Standardowa dystrybucja Ansible zawiera wiele modułów do pracy z różnymi pakietami systemowymi, dzięki czemu nie musimy z żadnego powodu pisać skryptów bashowych. Teraz potrzebujemy jednego z tych modułów, aby zaktualizować system i zainstalować pakiety systemowe. Mam Ubuntu Linux na moim VPS, więc do instalacji pakietów używam apt-get и moduł do tego. Jeśli korzystasz z innego systemu operacyjnego, to być może będziesz potrzebować innego modułu (pamiętaj, mówiłem na początku, że musimy wiedzieć z góry, co i jak będziemy robić). Jednak składnia będzie najprawdopodobniej podobna.

Uzupełnijmy nasz poradnik o pierwsze zadania:

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

Zadanie to dokładnie zadanie, które Ansible wykona na zdalnych serwerach. Nadajemy zadaniu nazwę, abyśmy mogli śledzić jego wykonanie w logu. I opisujemy, używając składni konkretnego modułu, co ma on zrobić. W tym przypadku apt: update_cache=yes - mówi, aby zaktualizować pakiety systemowe za pomocą modułu apt. Drugie polecenie jest nieco bardziej skomplikowane. Przekazujemy listę pakietów do modułu apt i mówimy, że tak state powinno stać się present, to znaczy, mówimy: zainstaluj te pakiety. W podobny sposób możemy kazać im je usunąć lub zaktualizować, po prostu zmieniając state. Pamiętaj, że aby Railsy mogły współpracować z postgresql, potrzebujemy pakietu postgresql-contrib, który właśnie instalujemy. Powtórzę: musisz to wiedzieć i zrobić; sam ansible tego nie zrobi.

Spróbuj ponownie uruchomić podręcznik i sprawdź, czy pakiety są zainstalowane.

Tworzenie nowych użytkowników.

Do pracy z użytkownikami Ansible ma również moduł - użytkownik. Dodajmy jeszcze jedno zadanie (znane już fragmenty poradnika ukryłem za komentarzami, żeby za każdym razem nie kopiować go w całości):

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

Tworzymy nowego użytkownika, ustalamy dla niego schel i hasło. I wtedy napotykamy kilka problemów. Co się stanie, jeśli nazwy użytkowników muszą być różne dla różnych hostów? A przechowywanie hasła w postaci zwykłego tekstu w podręczniku to bardzo zły pomysł. Na początek umieśćmy nazwę użytkownika i hasło w zmiennych, a pod koniec artykułu pokażę, jak zaszyfrować hasło.

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

Zmienne są ustawiane w podręcznikach za pomocą podwójnych nawiasów klamrowych.

Wskażemy wartości zmiennych w pliku inwentarzowym:

123.123.123.123

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

Proszę zwrócić uwagę na dyrektywę [all:vars] - mówi, że następny blok tekstu to zmienne (vars) i mają one zastosowanie do wszystkich hostów (all).

Projekt również jest ciekawy "{{ user_password | password_hash('sha512') }}". Rzecz w tym, że ansible nie instaluje użytkownika przez user_add tak jakbyś to zrobił ręcznie. I zapisuje wszystkie dane bezpośrednio, dlatego musimy również wcześniej przekonwertować hasło na skrót, co robi to polecenie.

Dodajmy naszego użytkownika do grupy sudo. Jednak wcześniej musimy się upewnić, że taka grupa istnieje, bo nikt za nas tego nie zrobi:

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

Wszystko jest dość proste, mamy też moduł group do tworzenia grup, o składni bardzo podobnej do apt. Następnie wystarczy zarejestrować tę grupę dla użytkownika (groups: "sudo").
Przydatne jest także dodanie do tego użytkownika klucza ssh, abyśmy mogli się nim logować bez hasła:

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

W tym przypadku projekt jest interesujący "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" — kopiuje zawartość pliku id_rsa.pub (Twoje imię może być inne), czyli część publiczną klucza ssh i przesyła ją na listę autoryzowanych kluczy użytkownika na serwerze.

Role

Wszystkie trzy zadania związane z tworzeniem użytku można łatwo zaklasyfikować do jednej grupy zadań i dobrym pomysłem byłoby przechowywanie tej grupy oddzielnie od głównego podręcznika, aby nie urosła za bardzo. W tym celu Ansible ma rola.
Zgodnie ze strukturą plików wskazaną na samym początku, role należy umieścić w osobnym katalogu ról, dla każdej roli istnieje oddzielny katalog o tej samej nazwie, wewnątrz katalogu zadań, plików, szablonów itp.
Stwórzmy strukturę plików: ./ansible/roles/user/tasks/main.yml (main to główny plik, który zostanie załadowany i wykonany, gdy rola zostanie połączona z podręcznikiem; można do niego podłączyć inne pliki ról). Teraz możesz przenieść wszystkie zadania związane z użytkownikiem do tego pliku:

# 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 podręczniku musisz określić, czy chcesz używać roli użytkownika:

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

Ponadto sensowna może być aktualizacja systemu przed wszystkimi innymi zadaniami; w tym celu możesz zmienić nazwę bloku tasks w którym są one zdefiniowane pre_tasks.

Konfiguracja nginxa

Powinniśmy już mieć zainstalowany Nginx, musimy go skonfigurować i uruchomić. Zróbmy to od razu w roli. Stwórzmy strukturę plików:

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

Teraz potrzebujemy plików i szablonów. Różnica między nimi polega na tym, że ansible kopiuje pliki bezpośrednio w niezmienionej postaci. A szablony muszą mieć rozszerzenie j2 i mogą używać wartości zmiennych przy użyciu tych samych podwójnych nawiasów klamrowych.

Włączmy nginx main.yml plik. W tym celu mamy moduł systemowy:

# 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 musi zostać uruchomiony (czyli go uruchamiamy), ale od razu mówimy, że należy go włączyć.
Skopiujmy teraz 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żesz pobrać go bezpośrednio z serwera lub napisać samodzielnie). A także plik konfiguracyjny naszej aplikacji w katalogu sites_available (nie jest to konieczne, ale przydatne). W pierwszym przypadku do kopiowania plików używamy modułu kopiowania (plik musi być w formacie /ansible/roles/nginx/files/nginx.conf). W drugim kopiujemy szablon, podstawiając wartości zmiennych. Szablon powinien być w środku /ansible/roles/nginx/templates/my_app.j2). A mogłoby to 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 }};
  ....
}

Zwróć uwagę na wstawki {{ app_name }}, {{ app_path }}, {{ server_name }}, {{ inventory_hostname }} — są to wszystkie zmienne, których wartości Ansible podstawi do szablonu przed skopiowaniem. Jest to przydatne, jeśli używasz podręcznika dla różnych grup hostów. Na przykład możemy dodać nasz plik inwentarza:

[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 podręcznik, wykona on określone zadania dla obu hostów. Ale jednocześnie w przypadku hosta pomostowego zmienne będą się różnić od zmiennych produkcyjnych, i to nie tylko pod względem ról i podręczników, ale także w konfiguracjach Nginx. {{ inventory_hostname }} nie muszą być określone w pliku inwentarza - to specjalna zmienna ansible i host, dla którego aktualnie działa podręcznik, jest tam przechowywany.
Jeśli chcesz mieć plik inwentarza dla kilku hostów, ale uruchamiany tylko dla jednej grupy, można to zrobić za pomocą następującego polecenia:

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

Inną opcją jest posiadanie oddzielnych plików inwentarza dla różnych grup. Możesz też połączyć te dwa podejścia, jeśli masz wielu różnych hostów.

Wróćmy do konfigurowania Nginx. Po skopiowaniu plików konfiguracyjnych musimy utworzyć dowiązanie symboliczne w sitest_enabled do my_app.conf z sites_available. I uruchom ponownie Nginx.

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

Tutaj wszystko jest proste - znowu moduły ansible o dość standardowej składni. Ale jest jeden punkt. Nie ma sensu restartować Nginx za każdym razem. Czy zauważyłeś, że nie piszemy poleceń typu: „zrób to w ten sposób”, składnia wygląda bardziej jak „to powinno mieć ten stan”. I najczęściej właśnie tak działa ansible. Jeśli grupa już istnieje lub pakiet systemowy jest już zainstalowany, ansible sprawdzi to i pominie zadanie. Ponadto pliki nie zostaną skopiowane, jeśli będą całkowicie zgodne z plikami już znajdującymi się na serwerze. Możemy to wykorzystać i ponownie uruchomić Nginx tylko wtedy, gdy pliki konfiguracyjne zostały zmienione. Służy do tego dyrektywa dotycząca rejestrów:

# 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żeli któryś z plików konfiguracyjnych ulegnie zmianie, zostanie utworzona kopia i zmienna zostanie zarejestrowana restart_nginx. I dopiero po zarejestrowaniu tej zmiennej usługa zostanie uruchomiona ponownie.

I oczywiście musisz dodać rolę nginx do głównego podręcznika.

Konfigurowanie postgresqla

Musimy włączyć postgresql przy użyciu systemd w taki sam sposób, jak zrobiliśmy to z nginx, a także utworzyć użytkownika, którego będziemy używać do uzyskiwania dostępu do bazy danych i samej bazy 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ę opisywał sposobu dodawania zmiennych do inwentarza, było to już robione wiele razy, podobnie jak składnia modułów postgresql_db i postgresql_user. Więcej informacji można znaleźć w dokumentacji. Najciekawszą dyrektywą jest tutaj become_user: postgres. Fakt jest taki, że domyślnie dostęp do bazy postgresql ma tylko użytkownik postgresql i tylko lokalnie. Ta dyrektywa pozwala nam wykonywać polecenia w imieniu tego użytkownika (oczywiście jeśli mamy dostęp).
Może być także konieczne dodanie linii do pg_hba.conf, aby umożliwić nowemu użytkownikowi dostęp do bazy danych. Można to zrobić w taki sam sposób, w jaki zmieniliśmy konfigurację nginx.

I oczywiście musisz dodać rolę postgresql do głównego podręcznika.

Instalowanie Ruby przez rbenv

Ansible nie ma modułów do pracy z rbenv, ale instaluje się go poprzez klonowanie repozytorium git. Dlatego problem ten staje się najbardziej niestandardowy. Stwórzmy dla niej rolę /ansible/roles/ruby_rbenv/main.yml i zacznijmy go 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 Bee_user do pracy z użytkownikiem, którego utworzyliśmy w tym celu. Ponieważ rbenv jest zainstalowany w swoim katalogu domowym, a nie globalnie. Używamy także modułu git do klonowania repozytorium, określając repo i dest.

Następnie musimy zarejestrować rbenv init w bashrc i tam dodać rbenv do PATH. W tym celu mamy moduł lineinfile:

- 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 -)"'

Następnie musisz zainstalować ruby_build:

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

I na koniec zainstaluj Ruby. Odbywa się to poprzez rbenv, czyli po prostu za pomocą polecenia bash:

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

Mówimy, które polecenie wykonać i za pomocą czego. Jednak tutaj spotykamy się z faktem, że ansible nie uruchamia kodu zawartego w bashrc przed uruchomieniem poleceń. Oznacza to, że rbenv będzie musiał zostać zdefiniowany bezpośrednio w tym samym skrypcie.

Następny problem wynika z faktu, że z punktu widzenia ansibla polecenie powłoki nie ma stanu. Oznacza to, że nie będzie automatycznego sprawdzania, czy ta wersja Ruby jest zainstalowana, czy nie. Możemy to zrobić sami:

- 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

Pozostaje tylko zainstalować pakiet:

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

I ponownie dodaj naszą rolę ruby_rbenv do głównego podręcznika.

Udostępnione pliki.

Ogólnie rzecz biorąc, konfigurację można zakończyć w tym miejscu. Następnie pozostaje już tylko uruchomić capistrano, które samo skopiuje kod, utworzy niezbędne katalogi i uruchomi aplikację (o ile wszystko jest poprawnie skonfigurowane). Jednak capistrano często wymaga dodatkowych plików konfiguracyjnych, takich jak database.yml lub .env Można je kopiować tak samo jak pliki i szablony dla nginx. Jest tylko jedna subtelność. Przed skopiowaniem plików musisz utworzyć dla nich strukturę katalogów, mniej więcej taką:

# 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 w razie potrzeby automatycznie utworzy katalog nadrzędny.

Skarbiec ansible

Spotkaliśmy się już z faktem, że zmienne mogą zawierać tajne dane, takie jak hasło użytkownika. Jeśli stworzyłeś .env plik do wniosku i database.yml wtedy takich krytycznych danych musi być jeszcze więcej. Dobrze byłoby ukryć je przed wzrokiem ciekawskich. W tym celu się go używa skarbiec ansible.

Stwórzmy plik dla zmiennych /ansible/vars/all.yml (tutaj możesz tworzyć różne pliki dla różnych grup hostów, tak jak w pliku inwentarza: produkcja.yml, staging.yml itp.).
Wszystkie zmienne, które muszą zostać zaszyfrowane, muszą zostać przesłane do tego pliku przy użyciu 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ć za pomocą polecenia:

ansible-vault encrypt ./vars/all.yml

Naturalnie podczas szyfrowania konieczne będzie ustawienie hasła do odszyfrowania. Po wywołaniu tego polecenia możesz zobaczyć, co będzie w pliku.

Za pomocą ansible-vault decrypt plik można odszyfrować, zmodyfikować, a następnie ponownie zaszyfrować.

Nie musisz odszyfrowywać pliku, aby działać. Przechowujesz go w postaci zaszyfrowanej i uruchamiasz podręcznik z argumentem --ask-vault-pass. Ansible poprosi o hasło, pobierze zmienne i wykona zadania. Wszystkie dane pozostaną zaszyfrowane.

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

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

Ale nie podam Ci pełnego tekstu podręczników i ról, napisz to sam. Bo z ansiblem tak jest – jeśli nie rozumiesz, co należy zrobić, to nie zrobi tego za ciebie.

Źródło: www.habr.com

Dodaj komentarz