เมื่อแอปพลิเคชันเติบโตจนต้องแยกเป็น Microservices หลายตัว การ deploy และดูแลระบบด้วยมือทีละเซิร์ฟเวอร์ทำให้เกิดความผิดพลาดและใช้เวลามาก Ansible และ Docker ทำงานเสริมกันได้ดี — Docker จัดการ packaging และ isolation ของแต่ละ service ส่วน Ansible ทำให้กระบวนการ deploy เป็นอัตโนมัติ ทำซ้ำได้ และตรวจสอบได้ในทุกสภาพแวดล้อม
Workshop นี้จะสร้าง Ansible Playbook สำหรับ deploy Microservices Stack ที่ประกอบด้วย API Gateway, User Service และ Product Service บน Docker ครอบคลุมตั้งแต่การติดตั้ง Docker, สร้าง Compose template ด้วย Jinja2, deploy services, ตรวจสอบ health check ไปจนถึงกลยุทธ์ rolling update และ rollback
โครงสร้างโปรเจกต์
จัดโครงสร้างโปรเจกต์ให้แต่ละ Role รับผิดชอบงานเดียวชัดเจน:
ansible-microservices/
├── inventory/
│ ├── hosts.ini
│ └── group_vars/
│ └── all.yml
├── roles/
│ ├── docker-install/
│ │ └── tasks/
│ │ └── main.yml
│ ├── deploy-microservices/
│ │ ├── tasks/
│ │ │ └── main.yml
│ │ └── templates/
│ │ └── docker-compose.yml.j2
│ └── healthcheck/
│ └── tasks/
│ └── main.yml
├── site.yml
└── rollback.yml
ขั้นตอนที่ 1 — เตรียม Inventory และ Variables
กำหนด host ที่จะ deploy microservices และตัวแปรที่ใช้ร่วมกัน:
inventory/hosts.ini
[microservices]
app01 ansible_host=10.0.1.10
app02 ansible_host=10.0.1.11
[microservices:vars]
ansible_user=ubuntu
ansible_python_interpreter=/usr/bin/python3
inventory/group_vars/all.yml
---
# Docker image versions
images:
api_gateway: "nginx:1.25-alpine"
user_service: "myapp/user-service:{{ user_service_version }}"
product_service: "myapp/product-service:{{ product_service_version }}"
# Service versions (override ตอน deploy)
user_service_version: "1.0.0"
product_service_version: "1.0.0"
# Port mappings
ports:
api_gateway: 80
user_service: 8001
product_service: 8002
# Deploy path
deploy_dir: /opt/microservices
# Docker registry
registry_url: registry.example.com
registry_user: deploy
registry_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
38396265356365613632613062363...
# Health check settings
health_check_retries: 5
health_check_delay: 10
ขั้นตอนที่ 2 — ติดตั้ง Docker ด้วย Ansible Role
Role docker-install ติดตั้ง Docker Engine และ Compose Plugin จาก official repository:
roles/docker-install/tasks/main.yml
---
- name: ติดตั้ง dependencies สำหรับ Docker
apt:
name:
- ca-certificates
- curl
- gnupg
- lsb-release
state: present
update_cache: yes
- name: เพิ่ม Docker GPG key
apt_key:
url: https://download.docker.com/linux/ubuntu/gpg
state: present
- name: เพิ่ม Docker repository
apt_repository:
repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
state: present
filename: docker
- name: ติดตั้ง Docker Engine และ Compose Plugin
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
state: present
update_cache: yes
- name: เริ่ม Docker service
systemd:
name: docker
enabled: yes
state: started
- name: เพิ่ม deploy user เข้ากลุ่ม docker
user:
name: "{{ ansible_user }}"
groups: docker
append: yes
- name: ตรวจสอบ Docker version
command: docker --version
register: docker_ver
changed_when: false
- name: แสดง Docker version
debug:
msg: "{{ docker_ver.stdout }}"
ขั้นตอนที่ 3 — สร้าง Docker Compose Template ด้วย Jinja2
ใช้ Jinja2 template สร้าง docker-compose.yml ที่มีค่าตัวแปรต่างกันตาม environment โดยอัตโนมัติ:
roles/deploy-microservices/templates/docker-compose.yml.j2
version: "3.8"
services:
api-gateway:
image: {{ images.api_gateway }}
container_name: api-gateway
ports:
- "{{ ports.api_gateway }}:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- user-service
- product-service
restart: unless-stopped
networks:
- microservices-net
user-service:
image: {{ registry_url }}/{{ images.user_service }}
container_name: user-service
ports:
- "{{ ports.user_service }}:8001"
environment:
- DB_HOST={{ db_host | default('db') }}
- DB_NAME=userdb
- LOG_LEVEL={{ log_level | default('info') }}
restart: unless-stopped
networks:
- microservices-net
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8001/health"]
interval: 30s
timeout: 10s
retries: 3
product-service:
image: {{ registry_url }}/{{ images.product_service }}
container_name: product-service
ports:
- "{{ ports.product_service }}:8002"
environment:
- DB_HOST={{ db_host | default('db') }}
- DB_NAME=productdb
- LOG_LEVEL={{ log_level | default('info') }}
restart: unless-stopped
networks:
- microservices-net
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8002/health"]
interval: 30s
timeout: 10s
retries: 3
networks:
microservices-net:
driver: bridge
ขั้นตอนที่ 4 — Deploy Microservices ด้วย Ansible
Role deploy-microservices ทำหน้าที่ login registry, สร้าง directory, copy compose file, pull images และ start services:
roles/deploy-microservices/tasks/main.yml
---
- name: สร้าง deploy directory
file:
path: "{{ deploy_dir }}"
state: directory
mode: "0755"
- name: Login Docker registry
community.docker.docker_login:
registry_url: "{{ registry_url }}"
username: "{{ registry_user }}"
password: "{{ registry_password }}"
no_log: true
- name: Copy docker-compose.yml จาก template
template:
src: docker-compose.yml.j2
dest: "{{ deploy_dir }}/docker-compose.yml"
mode: "0644"
notify: restart microservices
- name: Pull Docker images ล่าสุด
community.docker.docker_compose_v2_pull:
project_src: "{{ deploy_dir }}"
- name: Start microservices ด้วย Docker Compose
community.docker.docker_compose_v2:
project_src: "{{ deploy_dir }}"
state: present
pull: missing
register: compose_result
- name: แสดงสถานะ services
debug:
var: compose_result.services
เพิ่ม Handler สำหรับ restart เมื่อ compose file เปลี่ยน:
---
# roles/deploy-microservices/handlers/main.yml
- name: restart microservices
community.docker.docker_compose_v2:
project_src: "{{ deploy_dir }}"
state: restarted
ขั้นตอนที่ 5 — Health Check หลัง Deploy
Role healthcheck ตรวจสอบว่าทุก service ตอบสนองก่อนถือว่า deploy สำเร็จ:
roles/healthcheck/tasks/main.yml
---
- name: รอให้ API Gateway พร้อม
uri:
url: "http://{{ ansible_host }}/health"
method: GET
status_code: 200
register: gw_result
retries: "{{ health_check_retries }}"
delay: "{{ health_check_delay }}"
until: gw_result.status == 200
- name: ตรวจสอบ User Service health endpoint
uri:
url: "http://{{ ansible_host }}:{{ ports.user_service }}/health"
method: GET
status_code: 200
register: user_result
retries: "{{ health_check_retries }}"
delay: "{{ health_check_delay }}"
until: user_result.status == 200
- name: ตรวจสอบ Product Service health endpoint
uri:
url: "http://{{ ansible_host }}:{{ ports.product_service }}/health"
method: GET
status_code: 200
register: product_result
retries: "{{ health_check_retries }}"
delay: "{{ health_check_delay }}"
until: product_result.status == 200
- name: สรุปผล Health Check
debug:
msg:
- "API Gateway: {{ gw_result.status }}"
- "User Service: {{ user_result.status }}"
- "Product Service: {{ product_result.status }}"
ขั้นตอนที่ 6 — Main Playbook และ Rolling Update
site.yml รวม Role ทั้งหมดและรองรับการ deploy แบบ rolling (ทีละเซิร์ฟเวอร์) เพื่อไม่ให้ระบบหยุดทำงาน:
---
- name: ติดตั้ง Docker บนทุก server
hosts: microservices
become: yes
gather_facts: yes
roles:
- docker-install
- name: Deploy Microservices (Rolling Update)
hosts: microservices
become: yes
serial: 1 # deploy ทีละ 1 เซิร์ฟเวอร์
max_fail_percentage: 0 # หยุดทันทีถ้า server ใดล้มเหลว
vars_prompt:
- name: user_service_version
prompt: "User Service version (e.g. 1.2.0)"
default: "1.0.0"
private: no
- name: product_service_version
prompt: "Product Service version (e.g. 1.2.0)"
default: "1.0.0"
private: no
roles:
- deploy-microservices
- healthcheck
รัน deploy ด้วยคำสั่ง:
ansible-playbook -i inventory/hosts.ini site.yml --ask-vault-pass
หรือกำหนด version ผ่าน extra-vars โดยไม่ต้องป้อนทาง prompt:
ansible-playbook -i inventory/hosts.ini site.yml \
--ask-vault-pass \
-e "user_service_version=1.2.0 product_service_version=1.2.0"
ขั้นตอนที่ 7 — Rollback เมื่อ Deploy ล้มเหลว
ไฟล์ rollback.yml ใช้คืนค่า version เดิมเมื่อ deploy ใหม่มีปัญหา โดยระบุ version ก่อนหน้าผ่าน extra-vars:
---
- name: Rollback Microservices
hosts: microservices
become: yes
serial: 1
vars_prompt:
- name: user_service_version
prompt: "Rollback User Service to version"
private: no
- name: product_service_version
prompt: "Rollback Product Service to version"
private: no
roles:
- deploy-microservices
- healthcheck
รัน rollback:
ansible-playbook -i inventory/hosts.ini rollback.yml \
--ask-vault-pass \
-e "user_service_version=1.1.0 product_service_version=1.1.0"
เทคนิคเพิ่มเติม
นอกจากโครงสร้างหลัก ยังมีเทคนิคที่ช่วยให้ workflow สมบูรณ์ขึ้น ได้แก่ การใช้ Tags เพื่อ deploy เฉพาะ service ที่ต้องการ (ansible-playbook site.yml --tags user-service), การใช้ Ansible Vault เพื่อเก็บ registry credentials อย่างปลอดภัย, และการสร้าง Staging environment ด้วย inventory แยก (inventory/staging/hosts.ini) เพื่อทดสอบก่อน deploy production
สำหรับ environment ที่มี services หลายตัว แนะนำให้ใช้ community.docker.docker_compose_v2 module แทนการ shell docker compose up โดยตรง เพราะ module รองรับ idempotency และให้ข้อมูล diff ของ services ที่เปลี่ยนแปลงกลับมาใน register variable ซึ่งนำไปใช้ใน subsequent tasks หรือ notification ได้
สรุป
Workshop นี้สร้าง Ansible Playbook ที่ครบวงจรสำหรับ Microservices บน Docker ตั้งแต่ติดตั้ง Docker Engine ด้วย Role ที่ reusable, สร้าง docker-compose.yml ด้วย Jinja2 template ที่ปรับ version และ config ตาม environment, deploy แบบ rolling update ทีละเซิร์ฟเวอร์เพื่อไม่ให้ระบบหยุด, ตรวจสอบ health check ทุก service ด้วย uri module ไปจนถึง rollback อย่างรวดเร็วด้วย playbook แยก
การผสาน Ansible กับ Docker ช่วยให้ทีมมี deployment workflow ที่ standardized, ตรวจสอบได้ด้วย version control และปรับขนาดได้โดยเพียงเพิ่ม host ในไฟล์ inventory — โดยไม่ต้องเขียน script ใหม่ทุกครั้ง

