
入社2か月で挑戦!無停止でEKSを2バージョンアップ-準備編-
はじめに
こんにちは。 サービスプラットフォームグループの武井です。 SHIFTには2024年9月にSHIFTに中途入社し、現在は社内の開発部門でインフラ担当をしています。
開発チームにJoinして早々に本番環境のAmazon Elastic Kubernetes Service(以降EKS)のアップグレードという大役を任せていただくことになったので今回はその話を2回に分けてしたいと思います。
(2024年中に出そうと思っていましたが、なんだかんだで年を跨いでしまいました・・・)
その他、社外の活動としてCloudNativeをテーマとしたコミュニティにも参加しております。
活動内容に関する記事を過去に書いておりますので、ご興味がありましたらご覧ください。
EKSとは
ご存じの方も多いと思いますが、EKSとはAWSが提供するマネージドなKubernetes(以降K8s)サービスです。 コントロールプレーンの運用をある程度AWSに任せることができるため、運用負荷軽減の観点で導入している方も多いと思います。
尚、公式ドキュメント ではこのように紹介されています。
Amazon Elastic Kubernetes Service (Amazon EKS) は、Amazon Web Services (AWS) 上で、独自の Kubernetes コントロールプレーンをインストール、運用、保守する必要がないマネージド型サービスです。Kubernetes は、コンテナ化されたアプリケーションの管理、スケーリング、デプロイを自動化するオープンソースシステムです。
運用・保守する必要がないとはいえ、当然ながらユーザ側の責任が完全に無くなるわけではありません。 特に、K8s特有のバージョンライフサイクルの短さによるEOL問題はEKSであってもついて回ります。 (ユーザ側の責任において、アップグレードの計画と実施を行う必要があります。)
では、次の章から私が立てたアップグレード計画と実際の作業について説明したいと思います。
背景
序章にてK8sのバージョンに関するライフサイクルは短いということを述べましたが、EKSもリリースとサポートのサイクルはアップストリーム側に従う旨が公式ドキュメント にも明記されています。
コミュニティは、平均して 4 か月に一度、新しい Kubernetes のマイナーバージョン (1.31など) をリリースします。Amazon EKS は、マイナーバージョンのアップストリームリリースと非推奨サイクルに従います。新しい Kubernetes バージョンが Amazon EKS で利用可能になったら、利用可能な最新のバージョンが使用できるよう、クラスターをタイムリーに更新することをお勧めします。
上記のように、マネージドサービスであってもK8sを使う以上は頻繁なアップグレードが必要になります。
当時私たちがホストしているEKSは当時Version1.28で稼働しており、標準EOLが2024年11月26日に迫っていました。 この早急な対応が必要な状況の中、私は運よく(?)9月のタイミングで入社をし、チームにJoinしてすぐにこのタスクを任せていただくことになりました。
本番稼働しているK8sに触れるのは初めてでしたが、趣味でK8sクラスタを1から手動で組んでみたり、資格を取得したりしていたので不安よりもその知識がようやく活かされることの喜びの方が大きかったです。
アップグレード計画
さて、本章では私が実際に立てたアップグレードの計画を解説したいと思います。
まずは、現在稼働しているEKSとその周辺システムの状況を把握するところからスタートしました。 一口にEKSのアップグレードといっても、サーバレスやIaCの有無、そしてその管理方法などでもアプローチは変わってくると考えたからです。また、当時は入社1か月目ということもあり周辺の環境が全く頭に入っていなかったため、その把握を最優先に行う必要がありました。そして、それを踏まえた上で最適なアップグレードの方針を立てようと考えました。
それでは、私が確認した観点をいくつか紹介したいと思います。
EKSの管理方法
私は最初にEKSがどのように管理されているかを確認しました。具体的には、IaCを使ってのバージョンアップ実現可否です。 結論からお伝えすると、私たちがホストしているEKSはDeploymentなどのK8sリソースも含めて全てTerraformで管理されており、バージョンアップも数行のコード変更で実現できそうであることが判明しました。
TerraformをはじめとしたIaCを前提としたアップグレードには以下のメリットがあると考えています。
環境ごとの設定の差異がなくなる
ヒューマンエラーの発生確率を下げる
手順の簡素化
下記のディレクトリ構成にあるenv-vars配下のファイルで示している通り、私たちのプロダクトはWork/開発/Staging/本番合わせて6環境あります。この6環境を手動でアップグレードするとなると、設定の差異を生み出すリスクに繋がりかねません。また、今回は次回のアップグレードまでの期間に余裕を持たせるため、2バージョン一気に上げることになりました。K8sのアップグレードは必ずバージョンを1つずつ上げなければならないため、なるべく手動で行う手順を減らすことが望ましいと考えました。
eks
├── close.sh
├── env-vars
│ ├── cc.tfvars
│ ├── dev.tfvars
│ ├── lab.tfvars
│ ├── prod.tfvars
│ ├── stg.tfvars
│ └── stg2.tfvars
├── launch.sh
├── main.tf
├── outputs.tf
├── providers.tf
└── variables.tf
バージョンアップによる影響有無
K8sの新しいバージョンでは、古いAPIバージョンが廃止されて新しいAPIバージョンが導入されることがあります。もし古いAPIバージョンに依存しているリソースやマニフェストが動作している場合、バージョンアップに伴い動作しなくなる可能性があるため注意する必要があります。また、導入しているアドオンとのバージョン互換性も考慮しなければなりません。
しかしながら、クラスタ内で動作しているリソースのAPIのバージョンを1つずつ行うのは手間がかかるため、Upgrade Insights と呼ばれる、バージョンアップ前に非推奨のAPIを検知してくれる機能を使いました。
以下のスクリーンショットのように、現状でバージョンアップを行っても影響が無いようであればステータスが合格となり、逆に問題があるようであれば警告として表示を出してくれます。

