Series Phỏng vấn Jenkins Thực chiến #3: Cơn ác mộng "Copy-Paste" và Nghệ thuật tối ưu bằng Shared Libraries
1. Câu hỏi từ Nhà tuyển dụng
"Giả sử hệ thống xử lý giao dịch AFC của chúng ta hiện đang có 50 Microservices, tất cả đều viết bằng Golang và đóng gói bằng Docker. Một ngày, Trưởng phòng bảo mật yêu cầu phải cập nhật image nền từ golang:1.20-alpine lên golang:1.21-alpine cho toàn bộ hệ thống để vá lỗ hổng. Nếu thiết kế CI/CD theo cách thông thường, bạn sẽ phải mở 50 file Jenkinsfile ở 50 repo khác nhau để sửa. Bạn có giải pháp nào để tôi chỉ cần sửa đúng 1 dòng code mà cập nhật được cho cả 50 repo không?"
2. Phân tích tâm lý Nhà tuyển dụng
Đây là câu hỏi phân loại ứng viên Senior. Nhà tuyển dụng đang soi xét các yếu tố:
- Nguyên tắc DRY (Don't Repeat Yourself) trong tư duy viết code CI/CD.
- Khả năng thiết kế hệ thống ở quy mô lớn (Scalability).
- Kiến thức về tính năng mở rộng của Jenkins: Shared Libraries.
3. Cách trả lời ghi điểm tuyệt đối (Mẫu tham khảo)
Hãy trả lời theo cấu trúc 3 lớp để thể hiện tư duy của một Software Architect:
Cấp độ 1: Bắt trúng "bệnh" và kê đúng "thuốc"
"Nếu phải mở 50 repo ra để sửa tay từng file Jenkinsfile, đó là một thiết kế CI/CD thất bại vì nó vi phạm nghiêm trọng nguyên tắc DRY. Để giải quyết bài toán quy mô lớn này, em sẽ sử dụng Jenkins Shared Libraries. Thay vì viết logic (kéo code, test, build Docker) lặp đi lặp lại ở 50 file, em sẽ rút trích toàn bộ luồng đó thành một hàm dùng chung và lưu ở một kho Git trung tâm."
Cấp độ 2: Mô tả cách triển khai (Thực chiến)
"Cách em thiết kế sẽ như sau:
- Kho trung tâm: Em tạo một kho Git riêng (ví dụ: jenkins-shared-libs). Trong đó, em định nghĩa một hàm Groovy tên là buildGolangMicroservice(), bên trong chứa tham số phiên bản base image mặc định là 1.20.
- Ở 50 repo Microservices: File Jenkinsfile lúc này chỉ còn đúng 2 dòng code. Dòng 1 là lệnh import thư viện @Library('my-shared-library') _, và dòng 2 là gọi hàm buildGolangMicroservice().
- Giải quyết bài toán: Khi có yêu cầu nâng cấp lên bản 1.21, em chỉ cần vào repo jenkins-shared-libs sửa đúng một biến. Lần build tiếp theo, cả 50 Microservices sẽ tự động tải thư viện mới nhất về và áp dụng bản Golang 1.21 ngay lập tức."
Cấp độ 3: Khóa chốt bằng "Tư duy quản trị rủi ro" (Điểm 10/10)
"Tuy nhiên, nếu sửa 1 dòng mà áp dụng ngay cho 50 services thì cực kỳ nguy hiểm (Blast Radius quá lớn). Lỡ đoạn code thư viện dùng chung bị lỗi, toàn bộ luồng deploy của công ty sẽ tê liệt. Vì vậy, em bắt buộc phải áp dụng cơ chế Versioning (Đánh phiên bản) cho Shared Libraries. Các repo ổn định sẽ gọi thư viện thông qua tag @Library('my-shared-library@v1.0'). Khi nâng cấp lên 1.21, em sẽ phát hành thư viện v2.0 và test thử trên một vài service không quan trọng trước, khi nào chắc chắn ổn định mới tiến hành cập nhật tag v2.0 cho các service lõi."
4. Góc nhỏ "Bắt bẻ" (Nhà tuyển dụng phản đòn)
Nếu bạn đưa ra giải pháp Versioning quá tốt, người phỏng vấn sẽ lật lại vấn đề từ góc độ của Developer.
- Interviewer vặn lại: "Nếu dùng Shared Libraries, file Jenkinsfile ở các repo trở nên quá ngắn và bị che giấu toàn bộ logic. Các bạn Dev nhìn vào sẽ giống như một 'chiếc hộp đen' (Blackbox) và không biết code của mình đang được build qua các bước nào. Bạn cân bằng giữa sự tiện lợi và tính minh bạch này ra sao?"
- Cách bạn "đỡ đòn": "Đó là một điểm yếu của Shared Libraries. Để khắc phục, em không đóng gói cứng (hardcode) toàn bộ Pipeline thành một hàm duy nhất. Thay vào đó, em thiết kế thư viện theo dạng Modular (Các block chức năng nhỏ). Ví dụ: customCheckout(), buildGoDocker(), deployToK8s(). File Jenkinsfile của Dev vẫn giữ nguyên cấu trúc Declarative (vẫn có stages, steps rõ ràng), nhưng bên trong phần steps họ sẽ gọi các hàm module này. Cách này vừa tuân thủ DRY, vừa giúp Dev đọc file là hiểu ngay tiến trình đang diễn ra như thế nào."
All rights reserved