เรียน Ansible: เริ่มต้นใช้ Ansible สำหรับทำ Automation
Learn Ansible: Get Started with Ansible for Automation
— — — — — — — — — — — — — — —
สารบัญเนื้อหาทั้งหมด (My Contents)
— — — — — — — — — — — — — — —
บทความนี้เหมาะสำหรับมือใหม่หัดขับ Ansible เลย เพราะผมจะเขียนไล่ไปตั้งแต่การติดตั้ง Ansible, ความรู้พื้นฐานและส่วนประกอบของ Ansible, การเตรียม lab สำหรับทดสอบ และสุดท้ายคือตัวอย่างการเขียน playbook เพื่อใช้ Ansible ช่วยในการติดตั้งและ configure NGINX เป็น web server ให้กับเรา
โลกของ Ansible นั้นกว้างใหญ่มาก ผมเองคงไม่สามารถพาทุกท่านไปจนถึงจุดหมายได้ภายในบทความเดียว แต่อย่างน้อยก็น่าจะช่วยให้ทุกท่านเริ่มต้นได้ง่ายขึ้น
Lab ที่ใช้ในบทความนี้
ด้านบนนี้คือ logical diagram ของ lab ที่ใช้ในบทความนี้ ซึ่งถูก host อยู่บน AWS ทั้งหมด แต่ไม่จำเป็นว่าจะต้องใช้ AWS เหมือนผมนะครับ ท่านจะใช้ Vagrant, VMware ESXi หรืออะไรก็ได้ ขอแค่ ansible node สามารถ SSH ไปยัง server ทั้งหมดได้เป็นพอ
โดยใน lab นี้ผมมี server ทั้งหมด 5 ตัว ได้แก่
- Ansible node x 1 (Ubuntu Server 20.04)
- Server x 4 (Ubuntu Server 20.04)
เมื่อท่านเตรียม lab เรียบร้อยแล้วก็มาเริ่มเรียน Ansible ไปพร้อมกันเลย
1) ติดตั้ง Ansible บน Ubuntu 20.04
ขั้นตอนนี้คือการติดตั้ง Ansible บน control node ซึ่งที่จริงนั้นมีหลายวิธีมาก(ผมชอบใช้ virtualenv ของ Python) แต่ในบทความนี้ผมขอเลือกวิธีที่ง่ายและสะดวกที่สุดก่อนละกันนะครับ
1.1 ติดตั้ง Ansible บน Ubuntu 20.04 โดยใช้ Package Manager
sudo apt-add-repository ppa:ansible/ansible -y
sudo apt update -y
sudo apt install ansible -y
1.2 ตรวจสอบการติดตั้ง Ansible
ansible --version
ซึ่งถ้าได้ผลลัพธ์แบบด้านล่างนี้ก็ถือว่าเป็นอันเรียบร้อยครับ
ubuntu@ip-172-31-17-121:~$ ansible --version
ansible 2.9.6
config file = /etc/ansible/ansible.cfg
configured module search path = ['/home/ubuntu/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3/dist-packages/ansible
executable location = /usr/bin/ansible
python version = 3.8.5 (default, Jul 28 2020, 12:59:40) [GCC 9.3.0]
2) เตรียมไฟล์สำหรับใช้งาน Ansible
ผมแนะนำว่าให้ clone มาจาก Git repository ของผมได้เลย โดย clone ลงไปที่ ansible node นั่นแหละครับ
git clone https://github.com/nopnithi/ansible_tutorial
โครงสร้างของ project directory จะเป็นแบบนี้
สำหรับ key ที่ใช้ SSH ไปยัง managed node ก็ให้นำมาใส่ไว้เองตาม path ที่ configure ไว้บน ansible.cfg นะครับ
2.1 ไฟล์ ansible.cfg (Config)
[defaults]
inventory = hosts
remote_user = ubuntu
private_key_file = keys/ansibleSSH.pem
host_key_checking = False
หมายเหตุ: เดี๋ยวจะอธิบายรายละเอียดเพิ่มเติมในบทที่ 3
2.2 ไฟล์ hosts (Inventory)
[test_ab:children]
test_a
test_b[test_a]
server1 ansible_ssh_host=172.31.31.216
server2 ansible_ssh_host=172.31.26.150[test_b]
server3 ansible_ssh_host=172.31.17.61
server4 ansible_ssh_host=172.31.27.44
หมายเหตุ: เดี๋ยวจะอธิบายรายละเอียดเพิ่มเติมในบทที่ 3
2.3 ไฟล์ playbooks/install_nginx.yaml (Playbook)
- name: Configure Webserver with NGINX
hosts: test_b
become: True
tasks:
- name: Install NGINX
apt: name=nginx update_cache=yes - name: Copy NGINX Config File
copy: src=~/ansible_tutorial/files/nginx.conf dest=/etc/nginx/sites-available/default - name: Enable Configuration
file: >
dest=/etc/nginx/sites-enabled/default
src=/etc/nginx/sites-available/default
state=link - name: Render index.html and copy it
template: src=~/ansible_tutorial/files/index.html.j2 dest=/usr/share/nginx/html/index.html mode=0644 - name: Restart NGINX Service
service: name=nginx state=restarted
ภายใน playbook นี้มีแค่ play เดียวชื่อ Configure Webserver with NGINX ซึ่งจะรันกับ test_b
group เท่านั้น และภายใน play มีทั้งหมด 5 task ดังนี้
- ติดตั้ง NGINX โดยใช้ apt module
- Copy
nginx.conf
ไปไว้ที่/etc/nginx/sites-available/default
โดยใช้ copy module - สร้าง symbolic link จาก
/etc/nginx/sites-available/default
ไป/etc/nginx/sites-enabled/default
โดยใช้ file module - เรนเดอร์ไฟล์ HTML และนำไปวางที่
/usr/share/nginx/html/index.html
จากนั้นทำการกำหนด CHMOD 664 โดยใช้ template module - รีสตาร์ท NGINX service โดยใช้ service module
หมายเหตุ: เดี๋ยวจะอธิบายรายละเอียดเพิ่มเติมในบทที่ 3
2.4 ไฟล์ files/nginx.conf
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
root /usr/share/nginx/html;
index index.html index.htm; server_name localhost; location / {
try_files $uri $uri/ =404;
}
}
ตัวนี้เป็นไฟล์ configuration ของ NGINX ซึ่งเดี๋ยวเราจะต้อง copy ไปไว้ที่ /etc/nginx/sites-available/default
ด้วย Ansible ครับ
2.5 ไฟล์ files/index.html.j2
<html>
<head>
<title>Ansible Tutorial by Nopnithi</title>
</head>
<body>
<h1>NGINX is running on "{{ inventory_hostname }}"</h1>
<p>This web server is configured by Ansible</p>
</body>
</html>
อันนี้เป็นไฟล์ Jinja2 template ครับ เราสามารถใช้ Ansible ในการ render ไฟล์ออกมาให้เราได้ ในที่นี้จะเป็นไฟล์ HTML โดยมันจะดึงค่าตัวแปร inventory_hostname
มาใช้งานด้วย (เป็น alias ของ server)
3) Ansible เบื้องต้น
3.1) ภาพรวมโครงสร้างของ Ansible Playbook
การจะสั่งงาน Ansible นั้น หลัก ๆ แล้วจะต้องทำผ่าน playbook ซึ่งประกอบด้วยส่วนต่าง ๆ หลายส่วน ดังรูปด้านล่างนี้
และนี่คือหน้าตาของไฟล์ playbook จริง ๆ ซึ่งเขียนอยู่ในรูปแบบของ YAML
ซึ่งเมื่อผมเขียนอธิบายส่วนประกอบต่าง ๆ ลงไปก็จะได้แบบนี้
เอาหละ เมื่อเห็นภาพรวมทั้งหมดแล้วทีนี้มาดูความหมายของแต่ละส่วนกันครับ
3.2) Playbook
ส่วนตัวผมมองว่า playbook คือใบสั่งงาน เป็นไฟล์ที่เราใช้บอกกับ Ansible ว่ามันจะต้องทำอะไรบ้าง และทำกับ host ใดบ้าง ซึ่งเขียนโดยใช้รูปแบบของ YAML
และหนึ่งในข้อดีของ Ansible ที่เหนือกว่า configuration management tool ตัวอื่น ๆ คือเราไม่ต้องเสียเวลานั่งเรียนรู้ภาษาเฉพาะในการเขียนสั่งงานแบบ Chef หรือ Puppet เนื่องจาก YAML เป็น data format ที่ใช้งานกันอย่างแพร่หลายอยู่แล้วในยุคนี้
→ 3.2.1) Play
ภายใน playbook จะต้องมีอย่างน้อยหนึ่ง play ซึ่งใน play ก็จะเป็นการกำหนดกระทำบางอย่างกับ host ใด ๆ (ตัวเดียว, เป็นกลุ่ม หรือทั้งหมด) แต่ในหนึ่ง playbook ของเราสามารถมีได้หลาย play นะครับ ขึ้นอยู่กับการจัดการของเรา
โดยภายในหนึ่ง play จะต้อง…
- กำหนด host ว่า play นี้จะกระทำกับใครบ้าง
- มี task (หรือหลาย task) เพื่อที่จะกระทำบางอย่างกับ host ที่ระบุเอาไว้
→ 3.2.2) Task
Task คืองานย่อย ๆ นั่นเองครับ ซึ่งเราอาจมีหลาย task ภายในหนึ่ง play ก็ได้ เช่น ผมมี play ที่เป็นการติดตั้ง NGINX ซึ่งการติดตั้งก็อาจจะมีหลาย task ประกอบกัน
→ 3.2.3) Module
Module คือ script (หรือ code สำเร็จรูป)พร้อมเรียกใช้ที่ถูก built-in มาอยู่แล้ว เพื่อที่จะทำให้ Ansible นั้นสามารถกระทำการบางอย่างกับ host ปลายทางได้ (ตัวนี้แหละที่ทำให้เราไม่ต้องเขียน code เองเพื่อทำ automation)
เช่น ถ้าผมต้องการ copy ไฟล์ nopnithi.txt จากเครื่องผม(ที่รัน Ansible)ไปยังไดเรกทอรี่ opt ของ host ทั้งหมด ผมก็จะเรียกใช้ copy
module แบบนี้
- name: Copy nopnithi.txt to destination host(s)
copy:
src: /files/nopnithi.txt
dest: /opt/nopnithi.txt
ซึ่งใน Ansible มี module เยอะมาก เราสามารถเลือกใช้งานได้จากที่นี่เลย
https://docs.ansible.com/ansible/2.9/modules/modules_by_category.html
3.3) Config (ansible.cfg)
แน่นอนว่า ansible.cfg คือไฟล์ configuration ของ Ansible โดยในบทความนี้เราจะ configure กันแค่นี้พอครับ
[defaults]
inventory = hosts
remote_user = ubuntu
private_key_file = ansibleSSH.pem
host_key_checking = False
แล้วความหมายของแต่ละบรรทัดมีอะไรบ้าง?
- inventory → เป็นการระบุว่าไฟล์ inventory ชื่ออะไรและอยู่ที่ไหน
- remote_user → กำหนด user สำหรับใช้ SSH ไปยัง host
- private_key_file → กำหนด SSH private key สำหรับใช้ SSH
- host_key_checking → ไม่ต้องมีการเช็ค host key
ทีนี้ยังมีอีกเรื่องที่ควรรู้ครับ นั่นก็คือที่จริงแล้ว ansible.cfg สามารถวางอยู่ได้หลายที่ ซึ่ง Ansible จะไล่ค้นหาเรียงไปตามลำดับนี้ (ถ้าเจอที่ไหนก่อนก็เอาที่นั่น)
- ansible.cfg ใน environment variable ชื่อ
ANSIBLE_CONFIG
- ./ansible.cfg หรือก็คือ ansible.cfg ใน current directory (ที่รัน Ansible อยู่)
- ~/.ansible.cfg หรือก็คือ ansible.cfg ใน home directory
- /etc/ansible/ansible.cfg
ซึ่งในกรณีของเรานั้นใช้ตามข้อที่ 2 ครับ คือวาง ansible.cfg ไว้ที่เดียวกับ project directory เลยนั่นเอง
ส่วนเราสามารถ configure อะไรบ้างสามารถไปดูได้ที่นี่ครับ
https://github.com/ansible/ansible/blob/devel/examples/ansible.cfg
3.4) Inventory
Inventory คือไฟล์ text ที่ช่วยในการจัดการ host ให้ง่ายขึ้น ทำหน้าที่ในการอธิบายรายละเอียดเกี่ยวกับ host ให้ Ansible รู้จักและเข้าใจ
ตัวอย่าง: ไฟล์ inventory แบบง่าย ๆ
web1.nopnithi.com
web2.nopnithi.com
web3.nopnithi.com
10.1.1.1
10.1.1.2
10.1.1.3
และยังสามารถใช้ฟังก์ชั่น range(แบบใน Python) ในไฟล์ inventory ได้ด้วยนะ
web[1:3].nopnithi.com
ก็จะหมายถึง webserver1.nopnithi.com
, webserver2.nopnithi.com
และ webserver3.nopnithi.com
หรืออีกตัวอย่างนึงที่ใช้จริงในบทความก็จะประมาณนี้
server1 ansible_ssh_host=172.31.26.111
server2 ansible_ssh_host=172.31.34.44
server3 ansible_ssh_host=172.31.29.192
server4 ansible_ssh_host=172.31.18.232
โดยด้านบนที่เขียนไปก็เพื่อจะบอก Ansible ให้รู้จัก host ทั้งหมดที่เรามี เช่น บอกว่า server1
ต้องใช้ IP 172.31.26.111 ในการ SSH เป็นต้น
ส่วนตรง server1
, server2
, server3
และ server4
ที่เราเขียนลงไฟล์ inventory นั้นเป็น alias นะครับ ไม่ใช่ hostname จริง ๆ (หรือจะเป็นก็ได้)
→ 3.4.1) Group
ภายในไฟล์ inventory เราสามารถจัด group ให้กับ host ของเราได้โดยใช้ [<group-name>]
เช่น ผมกำหนดให้มี 2 group คือ test_a
และ test_b
[test_a]
server1 ansible_ssh_host=172.31.26.111
server2 ansible_ssh_host=172.31.34.44[test_b]
server3 ansible_ssh_host=172.31.29.192
server4 ansible_ssh_host=172.31.18.232
ดังนั้นเมื่อผมนำชื่อ group ไปใช้เรียกใช้ผ่าน playbook แบบนี้
---
- hosts: test_a
tasks:
- name: Check IP Connectivity
ping:
นั่นแปลว่า play นี้ผมจะทำการ ping ไปยัง server1
และ server2
ยังไม่หมดครับ เรายังสามารถจัด group ของ group ได้อีกด้วยแบบนี้
[test_ab:children]
test_a
test_b[test_a]
server1 ansible_ssh_host=172.31.26.111
server2 ansible_ssh_host=172.31.34.44[test_b]
server3 ansible_ssh_host=172.31.29.192
server4 ansible_ssh_host=172.31.18.232
ดังนั้นการเรียกใช้ test_ab
group ก็จะหมายถึง host ทั้งหมดใน test_a
group และ test_b
group รวมกันนั่นเอง
→ 3.4.2) Variable
จากตัวอย่างไฟล์ inventory ที่ผ่านมา ที่จริงเรามีการใช้งาน variable อยู่แล้วในระดับ host นั่นก็คือ ansible_ssh_host
นั่นเอง เพียงแต่สองตัวนี้เป็น default variable ใน Ansible (เรียกว่า behavioral inventory parameter)
แต่เรายังสามารถสร้าง variable ขึ้นมาใช้งานภายใน playbook เองได้หลายวิธี
- เขียน variable เอาไว้ในไฟล์ inventory เลย
- แต่วิธีที่เป็น best practice นั่นก็คือส่วนใหญ่เรามักจะจัด variable สำหรับแต่ละ group หรือ host ไว้ในไฟล์แยกต่างหากเลย
โดยถ้าเป็น
- Variable สำหรับ host จะไว้ในโฟลเดอร์ชื่อ host_vars
- Variable สำหรับ group จะไว้ในโฟลเดอร์ชื่อ group_vars
ตัวอย่างเช่น ถ้า server ใน test_a
group และ test_a
group ของผมอาจจะมีการใช้งาน parameter บางตัวที่ต่างกัน แต่ host ภายใน group นั้นใช้เหมือนกัน เราก็จะมากำหนดลงใน group_vars แบบนี้
ตัวอย่างการกำหนด Variable สำหรับ Group แบบธรรมดา
ไฟล์ group_vars/test_a
dns_primary: dns1.nopnithi.com
dns_secondary: dns2.nopnithi.com
ไฟล์ group_vars/test_b
dns_primary: dns3.nopnithi.com
dns_secondary: dns4.nopnithi.com
เวลาจะเรียกใช้ variable แบบนี้บน playbook ก็ทำแบบนี้ได้เลย เช่น {{ dns_primary }}
หรือ {{ dns_secondary }}
เป็นต้น
ตัวอย่างการกำหนด Variable สำหรับ Group แบบ YAML
ไฟล์ group_vars/test_a.yaml
dns:
primary: dns1.nopnithi.com
secondary: dns2.nopnithi.com
ไฟล์ group_vars/test_b.yaml
dns:
primary: dns3.nopnithi.com
secondary: dns4.nopnithi.com
วิธีเรียกใช้ variable ที่เขียนด้วย YAML จะแตกต่างจากแบบธรรมดานิดหน่อยครับ เพราะเราจะต้องเรียกแบบนี้แทน เช่น {{ dns.primary }}
หรือ {{ dns.secondary }}
เป็นต้น
แต่เวลาใช้งานจริงนั้นต้องเลือกเอาแค่แบบใดแบบหนึ่งเท่านั้น และก็นี่คือตัวอย่าง playbook ที่มีการเรียกใช้งาน variable ด้วย ลองเอาไปรันเล่น ๆ ดูก็ได้ครับ
- name: call group variable and return to a new variable
hosts: test_a
tasks:
- name: run echo command and capture output to myoutputvar
command: "echo {{ dns.primary }}"
register: myoutputvar
- debug: var=myoutputvar
จะได้ output แบบนี้
ok: [server1] => {
"myoutputvar": {
"changed": true,
"cmd": [
"echo",
"dns1.nopnithi.com"
],
"delta": "0:00:00.002880",
"end": "2020-11-16 14:01:07.400410",
"failed": false,
"rc": 0,
"start": "2020-11-16 14:01:07.397530",
"stderr": "",
"stderr_lines": [],
"stdout": "dns1.nopnithi.com",
"stdout_lines": [
"dns1.nopnithi.com"
]
}
}
ok: [server2] => {
"myoutputvar": {
"changed": true,
"cmd": [
"echo",
"dns1.nopnithi.com"
],
"delta": "0:00:00.002845",
"end": "2020-11-16 14:01:07.393888",
"failed": false,
"rc": 0,
"start": "2020-11-16 14:01:07.391043",
"stderr": "",
"stderr_lines": [],
"stdout": "dns1.nopnithi.com",
"stdout_lines": [
"dns1.nopnithi.com"
]
}
}
4) รัน Playbook เพื่อติดตั้งและ Configure NGINX
เดี๋ยวเราจะใช้ Ansible ช่วยในการติดตั้งและ configure NGINX บน test_a
group นั่นก็คือ server3
และ server4
นั่นเอง โดยใช้ command นี้
ansible-playbook playbooks/install_nginx.yaml
แต่เดี๋ยวก่อนจะรัน ansible-playbook ผมจะพามาเช็คก่อนว่า ณ ตอนนี้เรายังไม่สามารถ access เข้า website ใน server3
และ server4
ได้
ทีนี้เรามาลองรัน playbook กันเลยครับ
ผลที่ได้ก็คือผมสามารถ access เข้า website ใน server3
และ server4
ได้แล้ว
สรุป
ความจริงแล้วเนื้อหาของ Ansible นั้นยังมีอีกเยอะมาก แม้เราจะตัดเรื่องวิธีการใช้งาน module ต่าง ๆ ออกไปแล้วก็ตาม แต่อย่างน้อยผมก็หวังว่าบทความนี้จะช่วยให้ทุกท่านที่กำลังเริ่มต้นเรียนรู้ Ansible สามารถไปต่อได้ลื่นไหลมากขึ้นครับ
— — — — — — — — — — — — — — —
สารบัญเนื้อหาทั้งหมด (My Contents)
— — — — — — — — — — — — — — —