๐Ÿ”ง

Ansible - ๋ฉ€ํ‹ฐ play, serial, delegate_to

์ตœ๋ฏผ์„ยท2026-03-16

ansible

๋ฉ€ํ‹ฐ play, serial, delegate_to

๐Ÿ”ง Ansible ์‹ค์Šต ์‹œ๋ฆฌ์ฆˆ

์ด์ „ ์ฑ•ํ„ฐ๊นŒ์ง€ ํ•˜๋‚˜์˜ play์—์„œ ๋ชจ๋“  ๋…ธ๋“œ๋ฅผ ๋™์‹œ์— ์ฒ˜๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ์—๋Š” play๋ฅผ ์—ฌ๋Ÿฌ ๊ฐœ๋กœ ๋‚˜๋ˆ„์–ด ๊ทธ๋ฃน๋ณ„๋กœ ๋‹ค๋ฅธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ณ , serial๋กœ ํ•œ ๋Œ€์”ฉ ์ˆœ์ฐจ ์ฒ˜๋ฆฌํ•˜๋ฉฐ, delegate_to๋กœ ํŠน์ • task๋งŒ ๋‹ค๋ฅธ ๋…ธ๋“œ์—์„œ ์‹คํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.


์‚ฌ์ „ ์ค€๋น„: inventory ์„œ๋ธŒ๊ทธ๋ฃน

์‹ค์Šต์„ ์œ„ํ•ด inventory.ini์— ์„œ๋ธŒ๊ทธ๋ฃน์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค:

[managed]
node1 ansible_host=node1
node2 ansible_host=node2
node3 ansible_host=node3

[primary]
node1 ansible_host=node1

[secondary]
node2 ansible_host=node2
node3 ansible_host=node3

[managed:vars]
ansible_connection=ssh
ansible_user=ansible
ansible_password=ansible

[primary:vars]
ansible_connection=ssh
ansible_user=ansible
ansible_password=ansible

[secondary:vars]
ansible_connection=ssh
ansible_user=ansible
ansible_password=ansible

primary(node1)์™€ secondary(node2, node3)๋กœ ์—ญํ• ์„ ๋‚˜๋ˆ ๋‘๋ฉด, play๋ณ„๋กœ ๋Œ€์ƒ ๊ทธ๋ฃน์„ ๋‹ค๋ฅด๊ฒŒ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๋ฉ€ํ‹ฐ play โ€” ํ•˜๋‚˜์˜ playbook์— ์—ฌ๋Ÿฌ play

play์™€ task์˜ ์ฐจ์ด

์ง€๊ธˆ๊นŒ์ง€ playbook์—๋Š” ํ•ญ์ƒ play๊ฐ€ 1๊ฐœ์˜€์Šต๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ ํŒŒ์ผ์— play๋ฅผ ์—ฌ๋Ÿฌ ๊ฐœ ๋„ฃ์œผ๋ฉด ๊ทธ๋ฃน๋ณ„๋กœ ๋‹ค๋ฅธ ์ž‘์—…์„ ์ˆœ์„œ๋Œ€๋กœ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

task play
๋‹จ์œ„ ๋ฌด์—‡์„ ํ•˜๋А๋ƒ ๋ˆ„๊ตฌ์—๊ฒŒ ๋ฌด์—‡์„ ํ•˜๋А๋ƒ
๋Œ€์ƒ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€ (play์˜ hosts๋ฅผ ๋”ฐ๋ฆ„) play๋งˆ๋‹ค hosts, become, serial ๋“ฑ์„ ๋‹ค๋ฅด๊ฒŒ ์„ค์ • ๊ฐ€๋Šฅ
์‹คํ–‰ ์ˆœ์„œ play ์•ˆ์—์„œ ์œ„ โ†’ ์•„๋ž˜ ์ˆœ์ฐจ playbook ์•ˆ์—์„œ ์œ„ โ†’ ์•„๋ž˜ ์ˆœ์ฐจ

task๋งŒ์œผ๋กœ๋Š” ์ค‘๊ฐ„์— ๋Œ€์ƒ ํ˜ธ์ŠคํŠธ๋ฅผ ๋ฐ”๊ฟ€ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋Œ€์ƒ์ด ๋ฐ”๋€Œ์–ด์•ผ ํ•˜๋ฉด play๋ฅผ ๋‚˜๋ˆ ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

