Bài áp chót. Đây 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.
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.
Thử ba mươi giây
Chọn ba dòng bạn chưa chắc trong danh sách trên và kiểm ngay hôm nay.
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.