Viết script preflight để máy khách nói thật trước khi bạn cài
Mỗi kiểm tra trả về PASS, WARN hoặc FAIL, và chỉ FAIL mới được dừng việc cài.
Tóm tắt nhanh
- Elasticsearch từ chối khởi động ở chế độ production nếu một bootstrap check thất bại, còn ở chế độ development thì lỗi đó chỉ hiện thành cảnh báo. Script preflight nên chia mức độ theo đúng cách này.
- Mỗi thông báo FAIL cần nêu rõ con số đo được, ngưỡng yêu cầu và cách sửa, giống báo cáo của Leapp khi nó ghi rõ vì sao nâng cấp bị chặn.
- Cho miễn trừ từng kiểm tra theo tên, như kubeadm hạ lỗi của các check được chỉ định xuống thành cảnh báo, nhưng đừng bao giờ chấp nhận miễn trừ tất cả.
Nếu một bootstrap check thất bại khi Elasticsearch đang ở chế độ production, nó sẽ không chịu khởi động. Cũng lỗi đó, ở chế độ development, chỉ hiện thành một dòng cảnh báo trong log. Có thể bạn chưa từng cài Elasticsearch cho khách, nhưng quy tắc này vẫn đáng mang theo đến mọi lần triển khai.
Thử hình dung một buổi cài ở site khách: bộ cài chạy được nửa chừng thì đổ lỗi, rồi mới lộ ra swap đang bật, thông số kernel chưa đặt hoặc firewall chặn cổng. Script preflight đưa toàn bộ những phát hiện này lên trước giờ cài. Nó còn quy định rõ phát hiện nào được phép dừng việc cài, phát hiện nào chỉ cần ghi lại.
Bài này hướng dẫn bạn dựng một script như thế bằng bash. Bạn cần một máy Linux hoặc VM có quyền sudo để thử. Các lệnh đọc RAM, swap, cổng mạng là bản rút gọn, giả định Linux: trước khi dùng thật, hãy kiểm tra lại trên đúng bản distro của khách.
Mức độ phải có luật, không thể tùy hứng
Mỗi phát hiện chỉ được xếp vào một mức theo một câu hỏi: nếu bỏ qua nó, thành phần sắp cài sẽ từ chối chạy hay chỉ chạy kém? Từ chối chạy là FAIL. Chạy kém, hoặc chưa chắc, là WARN.
Tài liệu Kubernetes cho sẵn một ví dụ FAIL rõ ràng: mặc định, kubelet không khởi động nếu phát hiện swap trên node. Ngược lại, một bản OS bạn chưa từng thử với sản phẩm của mình chưa có nghĩa là hỏng, nên nó chỉ đáng là WARN.
Để ý chữ “thành phần sắp cài” trong câu hỏi. Swap là FAIL vì kubelet, chứ không phải vì mọi phần mềm. Mỗi kiểm tra chỉ có giá trị khi đúng thành phần của nó nằm trong danh sách cài, và script bên dưới sẽ bật check theo thành phần.
Bước 1: một hàm report chịu trách nhiệm cho mọi kết quả
Mọi kiểm tra đều đi qua một hàm duy nhất. Chính hàm này áp chế độ chạy và danh sách miễn trừ.
#!/usr/bin/env bash
# preflight.sh — chỉ đọc, không sửa gì trên máy khách
MODE="${MODE:-production}" # production | development (chỉ dùng trên VM thử)
WAIVE="${WAIVE:-}" # check được hạ FAIL -> WARN, ví dụ "swap,net_443"
FAILS=0; WARNS=0
if [ "$WAIVE" = all ]; then
echo "Không chấp nhận WAIVE=all: hãy liệt kê từng check"; exit 2
fi
report() { # report <id> <PASS|WARN|FAIL> <thông báo>
local id="$1" level="$2" msg="$3"
if [ "$level" = FAIL ]; then
if [ "$MODE" = development ] || [[ ",$WAIVE," == *",$id,"* ]]; then
level=WARN; msg="$msg (đã hạ xuống WARN)"
fi
fi
case "$level" in FAIL) FAILS=$((FAILS+1));; WARN) WARNS=$((WARNS+1));; esac
printf '%-5s %-14s %s\n' "$level" "$id" "$msg"
}
Chế độ development mô phỏng cách Elasticsearch hạ lỗi xuống cảnh báo. WAIVE thì học từ kubeadm: pha preflight chạy trước khi nâng cấp có cờ --ignore-preflight-errors, nhận một danh sách check mà lỗi của chúng chỉ hiện thành cảnh báo. Script này cố ý từ chối giá trị all, để mỗi lần miễn trừ đều phải gọi đúng tên.
Có điều MODE=development hạ mọi FAIL xuống WARN, nên trên thực tế nó cũng là một kiểu bỏ qua tất cả. Vì thế chế độ này chỉ dành cho VM thử nghiệm của bạn, không bao giờ chạy trên máy production của khách. Ở đó chỉ có production, cộng với WAIVE liệt kê từng tên check.
Kiểm tra: gọi thử report demo FAIL "test" với WAIVE=demo rồi chạy lại. Lần chạy đó phải in ra WARN kèm ghi chú “đã hạ xuống WARN”.
Bước 2: tài nguyên, có ngưỡng ghi rõ trong thông báo
MIN_RAM_KB="${MIN_RAM_KB:-2097152}" # ngưỡng giả định, thay bằng khuyến nghị của sản phẩm bạn
check_ram() {
local kb; kb=$(awk '/^MemTotal:/ {print $2}' /proc/meminfo)
if [ "$kb" -ge "$MIN_RAM_KB" ]; then report ram PASS "MemTotal=${kb}kB"
else report ram WARN "MemTotal=${kb}kB, khuyến nghị từ ${MIN_RAM_KB}kB"; fi
}
check_swap() {
local kb; kb=$(awk '/^SwapTotal:/ {print $2}' /proc/meminfo)
if [ "$kb" -eq 0 ]; then report swap PASS "không có swap"
else report swap FAIL "swap=${kb}kB, kubelet mặc định sẽ không khởi động"; fi
}
check_max_map_count() {
local v; v=$(sysctl -n vm.max_map_count 2>/dev/null || echo 0)
if [ "$v" -ge 262144 ]; then report max_map_count PASS "vm.max_map_count=$v"
else report max_map_count FAIL "vm.max_map_count=$v, cần >= 262144, sửa bằng sysctl"; fi
}
Mỗi ngưỡng thuộc về một sản phẩm. Swap là chuyện của Kubernetes, vì kubelet không chạy khi có swap. Còn vm.max_map_count đặt qua sysctl ở mức ít nhất 262144 là yêu cầu của Elasticsearch, nên khách chỉ cài Kubernetes thì check này không có lý do để chặn họ.
RAM được xếp vào WARN vì thiếu RAM thường làm hệ thống chạy kém chứ chưa chắc khiến nó từ chối chạy. Ngưỡng MIN_RAM_KB trong ví dụ là giả định: hãy lấy con số từ tài liệu sản phẩm bạn đang cài và ghi nguồn vào README.
Có một cái bẫy nhỏ: con số MemTotal mà máy báo có thể thấp hơn dung lượng RAM ghi trên giấy, nên một VM “2 GB” vẫn có thể rơi xuống dưới ngưỡng và bị WARN oan. Trước khi chốt, hãy chạy thử trên máy thật của khách xem nó báo bao nhiêu.
Kiểm tra: trên một VM có bật swap, với COMPONENTS=kubernetes, bạn phải thấy FAIL ở dòng swap.
Bước 3: phiên bản, mạng và quyền
check_os() {
. /etc/os-release
case "$ID-$VERSION_ID" in
rhel-8*|rhel-9*) report os PASS "$PRETTY_NAME" ;; # danh sách ví dụ
*) report os WARN "$PRETTY_NAME chưa nằm trong danh sách đã thử" ;;
esac
}
check_port() { # check_port <host> <port> — kết nối TCP đi ra từ máy đang chạy script
if timeout 3 bash -c "</dev/tcp/$1/$2" 2>/dev/null; then
report "net_$2" PASS "kết nối được $1:$2"
else report "net_$2" FAIL "không mở được kết nối tới $1:$2"; fi
}
check_root() {
if [ "$(id -u)" -eq 0 ]; then report root PASS "đang chạy bằng root"
else report root WARN "không phải root, các bước sửa sẽ cần sudo"; fi
}
Danh sách OS trong ví dụ là giả định. Bạn thay bằng danh sách đã thật sự kiểm thử với sản phẩm của mình, và giữ OS lạ ở mức WARN.
check_port chỉ kiểm tra chiều đi ra: máy này có mở được kết nối TCP tới một endpoint, chẳng hạn registry, hay không. Muốn soát chiều đi vào, như cổng 6443 của API server Kubernetes, hãy chạy cùng hàm từ một máy khác trong mạng của khách. Tài liệu Kubernetes cũng gợi ý dùng netcat cho việc này.
Cách kết nối qua /dev/tcp là bản rút gọn. Nó cho biết có mở được kết nối hay không, nhưng không thay được việc hỏi khách về proxy và firewall. Nếu khách dựng nhiều VM từ cùng một template, hãy soát thêm MAC address và product_uuid giữa các node: tài liệu kubeadm cảnh báo giá trị trùng nhau có thể làm cài đặt thất bại.
Bước 4: chọn check theo thành phần, rồi trả exit code
COMPONENTS="${COMPONENTS:-kubernetes}" # ví dụ "kubernetes,elasticsearch"
has() { [[ ",$COMPONENTS," == *",$1,"* ]]; }
check_os; check_root; check_ram
has kubernetes && check_swap
has elasticsearch && check_max_map_count
check_port registry.example.com 443 # thay bằng endpoint thật
echo "Tổng: $FAILS FAIL, $WARNS WARN"
[ "$FAILS" -eq 0 ]
Biến COMPONENTS giữ cho mỗi check ở đúng chỗ của nó: swap chỉ được chặn khi có kubelet, vm.max_map_count chỉ được chặn khi có Elasticsearch. Lệnh cuối biến kết quả thành exit code, để bộ cài, Ansible hay CI dừng lại mà không cần người ngồi đọc output.
Trên một VM thử đang bật swap, chạy bằng user thường, output sẽ có dạng như dưới đây. Đây là output minh họa: tên OS và các con số kB là giả định, máy của bạn sẽ báo số khác.
PASS os Red Hat Enterprise Linux 9
WARN root không phải root, các bước sửa sẽ cần sudo
WARN ram MemTotal=1863000kB, khuyến nghị từ 2097152kB
FAIL swap swap=2097148kB, kubelet mặc định sẽ không khởi động
PASS net_443 kết nối được registry.example.com:443
Tổng: 1 FAIL, 2 WARN
Kiểm tra: chạy bash preflight.sh; echo $?. Còn FAIL thì kết quả khác 0. Trên VM thử, chạy MODE=development bash preflight.sh; echo $? thì phải ra 0, kể cả khi máy lỗi.
Những lỗi khiến preflight mất uy tín
Lỗi nặng nhất là để script tự sửa máy, ví dụ tự gọi sysctl -w. kubeadm init mở đầu bằng loạt kiểm tra preflight để xác nhận trạng thái hệ thống trước khi thay đổi. Còn pha preflight của kubeadm upgrade apply có chế độ --dry-run, chỉ in ra những việc sẽ làm mà không đổi trạng thái.
Giá trị của preflight nằm ở chỗ chưa đụng vào gì; phần sửa là việc của đội IT bên khách, theo quy trình của họ.
Lỗi kế tiếp là thông báo mơ hồ. Luật cần nhớ: mỗi dòng FAIL phải có giá trị đo được, ngưỡng yêu cầu và hướng sửa, như dòng max_map_count ở Bước 2.
Leapp, công cụ nâng cấp RHEL, là mẫu đáng học. Nó gọi những phát hiện chặn là inhibitor, báo thẳng nâng cấp bị chặn vì những vấn đề nào, chẳng hạn một driver kernel đã bị gỡ, và ghi báo cáo thành file trong /var/log/leapp.
Theo Microsoft Learn, inhibitor phải được xử lý xong mới nâng cấp tiếp được, nên nó chính là FAIL của bạn; hãy lưu output ra file, chẳng hạn qua tee, để khách xem lại.
Còn hai lỗi nữa. Gắn FAIL cho mọi thứ, kể cả check của sản phẩm khách không cài, khiến khách quen tay bỏ qua và chẳng bao lâu ai cũng đòi miễn trừ tất cả. Exit code lúc nào cũng 0 thì pipeline chạy tiếp như không có chuyện gì.
Ở site khách, script này mở đầu cuộc trò chuyện
Cách dùng hiệu quả nhất là gửi script cho đội IT của khách vài ngày trước buổi cài, nhờ họ chạy ở chế độ production và dán lại output. Mỗi dòng FAIL thành một ticket có chủ. Mỗi lần miễn trừ qua WAIVE thì ghi tên người đồng ý vào biên bản. Đến hôm cài, bạn đã biết trước máy nào chưa sẵn sàng.
Với người đang muốn chuyển sang FDE, đây là một dự án portfolio gọn nhưng cho thấy đúng thứ nhà tuyển dụng cần. Đưa script lên repo, kèm một output mẫu và một bảng giải thích vì sao từng check là WARN hay FAIL.
Trong CV, hãy mô tả nó như một công cụ giảm rủi ro khi triển khai lên hạ tầng khách. Khi phỏng vấn, hãy sẵn sàng giải thích vì sao swap là FAIL còn RAM thấp chỉ là WARN: câu trả lời đó cho thấy bạn biết cân rủi ro chứ không chỉ biết viết lệnh.
Thứ khách nhớ thường không phải là bộ cài chạy nhanh đến đâu. Họ nhớ việc bạn đã chỉ ra máy nào chưa sẵn sàng, ngay từ trước khi có gì kịp hỏng.
Bài này có hữu ích không?
Cảm ơn bạn đã góp ý!
7 nguồn
- Bootstrap checks | Elastic Documentation
- Maximum map count check | Elasticsearch Guide 8.19
- kubeadm upgrade apply phase preflight | Kubernetes v1.34
- kubeadm init | Kubernetes
- Installing kubeadm | Kubernetes
- Running Leapp pre-upgrade analysis: take unknowns out of your next RHEL upgrade · 2024-04-24
- Upgrade RHEL pay-as-you-go virtual machines using Leapp - Virtual Machines | Microsoft Learn · 2026-08-21