Chuyển tới nội dung chính

3 bài viết được gắn thẻ "Docker"

Container hoá ứng dụng .NET, giới hạn tài nguyên và các khác biệt giữa image với máy thật.

Xem tất cả thẻ

ONNX Runtime chết trên Alpine: lỗi ld-linux-x86-64.so.2 và vì sao libc6-compat không cứu được

· 16 phút để đọc
Nguyễn Huỳnh Minh Tiến
Fullstack Developer @ Utop.io
Tóm tắt

onnxruntime-node phát hành binary dựng sẵn cho glibc, và libonnxruntime.so.1 khai báo DT_NEEDED trỏ thẳng vào ld-linux-x86-64.so.2 — chính là bộ nạp động của glibc. Alpine dùng musl, nơi file đó không tồn tại, nên dlopen hỏng ngay ở bước nạp thư viện. Cài libc6-compat hay gcompat không sửa được: chúng cấp file loader nên qua được lỗi thứ nhất, rồi chết ở lỗi thứ hai là __vsnprintf_chk: symbol not found. Cách xử lý thực tế là đổi base image sang node:22-bookworm-slim, trả thêm khoảng 89 MB image để đổi lấy một thứ chạy được.

21 giờ 30, tôi thêm Dockerfile cho ClubDay — một web app sự kiện có trò vẽ hình được chấm điểm bằng model ONNX chạy ngay trên máy chủ. Base image chọn theo phản xạ: node:22-alpine, vì nhẹ.

Chọn base image là bước đầu tiên của Docker và deployment, và cũng là bước dễ chọn sai nhất — như bên dưới.

Image build sạch. Container lên. docker ps xanh. Rồi đến lúc server nạp model thì mọi thứ đổ, với một dòng lỗi không nhắc gì tới ONNX:

Error loading shared library ld-linux-x86-64.so.2: No such file or directory
(needed by /app/node_modules/onnxruntime-node/bin/napi-v6/linux/x64/libonnxruntime.so.1)

21 giờ 44 tôi đổi một dòng trong Dockerfile và mọi thứ chạy. Nhưng mười bốn phút ở giữa đáng được viết ra, vì dòng lỗi đó nói về một file mà bạn chưa bao giờ cài, chưa bao giờ khai trong package.json, và trên máy dev thì nó vẫn luôn ở đó.

Traefik + Cloudflare: vì sao cert hết hạn đồng loạt sau 60 ngày?

· 21 phút để đọc
Nguyễn Huỳnh Minh Tiến
Fullstack Developer @ Utop.io
Tóm tắt

Khi domain bật proxy Cloudflare (mây cam) với SSL mode Full (strict), ACME HTTP-01 không dùng được: Let's Encrypt gọi http://domain/.well-known/acme-challenge/... ở cổng 80, Cloudflare nhận rồi gọi ngược về origin bằng HTTPS:443 — nơi bộ xử lý challenge của Traefik không có mặt. Bạn cần cert để qua được Cloudflare, mà cần qua Cloudflare mới lấy được cert. Lách lần đầu bằng cách tạm tắt mây cam thì được, nhưng lần gia hạn tự động sau ~60 ngày sẽ trượt âm thầm và mọi site hết hạn cùng lúc. Lối ra là DNS-01: xác thực bằng bản ghi TXT qua API Cloudflare, không request nào chạm origin. Đổi lại, cert chỉ cấp được cho những zone mà API token nhìn thấy.

Trên VPS này đang có 14 container chung một reverse proxy: 10 site WordPress, một API .NET, một app Angular, một web app sự kiện, và Traefik. Mười ba hostname, tất cả nằm sau Cloudflare, tất cả cần HTTPS và không ai muốn gia hạn cert bằng tay.

Phần kiến trúc thì đơn giản đến mức nhàm. Phần thú vị nằm ở chỗ Cloudflare và Let's Encrypt, mỗi bên làm đúng việc của mình, nhưng ghép lại thì tạo ra một vòng luẩn quẩn mà bạn chỉ phát hiện vào ngày cert hết hạn — tức là hai tháng sau khi mọi thứ trông có vẻ đã xong.

Vì sao .NET ngốn 96% RAM container dù app đang rảnh? Server GC và cách giảm 65% bộ nhớ

· 13 phút để đọc
Nguyễn Huỳnh Minh Tiến
Fullstack Developer @ Utop.io
Tóm tắt

Một container ASP.NET Core 9 chiếm 987 MiB trên trần 1 GiB (96%) dù suốt 2 giờ không nhận request nào. Đây không phải rò rỉ bộ nhớ: mức RAM đứng yên tuyệt đối qua nhiều lần đo, dấu hiệu của heap bị giữ lại chứ không phải rò rỉ. Nguyên nhân là Server GC — mặc định của ASP.NET Core, tạo một heap cho mỗi CPU core và rất ít trả bộ nhớ về hệ điều hành. Đặt DOTNET_gcServer=0 và DOTNET_GCConserveMemory=5 giảm bộ nhớ anon từ 822 MB xuống 280 MB (−65%), đo ổn định sau 30 phút. Đánh đổi: Workstation GC cho throughput thấp hơn khi tải cao và nhiều core.

Bài này ghi lại một buổi chẩn đoán thật trên VPS 4 core / 8 GB RAM đang chạy song song 9 site WordPress và một API .NET. Mọi con số bên dưới là số đo thật, không phải ví dụ minh hoạ.