playbook ์ž‘์„ฑ

playbook/multi_play.yml:

---
- name: multi playbook primary
  hosts: primary
  become: true
  gather_facts: false
  vars:
    node_role: "primary"
  tasks:
    - name: "write {{ node_role }}"
      ansible.builtin.copy:
        content: "{{ node_role }}\n"
        dest: /tmp/role.txt
    - name: ํŒŒ์ผ ์ฝ๊ธฐ
      command: cat /tmp/role.txt
      register: result
    - name: ๊ฒฐ๊ณผ ์ถœ๋ ฅ
      debug:
        msg: "{{ result.stdout }}"

- name: multi playbook secondary
  hosts: secondary
  become: true
  gather_facts: false
  vars:
    node_role: "secondary"
  tasks:
    - name: "write {{ node_role }}"
      ansible.builtin.copy:
        content: "{{ node_role }}\n"
        dest: /tmp/role.txt
    - name: ํŒŒ์ผ ์ฝ๊ธฐ
      command: cat /tmp/role.txt
      register: result
    - name: ๊ฒฐ๊ณผ ์ถœ๋ ฅ
      debug:
        msg: "{{ result.stdout }}"

YAML ์ตœ์ƒ์œ„๊ฐ€ ๋ฆฌ์ŠคํŠธ(-)์ด๋ฏ€๋กœ, play๋ฅผ ์—ฌ๋Ÿฌ ๊ฐœ ๋‚˜์—ดํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ฃผ์˜ํ•  ์ ์€ ---(YAML ๋ฌธ์„œ ๊ตฌ๋ถ„์ž)๋Š” ํŒŒ์ผ ๋งจ ์œ„์— ํ•œ ๋ฒˆ๋งŒ ์“ด๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. play ์‚ฌ์ด์— ---๋ฅผ ๋˜ ๋„ฃ์œผ๋ฉด ๋ณ„๋„ YAML ๋ฌธ์„œ๋กœ ํ•ด์„๋˜์–ด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

์‹คํ–‰ ๊ฒฐ๊ณผ

PLAY [multi playbook primary] ****************************************************

TASK [write primary] *************************************************************
changed: [node1]

TASK [ํŒŒ์ผ ์ฝ๊ธฐ] *****************************************************************
changed: [node1]

TASK [๊ฒฐ๊ณผ ์ถœ๋ ฅ] *****************************************************************
ok: [node1] => {
    "msg": "primary"
}

PLAY [multi playbook secondary] **************************************************

TASK [write secondary] ***********************************************************
changed: [node3]
changed: [node2]

TASK [ํŒŒ์ผ ์ฝ๊ธฐ] *****************************************************************
changed: [node3]
changed: [node2]

TASK [๊ฒฐ๊ณผ ์ถœ๋ ฅ] *****************************************************************
ok: [node2] => {
    "msg": "secondary"
}
ok: [node3] => {
    "msg": "secondary"
}

Play 1(primary)์ด ์™„์ „ํžˆ ๋๋‚œ ํ›„ Play 2(secondary)๊ฐ€ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค. ๊ฐ™์€ play ์•ˆ์˜ ๋…ธ๋“œ๋“ค(node2, node3)์€ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌ๋˜์–ด, ์ถœ๋ ฅ ์ˆœ์„œ๊ฐ€ node3 โ†’ node2๋กœ ๋’ค๋ฐ”๋€” ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฉ€ํ‹ฐ play๊ฐ€ ์•„๋‹ˆ๋ผ ๋ณ€์ˆ˜๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜๋„ ์žˆ๋‹ค

์œ„ ์˜ˆ์ œ์—์„œ ๋‘ play๋Š” hosts๋งŒ ๋‹ค๋ฅด๊ณ  task ๊ตฌ์กฐ๋Š” ๋™์ผํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” inventory์˜ ๊ทธ๋ฃน ๋ณ€์ˆ˜์— node_role์„ ์ •์˜ํ•˜๊ณ , play ํ•˜๋‚˜๋กœ hosts: managed๋ฅผ ์“ฐ๋Š” ๊ฒƒ์ด ๋” ๊ฐ„๊ฒฐํ•ฉ๋‹ˆ๋‹ค:

