Ba thứ hỏng mà không kêu, cả ba đều do chính công cụ vận hành gây ra
Buổi sáng hôm đó apt list --upgradable trên máy production trả về 0 gói chờ nâng. Buổi chiều, sau khi gắn Ubuntu Pro vào đúng cái máy đó, không cài thêm gì, không đổi cấu hình gì, lệnh y hệt trả về 25 bản vá bảo mật đang chờ.
Hai phép đo cùng một hệ, cách nhau vài tiếng, cho hai kết quả ngược nhau. Không cái nào sai. Bài này ghi lại buổi rà soát định kỳ đội ba máy chủ nhỏ của chúng tôi, và ba chỗ hỏng tìm thấy trong đó. Điểm chung của cả ba là thứ làm nó đáng viết ra: không cái nào do phần mềm bên ngoài hỏng, cả ba đều do chính công cụ vận hành gây ra.
Định thử gì
Đội máy gồm ba cái: một máy production chạy n8n với một dịch vụ scraping tự dựng, một máy R&D để thử luồng tích hợp trước khi đưa vào dự án, và một máy lab ARM làm đích diễn tập khôi phục. Cả ba đều Ubuntu, đều Docker Compose, đều đã bật unattended-upgrades.
Câu hỏi ban đầu rất hẹp: có bản vá nào đang chờ không. Bộ rà soát viết ra để trả lời câu đó, chạy chung một script trên cả ba máy, chỉ đọc chứ không sửa gì ngoài apt-get update.
Nó trả lời được câu hỏi ban đầu trong ba phút. Phần còn lại của buổi chiều là những thứ nó lộ ra mà tôi không đi tìm.
Cron dọn dẹp ăn mất ảnh mà service cần để sống
Lệnh docker compose up -d trên máy production dừng lại với một dòng:
Error response from daemon: No such image: <ten-anh>:latestContainer đó vẫn đang chạy bình thường. Nó chạy được vì container giữ tham chiếu tới image ID, còn cái mất là tag. Chừng nào không ai đụng vào thì không có gì kêu, và không có gì trong log nói rằng chỉ cần container này dừng một lần là hết đường dựng lại.
Tag mất vì một dòng cron chúng tôi tự đặt:
8 0 * * * root docker image prune -af --filter "until=24h"Tài liệu Docker viết rõ về cờ -a: nó "remove all unused images, not just dangling ones", và "also remove all images not referenced by any container". Phần "not referenced by any container" là chỗ tôi đọc lướt. Ảnh có tag vẫn bị xoá, miễn là không container nào tham chiếu tới nó.
Chi tiết thứ hai đắt hơn: --filter until= lọc theo thời điểm tạo ảnh, không phải thời điểm gắn tag. Nên khi tôi giữ một ảnh cũ làm điểm lùi bằng docker tag, ảnh đó vẫn mang ngày tạo gốc từ hai tuần trước, vẫn khớp until=24h, và biến mất trong lần cron chạy kế tiếp. Điểm lùi tôi vừa tạo sống được đúng một đêm.
Cách sửa là bỏ -a, để bản daily chỉ dọn ảnh mồ côi:
8 0 * * * root docker image prune -f --filter "until=24h"Việc thu hồi dung lượng vẫn còn một job hàng tuần với cửa sổ until=168h lo. Và quy ước mới, ghi thẳng vào runbook: giữ ảnh cũ bằng docker tag không phải là điểm lùi bền. Muốn lùi được sau một đêm thì docker save ra tarball, vì tarball nằm ngoài tầm với của mọi lệnh prune.
Đổi host và bốn hành vi nền tảng đang làm hộ mà bạn không sở hữu
Chuyển hecigo.com sang một host khác. Adapter mất mười lăm dòng. Bốn hành vi nền tảng vẫn làm hộ thì mỗi cái hỏng một kiểu, và không cái nào làm...
Ảnh không dựng lại được vì compose không nói nó dựng từ đâu
Vì sao mất tag lại thành chặn được cả compose up? Vì khối service đó trông như thế này:
services:
scraper-browser:
image: scraper-browser-custom:latest
pull_policy: neverpull_policy: never nghĩa là "Compose doesn't pull the image from a registry and relies on the platform cached image. If there is no cached image, a failure is reported." Không có khối build:, cũng không có registry để kéo về. Ảnh nằm sẵn trên máy thì chạy, không nằm sẵn thì hết cách.
Điều đáng nói không phải là pull_policy: never sai. Nó đúng cho ảnh dựng tại chỗ. Điều sai là compose không nói ảnh đó dựng từ đâu, nên người đọc file không có đường nào lần ra mã nguồn.
Mã nguồn hoá ra vẫn còn, nằm trong thư mục của một service khác đã ngừng dùng từ lâu, cùng chỗ chỉ vì lịch sử chứ không vì liên quan. Không ai đọc compose mà đoán được điều đó.
Cách sửa là để mã nguồn cạnh chính file dùng nó, và để file tự khai:
services:
scraper-browser:
build:
context: ./scraper-browser
image: scraper-browser-custom:latestDựng lại thật từ đầu để kiểm chứ không chỉ sửa file: container healthy sau 15 giây, và một lượt scrape thật qua trình duyệt trả 200 với 9.276 ký tự.
Câu tự kiểm cho hệ của bạn: mở file compose ra, có service nào mà bạn không lần được từ đó tới mã nguồn hoặc tới một registry không? Nếu có, nó đang ở cùng trạng thái với cái này trước hôm nay.
0 gói chờ nâng, khi không có nguồn nào để so
Cam kết bảo trì mặc định của Ubuntu LTS phủ repository Main, khoảng 2.300 gói, trong năm năm. universe không nằm trong đó. Tài liệu Ubuntu tách hai luồng ESM rõ ràng: "The Infra pockets cover packages in the Main component, while the Apps pockets cover packages in the Universe component."
Máy production của chúng tôi chạy 24.04, còn trong hạn hỗ trợ tiêu chuẩn tới 2029. Nên apt báo 0 gói chờ nâng là đúng với những nguồn nó đang có. Nó không sai, nó chỉ không có gì để so ở phần universe.
Gắn Ubuntu Pro xong, noble-apps-security xuất hiện trong danh sách nguồn, và cùng lệnh đó trả về 25 gói. Đây là những gì có trong đó:
ffmpeg + toàn bộ họ libav* imagemagick + libmagick*
python3-pip node-lodash
libmbedcrypto7t64 libopenexr, libcjson1, libzvbiCài hết trong một lượt: không cần reboot, không dịch vụ nào phải restart, không container nào bị đụng. Đây là gói của host, còn các container mang bản riêng trong ảnh của chúng, nên đợt vá này không chạm tới chúng và cũng không làm gián đoạn chúng. Đó vừa là tin tốt vừa là một nhắc nhở: vá host xong không có nghĩa là đã vá xong những gì chạy trong container.
Tôi cần tách hai mức phát biểu ở đây, vì chúng khác nhau về thẩm quyền. Tài liệu Ubuntu nói cam kết tiêu chuẩn phủ Main năm năm, và Pro mở rộng ra toàn bộ archive trong mười năm. Chúng tôi đo được là trên một máy 24.04 còn trong hạn tiêu chuẩn, việc gắn Pro làm lộ ra ngay 25 bản vá universe đang chờ. Phép đo đó đúng cho ba máy này; tôi không có dữ liệu để nói nó đúng cho mọi cấu hình.
Bản Ubuntu Pro cá nhân "is and always will be free for personal use on up to 5 physical machines", đủ chỗ cho một đội ba máy.
Bài học đưa thẳng vào bộ rà soát, và nó là câu đáng nhớ nhất trong cả buổi: "0 gói chờ nâng" chỉ có nghĩa khi biết mình đang so với những nguồn nào. Script rà soát nay in trạng thái Pro chứ không chỉ đếm gói.
Chỗ tôi làm sập production
Phần này không nằm trong kế hoạch.
Cùng buổi đó tôi phát hiện n8n tải khoảng 1,2 GB trình duyệt Playwright mỗi lần khởi động, vào lớp ghi của container chứ không phải volume. Nghĩa là mỗi lần compose up tạo lại container là tải lại từ đầu, và vì có hai container nên 2,8 GB cho cùng một bộ trình duyệt.
Cách sửa hiển nhiên là mount cache ra volume dùng chung:
volumes:
- n8n_cache:/home/node/.cache/ms-playwright # dòng này làm sập productionThư mục /home/node/.cache không tồn tại sẵn trong ảnh. Mount vào thư mục con của nó khiến Docker tự tạo thư mục cha, và tạo bằng root. n8n chạy bằng user node, mất quyền ghi /home/node/.cache/n8n, và crash-loop:
EACCES: permission denied, mkdir '/home/node/.cache/n8n'Production n8n chết vài phút trước khi tôi nhìn ra. Cách đúng là mount cả thư mục cha, sau khi đã đặt sở hữu cho volume:
docker volume create n8n_cache
docker run --rm -v n8n_cache:/c alpine chown 1000:1000 /cvolumes:
- n8n_cache:/home/node/.cacheSố đo sau khi sửa: worker sẵn sàng trong 18 giây thay vì hơn hai phút, 0 dòng "Downloading Chrome", lớp ghi của worker từ 1,37 GB xuống 0 B và của n8n từ 1,45 GB xuống 67 MB.
Lý do viết ra: nếu bài này chỉ có ba chỗ hỏng do người khác gây ra và không có chỗ nào do tôi, nó là quảng cáo chứ không phải lab note.
Ý phản đối mạnh nhất với chính bài này
Ý phản đối tôi thấy khó cãi nhất là: cả ba thứ này đều do chính chúng tôi tự đặt ra. Cron prune -af là chúng tôi viết. pull_policy: never không kèm build: là chúng tôi để lại. Không gắn Pro là lựa chọn của chúng tôi. Vậy bài học có thật sự tổng quát, hay chỉ là ghi lại việc dọn nợ của chính mình?
Tôi nghiêng về vế thứ hai nhiều hơn tôi muốn thừa nhận. Ba cái này không phải quy luật của Docker hay của Ubuntu, chúng là hệ quả của những lựa chọn cụ thể trên ba máy cụ thể.
Nhưng có một điểm chung tôi nghĩ đi xa hơn ba máy này: cả ba đều là công cụ vận hành làm đúng thứ nó được bảo làm. Cron dọn ảnh đã dọn ảnh. pull_policy: never đã không kéo ảnh về. apt đã báo cáo đúng những nguồn nó có. Không cái nào là lỗi, nên không cái nào phát ra tín hiệu lỗi, và đó là lý do cả ba sống sót qua nhiều tháng giám sát.
Còn một ý phản đối nữa tôi không có số liệu để cãi: có thể bốn tiếng bỏ ra hôm đó lẽ ra nên dùng để dựng thêm giám sát thay vì rà tay. Tôi không đo được cái nào rẻ hơn, nên không nói được.
Thấy bài này dùng được? Theo dõi hecigo trên Zalo OA để nhận bài kỹ thuật mới, hoặc nhắn cho chúng tôi nếu hai hệ thống của bạn đang phải nói chuyện với nhau và có gì đó hỏng ở khoảng giữa.
Đọc tiếp: Middleware: phần việc n8n, OpenClaw và mọi nền tảng tự động hóa không làm hộ bạn
Nối được API là phần dễ. Phần khó lộ ra sau vài tuần chạy thật: sự kiện gửi lại hai lần, webhook rơi mất một giao dịch, hóa đơn bị hủy nhưng hệ...
Sau buổi đó
Bộ rà soát nay có thêm hai mục: một mục liệt kê service nào khai pull_policy: never mà không có build:, và một mục in trạng thái Ubuntu Pro trước khi đếm gói. Cả hai đều là câu hỏi mà buổi chiều đó dạy tôi phải hỏi.
Một chỗ vẫn chưa xong, và nó đáng ghi ra vì nó không sửa được bằng cấu hình. Máy lab của chúng tôi chạy Ubuntu 24.04 trên ARM, và livepatch báo n/a ở đó. Tôi tưởng lý do là kernel flavour, nhưng bảng kernel được phủ nói khác: flavour oracle có trong danh sách, chỉ là cho x86 64-bit. Với 24.04 thì arm64 chưa được phủ, và tài liệu ghi coverage cho arm64 bắt đầu từ bản 26.04. Nghĩa là hai máy x86 vá kernel không cần reboot, còn máy ARM thì vẫn phải, cho tới khi nâng bản.
Câu tôi chưa trả lời được, và cũng đang muốn biết: cả ba chỗ này chỉ lộ ra vì có người ngồi rà tay. Một cái cron ăn mất tag, một ảnh không dựng lại được, một danh sách bản vá rỗng vì thiếu nguồn - chúng đều không sinh ra tín hiệu nào để bắt. Vậy loại hỏng "công cụ vận hành làm đúng thứ nó được bảo làm" thì giám sát được bằng cách nào, hay nó vốn chỉ bắt được bằng mắt người theo chu kỳ?
Nguồn tham khảo
- docker image prune - Docker Docs
- Services top-level element: pull_policy - Docker Docs
- Expanded Security Maintenance (ESM) - Ubuntu security documentation
- Ubuntu Pro - Ubuntu
- Kernels covered by Livepatch - Ubuntu