1. Ansible là gì?
Ansible là một công cụ cho phép tự động hóa việc triển khai (deploy) cài đặt các ứng dụng, thực hiện các tác vụ (task) và quản lý việc cấu hình (configuration) trên nhiều máy cùng lúc.
Ansible phân biệt hai loại máy (machine) hay còn gọi là node:
- Managed nodes hoặc hosts : Là những máy mà ta muốn cấu hình và cài đặt các ứng dụng trên chúng.
- Control node (máy điều khiển) : Ansible được cài đặt trên máy này ở hệ điều hành Linux, Windows không được hỗ trợ để làm control node. Máy này đọc các tập tin định nghĩa cấu hình và chạy Ansible để đẩy các cấu hình này vào các hosts.
Ansible dùng SSH để giao tiếp giữa máy điều khiển và các hosts. Ta cần cài đặt Python và SSH trên các nodes để có thể xài Ansible.
2. Một số khái niệm trong Ansible
- Inventory : là danh sách các hosts.
- Playbook : là tập tin định nghĩa các tác vụ cho Ansible và được viết bằng ngôn ngữ YAML. Nó định nghĩa các bước mà người dùng muốn thực thi trên một máy nào đó. Các tác vụ trong Playbook được thực thi tuần tự.
- YAML (là viết tắt của Yet Another Makeup Language và sau là YAML Ain't Markup Language) : là một ngôn ngữ đặc tả dễ hiểu cho người dùng.
- module : Khi bạn dùng Ansible là bạn dùng các module của Ansible. Các module này được viết bằng Python. Một số module của Ansible như file, apt, user...
3. Ansible hoạt động ra sao
Hình dưới đây miêu tả hoạt động của Ansible
________
______________ ssh | Host 1 |
| |--------------------->|________|
| | ssh ________
| Ansible |--------------------->| Host 2 |
| control node | |________|
| | ssh ________
|______________|--------------------->| Host n |
______|________ |________|
______|_____ _____|_____
| Playbook | | Inventory |
|____________| |___________|
host 1
host 2
host n
Khi dùng Ansible trên máy điều khiển (control node) ta sẽ cung cấp cho
nó tập tin Playbook định nghĩa các tác vụ mà ta muốn thực thi tuần tự
trên các host. Danh sách các host này được định nghĩa trong tập tin
Inventory.
Ansible sẽ kết nối với các host và đẩy các tác vụ đến
các nodes này. Sau đó Ansible thực thi tuần tự các modules định nghĩa
trong các tác vụ này trên các host (thông qua kết nối SSH) để thực hiện
các thao tác hoặc cài đặt một số phần mềm trên các host này.
4. Căn bản về ngôn ngữ YAML dùng trong Ansible
Như đã nói ở trên Ansible dùng YAML để định nghĩa các tập tin playbook của nó vì ngôn ngữ này dễ hiểu với người dùng.
Mỗi tập tin YAML có thể bắt đầu bằng "---" và kết thúc bằng "..." nhưng điều này không bắt buộc.
YAML dùng cặp key-value để định nghĩa dữ liệu. Chú ý là phải có khoảng trắng sau : và value
Ví dụ : thông tin sinh viên
--- # Optional YAML start syntax
toan:
name: toan
number: 1
sex: male
... # Optional YAML end syntax
Định nghĩa một danh sách trong YAML. Mỗi thành phần của danh sách được viết xuống hàng bắt đầu bằng "- " (- và một khoảng trắng). Các thành phần trong danh sách có cùng khoảng cách đầu dòng như nhau.
Ví dụ :
---
languages:
- Java
- Python
- PHP
- C++
...
Danh sách bên trong dictionary tức giá trị của một key trong cặp key-value là một danh sách
Ví dụ :
---
toan:
name: toan
number: 1
sex: male
likes:
- Java
- Python
- PHP
...
Danh sách các dictionary
Ví dụ:
---
- toan:
name: toan
number: 1
sex: male
likes:
- Java
- Python
- PHP
- tri:
name: tri
number: 2
sex: male
likes:
- C++
- HTML
- JavaScript
...
5. Cài đặt Ansible trên Ubuntu
Ta chỉ cần cài đặt Ansible trên một máy điều khiển tức control node. Máy này cần cài đặt Python 2 (phiên bản 2.7) hoặc Python 3 (phiên bản 3.5 trở lên). Hệ điều hành Windows không được hỗ trợ để làm control node cho Ansible. Trên các managed nodes cũng cần phải cài Python.
Dưới đây là lệnh cài đặt Ansible trên Ubuntu
$ sudo apt update $ sudo apt install software-properties-common $ sudo apt-add-repository --yes --update ppa:ansible/ansible $ sudo apt install ansible
Sau khi cài xong Ansible, ta kiểm tra phiên bản vừa cài đặt bằng lệnh :
$ ansible --version
6. Tạo một tập tin Playbook cho Ansible
Trong tập tin playbook ta định nghĩa những tác vụ ta muốn tự động hóa. Ansible gọi danh sách các tác vụ này (task) là playbook.
Nếu ta định nghĩa hosts là all thì Ansible sẽ thực thi playbook trên toàn bộ các hosts một cách song song. Nhưng các tác vụ trong playbook sẽ được thực thi tuần tự, xong tác vụ này mới đến tác vụ kế tiếp.
Chú ý, cú pháp của YAML chú trọng thụt đầu dòng ngay ngắn và đều nhau, nên khi viết tập tin playbook ta cần chú ý việc thụt đầu dòng cho đúng.
Dưới đây là nội dung tập tin playbook java11-setup.yml nhằm cài đặt java 11 trên toàn bộ các hosts.
---
- hosts: all
become: yes
vars:
packages:
- openjdk-11-jdk
- openjdk-11-jre
tasks:
- name: add openjdk repository
apt_repository:
repo: ppa:openjdk-r/ppa
state: present
- name: install openjdk 11
apt:
name: "{{ packages}}"
state: present
Giải thích :
- hosts: all : nói Ansible thực thi playbook này cho toàn bộ các host định nghĩa trong hosfile hoặc trong tập tin inventory.
- become: yes : thực hiện các lệnh này bằng sudo user. Xem tại đây https://docs.ansible.com/ansible/latest/user_guide/become.html
- vars: các biến variable định nghĩa bởi người dùng. Ngoài ra còn có một số nguồn biến khác, chẳng hạn như biến của hệ thống (system). Các biến hệ thống được gọi là fact.
- tasks: danh sách các tác vụ ta muốn thực thi.
Ta hãy xem tác vụ đầu tiên trong danh sách này :
- name: add openjdk repository- name : mô tả ngắn gọn về tác vụ
apt_repository:
repo: ppa:openjdk-r/ppa
state: present
- apt_repository: là module mà tác vụ này dùng để cấu hình và thực thi. Chi tiết về module này có thể xem tại đây
https://docs.ansible.com/ansible/latest/collections/ansible/builtin/apt_repository_module.html
Ansible cung cấp nhiều module định nghĩa sẵn. Module apt_repository cho phép thêm hoặc xóa một APT repository trong Ubuntu. Hầu hết các module có biến state để định nghĩa cho nó nên làm gì : present (thêm vào, khởi động hay thực thi), absent (xóa đi, dừng lại)
- repo : chuỗi định nghĩa repository
Module apt
Phần vars định nghĩa các package cần cài đặt
vars:
packages:
- openjdk-11-jdk
- openjdk-11-jre
tasks:
- name: install openjdk 11
apt:
name: "{{ packages}}"
state: present
Module apt quản lý các gói apt trong Ubuntu.
- name : có thể định nghĩa ở đây một danh sách các package cần cài đặt. Ở đây, danh sách này được định nghĩa riêng trong một biến có tên "packages" trong mục "vars" để cho tác vụ này rõ ràng hơn.
- state: được gán giá trị "present" nghĩa là ansible sẽ cài các gói này nếu chúng chưa được cài, còn nếu chúng đã có rồi thì bỏ qua không thực hiện việc cài chúng nữa. Nói ngắn gọn là đảm bảo sự hiện diện của chúng. Ngoài ra ta có thể gán các giá trị "absent"(đảm bảo chúng không hiện diện), "latest" (đảm bảo chúng hiện diện và có phiên bản mới nhất), "build-deps"(đảm bảo những gói mà chúng phụ thuộc vào cũng hiện diện), hoặc "fixed" (gắng sửa lại hệ thống nếu có những phụ thuộc bị thiếu).
* Một cách dễ hiểu, tập tin playbook java11-setup.yml ở trên tương đương với việc thực hiện các lệnh sau để cài đặt java 11 :
sudo add-apt-repository ppa:openjdk-r/ppa
sudo apt install openjdk-11-jdk
sudo apt install openjdk-11-jre
7. Tạo một tập tin Inventory cho Ansible
Như đã đề cập trong mục (3) ở trên về cách hoạt động của Ansible. Ta vừa định nghĩa các tác vụ để Ansible thực thi. Giờ ta phải cung cấp danh sách các host để Ansible tiến hành thực hiện các tác vụ này trên đó. Ở đây ta tạo một tập tin Inventory để cung cấp danh sách các host này. Thông tin cụ thể về Inventory có thể tham khảo tại đây https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html
Ta đặt tên tập tin Inventory này là inventory-all-vms.yml và nó có nội dung như sau :
all:
children:
ubuntu_servers:
hosts:
jenkins_ubuntu18:
ansible_host: jenkins.local
toan_local:
hosts:
toan_ubuntu16:
ansible_host: toan.local
Trong tập tin trên, ta khai báo hai hosts là jenkins.local và toan.local vì đây là hai VM mà tôi dùng Vagrant để tạo ra. Ngoài ra bạn cũng có thể dùng adresse ip để định nghĩa cho các host này.
Đến đây ta đã có tập tin playbook và inventory, từ control node của ansible ta có thể dùng lệnh ansible-playbook để thực hiện việc cài đặt java 11 (định nghĩa trong playbook) cho các hosts (định nghĩa trong inventory) theo cú pháp sau :
ansible-playbook java11-setup.yml -i inventory-all-vms.yml
8. Dùng Vagrant tạo control node và hosts cho Ansible và cấu hình cho chúng
Dưới đây, ta sẽ dùng Vagrant để tạo một máy ảo làm control node và hai máy ảo làm host cho ansible. Xem chi tiết việc dùng Vagrant để tạo VM tại đây https://openspacevn.blogspot.com/2020/11/cai-at-vagrant-tren-windows.html
Tập tin Vagrantfile định nghĩa ba máy ảo này có nội dung như sau :
Vagrant.configure("2") do |config|
#common part of vms
BOX_IMAGE = "ubuntu/bionic64" # bionic 64 = ubuntu 18.04 64 bits
#ansible vm description
config.vm.define "ansible" do |ansible|
ansible.vm.box = BOX_IMAGE
ansible.vm.hostname = "ansible"
ansible.vm.network "private_network", ip: "10.0.0.10", virtualbox__intnet: true
ansible.vm.provision "shell", inline: "apt install python -yq", privileged: true
ansible.vm.provision "shell", inline: "sudo apt-get install -yq software-properties-common", privileged: true
ansible.vm.provision "shell", inline: "sudo apt-add-repository -y ppa:ansible/ansible", privileged: true
ansible.vm.provision "shell", inline: "sudo apt-get update -yq", privileged: true
ansible.vm.provision "shell", inline: "sudo apt-get install -yq ansible", privileged: true
ansible.vm.provision "shell", inline: "sudo apt-get install -yq mc", privileged: true
end
#jenkins vm description
config.vm.define "jenkins" do |jenkins|
jenkins.vm.box = BOX_IMAGE
jenkins.vm.hostname = "jenkins"
jenkins.vm.network "private_network", ip: "10.0.0.11", virtualbox__intnet: true
jenkins.vm.provision "shell", inline: "sudo apt-get install -yq mc", privileged: true
end
config.vm.define "toan" do |toan|
toan.vm.box = "ubuntu/xenial64"
toan.vm.hostname = "toan"
toan.vm.network "private_network", ip: "10.0.0.12", virtualbox__intnet: true
toan.vm.provision "shell", inline: "sudo apt-get install -yq mc", privileged: true
end
end
Như ta thấy trong tập tin trên, máy ảo control node có tên "ansible" và trên đó ta sẽ cài đặt Python và Ansible. Hai máy ảo host có tên là "jenkins" và "toan"
* Cấu hình để control node có thể kết nối với các host thông qua SSH
Ta mở màn hình lệnh tại thư mục chứa tập tin Vagrantfile, chẳng hạn tập tin này được lưu tại thư mục C:\vagrant\ubuntu
Tại commande line ở thư mục này, ta đánh lệnh : "vagrant up ansible" để khởi động control node tên là ansible
C:\vagrant\ubuntu>vagrant up ansible
Sau khi máy ảo ansible này khởi động, ta kết nối vào nó bằng lệnh "vagrant ssh ansible"
C:\vagrant\ubuntu>vagrant ssh ansible
Tương tự ta cũng khởi động và kết nối vào máy ảo "toan" như sau
C:\vagrant\ubuntu>vagrant up toan
C:\vagrant\ubuntu>vagrant ssh toan
a) Cấu hình các hosts
Tại máy ảo "toan" vừa kết nối đến này, ta sẽ cấu hình để cho control node "ansible" có thể kết nối ssh đến nó như sau.
Trước tiên ta mở tập tin cấu hình ssh /etc/ssh/sshd_config
vagrant@toan:~$ sudo vi /etc/ssh/sshd_config
Rồi kiểm tra các dòng thông tin sau nếu chưa được kích hoạt (tức có dấu # ở đầu dòng hoặc có giá trị là "no") thì ta kích hoạt nó bằng cách xóa dấu # ở đầu dòng hoặc đổi giá trị thành "yes" :
PubkeyAuthentication yes
AuthorizedKeysFile <thông tin đường dẫn>
PasswordAuthentication yes
Sau đó ta khởi động lại service sshd để cập nhật cấu hình vừa thay đổi với lệnh "sudo service sshd restart" :
vagrant@toan:~$ sudo service sshd restart
Với máy ảo "jenkins", ta cũng khởi động và cấu hình tương tự như máy ảo "toan":
C:\vagrant\ubuntu>vagrant up jenkins
C:\vagrant\ubuntu>vagrant ssh jenkins
vagrant@jenkins:~$ sudo vi /etc/ssh/sshd_config
vagrant@jenkins:~$ sudo service sshd restart
b) cấu hình cho control node "ansible"
Trên máy ảo "ansible" ta kiểm tra xem máy đã có public key rsa hay chưa :
vagrant@ansible:~$ ls -l ~/.ssh/
total 4
-rw------- 1 vagrant vagrant 389 Dec 21 09:07 authorized_keys
Trong trường hợp này ta thấy chưa có, ta sẽ phát sinh public key cho máy ảo "ansible" với lệnh ssh-keygen :
vagrant@ansible:~$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/vagrant/.ssh/id_rsa): <nhấn enter để mặc định phát sinh các tập tin này ở thư mục .ssh>
Enter passphrase (empty for no passphrase): <nhấn enter>
Enter same passphrase again: <nhấn enter>
Your identification has been saved in /home/vagrant/.ssh/id_rsa.
Your public key has been saved in /home/vagrant/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:XijkRt3uI++yKn06gMC+n6kvpAUHM5eOs5r8HT1RkBs vagrant@ansible
The key's randomart image is:
+---[RSA 2048]----+
| . .. |
|+ o E.. |
|.B o +.. |
|=.o + ..o |
|.* . +.S o |
|.oo ...o.o |
|=o. + oo o |
|++ = + +o . |
| .** oo=.+o |
+----[SHA256]-----+
Giờ đây public và private key rsa đã được phát sinh :
vagrant@ansible:~$ ls -l ~/.ssh/
total 12
-rw------- 1 vagrant vagrant 389 Dec 21 09:07 authorized_keys
-rw------- 1 vagrant vagrant 1675 Dec 21 09:57 id_rsa
-rw-r--r-- 1 vagrant vagrant 397 Dec 21 09:57 id_rsa.pub
Đến đây ta sao chép public key của máy ảo "ansible" đến các host để cho phép nó kết nối với các máy này thông qua ssh với lệnh ssh-copy-id như sau :
vagrant@ansible:~$ sudo ssh-copy-id vagrant@toan.local
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/vagrant/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: ERROR: ssh: Could not resolve hostname toan.local: Temporary failure in name resolution
Nếu bị lỗi khi kết nối ssh với hostname "toan.local" như trên ta có thế thay thế hostname bằng địa chỉ ip "10.0.0.12". Trong tập tin Vagrantfile địa chỉ ip ứng với hostname "toan" là "10.0.0.12"
vagrant@ansible:~$ sudo ssh-copy-id vagrant@10.0.0.12
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/vagrant/.ssh/id_rsa.pub"
The authenticity of host '10.0.0.12 (10.0.0.12)' can't be established.
ECDSA key fingerprint is SHA256:65N5OhnmyUuoJ0F7qIOa33eA0OGci4CjP1C/aePIlvw.
Are you sure you want to continue connecting (yes/no)? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
vagrant@10.0.0.12's password: <password là vagrant>
Number of key(s) added: 1
Now try logging into the machine, with: "ssh 'vagrant@10.0.0.12'"
and check to make sure that only the key(s) you wanted were added.
Để kiểm tra việc thêm public key thành công, ta kết nối đến host, rồi thoát ra bằng lệnh exit :
vagrant@ansible:~$ ssh vagrant@10.0.0.12
vagrant@toan:~$ exit
Tương tự, ta cũng sao chép public key của máy ảo "ansible" vào máy ảo "jenkins" như trên :
vagrant@ansible:~$ sudo ssh-copy-id vagrant@jenkins.local
* Dùng ansible để cài đặt java 11
Đến đây, ta đã tạo được một mạng lưới gồm control node "ansible" và hai host "toan" và "jenkins" và cấu hình để vm "ansible" có thể kết nối ssh với hai host này. Vậy là từ vm "ansible" ta có thể dùng hai tập tin java11-setup.yml và inventory-all-vms.yml định nghĩa ở trên để cài đặt java 11 lên hai vm "toan" và "jenkins".
Trong thư mục C:\vagrant\ubuntu (tức thư mục chứa tập tin Vagrantfile), ta tạo thư mục "toan" và chép hai tập tin java11-setup.yml và inventory-all-vms.yml vào đây
Từ vm "ansible", ta có thể truy xuất đến hai tập tin này tại /vagrant/toan
Chú ý, nếu ta bị lỗi không kết nối được đến host bằng hostname như ở trên, thì ta phải thay đổi hostname bằng địa chỉ ip trong tập tin inventory-all-vms.yml như sau :
all:
children:
ubuntu_servers:
hosts:
jenkins_ubuntu18:
ansible_host: 10.0.0.11
toan_local:
hosts:
toan_ubuntu16:
ansible_host: 10.0.0.12
Chẳng hạn nếu ta chỉ muốn cài java 11 trên host "toan" thôi chứ không phải trên tất cả các host, thì trong tập tin java11-setup.yml, ta định nghĩa host là toan_ubuntu16 :
- hosts: toan_ubuntu16
Sau đó dùng lệnh ansible-playbook để thực thi :
vagrant@ansible:~$ ansible-playbook /vagrant/toan/java11-setup.yml -i /vagrant/toan/inventory-all-vms.yml
PLAY [toan_ubuntu16] ***************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************
ok: [toan_ubuntu16]
TASK [add openjdk repository] ******************************************************************************************
changed: [toan_ubuntu16]
TASK [install openjdk 11] **********************************************************************************************
changed: [toan_ubuntu16]
PLAY RECAP *************************************************************************************************************
toan_ubuntu16 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Ta thấy java 11 được cài đặt trên host toan_ubuntu16.
Nếu muốn thực hiện cài đặt java 11 trên tất cả các host thì ta định nghĩa host là all trong tập tin java11-setup.yml
- hosts: all
Thực hiện lại lệnh ansible-playbook :
vagrant@ansible:~$ ansible-playbook /vagrant/toan/java11-setup.yml -i /vagrant/toan/inventory-all-vms.yml
PLAY [all] *************************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************
ok: [jenkins_ubuntu18]
ok: [toan_ubuntu16]
TASK [add openjdk repository] ******************************************************************************************
ok: [toan_ubuntu16]
changed: [jenkins_ubuntu18]
TASK [install openjdk 11] **********************************************************************************************
ok: [toan_ubuntu16]
changed: [jenkins_ubuntu18]
PLAY RECAP *************************************************************************************************************
jenkins_ubuntu18 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
toan_ubuntu16 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Ta thấy java 11 được cài đặt trên cả hai host toan_ubuntu16 và jenkins_ubuntu18.