Ansible มี modules หลายร้อยตัวสำหรับงานที่แตกต่างกัน แต่ modules กลุ่ม command execution คือพื้นฐานที่ใช้บ่อยที่สุด ไม่ว่าจะเป็น command, shell, script และสำหรับงานที่ไม่มี module รองรับ ก็ยังสร้าง custom module ขึ้นมาเองได้
บทความนี้อธิบายความแตกต่างระหว่าง command และ shell, วิธีใช้ script รัน local script บน remote host, การ register output, และวิธีสร้าง custom module ด้วย Python เบื้องต้น
command Module: รัน Command ปลอดภัยที่สุด
command เป็น module พื้นฐานสำหรับรัน command บน remote host โดยไม่ผ่าน shell ทำให้ปลอดภัยจาก shell injection และ predictable กว่า
---
- name: Using command module
hosts: all
tasks:
- name: Check OS version
command: cat /etc/os-release
register: os_info
- name: Print OS info
debug:
var: os_info.stdout_lines
- name: Create directory
command: mkdir -p /opt/myapp/data
- name: Run command with arguments
command:
cmd: df -h /
chdir: /tmp # เปลี่ยน working directory ก่อนรัน
- name: Run command only if file exists
command: cat /etc/app.conf
args:
creates: /opt/app/initialized # ข้ามถ้า file นี้มีอยู่แล้ว
command module ไม่รองรับ shell features เช่น pipes (|), redirects (>), wildcards (*) หรือ environment variable expansion เช่น $HOME — ถ้าต้องการสิ่งเหล่านี้ต้องใช้ shell module แทน
shell Module: รัน Command ผ่าน Shell
shell module ส่ง command ไปรันผ่าน /bin/sh บน remote host ทำให้ใช้ shell features ได้ครบ ทั้ง pipes, redirects, wildcards และ subshell
---
- name: Using shell module
hosts: all
tasks:
- name: Get process count
shell: ps aux | grep nginx | grep -v grep | wc -l
register: nginx_procs
- name: Check if service is active
shell: systemctl is-active nginx && echo "running" || echo "stopped"
register: nginx_status
ignore_errors: yes
- name: Write output to file
shell: df -h > /tmp/disk_usage.txt
- name: Use environment variables
shell: echo "Home is $HOME, User is $USER"
register: env_output
- name: Multi-line shell command
shell: |
cd /opt/myapp
git pull origin main
pip install -r requirements.txt
register: deploy_output
ใช้ shell เฉพาะเมื่อจำเป็นต้องใช้ shell features จริง ๆ เพราะอาจเกิด shell injection ได้ถ้า input มาจากผู้ใช้ ถ้า command ง่ายและไม่ต้องการ pipes ให้ใช้ command เสมอ
command vs shell: เมื่อไรใช้อะไร
ความแตกต่างหลักระหว่าง command และ shell คือวิธีที่ command ถูกส่งไปรัน ซึ่งส่งผลต่อความปลอดภัยและความสามารถ
# ใช้ command (ปลอดภัยกว่า — ไม่ผ่าน shell)
- command: /usr/bin/python3 /opt/scripts/check.py # ✅ ไม่มี shell features
# ใช้ shell (ต้องการ shell features)
- shell: cat /var/log/app.log | grep ERROR | tail -20 # ✅ ต้องการ pipe
- shell: echo "{{ item }}" >> /tmp/list.txt # ✅ ต้องการ redirect
- shell: rm -f /tmp/old_* # ✅ ต้องการ wildcard
# อย่าใช้ shell เมื่อไม่จำเป็น
- shell: ls /etc/nginx # ❌ ไม่มี shell features — ใช้ command แทน
- shell: /usr/bin/python3 /opt/scripts/check.py # ❌ ใช้ command แทนได้
script Module: รัน Local Script บน Remote Host
script module คัดลอก script จาก control node ไปรันบน remote host โดยตรง ไม่ต้อง copy ก่อนแล้วค่อยรัน เหมาะสำหรับ scripts ที่ซับซ้อนหรือมีอยู่แล้ว
---
- name: Using script module
hosts: all
tasks:
- name: Run local bash script on remote
script: scripts/setup.sh
register: setup_result
- name: Run script with arguments
script: scripts/configure.sh --env production --port 8080
- name: Run Python script from local
script: scripts/health_check.py
args:
executable: /usr/bin/python3
- name: Run script only if not already done
script: scripts/init.sh
args:
creates: /opt/myapp/.initialized # ข้ามถ้า file นี้มีอยู่บน remote แล้ว
script module ไม่ตรวจ idempotency โดยอัตโนมัติ ต้องใช้ creates หรือ removes argument เพื่อควบคุม หรือใช้ร่วมกับ when condition
Register: เก็บ Output จาก Command
register เก็บผลลัพธ์จาก module ไว้ใน variable สำหรับใช้ใน task ถัดไป ตัวแปรที่ได้จะมี fields หลัก ๆ คือ stdout, stderr, rc, และ stdout_lines
---
- name: Register examples
hosts: all
tasks:
- name: Check disk usage
command: df -h /
register: disk_result
- name: Show stdout
debug:
msg: "{{ disk_result.stdout }}"
- name: Show as lines
debug:
msg: "{{ disk_result.stdout_lines }}"
- name: Check return code
debug:
msg: "Return code: {{ disk_result.rc }}"
- name: Run health check
shell: curl -s -o /dev/null -w "%{http_code}" http://localhost/health
register: health_code
- name: Fail if health check bad
fail:
msg: "Health check failed: {{ health_code.stdout }}"
when: health_code.stdout != "200"
- name: Parse output with conditions
shell: free -m | awk 'NR==2{print $4}'
register: free_mem
- name: Warn if low memory
debug:
msg: "Warning: Free memory is {{ free_mem.stdout }}MB"
when: free_mem.stdout | int < 512
raw Module: สำหรับ Host ที่ไม่มี Python
raw module ส่ง command ไปยัง remote host ผ่าน SSH โดยตรงโดยไม่ต้องการ Python บน remote host เหมาะสำหรับ bootstrap เครื่องใหม่หรือ network devices ที่ไม่มี Python
---
- name: Bootstrap new server
hosts: new_servers
gather_facts: false # ปิด facts gathering เพราะยังไม่มี Python
tasks:
- name: Install Python (required for Ansible)
raw: apt-get install -y python3 python3-apt
register: python_install
- name: Check Python installed
raw: python3 --version
register: python_version
- name: Show Python version
debug:
var: python_version.stdout
หลังจาก bootstrap แล้ว ให้ใช้ module ปกติแทน raw เสมอ เพราะ raw ไม่มี idempotency, ไม่มี error handling และไม่ return structured data
สร้าง Custom Module ด้วย Python
เมื่อไม่มี module ที่ตรงกับความต้องการ สามารถสร้าง custom module ขึ้นมาเองได้ วาง Python script ไว้ใน directory library/ ข้าง Playbook หรือใน Role
# library/check_port.py — Custom module ตรวจสอบว่า port เปิดอยู่หรือไม่
#!/usr/bin/python3
from ansible.module_utils.basic import AnsibleModule
import socket
def run_module():
# กำหนด arguments ที่ module รับ
module_args = dict(
host=dict(type='str', required=True),
port=dict(type='int', required=True),
timeout=dict(type='int', default=5)
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
host = module.params['host']
port = module.params['port']
timeout = module.params['timeout']
# ถ้า check mode ไม่ต้องทำอะไรจริง
if module.check_mode:
module.exit_json(changed=False, msg="Check mode: no action taken")
# ลอง connect port
try:
sock = socket.create_connection((host, port), timeout=timeout)
sock.close()
module.exit_json(
changed=False,
open=True,
msg=f"Port {port} on {host} is open"
)
except (socket.timeout, ConnectionRefusedError, OSError) as e:
module.exit_json(
changed=False,
open=False,
msg=f"Port {port} on {host} is closed: {str(e)}"
)
if __name__ == '__main__':
run_module()
---
# ใช้ custom module ใน Playbook
- name: Check services with custom module
hosts: all
tasks:
- name: Check if web server port is open
check_port:
host: "{{ inventory_hostname }}"
port: 80
timeout: 3
register: web_port
- name: Report port status
debug:
msg: "Port 80: {{ web_port.msg }}"
- name: Check database port
check_port:
host: "{{ db_host }}"
port: 5432
register: db_port
- name: Fail if database unreachable
fail:
msg: "Database port 5432 is not open!"
when: not db_port.open
Custom module ต้อง return JSON ผ่าน module.exit_json() (สำเร็จ) หรือ module.fail_json() (ล้มเหลว) เสมอ และควรรองรับ check_mode เพื่อให้ใช้ร่วมกับ --check flag ได้
Module ที่ควรรู้จัก
นอกจาก command/shell/script ยังมี modules พื้นฐานอีกหลายตัวที่ใช้บ่อยใน Playbook ทั่วไป ได้แก่ debug สำหรับ print ข้อความหรือ variable, fail สำหรับหยุด play และแสดงข้อความ error, set_fact สำหรับกำหนด variable ใหม่ระหว่าง play, pause สำหรับหยุดรอ input หรือรอเวลา, wait_for สำหรับรอจนกว่า port หรือ file จะพร้อม และ assert สำหรับตรวจสอบ condition ก่อนดำเนินต่อ
---
- name: Utility modules demo
hosts: all
tasks:
- name: Set computed variable
set_fact:
app_url: "http://{{ inventory_hostname }}:{{ app_port }}"
- name: Wait for port to be open
wait_for:
host: "{{ inventory_hostname }}"
port: "{{ app_port }}"
delay: 5
timeout: 60
- name: Assert service is running
assert:
that:
- app_port is defined
- app_port | int > 0
- app_port | int < 65536
fail_msg: "Invalid app_port: {{ app_port }}"
success_msg: "app_port {{ app_port }} is valid"
- name: Pause for manual verification
pause:
prompt: "Please verify the deployment at {{ app_url }}. Press Enter to continue"
สรุป
การเลือกใช้ module ที่เหมาะสมช่วยให้ Playbook ปลอดภัยและดูแลรักษาง่าย หลักการง่าย ๆ คือ: ใช้ command เป็น default เมื่อต้องรัน command, ใช้ shell เมื่อต้องการ pipes หรือ shell features, ใช้ script เมื่อมี script ที่ซับซ้อนอยู่แล้วและไม่ต้องการ rewrite เป็น Ansible tasks, และใช้ raw เฉพาะตอน bootstrap เครื่องที่ยังไม่มี Python
สำหรับงานที่ไม่มี built-in module รองรับ การสร้าง custom module ด้วย Python ไม่ซับซ้อน เพียงใช้ AnsibleModule class รับ arguments และ return JSON ก็สามารถใช้งานได้เหมือน built-in module ทุกประการ

