【Tekton】Blue/Greenデプロイをやってみる

2022.09.30
2024.03.24
Kubernetes
Tektonブルーグリーンデプロイ

はじめに

TektonでBlue/Greenデプロイをやってみます。

Blue/Greenデプロイ

Blue/Greenデプロイは、新しいバージョンを実行してからトラフィックを切り替えるデプロイ方法です。

メリットデメリット
・ダウンタイムがゼロ
・異なるバージョンが同時に実行されない
・すぐに旧バージョンに戻せる
・2倍のリソースが必要

Tektonを使わないBlue/Greenデプロイは下記で試しています。

【Kubernetes】DeploymentとServiceでブルーグリーンデプロイやってみる

【Kubernetes】DeploymentとServiceでブルーグリーンデプロイやってみる

はじめに Kubernetesのデプロイ戦略として、**DeploymentとServiceだけ

TektonでBlue/Greenデプロイ

実際にTektonでBlue/Greenデプロイをやっていきたいと思います。

Tektonを使ってBlue/Greenデプロイをする場合は、catalogから下記を利用します。新しいバージョンのマニフェストを提供する方法はいくつかありますが、今回はConfigMapを利用します。

catalog/task/blue-green-deploy/0.1 at main · tektoncd/catalog

catalog/task/blue-green-deploy/0.1 at main · tektoncd/catalog

Catalog of shared Tasks and Pipelines. Contribute to tektoncd/catalog development by creating an account on GitHub.

作成するマニフェストは下記の通りです。

1.
2├── blue-green-deploy-taskrun.yaml
3├── blue-green-deploy.yaml
4├── clusterbinding.yaml
5├── v1
6│   ├── blue-deployment.yaml
7│   └── service.yaml
8└── v2
9    └── green-deployment.yaml

ClusterRoleBindingの作成

まずは、ServiceAccountにClusterRoleをバインドします。

1apiVersion: rbac.authorization.k8s.io/v1
2kind: ClusterRoleBinding
3metadata:
4  name: default-blue-green
5subjects:
6  - kind: ServiceAccount
7    name: default
8    namespace: default
9roleRef:
10  kind: ClusterRole
11  name: cluster-admin
12  apiGroup: rbac.authorization.k8s.io
1kubectl apply -f clusterbinding.yaml

デモアプリv1のデプロイ

version1のアプリをデプロイします。

v1/blue-deployment.yamlv1/service.yamlは下記の通りです。ラベルのversion: "v1"でバージョンを表しています。

1apiVersion: apps/v1
2kind: Deployment
3metadata:
4  name: myapp-v1
5spec:
6  replicas: 1
7  selector:
8    matchLabels:
9      app: myapp
10      version: v1
11  template:
12    metadata:
13      labels:
14        app: "myapp"
15        version: "v1" # version1
16    spec:
17      containers:
18        - name: myapp
19          image: quay.io/vinamra2807/social-client:v1
20          imagePullPolicy: IfNotPresent
21          ports:
22            - containerPort: 3000
23              name: http
1apiVersion: v1
2kind: Service
3metadata:
4  name: myapp
5  labels:
6    app: myapp
7spec:
8  type: NodePort
9  ports:
10    - port: 3000
11      name: http
12  selector:
13    app: myapp
14    version: v1 # version1を指定
1kubectl apply -f v1

デプロイしたアプリを確認してみます。

1❯ kubectl get deploy,pod,svc
2NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
3deployment.apps/myapp-v1   1/1     1            1           44s
4
5NAME                                READY   STATUS      RESTARTS   AGE
6pod/myapp-v1-584d5bb7d-j5gmk        1/1     Running     0          44s
7
8NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
9service/myapp        NodePort    10.107.209.241   <none>        3000:31863/TCP   44s

ブラウザからアプリにアクセスしてみるとversion1であることがわかります。

Blue-Green-Deploy Taskの作成

Blue/Greenデプロイはcatalogの下記のTaskを利用します。

catalog/task/blue-green-deploy/0.1/blue-green-deploy.yaml at main · tektoncd/catalog

catalog/task/blue-green-deploy/0.1/blue-green-deploy.yaml at main · tektoncd/catalog

Catalog of shared Tasks and Pipelines. Contribute to tektoncd/catalog development by creating an account on GitHub.

