Ansible loop ใช้รัน task ซ้ำหลายครั้งด้วยข้อมูลที่ต่างกัน — เช่น ติดตั้ง packages หลายตัว, สร้างหลาย users, หรือ deploy หลาย config files ในคำสั่งเดียว แทนที่จะเขียน task แยกสำหรับแต่ละรายการ
บทความนี้ครอบคลุม syntax พื้นฐานของ loop, การวนซ้ำบน list, dictionary, และ nested data, การใช้ loop_control เพื่อควบคุม output, การใช้ register กับ loop และ pattern สำหรับ bulk provisioning
loop พื้นฐาน — วนซ้ำบน List
ใช้ loop กับ list ของ string — แต่ละ item จะถูกเข้าถึงผ่าน {{ item }} ใน task loop เป็น syntax มาตรฐานที่แนะนำใน Ansible 2.5+ แทน with_items รุ่นเก่า
---
- name: Basic loop usage
hosts: all
become: true
tasks:
# วนซ้ำบน list ของ string
- name: Install required packages
apt:
name: "{{ item }}"
state: present
update_cache: true
loop:
- nginx
- curl
- git
- unzip
- python3-pip
# สร้างหลาย directories
- name: Create application directories
file:
path: "{{ item }}"
state: directory
owner: appuser
mode: '0755'
loop:
- /opt/myapp
- /opt/myapp/bin
- /opt/myapp/config
- /opt/myapp/logs
- /opt/myapp/data
# ลบหลาย files
- name: Remove old log files
file:
path: "{{ item }}"
state: absent
loop:
- /var/log/myapp/old.log
- /var/log/myapp/backup.log
- /tmp/myapp.pid
วนซ้ำบน List of Dictionaries
เมื่อต้องการส่งหลาย parameters ต่อรายการ ใช้ list of dictionaries — เข้าถึงแต่ละ field ด้วย {{ item.field_name }}
---
- name: Loop over dictionaries
hosts: all
become: true
tasks:
# สร้างหลาย users พร้อม properties
- name: Create system users
user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
groups: "{{ item.groups }}"
shell: "{{ item.shell | default('/bin/bash') }}"
state: present
loop:
- { name: appuser, uid: 1001, groups: "www-data", shell: /bin/bash }
- { name: deploy, uid: 1002, groups: "deploy", shell: /bin/bash }
- { name: monitor, uid: 1003, groups: "monitor", shell: /bin/false }
# Deploy หลาย config files จาก templates
- name: Deploy configuration files
template:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
owner: "{{ item.owner | default('root') }}"
mode: "{{ item.mode | default('0644') }}"
loop:
- { src: nginx.conf.j2, dest: /etc/nginx/nginx.conf }
- { src: app.conf.j2, dest: /etc/myapp/app.conf, owner: appuser, mode: '0640' }
- { src: logrotate.conf.j2, dest: /etc/logrotate.d/myapp }
# เพิ่ม firewall rules
- name: Open firewall ports
ufw:
rule: allow
port: "{{ item.port }}"
proto: "{{ item.proto | default('tcp') }}"
loop:
- { port: "22", proto: tcp }
- { port: "80", proto: tcp }
- { port: "443", proto: tcp }
- { port: "8080" }
วนซ้ำบน Variable List
แทนที่จะ hardcode list ใน task สามารถอ่านจาก variable ที่ define ใน vars, inventory หรือ group vars ทำให้ Playbook ยืดหยุ่นและ reusable มากขึ้น
---
- name: Loop with variables
hosts: all
become: true
vars:
required_packages:
- nginx
- nodejs
- postgresql-client
app_users:
- name: appuser
groups: www-data
- name: deploy
groups: deploy
vhosts:
- name: mysite
domain: mysite.example.com
port: 80
- name: api
domain: api.example.com
port: 8080
tasks:
- name: Install packages from variable
apt:
name: "{{ item }}"
state: present
loop: "{{ required_packages }}"
- name: Create users from variable
user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
state: present
loop: "{{ app_users }}"
- name: Deploy virtual hosts
template:
src: vhost.conf.j2
dest: "/etc/nginx/conf.d/{{ item.name }}.conf"
loop: "{{ vhosts }}"
notify: Reload Nginx
loop กับ register — เก็บผลทุก Iteration
เมื่อใช้ register กับ loop ผลลัพธ์จะถูกเก็บเป็น list ใน .results property — แต่ละ item ใน list มี .item บอกว่าผลนั้นมาจาก iteration ไหน
---
- name: register with loop
hosts: all
tasks:
# ตรวจสถานะหลาย services
- name: Check service status
command: "systemctl is-active {{ item }}"
register: service_status
ignore_errors: true
changed_when: false
loop:
- nginx
- postgresql
- myapp
- redis
# แสดงสถานะแต่ละ service
- name: Show service status
debug:
msg: "{{ item.item }}: {{ 'running' if item.rc == 0 else 'stopped' }}"
loop: "{{ service_status.results }}"
# ตรวจ files หลายไฟล์
- name: Check if config files exist
stat:
path: "{{ item }}"
register: config_checks
loop:
- /etc/nginx/nginx.conf
- /etc/myapp/app.conf
- /etc/postgresql/14/main/postgresql.conf
- name: Report missing configs
debug:
msg: "MISSING: {{ item.item }}"
loop: "{{ config_checks.results }}"
when: not item.stat.exists
loop_control — ควบคุม Label และ Pause
loop_control ใช้ปรับพฤติกรรมของ loop เช่น กำหนด label ที่แสดงใน output แทน item ทั้งหมด, หยุดรอระหว่าง iterations หรือเปลี่ยนชื่อตัวแปร loop variable
---
- name: loop_control examples
hosts: all
tasks:
# label: แสดงแค่ชื่อแทน item ทั้งหมด
- name: Deploy user configurations
template:
src: user.conf.j2
dest: "/home/{{ item.name }}/.myapp.conf"
owner: "{{ item.name }}"
mode: '0600'
loop:
- { name: alice, role: admin, quota: 10000 }
- { name: bob, role: developer, quota: 5000 }
- { name: carol, role: viewer, quota: 1000 }
loop_control:
label: "{{ item.name }}" # แสดงแค่ชื่อใน output แทน dict ทั้งก้อน
# pause: หน่วงเวลาระหว่าง iterations
- name: Restart services with delay
service:
name: "{{ item }}"
state: restarted
loop:
- nginx
- myapp
- worker
loop_control:
pause: 5 # รอ 5 วินาทีระหว่าง service restart
# loop_var: เปลี่ยนชื่อจาก item เป็นชื่ออื่น (สำหรับ nested loops)
- name: Process servers
debug:
msg: "Processing: {{ server.name }}"
loop: "{{ servers }}"
loop_control:
loop_var: server
with_dict — วนซ้ำบน Dictionary
ใช้ loop กับ dict2items filter เพื่อวนซ้ำบน dictionary — แต่ละ iteration จะมี item.key และ item.value
---
- name: Loop over dictionary
hosts: all
vars:
app_env_vars:
DATABASE_URL: "postgresql://localhost/myapp"
REDIS_URL: "redis://localhost:6379"
LOG_LEVEL: "info"
MAX_WORKERS: "4"
nginx_params:
worker_processes: auto
worker_connections: 1024
keepalive_timeout: 65
tasks:
# วนซ้ำบน dict ด้วย dict2items filter
- name: Set environment variables
lineinfile:
path: /etc/myapp/environment
regexp: "^{{ item.key }}="
line: "{{ item.key }}={{ item.value }}"
create: true
loop: "{{ app_env_vars | dict2items }}"
loop_control:
label: "{{ item.key }}"
# ใช้ key และ value ใน template logic
- name: Show config entries
debug:
msg: "Setting {{ item.key }} = {{ item.value }}"
loop: "{{ nginx_params | dict2items }}"
Pattern: Bulk User และ SSH Key Provisioning
ตัวอย่าง Playbook สร้าง users, directories และ SSH authorized keys จาก variable list — pattern ที่ใช้บ่อยใน user onboarding automation
---
- name: Bulk user provisioning
hosts: all
become: true
vars:
dev_users:
- name: alice
uid: 2001
groups: ["sudo", "docker"]
ssh_key: "ssh-ed25519 AAAA... alice@workstation"
- name: bob
uid: 2002
groups: ["docker"]
ssh_key: "ssh-ed25519 AAAA... bob@workstation"
- name: carol
uid: 2003
groups: ["sudo", "docker", "deploy"]
ssh_key: "ssh-ed25519 AAAA... carol@workstation"
tasks:
# สร้าง users
- name: Create developer accounts
user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
groups: "{{ item.groups }}"
append: true
shell: /bin/bash
create_home: true
state: present
loop: "{{ dev_users }}"
loop_control:
label: "{{ item.name }}"
# สร้าง .ssh directory
- name: Create .ssh directories
file:
path: "/home/{{ item.name }}/.ssh"
state: directory
owner: "{{ item.name }}"
group: "{{ item.name }}"
mode: '0700'
loop: "{{ dev_users }}"
loop_control:
label: "{{ item.name }}"
# เพิ่ม SSH authorized keys
- name: Add authorized SSH keys
authorized_key:
user: "{{ item.name }}"
key: "{{ item.ssh_key }}"
state: present
loop: "{{ dev_users }}"
loop_control:
label: "{{ item.name }}"
# สร้าง sudoers rules
- name: Configure sudo access
lineinfile:
path: "/etc/sudoers.d/{{ item.name }}"
line: "{{ item.name }} ALL=(ALL) NOPASSWD:ALL"
create: true
mode: '0440'
validate: 'visudo -cf %s'
loop: "{{ dev_users }}"
when: '"sudo" in item.groups'
loop_control:
label: "{{ item.name }}"
สรุป
loop ทำให้ Playbook กระชับและ reusable โดยไม่ต้องเขียน task ซ้ำ รองรับ list, list of dicts, dictionary ผ่าน dict2items และ variable list จาก inventory หรือ vars
Pattern ที่ควรจำ: ใช้ loop_control.label เสมอเมื่อ item เป็น dict เพื่อให้ output อ่านง่าย, ใช้ register กับ loop แล้วเข้าถึงผลผ่าน .results, ใช้ when ร่วมกับ loop เพื่อ skip เฉพาะ item ที่ไม่ตรงเงื่อนไข และใช้ loop_control.pause เมื่อ restart services เพื่อให้ระบบ stabilize ระหว่าง iterations

