Khi cần dựng mới hoặc dựng lại hàng loạt Cloud VPS (balancer, minion, database, máy vận hành), cài hệ điều hành bằng tay cho từng máy vừa chậm vừa khó đồng nhất. Cách làm hiện đại là tạo mọi máy từ cùng một cloud image, và để cloud-init lo phần việc của lần boot đầu: đặt hostname, nới phân vùng theo dung lượng đĩa thật, nạp SSH key. Tài liệu này giải thích cloud image khác gì cài tay, cách kiểm tra cloud-init đã làm gì trên máy, phần nào vẫn phải chuẩn hoá bằng script, mẫu cloud-config cho minion và mẹo xác định đúng ngày một máy được dựng.
1. Cloud image khác gì cài hệ điều hành bằng tay
Cài tay là boot từ file ISO, chọn phân vùng, chọn gói, đặt user rồi chờ cài xong. Mỗi máy mất vài chục phút, và hai máy cài bởi hai người hiếm khi giống hệt nhau. Cloud image là một ổ đĩa đã cài sẵn hệ điều hành ở trạng thái tối thiểu; nền tảng cloud chỉ việc copy ổ đĩa đó cho VPS mới rồi boot lên. Debian phát hành sẵn các bản này tại kho cloud image chính thức của Debian, nhiều nhà cung cấp dùng chúng làm template.
| Cài tay từ ISO | Cloud image + cloud-init | |
|---|---|---|
| Thời gian có máy dùng được | Vài chục phút, cần người ngồi chọn | Vài giây sau khi boot đã SSH được |
| Độ đồng nhất | Phụ thuộc người cài | Mọi máy giống nhau tới từng gói |
| Hostname, IP, SSH key | Nhập trong lúc cài | cloud-init đọc từ metadata của nền tảng |
| Kích thước phân vùng | Chọn khi cài | Image nhỏ, cloud-init tự nới ra bằng dung lượng đĩa |
Vấn đề duy nhất của cloud image: một ổ đĩa giống hệt nhau không thể chứa sẵn hostname hay SSH key riêng của từng máy. Phần "cá nhân hoá" đó là việc của cloud-init.
2. cloud-init là gì và chạy lúc nào
cloud-init là bộ công cụ có sẵn trong hầu hết cloud image Linux. Ở lần boot đầu, nó tìm datasource (nguồn metadata do nền tảng cung cấp), đọc thông tin của máy rồi chạy một chuỗi module theo bốn giai đoạn:
- init-local: trước khi có mạng, xác định datasource và cấu hình mạng.
- init: có mạng rồi, chạy các module nền như đặt hostname,
growpart,resizefs, tạo user, nạp SSH key. - config: các module cấu hình như locale, timezone, apt, ntp,
runcmd. - final: cài gói, chạy script của người dùng, ghi file đánh dấu
boot-finished.
Mỗi module có tần suất riêng: per-instance (chỉ chạy một lần cho mỗi máy), per-boot hoặc always. Người dùng điều khiển cloud-init qua user-data, thường là một file YAML #cloud-config truyền vào lúc tạo máy. Danh sách module và cú pháp đầy đủ có trong tài liệu chính thức của cloud-init.
3. Kiểm tra cloud-init đã làm gì trên máy
Thay vì đoán, hãy đọc thẳng trên máy vừa tạo:
$ cloud-init --version
$ cloud-init status --long # mong đợi: status: done
# các mốc chính trong log
$ grep -E "running 'init-local'|Setting the hostname|resized" /var/log/cloud-init.log
... Cloud-init v. 22.4.2 running 'init-local' at ... Up 5.33 seconds.
... cc_set_hostname.py[DEBUG]: Setting the hostname to app-php82-1 (app-php82-1)
... cc_growpart.py[INFO]: '/' resized: changed (/dev/vda, 1) ...
# dữ liệu cloud-init đã nhận
$ ls -l /var/lib/cloud/instance/
$ cat /var/lib/cloud/instance/user-data.txtThông thường chỉ vài giây sau khi máy bật, cloud-init đã đặt xong hostname và nới phân vùng gốc ra hết dung lượng đĩa (growpart rồi resize2fs). Nếu user-data.txt rỗng, nghĩa là không có cloud-config nào được truyền vào: mọi việc cloud-init làm đều đến từ cấu hình mặc định trong /etc/cloud/cloud.cfg của image cùng metadata của nền tảng. Hãy đọc kỹ file này; ví dụ dòng disable_root: false nghĩa là image cho phép SSH bằng root ngay từ đầu, thuận tiện nhưng cần siết lại sau khi máy lên.
4. Phần cloud-init không làm: chuẩn hoá máy mới
Một máy vừa boot từ cloud image mới chỉ là Debian trống. Để thành minion hay balancer, nó cần thêm một danh sách việc. Nhật ký dpkg cho biết thứ tự những gì đã được cài sau lần boot đầu:
$ zgrep -h " install " /var/log/dpkg.log* | sort | awk '{print $1, $2, $4}'Danh sách chuẩn hoá tối thiểu cho một Cloud VPS mới:
| Việc | Áp dụng cho | Vì sao |
|---|---|---|
| Đặt hostname đúng quy ước, khai báo vào DNS nội bộ | Mọi máy | Script và cấu hình gọi máy bằng tên (xem bài DNS nội bộ) |
| Repo PHP sury, PHP-FPM đúng phiên bản của nhóm minion | Minion | Debian 12 chỉ có sẵn PHP 8.2 (xem bài chạy nhiều phiên bản PHP song song) |
User chạy web, thư mục site thống nhất (ví dụ /var/www) | Minion | Mọi site, mọi script dùng chung một quy ước sở hữu và đường dẫn |
| nginx, supervisor (include file cấu hình theo site) | Minion | Web server và queue worker theo site |
| nginx, certbot, snippet bảo mật | Balancer | Reverse proxy và SSL |
Swapfile nhỏ, vm.swappiness thấp | Máy RAM nhỏ | Có vùng đệm trước khi OOM killer ra tay |
| node_exporter hoặc agent giám sát | Mọi máy | Đưa CPU, RAM, đĩa của máy vào giám sát |
| ufw, SSH key của hệ thống vận hành, tắt đăng nhập root bằng mật khẩu | Mọi máy | Chỉ mở cổng cần thiết, script tự động SSH được |
Phần cố định của danh sách nên đưa vào cloud-config để máy mới tự hoàn chỉnh ngay lần boot đầu, thay vì chạy script tay sau khi máy lên. Một mẫu minh hoạ cho minion:
#cloud-config
hostname: app-php82-1
package_update: true
packages: [nginx, supervisor, git, zip, unzip, ufw, ca-certificates, curl]
swap:
filename: /swapfile
size: 1G
write_files:
- path: /etc/sysctl.d/90-swap.conf
content: "vm.swappiness=10\n"
runcmd:
- curl -fsSL https://packages.sury.org/php/apt.gpg -o /etc/apt/keyrings/php.gpg
- echo "deb [signed-by=/etc/apt/keyrings/php.gpg] https://packages.sury.org/php/ bookworm main" > /etc/apt/sources.list.d/php.list
- apt-get update && apt-get install -y php8.2-fpm php8.2-cli php8.2-mysql php8.2-mbstring php8.2-xml
- mkdir -p /var/www && chown www-data:www-data /var/www
- sysctl --systemTrước khi tin một file user-data, kiểm tra cú pháp bằng cloud-init schema --config-file user-data.yaml, và sau lần boot đầu, đọc /var/log/cloud-init-output.log để thấy output của từng lệnh trong runcmd.
5. Mẹo điều tra: máy này thật sự được dựng ngày nào?
Khi dựng lại hàng loạt máy, câu hỏi "máy này dựng lúc nào, trước hay sau đợt thay đổi kia" xuất hiện liên tục. Cách đầu tiên ai cũng nghĩ tới lại là cách sai:
$ tune2fs -l /dev/vda1 | grep created
Filesystem created: <ngày build image>Trên mọi máy tạo từ cùng một cloud image, giá trị này giống hệt nhau, dù máy dựng vào ngày nào. Đó là ngày filesystem được tạo khi build cloud image, không phải ngày tạo VPS; thư mục /lost+found cũng mang đúng ngày đó. Ngày dựng thật phải tìm ở những file mà cloud-init hoặc systemd ghi ra trong lần boot đầu:
# 1. Lần boot đầu tiên trong log cloud-init (nếu log chưa bị xoay mất)
zcat -f /var/log/cloud-init.log* | grep -m1 "running 'init-local'"
# 2. machine-id được sinh ra ở lần boot đầu
stat -c '%y' /etc/machine-id
# 3. Thư mục instance: mỗi instance-id mới là một thư mục
ls -l --time-style=+%F /var/lib/cloud/instances/
# 4. Lần đặt hostname gần nhất
stat -c '%y' /etc/hostname
zcat -f /var/log/cloud-init.log* | grep "Setting the hostname"
# 5. Gói đầu tiên được cài thêm
zgrep -h " install " /var/log/dpkg.log* | sort | head -1Đối chiếu các nguồn này với nhau mới ra câu trả lời đúng. Lý do cần đối chiếu: hostname do cloud-init đặt theo tên trong metadata của nền tảng ở mỗi lần boot. Một máy có thể boot lần đầu với tên tạm, vài ngày sau mới được đổi sang tên đúng quy ước, thậm chí đổi nhiều lần nếu gõ nhầm; log cloud-init ghi lại đủ từng lần. Vì vậy:
- mtime của
/etc/hostnamelà ngày đặt tên cuối cùng, gần với ngày máy được đưa vào sử dụng theo quy ước; /etc/machine-id, thư mục instance và dònginit-localđầu tiên mới là ngày máy ra đời;- ngày tạo filesystem chỉ cho biết image được build khi nào.
6. Lỗi thường gặp với cloud image và cloud-init
- Đổi tên máy nhưng hostname quay về tên cũ sau reboot: sửa
/etc/hostnamebằng tay trong khi metadata của nền tảng vẫn giữ tên cũ. Đổi tên ở cả nền tảng, hoặc đặtpreserve_hostname: truenếu muốn tự quản lý. - Đĩa mua lớn mà
dfchỉ thấy vài GB:growpartkhông chạy được (thiếu góicloud-guest-utilshoặc phân vùng không nằm cuối đĩa). Kiểm tra dòngcc_growparttrong log. - runcmd lỗi nhưng máy vẫn báo "done": lệnh trong
runcmdlỗi không chặn boot. Luôn đọccloud-init-output.logvàcloud-init status --long. - Chạy lại cloud-init trên máy đang sống:
cloud-init cleanrồi reboot sẽ chạy lại mọi moduleper-instance, kể cả tạo user và ghi đè file. Chỉ làm trên máy thử. - Template image cũ: image có thể được build từ lâu, nên máy mới boot lên đã thiếu nhiều bản vá. Bước đầu tiên luôn là
apt-get update && apt-get upgrade, hoặc bậtpackage_upgrade: truetrong cloud-config.
7. Checklist dựng Cloud VPS bằng cloud-init
- Chọn image chính thức, ghi lại phiên bản image đang dùng làm template.
- Đặt tên VPS đúng quy ước ngay khi tạo, vì cloud-init lấy hostname từ đó.
- Phần cố định (repo, gói, user, swap, sysctl) nằm trong user-data, đã qua
cloud-init schema. - Sau lần boot đầu:
cloud-init status --long, đọccloud-init-output.log, kiểm tradf -h. - Siết SSH (
disable_root, chỉ đăng nhập bằng key), bật ufw, cập nhật bản vá. - Khai báo máy vào DNS nội bộ và giám sát trước khi đưa site lên.
Cloud image và cloud-init giải quyết phần "máy trống": vài giây có máy SSH được, hostname đúng, đĩa đủ. Phần "máy đúng vai trò" vẫn là trách nhiệm của mình: cái gì cố định thì đưa vào user-data, cái gì phụ thuộc site thì để cho pipeline như trong bài deploy tự động đầu tiên.
Bài tiếp theo
Jenkins làm "control plane" cho hàng trăm website. Khi đã có hàng chục Cloud VPS, mọi thao tác tạo site, deploy, backup, đổi domain được gom về một chỗ.
