Sunday, 18 December 2016

Làm quen với Ansible - viết playbook đơn giản

Sau khi đã cài đặt Ansible và chạy được ad-hoc command, bài viết này sẽ hướng dẫn viết các file playbook để thực hiện hàng loạt task thay vì chỉ chạy từng câu lệnh một. Các file Playbook này thường cho vào một git repo để tiện quản lý.

Ansible playbook

Một Ansible Playbook là môt file ở định dạng YAML (.yml), nó ở dạng list và chứa một hoặc nhiều "play".

Play

Mỗi "play" dùng để kết nối (map) một nhóm các "host" với một vài "role". Roles trong playbook được gọi là "tasks".

Role và task

Một task thực hiện gọi đến một Ansible module nào đó.

Ví dụ một playbook tên là vim.yml, chỉ chứa 1 play map tất cả các hosts với role cài vim:
$ cat vim.yml
---
- hosts: all
  remote_user: root
  tasks:
    - name: Install vim
      apt: name=vim state=present

Ở đây chỉ có một role (task) trong tasks list, task này chỉ định một package tên là "vim" (name=vim) ở trạng thái "present" (state=present) trên máy đích. Ta sử dụng module ``apt`` với các argument tương ứng.
Module này không thấy trong các ví dụ chuẩn của Ansible (các ví dụ này toàn dùng yum - package manager của RedHat và đồng bọn) mà tìm thấy do dùng Google tại:
https://github.com/geerlingguy/ansible-role-redis/blob/5bf1957da0ee523080e9d7af132f15d5b40e0cbc/tasks/setup-Debian.yml
 
Mọi task đều NÊN có name, "Install vim" là đoạn string giải thích xem task này làm gì.

Chạy (execute) một playbook

$ ansible-playbook -i ./hosts vim.yml -v
No config file found; using defaults

PLAY [all] *********************************************************************

TASK [setup] *******************************************************************
ok: [128.199.6.9]

TASK [Install vim] *************************************************************
ok: [128.199.6.9] => {"cache_update_time": 1481021292, "cache_updated": false, "changed": false}

PLAY RECAP *********************************************************************
128.199.6.9            : ok=2    changed=0    unreachable=0    failed=0
Cú pháp:
ansible-playbook path_to_playbook_file [options]
Option ``-v`` sẽ cho output chi tiết hơn khi chạy playbook.

Chạy một playbook chứa nhiều task hơn:
$ cat main.yml

---
- hosts: all
  remote_user: root
  tasks:
    - name: test connection
      ping:
    - name: Install NGINX
      apt: name=nginx state=present
    - name: get top 5 processes
      shell: ps xau | sort -nrk6 | head -n 5

$ ansible-playbook -i ./hosts -v main.yml
No config file found; using defaults

PLAY [all] *********************************************************************

TASK [setup] *******************************************************************
ok: [128.199.6.9]

TASK [test connection] *********************************************************
ok: [128.199.6.9] => {"changed": false, "ping": "pong"}

TASK [Install NGINX] ***********************************************************
ok: [128.199.6.9] => {"cache_update_time": 1481021292, "cache_updated": false, "changed": false}

