Phi công không đọc bảng kiểm trước khi cất cánh vì họ hay quên. Họ đọc vì cái giá của một mục bị bỏ sót thì thảm khốc mà lại hiếm — hiếm tới mức trí nhớ không bao giờ đủ tin. Triển khai lên sản xuất cũng vậy. Đây là bài áp chót, là danh sách tôi thật sự chạy qua, và mỗi mục đều dẫn về một bài đã đo — hay nói thẳng ra, một lần đã ngã.

Cấu hình

Không bí mật nào trong Git. Kể cả repo riêng tư — lịch sử Git giữ mãi.

Tên biến khớp nhau ở ba tầng — application.yml, compose, .env.example. Bài 50: đổi tên giữa các tầng là im lặng không có tác dụng. Một test đối chiếu ba nơi rẻ hơn nhiều so với lần gỡ lỗi đầu tiên.

Chuỗi rỗng có được xử lý đúng không. Bài 50 đo: SMTP_HOST= cho ra '', không dùng giá trị mặc định.

TZ đặt đúng. Mặc định container là UTC, và mọi lịch chạy theo giờ địa phương sẽ lệch.

Cấu hình dùng @ConfigurationProperties có @Validated để sai là không khởi động được (bài 50).

docker compose config | grep -iE "password|secret|key" | head

Dữ liệu

Sao lưu CSDL, và đã thử khôi phục. Bản sao lưu chưa từng khôi phục thử không phải bản sao lưu.

Migration bằng Flyway, chạy ở bước riêng trước khi triển khai (bài 33, 57). Không phải ddl-auto: update.

Ba dòng cấu hình JPA (bài 38):

spring.jpa.open-in-view: false
spring.jpa.properties.hibernate.jdbc.batch_size: 50
spring.jpa.properties.hibernate.default_batch_fetch_size: 20

Pool kết nối đặt theo số nhân CSDL, không đặt to cho chắc (bài 34). Và tính tổng qua mọi bản sao.

Đã đếm số truy vấn của ba endpoint bận nhất (bài 24).

docker compose logs blog | grep -ci "select"

Bảo mật

HTTPS bắt buộc, và HSTS thật sự được gửi — kiểm trên URL https, không phải http (bài 45).

X-Forwarded-Proto, X-Forwarded-Host, X-Real-IP được nginx chuyển tiếp. Thiếu chúng: bộ lọc IP mất tác dụng, giới hạn tần suất mất tác dụng, link trong email sai giao thức, HSTS biến mất.

Actuator không ra Internet, include liệt kê tường minh, tốt nhất là cổng riêng (bài 46, 51).

Dấu vết ngăn xếp tắt trong phản hồi.

Mật khẩu băm bằng BCrypt cost ≥ 10 hoặc Argon2 (bài 40).

Mọi @RequestBody có @Valid (bài 13).

Quyền kiểm trên dữ liệu, không chỉ trên endpoint (bài 41). Thử đổi id bằng tài khoản khác.

Không @PreAuthorize nào bị vô hiệu vì tự gọi (bài 41).

Đã quét phụ thuộc (bài 46).

curl -sI https://ten-mien/ | grep -icE "content-security|strict-transport|x-frame|x-content-type|referrer"
curl -s https://ten-mien/actuator/env | head -c 100

Đầu vào

Mọi thứ sinh nhiều bản ghi đều có trần, kiểm trước khi ghi (bài 46). Một request 44 byte từng làm blog này hết bộ nhớ.

?size= có trần (bài 22).

Tệp tải lên: giới hạn kích thước, không tin tên tệp, không tin Content-Type, làm sạch SVG (bài 19).

Chuyển hướng chỉ lấy phần đường dẫn, bắt đầu bằng đúng một dấu gạch chéo.

Gọi ra ngoài

Mọi client HTTP có phép chờ. Bài 20 đo: RestClient mặc định không có. Một dịch vụ treo giữ hết pool luồng của bạn.

Không gọi HTTP bên trong giao dịch (bài 26). Dùng @TransactionalEventListener(AFTER_COMMIT) (bài 54).

Có thử lại với lùi theo cấp số nhân, và chỉ cho thao tác lặp lại được (bài 20).

grep -rn "new RestTemplate()\|RestClient.create()" --include='*.java' src/main

Quan sát

Log ra stdout, có cấu trúc, có traceId (bài 52).

Log khởi động bình thường không có dòng WARN nào. Bài 52: log ồn che mất cảnh báo thật.

Không bí mật nào trong log (bài 46).

Bốn chỉ số tối thiểu: tỷ lệ lỗi theo endpoint, độ trễ p95/p99, thông lượng, tài nguyên bão hoà (bài 51).

Liveness và readiness tách bạch. Liveness chỉ kiểm thứ mà khởi động lại sửa được (bài 51).

Cảnh báo nối với người thật, không chỉ với Kubernetes.

/actuator/info cho biết commit nào đang chạy (bài 51).

Vận hành

Tắt cho đàng hoàng — server.shutdown: graceful (bài 57).

Kiểm tra sức khoẻ hỏi một endpoint thật, không chỉ kiểm tiến trình còn sống.

Đã thử triển khai lại, và thử lùi phiên bản.

Nhiều bản sao thì bốn thứ này đã xử lý (bài 57): session, cache, @Scheduled, tệp trên đĩa.

Container không giữ dữ liệu gì — xoá và dựng lại phải an toàn.

Ảnh được dựng lại thật. docker compose up -d không tự dựng lại image (bài 57).

Bộ test

Chạy xanh trên CSDL giống sản xuất, không phải H2 (bài 36).

Có vài test đi qua Tomcat thật. Bài 47 đo: MockMvc nói 403, máy chủ thật trả 405.

Có test đếm số truy vấn (bài 36).

Có test cho vai sai, không chỉ cho đường đi đúng (bài 47).

Sau khi triển khai

curl -sI https://ten-mien/ | head -1
docker compose ps
docker compose logs blog | grep -iE "ERROR|Caused by|CommandAcceptanceException"
curl -s localhost:8080/actuator/health | jq '.status'
curl -s localhost:8080/actuator/info | jq '.git.commit.id'

Năm lệnh, chưa tới một phút, và chúng bắt được phần lớn thứ hỏng ngay.

Điều tôi học được về danh sách kiểm

Danh sách này dài, và bạn sẽ không chạy hết nó mỗi lần triển khai.

Cách tôi dùng: tự động hoá phần nào tự động được (quét phụ thuộc, test, kiểm header trong CI), và giữ phần còn lại cho lần triển khai đầu tiên cùng những lần đổi hạ tầng.

Và điều quan trọng hơn danh sách: mỗi lần có sự cố, thêm một dòng. Danh sách này chính là kết quả của việc đó — mỗi mục đều đến từ một lần đã hỏng, của tôi hoặc của người khác.

Mẫu số chung

Một danh sách kiểm không phải là tài liệu hướng dẫn — nó là ký ức về những lần đã ngã, được kết tinh lại thành câu chữ. Mỗi dòng là một sự cố mà ai đó đã trả giá để học, đóng gói sao cho người sau không phải trả lại. Đó là lý do nó tồn tại ở mọi ngành có cái giá cao và trí nhớ không đủ tin: buồng lái máy bay, phòng mổ, và cả phòng máy chủ.

Trong nghề của chúng ta, nó đội nhiều lốt: một cái linter hay bộ quy tắc tĩnh là danh sách kiểm tự động hoá — mỗi luật thường sinh ra sau một lỗi thật; một cuốn runbook vận hành là danh sách kiểm cho lúc nửa đêm hệ thống cháy; một mẫu pull request với các ô tick là danh sách kiểm cho người review. Cả ba đều theo đúng nguyên tắc ở trên: tự động hoá thứ máy làm được, để mắt người cho thứ còn lại, và thêm một dòng sau mỗi lần đau.

Nên thước đo một danh sách kiểm tốt không phải nó dài bao nhiêu, mà là mỗi dòng có truy được về một lần hỏng cụ thể không — dòng nào không, dòng đó là nghi lễ chứ không phải bài học. Bắt đầu ngay hôm nay: chọn ba dòng bạn chưa chắc trong danh sách trên và kiểm liền. Không phải ba dòng bạn biết là mình làm rồi — ba dòng bạn không chắc. Đó là chỗ có vấn đề.

Ngày mai là bài cuối: nhìn lại sáu mươi ngày.