Product Management
Đăng nhập
ESC

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

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

CI/CD Integration — Jenkins

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

Bạn đã viết được một bộ test tự động chạy ngon trên máy mình. Bạn gõ mvn test hoặc pytest, mọi thứ xanh mướt, và bạn tự hào gửi tin nhắn cho team: "Test pass hết rồi nha!". Nhưng ba ngày sau, một bạn dev khác merge code, deploy lên staging, và khách hàng báo lỗi ở đúng luồng mà bạn đã test. Chuyện gì xảy ra? Bộ test của bạn chỉ chạy khi bạn nhớ chạy nó — trên máy của bạn — với dữ liệu của bạn. Nó không bảo vệ được ai cả.

Đây chính là khoảng cách giữa "biết viết test tự động" và "biết vận hành test tự động trong đời thực". Test automation chỉ thực sự tạo ra giá trị khi nó chạy tự động, liên tục, và mỗi khi có thay đổi code — không phụ thuộc vào việc ai đó nhớ bấm nút. Đó là lý do chúng ta cần CI/CD, và trong bài này, cụ thể là Jenkins.

Jenkins là "người gác cổng không bao giờ ngủ" cho codebase của bạn. Mỗi commit đẩy lên, Jenkins sẽ tự động kéo code về, build, và chạy toàn bộ suite test bạn đã viết. Nếu có test đỏ, nó chặn lại và hét lên cho cả team biết trước khi bug kịp bò tới production. Với vai trò một SDET hay QA automation, biết cấu hình Jenkins để chạy test là kỹ năng bắt buộc — không phải chuyện "nice to have". Rất nhiều tin tuyển dụng QA automation ở Việt Nam (FPT Software, KMS Technology, NashTech, Employment Hero...) liệt kê thẳng "Jenkins/CI experience" trong yêu cầu cứng.

Bài này tập trung riêng vào Jenkins — cách cài đặt, cách viết pipeline chạy test, cách publish kết quả và xử lý các vấn đề thực chiến. Chúng ta sẽ không lan sang GitHub Actions hay Docker chi tiết (đó là các bài riêng), mà đào sâu đúng công cụ Jenkins.

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

Jenkins là gì?

Jenkins là một CI/CD server mã nguồn mở, viết bằng Java, và là công cụ CI phổ biến nhất thế giới trong hơn một thập kỷ. Điểm mạnh lớn nhất của Jenkins là:

  • Self-hosted: bạn cài trên server của mình (on-premise hoặc cloud), toàn quyền kiểm soát. Điều này quan trọng với nhiều công ty Việt Nam trong lĩnh vực ngân hàng, fintech, bảo hiểm — nơi dữ liệu và hệ thống build không được phép rời khỏi hạ tầng nội bộ.
  • Plugin ecosystem khổng lồ: hơn 1.800 plugin. Cần tích hợp với Selenium Grid, Allure report, Slack, JIRA, Docker, Telegram? Gần như luôn có plugin sẵn.
  • Miễn phí: không tốn phí license, chỉ tốn chi phí server và công vận hành.
Đổi lại, Jenkins đòi bạn phải tự vận hành: tự nâng cấp, tự vá bảo mật, tự lo server không sập. Đây là đánh đổi giữa "quyền kiểm soát" và "công sức bảo trì".

Những khái niệm bạn phải nắm

Job / Pipeline: một đơn vị công việc Jenkins thực thi. Ngày xưa người ta dùng "Freestyle job" (cấu hình bằng giao diện click chuột). Ngày nay chuẩn mực là Pipeline — định nghĩa toàn bộ quy trình bằng code trong một file tên Jenkinsfile đặt ngay trong repo. Cách này gọi là "Pipeline as Code", giúp quy trình build được version control cùng source code.

Node / Agent / Executor: Jenkins có một máy "controller" (điều phối) và các máy "agent" (thực thi thật). Test của bạn sẽ chạy trên agent. Với UI test cần trình duyệt, agent phải có sẵn browser + driver, hoặc chạy headless.

Stage / Step: một Pipeline chia thành nhiều stage (ví dụ: Checkout → Build → Test → Report). Mỗi stage gồm nhiều step (lệnh cụ thể).

Trigger: điều kiện kích hoạt pipeline. Phổ biến nhất với QA:

  • SCM polling: Jenkins hỏi Git định kỳ "có commit mới không?".
  • Webhook: Git chủ động báo Jenkins ngay khi có push (nhanh và hiệu quả hơn polling).
  • Scheduled (cron): chạy theo lịch, ví dụ suite regression nặng chạy 2h sáng mỗi ngày.