TASK [get top 5 processes] *****************************************************
changed: [128.199.6.9] => {"changed": true, "cmd": "ps xau | sort -nrk6 | head -n 5", "delta": "0:00:00.033030", "end": "2016-12-18 10:53:48.920725", "rc": 0, "start": "2016-12-18 10:53:48.887695", "stderr": "", "stdout": "www-data 21504  0.0 10.5 427992 107484 ?       Sl   Dec06   0:15 sentry-worker\nsyslog    7158  0.0  2.3 414292 23744 ?        Ssl  Dec06   1:56 rsyslogd\npostgres 12259  0.0  2.3 135040 23720 ?        Ss   Dec06   0:04 postgres: checkpointer process                                                                                              \nroot      4161  0.0  1.1  37968 11900 pts/1    S+   10:53   0:00 /usr/bin/python /tmp/ansible_gVcEjq/ansible_module_command.py\nwww-data 20548  0.0  1.0 334572 10260 ?        Ssl  Dec06   1:38 /usr/bin/memcached -m 24 -s /var/run/memcache/memcache.sock -u www-data", "stdout_lines": ["www-data 21504  0.0 10.5 427992 107484 ?       Sl   Dec06   0:15 sentry-worker", "syslog    7158  0.0  2.3 414292 23744 ?        Ssl  Dec06   1:56 rsyslogd", "postgres 12259  0.0  2.3 135040 23720 ?        Ss   Dec06   0:04 postgres: checkpointer process                                                                                              ", "root      4161  0.0  1.1  37968 11900 pts/1    S+   10:53   0:00 /usr/bin/python /tmp/ansible_gVcEjq/ansible_module_command.py", "www-data 20548  0.0  1.0 334572 10260 ?        Ssl  Dec06   1:38 /usr/bin/memcached -m 24 -s /var/run/memcache/memcache.sock -u www-data"], "warnings": []}

PLAY RECAP *********************************************************************
128.199.6.9            : ok=4    changed=1    unreachable=0    failed=0

Handler

Một tính năng cần thiết của hệ thống configuration management là khả năng "thông báo" cho một dịch vụ khi một hay nhiều file cấu hình thay đổi. Ansible xử lý vấn đề này bằng khái niệm "notify" và "handler".
Khi thêm action "notify" vào một task, sau khi task này kết thúc thành công, Ansible sẽ "notify" đến những "handler" được liệt kê để báo có thay đổi. Dù được bao nhiêu task trigger thì handler cũng sẽ chỉ chạy một lần.

Chạy một playbook có task notify NGINX service để restart nếu như task cài package "gunicorn" có thay đổi (được cài mới).
$ cat restart_nginx.yml
---
- hosts: all
  remote_user: root
  tasks:
    - name: Install NGINX
      apt: name=nginx state=present
    - name: Install Gunicorn
      apt:
        name: gunicorn # cách viết khác thay vì name=gunicorn state=present - được khuyên dùng
        state: present
      notify: restart nginx

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

$ ansible-playbook -i ./hosts restart_nginx.yml

PLAY [all] *********************************************************************

TASK [setup] *******************************************************************
ok: [128.199.6.9]

TASK [Install NGINX] ***********************************************************
ok: [128.199.6.9]

TASK [Install Gunicorn] ********************************************************
changed: [128.199.6.9]

RUNNING HANDLER [restart nginx] ************************************************
changed: [128.199.6.9]

PLAY RECAP *********************************************************************
128.199.6.9            : ok=4    changed=2    unreachable=0    failed=0
Nếu chạy lại lần nữa, task cài đặt gunicorn không có thay đổi gì (do đã cài ở lần chạy trước), do vậy nó không"trigger" notify nên handler "restart nginx" sẽ không chạy. Tính chất "đã chạy rồi thì không chạy lại" gọi là "idempotent" - một điểm khác biệt nổi bật giữa việc dùng 1 hệ thống configuration management so với chạy một bash script cài đặt thông thường (tất nhiên có thể viết code bash kiểm tra trước từng bước chạy lệnh, nhưng rất mệt):
 $ ansible-playbook -i ./hosts restart_nginx.yml

PLAY [all] *********************************************************************

TASK [setup] *******************************************************************
ok: [128.199.6.9]

TASK [Install NGINX] ***********************************************************
ok: [128.199.6.9]

TASK [Install Gunicorn] ********************************************************
ok: [128.199.6.9]

PLAY RECAP *********************************************************************
128.199.6.9            : ok=3    changed=0    unreachable=0    failed=0
NOTE: handler cũng là task.
Các roles (tasks) mẫu có thể xem tại Ansible Galaxy.
Tham khảo: http://docs.ansible.com/ansible/playbooks_intro.html

Qua bài viết này, người đọc đã có thể:
- Viết một file playbook
- Hiểu các khái niệm quan trọng: play, task, role, handler

Hết.
HVN at http://www.familug.org/ and http://pymi.vn