尚、アドオンに関して今回はそれぞれ調査を行いました。
以下にアドオン別の調査方法を記していますが、基本的にはGithubや公式ドキュメントのリリース内容を見て、バージョンの互換性があるかを確認していきます。
AWS Load Balancer Controller
Github のリリース内容を確認して、バージョンアップが必要かを判断します。
現在のバージョンの確認コマンドは以下です。
kubectl get deployment aws-load-balancer-controller -n kube-system -o yaml | grep "image:"
Kubernetes Metrics Server
こちらもGithub のリリース内容を確認して、バージョンアップが必要かを判断します。
現在のバージョンの確認コマンドは以下です。
kubectl get deployment metrics-server -n kube-system -o yaml | grep "image:"
Amazon VPC CNI plugin for Kubernetes
AWS公式のユーザガイド を確認して、バージョンアップが必要かを判断します。
現在のバージョンの確認コマンドは以下です。
kubectl describe daemonset aws-node -n kube-system | grep amazon-k8s-cni: | cut -d : -f 3
CoreDNS
こちらもAWS公式のユーザガイド を確認して、バージョンアップが必要かを判断します。
現在のバージョンの確認コマンドは以下です。
kubectl describe deployment coredns -n kube-system | grep Image | cut -d ":" -f 3
kube-proxy
こちらもAWS公式のユーザガイド を確認して、バージョンアップが必要かを判断します。
現在のバージョンの確認コマンドは以下です。
kubectl describe daemonset kube-proxy -n kube-system | grep Image | cut -d ":" -f 3
Cluster Autoscaler
こちらは、これまで紹介したアドオンと異なり、更新後のK8sバージョンに合わせる必要があるためクラスタのバージョンアップ前に更新が必須です。
上記の注意事項は、ユーザガイド にも明記されています。
(オプション) クラスターを更新する前に、そのクラスターに Kubernetes Cluster Autoscaler をデプロイしてある場合は、更新後の Kubernetes のメジャーバージョンとマイナーバージョンに一致するように、Cluster Autoscaler を最新バージョンに更新します。)
バージョンの確認コマンドは以下です。
kubectl get deployment cluster-autoscaler -n kube-system -o yaml | grep "image:"
Podの設定
EKSのアップグレードを行うにあたってPodの動きを把握しておくことは重要と考えました。 アップグレード中はどうしてもPodのNode間の移動が発生します。移動といっても実際はPodを一度削除し、Deployment Manifestに記載のあるPodの設定に基づいて再作成されるという動きをします。Replicaを複数設定していたにも関わらずPodが全て消えてしまったり、再作成が上手く行えずPodが起動しないといった事象が起こらないように、Podの設定内容を確認して動きを事前に把握する必要があると考えました。以下に重要と思われるPodの機能を示します。
PodDisruptionBudget
PodDisruptionBudgetとは、Kubernetesでアプリケーションの高可用性を確保するためのリソースです。最小Pod数を指定することが可能で、指定した数のPodが常に稼働するよう保証されます。この設定を行うことで、今回のようなアップグレード時にもサービスの中断を防ぎつつアプリケーションの健全性を維持することができます。
Manifest,Terraformそれぞれのサンプルコードは以下です。
minAvailableには利用可能なPodの最小数を指定します。
この例では、少なくとも2つのPodが利用可能である必要があります。
# Manifest
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: my-app
# Terraform
provider "kubernetes" {
config_path = "~/.kube/config"
}
resource "kubernetes_pod_disruption_budget" "example" {
metadata {
name = "my-pdb"
}
spec {
min_available = 2
selector {
match_labels = {
app = "my-app"
}
}
}
}
また、PDBではminAvailableの代わりにmaxUnavailableを使用することもできます。 maxUnavailableには利用できなくてもよいPodの最大数を指定します。
この例では、1つのPodが利用できなくてもよいことを示しています。
# Manifest
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-pdb
spec:
maxUnavailable: 1
selector:
matchLabels:
app: my-app
# Terraform
provider "kubernetes" {
config_path = "~/.kube/config"
}
resource "kubernetes_pod_disruption_budget" "example" {
metadata {
name = "my-pdb"
}
spec {
max_unavailable = 1
selector {
match_labels = {
app = "my-app"
}
}
}
}
私たちはEKS NodeにEC2&ノードグループを使用しているのですが、今回2バージョンを一気に上げるにあたりノードグループのバージョンアップも必要になってきます。(コントロールプレーンとのマイナーバージョンの差異が+/-1でなければならないため)
drain+PDBを使ってNodeをアップグレードするのがオーソドックスな方法かと思いますが、今回はアップグレードの期日が迫ってきているのと、なるべくkubectlではなくTerraformからキックしてバージョンアップを行いたいと考えたためnodeSelectorのvalueを変更してPodの移動を計画しました。
Deployment strategy(RollingUpdate)
KubernetesのDeploymentには、主に以下の2つのデプロイメント戦略があります。
K8sのデフォルト設定では既にRollingUpdateになっているはずですが、念のため確認しました。
この設定が適切に行えていれば、nodeSelectorのvalueを変更しPodを再デプロイすることでダウンタイムを抑えて安全にPodの移動を行うことができると考えました。
RollingUpdate(ローリングアップデート):
概要: 新しいバージョンのPodを少しずつデプロイし、古いバージョンのPodを徐々に置き換えていく方法です。
利点: サービスのダウンタイムを最小限に抑えながらアップデートを行うことができます。
設定: maxUnavailable(同時に停止できるPodの最大数)とmaxSurge(同時に作成できる新しいPodの最大数)を指定できます。
以下、サンプルコードです。
# Manifest
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
# Terraform
provider "kubernetes" {
config_path = "~/.kube/config"
}
resource "kubernetes_deployment" "example" {
metadata {
name = "my-deployment"
labels = {
app = "my-app"
}
}
spec {
replicas = 3
strategy {
type = "RollingUpdate"
rolling_update {
max_unavailable = 1
max_surge = 1
}
}
Recreate(再作成)
概要: すべての古いバージョンのPodを一度に削除し、その後新しいバージョンのPodを作成する方法です。
利点: 簡単で直感的な方法でデプロイを実現できます。二重起動をしたくない場合などに有効です。
設定: 特に追加の設定は必要ありません。
サンプルコードは以下です。
とはいえ、typeのvalueをRecreateに変えるだけなので特段注意が必要なことはありません。
# Manifest
strategy:
type: Recreate
# Terraform
strategy {
type = "Recreate"
}
Readiness Probe/Liveness Probe
K8sでは、Podの状態を監視するためにReadiness ProbeとLiveness Probeの2つのプローブが提供されています。
クラスタのバージョンアップの過程でPodの移動(再作成)がされるため、これらのプローブを使用することでアプリケーションの健全性を確認し、適切にPodが移動(再作成)されたかを判断することができます。
Readiness Probe
概要: Readiness Probeは、Podがトラフィックを受け入れる準備ができているかどうかを確認するためのプローブです。Podが準備完了状態でない場合、そのPodにはトラフィックが送られません。
用途: アプリケーションの初期化が完了していない場合や、依存するサービスが利用可能でない場合などに使用されます。
設定方法:
HTTPリクエスト
TCPソケット
コマンドの実行
Liveness Probe
概要: Liveness Probeは、Podが正常に動作しているかどうかを確認するためのプローブです。Liveness Probeに失敗した場合、KubernetesはそのPodを再起動します。
用途: アプリケーションがデッドロック状態に陥ったり、応答しなくなった場合などに使用されます。
設定方法:
HTTPリクエスト
TCPソケット
コマンドの実行
サンプルコード
Readiness ProbeとLiveness Probeはどちらか片方ではなく合わせて設定することが多いかと思います。
以下にサンプルコードを示します。
# Manifest
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-container
image: my-image:latest
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
# Terraform
provider "kubernetes" {
config_path = "~/.kube/config"
}
resource "kubernetes_pod" "example" {
metadata {
name = "my-pod"
}
spec {
container {
name = "my-container"
image = "my-image:latest"
readiness_probe {
http_get {
path = "/healthz"
port = 8080
}
initial_delay_seconds = 5
period_seconds = 10
}
liveness_probe {
http_get {
path = "/healthz"
port = 8080
}
initial_delay_seconds = 5
period_seconds = 10
}
}
}
}
まとめ
今回のK8sを2バージョン上げるための準備として、以下の観点で準備を進めました。
EKSの管理方法
Deployment含めてTerraformでの管理が行われているため、なるべくterraform applyで変更する方針で計画
バージョンアップの影響有無
API Versionの互換性
クラスタインサイトの確認
各種アドオンのバージョン互換性
現バージョンと公式リリースノートを照らし合わせて確認
Podの設定を確認
PDB
今回は適用しなくても影響なしと判断
RollingUpdate
Readiness Probe/Liveness Probe
次回は実際に検証で起こった想定外の事象と、本番作業の手順&振り返りを簡単に紹介します。
執筆者プロフィール:武井 竜一
ネットワークエンジニアからキャリアをスタートさせ、OpenstackやOpenflow系SDNを扱った仮想化基盤プロジェクトのリードエンジニア、ソリューションアーキテクトなどを経験し2024年9月にSHIFTに入社。KubernetesとAWSが好き。
お問合せはお気軽に
SHIFTについて(コーポレートサイト)
SHIFTのサービスについて(サービスサイト)
SHIFTの導入事例
お役立ち資料はこちら
SHIFTの採用情報はこちら
PHOTO:UnsplashのPlanet Volumes