การตั้งค่า Load Balancer ด้วยมือบนหลาย Backend Server ใช้เวลานานและมีโอกาสพลาดในขั้นตอน configuration โดยเฉพาะเมื่อต้องทำซ้ำบน Server หลายเครื่องพร้อมกัน Ansible ช่วยให้กระบวนการนี้เป็นอัตโนมัติ ตั้งค่า Nginx Load Balancer และ Backend Servers ได้พร้อมกันด้วย Playbook เดียว
Workshop นี้จะสร้าง Infrastructure ที่ประกอบด้วย Nginx Load Balancer 1 เครื่องพร้อม Backend Application Servers อีก 2 เครื่อง โดยใช้ Ansible จัดการ Configuration ทั้งหมดแบบ Idempotent
โครงสร้าง Infrastructure และ Project
Infrastructure ที่เราจะสร้างประกอบด้วย 3 เซิร์ฟเวอร์: Load Balancer 1 ตัว และ Backend 2 ตัว
Infrastructure:
lb01 (192.168.1.10) — Nginx Load Balancer
backend01 (192.168.1.11) — App Server #1
backend02 (192.168.1.12) — App Server #2
Project Structure:
load-balancer/
├── inventory/
│ └── hosts.ini
├── group_vars/
│ ├── all.yml
│ ├── loadbalancers.yml
│ └── backends.yml
├── roles/
│ ├── loadbalancer/
│ │ ├── tasks/main.yml
│ │ ├── templates/
│ │ │ └── nginx-lb.conf.j2
│ │ └── handlers/main.yml
│ └── backend/
│ ├── tasks/main.yml
│ ├── templates/
│ │ └── app.conf.j2
│ └── handlers/main.yml
└── site.yml
Inventory: แยก Group ตามบทบาท
การแยก Host ออกเป็น Group ตามบทบาทช่วยให้ Apply Role ที่เหมาะสมกับแต่ละกลุ่มได้:
[loadbalancers]
lb01 ansible_host=192.168.1.10 ansible_user=ubuntu
[backends]
backend01 ansible_host=192.168.1.11 ansible_user=ubuntu
backend02 ansible_host=192.168.1.12 ansible_user=ubuntu
[all:vars]
ansible_ssh_private_key_file=~/.ssh/id_rsa
ansible_python_interpreter=/usr/bin/python3
Variables: กำหนด Backend Pool
กำหนด Variables สำหรับ Load Balancer ใน group_vars/loadbalancers.yml — Ansible จะรวม IP ของ Backend servers อัตโนมัติจาก Inventory:
# group_vars/loadbalancers.yml
lb_method: least_conn # round_robin (ค่าเริ่มต้น), least_conn, ip_hash
lb_port: 80
backend_port: 8080
# รวม backend IPs จาก inventory group อัตโนมัติ
backend_servers: "{{ groups['backends'] | map('extract', hostvars, 'ansible_host') | list }}"
กำหนด Variables สำหรับ Backend servers ใน group_vars/backends.yml:
# group_vars/backends.yml
app_port: 8080
app_user: www-data
app_root: /var/www/app
server_id: "{{ inventory_hostname }}" # ใช้ชื่อ host เป็น Server ID ในการ debug
Role: Load Balancer
สร้าง roles/loadbalancer/tasks/main.yml:
---
- name: Install Nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Deploy Load Balancer configuration
template:
src: nginx-lb.conf.j2
dest: /etc/nginx/conf.d/loadbalancer.conf
owner: root
group: root
mode: "0644"
notify: Reload Nginx
- name: Remove default site
file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: Reload Nginx
- name: Ensure Nginx is started and enabled
service:
name: nginx
state: started
enabled: yes
สร้าง roles/loadbalancer/templates/nginx-lb.conf.j2 — Load Balancer config ที่ดึง Backend IPs จาก Variables:
upstream backend_pool {
{{ lb_method }};
{% for server_ip in backend_servers %}
server {{ server_ip }}:{{ backend_port }};
{% endfor %}
keepalive 32;
}
server {
listen {{ lb_port }};
server_name _;
access_log /var/log/nginx/lb.access.log;
error_log /var/log/nginx/lb.error.log;
location / {
proxy_pass http://backend_pool;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
}
# Health check endpoint สำหรับ monitoring
location /lb-health {
return 200 "OK\n";
add_header Content-Type text/plain;
}
}
Jinja2 loop {% for server_ip in backend_servers %} จะขยายเป็น upstream entries ตามจำนวน Backend จริงใน Inventory โดยอัตโนมัติ ทำให้เพิ่ม Backend ได้เพียงแค่เพิ่ม Host ใน hosts.ini
Role: Backend Application Server
สร้าง roles/backend/tasks/main.yml — ติดตั้ง Nginx ฝั่ง Backend และ Deploy Application:
---
- name: Install Nginx for backend
apt:
name: nginx
state: present
update_cache: yes
- name: Create application directory
file:
path: "{{ app_root }}"
state: directory
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0755"
- name: Deploy application config (listen on backend port)
template:
src: app.conf.j2
dest: /etc/nginx/conf.d/app.conf
owner: root
group: root
mode: "0644"
notify: Reload Nginx
- name: Remove default site
file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: Reload Nginx
- name: Deploy application (index page identifying this backend)
copy:
content: |
<!DOCTYPE html>
<html>
<head><title>Backend: {{ server_id }}</title></head>
<body>
<h1>Response from: {{ server_id }}</h1>
<p>IP: {{ ansible_host }}</p>
</body>
</html>
dest: "{{ app_root }}/index.html"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0644"
- name: Ensure Nginx is started and enabled
service:
name: nginx
state: started
enabled: yes
สร้าง roles/backend/templates/app.conf.j2:
server {
listen {{ app_port }};
server_name _;
root {{ app_root }};
index index.html index.htm;
access_log /var/log/nginx/app.access.log;
error_log /var/log/nginx/app.error.log;
location / {
try_files $uri $uri/ =404;
}
# Health check สำหรับ Load Balancer ตรวจสอบ
location /health {
return 200 "{{ server_id }} OK\n";
add_header Content-Type text/plain;
}
}
Main Playbook: site.yml
สร้าง site.yml ที่ Apply Role ต่างกันสำหรับ Group ต่างกัน:
---
# Deploy Backend servers ก่อน Load Balancer
- name: Configure Backend Application Servers
hosts: backends
become: yes
roles:
- backend
# Deploy Load Balancer หลังจาก Backend พร้อมแล้ว
- name: Configure Load Balancer
hosts: loadbalancers
become: yes
roles:
- loadbalancer
post_tasks:
- name: Wait for Load Balancer to be ready
wait_for:
host: "{{ ansible_host }}"
port: "{{ lb_port }}"
delay: 2
timeout: 30
delegate_to: localhost
become: no
- name: Verify Load Balancer responds
uri:
url: "http://{{ ansible_host }}/lb-health"
status_code: 200
delegate_to: localhost
become: no
register: lb_check
- name: Show Load Balancer health check result
debug:
msg: "Load Balancer health: {{ lb_check.status }}"
รัน Playbook และตรวจสอบ Load Balancing
# Deploy ทั้งหมด
ansible-playbook -i inventory/hosts.ini site.yml
# ทดสอบ Load Balancing — รัน curl หลายครั้งควรได้ response จาก Backend ต่างกัน
for i in $(seq 1 6); do
curl -s http://192.168.1.10/ | grep "Response from"
done
ผลที่ควรเห็น — Load Balancer กระจาย Request ไปยัง Backend ทั้งสอง:
Response from: backend01
Response from: backend02
Response from: backend01
Response from: backend02
Response from: backend01
Response from: backend02
ตรวจสอบ Backend Health แต่ละตัว
# ตรวจสอบ health endpoint ของ backend แต่ละตัว
ansible backends -i inventory/hosts.ini -m uri \
-a "url=http://localhost:8080/health" --become
# ดู Nginx upstream status
ansible loadbalancers -i inventory/hosts.ini -m command \
-a "nginx -T" --become | grep upstream
เพิ่ม Backend Server โดยไม่แก้ Playbook
จุดเด่นของ Design นี้คือการ Scale โดยเพิ่มแค่ Inventory:
# เพิ่ม backend03 ใน hosts.ini
[backends]
backend01 ansible_host=192.168.1.11 ansible_user=ubuntu
backend02 ansible_host=192.168.1.12 ansible_user=ubuntu
backend03 ansible_host=192.168.1.13 ansible_user=ubuntu # เพิ่มใหม่
# รัน Playbook ใหม่ — Ansible จะ:
# 1. Configure backend03 ด้วย Role "backend"
# 2. Update nginx-lb.conf บน lb01 ให้รวม backend03:8080 ใน upstream pool อัตโนมัติ
ansible-playbook -i inventory/hosts.ini site.yml
เนื่องจาก template ใช้ {% for server_ip in backend_servers %} และ backend_servers ดึงมาจาก groups['backends'] ใน Inventory การเพิ่ม Host ใหม่จึงอัพเดต Load Balancer config โดยอัตโนมัติ
สรุป
Workshop นี้สร้าง Load Balancer + Multiple Backend Infrastructure ด้วย Ansible โดยมี Design สำคัญสองจุด ได้แก่ การใช้ Jinja2 Loop ใน Template ทำให้ upstream pool อัพเดตอัตโนมัติตาม Inventory และการแยก Playbook ให้ Deploy Backend ก่อน Load Balancer เพื่อให้ระบบพร้อมรับ Traffic ตั้งแต่แรก
การ Scale เพิ่ม Backend เพียงแค่เพิ่ม Host ใน Inventory แล้วรัน Playbook ซ้ำ โดยไม่ต้องแก้ไข Template หรือ Task ใดๆ ทำให้ Infrastructure ขยายตัวได้อย่างปลอดภัยและรวดเร็ว