1apiVersion: tekton.dev/v1beta1
2kind: Task
3metadata:
4  name: blue-green-deploy
5  labels:
6    app.kubernetes.io/version: "0.1"
7  annotations:
8    tekton.dev/pipelines.minVersion: "0.12.1"
9    tekton.dev/categories: Deployment
10    tekton.dev/tags: deployment
11    tekton.dev/displayName: "blue green deployment"
12    tekton.dev/platforms: "linux/amd64"
13spec:
14  description: >-
15    This task can be used to do Blue-Green deployment
16  workspaces:
17    - name: manifest-dir
18      description: Consisting of kubernetes manifests
19    - name: kubeconfig-dir
20      description: Can be used in case kubeconfig file is mounted
21  params:
22    - name: SERVICE_NAME
23      type: string
24      description: Name of the service which is pointing to existing deployment
25    - name: NEW_VERSION
26      type: string
27      description: The version of newer deployment which is to be patched to existing service
28    - name: MANIFEST
29      type: string
30      default: "."
31      description: The newer version of the deployment to be deployed in another zone
32  steps:
33    - name: change-deployment
34      image: quay.io/openshift/origin-cli:4.6
35      workingDir: $(workspaces.manifest-dir.path)
36      script: |
37        #!/usr/bin/env bash
38        set -u -o pipefail
39
40        if [[ -f $(workspaces.kubeconfig-dir.path)/kubeconfig ]]; then
41          export KUBECONFIG=$(workspaces.kubeconfig-dir.path)/kubeconfig
42        fi
43
44        getReasons() {
45          local deploy_name=$1
46          kubectl get deploy "$deploy_name" \
47            -o jsonpath='{range .status.conditions[*]}{.reason}{"\n"}{end}'
48        }
49
50        # Getting the status at particular index
51        getStatusAtIndex() {
52          local deploy_name=$1
53          local index=$2
54          kubectl get deploy "$deploy_name" \
55            -o jsonpath='{range .status.conditions['$index']}{.status}{"\n"}{end}'
56        }
57
58        # Checking for unavailable pods in case the deployment is
59        # patched with a newer image
60        checkUnavailableReplicas() {
61          local deploy_name=$1
62          local replicasUnavailable=$(kubectl get deploy "$deploy_name" -o jsonpath='{.status.unavailableReplicas}')
63          [[ "$replicasUnavailable" == "" ]] && return 0 || return 1
64        }
65
66        getMinimumReplicaStatus() {
67          local deploy_name=$1
68          local INDEX=0
69          for reason in $(getReasons $deploy_name); do
70            if [[ "$reason" == "MinimumReplicasAvailable" ]]; then
71              local ready=$(getStatusAtIndex $deploy_name ${INDEX})
72              [[ "$ready" != "True" ]] && return 1 || return 0
73            fi
74            ((INDEX++))
75          done
76          return 1
77        }
78
79        checkProgressDeadline() {
80          local deploy_name=$1
81          local INDEX=0
82          for reason in $(getReasons $deploy_name); do
83            if [[ "$reason" == "ProgressDeadlineExceeded" ]]; then
84              local status=$(getStatusAtIndex $deploy_name ${INDEX})
85              [[ "$status" == "True" ]] && return 0 || return 1
86            fi
87            ((INDEX++))
88          done
89          return 1
90        }
91
92        main() {
93          local deploy_name=$(params.SERVICE_NAME)-$(params.NEW_VERSION)
94          local service=$(params.SERVICE_NAME)
95          local version=$(params.NEW_VERSION)
96
97          kubectl apply -f $(params.MANIFEST)
98
99          # Wait until the Deployment is ready by checking the
100          # MinimumReplicasAvailable condition.
101          # Also checking for the ProgressDeadlineExceeded
102          # condition
103          while true; do
104            getMinimumReplicaStatus ${deploy_name}
105            [[ $? -eq 0 ]] && break
106            checkProgressDeadline ${deploy_name}
107            [[ $? -eq 0 ]] && break
108            sleep 5
109          done
110          # If ProgressDeadlineExceeded then break from the main
111          # and avoid checking for the next condition
112          checkProgressDeadline ${deploy_name}
113          if [[ $? -eq 0 ]]; then
114            echo "Failed to change the service" && break
115          fi
116          # If MinimumReplicas are available then check
117          # for the unavailable Pods
118          # Also checking for the ProgressDeadlineExceeded condition
119          while true; do
120            checkUnavailableReplicas ${deploy_name}
121            [[ $? -eq 0 ]] && break
122            checkProgressDeadline ${deploy_name}
123            [[ $? -eq 0 ]] && break
124            sleep 5
125          done
126
127          # Update the service selector with the new version
128          # if ProgressDeadline not exceeded
129          checkProgressDeadline ${deploy_name}
130          if [[ $? -eq 0 ]]; then
131            echo "Failed to change the service"
132          else
133            kubectl patch svc ${service} \
134              -p "{\"spec\":{\"selector\": {\"app\": \"${service}\", \"version\": \"${version}\"}}}"
135            echo "Done."
136          fi
137        }
138
139        main "$@"
1kubectl apply -f blue-green-deploy.yaml

