Translate

четвер, 14 травня 2015 р.

Provisioning with Ansible


З часом кожному системному адміністратору випадає необхідність встановлювати та підтримувати дедалі більшу кількість однотипних серверів. Робити кожен раз одне й те саме далеко не найприємніша справа та і надалі це буде займати все більшу кількість часу. Перше, що приходить на думку для вирішення таких проблем - це все заскриптувати, проте цей спосіб далеко не ідеальний. По-перше, те що написано буде з часом зрозуміле лише автору скриптів, а, по-друге, скрипти навряд чи можуть запропонувати ідемпотентність, тобто кожен наступний запуск самописних скриптів над вже працюючою системою може призвести до поломок.

Щоб уникнути подібних страждань людство і придумало системи управління конфігураціями. До найбільш популярних таких систем можна віднести Puppet, Salt, Chef, CFEngine. Хронологія їх появи така:

* CFEngine. Перший реліз - 1993, написаний на С.
* Puppet - 2005, написаний на Ruby. DSL
* Chef - 2009, написаний також на Ruby, DSL
* Juju - 2010, Python.
* Salt - 2011, Python.
* Ansible. Перший реліз - 2012, Python.

Ansible один із наймолодших із існуючих популярних систем конфігурації. Проект стартував лише в 2012 році, проте вже має багато прихильників. Засновник проекту - Майкл Де Хаанн, попередньо працював над Puppet і Cobbler/Func. Назва продукту взято з науково-фантастичної літератури: в романах американської письменниці Урсули Ле Гуїн ансіблом називається пристрій для оперативного космічного зв'язку. Про Ansible далі і піде мова.

Ansible - програма з відкритим вихідним кодом для налаштування і управління серверами. Для деплоя конфігурації на новий сервер необхідно лише SSH-з’єднання та Python-інтерпритатор, який зазвичай йде по-замовчуванню в основних дистрибутивах. Сценарій конфігурації (в Ansible він називається Playbook) описується за допомогою мови розмітки YAML.


Установити останню версію Ansible можна через PIP:

# pip install ansible

Чи додавши PPA-репозиторій, якщо у якості ОС - Ubuntu:

# apt-get install software-properties-common
# apt-add-repository ppa:ansible/ansible
# apt-get update
# apt-get install ansible

Разом із основним пакетом по залежностям установляться paramiko, jinja2, PyYAML. Про установку для інших дистрибутивів можна почитати тут.

Для того щоб при кожному запуску Ansible не набирати пароль необхідно додати ssh-ключ:

# ssh-keygen -t rsa
# ssh-copy-id ubuntu@192.168.1.3

ubuntu - користувач від імені якого будуть запускатись інструкції Ansible, 192.168.1.3 - IP-адреса піддослідної віртуальної машини, на якій і будуть виконуватись всі експерименти.

Майже все готово. Cтворимо директорію для наших тестів та опишемо групу хостів.

# mkdir ~/my_ansible

hosts-file указує на те де описані хости та їх групи. Cтворимо його:

# vim ~/my_ansible/hosts
[example_hosts]
192.168.1.3

Перевіримо роботу Ansible:

# cd ~/my_ansible/
# ansible -i hosts -u ubuntu example_hosts -m ping      
192.168.1.3 | success >> {
    "changed": false,
    "ping": "pong"
}

З виводу бачимо, що тестовий запуск пройшов успішно.

-i hosts - файл з описом груп хостів.
-u ubuntu - користувач від імені якого будуть виконуватись інструкції
-m ping - запуск ping модуля.

Ansible зручно використовувати у випадку, коли необхідно запустити одну і ту саму дію на певній групі серверів, тобто у якості таких альтернатив, як Dsh, Pdsh, Cluster SSH і т.п.

# ansible -i hosts -u ubuntu example_hosts -m command -a 'uname -a'
192.168.1.3 | success | rc=0 >>
Linux ansible1 3.13.0-49-generic #83-Ubuntu SMP Fri Apr 10 20:11:33 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux

# ansible -i hosts -u ubuntu example_hosts -m command -a 'date'
192.168.1.3 | success | rc=0 >>
Fri Apr 24 16:43:35 CEST 2015

