---
id: config-playbook
title: "Ansible playbook"
shortTitle: "Playbook"
track: config
trackTitle: "Configuration Management"
order: 2
level: core
status: published
estimatedMinutes: 40
prerequisites:
  - config-ansible
next: config-puppet
reviewedAgainst: 2026-10-12
sources:
  - title: "Ansible playbooks introduction"
    url: https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_intro.html
  - title: "Check mode"
    url: https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_checkmode.html
  - title: "ansible.builtin.copy"
    url: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/copy_module.html
---

# Ansible playbook

Dùng inventory `localhost` của bài trước, trong `$HOME/devops-lab/ansible`.

## Để làm gì

Bạn gom vài task thành một file YAML chạy lại được. Lần hai, khi file đã đúng, Ansible báo `ok` chứ không sửa nữa. Xem trước bằng `--check` trước khi đụng máy thật.

## Gồm những gì

- Play, task, module `ansible.builtin.copy`.
- Chạy lại để thấy idempotence.
- `--check`.

Bài này chưa đụng role, Vault, hay cài Nginx lên server remote. `copy` ghi một file trong home của bạn, đủ để thấy `changed` rồi `ok`.

## Dùng khi nào

Việc phải lặp trên nhiều máy hoặc phải review trước khi chạy. Một lệnh bạn gõ một lần thì ad-hoc vẫn được. Mọi thứ sẽ chạy lại tuần sau thì viết playbook và commit.

## Kiến thức cốt lõi

Playbook là YAML. Một play chọn `hosts`, rồi danh sách `tasks`. Mỗi task gọi một module. Docs playbook intro dùng đúng cấu trúc này.

`ansible.builtin.copy` ghi nội dung vào `dest`. Nội dung và quyền đã khớp thì task `ok`. Khác thì ghi đè và `changed`. Đó là idempotence: trạng thái đích mô tả trong file, không phải một chuỗi lệnh "làm thêm một lần nữa".

`--check` là dry run. Docs check mode: Ansible báo task nào sẽ `changed` mà không ghi file, với module có hỗ trợ. `copy` có hỗ trợ. `command` thì không biết trước lệnh sẽ làm gì, nên check mode của `command` không đáng tin. Đó là lý do ưu tiên module.

`--diff` in phần khác nhau. Trong CI, playbook chạy `--check` rồi fail nếu còn `changed` ngoài ý muốn.

## Lệnh và code mẫu

`hello.yml` cạnh `inventory.ini`:

```yaml
- name: Lab file on localhost
  hosts: lab
  tasks:
    - name: Write a marker file
      ansible.builtin.copy:
        dest: "{{ ansible_env.HOME }}/devops-lab/ansible/hello.txt"
        content: "ok\n"
        mode: "0644"
```

```bash
cd "$HOME/devops-lab/ansible"
ansible-playbook -i inventory.ini hello.yml
ansible-playbook -i inventory.ini hello.yml
ansible-playbook -i inventory.ini hello.yml --check --diff
```

## Thực hành

1. Chạy playbook lần một. Task `changed`, file `hello.txt` nội dung `ok`.
2. Chạy lần hai. Task `ok`, `changed=0`.
3. Sửa `content` thành `"ok2\n"`. Chạy `--check --diff` trước. Diff hiện, file trên đĩa vẫn là `ok` vì check không ghi.
4. Chạy không `--check`. File thành `ok2`.
5. Chạy thêm một lần. Lại `ok`.

## Kết quả mong đợi

- Lần đầu `changed=1`. Lần ngay sau `changed=0`.
- `--check` sau khi sửa content báo sẽ đổi, file chưa đổi đến khi chạy thật.
- `hello.txt` mode `0644`.

## Checklist

- [ ] Viết trạng thái cần có, không viết "chạy lệnh này thêm lần nữa".
- [ ] Chạy hai lần và thấy lần hai `ok`.
- [ ] `--check` trước khi chạy playbook lên máy không phải localhost.
- [ ] Không dùng `command: curl | bash` trong playbook.

## Lỗi thường gặp

**Playbook toàn `shell` và `command`.** Lần hai vẫn `changed`, và bạn không biết lệnh có an toàn để lặp không. Đổi sang module `copy`, `file`, `package`, `user`.

**`--check` rồi tưởng đã cài xong.** Check không ghi. Máy vẫn cũ. Đọc dòng `check mode` trong output.

**`hosts: all` trên inventory production.** Một playbook lab trỏ nhầm inventory là chạy lên mọi máy. Lab này cố định `-i inventory.ini` chỉ có localhost. Trên máy thật, xem lại `-i` trước khi Enter.

## Nguồn

1. [Playbooks introduction](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_intro.html) — Ansible, play và task.
2. [Check mode](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_checkmode.html) — `--check`.
3. [ansible.builtin.copy](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/copy_module.html) — `dest`, `content`, `mode`.

## Tiếp theo

[Puppet](./03-puppet.md)