Declarative vs Scripted Pipeline: Jenkins có hai cú pháp viết Jenkinsfile. Declarative (bắt đầu bằng pipeline { }) có cấu trúc rõ ràng, dễ đọc, khuyến nghị cho hầu hết trường hợp. Scripted (bắt đầu bằng node { }) linh hoạt hơn nhưng phức tạp. Người mới nên bắt đầu với Declarative.

Cài đặt nhanh bằng Docker

Cách nhanh nhất để có một Jenkins chạy thử:

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

Sau đó mở http://localhost:8080, lấy mật khẩu khởi tạo bằng:

docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword

Làm theo wizard, chọn "Install suggested plugins", tạo tài khoản admin. Xong — bạn đã có một CI server. Chú ý dùng bản lts (Long-Term Support) cho ổn định, và mount volume jenkins_home để không mất cấu hình khi container restart.

Jenkinsfile mẫu cho một suite test

Đây là trái tim của bài học. Một pipeline chạy test Selenium/Maven điển hình:

pipeline {
    agent any
    tools {
        maven 'Maven-3.9'
        jdk 'JDK17'
    }
    triggers {
        pollSCM('H/5    ')   // hỏi Git mỗi 5 phút
    }
    stages {
        stage('Checkout') {
            steps {
                git branch: 'main',
                    url: 'https://github.com/cong-ty/qa-automation.git'
            }
        }
        stage('Build') {
            steps {
                sh 'mvn clean compile'
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
    }
    post {
        always {
            junit '*/target/surefire-reports/.xml'
        }
        failure {
            echo 'Test that bai! Kiem tra report ngay.'
        }
    }
}

Hãy để ý khối post. Đây là phần mà người mới hay bỏ quên nhưng cực kỳ quan trọng: junit '*/target/surefire-reports/.xml' bảo Jenkins đọc file kết quả test dạng JUnit XML và hiển thị lên giao diện thành biểu đồ trend đẹp mắt — bao nhiêu test pass, fail, skip, test nào chậm. Không có bước này, kết quả test của bạn chỉ nằm chôn trong log dài dằng dặc.

Tình huống thực tế

Ví dụ 1 — Fintech ở TP.HCM: từ "chạy tay" tới "gác cổng tự động"

Một công ty fintech tầm trung ở Quận 1, TP.HCM, có team QA 6 người phụ trách sản phẩm ví điện tử. Ban đầu, mỗi lần dev báo "code xong rồi", một bạn QA sẽ mở IntelliJ, kéo code về, chạy mvn test cho 240 test case regression. Mỗi lần chạy mất khoảng 35 phút, và bạn QA đó phải ngồi canh. Một tuần deploy 3–4 lần, tính ra mất gần 4 giờ chỉ để... ngồi nhìn thanh progress.

Tệ hơn, có lần bạn QA quên chạy suite trước khi cho phép release, một bug ở luồng nạp tiền lọt lên production, gây sai lệch số dư cho khoảng 300 người dùng trong 2 giờ trước khi bị phát hiện.

Sau khi dựng Jenkins: họ cấu hình một pipeline với webhook từ GitLab. Mỗi khi có merge request vào nhánh develop, Jenkins tự động chạy toàn bộ 240 test trên một agent riêng, và chặn merge nếu có test đỏ (qua tích hợp với GitLab merge check). Kết quả sau 2 tháng:

  • Con người không còn tốn 4 giờ/tuần ngồi canh test.
  • Bug lọt production giảm rõ rệt vì mọi merge đều bị "gác cổng".
  • Report Allure được publish tự động, PM chỉ cần mở link Jenkins là thấy tình trạng.
Bài học: giá trị lớn nhất của Jenkins không phải là "chạy test nhanh hơn" (nó không nhanh hơn) mà là chạy test một cách kỷ luật, không phụ thuộc trí nhớ con người, và biến kết quả test thành một cổng kiểm soát bắt buộc.

Ví dụ 2 — Công ty outsourcing: regression đêm và vấn đề "sáng ra thấy đỏ"

Một team tại một công ty outsourcing lớn (kiểu FPT Software / KMS) làm cho khách hàng Úc. Suite end-to-end của họ có 800+ test case UI, chạy mất gần 2 tiếng. Chạy suite này sau mỗi commit là không khả thi — sẽ tắc nghẽn cả ngày.

Giải pháp họ chọn: hai pipeline tách biệt.

  • Pipeline "smoke": 40 test quan trọng nhất, chạy sau mỗi commit, xong trong 6 phút.
  • Pipeline "full regression": 800 test, cấu hình triggers { cron('0 2 *') } để chạy lúc 2h sáng mỗi ngày, tận dụng lúc múi giờ Việt Nam đêm khuya và server rảnh.
Mỗi sáng, team leader mở Jenkins uống cà phê và xem báo cáo đêm qua. Họ tích hợp thêm plugin gửi thông báo về Telegram của nhóm khi build fail. Có một giai đoạn, cứ 2h sáng suite lại đỏ ngẫu nhiên vài test khác nhau — sau điều tra, đó là do các test flaky phụ thuộc thời gian phản hồi API. Họ dùng chính dữ liệu trend trên Jenkins để nhận diện test nào hay đỏ nhất mà cô lập ra xử lý riêng.

Bài học: đừng cố nhét cả suite khổng lồ vào mỗi commit. Hãy phân tầng — smoke nhanh cho mỗi commit, regression nặng theo lịch đêm. Jenkins cho phép nhiều pipeline với trigger khác nhau chính là để làm điều này.

Ví dụ 3 — Startup nhỏ và cái bẫy "Jenkins tự sập"

Một startup edtech ở Đà Nẵng, team chỉ 3 dev kiêm QA, dựng Jenkins trên một VPS 2GB RAM rẻ tiền để tiết kiệm. Ban đầu mọi thứ ổn. Nhưng khi suite test Selenium chạy song song 4 luồng trình duyệt Chrome, RAM cạn kiệt, Jenkins bị OOM (out of memory) và container tự chết giữa chừng. Kết quả build lúc pass lúc fail vô lý, team mất niềm tin vào CI và bắt đầu... bỏ qua kết quả đỏ.

Đây là cái bẫy chết người: một CI không đáng tin còn tệ hơn không có CI, vì nó tạo ra "cry wolf" — mọi người quen với việc thấy đỏ mà kệ. Họ giải quyết bằng cách nâng VPS lên 8GB, chạy Chrome ở chế độ headless để tiết kiệm tài nguyên, và giới hạn số executor song song hợp lý.

Bài học: Jenkins self-hosted đòi bạn phải lo hạ tầng đủ mạnh. UI test đặc biệt ngốn RAM. Hãy tính toán tài nguyên trước, và luôn giữ CI ở trạng thái "xanh là tin được".

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

Đây là lộ trình dựng một pipeline Jenkins chạy test từ số không:

  • Cài Jenkins bằng lệnh Docker ở trên, đăng nhập, cài suggested plugins. Cài thêm plugin cần thiết cho QA: JUnit, Allure (nếu dùng Allure report), Git.
  • Cấu hình công cụ build. Vào Manage Jenkins → Tools, khai báo JDK và Maven (đặt tên khớp với tên bạn dùng trong Jenkinsfile, ví dụ Maven-3.9, JDK17).
  • Thêm credentials cho Git. Vào Manage Jenkins → Credentials, thêm username/password hoặc SSH key để Jenkins kéo được private repo. Đừng bao giờ hardcode mật khẩu vào Jenkinsfile.
  • Đưa Jenkinsfile vào repo. Tạo file Jenkinsfile ở thư mục gốc dự án, nội dung như mẫu ở trên. Commit và push lên Git.
  • Tạo Pipeline job. Trong Jenkins: New Item → Pipeline. Ở phần Pipeline, chọn "Pipeline script from SCM", trỏ vào repo Git của bạn và chỉ định đường dẫn Jenkinsfile. Cách này giúp pipeline được đọc trực tiếp từ code.
  • Cấu hình trigger. Nếu Git server hỗ trợ webhook (GitHub/GitLab đều có), cấu hình webhook trỏ về http://jenkins-cua-ban/github-webhook/. Nếu không, tạm dùng pollSCM như trong mẫu.
  • Chạy thử (Build Now). Bấm "Build Now", xem log real-time qua "Console Output". Sửa lỗi đường dẫn/tool cho tới khi xanh.
  • Publish report. Đảm bảo khối post { always { junit '...' } } hoạt động. Nếu dùng Allure, thêm allure includeProperties: false, results: [[path: 'target/allure-results']].
  • Thêm thông báo. Tích hợp Slack/Telegram/email trong khối post { failure { ... } } để cả team biết ngay khi có test đỏ.
  • Kiểm chứng gác cổng. Cố tình đẩy một commit làm hỏng test, xác nhận Jenkins báo đỏ và (nếu đã cấu hình) chặn merge. Đây là bước chứng minh hệ thống thật sự bảo vệ bạn.

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

Lỗi: "mvn: command not found" hoặc "chromedriver not found" trên agent. Nguyên nhân là agent thiếu công cụ. Máy bạn có Maven/Chrome không có nghĩa agent Jenkins cũng có. Hãy khai báo tool trong Jenkins hoặc dùng agent Docker có sẵn môi trường.

Lỗi: build "xanh" nhưng thật ra test fail. Cực nguy hiểm. Xảy ra khi lệnh mvn test fail nhưng Jenkins vẫn coi là thành công, thường do bạn nuốt exit code (ví dụ thêm || true). Đừng bao giờ che giấu exit code của lệnh test — Jenkins dựa vào đó để biết pass hay fail.

Mẹo: luôn dùng junit để archive report kể cả khi test fail. Đặt trong post { always } chứ không phải post { success }, nếu không khi test đỏ bạn sẽ mất luôn báo cáo — đúng lúc cần nó nhất.

Mẹo: chạy UI test headless trên CI. Trên server thường không có màn hình. Cấu hình Chrome/Firefox headless (--headless=new) để test không treo vì thiếu display.

Mẹo: tách secret ra khỏi Jenkinsfile. Dùng withCredentials để inject token, mật khẩu DB, API key. Không commit chúng vào repo.

Mẹo: đặt timeout cho pipeline. Thêm options { timeout(time: 30, unit: 'MINUTES') } để một test bị treo không khiến build chạy vô tận, chiếm executor và làm nghẽn cả hàng đợi.

Mẹo: giữ workspace sạch. Thêm mvn clean hoặc dùng cleanWs() để tránh kết quả build cũ gây nhiễu — nguồn cơn của nhiều bug "chỉ xảy ra trên CI".

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

  • Dựng Jenkins bằng Docker theo lệnh trong bài, đăng nhập thành công và cài các plugin JUnit + Git.
  • Fork một repo test mẫu (bất kỳ project Maven + TestNG/JUnit nào có sẵn test), viết một Jenkinsfile Declarative gồm 3 stage: Checkout, Build, Test.
  • Tạo Pipeline job đọc Jenkinsfile từ SCM, chạy "Build Now" và làm cho nó xanh. Ghi lại thời gian build.
  • Thêm publish JUnit report vào khối post, mở giao diện Jenkins và xác nhận bạn thấy biểu đồ test trend.
  • Cố tình phá một test (sửa assertion cho sai), push lên, và xác nhận Jenkins báo build đỏ. Sau đó sửa lại cho xanh. Đây là bước cảm nhận rõ nhất vai trò "gác cổng" của CI.
  • (Nâng cao) Thêm một trigger cron để pipeline tự chạy theo lịch, và một thông báo Slack/Telegram/email trong khối post { failure }.

Tóm tắt

Jenkins biến bộ test tự động của bạn từ "một thứ chạy khi ai đó nhớ" thành "một người gác cổng không bao giờ ngủ". Nó là CI/CD server mã nguồn mở, self-hosted, mạnh nhờ hệ sinh thái plugin — đánh đổi lại là bạn phải tự vận hành hạ tầng.

Những điều cốt lõi cần nhớ:

  • Pipeline as Code qua Jenkinsfile trong repo là chuẩn mực; ưu tiên cú pháp Declarative.
  • Một pipeline test điển hình gồm các stage Checkout → Build → Test, cộng khối post để publish report và gửi thông báo.
  • Phân tầng pipeline: smoke nhanh cho mỗi commit, regression nặng theo lịch đêm.
  • CI chỉ có giá trị khi đáng tin — một Jenkins hay sập, hay báo đỏ vô lý sẽ khiến cả team mất niềm tin và bỏ qua kết quả, còn tệ hơn không có CI.
  • Đừng che giấu exit code, luôn archive report trong post { always }, tách secret ra khỏi code, và cấp đủ tài nguyên cho agent chạy UI test.
Khi bạn đã cho test tự chạy trên mỗi thay đổi code và chặn được bug trước khi lên production, bạn đã chuyển từ "người viết test" thành "người vận hành chất lượng" — đúng tinh thần của một SDET. Ở các bài sau, chúng ta sẽ so sánh với GitHub Actions, đóng gói môi trường test bằng Docker, và chạy test song song để tăng tốc — nhưng nền tảng tư duy CI mà bạn vừa dựng với Jenkins sẽ theo bạn suốt sự nghiệp.

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