Тобто команда буде запущена для групи хостів example_host, що описана в локальному файлі hosts.

Спершу приведу дещо просту Ansible інструкцію. Нехай перед нами стоїть задача встановити останній стабільний Nginx з ppa-репозиторію, залити власний index.html в docroot, змінити номер порту веб-сервера та перевантажити/запустити nginx. Коректний playbook матиме наступний вигляд:

# vim ~/my_ansible/simple_playbook.yml

---
- hosts: example_hosts
  sudo: yes
  user: ubuntu
  tasks:
    - name: Add PPA-repository
      apt_repository: repo='ppa:nginx/stable' state=present
      register: ppastable

    - name: Install Nginx
      apt: name=nginx update_cache=yes state=latest
      when: ppastable|success

    - name: Upload default index.html for host
      copy: src=static_files/index.html dest=/usr/share/nginx/www/ mode=0644

    - name: Move Nginx virtualhost on port 8080
      lineinfile: backup=yes dest=/etc/nginx/sites-available/default regexp="listen 80 default_server;" backrefs=yes line="\tlisten 8080 default_server;" state=present
      notify:
        - restart nginx

  handlers:
    - name: restart nginx
      service: name=nginx state=restarted

Необхідно чітко дотримуватись відступів, інакше виникнуть помилки. Розберемось крок за кроком з логікою роботи.

- hosts: example_hosts
  sudo: yes
  user: ubuntu

hosts - група хостів над котрою будуть запущені інструкції.
sudo - дозвіл запускати інструкції від sudo
user - користувач, від імені якого будуть запускатись інструкції.

Далі йде список завдань, tasks:

  tasks:
    - name: Add PPA-repository
      apt_repository: repo='ppa:nginx/stable' state=present
      register: ppastable

Перша задача відповідає за додавання нового PPA-репозиторії ppa:nginx/stable. Модуль apt_repository додає новий репозиторій. Якщо він вже присутній (state=present), то з кожним наступним запуском прейбука повторно його вже не буде додано. Ansible слідкує за станом і у разі, якщо умова дотримується, задача вважається успішно виконаною. Вкінці задачі буде зареєстрована змінна "ppastable". Вона може отримати значення success чи failed і вже до цих значень можуть бути прив’язані виконання інших задач, як от насупної в даному прикладі:

    - name: Install Nginx
      apt: name=nginx update_cache=yes state=latest
      when: ppastable|success

У випадку якщо попередня задача буде виконана успішно (when: ppastable | success), тобто буде успішно доданий репозиторій, apt-модуль Ansible запустить оновлення пакетів (update_cache=yes) та встановить останню версію веб-серверу. Успішним виконанням та кінцевою метою задачі вважається наявність встановленої останньої версії Nginx. Знову ж, кожний наступний запуск задачі лише перевірятиме стан виконання умов і лише у разі, якщо пакет не встановлений чи версія не остання - буде знову його встановлено.

    - name: Upload default index.html for host
      copy: src=static_files/index.html dest=/usr/share/nginx/www/ mode=0644

Наступна задача в переліку - завантаження на піддослідний хост локального файлу static_files/index.html за допомогою модуля copy. Після копіювання будуть виставлені права 0644. src/dest - відповідно джерело файлу і його майбутня адреса на віддаленій машині.

    - name: Move Nginx virtualhost on port 8080
      lineinfile: backup=yes dest=/etc/nginx/sites-available/default regexp="listen 80 default_server;" backrefs=yes line="\tlisten 8080 default_server;" state=present
      notify:
        - restart nginx

Модуль Ansible lineinfile допоможе виконати задачу зі зміни стандартного порту Nginx 80 на 8080. Без опції "backrefs=yes" модуль lineinfile буде додавати значення змінної line в кінець конфігураційного файлу, навіть коли немає жодного співпадання з regexp. "backup=yes" дозволяє бекапити попередні конфігураційни файли перед кожною зміною.
Після цієї задачі необхідно перевантажити веб-сервер, щоб зміни вступили в силу. Для цього використовується інструкція notify. notify сигналізує шо необідно виконати по завершенню команди. У нашому випадку вона запустить handler із іменем restart nginx, котрий описаний за допомогою модуля service останнім:

  handlers:
    - name: restart nginx
      service: name=nginx state=restarted

Лишилось залити index.html в локальну диреторію static_files:

# mkdir ~/my_ansible/static_files
# cd ~/my_ansible/static_files

# vim index.html
<h1>Hello, Ansible!<h1>

Файл hosts залишимо в незмінному стані. Запускаємо Playbook:

# ansible-playbook -i hosts simple_playbook.yml

PLAY [example_hosts] **********************************************************

GATHERING FACTS ***************************************************************
...
changed: [192.168.1.3] => {"changed": true, "name": "nginx", "state": "started"}

PLAY RECAP ********************************************************************
192.168.1.3                : ok=6    changed=5    unreachable=0    failed=0

Перевіримо результат в браузері:


Ура!

Плейбук готовий до використання. Для його запуску на більшій групі серверів необхідно додати більше хостів в ~/my_ansible/hosts.

Наступний приклад плейбука буде побудований з використанням ролей. Роль - це дія чи група дій виділена окремо задля використання в багатьох плейбуках одразу чи просто для зручності її підтримки. По факту, роль - це директорія, в котрій уже і знаходяться окремі дії плейбука.

Кожна роль може мати додаткові директорії: tasks, handlers, templates. Задач (tasks) може бути декілька, проте вони мають бути підключені в roles/tasks/main.yml в необхідному порядку. Обробники (handlers) повинні бути також описані в своїй дирекорії roles/handlers/main.yml. Роль також може мати директорію з темплейтами (templates), в котрій лежатимуть різні шаблони. На найвищому рівні (вище директорії з ролями roles) описується основний YAML-файл із включенням необхідних ролей, змінних, груп хостів. Останні, зазвичай, описуються в локальному файлі hosts.

Розберемось з цим на прикладі. Поставимо перед собою дещо складнішу задачу: проведемо повну установку Drupal на базі Nginx, PHP-FPM, MySQL. Із Ansible це справді не складно!

Кожна з ролей має описувати якийсь певний етап налаштування на ваш власний розсуд. Наприклад задачу по встановленню Drupal я поділив на такі етапи (ролі):

- оновлення пакетної бази дистрибутиву (роль update)
- установка Nginx (роль nginx):
   - додавання ppa-репозиторію ppa:nginx/stable
   - установка самого пакету
   - видалення лінки default віртуального хосту
   - копіювання на віддалений хост нового vhost для nginx
   - лінкування останнього в директорію sites-enabled
   - перевантаження Nginx
- установка PHP-FPM (роль php5-fpm):
   - установка php5-fpm та бібліотеки php5-gd
   - редагування конфігу php5-fpm
- установка MySQL (роль mysql):
   - установка пакету mysql-server та бібліотеки php5-mysql
   - створення нової бази
   - створення нового користувача з паролем і необхідним доступом до новох бази
- копіювання коду Drupal (роль drupal):
   - установка пакету git
   - клонування репозиторію Drupal
   - створення конфігураційного файлу settings.php
   - створення конфігураційного файлу services.yml
   - установка відповідних прав для конфігураційних файлів
   - установка прав для директорії files

Детально про установку Drupal можна почитати наприклад тут.

Створимо нову директорію для плейбука та ролей:

# mkdir ~/drupal_setup_nginx

Як і в попередньому випадку опишемо групи хостів:

# cd ~/drupal_setup_nginx/
# vim hosts

[drupal_hosts]
192.168.1.3

Звісно, що до груп може входити більше ніж один хост.
Переходимо до опису ролей. Перша - update:

# cd ~/drupal_setup_nginx/
# mkdir roles
# cd roles
# mkdir update
# cd update
# mkdir tasks
# cd tasks

# vim main.yml
---

- name: apt-get update the server
  apt: update_cache=yes

Ця роль за допомогою модуля apt оновить кеш пакетів.

Наступна роль - nginx. Вона складатиметься із двох задач - установки веб-сервера (setup.yml) та створення віртуального хосту (create_vhost.yml). Тому їх потрібно включити в загальний main.yml:

# cd ~/drupal_setup_nginx/roles
# mkdir nginx
# cd nginx
# mkdir tasks
# cd tasks

# vim main.yml
---

- include: setup.yml
- include: create_vhost.yml

Створюємо задачу для установки nginx:

# vim setup.yml
---

- name: Add Nginx Repository
  apt_repository: repo='ppa:nginx/stable' state=present
  register: ppastable

- name: Install Nginx
  apt: name={{ item }} update_cache=yes state=latest
  with_items:
    - nginx
  when: ppastable|success

Ця задача додасть репозиторій Nginx та встановить його в разі, якщо репозиторій було додано успішно (when: ppastable | success)
Після установки веб-сервера, необхідно створити хост для майбутньої інсталяції Drupal:

# vim create_vhost.yml
---

- name: Remove default vhost
  file: path=/etc/nginx/sites-enabled/default state=absent

- name: Create Nginx vhost
  template: src=nginx.conf.j2 dest=/etc/nginx/sites-available/{{ domain }}

- name: Create link in sites-enabled
  file: src=/etc/nginx/sites-available/{{ domain }} dest=/etc/nginx/sites-enabled/{{ domain }} state=link
  notify:
    - Restart Nginx

В цій задачі буде видалено дефолтний віртуальний хост, додано новий, за допомогою модуля template, та створено посилання в /etc/nginx/sites-enabled/ для його активації. Темплейт для нового домена буде взято з директорії templates ролі:

# cd ~/drupal_setup_nginx/roles/nginx/
# mkdir templates
# cd templates

# vim nginx.conf.j2

server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    root /var/www/{{ domain }};
    index index.php index.html index.htm;

    server_name {{ domain }};

    location / {
        #try_files $uri $uri/ =404;
        try_files $uri $uri/ /index.php?q=$uri&$args;
    }

    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /var/www/{{ domain }};
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

Для опису темплейтів використовується темплейт-мова Jinja2.

{{ domain }} - змінна, котру буде взято з основного плейбуку.
Після зміни конфігураційних файлів необхідно перевантажити Nginx. В Ansible це реалізовується через директиву notify. notify викличе handler із іменем "Restart Nginx", що має бути описаний в директорії handlers:

# cd ~/drupal_setup_nginx/roles/nginx/
# mkdir handlers
# cd handlers

# vim main.yml
---

- name: Restart Nginx
  service: name=nginx state=restarted

Отже веб-сервер буде перевантажений по закінченню ролі завдяки Ansible модулю service.

Наступна роль - php5-fpm.

# cd ~/drupal_setup_nginx/roles
# mkdir php5-fpm
# cd php5-fpm
# mkdir tasks
# cd tasks

# vim main.yml
---

- include: setup.yml
- include: edit_config.yml

Як і в попередній ролі, задачі я поділив на дві частини: setup.yml та edit_config.yml.

# vim setup.yml
---

- name: Install PHP-FPM and associated packages
  apt: name={{ item }} update_cache=yes state=latest
  with_items:
    - php5-fpm
    - php5-gd
  notify:
    - Restart PHP-FPM
    - Restart Nginx

# vim edit_config.yml
---

- name: Ensure PHP-FPM cgi.fix_pathinfo=0
  lineinfile: dest=/etc/php5/fpm/php.ini regexp='^(.*)cgi.fix_pathinfo=' line=cgi.fix_pathinfo=0
  notify:
    - Restart PHP-FPM

По закінченню задач відбудеться перевантаження демонів Nginx та PHP-FPM:

# cd ~/drupal_setup_nginx/roles/php5-fpm/
# mkdir handlers
# cd handlers

# vim main.yml
---

- name: Restart Nginx
  service: name=nginx state=restarted

- name: Restart PHP-FPM
  service: name=php5-fpm state=restarted

Нічого особливо нового на цьому етапі.

Передостання роль - mysql.

# cd ~/drupal_setup_nginx/roles
# mkdir mysql
# cd mysql
# mkdir tasks
# cd tasks

# vim main.yml
---

- include: setup.yml
- include: create_db.yml

Вміст setup.yml:

# vim setup.yml
---