デモアプリv2の用意

version2のアプリのマニフェストはConfigMapに格納します。

v2/green-deployment.yamlの内容は下記の通りです。

1apiVersion: apps/v1
2kind: Deployment
3metadata:
4  name: myapp-v2
5spec:
6  replicas: 1
7  selector:
8    matchLabels:
9      app: myapp
10      version: v2
11  template:
12    metadata:
13      labels:
14        app: "myapp"
15        version: "v2" # version2
16    spec:
17      containers:
18        - name: myapp
19          image: quay.io/vinamra2807/social-client:v2
20          imagePullPolicy: IfNotPresent
21          ports:
22            - containerPort: 3000
23              name: http

このファイルを使って、ConfigMapを作成します。

1kubectl create configmap manifests --from-file="v2/green-deployment.yaml"

TaskRunの作成

TaskRunを作成して、Tektonを実行します。

blue-green-deploy-taskrun.yamlは下記の通りです。

1apiVersion: tekton.dev/v1beta1
2kind: TaskRun
3metadata:
4  name: blue-green-deploy-taskrun
5spec:
6  taskRef:
7    name: blue-green-deploy
8  params:
9    - name: SERVICE_NAME
10      value: myapp # Service名
11    - name: NEW_VERSION
12      value: v2 # 新しいバージョンの名前
13  workspaces:
14    - name: manifest-dir
15      configMap:
16        name: manifests
17    - name: kubeconfig-dir
18      emptyDir: {}
1kubectl apply -f blue-green-deploy-taskrun.yaml

実行結果の確認

実行した結果を確認します。

まずはTaskRunのログを見てみます。

1❯ tkn tr logs blue-green-deploy-taskrun
2[change-deployment] deployment.apps/myapp-v2 created
3[change-deployment] service/myapp patched
4[change-deployment] Done.

Deployment、Pod、Serviceを確認するとversion2のアプリがデプロイされ、Serviceのセレクタがversion2に切り替わっているのが確認できます。

1❯ kubectl get deploy,pod,svc
2NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
3deployment.apps/myapp-v1   1/1     1            1           73m
4deployment.apps/myapp-v2   1/1     1            1           51s
5
6NAME                                READY   STATUS      RESTARTS   AGE
7pod/myapp-v1-584d5bb7d-j5gmk        1/1     Running     0          73m
8pod/myapp-v2-fcd8b6f98-g4q7q        1/1     Running     0          51s
9
10NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
11service/myapp        NodePort    10.107.209.241   <none>        3000:31863/TCP   73m
12
13❯ kubectl describe svc myapp
14Name:                     myapp
15Namespace:                default
16Labels:                   app=myapp
17Annotations:              <none>
18Selector:                 app=myapp,version=v2
19Type:                     NodePort
20IP Family Policy:         SingleStack
21IP Families:              IPv4
22IP:                       10.107.209.241
23IPs:                      10.107.209.241
24Port:                     http  3000/TCP
25TargetPort:               3000/TCP
26NodePort:                 http  31863/TCP
27Endpoints:                10.1.4.169:3000
28Session Affinity:         None
29External Traffic Policy:  Cluster
30Events:                   <none>

最後にブラウザからアプリにアクセスしてみると、version2になっていることが確認できます。

blue-green-deploy Task

catalogのblue-green-deploy TaskのWorkspaceとParameterについて簡単に紹介します。

catalog/task/blue-green-deploy/0.1 at main · tektoncd/catalog

catalog/task/blue-green-deploy/0.1 at main · tektoncd/catalog

Catalog of shared Tasks and Pipelines. Contribute to tektoncd/catalog development by creating an account on GitHub.

Workspaces

  • manifest-dir: マニフェストがあるworkspace(emptyDirだとマニフェストなし)
  • kubeconfig-dir: 別のクラスタにデプロイする場合にkubeconfigをマウントするworkspace(emptyDirだとkubeconfigなし)

Parameters

  • SERVICE_NAME: 対象のDeploymentと紐づいているSerivce名(必須)
  • NEW_VERSION: 新しくデプロイするアプリのバージョン名(必須)
  • MANIFEST: マニフェストのURL

参考

Support

\ この記事が役に立ったと思ったら、サポートお願いします! /

buy me a coffee
Share

Profile

author

Masa

都内のIT企業で働くエンジニア
自分が学んだことをブログでわかりやすく発信していきながらスキルアップを目指していきます!

buy me a coffee