[primary:vars]
node_role=primary

[secondary:vars]
node_role=secondary

๋ฉ€ํ‹ฐ play๊ฐ€ ์ง„์งœ ํ•„์š”ํ•œ ๊ฒฝ์šฐ๋Š” ๊ทธ๋ฃน๋ณ„๋กœ task ์ž์ฒด๊ฐ€ ๋‹ค๋ฅผ ๋•Œ์ž…๋‹ˆ๋‹ค.


serial โ€” ํ•œ ๋Œ€์”ฉ ์ˆœ์ฐจ ์ฒ˜๋ฆฌ

serial ์ถ”๊ฐ€

Play 2์— serial: 1์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค:

- name: multi playbook secondary
  hosts: secondary
  become: true
  gather_facts: false
  serial: 1
  vars:
    node_role: "secondary"
  tasks:
    ...

serial ์—†์ด vs serial: 1

serial ์—†์ด ์‹คํ–‰:

PLAY [multi playbook secondary] **************************************************

TASK [write secondary] ***********************************************************
changed: [node3]
changed: [node2]

node2, node3์ด ๋™์‹œ์— ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. PLAY ํ—ค๋”๊ฐ€ 1๋ฒˆ ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค.

serial: 1 ์ถ”๊ฐ€ ํ›„:

PLAY [multi playbook secondary] **************************************************

TASK [write secondary] ***********************************************************
ok: [node2]

TASK [ํŒŒ์ผ ์ฝ๊ธฐ] *****************************************************************
changed: [node2]

TASK [๊ฒฐ๊ณผ ์ถœ๋ ฅ] *****************************************************************
ok: [node2] => {
    "msg": "secondary"
}

PLAY [multi playbook secondary] **************************************************

TASK [write secondary] ***********************************************************
ok: [node3]

TASK [ํŒŒ์ผ ์ฝ๊ธฐ] *****************************************************************
changed: [node3]

TASK [๊ฒฐ๊ณผ ์ถœ๋ ฅ] *****************************************************************
ok: [node3] => {
    "msg": "secondary"
}

PLAY [multi playbook secondary]๊ฐ€ ๋‘ ๋ฒˆ ๋‚˜์˜ต๋‹ˆ๋‹ค. Ansible์ด ๋‚ด๋ถ€์ ์œผ๋กœ play๋ฅผ ๋…ธ๋“œ ์ˆ˜๋งŒํผ ๋ฐ˜๋ณต ์‹คํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. node2์˜ ๋ชจ๋“  task๊ฐ€ ๋๋‚œ ํ›„์—์•ผ node3์ด ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค.

serial: 2๋กœ ํ•˜๋ฉด 2๋Œ€์”ฉ ๋ฌถ์–ด์„œ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์—…๋ฐ์ดํŠธ ์ค‘ ์žฅ์• ๊ฐ€ ๋ฐœ์ƒํ•ด๋„ ๋‚˜๋จธ์ง€ ๋…ธ๋“œ๋Š” ์•„์ง ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์€ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์–ด, ๋กค๋ง ์—…๋ฐ์ดํŠธ์— ํ•ต์‹ฌ์ ์ธ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.


delegate_to โ€” ๋‹ค๋ฅธ ๋…ธ๋“œ์—์„œ task ์‹คํ–‰

์™œ ํ•„์š”ํ•œ๊ฐ€

serial: 1๋กœ Worker ๋…ธ๋“œ๋ฅผ ํ•œ ๋Œ€์”ฉ ์ˆœํšŒํ•˜๋ฉด์„œ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๋Š” ์ƒํ™ฉ์„ ์ƒ๊ฐํ•ด๋ด…์‹œ๋‹ค. drain(kubectl drain)์€ Worker ์ž์‹ ์ด ์•„๋‹ˆ๋ผ kubectl์ด ์„ค์น˜๋œ ๋‹ค๋ฅธ ๋…ธ๋“œ์—์„œ ์‹คํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

delegate_to ์—†์ด๋Š” ์ด๊ฒƒ์ด ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค:

  • ๋ณ„๋„ play๋กœ hosts: primary๋ฅผ ๋งŒ๋“ค๋ฉด, "์ง€๊ธˆ ์–ด๋–ค Worker ์ฐจ๋ก€์ธ์ง€"๋ผ๋Š” ์ˆœํšŒ ์ปจํ…์ŠคํŠธ๊ฐ€ ๋Š๊น๋‹ˆ๋‹ค. drain โ†’ ์—…๊ทธ๋ ˆ์ด๋“œ โ†’ uncordon์„ ํ•œ ๋…ธ๋“œ ๋‹จ์œ„๋กœ ๋ฌถ์–ด์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
  • play ์•ˆ์˜ ๋ชจ๋“  task๋Š” hosts์— ์ง€์ •๋œ ๋…ธ๋“œ์—์„œ๋งŒ ์‹คํ–‰๋˜๋‹ˆ๊นŒ์š”.

delegate_to๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด play์˜ ์ˆœํšŒ ์ปจํ…์ŠคํŠธ๋Š” ์œ ์ง€ํ•˜๋ฉด์„œ, ํŠน์ • task๋งŒ ๋‹ค๋ฅธ ๋…ธ๋“œ์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

- hosts: secondary
  serial: 1
  tasks:
    - name: drain
      command: kubectl drain {{ inventory_hostname }}
      delegate_to: node1    # node1์—์„œ ์‹คํ–‰

    - name: ์—…๊ทธ๋ ˆ์ด๋“œ
      apt: ...              # ํ˜„์žฌ ๋…ธ๋“œ์—์„œ ์‹คํ–‰

    - name: uncordon
      command: kubectl uncordon {{ inventory_hostname }}
      delegate_to: node1    # node1์—์„œ ์‹คํ–‰

node2 ์ฐจ๋ก€์ผ ๋•Œ: drain(node1์—์„œ) โ†’ ์—…๊ทธ๋ ˆ์ด๋“œ(node2์—์„œ) โ†’ uncordon(node1์—์„œ). node2๊ฐ€ ์™„์ „ํžˆ ๋๋‚˜์•ผ node3์œผ๋กœ ๋„˜์–ด๊ฐ‘๋‹ˆ๋‹ค.

์—๋Ÿฌ: delegate_to๋Š” play ๋ ˆ๋ฒจ์ด ์•„๋‹ˆ๋‹ค

์ฒ˜์Œ์— delegate_to๋ฅผ play ๋ ˆ๋ฒจ์— ๋„ฃ์œผ๋ฉด ๋‹ค์Œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค:

[ERROR]: 'delegate_to' is not a valid attribute for a Play
Origin: /workspace/ansible/playbook/delegate.yml:7:3

5   gather_facts: false
6   serial: 1
7   delegate_to: node1
    ^ column 3

์›์ธ: delegate_to๋Š” task ๋ ˆ๋ฒจ ์†์„ฑ์ž…๋‹ˆ๋‹ค. hosts, become, serial์ฒ˜๋Ÿผ play ๋ ˆ๋ฒจ์—์„œ ์“ธ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ: ๊ฐœ๋ณ„ task์— delegate_to๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค:

tasks:
  - name: delegate_to test
    command: hostname
    delegate_to: node1    # task ๋ ˆ๋ฒจ

playbook ์ž‘์„ฑ

playbook/delegate.yml:

---
- name: delegated playbook
  hosts: secondary
  become: true
  gather_facts: false
  serial: 1
  tasks:
    - name: delegate_to test
      command: hostname
      delegate_to: node1
      register: result
    - name: ๊ฒฐ๊ณผ ์ถœ๋ ฅ
      debug:
        msg: "{{ result.stdout }}"

์‹คํ–‰ ๊ฒฐ๊ณผ

PLAY [delegated playbook] ********************************************************

TASK [delegate_to test] **********************************************************
changed: [node2 -> node1]

TASK [๊ฒฐ๊ณผ ์ถœ๋ ฅ] *****************************************************************
ok: [node2] => {
    "msg": "node1"
}

PLAY [delegated playbook] ********************************************************

TASK [delegate_to test] **********************************************************
changed: [node3 -> node1]

TASK [๊ฒฐ๊ณผ ์ถœ๋ ฅ] *****************************************************************
ok: [node3] => {
    "msg": "node1"
}

[node2 -> node1]์ด ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค. play์˜ ๋Œ€์ƒ์€ node2์ด์ง€๋งŒ, ์‹ค์ œ hostname ๋ช…๋ น์€ node1์—์„œ ์‹คํ–‰๋˜์–ด ๊ฒฐ๊ณผ๊ฐ€ node1์ž…๋‹ˆ๋‹ค.

PLAY RECAP์„ ๋ณด๋ฉด node1์ด ์•„๋‹ˆ๋ผ node2, node3์œผ๋กœ ์ง‘๊ณ„๋ฉ๋‹ˆ๋‹ค. play์˜ "ํ˜„์žฌ ํ˜ธ์ŠคํŠธ" ์ปจํ…์ŠคํŠธ๋Š” ์œ ์ง€๋˜๊ณ , ์‹คํ–‰๋งŒ ์œ„์ž„ํ•˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.


--check (dry-run) ๋ชจ๋“œ์—์„œ์˜ ์ฃผ์˜์ 

์‹ค์Šต ์ค‘ --check ์˜ต์…˜์œผ๋กœ dry-run์„ ์‹œ๋„ํ–ˆ๋Š”๋ฐ, command task๊ฐ€ skipping๋˜์—ˆ์Šต๋‹ˆ๋‹ค:

TASK [ํŒŒ์ผ ์ฝ๊ธฐ] *****************************************************************
skipping: [node1]

TASK [๊ฒฐ๊ณผ ์ถœ๋ ฅ] *****************************************************************
ok: [node1] => {
    "msg": ""
}

--check๋Š” ์‹ค์ œ ๋ณ€๊ฒฝ์„ ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ, copy๊ฐ€ ํŒŒ์ผ์„ ์“ฐ์ง€ ์•Š๊ณ  command ๋ชจ๋“ˆ๋„ ๊ธฐ๋ณธ์ ์œผ๋กœ skip๋ฉ๋‹ˆ๋‹ค. command, shell ๊ฐ™์€ ์‹คํ–‰ ๋ชจ๋“ˆ์€ dry-run์—์„œ "์ด๊ฑธ ์‹ค์ œ๋กœ ์‹คํ–‰ํ•˜๋ฉด ์–ด๋–ค ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ฌ์ง€" ์˜ˆ์ธกํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.


์ •๋ฆฌ

๊ฐœ๋… ์—ญํ•  ๋ ˆ๋ฒจ
๋ฉ€ํ‹ฐ play ๊ทธ๋ฃน๋ณ„๋กœ ๋‹ค๋ฅธ ์ž‘์—…์„ ์ˆœ์„œ๋Œ€๋กœ ์ˆ˜ํ–‰ playbook ๋ ˆ๋ฒจ
serial play ๋Œ€์ƒ ํ˜ธ์ŠคํŠธ๋ฅผ N๋Œ€์”ฉ ๋‚˜๋ˆ„์–ด ์ˆœ์ฐจ ์ฒ˜๋ฆฌ play ๋ ˆ๋ฒจ
delegate_to ์ˆœํšŒ ์ปจํ…์ŠคํŠธ๋Š” ์œ ์ง€ํ•˜๋ฉด์„œ ํŠน์ • task๋งŒ ๋‹ค๋ฅธ ๋…ธ๋“œ์—์„œ ์‹คํ–‰ task ๋ ˆ๋ฒจ

์ด ์„ธ ๊ฐ€์ง€๋ฅผ ์กฐํ•ฉํ•˜๋ฉด, "Phase 1์—์„œ๋Š” A ๊ทธ๋ฃน์—์„œ ์ž‘์—…ํ•˜๊ณ , Phase 2์—์„œ๋Š” B ๊ทธ๋ฃน์„ ํ•œ ๋Œ€์”ฉ ์ˆœํšŒํ•˜๋˜ ์ผ๋ถ€ ๋ช…๋ น์€ A์—์„œ ์‹คํ–‰"ํ•˜๋Š” ๋ณต์žกํ•œ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜์ด ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค.