Kubernetes Practice - Triển khai nodejs microservice trên Kubernetes - phần 3 - Xây dựng CI/CD
Bài đăng này đã không được cập nhật trong 2 năm
Giới thiệu
Chào mọi người đến với series practice về kubernetes của mình. Tiếp tục ở bài các phần trước, các bạn nên đọc bài trước để hiểu về hệ thống ta đang triển khai ra sao, phần 1 nói về cách viết config và cách triển khai microservice lên trên Kubernetes, phần 2 nói về ta dùng Argocd để tự động cập nhật application khi template config kubernetes của ta thay đổi. Ở bài này, mình sẽ nói về cách khi developer viết code cho chức năng mới của ứng dụng xong và push nó lên trên git repo, thì ta sẽ xây dựng luồng CI/CD thế nào để cập nhật lại ứng dụng với code mới của developer trên môi trường kubernetes.
Ở bài này mình sẽ sử dụng repository là gitlab và chạy CI/CD với gitlab CI. Để làm được bài này thì các bạn cần tạo một repo trên gitlab và đẩy code lên đó nhé, các bạn đặt tên repo là gì cũng được. Các bạn đẩy code nằm ở trong folder code của repo này https://github.com/hoalongnatsu/microservices.git lên repo gitlab của các bạn nha. Sau khi xong hết ta bắt tay vào làm.
Gitlab Runner
Gitlab cung cấp cho ta khá nhiều cách để chạy CI/CD, mình sẽ nói tới phần đơn giản nhất trước, là ta sử dụng Gitlab Runner. Gitlab Runner là một application mà ta sẽ cài ở trên server ta muốn nó chạy CI/CD. Để install gitlab runner, các bạn ssh tới server mà mình muốn chạy CI/CD trên đó, và cài runner như sau, mình sẽ hướng dẫn cài ở trên server linux.
Install gitlab-runner
Download the binary for your system
Chọn OS phù hợp với server của các bạn nhé.
# Linux x86-64
sudo curl -L --output /usr/local/bin/gitlab-runner "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64"
# Linux x86
sudo curl -L --output /usr/local/bin/gitlab-runner "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-386"
# Linux arm
sudo curl -L --output /usr/local/bin/gitlab-runner "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm"
# Linux arm64
sudo curl -L --output /usr/local/bin/gitlab-runner "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm64"
# Linux s390x
sudo curl -L --output /usr/local/bin/gitlab-runner "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-s390x"
# Linux ppc64le
sudo curl -L --output /usr/local/bin/gitlab-runner "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-ppc64le"
Give it permissions
sudo chmod +x /usr/local/bin/gitlab-runner
Create a GitLab CI user
sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
Install and run as service
sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
sudo gitlab-runner start
Nếu bạn gặp lỗi sudo: gitlab-runner: command not found
thì mở /etc/sudoers lên và thêm vào ở Defaults secure_path
với dường dẫn /usr/local/bin
nữa, còn không thì bạn install gitlab-runner ở dường dẫn /usr/bin
cũng được.
Để install trên các môi trường khác thì các bạn xem thêm ở đây https://docs.gitlab.com/runner/install/.
Sau khi install xong thì bạn có thể dùng câu lệnh này để kiểm tra:
$ sudo gitlab-runner list
Runtime platform arch=amd64 os=linux pid=2850 revision=de104fcd version=14.5.1
Listing configured runners ConfigFile=/etc/gitlab-runner/config.toml
Câu lệnh này là để ta list toàn bộ runner hiện tại trên server, bây giờ thì tất niên khi ta list ra thì sẽ không có runner nào 😁. Giờ thì ta sẽ đăng ký một con runner để nó chạy CI/CD cho repo của ta ở trên gitlab. Bạn mở gitlab lên, ở chỗ repo của mình chọn mục Settings -> CI/CD.
Sau đó bạn mở mục Runners ra.
Sau khi mở ra, bạn sẽ thấy hai trường mà ta sẽ dùng nó để register runner cho repo của ta là gitlab url và token.
Register runner
Giờ thì ta sẽ register một con runner cho repo của ta lên trên server, các bạn làm theo các bước như sau, đầu tiên ta chạy câu lệnh register.
sudo gitlab-runner register
Sau khi run thì nó sẽ bắt bạn nhập gitlab url vào, nhập giá trị url ở phía trên vào. Tiếp đó nó sẽ bắt bạn nhập token, bạn nhập token ở trên vào.
Tiếp theo là nó bắt bạn nhập description, chỗ này thì bạn nhập gì cũng được. Tiếp theo là phần tags, phần này thì quan trọng, lúc chạy CI thì runner sẽ được chạy dựa vào tags, bạn nhập ở đây là microservice nhé.
Bây giờ thì sẽ tới bước ta chọn runner của ta sẽ được thực thi ra sao. Nó sẽ chạy thẳng trên server, hay là mỗi lần chạy nó sẽ tạo một container ra và chạy job CI/CD trên container đó. Chọn shell nếu các bạn muốn runner chạy thẳng trên môi trường server, chọn docker nếu bạn muốn runner chạy trong môi trường docker. Ở đây thì mình sẽ chọn docker, các bạn nên chọn theo mình để tránh bị lỗi nha.
Tiếp theo nó sẽ hỏi default Docker image thì bạn nhập vào là docker:stable
, tới đây thì ta đã register runner cho repo của ta thành công, để kiểm tra các runner ta đã đăng ký thì ta chạy câu lệnh list ở trên, giờ thì bạn sẽ thấy được runner mà ta vừa đăng ký.
Runtime platform arch=amd64 os=linux pid=2969 revision=de104fcd version=14.5.1
Listing configured runners ConfigFile=/etc/gitlab-runner/config.toml
microservice Executor=docker Token=zC8B6PZzDouA46mgGry6 URL=https://gitlab.kala.ai/
Kiểm tra trên gitlab bạn sẽ thấy nó hiển thị con runner của ta.
Oke vậy là ta đã xong bước register runner. Và thay vì phải nhập từng bước khá lằng nhằng như trên thì bạn có thể register nhanh một con runner bằng câu lệnh sau:
sudo gitlab-runner register -n \
--url https://gitlab.com/ \
--registration-token REGISTRATION_TOKEN \
--executor docker \
--description "microservice" \
--docker-image "docker:stable" \
--docker-privileged
Giờ thì ta chỉ cần viết file CI/CD ở trong repo của chúng ta, và mỗi lần ta đẩy code lên thì file CI/CD này sẽ được con runner thực thi. Lưu ý là vì ta chọn docker nên bắt buộc trên server chạy CI/CD của ta phải có cài docker rồi thì runner mới thực thi được. Tiếp theo ta sẽ bắt tay vào làm luồng CI.
Continuous Integration
Đầu tiên là ta sẽ làm bước mà integrate code mới của developer với image mà dùng để chạy ứng dụng của ta trước, sau đó ta mới làm bước deploy. Ở bước integrate này thì ta sẽ viết file CI, công việc của nó sẽ là build image tương ứng với code của branch hiện tại, sau đó sẽ push image đó lên docker registery (nơi ta chứa image).
Gitlab có hỗ trợ cho ta image registry, nên ta sẽ sử dụng nó luôn cho tiện. Để viết CI/CD cho gitlab thì ta sẽ viết trong file .gitlab-ci.yml
, ở folder mà ta chứa code của repo, tạo một file .gitlab-ci.yml với config như sau:
stages:
- build
build root image:
stage: build
tags:
- microservice
script:
- docker build . -t $CI_REGISTRY_IMAGE:latest -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE:latest
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
only:
- main
Trường stages là ta sẽ định nghĩa những pipe trong một job của ta, hiện tại thì ta chỉ có một pipe là build thôi. Ở trong file config trên ta sẽ có biến là CI_REGISTRY_IMAGE, đây là một trong những biến mặc định của gitlab CI, biến CI_REGISTRY_IMAGE sẽ là tên image của chúng ta. Ở trên gitlab repo, bạn bấm sang phần Package & Registries -> Container Registry, thì bạn sẽ thấy được tên image của chúng ta. Khi ta push image thì ta sẽ push lên chỗ Container Registry này. Trường script là trường ta sẽ chỉ định những công việc ta sẽ làm, ở file trên đơn giản là ta sẽ build image và push image với code mới nhất lên.
Giờ ta commit và push code lên, ta sẽ thấy CI/CD của ta sẽ được thực thi.
Bạn nhấn vào chữ running thì nó sẽ dẫn bạn vào trong.
Sau đó bạn nhấn vào chỗ build thì nó sẽ dẫn ta qua chỗ coi log.
Runner của ta sẽ chạy và nó tự động pull code của chúng ta xuống container đang chạy job, sau đó nó sẽ thực thi build image.
Sau khi image build xong thì nó sẽ push lên gitlab registry, và ở đây bạn sẽ thấy nó có lỗi là denied: access forbidden
.
Lý do là do ta chưa login tới gitlab registry để docker có thể push image lên được. Để login vào gitlab registry, ta làm như sau.
Login gitlab registry
Ta chọn phần Settings -> Repository, chọn mục Deploy tokens
Ở phần Deploy tokens, bạn nhập trường name và username là microservice (này bạn muốn nhập gì cũng được).
Chỗ scope thì bạn tạm thời chứ chọn hết. Sau đó ta nhấn tạo.
Sau khi nhấn tạo xong thì nó sẽ hiện ra cho bạn hai trường là username và password, nhớ lưu lại nha.
Sau khi có deploy token rồi, bạn chọn mục Settings -> CI/CD, mục Variables. Tạo một env là REGISTRY_PASSWORD để lưu giá trị password của deploy token.
Ok, sau khi tạo xong thì bạn update lại file .gitlab-ci.yml như sau:
stages:
- build
build root image:
stage: build
tags:
- microservice
before_script:
- echo "$REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u microservice --password-stdin
script:
- docker build . -t $CI_REGISTRY_IMAGE:latest -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE:latest
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
only:
- main
Ta sẽ thêm trường before_script để chạy câu lệnh login vào gitlab registry. Biến CI_REGISTRY là biến mặc định của gitlab CI, biến REGISTRY_PASSWORD là ta vừa mới tạo. Cập nhật xong thì ta commit code và push nó lên lại. Lúc này bạn sẽ thấy là ta đã build được image và push nó lên thành công.
Nhưng bạn sẽ để ý là vì ta chạy build image trong một container, nên khi ta build nó không có lưu cache, khiến việc ta build image bị lâu hơn, để khác phục việc này thì trước khi build ta sẽ pull image đã build xuống trước, để nó có layer cache, và khi ta build ta sẽ chỉ định thêm option là --cache-from $CI_REGISTRY_IMAGE:latest
. Cập nhật lại file .gitlab-ci.yml.
stages:
- build
build root image:
stage: build
tags:
- microservice
before_script:
- echo "$REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u microservice --password-stdin
script:
- docker pull $CI_REGISTRY_IMAGE:latest || true
- docker build . --cache-from $CI_REGISTRY_IMAGE:latest -t $CI_REGISTRY_IMAGE:latest -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE:latest
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
only:
- main
Commit code và push lên, lúc này thì image của ta sẽ được build với tốc độ nhanh hơn.
Cập nhật lại file config của kubernetes
Tiếp theo ta sẽ cập nhật lại file config Deployment với image mà ta mới build. Để coi tên image, bạn chọn mục copy ở phần Container Registry.
Các file Deployment ở folder k8s của repo này https://github.com/hoalongnatsu/microservices.git, như api-gateway-deployment.yaml, categories-news-deployment.yaml bạn sửa lại thành tên image của gitlab
# api-gateway-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-gateway
labels:
component: api-gateway
spec:
revisionHistoryLimit: 1
selector:
matchLabels:
component: api-gateway
template:
metadata:
labels:
component: api-gateway
spec:
containers:
- name: api-gateway
image: registry.kala.ai/microservice/code
ports:
- name: http
containerPort: 3000
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
env:
- name: SERVICES
value: api
- name: PORT
value: "3000"
envFrom:
- configMapRef:
name: microservice-cm
# categories-news-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: categories-service
labels:
component: categories-service
spec:
revisionHistoryLimit: 1
selector:
matchLabels:
component: categories-service
template:
metadata:
labels:
component: categories-service
spec:
containers:
- name: categories-service
image: registry.kala.ai/microservice/code
env:
- name: SERVICES
value: categories
envFrom:
- configMapRef:
name: microservice-cm
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: news-service
labels:
component: news-service
spec:
revisionHistoryLimit: 1
selector:
matchLabels:
component: news-service
template:
metadata:
labels:
component: news-service
spec:
containers:
- name: news-service
image: registry.kala.ai/microservice/code
env:
- name: SERVICES
value: news
envFrom:
- configMapRef:
name: microservice-cm
Image của mình thì mình sửa lại là registry.kala.ai/microservice/code
. Sau đó nếu bạn làm như theo bài trước ở phần 2, thì bạn chỉ cần push config lên repo chứa template config để Argocd nó tự động cập nhật lại. Còn không thì bạn chạy câu lệnh sau ở folder k8s.
$ kubectl apply -f k8s --recursive
deployment.apps/api-gateway created
deployment.apps/categories-service created
deployment.apps/news-service created
configmap/microservice-cm created
deployment.apps/nats created
service/nats created
service/postgres created
statefulset.apps/postgres created
deployment.apps/redis created
service/redis created
Sau khi ta chạy câu lệnh trên thì ta đã deploy ứng dụng của ta lên kubernetes với image là image mà ta mới build và push trên gitlab. Giờ ta sẽ get pod ra để xem thử.
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
api-gateway-84677bf776-jj8x4 0/1 ErrImagePull 0 86s
categories-service-867f848c77-l2c4m 0/1 ImagePullBackOff 0 86s
nats-65687968fc-rbtd4 1/1 Running 0 86s
news-service-686b8557c8-ch2v5 0/1 ImagePullBackOff 0 86s
postgres-0 1/1 Running 0 86s
redis-58c4799ccc-xn9zk 1/1 Running 0 86s
Nhưng khi bạn get pod ra, bạn sẽ thấy những Deployment mà ta chỉ định dùng image của gitlab thì nó sẽ bị lỗi là ErrImagePull. Bạn describe nó để xem lý do thì sẽ thấy là do những node mà pod được deploy tới không có quyền để pull image từ gitlab của ta xuống.
$ kubectl describe pod api-gateway-84677bf776-jj8x4
...
Type Reason Age From Message
---- ------ ---- ---- -------
...
Normal Pulling 61s (x4 over 2m48s) kubelet Pulling image "registry.kala.ai/microservice/code"
Warning Failed 61s (x4 over 2m37s) kubelet Failed to pull image "registry.kala.ai/microservice/code": rpc error: code = Unknown desc = Error response from daemon: pull access denied for registry.kala.ai/microservice/code, repository does not exist or may require 'docker login': denied: requested access to the resource is denied
Warning Failed 61s (x4 over 2m37s) kubelet Error: ErrImagePull
Warning Failed 36s (x6 over 2m37s) kubelet Error: ImagePullBackOff
Normal BackOff 25s (x7 over 2m37s) kubelet Back-off pulling image "registry.kala.ai/microservice/code"
Để kubernetes có thể pull được image từ gitlab, ta cần tạo một Secret với loại là docker-registry rồi chỉ định vào trường imagePullSecrets khi khai báo config cho Pod. Ta tạo Secret bằng câu lệnh sau.
$ kubectl create secret docker-registry microservice-registry --docker-server=https://registry.kala.ai --docker-username=microservice --docker-password=8Su2-9MnqT7Qt4uNyfqz
secret/microservice-registry created
Với --docker-server là tên gitlab server của bạn, --docker-username là username của deploy token ta tạo khi nãy, và --docker-password là password của deploy token. Cập nhật lại file Deployment thêm vào trường imagePullSecrets với Secret ta vừa tạo.
# api-gateway-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-gateway
labels:
component: api-gateway
spec:
...
spec:
imagePullSecrets:
- name: microservice-registry
containers:
- name: api-gateway
image: registry.kala.ai/microservice/code
ports:
- name: http
containerPort: 3000
protocol: TCP
...
# categories-news-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: categories-service
labels:
component: categories-service
spec:
...
spec:
imagePullSecrets:
- name: microservice-registry
containers:
- name: categories-service
image: registry.kala.ai/microservice/code
...
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: news-service
labels:
component: news-service
spec:
...
spec:
imagePullSecrets:
- name: microservice-registry
containers:
- name: news-service
...
$ kubectl apply -f k8s --recursive
deployment.apps/api-gateway configured
deployment.apps/categories-service configured
...
statefulset.apps/postgres configured
...
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
api-gateway-6c9f8f68b7-mj6r9 1/1 Running 0 40s
categories-service-84db8d885b-g85md 1/1 Running 0 40s
nats-65687968fc-rbtd4 1/1 Running 0 19m
news-service-76d559db69-vqg59 1/1 Running 0 40s
postgres-0 1/1 Running 0 19m
redis-58c4799ccc-xn9zk 1/1 Running 0 19m
Vậy là ta đã hoàn thành xong bước CI, tiếp theo ta sẽ xem cách làm CD.
Continuous deployment
Bước này thì ta cũng sẽ có nhiều cách, mình sẽ nói về cách dễ nhất trước là ta sẽ ssh lên kubernetes master và thực hiện câu lệnh kubectl, các cách khác mình sẽ nói ở bài sau. Cập nhật lại file .gitlab-ci.yml như sau:
stages:
- build
- deploy
build root image:
stage: build
tags:
- microservice
script:
- docker build . -t $CI_REGISTRY_IMAGE:latest -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE:latest
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
only:
- main
deploy stage:
stage: deploy
tags:
- microservice
before_script:
- mkdir ~/.ssh
- echo -e "${SERVER_KEY_PEM//_/\\n}" > ~/.ssh/key.pem
- apt-get -y update && apt-get -y install openssh-client rsync grsync
- ssh-keyscan -H $SERVER_IP >> ~/.ssh/known_hosts
- chmod 400 ~/.ssh/rnd-ecommerce.pem
script:
- |
sudo ssh -i ~/.ssh/key.pem $SERVER_USER@$SERVER_IP << EOF
kubectl set image deployment/api-gateway api-gateway=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
kubectl set image deployment/news-service news-service=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
kubectl set image deployment/categories-service categories-service=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
exit
EOF
only:
- main
Ta sẽ thêm một stages nữa là deploy. Để ssh được tới server thì tùy cách bạn dùng là password hoặc key pem thì sẽ khác nhau. Của mình sẽ dùng cách key pem. Bạn vào phần Settings -> CI/CD -> Variables khi nãy, khai báo thêm các biến là SERVER_KEY_PEM, SERVER_USER, SERVER_IP tương ứng với server mà đang chạy kubernetes master. Khi ta build image và push lên gitlab xong, job deploy sẽ được chạy, nó sẽ ssh tới kubernetes master và cập nhật lại Deployment với image là image là mới build xong. Vì ở đây mình làm demo nên mình gộp 3 câu cập nhật Deployment lại trong một repo, còn thực tế các bạn nên làm mỗi repo một Deployment khác nhau nhé.
Commit và push code lên, lúc này bạn sẽ thấy pipe của ta sẽ có 2 job là build với deploy.
Vào xem log phần deploy.
Ok, vậy là luồng CD của ta đã chạy thành công 😁.
Kết luận
Vậy là ta đã tìm hiểu xong cách xây dựng luồn CI/CD với kubernetes, khi ta xây dựng CI/CD thì công việc deploy của ta trở nên dễ dàng hơn nhiều. Nếu các bạn có thắc mắc hoặc chưa hiểu rõ chỗ nào, các bạn có thể hỏi ở phần comment. Ở phần tiếp theo mình sẽ nói về cách integrate gitlab CI với thẳng kubernetes, không cần phải chạy gitlab runner, và cách xài RBAC để thiết lập permission cho từng job với namespace cụ thể.
All rights reserved