Setup Canary release trên Kubernetes với Nginx Ingress và tích hợp với Github Actions
Hello các bạn lại là mình đây 👋👋
Chúc các bạn đọc của mình cuối tuần vui vẻ mát mẻ 😁, kết thúc một tuần làm việc thì nay cuối tuần ta lại có dịp được ngồi với nhau rồi đây
Tiếp tục quay trở lại với series Học Kubernetes từ cơ bản đến "gần" nâng cao. Ở bài này ta sẽ tìm hiểu cách setup Canary release trên Kubernetes với Nginx Ingress, cùng với đó là tích hợp vào CICD như cách mà các dự án thực tế hay làm nhé.
Mặc áo phao rồi lên thuyền với mình nàoooooo 🛥️🛥️
Canary release là gì?
Theo cách hiểu của mình thì đây là cách mà ta triển khai ra các tính năng mới cho một nhóm người dùng thử nghiệm ban đầu, kiểu thay vì dùng 100% traffic thì đầu tiên ta rollout
(tung ra) feature mới cho 10% users, collect feedback, monitor xem có lỗi không? oke thì tiếp tục cho 20%, rồi 50%, rồi 100%
Kiểu này mình thấy cực kì hợp với những product customer facing (user thật sử dụng), vì sẽ rất nguy hiểm nếu ta đẩy 100% ra ngay lập tức, chẳng may nếu có lỗi thì toàn bộ user sẽ bị ảnh hưởng. Nếu có vấn đề trong quá trình canary release thì ta đơn giản là stop và rollback về phiên bản stable đang chạy, giảm thiểu tối đa ảnh hưởng tới các users sẵn có.
Thuật ngữ "canary release" (hay "phát hành theo kiểu chim hoàng yến") xuất phát từ cách các thợ mỏ ở châu Âu trước đây dùng chim hoàng yến để cảnh báo khí độc trong hầm mỏ. Họ mang theo chim hoàng yến khi xuống hầm, vì chim rất nhạy cảm với khí độc như carbon monoxide. Nếu chim có dấu hiệu khó thở hoặc chết, đó là cảnh báo cho thợ mỏ về môi trường nguy hiểm, giúp họ có thể thoát ra kịp thời. (nghe có câu chuyện phết ý nhỉ 😂😂)
Giờ ta zô món chính của ngày hôm nay nhé 💪💪
Lấy K8S session
Như thường lệ ở các bài trước, để thực hành thì ta cần lấy session truy cập Kubernetes cluster của mình ở đây nhé: https://learnk8s.jamesisme.com/
Nhớ tick vào Require Domain
để tí nữa ta dùng với Ingress
nha 😉
Tạo canary release với Nginx ingress
Setup
Để setup canary release trên Kubernetes thì có nhiều giải pháp, nhưng tiện và đơn giản nhất mình thấy có nginx ingress, vì thường ta cũng đã cài sẵn vào cluster rồi, như ta vẫn làm từ đầu series tới giờ vậy. Do đó ta cũng dùng luôn nginx nhé. 👍️
Đầu tiên các bạn tạo cho mình folder k8s-canary-release
và đưa file kubernetes-config
vào đó nhé, ở bài này ta sẽ thao tác hoàn toàn ở folder này.
Để bắt đầu, ta tạo file deployment.yml
với nội dung như sau:
apiVersion: v1
kind: Service
metadata:
name: myapp-svc
labels:
app: myapp
spec:
type: ClusterIP
ports:
- port: 80
name: http
targetPort: http
selector:
app: myapp
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
labels:
app: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: maitrungduc1410/sample-node:latest
ports:
- containerPort: 3000
name: http
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
Ở trên mình để luôn Service và Deployment chung 1 file luôn. Bên trong thì mình có những thứ rất cơ bản mà mình đã giải thích xuyên suốt series này thôi 😁😁. 1 Service + 1 Deployment có 1 replica
Sau đó ta apply
nhé:
kubectl apply -f deployment.yml --kubeconfig=./kubernetes-config
>>>
service/myapp-svc created
deployment.apps/myapp created
Sau đó ta get po
và get svc
kiểm tra xem oke chưa nha:
kubectl get po --kubeconfig=./kubernetes-config
>>>
NAME READY STATUS RESTARTS AGE
myapp-6866c5b586-q8cvq 1/1 Running 0 5s
kubectl get svc --kubeconfig=./kubernetes-config
>>>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
myapp-svc ClusterIP 10.245.17.248 <none> 80/TCP 7s
Tiếp đó ta tạo file ingress.yml
:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp
spec:
ingressClassName: "nginx"
rules:
- host: 6b4407.learnk8s.jamesisme.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-svc
port:
name: http
Ở đây ta có 1 ingress trỏ vào myapp-svc
mà ta vừa tạo ở trên, nếu các bạn chưa rõ Ingress là gì thì xem lại bài Bảo mật Nginx Ingress với Cert Manager trên Kubernetes của mình nha.
Nhớ thay tên domain của các bạn vào host
cho đúng nhé.
Sau đó ta apply ingress:
kubectl apply -f ingress.yml --kubeconfig=./kubernetes-config
>>>
ingress.networking.k8s.io/myapp created
Và ta get ing
để kiểm tra trạng thái của ingress:
kubectl get ing --kubeconfig=./kubernetes-config
>>>
NAME CLASS HOSTS ADDRESS PORTS AGE
myapp nginx 6b4407.learnk8s.jamesisme.com 80 12s
Hiện như trên là oke rồi đoá 🤩🤩
Giờ ta thử truy cập từ trình duyệt từ địa chỉ http://6b4407.learnk8s.jamesisme.com
xem như thế nào nhé, để đảm bảo là trình duyệt không tự redirect HTTP->HTTPS (do settings của các bạn), thì ta mở tab ẩn danh nhé:
Nhớ thay tên domain của các bạn vào cho đúng nha
Thấy như này là oke rồi nà, Hostname
ở đây chính là tên pod của chúng ta
À hiện tại Chrome có settings mới là sẽ cảnh báo nếu ta muốn truy cập trang web không có HTTPS (kể cả mở từ ẩn danh):
Ta cứ bấm Continue to site
là được nhé
Oke xong bước setup rồi giờ ta mới zô món chính của chính nè 🤣🤣
Tạo canary release
Lý do mình nói setup canary release với Nginx ingress rất tiện vì họ cho phép ta cấu hình qua annotation, rất dễ dàng: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#canary
Về cơ bản là ta sẽ dùng:
nginx.ingress.kubernetes.io/canary
: set bằngtrue
để mark cái Ingress này là canarynginx.ingress.kubernetes.io/canary-weight
: trọng số, để Nginx tính tỉ lệ bao nhiêu phần trăm request sẽ ăn vào ingress rule này, mặc định tính theo thang 100 (0 -> 100), ta có thể lấy theo thang 10, 1000,... tuỳ ý
chú ý rằng "phần trăm" ở đây nó là xác suất ngẫu nhiên, chứ không phải chắc chắn cứ 100 thì có 10 hay 20, có thể hơi chênh lệch chút, ví dụ
weight=20
thì số request thực tế là 18 hay 22. Nhưng số request càng nhiều thì cái tỉ lệ nó sẽ càng tiệm cậnweigth
Âu cây, giờ ta tạo deployment-canary.yml
với nội dung như sau:
apiVersion: v1
kind: Service
metadata:
name: myapp-svc-canary
labels:
app: myapp-canary
spec:
type: ClusterIP
ports:
- port: 80
name: http
targetPort: http
selector:
app: myapp-canary
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-canary
labels:
app: myapp-canary
spec:
replicas: 1
selector:
matchLabels:
app: myapp-canary
template:
metadata:
labels:
app: myapp-canary
spec:
containers:
- name: myapp
image: maitrungduc1410/sample-node:v1
ports:
- containerPort: 3000
name: http
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
Ở trên mình copy từ deployment.yml
xong sửa name/label
, và đổi tag image
thành V1
(để tí nữa ta test cho dễ nhìn)
Sau đó ta apply:
kubectl apply -f deployment-canary.yml --kubeconfig=./kubernetes-config
>>>
service/myapp-svc-canary created
deployment.apps/myapp-canary created
Sau đó ta get pod và service kiểm tra đảm bảo mọi thứ oke:
kubectl get po --kubeconfig=./kubernetes-config
>>>
NAME READY STATUS RESTARTS AGE
myapp-6866c5b586-q8cvq 1/1 Running 0 23m
myapp-canary-7fbfc575cf-4fr65 1/1 Running 0 12s
kubectl get svc --kubeconfig=./kubernetes-config
>>>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
myapp-svc ClusterIP 10.245.17.248 <none> 80/TCP 23m
myapp-svc-canary ClusterIP 10.245.139.247 <none> 80/TCP 15s
Tiếp đó, ta tạo 1 ingress mới ingress-canary.yml
:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-canary
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "30"
spec:
ingressClassName: "nginx"
rules:
- host: 70a8d3.learnk8s.jamesisme.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-svc-canary
port:
name: http
Ta chú ý rằng cái host
là ta phải dùng y hệt, bên cạnh đó ta có thêm 2 annotations
, 1 để mark cái ingress rule này là canary
, cái còn lại ý bảo là "30% requests hãy route vào ingress rule này"
Chú ý rằng nếu ta không có cái annotations kia, tức là ta có 2 ingress rules y hệt nhau, chung host
, khác mỗi name
thì khi apply sẽ báo lỗi như sau:
Error from server (BadRequest): error when creating "ingress-canary.yml": admission webhook "validate.nginx.ingress.kubernetes.io" denied the request: host "6b4407.learnk8s.jamesisme.com" and path "/" is already defined in ingress lk8s-6b4407/myapp
Oke ròi giờ ta apply nhé:
ka -f ingress-canary.yml --kubeconfig=./kubernetes-config
>>>
ingress.networking.k8s.io/myapp-canary created
Sau đó ta get ingress
:
kubectl get ing --kubeconfig=./kubernetes-config
>>>
NAME CLASS HOSTS ADDRESS PORTS AGE
myapp nginx 6b4407.learnk8s.jamesisme.com k8s.jamesisme.com 80 27m
myapp-canary nginx 6b4407.learnk8s.jamesisme.com k8s.jamesisme.com 80 37s
Giờ nếu ta quay lại trình duyệt và F5 liên tục sẽ thấy request sẽ chuyển giữa bản chính và bản V1:
Để demo rõ hơn thì ta mở terminal và chạy script sau:
for i in $(seq 1 10); do curl -s 6b4407.learnk8s.jamesisme.com | grep "Hostname"; done
>>>
Hostname: myapp-6866c5b586-q8cvq
Hostname: myapp-6866c5b586-q8cvq
Hostname: myapp-6866c5b586-q8cvq
Hostname: myapp-6866c5b586-q8cvq
Hostname: myapp-6866c5b586-q8cvq
Hostname: myapp-6866c5b586-q8cvq
Hostname: myapp-6866c5b586-q8cvq
Hostname: myapp-6866c5b586-q8cvq
Hostname: myapp-6866c5b586-q8cvq
Hostname: myapp-canary-7fbfc575cf-4fr65, V1
Thay tên domain của các bạn vào cho đúng nhé
Ta chạy command đó vài lần sẽ thấy rằng xác suất request đi vào canary release khoảng 30% như ta đã khai báo ở ingress rule. Kể ra setup cũng dễ ý nhờ 😎😎
Khi làm thực tế thì ta sẽ chạy canary release và set canary-weight
tăng dần, mỗi lần chạy trong một khoảng thời gian "đủ" để ta theo dõi (monitor) các chỉ số (metrics) và xác định là "ok có thể tăng thêm traffic". Oke rồi thì ta có thể apply
lại ingress và tăng canary-weight
, khi nào tới 100% thì ta chỉ cần delete cái ingress myapp-canary
là nó sẽ quay về sử dụng toàn bộ cái ingress myapp
ban đầu ta tạo
Nhưng đương nhiên ta không muốn làm bước tăng traffic kia một cách thủ công rồi, ví dụ kiểu anh em DevOps sẽ setup trước hết, và để cho anh em dev chỉ zô bấm cái nút là được thôi. Giờ ta tới bước tiếp theo là tích hợp Canary release vào Github Actions để tự động hoá quy trình nhé. Múccccccc 💪💪💪
Tích hợp với Github Actions
Ở bước này ta sẽ làm những thứ sau:
- Tạo CICD để thực hiện canary release (10%, 50%, 100%)
- Nếu fail (reject) thì thực hiện rollback. Nếu success thì full release
Setup Github Actions
Đầu tiên ta tạo 1 repo mới tên là viblo-k8s-canary-release
, ta nhớ tick chọn Add a README file
nhé:
Thực tế là ta chỉ cần tạo 1 file bất kì có sẵn để lát ta mở nó từ Web IDE được thôi, chứ ta cũng không động vào file README này 😁
Tiếp theo chúng ta vào Settings > Actions secrets and variables > New repository secret
để tạo biến môi trường lưu file kubeconfig để tí nữa bên trong CICD nó còn có permission để apply
trên Kubernetes:
Tên biến ta để là KUBECONFIG
, nội dung thì ta copy y hệt từ file kubernetes-config
của các bạn từ local:
Sau đó ta cần tạo environment
cho 10%, 50%, 100%
và full release (production
)
Lí do ta cần dùng tới
environment
là vì Github chỉ support Approval cho các job cóenvironment
. Approval là kiểu job sẽ Pending và chờ cho có người approve thì mới thực hiện, vì ở project thực tế thường là ta sẽ chạy canary release và monitor 1 khoảng thời gian sau đó người release sẽ bấm Approve để tiếp tục
Ta sẽ tạo các environment
sau:
- canary-10
- canary-50
- canary-100: 100% đã vào canary release, nhưng chưa clean up resource
- production: clean up resource, full release
Chú ý rằng khi tạo environment thì ta cần tick chọn Required reviewers
, sau đó nhập vào username của reviewer, ở đây mình nhập username của mình luôn, các bạn nên nhập của các bạn. Xong thì ta bấm Save Protection Rules
, các option còn lại để mặc định:
Sau khi tạo environment
xong thì ta có như sau:
Sau đó ta quay trở lại trang chính của repo và các bạn bấm dấu .
(chấm) để mở Web IDE:
Tiếp theo ta lần lượt tạo các file manifest y như ta làm ở local:
nhớ thay tên domain của các bạn vào cho đúng nhé
Có 1 điểm chú ý là với ingress-canary
ta sẽ không set weight
cho nó, mà ta sẽ set ở phía CICD lát nữa:
Nếu ta không set
weigth
thìingress-canary.yml
rule đó sẽ không có tác dụng mà nó sẽ dùngingress.yml
Sau đó ta tạo folder .github
, trong đó tạo tiếp folder workflows
, bên trong ta tạo file deploy.yml
:
name: "Kubernetes Canary Release with Rollback"
on:
workflow_dispatch: # Manually triggered for flexibility
jobs:
canary-deployment:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Kubeconfig
run: |
echo "${{ secrets.KUBECONFIG }}" > ./kubernetes-config
- name: Setup kubectl
uses: azure/setup-kubectl@v4
- name: Clean up if needed
run: |
kubectl delete -f deployment-canary.yml --kubeconfig=./kubernetes-config
kubectl delete -f ingress-canary.yml --kubeconfig=./kubernetes-config
- name: Deploy Stable to Kubernetes
run: |
kubectl apply -f deployment.yml --kubeconfig=./kubernetes-config
kubectl apply -f ingress.yml --kubeconfig=./kubernetes-config
- name: Deploy Canary to Kubernetes
run: |
kubectl apply -f deployment-canary.yml --kubeconfig=./kubernetes-config
kubectl apply -f ingress-canary.yml --kubeconfig=./kubernetes-config
canary-10-percent:
runs-on: ubuntu-latest
environment:
name: canary-10
url: https://6b4407.learnk8s.jamesisme.com # Your canary environment URL
needs: canary-deployment
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Kubeconfig
run: |
echo "${{ secrets.KUBECONFIG }}" > ./kubernetes-config
- name: Setup kubectl
uses: azure/setup-kubectl@v4
- name: Set Canary to 10% Traffic
run: |
kubectl annotate ingress myapp-canary nginx.ingress.kubernetes.io/canary-weight="10" --overwrite --kubeconfig=./kubernetes-config
canary-50-percent:
runs-on: ubuntu-latest
environment:
name: canary-50
needs: canary-10-percent
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Kubeconfig
run: |
echo "${{ secrets.KUBECONFIG }}" > ./kubernetes-config
- name: Setup kubectl
uses: azure/setup-kubectl@v4
- name: Set Canary to 50% Traffic
run: |
kubectl annotate ingress myapp-canary nginx.ingress.kubernetes.io/canary-weight="50" --overwrite --kubeconfig=./kubernetes-config
canary-100-percent:
runs-on: ubuntu-latest
environment:
name: canary-100
needs: canary-50-percent
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Kubeconfig
run: |
echo "${{ secrets.KUBECONFIG }}" > ./kubernetes-config
- name: Setup kubectl
uses: azure/setup-kubectl@v4
- name: Set Canary to 100% Traffic
run: |
kubectl annotate ingress myapp-canary nginx.ingress.kubernetes.io/canary-weight="100" --overwrite --kubeconfig=./kubernetes-config
finalize-deployment:
runs-on: ubuntu-latest
needs: canary-100-percent
environment: production
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Kubeconfig
run: |
echo "${{ secrets.KUBECONFIG }}" > ./kubernetes-config
- name: Setup kubectl
uses: azure/setup-kubectl@v4
- name: Complete Deployment by removing previous deployment
run: |
kubectl delete -f deployment.yml --kubeconfig=./kubernetes-config
kubectl delete -f ingress.yml --kubeconfig=./kubernetes-config
kubectl annotate ingress myapp-canary nginx.ingress.kubernetes.io/canary- --kubeconfig=./kubernetes-config
kubectl annotate ingress myapp-canary nginx.ingress.kubernetes.io/canary-weight- --kubeconfig=./kubernetes-config
Ở trên ta có:
- job đầu tiên là
canary-deployment
sẽ luôn chạy, không cần approval: ở bước này ta sẽ deploy cả bản stable và canary, chú ý rằng vì bản canary chưa có weight nên nó sẽ chưa có tác dụng --ignore-not-found=true
ý là ta cứ delete resource và ignore không throw lỗi nếu không tìm thấy resource- Ở các bước
Set up Kubeconfig
ta sẽ lấy giá trị của kubeconfig ở Github Secret và viết (write) nó vào filekubernetes-config
- Ta chỉ cần
Checkout code
ở các job mà cókubectl apply
- Mỗi bước ta sẽ thực hiện trên 1
environment
, vì Github chỉ support Approval cho các job cóenvironment
. Approval là kiểu job sẽ Pending và chờ cho có người approve thì mới thực hiện, vì ở project thực tế thường là ta sẽ chạy canary release và monitor 1 khoảng thời gian sau đó người release sẽ bấm Approve để tiếp tục. - Ở job
finalize-deployment
ta sẽ xoá cái deployment hiện tại và full release cái deployment của canary, chú ý ở bước này để remove annotation thì ta thêm dấu-
("trừ") vào cuối của tên annotation
Xong sau đó ta Commit & Push
lên master
nhé, commit message ta viết gì cũng được, mình để là feat: add k8s manifest, add cicd
:
Xong sau đó ta quay trở lại repo > Actions và Run Workflow như sau:
Sau khi ta start thì sẽ thấy như sau:
Và khi tới bước release canary-10
ta sẽ thấy báo waiting approval như sau:
Ta bấm vào Review deployments
, sau đó tick chọn canary-10
và nhập comment nếu muốn, cuối cùng là ta bấm Approva and deploy
Sau đó ta sẽ thấy tiến trình bắt đầu:
Khi thành công ta sẽ thấy báo như sau:
Ta thấy canary-10
đã oke, có in ra cả URL nhưng Github mặc định để HTTPS, trong khi ingress của ta chưa setup HTTPS nên nếu ta mở trực tiếp sẽ thấy Chrome cảnh báo đó nha 😆
Giờ ta có thể test như ở phần trước:
for i in $(seq 1 10); do curl -s 6b4407.learnk8s.jamesisme.com | grep "Hostname"; done
>>>
Hostname: myapp-6866c5b586-4zfr8
Hostname: myapp-6866c5b586-4zfr8
Hostname: myapp-6866c5b586-4zfr8
Hostname: myapp-6866c5b586-4zfr8
Hostname: myapp-6866c5b586-4zfr8
Hostname: myapp-6866c5b586-4zfr8
Hostname: myapp-6866c5b586-4zfr8
Hostname: myapp-canary-7fbfc575cf-sdwrt, V1
Hostname: myapp-6866c5b586-4zfr8
Hostname: myapp-6866c5b586-4zfr8
vì hiện tại
weight=10
nên tỉ lệ request trúng V1 khá thấp 😅😅
Ta có thể kiểm tra lại cho chắc bằng cách describe ing
kubectl describe ing myapp-canary --kubeconfig=./kubernetes-config
>>>
Name: myapp-canary
Labels: <none>
Namespace: lk8s-6b4407
Address: k8s.jamesisme.com
Ingress Class: nginx
Default backend: <default>
Rules:
Host Path Backends
---- ---- --------
6b4407.learnk8s.jamesisme.com
/ myapp-svc-canary:http (10.244.0.19:3000)
Annotations: nginx.ingress.kubernetes.io/canary: true
nginx.ingress.kubernetes.io/canary-weight: 10
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 4m59s (x3 over 8m39s) nginx-ingress-controller Scheduled for sync
oke thấy weight=10
roài 👍️👍️
Tiếp tục ta quay trở lại và start release 50% traffic nha:
Oke thì ta check describe ing
xem nhé, sau đó ta cũng test với command này cho chắc nha:
for i in $(seq 1 10); do curl -s 6b4407.learnk8s.jamesisme.com | grep "Hostname"; done
Sau đó ta tiếp tục làm tương tự cho 100% traffic, kiểm tra oke thì ta tới bước cuối finalize-deployment
:
Khi mọi thứ oke ta check sẽ thấy traffic hiện tại sẽ luôn vào V1:
get deploy
cũng sẽ thấy chỉ còn 1 cái:
kubectl get deploy --kubeconfig=./kubernetes-config
>>>
NAME READY UP-TO-DATE AVAILABLE AGE
myapp-canary 1/1 1 1 16m
Rollback
Bây giờ, cái ta muốn là tại bất kì bước release canary nào, nếu người release bấm Reject (làm pipeline failed), thì ta sẽ tiến hành rollback
Giờ ta update lại file .github/workflows/deploy.yml
(vẫn dùng Cloud IDE), và thêm vào 1 job mới:
name: "Kubernetes Canary Release with Rollback"
on:
workflow_dispatch: # Manually triggered for flexibility
jobs:
canary-deployment:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Kubeconfig
run: |
echo "${{ secrets.KUBECONFIG }}" > ./kubernetes-config
- name: Setup kubectl
uses: azure/setup-kubectl@v4
- name: Clean up if needed
run: |
kubectl delete -f deployment-canary.yml --kubeconfig=./kubernetes-config --ignore-not-found=true
kubectl delete -f ingress-canary.yml --kubeconfig=./kubernetes-config --ignore-not-found=true
- name: Deploy Stable to Kubernetes
run: |
kubectl apply -f deployment.yml --kubeconfig=./kubernetes-config
kubectl apply -f ingress.yml --kubeconfig=./kubernetes-config
- name: Deploy Canary to Kubernetes
run: |
kubectl apply -f deployment-canary.yml --kubeconfig=./kubernetes-config
kubectl apply -f ingress-canary.yml --kubeconfig=./kubernetes-config
canary-10-percent:
runs-on: ubuntu-latest
environment:
name: canary-10
url: http://5b3604.learnk8s.jamesisme.com # Your canary environment URL
needs: canary-deployment
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Kubeconfig
run: |
echo "${{ secrets.KUBECONFIG }}" > ./kubernetes-config
- name: Setup kubectl
uses: azure/setup-kubectl@v4
- name: Set Canary to 10% Traffic
run: |
kubectl annotate ingress myapp-canary nginx.ingress.kubernetes.io/canary-weight="10" --overwrite --kubeconfig=./kubernetes-config
canary-50-percent:
runs-on: ubuntu-latest
environment:
name: canary-50
needs: canary-10-percent
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Kubeconfig
run: |
echo "${{ secrets.KUBECONFIG }}" > ./kubernetes-config
- name: Setup kubectl
uses: azure/setup-kubectl@v4
- name: Set Canary to 50% Traffic
run: |
kubectl annotate ingress myapp-canary nginx.ingress.kubernetes.io/canary-weight="50" --overwrite --kubeconfig=./kubernetes-config
canary-100-percent:
runs-on: ubuntu-latest
environment:
name: canary-100
needs: canary-50-percent
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Kubeconfig
run: |
echo "${{ secrets.KUBECONFIG }}" > ./kubernetes-config
- name: Setup kubectl
uses: azure/setup-kubectl@v4
- name: Set Canary to 100% Traffic
run: |
kubectl annotate ingress myapp-canary nginx.ingress.kubernetes.io/canary-weight="100" --overwrite --kubeconfig=./kubernetes-config
finalize-deployment:
runs-on: ubuntu-latest
needs: canary-100-percent
environment: production
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Kubeconfig
run: |
echo "${{ secrets.KUBECONFIG }}" > ./kubernetes-config
- name: Setup kubectl
uses: azure/setup-kubectl@v4
- name: Complete Deployment by removing previous deployment
run: |
kubectl delete -f deployment.yml --kubeconfig=./kubernetes-config
kubectl delete -f ingress.yml --kubeconfig=./kubernetes-config
kubectl annotate ingress myapp-canary nginx.ingress.kubernetes.io/canary- --kubeconfig=./kubernetes-config
kubectl annotate ingress myapp-canary nginx.ingress.kubernetes.io/canary-weight- --kubeconfig=./kubernetes-config
rollback:
runs-on: ubuntu-latest
if: failure() # Executes if any of the previous jobs fail
needs: [canary-10-percent, canary-50-percent, canary-100-percent, finalize-deployment]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Kubeconfig
run: |
echo "${{ secrets.KUBECONFIG }}" > ./kubernetes-config
- name: Rollback to Stable
run: |
kubectl apply -f deployment.yml --kubeconfig=./kubernetes-config
kubectl apply -f ingress.yml --kubeconfig=./kubernetes-config
kubectl delete -f deployment-canary.yml --kubeconfig=./kubernetes-config
kubectl delete -f ingress-canary.yml --kubeconfig=./kubernetes-config
Ở trên ta thêm vào 1 job rollback
, job này sẽ phụ thuộc vào các jobs được khai báo ở needs
, và nếu bất kì job nào failed (if: failure()
) thì sẽ thực hiện rollback, ở bước này ta đơn giản là apply cái release ban đầu và xoá cái canary release đi
Giờ ta lưu file deploy.yml
này lại, commit, sau đó trigger Github Actions lần nữa, và ta bấm Reject ở bất kì bước nào:
Ta sẽ thấy job rollback
sẽ chạy ngay sau đó:
Khi get po
ta cũng sẽ chỉ thấy còn cái release ban đầu, và 100% traffic sẽ đi vào pod này:
kubectl get po --kubeconfig=./kubernetes-config
>>>
NAME READY STATUS RESTARTS AGE
myapp-6866c5b586-thg6p 1/1 Running 0 11m
Tổng kết và thân ái
Ở bài này phần setup Github Actions mình làm ở mức khá đơn giản, trong thực tế thường là ta sẽ cần một pipeline phức tạp hơn nhiều để đảm bảo rằng quá trình thực hiện release một cách cẩn thận, chỉn chu nhất, thu thập/monitor đầy đủ metrics trước khi đẩy tính năng ra cho nhiều users dùng hơn.
Thêm một điểm nữa là vì hiện tại Github (và cả Gitlab) chỉ support Approval
cho environment
, chứ mình thấy đẹp nhất là support approval mà không cần environment, vì ta dùng environment cho canary release có vẻ chưa thật sự đúng 100% bản chất của environment lắm 😂
Hi vọng rằng qua bài này các bạn đã hiểu thêm về cách setup Canary release trên Kubernetes Cluster với Nginx Ingress, cùng với đó là tích hợp vào CICD để tự động hoá quy trình.
Chúc các bạn buổi tối vui vẻ và hẹn gặp lại các bạn vào những bài sau 👋👋
All rights reserved