Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 9 — Jenkins — Classic CI Server

Đã đọc 0/15 mục · còn khoảng 12 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Mở đầu — vì sao bài này quan trọng

Nếu bạn theo dõi các bài trước, bạn đã làm quen với GitHub Actions và GitLab CI/CD — những công cụ CI/CD hiện đại chạy trên nền tảng đám mây, cấu hình bằng file YAML gọn gàng nằm ngay trong repo. Vậy tại sao chúng ta còn phải học Jenkins — một công cụ ra đời từ năm 2011 (tách ra từ Hudson, dự án còn cũ hơn nữa)?

Câu trả lời rất thực tế: Jenkins vẫn là "con ngựa thồ" (workhorse) của hàng trăm nghìn doanh nghiệp trên thế giới, và đặc biệt phổ biến tại Việt Nam trong các ngân hàng, công ty tài chính, viễn thông, cơ quan nhà nước và các doanh nghiệp lớn có hệ thống on-premise (tự vận hành máy chủ, không dùng cloud công cộng). Khi bạn đi xin việc DevOps tại Vietcombank, VNPT, FPT, Viettel hay một công ty gia công phần mềm (outsourcing) phục vụ khách hàng Nhật/Mỹ, khả năng rất cao là bạn sẽ gặp Jenkins trong ngày làm việc đầu tiên.

Hiểu Jenkins không chỉ là biết bấm nút "Build Now". Nó là hiểu triết lý của một CI server tự host: bạn kiểm soát toàn bộ, đổi lại bạn phải tự chịu trách nhiệm vận hành, bảo trì, vá lỗi bảo mật. Đây là bài học cho bạn thấy mặt còn lại của thế giới CI/CD — nơi không có "magic cloud" lo hết mọi thứ cho bạn, mà bạn chính là người dựng và giữ cho cỗ máy chạy.

Khái niệm cốt lõi

Jenkins là gì?

Jenkins là một automation server mã nguồn mở, viết bằng Java, dùng để tự động hóa các phần việc trong vòng đời phát triển phần mềm — build, test, deploy. Điểm mấu chốt phân biệt Jenkins với GitHub Actions hay GitLab CI: Jenkins là self-hosted, nghĩa là bạn tự cài nó lên máy chủ của mình (một server vật lý trong data center của công ty, một VM trên cloud riêng, hoặc thậm chí máy tính cá nhân). Không có bên thứ ba nào chạy hộ bạn, không có hóa đơn tính theo phút build.

Kiến trúc Controller — Agent

Jenkins hoạt động theo mô hình Controller (trước đây gọi là Master) — Agent (trước đây gọi là Slave):

  • Controller: bộ não trung tâm. Nó lưu cấu hình, lập lịch job, quản lý giao diện web, điều phối công việc. Controller không nên tự chạy build nặng — vai trò của nó là điều phối.
  • Agent: các máy "thợ" nhận lệnh từ Controller để thực thi build thực tế. Bạn có thể có agent chạy Linux để build backend Java, agent chạy macOS để build ứng dụng iOS, agent chạy Windows để build .NET — tất cả cùng phục vụ một Controller.
Mô hình này cho phép mở rộng theo chiều ngang (horizontal scaling): cần build nhiều hơn thì thêm agent. Đây là lý do Jenkins gánh được khối lượng build khổng lồ trong các tập đoàn.

Freestyle Job vs Pipeline

Có hai cách chính để định nghĩa công việc trong Jenkins:

  • Freestyle Job: cấu hình qua giao diện web bằng cách bấm chọn từng bước. Dễ cho người mới nhưng khó version control, khó tái sử dụng, dễ trở thành "cấu hình ma" mà không ai biết ai đã sửa gì.
  • Pipeline: định nghĩa toàn bộ quy trình bằng code trong một file tên Jenkinsfile, viết bằng ngôn ngữ Groovy. Đây là cách hiện đại, đúng tinh thần Pipeline as Code (khái niệm bạn đã học ở Bài 5). File Jenkinsfile nằm trong repo, đi cùng source code, được review và version như mọi đoạn code khác.
Một Jenkinsfile cơ bản trông như sau:

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package'
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        stage('Deploy') {
            steps {
                sh './deploy.sh staging'
            }
        }
    }
}

Có hai cú pháp Pipeline: Declarative (như ví dụ trên — có cấu trúc rõ ràng, dễ đọc, được khuyến nghị cho đa số trường hợp) và Scripted (linh hoạt hơn, dùng Groovy thuần, dành cho tình huống phức tạp).

Hệ sinh thái plugin — sức mạnh và gót chân Achilles

Điểm mạnh nổi tiếng nhất của Jenkins là kho plugin hơn 1.800 cái. Cần tích hợp Slack? Có plugin. Cần đọc kết quả test JUnit? Có plugin. Cần deploy lên Kubernetes, gửi thông báo Telegram, ký artifact, quét bảo mật? Đều có plugin. Gần như mọi công cụ trong thế giới DevOps đều có ai đó viết plugin kết nối với Jenkins.

Nhưng chính điều này cũng là gót chân Achilles: plugin do cộng đồng viết nên chất lượng không đồng đều, đôi khi bỏ hoang không cập nhật, xung đột phiên bản với nhau, và là nguồn lỗ hổng bảo mật phổ biến nhất của Jenkins. Quản lý plugin là một kỹ năng vận hành quan trọng, không phải chuyện "cài cho có".

Vì sao Jenkins vẫn phổ biến dù đã "già"?

  • Tự host = kiểm soát hoàn toàn: dữ liệu, source code, secret không rời khỏi hạ tầng công ty. Cực kỳ quan trọng với ngân hàng và cơ quan nhà nước — nơi quy định pháp lý cấm đưa dữ liệu ra cloud công cộng.
  • Phù hợp môi trường on-premise, air-gapped (mạng cô lập, không nối Internet): nhiều hệ thống ngân hàng lõi (core banking) chạy trong mạng nội bộ hoàn toàn, nơi GitHub Actions cloud không thể với tới.
  • Miễn phí bản quyền: không tốn phí license, chỉ tốn chi phí hạ tầng và người vận hành.
  • Cực kỳ linh hoạt: gần như không có giới hạn về những gì bạn có thể tự động hóa.
  • Cộng đồng và tài liệu khổng lồ: gặp lỗi gì cũng thường tìm được người đã gặp trước.

Tình huống thực tế

Tình huống 1 — Ngân hàng thương mại tại TP.HCM với hệ thống air-gapped

Một ngân hàng thương mại cỡ trung ở TP.HCM có đội core banking gồm khoảng 40 lập trình viên. Vì quy định của Ngân hàng Nhà nước và chính sách bảo mật nội bộ, toàn bộ source code hệ thống lõi phải nằm trong mạng nội bộ cô lập, không được kết nối Internet. Điều này lập tức loại bỏ mọi giải pháp CI/CD cloud như GitHub Actions hay GitLab.com.

Đội DevOps dựng một cụm Jenkins gồm 1 Controller và 6 agent chạy trên các VM nội bộ. Mỗi lần commit lên GitLab tự host trong mạng, Jenkins tự động build ứng dụng Java Spring, chạy 2.400 test tự động, quét bảo mật bằng plugin SonarQube nội bộ, rồi đóng gói thành file WAR để triển khai lên môi trường staging. Toàn bộ quy trình từ commit đến staging mất khoảng 18 phút.

Bài học rút ra: Khi ràng buộc pháp lý và bảo mật buộc dữ liệu phải ở lại on-premise, Jenkins gần như là lựa chọn mặc định. "Miễn phí license" và "tự kiểm soát hoàn toàn" không phải là điểm cộng cho vui — với ngành ngân hàng, đó là điều kiện bắt buộc để hệ thống được phép hoạt động.

Tình huống 2 — Công ty outsourcing gia công cho khách hàng Nhật

Một công ty gia công phần mềm ở Hà Nội với khoảng 200 kỹ sư nhận dự án cho một khách hàng Nhật Bản. Khách hàng yêu cầu build server phải đặt trong hạ tầng do công ty kiểm soát, và mỗi dự án của mỗi khách hàng phải cô lập riêng biệt. Họ đã dùng Jenkins từ năm 2016 và tích lũy hàng trăm job.

Vấn đề nảy sinh: sau nhiều năm, họ có hơn 300 Freestyle Job cấu hình thủ công qua giao diện. Không ai nhớ rõ job nào của dự án nào, ai đã sửa gì, và khi một kỹ sư nghỉ việc thì kiến thức về job của người đó cũng biến mất. Một lần, một job build bị ai đó vô tình sửa và hỏng suốt hai ngày mới có người phát hiện.

Đội DevOps quyết định migrate toàn bộ sang Jenkinsfile (Pipeline as Code), đưa cấu hình pipeline vào chính repo của từng dự án, kết hợp Job DSL plugin và Shared Library để tái sử dụng các bước chung. Sau 4 tháng chuyển đổi, mọi thay đổi pipeline đều phải qua merge request và được review như code thường.

Bài học rút ra: Freestyle Job dễ bắt đầu nhưng không mở rộng nổi ở quy mô lớn. Từ ngày đầu, hãy dùng Jenkinsfile để pipeline được version control, review được, và không phụ thuộc vào trí nhớ của một cá nhân. "Cấu hình bằng chuột" là món nợ kỹ thuật (technical debt) sẽ đến ngày phải trả.

Tình huống 3 — Startup fintech đánh giá lại lựa chọn Jenkins

Một startup fintech ở TP.HCM với 15 kỹ sư ban đầu chọn Jenkins vì "ai cũng dùng". Họ dựng một Controller duy nhất trên một VM 2 nhân CPU của một nhà cung cấp cloud, không tách agent, để Controller tự chạy hết mọi build.

Sau 8 tháng, mọi thứ xuống cấp: build queue thường xuyên tắc nghẽn khi nhiều người push cùng lúc, Controller thỉnh thoảng hết RAM và treo, và tệ nhất — họ nhận email cảnh báo bảo mật rằng phiên bản Jenkins cùng nhiều plugin của họ có lỗ hổng CVE nghiêm trọng nhưng chưa ai cập nhật suốt nhiều tháng. Không có kỹ sư chuyên trách vận hành Jenkins, việc nâng cấp cứ bị trì hoãn.

Cuối cùng đội đưa ra quyết định: với một startup nhỏ, dùng toàn bộ trên cloud và không có ràng buộc air-gapped, chi phí "ẩn" của việc tự vận hành Jenkins (thời gian nâng cấp, vá lỗi, gỡ rối agent) đắt hơn lợi ích. Họ chuyển sang GitHub Actions cho phần lớn pipeline, chỉ giữ lại một Jenkins nhỏ cho vài job đặc thù.

Bài học rút ra: Jenkins mạnh nhưng "tự host = tự chịu trách nhiệm". Nếu đội bạn nhỏ, không có ràng buộc on-premise và không có người chuyên trách vận hành, thì tổng chi phí sở hữu (total cost of ownership) của Jenkins có thể cao hơn một dịch vụ managed. Chọn công cụ phải dựa trên bối cảnh, không dựa trên "ai cũng dùng".

Hướng dẫn từng bước

Dưới đây là lộ trình dựng một pipeline Jenkins cơ bản từ con số không.

Bước 1 — Cài đặt Jenkins. Cách nhanh và sạch nhất để thử là dùng Docker:

docker run -d --name jenkins \
  -p 8080:8080 -p 50000:50000 \
  -v jenkins_home:/var/jenkins_home \
  jenkins/jenkins:lts

Bản lts (Long Term Support) ổn định hơn bản latest, nên dùng cho môi trường thật. Cổng 8080 là giao diện web, 50000 là cổng agent kết nối về.

Bước 2 — Mở khóa và cài plugin ban đầu. Truy cập http://localhost:8080, lấy mật khẩu admin ban đầu bằng lệnh docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword. Chọn "Install suggested plugins" để có bộ plugin phổ biến (Git, Pipeline, Credentials...).

Bước 3 — Tạo credentials. Vào Manage Jenkins → Credentials để thêm khóa SSH, token GitLab/GitHub, hay tài khoản deploy. Tuyệt đối không hardcode mật khẩu vào Jenkinsfile — luôn dùng hệ thống Credentials của Jenkins.

Bước 4 — Viết Jenkinsfile. Tạo file Jenkinsfile ở gốc repo. Bắt đầu từ mẫu Declarative đơn giản, rồi bổ sung dần các stage lint, test, build, deploy.

Bước 5 — Tạo Pipeline job. Trong Jenkins, chọn New Item → Pipeline (hoặc Multibranch Pipeline nếu bạn muốn Jenkins tự phát hiện các branch có Jenkinsfile). Trỏ nó tới repo Git của bạn.

Bước 6 — Cấu hình trigger. Thiết lập webhook từ GitHub/GitLab để mỗi lần push tự động kích hoạt build, thay vì phải bấm tay. Đây chính là "CI" đúng nghĩa — tích hợp liên tục, tự động.

Bước 7 — Thêm agent để mở rộng. Khi build nhiều, thêm agent qua Manage Jenkins → Nodes. Cấu hình để Controller chỉ điều phối, agent mới là nơi chạy build thật.

Bước 8 — Bảo trì định kỳ. Lên lịch cập nhật Jenkins core và plugin đều đặn (ví dụ mỗi tháng), sao lưu thư mục JENKINS_HOME, và theo dõi cảnh báo bảo mật.

Lỗi thường gặp & mẹo

Lỗi 1 — Để Controller tự chạy build. Đây là sai lầm kinh điển của người mới. Controller nên chỉ điều phối; giao hết build cho agent. Cấu hình số executor của Controller về 0 để buộc mọi build chạy trên agent, giúp Controller ổn định.

Lỗi 2 — Bỏ bê cập nhật và plugin. Jenkins là mục tiêu tấn công phổ biến vì nhiều instance chạy phiên bản cũ đầy lỗ hổng. Đừng cài plugin bừa bãi — mỗi plugin là một bề mặt tấn công (attack surface). Chỉ cài cái thực sự cần, và cập nhật đều.

Lỗi 3 — Hardcode secret vào Jenkinsfile. Mật khẩu, token in ra log là thảm họa. Luôn dùng khối withCredentials để nạp secret an toàn:

withCredentials([string(credentialsId: 'deploy-token', variable: 'TOKEN')]) {
    sh './deploy.sh --token=$TOKEN'
}

Jenkins sẽ tự động che (mask) giá trị này trong log.

Lỗi 4 — Không sao lưu JENKINS_HOME. Toàn bộ cấu hình, job, credentials nằm trong thư mục này. Mất nó là mất tất cả. Sao lưu định kỳ.

Mẹo — Dùng Shared Library. Khi có nhiều pipeline giống nhau, tách logic chung ra thành Shared Library để tái sử dụng, tránh copy-paste (khái niệm này bạn sẽ đào sâu ở Bài 38 về Pipeline Templates & Reusability).

Mẹo — Blue Ocean. Cài plugin Blue Ocean để có giao diện trực quan hóa pipeline đẹp và dễ đọc hơn nhiều so với giao diện cổ điển, đặc biệt hữu ích khi debug pipeline nhiều stage.

Mẹo — agent { docker }. Cho mỗi stage chạy trong một container Docker riêng để môi trường build sạch và nhất quán, tránh cảnh "chạy được trên agent A nhưng lỗi trên agent B".

Bài tập thực hành

  • Dựng Jenkins bằng Docker theo hướng dẫn ở trên, mở khóa và cài suggested plugins. Chụp lại màn hình Dashboard đầu tiên.
  • Viết một Jenkinsfile ba stage (Build → Test → Deploy) cho một dự án nhỏ bất kỳ của bạn (Node.js, Java, hay Python đều được). Mỗi stage in ra một dòng thông báo và chạy một lệnh thật (ví dụ npm ci, npm test).
  • Tạo Pipeline job trỏ tới repo chứa Jenkinsfile đó, chạy thử và quan sát từng stage sáng lên trên giao diện.
  • Thêm một credential (ví dụ một token giả) và dùng withCredentials để nạp nó vào một stage, kiểm chứng rằng Jenkins che giá trị trong log.
  • Suy nghĩ và viết ra: với dự án hiện tại (hoặc mơ ước) của bạn, Jenkins hay một CI cloud như GitHub Actions phù hợp hơn? Liệt kê 3 lý do cho lựa chọn của bạn, dựa trên ràng buộc bảo mật, quy mô đội và năng lực vận hành.

Tóm tắt

Jenkins là automation server tự host lâu đời, viết bằng Java, hoạt động theo mô hình Controller — Agent để mở rộng khối lượng build. Sức mạnh của nó nằm ở khả năng kiểm soát hoàn toàn hạ tầng, kho hơn 1.800 plugin, và tính miễn phí bản quyền — khiến nó trở thành lựa chọn gần như bắt buộc cho ngân hàng, cơ quan nhà nước và các hệ thống on-premise/air-gapped, đặc biệt phổ biến tại Việt Nam.

Cách làm đúng đắn là dùng Pipeline as Code với Jenkinsfile thay vì Freestyle Job cấu hình bằng chuột, quản lý secret qua hệ thống Credentials, để Controller chỉ điều phối và giao build cho agent, đồng thời cập nhật core lẫn plugin đều đặn để tránh lỗ hổng bảo mật.

Nhưng "tự host = tự chịu trách nhiệm": nếu đội bạn nhỏ, chạy hoàn toàn trên cloud và không có người chuyên trách vận hành, tổng chi phí sở hữu Jenkins có thể vượt lợi ích, và một CI managed sẽ hợp lý hơn. Chọn công cụ phải dựa trên bối cảnh cụ thể — ràng buộc pháp lý, quy mô, và năng lực vận hành của đội — chứ không phải vì "ai cũng dùng".

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi