大した検証じゃないけど、EKSでステートフルなワークロードをアップグレードする際の手順を試してみる。
- Kubernetes のアップグレード
- ステートフルリソースありのノードインプレースアップグレード
- ステートフルリソースありのEKS MNG B/Gアップグレード
- ステートフルリソースありのCluster Migration
Kubernetes のアップグレード
ZOZOさんのSpeakerdeckがよくまとまっているが、Kubernetesのバージョンアップグレードについて、大きくわけるとインプレースアップグレードとBlue/Greenアップグレードの2つになる。
さらにBlue/GreenアップグレードについてはNode Groupレベルで、新バージョンのNode Groupをクラスタ内に作って切り替えする方法と、クラスターレベルで新バージョンのクラスターを新た作り切り替えする方法(Cluster Migration)がある。
それぞれの特徴は下記です。
①[インプレース]
- EKSのManaged Node Groupを使うことで比較的対応が楽だが、Upgrade時のNodeの上げ下げやPodのdrain/cordonがコントロールできない。
- 問題あった際にロールバックが困難。
- Control PlaneのバージョンとData Planeバージョンが一時的にズレ、それによりプラグインによっては問題がでる可能性がある。
②[B/G (Node Groupレベル) ]
- 問題あった際にロールバックしやすい(古いバージョンのNode Groupがあるため)。
- PodのNode Group間移動を自らの作業で実施する必要がある(NodeSelectorやNodeAffinityで)。
- Control PlaneのバージョンとData Planeバージョンが一時的にズレ、それによりプラグインによっては問題がでる可能性がある。
③[B/G (Cluusterレベル) ]
- Control Planeのバージョンも合わせて一気にバージョン変更が可能。
- ロールバックしやすい。
- 新規クラスタへの移行手順が複雑になる。(バックアップツール等の利用で効率化が必要)
- トラフィックはALBのTGやCDNの向き先変更にて実施する。
安全重視で、クラスタ作成を自動化するツールを実装されたりして③を選ぶ企業が大規模だと多い印象。
※参考:https://creators-note.chatwork.com/entry/2021/07/14/150515
ただ、クラスタレベルのBlue/Greenアップグレードの作業の大変さを考え、①を選ぶ層も割といる。
ステートフルリソースありのノードインプレースアップグレード
MNGの場合
- MNGの更新動作でPodがDrain/Cordon→新規ノードに再スケジュールされる。
- MNG(= auto scaling group)がAZをまたがって作成している場合に別AZでNodeが立つ可能性がある。その場合にPodが再スケジュールできない問題が発生するので、AZごとにMNGを作成することが望ましい
- https://aws.amazon.com/jp/blogs/containers/amazon-eks-cluster-multi-zone-auto-scaling-groups/
If your Kubernetes pods use EBS volumes, then you should set up your Auto Scaling groups to have one for every Availability Zone where you want to run workloads.
- https://aws.amazon.com/jp/blogs/containers/amazon-eks-cluster-multi-zone-auto-scaling-groups/
- Cluster Autoscalerと併用している場合はAZ偏る可能性が高いので、Mustでわける
- https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler/cloudprovider/aws#common-notes-and-gotchas
If you’re using Persistent Volumes, your deployment needs to run in the same AZ as where the EBS volume is, otherwise the pod scheduling could fail if it is scheduled in a different AZ and cannot find the EBS volume. To
- https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler/cloudprovider/aws#common-notes-and-gotchas
Karpenterの場合
- Volumeを持っているPodがいた場合に、そのAZを認識してNodeをスケジュールしてくれるので、上記のようなリスクはないはず。
- https://karpenter.sh/docs/concepts/scheduling/#persistent-volume-topology
ステートフルリソースありのEKS MNG B/Gアップグレード
MNG間のPod移行をNode Affinity等でやるイメージ。新NodeでもAZが同じであれば同じPVにてStatefulsetが起動する。
EKS MNG B/Gアップグレードを想定して、StatefulsetをMNG間でNodeAffinityで移行することを試してみる。
事前準備
EBS addon
マネコンからでも有効化できたが、IRSA作成が勝手にされなかったので、eksctlでやり直す。eksctl経由ならIRSAまでやってくれた
% aws eks describe-addon-versions --addon-name aws-ebs-csi-driver % eksctl create addon --name aws-ebs-csi-driver --version v1.5.2-eksbuild.1 --attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy --cluster
MNG作成
既存のMNGがあったので、検証用にもう1個MNGを追加。
eksctl create nodegroup -f nodegroup/mng.yaml
# dev-cluster.yaml apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: keda-test region: ap-northeast-1 managedNodeGroups: - name: mng-2 labels: { role: workers } instanceType: m6g.large desiredCapacity: 2 minSize: 2 maxSize: 2 volumeSize: 80 privateNetworking: false updateConfig: maxUnavailablePercentage: 100
Statefulset作成
apiVersion: apps/v1 kind: StatefulSet metadata: name: mysql-ebs namespace: ebs-test labels: app.kubernetes.io/created-by: eks-workshop app.kubernetes.io/team: database spec: replicas: 1 selector: matchLabels: app.kubernetes.io/name: mysql-ebs app.kubernetes.io/instance: mysql-ebs app.kubernetes.io/component: mysql-ebs serviceName: mysql template: metadata: labels: app.kubernetes.io/name: mysql-ebs app.kubernetes.io/instance: mysql-ebs app.kubernetes.io/component: mysql-ebs spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: eks.amazonaws.com/nodegroup operator: In values: - ng-c5197504 - key: topology.kubernetes.io/zone operator: In values: - ap-northeast-1c containers: - name: mysql image: "public.ecr.aws/docker/library/mysql:5.7" args: - "--ignore-db-dir=lost+found" imagePullPolicy: IfNotPresent env: - name: MYSQL_ROOT_PASSWORD value: my-secret-pw - name: MYSQL_USER value: test - name: MYSQL_PASSWORD value: test - name: MYSQL_DATABASE value: catalog ports: - name: mysql containerPort: 3306 protocol: TCP volumeMounts: - name: data mountPath: /var/lib/mysql volumeClaimTemplates: - metadata: name: data spec: accessModes: ["ReadWriteOnce"] storageClassName: gp2 resources: requests: storage: 30Gi
NodeGroup移行のテスト
AZを同じにしてNodeAffinityで新NodeGroupになるように(eks.amazonaws.com/nodegroup
)Statefulsetを移行したら、ちゃんとEBSは引き継がれた
% kg no -L topology.kubernetes.io/zone,eks.amazonaws.com/nodegroup NAME STATUS ROLES AGE VERSION ZONE NODEGROUP ip-192-168-13-124.ap-northeast-1.compute.internal Ready <none> 80s v1.25.16-eks-ae9a62a ap-northeast-1d mng-2 ip-192-168-34-247.ap-northeast-1.compute.internal Ready <none> 301d v1.25.11-eks-a5565ad ap-northeast-1a ng-c5197504 ip-192-168-65-238.ap-northeast-1.compute.internal Ready <none> 301d v1.25.11-eks-a5565ad ap-northeast-1c ng-c5197504 ip-192-168-73-84.ap-northeast-1.compute.internal Ready <none> 81s v1.25.16-eks-ae9a62a ap-northeast-1c mng-2
旧NodeGroupでPV書き込み
% kg po -n ebs-test -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES mysql-ebs-0 1/1 Running 0 3m30s 192.168.78.42 ip-192-168-65-238.ap-northeast-1.compute.internal <none> <none> % kubectl exec mysql-ebs-0 -n ebs-test -- bash -c "echo 123 > /var/lib/mysql/test.txt" % kubectl exec mysql-ebs-0 -n ebs-test -- ls -larth /var/lib/mysql/ | grep -i test -rw-r--r-- 1 root root 4 Jun 2 04:02 test.txt
新NodeGroupへ移行、PVチェック
StatefulsetのNodeAffinityを新NGに変更
apiVersion: apps/v1 kind: StatefulSet metadata: name: mysql-ebs ... spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: eks.amazonaws.com/nodegroup operator: In values: # - ng-c5197504 - mng-2 - key: topology.kubernetes.io/zone operator: In values: - ap-northeast-1c
% kg po -n ebs-test -o wide -w NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES mysql-ebs-0 0/1 ContainerCreating 0 5s <none> ip-192-168-73-84.ap-northeast-1.compute.internal <none> <none> mysql-ebs-0 1/1 Running 0 18s 192.168.85.105 ip-192-168-73-84.ap-northeast-1.compute.internal <none> <none> % kubectl exec mysql-ebs-0 -n ebs-test -- ls -larth /var/lib/mysql/ | grep -i test -rw-r--r-- 1 mysql root 4 Jun 2 04:02 test.txt
ちゃんと移行できたことを確認。
ステートフルリソースありのCluster Migration
- ボリュームの移行方法は2通り
- 1/EBSスナップショットからEBSを再作成して新EBSを新クラスタのStatefulsetに紐づける
- 2/Vereloなどのクラスタバックアップツールを使って持って行く
- リソースが多い場合はこちらがよさそう
- Vereloを利用する例
- Nulabさん:Backlog の Amazon EKS クラスターを Blue-Green アップデートするためにやっていること | 株式会社ヌーラボ(Nulab inc.)
- クラスタ間のリソース移行をマニフェストではなくVereloを利用
- WantedlyさんもUpgradeではないが、クラスタ移行でステートフルリソースバックアップにVereloを利用