- name: Install MySQL server
  apt: name=mysql-server state=latest

- name: Install MySQL module for PHP
  apt: name=php5-mysql state=latest

Та create_db.yml:

# vim create_db.yml
---

- name: Install Python MySQLdb
  apt: name=python-mysqldb state=latest

- name: Create the Drupal database
  mysql_db: db={{ db_name }} state=present

- name: Create the Drupal user
  mysql_user: >
    name={{ db_user }}
    password={{ db_password }}
    priv={{ db_name }}.*:ALL
    host=localhost

Python-бібліотека MySQLdb необхідна для створення користувачів, баз, та надання прав до них. Змінні {{ db_user }}, {{ db_password }}, {{ db_name }} будуть взяті із основного плейбуку, що об'єднає всі ролі.

Нарешті, остання роль - drupal.

# cd ~/drupal_setup_nginx/roles
# mkdir drupal
# cd drupal
# mkdir tasks
# cd tasks

# vim main.yml
---

- name: Install git
  apt: name=git state=latest

- name: Clone Drupal
  git: >
    repo=http://git.drupal.org/project/drupal.git
    dest=/var/www/{{ domain }}/
    update=no

- name: Create settings.php
  command: cp /var/www/{{ domain }}/sites/default/default.settings.php /var/www/{{ domain }}/sites/default/settings.php
  args:
    creates: /var/www/{{ domain }}/sites/default/settings.php

- name: Create services.yml
  command: cp /var/www/{{ domain }}/sites/default/default.services.yml /var/www/{{ domain }}/sites/default/services.yml
  args:
    creates: /var/www/{{ domain }}/sites/default/services.yml

- name: Update permissions of settings.php
  file: path=/var/www/{{ domain }}/sites/default/settings.php mode=777

- name: Update permissions of services.yml
  file: path=/var/www/{{ domain }}/sites/default/services.yml mode=777

- name: Update permissions of files directory
  file: >
    path=/var/www/{{ domain }}/sites/default/files
    mode=777
    state=directory
    recurse=yes

Для установки CMS Drupal скористуємось офіційним git-репозиторієм проекту http://git.drupal.org/project/drupal.git. Для успішного виконання цієї задачі необхідно спочатку встановити пакет git, клонувати репозиторій, і створити декілька конфігураційний файлів із наданих темплейтів шляхом копіювання: settings.php та services.yml. Три останні задачі ролі відповідають за встановлення необхідних прав для конфігураційних файлів та директорії files.

Між іншим, структуру директорій для ролі можна згенерувати наступним чином:

# ansible-galaxy init some_role
- some_role was created successfully

# ls -1 some_role
README.md
defaults
files
handlers
meta
tasks
templates
tests
vars

Як наслідок такої генерації, всі ролі будуть по структурі схожими. Ця можливість з'явилась в версії Ansible 1.4.2.

Основний плейбук має розміщуватись на одному рівні з директорією ролей:

# cd ~/drupal_setup_nginx/

# vim site.yml
---

- hosts: drupal_hosts
  user: ubuntu
  sudo: yes

  vars:
    - domain: drupal.com
    - db_name: drupal

  vars_files:
    - 'databag.yml'

  roles:
    - update
    - nginx
    - php5-fpm
    - mysql
    - drupal

Плейбук буде виконаний для групи drupal_hosts, що описана в ~/drupal_setup_nginx/hosts. Запускати інструкції буде користувач ubuntu від sudo.
Замість змінних у подвійних фігурних дужках "{{}}" будуть підставлені відповідні значення "domain: drupal.com" та "db_name: drupal". Змінні паролей, можливо, варто винести окремо в файл (databag.yml) та за допомогою ansible-vault зашифрувати:

# vim databag.yml
---

db_user: 'drupal_user'
db_password: 'drupal_db_pass'

Не завадить, звісно, обрати у якості пароля щось оригінальніше. Шифруємо результат:

# ansible-vault encrypt databag.yml

Після шифрування, при перегляді звичайним переглядачем, він буде схожим на таке:

# cat databag.yml
$ANSIBLE_VAULT;1.1;AES256
63326230353939613938333861316631643332306233633535616239633635643765633834626130
3366663934643166663731386235383961326537616662340a653836363561633033656136663132
36396233613165363237313039663362366337313864353463306265346566626361663836623830
3562636662646639310a303138373166623135646331623931363432383638313966646134323362
36363339316364356261373839613230663937366134393332316332323762663134393135306638
66616538313135326539626562633665323565306337656261366363346561313564633239333438
353164393263343766376334383930373731

Щоб розшифрувати чи відрудагувати databag необхідно знову пропустити його через ansible-vault і указати попередній пароль:

# ansible-vault decrypt vars.yml
# ansible-vault edit vars.yml

Отже, готово! Дерево всіх створений директорій має такий вигляд:

# tree drupal_setup_nginx
drupal_setup_nginx
├── databag.yml
├── hosts
├── roles
│   ├── drupal
│   │   └── tasks
│   │       └── main.yml
│   ├── mysql
│   │   └── tasks
│   │       ├── create_db.yml
│   │       ├── main.yml
│   │       └── setup.yml
│   ├── nginx
│   │   ├── handlers
│   │   │   └── main.yml
│   │   ├── tasks
│   │   │   ├── create_vhost.yml
│   │   │   ├── main.yml
│   │   │   └── setup.yml
│   │   └── templates
│   │       └── nginx.conf.j2
│   ├── php5-fpm
│   │   ├── handlers
│   │   │   └── main.yml
│   │   └── tasks
│   │       ├── edit_config.yml
│   │       ├── main.yml
│   │       └── setup.yml
│   └── update
│       └── tasks
│           └── main.yml
└── site.yml

14 directories, 17 files

Запускаємо основний плейбук і чекаємо на результат - автоматично налаштований Drupal:

# ansible-playbook -i hosts site.yml --ask-vault-pass
Vault password:

PLAY [drupal_hosts] ***********************************************************

GATHERING FACTS ***************************************************************
ok: [192.168.1.3]

TASK: [update | apt-get update the server] ************************************
ok: [192.168.1.3]
...

NOTIFIED: [php5-fpm | Restart PHP-FPM] ****************************************
changed: [192.168.1.3]

PLAY RECAP ********************************************************************
192.168.1.3                : ok=23   changed=21   unreachable=0    failed=0

Остання стрічка інформує, що все відпрацьовано успішно. Якщо виникла помилка - варто перезапустити плейбук із ключем -v/-vv/-vvv і уважно прочитати вивід команд.

Для успішності експерименту варто drupal.com прив’язати до 192.168.1.3 в /etc/hosts.


 Посилання:

https://serversforhackers.com/an-ansible-tutorial
https://www.digitalocean.com/community/tutorials/how-to-use-ansible-roles-to-abstract-your-infrastructure-environment
https://www.digitalocean.com/community/tutorials/how-to-create-ansible-playbooks-to-automate-system-configuration-on-ubuntu
https://www.digitalocean.com/community/tutorials/how-to-create-an-ansible-playbook-to-automate-drupal-installation-on-ubuntu-14-04
https://www.digitalocean.com/community/tutorials/how-to-deploy-a-basic-php-application-using-ansible-on-ubuntu-14-04
http://habrahabr.ru/company/selectel/blog/196620/
http://habrahabr.ru/post/195048/
http://habrahabr.ru/company/express42/blog/254959/
http://tomoconnor.eu/blogish/getting-started-ansible
https://adamcod.es/2014/09/23/vagrant-ansible-quickstart-tutorial.html
http://jpmens.net/2012/06/06/configuration-management-with-ansible/
https://sysadmincasts.com/episodes/43-19-minutes-with-ansible-part-1-4
https://developer.rackspace.com/blog/ansible-and-docker
http://docs.ansible.com/ansible/latest/playbooks_best_practices.html

Ansible vs Puppet
https://dantehranian.wordpress.com/2015/01/20/ansible-vs-puppet-overview/
https://dantehranian.wordpress.com/2015/01/20/ansible-vs-puppet-hands-on-with-ansible/
https://blog.ryandlane.com/2014/08/04/moving-away-from-puppet-saltstack-or-ansible/

Немає коментарів:

Дописати коментар