Mở đầu

Nếu có bạn nào làm việc với Kubernetes chắc hẳn là sẽ quan tâm đến vấn đề Zero Downtime. Những bạn nhiều kinh nghiệm chắc hẳn cũng đã tìm hiểu nhiều về vấn đề này, còn đối với những bạn chưa có nhiều kinh nghiệm thì sao? Bài viết này hy vọng sẽ giúp được ít nhiều cho bạn.
Nếu bạn search Google với cụm từ "Zero Downtime with Rolling Updates" thì sẽ có rất nhiều kết quả.
uc?id=1v5r5CSRFZ-3Ix279vyE1uF-Iwz6fwO4o&export=download
OK, Let's get started!

Rolling Updates

Như các bạn cũng biết trong Kubernetes thì Pod được xem là đơn vị nhỏ nhất, Pod chứa 1 hoặc nhiều container, cùng sử dụng chung vùng nhớ, có địa chỉ Ip riêng và các container được schedule cùng nhau. Chúng ta thường quản lý Pod tốt hơn bằng cách bọc chúng bởi các thành phần như ReplicaSet, DaemonSet hay Deployment v.v...
Mình sẽ nói kỹ hơn về Deployment, Deployment là một Resource trong K8s, nó quản lý nhiều ReplicaSet và có khả năng thực hiện Rolling update, Rollback v.v...
Deployment:
uc?id=15Q00fUWvtzd0Xe0dx_NkRgeyBU08GfL1&export=download

Cơ chế của Rolling Updates đơn giản như sau:

  1. Tạo một ReplicaSet mới
  2. Dần dần tăng số lượng bản sao (số Pod) trên ReplicaSet mới
  3. Dần dần giảm số lượng bản sao (số Pod) trên ReplicaSet cũ
  4. (Lặp lại bước 2 và 3)
  5. Lưu các ReplicaSet cũ với số bản sao = 0

uc?id=1qDvexgOq6M7UJNKAh4J1-FnSEwFTi2Ud&export=download
Url:https://www.bluematador.com/blog/kubernetes-deployments-rolling-update-configuration
Hoặc là hình dung theo hình bên dưới.
uc?id=1LIqlkbm8KuIftqlI2aFS0CzSFBxM3yiu&export=download
Url: https://blog.vietnamlab.vn/2019/04/26/nhap-mon-kubernetes-p4-kubernetes-workloads-resource-1/
Vậy cơ bản chúng ta đã biết Rolling update là như thế nào rồi. Thoạt nhìn thì chúng ta nghĩ, "Oimeo! Tuyệt vời, với cơ chế này thì làm gì có chuyện downtime khi rolling update nữa". Thế nhưng không phải vậy, đời không như mơ.

Downtime When Rolling Updates

uc?id=1r0cZ3W4eP5jXqc8jsnZCBs_6MXLDH3IA&export=download
Trên đây là sơ đồ của một Pod Lifecycle, trên lý thuyết thì request sẽ được routing tới pod xử lý trong quãng thời gian từ điểm số 2 tới điểm số 4. Để giữ cho không có request nào bị lỗi thì ứng dụng cần có khả năng xử lý request ở điểm số 2, và dừng nhận request từ điểm số 4. Tuy nhiên trên thực tế, ứng dụng của chúng ta có thể sẽ đáp ứng được request từ điểm số 3 và kết thúc nhận request bị đẩy lùi đến điểm số 5.
Vậy thì khoảng thời gian không thể đáp ứng request là ở vùng màu vàng và màu đỏ. Lý do tại sao vậy:

  • Vùng màu vàng: Mặc dù container đã running rồi, tuy nhiên xét về mặt container đã có thể đáp ứng được request hay chưa thì tùy vào nhiều yếu tố và trong đó có cả việc ứng dụng của bạn được thiết kế như thế nào. Ví dụ như sau khi container running bạn muốn nó phải load một lượng lớn cache, hoặc là phải xử lý công việc gì đó mất thời gian. Vậy nên trong thời gian đó, request của bạn vẫn chưa được đáp ứng hoàn toàn
  • Vùng màu đỏ: Khi kubelet gửi một tín hiệu SIGTERM muốn terminated Pod của bạn, nó cho phép gia hạn tối đa 30s sau đó sẽ gửi tín hiệu SIGKILL. Trong thời gian gia hạn đó Pod của bạn vẫn nhận request nhưng không đáp ứng được, vì vậy không thể thực hiện Gracefully shutdown (dọn dẹp sạch sẽ trước khi shudown).

Zero Downtime With Rolling Updates

Vậy thì giải quyết như thế nào. Nếu như search google thì đa phần kết quả trả về là sử dụng Readiness check và Prestop hook. Readiness check và Prestop hook là gì thì mình cũng đã viết rõ ở P1 và P2 rồi, các bạn có thể tham khảo:
[Kubernetes Best Practice P1] - Application process management with postStart and preStop hook
[Kubernetes Best Practice P2] - Health Probes
Cụ thể để giải quyết và test cho vấn đề trên thì rất may mắn mình có đọc được bài viết của bạn Minh Momen - hay và chuẩn chỉnh, các bạn đọc ở link bên dưới nhé.
https://kipalog.com/posts/Zero-downtime-voi-Kubernetes-P1--Truly-stateless-application

Deployment strategy

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mywebserver
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
    maxSurge: 1
    maxUnavailable: 1
    ...
    ...

Deployment đang đảm bảo thay thế các Pods cũ bằng các Pod mới dần dần trong khi vẫn giữ một phần trong số các Pod chạy. Phần này có thể được kiểm soát bằng cách thay đổi các tham số maxSurge và maxUnavailable.

  • maxSurge: số lượng Pod có thể được triển khai tạm thời bên cạnh các bản sao mới. Setting thông số này có nghĩa là chúng ta có thể có tổng cộng tối đa năm Pod đang chạy trong quá trình cập nhật (4 bản sao + 1).
  • maxUnavailable: số lượng Pod có thể bị kill đồng thời trong quá trình update. Trong ví dụ của chúng tôi, chúng tôi có thể có ít nhất ba Pod running trong khi quá trình update đang diễn ra (4 bản sao - 1).

Fixed Deployment Strategy

Hầu hết các trường hợp chúng ta đề muốn sử dụng Rolling Update, tuy nhiên trong một số trường hợp chúng ta yêu cầu từ chối hoàn toàn request đến các Pod cũ, thậm chí trong thời gian update ứng dụng, chúng ta sẽ hiển thị thông báo cho người dùng rằng ứng dụng chúng ta đang được bảo trì trong thời gian ngắn. Trường hợp phổ biến để áp dụng kịch bản này là khi chúng ta có một lỗ hổng nghiêm trọng liên quan đến bảo mật, trong trường hợp đó rolling update strategy có vẻ không phù hợp, khách hàng vẫn dùng phiên bản cũ trong khi chúng ta thực hiện triển khai bản vá lỗi (Thật ra vẫn có cách khác đề phòng cho trường hợp này là enable maintenance mode để hiển thị thông báo cho người dùng tình trạng đang bảo trì). Việc thông báo cho người dùng về tình trạng bảo trì vẫn hơn so với giữ lỗ hổng bảo mật và tiến hành Rolling update phiên bản vá lỗi.
Tùy chọn tốt nhất của bạn ở đây là sử dụng Fixed Deployment Strategy. Trong quy trình triển khai này, Deployment sẽ không thay thế dần các Pods. Thay vào đó, nó kill tất cả các Pod chạy phiên bản ứng dụng cũ; sau đó nó tạo lại chúng bằng mẫu Pod mới. Để sử dụng chiến lược này, bạn setting strategy type là recreate.
uc?id=1jOgTjebdqnImvF8sy9v5m_wGBij71vco&export=download
sample-fixed-strategy.yml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-fixed-strategy
spec:
  replicas: 4
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: fixed-strategy
  template:
    metadata:
      labels:
        app: fixed-strategy
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.12
          ports:
            - containerPort: 80

---
apiVersion: v1
kind: Service
metadata:
  name: mywebservice
spec:
  selector:
    app: fixed-strategy
  ports:
  - port: 80
    targetPort: 80
  type: NodePort
[vagrant@ci zero-downtime]$ kubectl apply -f sample-fixed-strategy.yml
deployment.apps "sample-fixed-strategy" created
[vagrant@ci zero-downtime]$ kubectl get pod
NAME                                     READY     STATUS    RESTARTS   AGE
sample-fixed-strategy-7489665c64-6fptm   1/1       Running   0          39s
sample-fixed-strategy-7489665c64-nf6mm   1/1       Running   0          39s
sample-fixed-strategy-7489665c64-nrg9l   1/1       Running   0          39s
sample-fixed-strategy-7489665c64-swtj7   1/1       Running   0          39s

Bây giờ chúng ta thử thay đổi file sample-fixed-strategy.yml nginx:1.12 -> nginx:1.13 và thực hiện apply lại.

[vagrant@ci zero-downtime]$ kubectl apply -f sample-fixed-strategy.yml
deployment.apps "sample-fixed-strategy" configured

[vagrant@ci zero-downtime]$ kubectl get pods
NAME                                     READY     STATUS              RESTARTS   AGE
sample-fixed-strategy-58458c79c5-bv6bh   0/1       ContainerCreating   0          9s
sample-fixed-strategy-58458c79c5-d8k24   0/1       ContainerCreating   0          9s
sample-fixed-strategy-58458c79c5-n477l   0/1       ContainerCreating   0          9s
sample-fixed-strategy-58458c79c5-xf48t   0/1       ContainerCreating   0          9s

[vagrant@ci zero-downtime]$ kubectl get pods
NAME                                     READY     STATUS    RESTARTS   AGE
sample-fixed-strategy-58458c79c5-bv6bh   1/1       Running   0          41s
sample-fixed-strategy-58458c79c5-d8k24   1/1       Running   0          41s
sample-fixed-strategy-58458c79c5-n477l   1/1       Running   0          41s
sample-fixed-strategy-58458c79c5-xf48t   1/1       Running   0          41s

Như kết quả trên thì ta thấy, các Pod sẽ bị kill hoàn toàn sau đó mới tạo lại các Pod mới. Trong quá trình Update, sẽ có lúc bạn truy cập không được vì khi đó Pod đang trong tình trạng chưa Running, Vâng, đó chính là điều mà phương pháp triển khai này mong muốn, cho đến khi bản nginx:1.13 được release, mình không muốn người dùng tiếp tục sử dụng nginx:1.12.

Blue-Green, Red-Black or A/B Deployment

Nó còn được gọi là triển khai A/B. Nó liên quan đến việc chúng ta chuẩn bị 2 bộ môi trường khác nhau, triển khai 2 bản ứng dụng A và B lên mỗi môi trường này tuy nhiên chỉ 1 trong 2 môi trường nhận được request, còn môi trường còn lại không hoặt động (để backup). Khi một phiên bản mới của ứng dụng hoàn thành (bản màu green), thông qua việc routing ta sẽ hướng request người dùng đến phiên bản này. Trong trường hợp phát hiện sự cố ở phiên bản này, ta hoàn toàn có thể chuyển hướng traffic trở lại phiên bản ổn định (phiên bản màu blue).
Trong lần release tiếp theo, ta lại triển khai lên phiên bản màu green, lúc này phiên bản màu green xem như là môi trường staging (test) để thực hiện test việc va lỗi, cho đến khi nó Ok, chúng ta sẽ chuyển hướng request đến đây.
Mặc dù chiến lược triển khai này không giống như rolling update nhưng ưu điểm của nó là không bao giờ có trường hợp nhiều hơn 1 phiên bản của ứng dụng chạy cùng 1 lúc. Tuy nhiên nhược điểm của nó là cần gấp đôi tài nguyên lưu trữ ứng dụng, điều này làm tăng chi phí của bạn.
uc?id=1uW5II6ppx8wOYeyFkkvdTgfXbHMQtKid&export=download
uc?id=11P8qM9Dwoq5ZxEE8B9TB5EksMcEtbPDM&export=download

Lúc nãy chúng ta có một ví dụ về triển khai Recreate strategy. Mặc dù việc này có thể bảo vệ khách hàng khỏi những phiên bản ứng dụng gặp lỗ hổng, nhưng chúng ta phải chịu 1 khoảng thời gian downtime, điều này không cho phép trong những ứng dụng quan trọng. Tuy nhiên nếu chúng ta sử dụng Deployment và Service chúng ta có thể nhanh chóng đạt được 2 mục tiêu. Ý tưởng là dùng 2 Deployment cho Green và Blue. Phiên bản chính đang sử dụng là Blue, khi các Pod phiên bản Green hoàn thành, chúng ta chỉ cần setting điều hướng ở Service (Pod Selector). Nếu cần rollback thì chúng ta chỉ việc chỉ định lại Pod selecter trong cấu hình Service.

Testing the Blue/Green Deployment

Ví dụ đơn giản như sau:

  • nginx_deployment_green.yaml
  • nginx_deployment_blue.yaml
  • nginx_service.yaml

nginx_deployment_green.yaml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment-green
spec:
  replicas: 4
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nginx-green
  template:
    metadata:
      labels:
        app: nginx-green
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.12
          ports:
            - containerPort: 80

nginx_deployment_blue.yaml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment-blue
spec:
  replicas: 4
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nginx-blue
  template:
    metadata:
      labels:
        app: nginx-blue
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.13
          ports:
            - containerPort: 80

nginx_service.yaml

apiVersion: v1
kind: Service
metadata:
  name: mywebservice
spec:
  selector:
    app: nginx-green
  ports:
  - port: 80
    targetPort: 80
  type: NodePort

Đến đây chắc các bạn đã hiểu được ý đồ của mình rồi nhỉ. Chỉ cần điều hướng ở setting app: nginx-green hoặc app: nginx-blue là được.

Canary Deployment Strategy

Việc triển khai Canary giống như màu blue-green, ngoại trừ nó có nhiều rủi ro hơn. Thay vì chuyển từ màu blue sang green trong một bước, bạn sẽ tiếp cận theo từng giai đoạn.
Với Canary Deployment bạn sẽ cho một số ít người dùng thử trước phiên bản mới của ứng dụng, Nếu không có bất kỳ lỗi nào, chúng ta sẽ chuyển hướng hoàn toàn sang phiên bản mới.
uc?id=1266ih5rxrPxqWeuKHrGvR3EplPzzmYRo&export=download
url: https://dev.to/mostlyjason/intro-to-deployment-strategies-blue-green-canary-and-more-3a3

Ưu điểm chính của phương pháp này là chúng ta nhanh chóng nhận được phản hồi từ phía khách hàng về bất cứ tính năng mới nào mà ứng dụng chúng ta cung cấp. Nếu có nhiều phản hồi tích cực, chúng ta sẽ chuyển 100% sang phiên bản mới.
Trong triển khai Kubernetes để thực hiện điều này vô cùng đơn giản. Chúng ta sẽ hiểu được nếu xem xét hình bên dưới.
uc?id=19-02ozIzt1ed1fVu94coZX_53vkzjbEM&export=download

Canary Testing Using Kubernetes Deployments And Services

Để test Canary Deployments, chúng ta có một ví dụ đơn giản sau:
nginx_deployment_stable.yaml:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment-stable
spec:
  replicas: 6
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.12
          ports:
            - containerPort: 80

nginx_deployment_canary.yaml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment-canary
spec:
  replicas: 3
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.13
          ports:
            - containerPort: 80

nginx_service_canary.yaml

apiVersion: v1
kind: Service
metadata:
  name: mywebservice
spec:
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80
  type: NodePort

Ở đây mình chỉ đưa ra ví dụ đơn giản là nginx:1.12 và nginx:1.13 để các bạn dễ hình dung thôi.
Khi bản Canary ổn định chúng ta sẽ tăng số bản sao lên 100% (9 replica) và xóa Deployment phiên bản stable.

Kết luận

Tài liệu tham khảo

  1. https://www.magalix.com/blog/kubernetes-patterns-declarative-deployments
  2. https://kipalog.com/posts/Zero-downtime-voi-Kubernetes-P1--Truly-stateless-application
  3. https://dev.to/mostlyjason/intro-to-deployment-strategies-blue-green-canary-and-more-3a3