Lab 9 — Ansible Server Provisioning
Mục tiêu: Sau Lab này, người học có thể sử dụng Ansible để tự động dựng một server từ trạng thái gần như sạch thành một application server sẵn sàng để deploy.
Đây là Lab rất quan trọng vì từ đây Ansible bắt đầu được sử dụng đúng với vai trò Infrastructure as Code thay vì chỉ chạy command từ xa.
1. Bối cảnh
Ở Lab 8, chúng ta đã biết tổ chức automation bằng:
Roles
Ví dụ:
roles/
├── common/
├── docker/
├── nginx/
└── application/
Nhưng hiện tại chúng ta vẫn đang giả định server đã có sẵn khá nhiều thứ.
Trong thực tế, bạn có thể nhận một server mới:
New Ubuntu Server
│
├── Chưa có Docker
├── Chưa có Docker Compose
├── Chưa có user deploy
├── Chưa có directory application
├── Chưa có package cần thiết
├── Chưa cấu hình SSH
└── Chưa có cấu hình cơ bản
DevOps Engineer cần biến nó thành:
Ansible
│
▼
Fresh Server
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Common Docker SSH
│ │ │
└──────────────┼──────────────┘
▼
Ready Server
│
▼
Application Deploy
Đó chính là Server Provisioning.
2. Provisioning là gì?
Provisioning là quá trình chuẩn bị một server để nó có thể thực hiện một vai trò cụ thể.
Ví dụ:
Raw Server
↓
OS configuration
↓
Packages
↓
Users
↓
SSH
↓
Docker
↓
Docker Compose
↓
Directories
↓
System configuration
↓
Application-ready Server
Provisioning không nhất thiết là deploy application.
Có thể phân biệt:
Provisioning
↓
Chuẩn bị server
và:
Deployment
↓
Đưa application lên server
Ở Lab 9 chúng ta tập trung vào Provisioning.
3. Provisioning vs Deployment
Đây là distinction rất quan trọng.
Provisioning
Server
├── User
├── SSH
├── Packages
├── Docker
├── Directories
└── System configuration
Deployment
Application
├── Docker image
├── Configuration
├── Database
└── Service
Có thể hình dung:
Server
│
┌───────┴────────┐
│ │
Provisioning Deployment
│ │
▼ ▼
Server Ready App Running
4. Scenario của Lab
Chúng ta sẽ xây dựng một Todo Application Server.
Server ban đầu:
Ubuntu Server
Sau provisioning:
Ubuntu Server
│
├── Basic packages
│
├── deploy user
│
├── SSH configuration
│
├── Docker
│
├── Docker Compose
│
├── Application directories
│
└── System configuration
Chưa deploy application.
5. Kiến trúc
Project:
ansible-lab-09/
│
├── ansible.cfg
├── inventory/
│ └── hosts.ini
│
├── site.yml
│
├── group_vars/
│ └── all.yml
│
└── roles/
├── common/
├── users/
├── ssh/
├── docker/
└── application_server/
Flow:
site.yml
│
├── common
│
├── users
│
├── ssh
│
├── docker
│
└── application_server
6. Chuẩn bị Server
Bạn cần một Ubuntu Server.
Có thể sử dụng:
- VM
- Multipass
- VirtualBox
- VMware
- Cloud VM
- AWS EC2
- DigitalOcean
- Azure
- GCP
Trong Lab này giả sử:
Control Node
│
│ SSH
▼
Ubuntu Server
192.168.56.101
7. Kiểm tra SSH
Từ Control Node:
ssh ubuntu@192.168.56.101
Nếu SSH hoạt động:
ubuntu@server:~$
Thoát:
exit
8. Tạo Project
mkdir ansible-lab-09
cd ansible-lab-09
Tạo:
mkdir -p inventory group_vars roles
9. ansible.cfg
[defaults]
inventory = ./inventory/hosts.ini
roles_path = ./roles
host_key_checking = False
Trong production, không nên tùy tiện tắt:
host_key_checking = False
Ở lab dùng để tránh SSH host-key friction.
Production nên quản lý host keys đúng cách.
10. Inventory
Tạo:
inventory/hosts.ini
[application_servers]
app-01 ansible_host=192.168.56.101
[all:vars]
ansible_user=ubuntu
Kiểm tra:
ansible-inventory --graph
Kết quả:
@all:
|--@application_servers:
| |--app-01
11. Test kết nối
ansible all -m ansible.builtin.ping
Kỳ vọng:
app-01 | SUCCESS => {
"changed": false,
"ping": "pong"
}
12. Global Variables
Tạo:
group_vars/all.yml
---
timezone: Asia/Tokyo
deploy_user: deploy
application_name: todo
application_dir: /opt/todo
docker_packages:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
Ở đây chúng ta bắt đầu sử dụng Variables từ Lab 5 kết hợp với Roles từ Lab 8.
13. Role common
Tạo:
ansible-galaxy role init roles/common
Cấu trúc:
roles/common/
├── defaults/
├── handlers/
├── tasks/
├── templates/
└── vars/
14. Common Packages
File:
roles/common/tasks/main.yml
---
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- name: Install common packages
ansible.builtin.apt:
name:
- curl
- wget
- git
- vim
- unzip
- ca-certificates
- gnupg
- lsof
- htop
state: present
15. Tại sao dùng state: present?
Không dùng:
apt install curl
mà:
state: present
Ý nghĩa:
Đảm bảo package tồn tại.
Nếu package đã tồn tại:
changed = false
Nếu chưa tồn tại:
changed = true
Đây chính là Idempotency từ Lab 7.
16. Configure Timezone
Thêm:
- name: Set timezone
community.general.timezone:
name: "{{ timezone }}"
Nếu máy chưa có collection:
ansible-galaxy collection install community.general
Kiểm tra:
timedatectl
17. Role users
Tạo:
ansible-galaxy role init roles/users
File:
roles/users/tasks/main.yml
---
- name: Create deploy user
ansible.builtin.user:
name: "{{ deploy_user }}"
shell: /bin/bash
create_home: true
state: present
Sau khi chạy:
id deploy
Kết quả:
uid=1001(deploy)
18. Add User vào Docker Group
Sau này chúng ta muốn:
docker ps
mà không cần:
sudo docker ps
Task:
- name: Add deploy user to docker group
ansible.builtin.user:
name: "{{ deploy_user }}"
groups: docker
append: true
Điểm cần nhớ:
Group membership có thể cần logout/login lại mới có hiệu lực đối với session hiện tại.
19. SSH Key
Giả sử public key của Control Node:
~/.ssh/id_ed25519.pub
Có thể sử dụng:
- name: Configure deploy user SSH key
ansible.posix.authorized_key:
user: "{{ deploy_user }}"
state: present
key: "{{ lookup('file', lookup('env', 'HOME') + '/.ssh/id_ed25519.pub') }}"
Cần collection:
ansible-galaxy collection install ansible.posix
20. Tại sao không copy private key?
Tuyệt đối không làm:
Control Node
│
│ private key
▼
Server
Private key phải nằm ở nơi cần dùng nó, thông thường là:
Control Node
~/.ssh/id_ed25519
Server chỉ cần:
~/.ssh/authorized_keys
Tư duy:
Private Key → NEVER upload to server
Public Key → authorized_keys
21. Role ssh
Tạo:
ansible-galaxy role init roles/ssh
Mục tiêu:
SSH
├── Configure
├── Validate
└── Reload
22. SSH Configuration
Tạo:
roles/ssh/templates/sshd_config.j2
Ví dụ:
Port {{ ssh_port }}
PubkeyAuthentication yes
PasswordAuthentication {{ ssh_password_authentication }}
PermitRootLogin {{ ssh_permit_root_login }}
Variables:
ssh_port: 22
ssh_password_authentication: "no"
ssh_permit_root_login: "no"
23. Deploy SSH Configuration
---
- name: Deploy sshd configuration
ansible.builtin.template:
src: sshd_config.j2
dest: /etc/ssh/sshd_config
owner: root
group: root
mode: "0600"
validate: "/usr/sbin/sshd -t -f %s"
notify: Restart ssh
Đây là pattern chúng ta đã học:
Template
↓
Validate
↓
Changed?
↓
Notify
↓
Restart SSH
24. SSH Handler
roles/ssh/handlers/main.yml
---
- name: Restart ssh
ansible.builtin.service:
name: ssh
state: restarted
Cực kỳ quan trọng: SSH là service quản lý kết nối của chính Ansible.
Nếu cấu hình sai:
Ansible
↓
Modify SSH
↓
SSH broken
↓
Connection lost
Vì vậy luôn phải:
validate sshd_config
trước khi reload/restart.
25. Role docker
Tạo:
ansible-galaxy role init roles/docker
Docker trên Ubuntu nên được cài từ repository phù hợp thay vì chỉ chạy:
apt install docker.io
Đối với lab, chúng ta có thể sử dụng Docker's official repository.
26. Docker Repository
Task:
---
- name: Install Docker prerequisites
ansible.builtin.apt:
name:
- ca-certificates
- curl
state: present
- name: Create Docker keyring directory
ansible.builtin.file:
path: /etc/apt/keyrings
state: directory
mode: "0755"
Sau đó cấu hình Docker repository và GPG key.
Một điểm quan trọng:
Repository configuration cũng phải được quản lý bằng Ansible, không nên yêu cầu người học SSH vào server rồi tự chạy từng command.
27. Install Docker
Ví dụ task:
- name: Install Docker packages
ansible.builtin.apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
state: present
update_cache: true
28. Enable Docker
- name: Ensure Docker is enabled and running
ansible.builtin.service:
name: docker
state: started
enabled: true
Kỳ vọng:
Docker
├── installed
├── enabled
└── running
29. Kiểm tra Docker
docker --version
và:
docker compose version
Sau đó:
systemctl status docker
30. Role application_server
Role này chuẩn bị filesystem cho application.
Tạo:
ansible-galaxy role init roles/application_server
31. Tạo Application Directory
---
- name: Create application directory
ansible.builtin.file:
path: "{{ application_dir }}"
state: directory
owner: "{{ deploy_user }}"
group: "{{ deploy_user }}"
mode: "0755"
Ví dụ:
/opt/todo
32. Tạo Subdirectories
Có thể tạo:
- name: Create application subdirectories
ansible.builtin.file:
path: "{{ application_dir }}/{{ item }}"
state: directory
owner: "{{ deploy_user }}"
group: "{{ deploy_user }}"
mode: "0755"
loop:
- config
- data
- logs
- backups
Kết quả:
/opt/todo/
├── config/
├── data/
├── logs/
└── backups/
Đây là một ví dụ tốt về automation thay vì chạy:
mkdir
mkdir
mkdir
mkdir
33. Tạo Application Environment File
Template:
roles/application_server/templates/app.env.j2
APP_NAME={{ application_name }}
APP_ENV={{ app_environment }}
APP_PORT={{ app_port }}
Variables:
application_name: todo
app_environment: production
app_port: 8080
Task:
- name: Deploy application environment
ansible.builtin.template:
src: app.env.j2
dest: "{{ application_dir }}/config/app.env"
owner: "{{ deploy_user }}"
group: "{{ deploy_user }}"
mode: "0640"
34. Lưu ý về Secrets
File:
app.env
có thể chứa:
DATABASE_PASSWORD
JWT_SECRET
API_KEY
Không nên để secret trực tiếp:
database_password: "123456"
Trong Lab này chỉ tạo structure.
Secret management sẽ được xử lý kỹ ở:
Lab 11 — Ansible Secrets & Vault
35. Main Playbook
Tạo:
site.yml
---
- name: Provision application servers
hosts: application_servers
become: true
roles:
- common
- users
- docker
- ssh
- application_server
Bây giờ Playbook chỉ mô tả:
Server cần có:
Common
Users
Docker
SSH
Application structure
Không cần biết từng Task bên trong.
36. Thứ tự Role rất quan trọng
Chúng ta có:
common
↓
users
↓
docker
↓
ssh
↓
application_server
Tại sao?
common
Cài package cơ bản.
users
Tạo deploy.
docker
Cài Docker.
ssh
Configure access.
application_server
Sử dụng deploy user và filesystem.
Đây chính là dependency ordering.
37. Chạy Provisioning
ansible-playbook site.yml
Ansible sẽ:
app-01
│
├── common
│
├── users
│
├── docker
│
├── ssh
│
└── application_server
38. Kiểm tra kết quả
SSH:
ssh deploy@192.168.56.101
Kiểm tra user:
whoami
Kỳ vọng:
deploy
39. Kiểm tra Docker
docker ps
Nếu user vừa được thêm vào group:
groups
Nếu chưa thấy:
docker
logout/login lại.
40. Kiểm tra Directory
ls -la /opt/todo
Kỳ vọng:
config
data
logs
backups
41. Kiểm tra SSH
sshd -t
Nếu không có output:
Configuration OK
Kiểm tra service:
systemctl status ssh
42. Test Idempotency
Đây là phần bắt buộc.
Chạy:
ansible-playbook site.yml
Lần đầu:
changed > 0
Sau đó chạy:
ansible-playbook site.yml
Lần hai:
changed = 0
Nếu lần thứ hai vẫn:
changed > 0
thì phải tìm nguyên nhân.
43. Debug Idempotency
Ví dụ:
TASK [Create directory]
changed
mỗi lần.
Có thể do:
permissions
owner
group
path
không ổn định.
Hoặc:
ansible.builtin.command
được sử dụng không đúng.
Hoặc template luôn tạo ra output khác nhau.
44. Check Mode
Trước khi provisioning production:
ansible-playbook site.yml --check
Ansible sẽ cố gắng mô phỏng thay đổi.
Kết hợp:
ansible-playbook site.yml --check --diff
để xem configuration difference.
45. Tags
Khi project lớn, có thể thêm Tags.
Ví dụ:
- name: Install Docker
ansible.builtin.apt:
name: docker-ce
state: present
tags:
- docker
Sau đó:
ansible-playbook site.yml --tags docker
Chỉ chạy phần Docker.
46. Tags theo Role
Có thể:
roles:
- role: common
tags:
- common
- role: docker
tags:
- docker
- role: ssh
tags:
- ssh
Sau đó:
ansible-playbook site.yml --tags docker
47. --limit
Giả sử Inventory:
app-01
app-02
app-03
Muốn chỉ provision:
app-02
dùng:
ansible-playbook site.yml --limit app-02
Đây là kỹ năng rất quan trọng khi làm Production.
48. --limit trong Production
Không nên:
ansible-playbook site.yml
ngay lập tức trên:
100 servers
nếu chưa test.
Có thể:
1 server
↓
3 servers
↓
10 servers
↓
100 servers
Ví dụ:
ansible-playbook site.yml --limit app-01
Sau khi kiểm tra:
ansible-playbook site.yml --limit app-01,app-02
49. Serial Deployment
Ở level cao hơn, có thể sử dụng:
serial: 1
Ví dụ:
---
- name: Provision application servers
hosts: application_servers
become: true
serial: 1
roles:
- common
- docker
- ssh
Ansible sẽ xử lý:
app-01
↓
app-02
↓
app-03
thay vì:
app-01
app-02
app-03
↓
cùng lúc
Đây là tư duy rollout an toàn.
50. Provisioning và Failure
Giả sử:
app-01 → SUCCESS
app-02 → SUCCESS
app-03 → FAILED
Không nên chạy lại một cách mù quáng.
Trước tiên:
ansible-playbook site.yml --limit app-03
Debug:
Why did app-03 fail?
Sau khi fix:
ansible-playbook site.yml --limit app-03
Đây là workflow thực tế.
51. become
Provisioning thường cần quyền root.
Ví dụ:
hosts: application_servers
become: true
Ansible sẽ thực hiện:
deploy user
↓
sudo
↓
root
Các thao tác như:
apt install
/etc
systemd
user creation
SSH config
thường cần quyền cao.
52. Không nên SSH bằng root
Mô hình không tốt:
Internet
│
│ SSH
▼
root
Tốt hơn:
Developer / CI
│
│ SSH Key
▼
deploy user
│
│ sudo
▼
root
Đây là một nguyên tắc security quan trọng.
53. Tại sao cần deploy user?
Không nên để:
application
↓
root
Thay vào đó:
application
↓
deploy
với:
sudo
chỉ khi cần.
Điều này giảm blast radius nếu application bị compromise.
54. Provisioning hoàn chỉnh
Bây giờ chúng ta có:
Ansible
│
▼
New Server
│
┌─────────┴─────────┐
▼ ▼
Common Users
│ │
▼ ▼
Packages deploy
│ │
└─────────┬─────────┘
▼
Docker
│
▼
SSH
│
▼
Application Server
Đây là kết quả chính của Lab.
55. Exercise 1 — Provision Basic Server
Tạo Role:
common
Cài:
curl
git
vim
htop
unzip
Yêu cầu:
Run 1 → changed
Run 2 → changed = 0
56. Exercise 2 — Deploy User
Tạo:
deploy
Yêu cầu:
username: deploy
shell: /bin/bash
home: /home/deploy
Thêm vào:
sudo
và:
docker
group.
57. Exercise 3 — SSH Key
Copy public key của Control Node vào:
/home/deploy/.ssh/authorized_keys
Test:
ssh deploy@server
Không sử dụng password.
58. Exercise 4 — Docker
Provision:
Docker Engine
Docker Compose plugin
Kiểm tra:
docker --version
docker compose version
Sau đó:
docker run --rm hello-world
59. Exercise 5 — Application Filesystem
Tạo:
/opt/todo/
├── config/
├── data/
├── logs/
└── backups/
Owner:
deploy:deploy
Permissions:
0755
60. Exercise 6 — Environment Configuration
Template:
app.env.j2
Variables:
app_name: todo
app_environment: production
app_port: 8080
Kết quả:
/opt/todo/config/app.env
61. Exercise 7 — Full Provisioning
Chạy:
ansible-playbook site.yml
Sau đó verify:
✓ Packages
✓ User
✓ SSH
✓ Docker
✓ Docker Compose
✓ Directories
✓ Application configuration
62. Exercise 8 — Destroy and Rebuild
Đây là bài tập rất quan trọng.
Xóa VM hoặc tạo một VM Ubuntu mới.
Server:
Fresh Ubuntu
Không tự SSH vào server để cài Docker.
Chỉ chạy:
ansible-playbook site.yml
Sau đó kiểm tra:
Docker
User
SSH
Directories
Packages
Nếu toàn bộ hoạt động:
Bạn đã thực sự xây dựng được một provisioning automation.
63. Exercise 9 — New Server Challenge
Tạo:
app-02
Không sửa Role.
Chỉ thêm Inventory:
[application_servers]
app-01 ansible_host=192.168.56.101
app-02 ansible_host=192.168.56.102
Chạy:
ansible-playbook site.yml
Mục tiêu:
app-01 → Ready
app-02 → Ready
Đây chính là sức mạnh của Infrastructure as Code.
64. Exercise 10 — Production Simulation
Giả sử bạn có:
app-01
app-02
app-03
Provision theo thứ tự:
app-01
↓
verify
↓
app-02
↓
verify
↓
app-03
Sử dụng:
serial: 1
và:
--limit
65. Tư duy Senior #1 — Server phải Disposable
Một tư duy rất quan trọng:
Đừng coi server là thứ phải được cấu hình thủ công và giữ nguyên mãi mãi.
Thay vào đó:
Server chết
↓
Tạo server mới
↓
Ansible
↓
Ready
Mô hình:
Infrastructure
↓
Code
↓
Rebuild
Đây là một trong những nền tảng của Cloud và DevOps.
66. Tư duy Senior #2 — Không SSH thủ công để sửa Production
Không nên:
SSH
↓
vim /etc/...
↓
systemctl restart
sau đó quên mất mình đã thay đổi gì.
Tốt hơn:
Git
↓
Ansible
↓
Server
Thay đổi được:
Version controlled
Reviewable
Repeatable
Auditable
67. Tư duy Senior #3 — Infrastructure cũng là Code
Ví dụ:
deploy_user: deploy
timezone: Asia/Tokyo
docker_enabled: true
được lưu trong Git.
Do đó:
Infrastructure
↓
Code
↓
Git
↓
Review
↓
Deploy
Infrastructure bắt đầu có lifecycle giống application code.
68. Tư duy Senior #4 — Provisioning phải Idempotent
Một provisioning script tốt:
Run 1
↓
Build server
Run 2:
Run 2
↓
Nothing unnecessary
Không nên:
Run 2
↓
Reinstall Docker
↓
Recreate user
↓
Restart everything
69. Tư duy Senior #5 — Fail Fast
Ví dụ SSH configuration.
Không nên:
Deploy bad config
↓
Restart SSH
↓
Connection lost
Nên:
Generate
↓
Validate
↓
Deploy
↓
Restart
Tương tự:
Nginx
sshd
Docker
Application config
Validation là một phần của provisioning production-grade.
70. Tư duy Senior #6 — Không Hard-code Environment
Không nên:
server_name: production.example.com
trong Role.
Nên:
Role
↓
Variable
↓
Environment
Ví dụ:
Dev
server_name: todo.dev.example.com
Staging
server_name: todo.staging.example.com
Production
server_name: todo.example.com
Role vẫn giữ nguyên.
Chúng ta sẽ phát triển ý tưởng này ở:
Lab 13 — Ansible với nhiều Environment
71. Tư duy Senior #7 — Provisioning phải có thể Review
Một engineer khác nên có thể nhìn vào:
site.yml
và hiểu:
common
docker
ssh
application_server
Nếu muốn biết chi tiết:
roles/docker/
roles/ssh/
Điều này tốt hơn một Playbook 1.500 dòng.
72. Kiến trúc sau Lab 9
Đến đây project đã tiến hóa:
Lab 1
Fundamentals
↓
Lab 4
Playbook
↓
Lab 5
Variables
↓
Lab 6
Templates
↓
Lab 7
Handlers + Idempotency
↓
Lab 8
Roles
↓
Lab 9
Server Provisioning
Tư duy:
Run Commands
↓
Write Playbooks
↓
Manage Desired State
↓
Create Reusable Roles
↓
Provision Entire Servers
Đây là bước chuyển từ:
Ansible user
sang:
Infrastructure Automation Engineer
73. Checklist hoàn thành Lab 9
Người học cần có khả năng:
- [ ] Hiểu Server Provisioning.
- [ ] Phân biệt Provisioning và Deployment.
- [ ] Tạo server từ trạng thái sạch.
- [ ] Cài package bằng Ansible.
- [ ] Tạo user.
- [ ] Quản lý group.
- [ ] Quản lý SSH public key.
- [ ] Cấu hình SSH.
- [ ] Validate SSH configuration.
- [ ] Cài Docker.
- [ ] Cài Docker Compose plugin.
- [ ] Enable và start Docker.
- [ ] Tạo application directories.
- [ ] Quản lý ownership/permissions.
- [ ] Sử dụng Jinja2 trong provisioning.
- [ ] Sử dụng Roles để tổ chức provisioning.
- [ ] Sử dụng
become. - [ ] Sử dụng
--check. - [ ] Sử dụng
--diff. - [ ] Sử dụng
--limit. - [ ] Hiểu
serial. - [ ] Kiểm tra Idempotency.
- [ ] Rebuild một server mới chỉ bằng Ansible.
- [ ] Hiểu tại sao không nên cấu hình Production thủ công bằng SSH.
74. Final Project — Build a Production-Ready Server
Đây là bài quan trọng nhất của Lab.
Bạn cần xây dựng:
Ansible Control Node
│
│ SSH
▼
Ubuntu Server
│
┌────────────────┼────────────────┐
│ │ │
▼ ▼ ▼
Common Users SSH
│ │ │
└────────────────┼────────────────┘
▼
Docker
│
▼
Application Server
│
┌────────────┼────────────┐
▼ ▼ ▼
config data logs
Server cuối cùng phải đạt:
✓ Base packages installed
✓ deploy user created
✓ SSH key authentication
✓ Root SSH disabled
✓ Password SSH disabled
✓ Docker installed
✓ Docker Compose installed
✓ Docker enabled
✓ Docker running
✓ /opt/todo created
✓ Correct ownership
✓ Application config generated
✓ All tasks idempotent
Và quan trọng nhất:
ansible-playbook site.yml
có thể biến:
Fresh Ubuntu Server
thành:
Application-ready Server
mà không cần SSH vào server và chạy thủ công bất kỳ command cài đặt nào.
Đây chính là tiêu chuẩn mà người học nên đạt được sau Lab 9 trước khi bước sang Lab 10 — Ansible Application Deployment.
All rights reserved