Automation Test với Docker và Gitlab CI
Cập nhật gần nhất: 10/11/2024
Chào mừng các bạn đã quay trở lại với series học Docker và CICD của mình. 👋👋
Ở bài trước chúng ta đã cùng làm qua một số ví dụ đầu tiên về CICD với Gitlab. Ở bài này chúng ta cùng nhau setup automation test cho project Docker và sử dụng Gitlab CI để tự động quá trình này nhé.
Sao lại phải test?
Câu hỏi này chắc là muôn thuở rồi 😁. Thường chúng ta có xu hướng hơi "lười" trong việc viết test mà cứ thế deploy thẳng tay, bao giờ có lỗi thì sập hệ thống hoặc user phàn nàn là biết liền à 🤣🤣
Sau một quãng thời gian đi làm cũng "gọi là" có tí kinh nghiệm 😁, mình nhận thấy rằng việc viết test cho project có rất nhiều lợi ích như sau:
- đảm bảo được code của chúng ta chạy đúng, dữ liệu trả về chính xác, bao được các trường hợp lỗi
- code được 1 thời gian dài đầu mình không bị "trệch khỏi đường ray" khi luôn có test đảm bảo mỗi lần commit lên thì code của mình đều đã được test cẩn thận
- Đặc biệt nếu có nhiều người cùng làm project thì có thể đảm bảo được dữ liệu trả về từ các function/api theo chuẩn của cả project, tránh trường hợp API của mỗi người khác nhau trả về dữ liệu cấu trúc/kiểu khác nhau
- Test chính là điểm quan trọng và là mục đích chính của chữ chữ "CI" trong "CICD" (Continuous Integration/Continuous Delivery)
- Việc viết test mình thấy là cũng rèn cho mình được tính cẩn thận và chuyên nghiệp hơn khi viết code. Rèn luyện mài dũa bản thân trước nhỡ ra sau này được vào làm Facebook, Google thì có cái mà chiến luôn 😂 😂 (có ước mơ là có động lực 😉)
Mình thấy thì mọi người hay đề xuất là viết test càng sớm càng tốt ngay từ ban đầu. Mình thấy như thế là lí tưởng nhất 😉. Nhưng cá nhân khi làm thực tế thì mình thường bắt đầu viết test sau lần deploy đầu tiên, vì mình thấy viết test sớm mất nhiều thời gian mà các function/api chưa chắc đã là cuối cùng, có thể thay đổi liên tục. Tuỳ theo project của công ty các bạn, hoặc tự chọn cho mình 1 cách làm phù hợp nhất nhé 😘
Mục tiêu
Hết bài này chúng ta sẽ đạt được kết quả như sau:
- Setup automation test + coverage test chạy tự động với Gitlab CI
- Build và chạy test cho Docker Image
- Biết cách chia quá trình chạy CICD pipeline ra nhiều stage
- Lưu lại image cho mỗi commit để đảm bảo sau này ta luôn có thể chạy image của 1 commit tại 1 thời điểm bất kì lúc nào (rất hữu ích khi vừa deploy xong thì server thấy ngủm củ tỏi vì code lỗi và phải rollback về commit ngay trước đó khi mà code vẫn chạy băng băng 😎)
Nãy giờ dài dòng quá bắt đầu thôi nào ...
Ôi từ từ đã
<Lại cái gì nữa ông ơi 😤😤)>
Hôm nay vào Viblo xem thấy reputation lên 10K, tự thẩm du tinh thần và thấy là blog của mình hình như cũng giúp được khá nhiều bạn trong việc học lập trình (chủ yếu là Laravel/Vue và JS nói chung).
Thời gian trôi nhanh như con 🐕 chạy ngoài đồng, ngày đầu viết blog là cách đây 6 năm, ban đầu cũng chỉ vì yêu Vue và thấy tài liệu không có nhiều nên muốn viết blog để chia sẻ với mọi người và cũng để rượt lại mớ kiến thức trong đầu + những thứ trải nghiệm trong quá trình làm việc. Về sau thấy blog của mình cũng được một cơ số bạn quan tâm vì hữu ích, cảm thấy "thung thướng" tột độ.
Cảm ơn tất cả các bạn đọc đã theo dõi blog của mình trong suốt thời gian vừa qua, mình vẫn sẽ luôn chia sẻ những gì mình học được ở blog này để mọi người cùng nhau tiến lên
Dân tộc Việt nam có sánh vai với các cường quốc năm châu được hay không chính là nhờ một phần lớn vào công Debug của các cháu Developer....😂😂
Thôi chúng ta cùng bắt đầu nhé
Điều kiện tiên quyết
<Nghe như học sinh cấp 3 🤓🤓 >
Nếu bạn nào chưa có tài khoản Gitlab thì các bạn đăng kí trước đã nhé.
Setup
Đầu tiên các bạn clone code của mình ở đây. Ở bài này ta chỉ quan tâm tới folder cicd-automation-test nhé.
Tiếp theo, các bạn copy folder đó ra 1 nơi nào đó riêng bên ngoài nhé. Vì nếu để như vậy lát nữa các bạn commit sẽ vào repo của mình chứ không phải của riêng các bạn mất.
Sau đó các bạn quay trở lại Gitlab, tạo cho mình 1 Blank
repository với tên là cicd-automation-test
Tổng quan
Ở bài này mình đã setup sẵn cho các bạn một project NodeJS đơn giản có thể chạy được, và mình cũng đã Dockerize nó cho các bạn luôn, nếu bạn nào chưa hiểu các Dockerize project NodeJS thì xem lại các bạn trước trong series của mình nhé:
- Ở đây ta có một project NodeJS + Express cơ bản (mình tạo bằng express-generator). Dùng MongoDB làm database
- Các bạn mở file
routes/index.js
có thể thấy ta có 2 routes là/login
và/register
(cái tên nói lên tất cả 😁), dùng để tạo tài khoản mới và login user nhé. - Ở folder
__tests__
mình có 1 file làroutes.test.js
, file này dùng để test 2 route bên trên của chúng ta. Test API là một trong nhưng loại test mình hay dùng nhất vì nó là cái mà user sẽ tương tác trực tiếp sau này nên phải đảm bảo là nó chạy ngon 😉.
Ở bài này để test thì mình dùng Jest - một framework để test rất hot hiện nay, do Facebook phát triển (cái gì của Facebook với Google nó cũng hot luôn 😝). Cùng với đó mình dùng thêm 1 thư viện là supertest
để ta có thể test với HTTP request nhé.
Về cơ bản ở bài này, trong package.json
mình có định nghĩa 1 script
là test
để lát nữa khi chạy npm run test
thì Jest sẽ đọc file cấu hình jest.config.js
lên sau đó sẽ tự động detect folder __tests__
và chạy tất cả các file test trong đó (mặc định tự tìm tới folder này luôn nhé).
Các bạn mở file __tests__/routes.test.js
sẽ thấy trong đó mình test 2 route là Login và Register, với mỗi route mình test một số trường hợp cơ bản. Ví dụ với Register thì test khi user nhập thiếu email có trả về đúng hay không, password rỗng thì kết quả trả về có đúng mong đợi hay không,.... Cái tên nói lên tất cả, mỗi test đều rất cơ bản luôn các bạn tự sướng phần này nhé 😉 (nếu có gì thắc mắc comment cho mình biết nhé)
Bắt đầu
Đầu tiên chúng ta cùng chạy thử test ở local xem mọi thứ có ổn không đã nhé.
Thì để chạy test, ta có 2 cách:
- Chạy trực tiếp từ môi trường ngoài: ta cần chạy npm install + có cài MongoDB -> môi trường ngoài mất zin -> không thích 👎
- Chạy trong Docker container: giữ zin cho môi trường gốc, cùng với đó là ở production ta chạy với Docker (giả sử vậy), do đó test trong môi trường Docker sẽ oke hơn (nhỡ bằng 1 phép màu nào đó test ở 2 môi trường lại cho kết quả khác nhau chẳng hạn)
Nếu các bạn thích test trực tiếp từ môi trường ngoài cũng oke luôn nhé.
Build Docker image và test ở local
Bây giờ ta cùng tiến hành build Docker image và chạy thử ở local xem test ổn không đã nhé.
Các bạn chạy command sau:
docker build -t learning-docker:cicd-automation-test .
Sau khi hoàn tất ta cùng chạy project lên xem nhé:
docker compose up -d
Ở thời điểm hiện tại thì Docker compose đã có sẵn luôn khi ta cài Docker rồi nhé 😘
Note cho bạn nào đang dùng Windows: các bạn xem lại phần chú ý lúc mount volume cho MongoDB mình đã nói ở bài Dockerize ứng dụng NodeJS, Mongo rồi nhen
Tiếp theo ta thử đăng kí tài khoản mới xem đã oke chưa nhé, có 2 API:
localhost:3000/register
: method=POSTlocalhost:3000/login
: method=POST
Các bạn mở Postman cho tiện, body
thì các bạn xem ảnh dưới của mình nha (Postman là tool để test API rất tiện các bạn có thể dùng nó để test cho nhanh thay vì dùng CURL nha):
Sau đó các bạn thử luôn login xem ok chưa nhé (route login thì chỉ cần email
+ password
không cần displayName
nhé), phần này mình để các bạn tự sướng 💪
Sau khi check và thấy 2 route chúng ta đã chạy ổn định, thì ta tiến hành chạy Test nhé. Vì test trong môi trường Docker vì thế ta có 2 cách để chạy command test như sau:
- Chui vào container và chạy test
docker compose exec app sh
npm run test
- Đứng ở bên ngoài và chạy trực tiếp:
docker compose exec -T app npm run test
Các bạn chọn cách nào cũng được nhé, ở đây mình chọn cách 1 để kết quả in ra nó màu mè rõ ràng hơn, nhưng lát nữa khi setup CICD thì mình dùng cách 2 cho nhanh nhé. Ta tiến hành với cách 1:
docker compose exec app sh
npm run test
Các bạn sẽ thấy kết qủa in ra như sau:
Ở trên các bạn thấy kết quả in ra ta có 1 test suite pass bài test (1 test suite ở đây = 1 file test), tổng cộng 13 test cases đã test thành công.
Oke nom ổn rồi đó nhỉ, ta đã chạy test trong Docker container và các test của ta đều ổn, code của ta giờ đã có thể commit được rồi.
Tiếp theo ta copy folder làm việc ra ngoài khỏi folder learning-docker
nhé, nếu không Git có thể bị conflict đó, như này nè:
Tiếp theo, ở folder ta vừa copy ra ngoài là cicd-automation-test
, ta tiến hành commit code vào repo cicd-automation-test mà các bạn tạo ở đầu bài, nhớ thay username
bên dưới bằng username của các bạn nha (xem trên Gitlab):
git init
git add .
git commit -m "first commit"
git remote add origin https://gitlab.com/<username>/cicd-automation-test.git
git push -u origin master
Push xong lên Gitlab check đảm bảo code đã có trên đó nha những người anh em thiện lành 😎:
Tiếp đó để push được Docker image lên Gitlab Container Registry thì ta phải đặt tên image cho đúng trước khi push, để xem tên chính xác ta cần đặt thì các bạn vào Container Registry:
Ví dụ ở trên tên image của mình là registry.gitlab.com/maitrungduc1410/cicd-automation-test
Giờ ta quay lại local và tag image ta build khi nãy thành tên image mới để push lên Gitlab nha:
docker tag learning-docker:cicd-automation-test registry.gitlab.com/maitrungduc1410/cicd-automation-test
docker push registry.gitlab.com/maitrungduc1410/cicd-automation-test
Thay tên username của các bạn vào cho đúng nhé
Sau khi push xong ta quay lại Gitlab F5 sẽ thấy image nha:
Âu cây, xong 1 lần commit, code và image đã sẵn sàng cho production (một cảm giác tự hào không hề nhẹ 💪💪)
Có điều là, lần nào code xong cũng phải tự build image rồi tự chạy test thì mệt quá nhỉ, nhỡ mình quên test mà commit thẳng thì sao? Hay team có 1 dev vừa bị người yêu chia tay đang code trong nước mắt và hình như chuẩn bị bấm commit code mà không test, cùng không màng tới hậu quả sau này (còn gì đau hơn người yêu bỏ 😂)
Ở bước tiếp theo ta sẽ setup CICD để có thể chạy test một cách tự động, ta chỉ cần commit code, mọi thứ còn lại không phải nghí luôn 😎
Cấu hình Gitlab CI
Các bạn tạo cho mình file tên là .gitlab-ci.yml
nhé:
chi tiết về file này ở bài trước mình đã giải thích kĩ rồi nhé. Về cơ bản là lần tiếp theo khi ta commit, Gitlab sẽ "nhìn" thấy file này và khởi động quá trình CICD cho chúng ta nhé.
image: docker:27.3.1
services:
- docker:27.3.1-dind
stages:
- build
- test
- release
before_script:
- docker version
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
build:
stage: build
script:
- docker pull $CI_REGISTRY_IMAGE:latest || true
- docker build --cache-from $CI_REGISTRY_IMAGE:latest --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
test:
stage: test
before_script:
- docker compose version
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest
script:
- docker compose up -d
- sleep 15
- docker compose run -T -e NODE_OPTIONS="--no-experimental-fetch" app npm run test
release:
variables:
GIT_STRATEGY: none
stage: release
script:
- docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest
- docker push $CI_REGISTRY_IMAGE:latest
Ở bài này ta đã có 1 pipeline phức tạp hơn nhiều bài trước rồi, giải thích chút nhé:
- Pipeline của chúng ta có 3 stage (giai đoạn): build Docker image, chạy Test và stage cuối cùng là test image là
latest
nếu như vượt qua được bài test. - Chú ý ở bài này mỗi stage ta chỉ có 1 job, trùng tên với stage luôn. Thực tế ta có thể chạy nhiều job trong 1 stage.
- Ở job
test
đoạnbefore_script
sẽ override (ghi đè) đoạnbefore_script
trên đầu file. Ở đây mục ta tiến hành càidocker-compose
- Command
sleep 15
ý bảo "chờ 15 giây" rồi hẵng chạy test nhé, vì tại thời điểm khởi động project với Docker thì mất một chút thời gian để MongoDB hoàn tất quá trình khởi động của nó. - Ở job
release
ta có khai báo biến môi trườngGIT_STRATEGY
với giá trịnone
, ý bảo là không cần clone source code vào bên trong Gitlab Runner
Có bạn thắc mắc là ở job test
đưa before_script
thay vào đoạn before_script
trên đầu được không, thì câu trả lời là được nhé
Nếu các bạn để ý thì thấy ở job test
đoạn before_script
chúng ta tag image vừa pull về thành latest
, lí do bởi vì trong file docker-compose.yml
chúng ta fix sẵn giá trị là latest
nên chúng ta cần tag trước để bước sau đó chúng ta có thể chạy image lên.
Note: tại bất kì job nào nếu có lỗi xảy ra thì các job ở stage sau đó sẽ không được thực hiện (ta sẽ nhận được mail báo về)
Ở 2 jobs
test
vàrelease
ta đều pull image về trước, lí do bởi vì không có gì đảm bảo 2 job cùng được chạy trên 1 Gitlab Runner, nên để đảm bảo ta luôn phải pull image về trước.
Ở job test đoạn
npm run test
mình phải dùngNODE_OPTIONS="--no-experimental-fetch"
nếu không tí nữa đến đoạncoverage test
CICD sẽ failed
Sau đó ta sửa lại tên image của service app
trong docker-compose.yml
cho khớp với repo trên Gitlab trước khi commit nhé:
app:
image: registry.gitlab.com/maitrungduc1410/cicd-automation-test:latest
ports:
- "3000:3000"
restart: unless-stopped
env_file: .env
Tiếp theo ta commit lại code và xem kết quả thôi nào:
git add .
git commit -m "add CICD"
git push origin master
Sau đó ta quay lại Gitlab, F5 trình duyệt và sẽ thấy icon như sau tức là CICD đang chạy rồi nhé:
Click vào icon đó chúng ta sẽ thấy chi tiết pipeline như sau:
Các bạn có thể click vào từng job để xem chi tiết realtime quá trình chạy như thế nào nhé. Vì ở đây ta có tới 3 stage, nên sẽ mất một lúc để pipeline hoàn thành đó. Làm ngụm cà phê ☕️☕️ cho tỉnh táo hoặc nhắn tin dỗ dành người yêu đang đòi chia tay cũng được 🤪🤪
Sau khi người yêu hết giận thì các bạn quay lại Gitlab F5 trình duyệt thấy như sau là cuộc đời tươi sáng rồi nhé:
Như các bạn thấy trên hình pipeline của chúng ta đã hoàn thành, 3 jobs đều success trong 4 phút (cũng lâu ấy chứ nhỉ 😁)
Tiếp theo chúng ta check thử xem image đã có ở Registry chưa nhé:
Pằng pằng chíu chíu 🎆🎆. Vậy là ta đã có 2 image 1 image có tag là hash của commit đại diện cho code tại 1 thời điểm và 1 image là latest
đại diện cho code mới nhất của chúng ta. Sau này khi deploy thì ta sẽ dùng tag latest
, và bất kì khi nào có lỗi ta có thể ngay lập tức đổi về 1 commit trước đó khi mà code vẫn chạy ổn định
Registry ta được cấp free, private 10GB storage nên đừng ngần ngại khi lưu nhiều image nhé các bạn 😉, image trước khi push lên registry cũng được nén lại nữa nên cũng không tốn mấy
Giờ đây mỗi khi code xong chúng ta chỉ cần commit, mọi chuyện còn lại hãy để Gitlab lo 💪, việc của chúng ta là chỉ làm sao code cho thiệc là tốt 😘
Coverage test
Tiếp theo mình muốn chia sẻ cho các bạn 1 loại test nữa mà mình rất hay dùng có tên là coverage test
(tạm dịch là kiểm thử độ phủ). Trong test này chúng ta sẽ kiểm tra xem các test case có chạy qua tất cả code của chúng ta: các đoạn if/else, các đoạn try/catch, các đoạn xử lý lỗi,.... mục đích là để đảm bảo ta có hiểu và biết được code có chạy vào chỗ này chỗ kia hay không. Vì nếu có đoạn if mà code chẳng bao giờ chạy vào thì cũng hơi vô nghĩa đúng không nào 😉
Jest cung cấp luôn cho chúng ta option để test độ phủ luôn, tiện lợi không cần phải dùng thêm thư viện nào khác. Ta bắt tay vào làm nhé.
Các bạn sửa lại file package.json
đoạn script test
như sau:
"test": "jest --coverage --forceExit --detectOpenHandles",
Ta chỉ cần thêm option coverage
và khi chạy test thì Jest sẽ đọc file jest.config.js
trong đó mình có 3 dòng cấu hình cho coverage test ở dòng 24,27 và 32 các bạn đọc có gì không hiểu thì search google hoặc comment cho mình biết nha
Lần tới khi chạy test thì Jest sẽ sinh cho chúng ta 1 folder tên là coverage
bên trong có rất nhiều thông tin, có cả file HTML hiển thị giao diện đẹp lun.
Chúng ta thử chạy ở local xem oke không đã nhé. Các bạn tiến hành build lại image:
docker build -t registry.gitlab.com/maitrungduc1410/cicd-automation-test:latest .
Tiếp theo trước khi chạy project, vì lát nữa ta muốn xem folder coverage
ở môi trường ngoài cho tiện nhìn vì thế ta sửa lại 1 chút ở docker-compose.yml
và map volumn cho service app
nhé:
app:
image: registry.gitlab.com/maitrungduc1410/cicd-automation-test:latest
ports:
- "3000:3000"
restart: unless-stopped
env_file: .env
volumes:
- ./coverage:/app/coverage
# chỉ cần map folder coverage là đủ
Sau đó ta tiến hành chạy project lên nhé:
docker compose up -d
Tiếp theo chờ 1 chút (~30s) để MongoDB khởi động hoàn toàn thì ta tiến hành chạy test nhé:
docker compose run -T -e NODE_OPTIONS="--no-experimental-fetch" app npm run test
Ở đây mình phải dùng option
--no-experimental-fetch
nếu không sẽ có lỗi lúc chạy coverage
Như ở trên các bạn thấy Jest show cho chúng ta kết quả khi test độ phủ: độ phủ của statement (if/else), của nhánh, của các functions, độ phủ dựa trên dòng code.
Thử check lại folder coverage
thì ta thấy bên trong có một số file. Các bạn mở trình duyệt, sau đó kéo thả file coverage/lcov-report/index.html
vào và ta cùng xem nhé:
Như các bạn thấy trên hình thì độ phủ của chúng ta cũng khá là cao (hay phải nói là rất cao 😂), vì bây giờ code của chúng ta chưa có gì mấy nên phủ dễ, sau này code nhiều có khi xuống còn 40-50% không biết chừng 😊. Các bạn có thể click vào để xem chi tiết từng file nhé.
Âu cây vậy sau này khi commit hàng trăm hàng nghìn lần, mà muốn check lại độ phủ tại 1 thời điểm nào đó không lẽ phải lưu folder coverage
cho từng commit hay sao 🧐
Điều tuyệt vời là Gitlab đã hỗ trợ chúng ta điều đó 😎 (đấy bạn nào lại không thích Gitlab nữa đi, all in one 😌). Cùng xem thế nào nhé.
Các bạn mở lại file .gitlab-ci.yml
ở job test
các bạn sửa lại như sau nhé:
test:
stage: test
before_script:
- docker compose version
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest
script:
- docker compose up -d
- sleep 15
- docker compose run -T -e NODE_OPTIONS="--no-experimental-fetch" app npm run test
coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/ # Thêm vào dòng này
Ở trên ta thêm vào duy nhất dòng coverage....
, đoạn sau ta truyền vào Regex ý bảo là "sau khi test xong ở kết quả in ra thì ông bắt lấy đoạn như Regex này (All files....) làm kết quả Coverage test cho tôi nhá" .
Sau đó ta tiến hành commit và xem kết quả nhé:
git add .
git commit -m "add Coverage test"
git push origin master
Quay lại Gitlab F5 để check xem CICD đã bắt đầu chạy chưa nhé. Tiếp theo trong thời gian chờ CICD chạy xong thì ta lại dành thời gian tập dăm ba bài gập bụng giảm mỡ hoặc lại quay lại nhắn tin với người yêu dỗ dành gạ kèo cuối tuần đi chơi 😆.
Và khi vừa setup được kèo đi chơi với ngừi eo ta quay lại Gitlab F5 là cũng vừa kịp pipeline chạy xong. Chúng ta mở page Build -> Jobs
để check kết quả nhé:
Như các bạn thấy ở đây ta đã có kết quả là 90.52%
😘😘.
Ngon rồi đó, có kết quả coverage test thì ta show nó cho thuận tiện dễ nhìn hơn chút bằng Badge chứ nhỉ. Các bạn chọn Settings->CICD->General Pipelines, kéo xuống copy Markdown của Pipeline status
và Coverage report
nha:
Các bạn tạo file README.md
paste 2 giá trị các bạn vừa copy vào nhé (bên dưới là kết quả của mình các bạn thay vào cho khớp với của các bạn nhen):
# CICD Automation Test
[![pipeline status](https://gitlab.com/maitrungduc1410/cicd-automation-test/badges/master/pipeline.svg)](https://gitlab.com/maitrungduc1410/cicd-automation-test/-/commits/master)
[![coverage report](https://gitlab.com/maitrungduc1410/cicd-automation-test/badges/master/coverage.svg)](https://gitlab.com/maitrungduc1410/cicd-automation-test/-/commits/master)
Giờ ta tiến hành commit lại và xem kết quả nhé.
Từ...................... dừng......Như ở trên khi test ở local chúng ta có cả folder coverage
với bao nhiêu là loại thông tin khác nữa, bỏ đi thì phí quá, Gitlab CI của chúng ta bây giờ mới chỉ lưu lại mỗi kết quả cuối cùng là con số 90.52%
, nếu như lưu lại được cả folder coverage
để sau này phân tích thì tuyệt vời quá nhỉ 🤔🤔
Thì Gitlab CI cung cấp cho chúng ta 1 option tên là artifacts
(tạm dịch là tài sản
), dùng để lưu lại 1 file/folder nào đó và có thể download được chỉ bằng 1 cú click 😉.
Chúng ta sửa lại file .gitlab-ci.yml
đoạn job test
như sau nhé:
test:
stage: test
before_script:
- docker compose version
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest
script:
- docker compose up -d
- sleep 15
- docker compose run -T -e NODE_OPTIONS="--no-experimental-fetch" app npm run test
coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/ # Thêm vào dòng này
artifacts: # Thêm vào phần này
paths:
- coverage/
Ở trên các bạn có thể thấy là ta thêm vào artifacts
với path là coverage
ý bảo là "giữ lại folder coverage
tôi với nhé 😘". Các bạn có thể xem kĩ hơn về artifacts
ở đây nhé.
Cuối cùng là ta tiến hành commit và chờ xem kết quả nhé:
git add .
git commit -m "add README, artifacts"
git push origin master
Và vẫn như thường lệ, để tranh thủ thì ta tập hít đất giải lao hoặc quay lại nhắn tin xin lỗi người yêu vì nãy giờ mải ngồi code bỏ quên em mà chưa confirm là cuối tuần sẽ đi đâu chơi 😂😂
Và vừa lúc tìm được địa điểm ăn chơi đàn đúm cuối tuần với em yêu thì cũng là lúc pipeline của chúng ta chạy xong. Các bạn lại mở lại page Build->Jobs
và ta cùng xem kết quả nhé:
Như các bạn thấy ở trên, ngoài việc show kết quả test coverage ta còn có thêm 1 nút bên cạnh chính là artifacts
, các bạn có thể bấm Download về và xem nhé.
À quay lại trang chủ của repository xem Badge show hàng coi sao nào:
Pằng pằng chíu chíu 🎉🎉, nom xịn mà chuyên nghiệp phết rồi ý nhờ 😘😘
Những thứ hữu ích bạn nên biết
Chia repository thành nhiều branch
Các bạn có thể nhận thấy là giờ CICD pipeline của chúng ta sẽ được chạy bất kì khi nào ta commit, và tương ứng sẽ sinh ra 2 Docker image: 1 cho commit hiện tại, và 1 cho latest
. Nhưng khi làm thật thì ta thường có nhiều branch (master, dev, staging, test,.....) và với cách setup CICD như hiện tại thì image latest
sẽ liên tục bị các branch ghi đè lên nhau mỗi khi CICD pipeline hoàn tất, trong khi ta muốn latest
chỉ dành cho master để deploy ra production.
Giờ đây ta sửa lại 1 chút file .gitlab-ci.yml
, xoá job release
đi và thay thế đoạn sau vào nhé:
release-tag:
variables:
GIT_STRATEGY: none
stage: release
except:
- master
script:
- docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
release-latest:
variables:
GIT_STRATEGY: none
stage: release
only:
- master
script:
- docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest
- docker push $CI_REGISTRY_IMAGE:latest
Giờ đây ở stage release
ta có 2 jobs:
release-tag
: sẽ chạy ở các branch không phảimaster
do ta cóexcept: master
. Chúng ta sẽ tag image với commit hiện tại thành tên của branch.release-latest
: chạy duy nhất khi push code vàomaster
do ta cóonly: master
.
Ở cả 2 job này ta đề thiết lập GIT_STRATEGY: none
ý bảo không cần clone code vào Gitlab Runner, vì ở đây ta không làm gì liên quan tới source code nữa.
Sau đó ta checkout ra branch mới tên là dev
và thử commit nhé:
git checkout -b dev
git add .
git commit -m "add release tag and latest"
git push origin dev
Quay trở lại Gitlab chuyển qua branch dev
kiểm tra xem CICD pipeline đã chạy hay chưa nhé các bạn. Sau đó thì lại tiếp tục tranh thủ tập squat 30 cái cho một bờ mông săn chắc hoặc quay trở lại nhắn tin với người yêu về lịch đi chơi mùng 2/9 sắp tới nhé.
Và sau khi tìm ra được địa điểm hú hí với bạn gái dịp 2/9 thì ta quay trở lại Gitlab, F5 sau đó mở CICD->Pipelines
sẽ thấy rằng pipeline của chúng ta đã xong nhé:
Tiếp theo ta kiểm tra Deploy -> Container Registry
ta sẽ thấy như sau nhé:
Như các bạn thấy ta đã có image với tag dev
, và image này sẽ đại diện cho code mới nhất trên branch dev
nhé
Tiếp sau đó các bạn quay trở lại code, ta sẽ tiến hành merge dev
vào master
và commit lại nhé:
git checkout master
git merge dev
git push origin master
Sau đó ta lại chờ pipeline xong và xem kết quả nhé, đoạn này các bạn tự sướng nha 😘
Chỉ chạy job khi một số file thay đổi
Đôi khi ta muốn chỉ chạy CICD khi code ở trong folder src
thay đổi hoặc một số file nào đó thay đổi thì mới chạy CICD, trường hợp này ta làm như sau nhé:
build:
stage: build
only:
changes:
- Dockerfile
- src/*
- test/*
- package.json
...
Ở trên ta có job build
chỉ chạy nếu file Dockerfile
hoặc các file trong folder src
, ... thay đổi
Extend job
Trong trường hợp chúng ta có nhiều job mà chúng cùng share cấu hình thì vì lặp đi lặp lại viết cấu hình cho từng job ta có thể định nghĩa 1 job "tổng" chứa các cấu hình chung, rồi tạo các job "con" extend
từ job tổng, ví dụ như sau:
.tests:
stage: test
before_script: # this before script will overwrite the parent's one
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA # pull the image we just push to register
- docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest # tag this image as latest because in our docker-compose file we specify it with tag latest
- cp .env.example .env
start-up-test:
extends: .tests
script:
- docker compose up -d
- sleep 15
- docker compose exec -T app pm2 status
api-test:
extends: .tests
script:
- docker compose up -d
- sleep 15
- mkdir .docker && mkdir .docker/data && mkdir .docker/data/db && mkdir .docker/data/redis
- docker compose exec -T app npm run test
Ở trên các bạn thấy ta có 2 job là start-up-test
và api-test
, cả 2 job này trước khi chạy đều cần docker login
rồi docker pull
... do vậy ta định nghĩ 1 job "tổng" là tests
rồi cho 2 job kia extend từ nó.
Tăng tốc chạy pipeline với cache
Nhiều khi, trong quá trình chạy CICD ta muốn cache lại một số thứ để tái sử dụng cho các jobs khác nhau, ví dụ ta có nhiều jobs cần node_modules
, bình thường ta sẽ cần chạy npm install
cho tất cả các job đó, và thời gian chạy thì như các bạn cũng biết là khá lâu. Do vậy thay vì chạy đi chạy lại ta có thể chạy 1 lần sau đó cache lại cho các jobs sau sử dụng luôn:
# do not use "latest" here, if you want this to work in the future
image: docker:24.0.7
services:
- docker:24.0.7-dind
# cache khai báo ở top-level nên sẽ áp dụng cho tất cả các job trong tất cả stages
cache:
key: ${CI_COMMIT_REF_SLUG} # cache này chỉ áp dụng cho branch hiện tại
paths:
- node_modules/
stages:
- install
- linting
- build
.linting:
stage: linting
image: node:12.18-alpine
# install npm dependencies so it'll be cache in subsequent jobs
# note: we can't do this in linting stage as in that stage, 2 jobs run concurrently and both need node_modules
install_dependencies:
stage: install
image: node:12.18-alpine
script:
- npm install
# this job make sure commit message is conventional
lint-commit-msg:
extends:
- .linting
script:
- echo "$CI_COMMIT_MESSAGE" | npx commitlint
# this job make sure code is linted
lint-code:
extends:
- .linting
script:
- npm run lint
# this job is to build Docker image and push to registry
build:
....LET'S BUILD SOMETHING AMAZING :) .....
Ở trên ta có 1 pipeline, ta có định nghĩa thuộc tính cache
ở top-level
, ở đây ta sẽ cache folder node_modules
để tái sử dụng cho tất cả các jobs trong tất cả stage khi CICD pipeline chạy ở branch hiện tại.
Ta có 3 stage:
- stage
install
chỉ có 1 job làinstall_dependencies
, ở đây ta sẽ chạynpm install
sau đó - stage
linting
có 2 jobs làlint-commit-msg
để check commit message theo chuẩn vàlint-code
để check code có theo chuẩn hay không, vì 2 jobs này đều cần tớinode_modules
để chạy, và nó sẽ lấy từ cache ra. Và ở đây ta không cần chạynpm install
nữa - stage
build
thì không có gì đặc sắc 😁
Note: Để clear cache các bạn mở repository trên Gitlab, vào
CICD->pipelines
và bấm clickClear Runner Caches
Đóng máy
Vậy là đến cuối bài ta đã có 1 pipeline cũng khá là xịn xò rồi nhỉ. Qua đây chúng ta có thể thấy được những điều tuyệt vời ta có thể làm khi áp dụng CICD vào project để tự động hoá những thao tác lặp đi lặp lại, tiết kiệm thời gian, ta chỉ cần commit, mọi thứ còn lại từ lint, test, build release,... Gitlab lo hết 😘. Ví dụ như bên dưới là 1 pipeline trong project thật của mình 😎
Viết xong bài này vã quá mình đi ngủ đây 😪🥱, nếu có gì thắc mắc các bạn để lại comment cho mình nhé. Hẹn gặp lại các bạn ở các bài sau ^^.
Source code bài này các bạn xem ở đây nhé.
All rights reserved