Da Vinci Studio インフラ基盤部 SRE チームの林田です。
今回は前回わたくしがお送りした
EKS 素人が MAU ウン10万人以上のサービスのクラスターの k8s バージョンアップをやった話【前編】 - Da Vinci Studio Blog
の後編をお送り致します。
まずは前編のおさらい
前編では主に以下のことを書きました。
- EKS (※ Amazon Elastic Kubernetes Service) の k8s (※ Kubernetes) バージョンアップとは
- バージョンアップの事前準備について
- 大変なところ
そして後編
前編では結びの言葉として以下のように書きました。
果たして無事サービスにダウンタイムを発生させることなくアップデート出来たのか!?
続きは【後編】に書こうと思います~ 。
そして結果どうだったのか、、?
無事ダウンタイムなくアップデート出来たのか!?
↓ スクロール
残念ながらダメでした orz
正確に言いますと本番環境は大丈夫でしたが、ステージング環境でやらかしてしまいました。
「な~んだ、ステージング環境ならいいじゃん」
確かにそうなのですが、弊社では諸事情によりステージング環境が壊れると以下のように色々困ることがあります。
- 本番のデプロイにステージング環境を使用している。
- ステージング環境にて外部にテスト用途として公開している API がある。
特に前者に関しては弊社では1日に何回も本番のデプロイを行いますので
あくまでも関係者への影響に留まりますが、色んな方にご迷惑をお掛けしたので大変焦りました。
具体的にどんな失敗をしたか
今回アップデートした k8s バージョンですが、v1.21
から v1.22
にアップデートしました。
そしてここがまず勘違いだったのですが、今回の v1.22
へのアップデートにて
デフォルトコンテナランタイムが dockerd
から containerd
に変更されると思ってました。
※ 正確には次の次の v1.24
から変更されるみたいです。
Amazon EKS は、Kubernetes バージョン 1.24 のリリース以降、Dockershim のサポートも終了しました。
バージョン 1.24 以降、公式に公開される Amazon EKS AMI のランタイムは、containerd のみです。
※ コンテナランタイムとは?
とはいえ、前述のとおり勘違いしておりましたので
ワーカーノード(EC2インスタンス)起動時に
コンテナランタイムとして明示的に dockerd を使うよう
起動テンプレートの userdata を変更しました。
※ 起動テンプレートとは?
※ userdata とは?
具体的にどんな感じに変更したかと言うと、、、
MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="//" --// Content-Type: text/x-shellscript; charset="us-ascii" #!/bin/bash sed -i '/^CONTAINER_RUNTIME=/a CONTAINER_RUNTIME=docker' /etc/eks/bootstrap.sh --//--
こんな感じに sed コマンドを利用して
bootstrap.sh の CONTAINER_RUNTIME
という環境変数の値を dockerd
に変更しました。
※ 補足ですが、 EKS でワーカーノードを起動すると
上記の bootstrap.sh というシェルスクリプトが自動実行され必要な設定がセットアップされます。
bootstrap.sh の中身はここで確認出来ます。
図にすると以下のようになります。
これによりコンテナランタイムとして dockerd が選択されるはず、、
また今回はマイグレーション方式
でワーカノードのアップグレードを行います。
つまり既存のノードグループを残したまま
新しいバージョンのノードグループを作成します。
※ 余談ですが、この方式には障害発生時にロールバックが簡単に出来るという利点があります。
よって以下のように一時的に、v1.21 のノードと v1.22 のノードが共存する形になります。
さて無事構想を練ることが出来たので構成管理ツールのコードを更新します。
※ 弊社では構成管理ツールに terraform を使用しております。
※ terraform とは?
www.terraform.io
更新後のコードは以下になります。
# これは既存のノードグループ。何も変更しない resource "aws_eks_node_group" "eks-worker" { cluster_name = "hoge-cluster" node_group_name = "eks-worker" node_role_arn = "arn:aws:iam::1234567890:role/role-eks-worker" launch_template { name = aws_launch_template.eks-worker-template.name version = aws_launch_template.eks-worker-teamplate.latest_version } tags = { "Name" = "eks-worker" } version = "1.21" subnet_ids = [ "subnet-xxxxxx", "subnet-yyyyyy", ] depends_on = [ aws_launch_template.eks-worker-template ] # 以下略 ... } # 今回作成したノードグループ。分かり易いように suffix にバージョンを付けました。 resource "aws_eks_node_group" "eks-worker-v1-22" { cluster_name = "hoge-cluster" node_group_name = "eks-worker-v1-22" node_role_arn = "arn:aws:iam::1234567890:role/role-eks-worker" launch_template { name = aws_launch_template.eks-worker-template.name version = aws_launch_template.eks-worker-teamplate.latest_version } tags = { "Name" = "eks-worker-v1-22" } # ここで新バージョンを指定 version = "1.22" subnet_ids = [ "subnet-xxxxxx", "subnet-yyyyyy", ] depends_on = [ aws_launch_template.eks-worker-template ] # 以下略 ... } resource "aws_launch_template" "eks-worker-template" { name = "eks-worker-template" user_data = base64encode(<<EOF MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="//" --// #!/bin/bash set -o xtrace # ここでコンテナランタイムに dockerd を指定 sed -i '/^CONTAINER_RUNTIME=/a CONTAINER_RUNTIME=docker' /etc/eks/bootstrap.sh --//-- EOF ) instance_type = "m5a.large" block_device_mappings { device_name = "/dev/xvda" ebs { volume_size = 20 volume_type = "gp3" } } # 以下略 ... }
一応コードの説明をしておきますと、、、
- 既存の aws_launch_template(起動テンプレート)
eks-worker-template
にuser_data
を追加して、「明示的にコンテナランタイムに dockerd を指定」 - 新しい aws_eks_node_group(ノードグループ)
eks-worker-v1-22
を作成
これでコードの更新が完了したので apply します。
想定通り、v1.22 のノードグループが作成されているようです。
terraform apply aws_launch_template.eks-worker-template: Modifying... [id=lt-0000xxxx11111] aws_launch_template.eks-worker-template: Modifications complete after 0s [id=lt-0000xxxx11111] aws_eks_node_group.eks-worker-v1-22: Creating...
処理が完了するまで暫く待ちます、、、、
ログを見て頂いたらお解りのように20分経過しても終わりません、、長い、、(;^_^A
EC2 インスタンスを数台起動しているからだと思われます。
aws_eks_node_group.eks-worker-v-1-22: Still creating... [22m50s elapsed] aws_eks_node_group.eks-worker-v-1-22: Still creating... [23m0s elapsed] aws_eks_node_group.eks-worker-v-1-22: Still creating... [23m10s elapsed] aws_eks_node_group.eks-worker-v-1-22: Still creating... [23m20s elapsed]
まあ気長に待つか、、と思った矢先!
下記のエラーメッセージが表示されノードグループの作成に失敗してしまいました!
╷ │ Error: error waiting for EKS Node Group (hoge-cluster:eks-worker) version update (63fdf20b-90d1-3fc3-ac0c-2c0be351fa95): unexpected state 'Failed', wanted target 'Successful'. last error: 1 error occurred: │ * : NodeCreationFailure: Couldn't proceed with upgrade process as new nodes are not joining node group eks-worker │ │ │ │ with aws_eks_node_group.eks-worker, │ on eks-node-group.tf line 1, in resource "aws_eks_node_group" "eks-worker": │ 1: resource "aws_eks_node_group" "eks-worker" { │ ╵ ╷ │ Error: error waiting for EKS Node Group (hoge-cluster:eks-worker-v1-22) to create: unexpected state 'CREATE_FAILED', wanted target 'ACTIVE'. last error: 1 error occurred: │ * i-0251b8f22831fd2ce, i-03698f503b86a2455, i-039eb2b2b8a621eef, i-05125f93bf0fe1a30, i-069da1793770b10e0, i-0c17ae0f26687f17f: NodeCreationFailure: Instances failed to join the kubernetes cluster │ │ │ │ with aws_eks_node_group.eks-worker-v1-22, │ on eks-node-group.tf line 29, in resource "aws_eks_node_group" "eks-worker-v1-22": │ 29: resource "aws_eks_node_group" "eks-worker-v1-22" { │ ╵
AWS の Web コンソールを見てノードグループの状態を確認してみます。
がしかし、「Instances failed to join the kubernetes cluster」というエラーメッセージのみ、、
※ う~~ん、なんて不親切なんだ、、
このあと何度か(※おそらく5、6回)コードを修正してリトライするも失敗を繰り返します。
エラーメッセージは相変わらず、、
NodeCreationFailure: Instances failed to join the kubernetes cluster
そこで、userdata の書き方、特に sed コマンドの書き方に間違いがあるのかと思い
試しにコメントアウトしてみました。
user_data = base64encode(<<EOF MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="//" --// #!/bin/bash set -o xtrace # コメントアウト # sed -i '/^CONTAINER_RUNTIME=/a CONTAINER_RUNTIME=docker' /etc/eks/bootstrap.sh --//-- EOF )
すると、、
なんと無事成功!
やはり sed コマンドが間違っていたようでした。
Apply complete! Resources: 1 added, 2 changed, 0 destroyed.
一体なにがおかしいのだろう?とソースコードを眺めていると
実にしょ~もないミスをしていることに気づきました。
sed -i '/^CONTAINER_RUNTIME=/a CONTAINER_RUNTIME=docker' /etc/eks/bootstrap.sh
そう、docker
ではなく dockerd
って書かないといけないんですね~
いや~しょ~もない
× CONTAINER_RUNTIME=docker 〇 CONTAINER_RUNTIME=dockerd
さ~て、原因もわかったことだしリトライするか~と思った矢先、、
アプリケーション開発チームの方からチャットで問い合わせが来ました。
(-_-)「なんかステージング環境繋がらないんだけど何かやってます?」
俺「ふぁっ!?」
私の方でもブラウザから特定のサービスにリクエストを投げてみました。
すると、、
恐怖の 502 Bad Gateway 発生です。
ぎゃ~~~~(+o+)
これは想定外でした。
というのも今回は前述したとおりマイグレーション方式を取っているため
既存のノードグループは変更されないので、その上に乗っているサービスには影響しないはずです。
状況を理解出来ずにいると、あることに気づきます、、
terrafrom のログを確認してみると、、、
Apply complete! Resources: 1 added, 2 changed, 0 destroyed.
あれ?「2 changed」ってなってる!? Why !?
ここでおさらいになりますが、今回行った変更は下記2点です。
- 新しいノードグループの作成
- 既存の launch template の変更
なので
1 added, 1 changed
となると思っていたのに、、、現実は、、、
1 added, 2 changed
一体何が変更されてしまったのかと思い、さらにログを遡ってみます、、、
すると!
aws_eks_node_group.eks-worker: Modifying... [id=hoge-cluster:eks-worker] aws_eks_node_group.eks-worker-v1-22: Still creating... [10s elapsed] aws_eks_node_group.eks-worker: Still modifying... [id=hoge-cluster:eks-worker, 10s elapsed] aws_eks_node_group.eks-worker-v1-22: Still creating... [20s elapsed] aws_eks_node_group.eks-worker: Still modifying... [id=hoge-cluster:eks-worker, 20s elapsed] aws_eks_node_group.eks-worker-v1-22: Still creating... [30s elapsed] aws_eks_node_group.eks-worker: Still modifying... [id=hoge-cluster:eks-worker, 30s elapsed]
注目↓↓↓
aws_eks_node_group.eks-worker: Modifying... [id=hoge-cluster:eks-worker]
何ということでしょう、、、 、
既存のノードグループも意図せず更新されちゃってます。
原因を探ります。
ここでまたもやおさらいですが前述した構成図を見返してみます。
前述した通り今回変更した launch template は、既存のノードグループも利用しているものです。
よってそれを変更したがために既存のノードグループにも変更がかかったということですね~
納得。
※ 単に自分が無知であったため、userdata の内容を変えただけでノードグループの変更がかかると思っていなかっただけです。はい。
がしかし、ここで新たな疑問が生まれました。
というのもノードグループに変更がかかっただけで、502 Bad Gateway になるのは不可解だからです。
というのも AWS のマネージドノードグループに関するドキュメントを読んでみると
ノードグループに変更がかかった場合下記のように自動で処理が行われるようだからです。
- 配下の ノード(※EC2 インスタンス)が自動で新しい物に1台づつ入れ替わる
- 乗っている Pod は自動で drain される
[Update strategy] (更新戦略) で、次のいずれかのオプションを選択します。
[Rolling update] (ローリング更新) - このオプションは、クラスターの pod の中断予算を尊重します。pod 中断予算の問題により、Amazon EKS がこのノードグループで実行されている pods を正常にドレーンできない場合、更新が失敗します。
ここに書かれている通りローリングアップデートが行われるようなので、これなら 502 は発生しないはずです。
はて(・・?
とりあえず何が起こっているのか確認するためクラスターの状態を確認してみます。
すると、、
kubectl get pod --all-namespaces NAMESPACE NAME READY STATUS RESTARTS default hoge-pod-7b44b7cd-jcsdz 0/1 ImagePullBackOff 5 default fuga-pod-d7f89f847-vbbkd 0/1 ImagePullBackOff 5 default uga-pod-7b8c77d4bd-v9mqp 0/1 ImagePullBackOff 5
Pod のステータスが ImagePullBackOff
になっている~~~!!(+o+)
Why!?
※ ImagePullBackOff ステータスとは?
kubernetes.io
kubernetes 公式ドキュメントによると
ImagePullBackOffステータスは、KubernetesがコンテナイメージをPullできないために、コンテナを開始できないことを意味します
とのことなので、要はコンテナイメージの Pull に失敗しているようです。
原因を探るためより詳細なログを見てみます。
すると、、
kubectl describe pod hoge-pod-7b44b7cd-jcsdz Message ------- Failed to pull image "nginx:1.2x": rpc error: code = Unknown desc = Error response from daemon: toomanyrequests: You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit
Docker Hub 「You have reached your pull rate limit.」
俺 「(゜゜)!!」
何ということでしょう、、
Docker Hub の Pull 回数制限に引っかかってしまったようです、、
※ Docker Hub の Pull 回数制限とは?
匿名および無料の Docker Hub ユーザーは、6 時間あたり 100 および 200 のコンテナー イメージのプル リクエストに制限されています
匿名で使用する場合、6 時間あたり 100 個のコンテナ イメージ リクエストのレート制限
つまりこの小一時間くらいでいつの間にか私は、100回以上 Pull してしまったわけですね、、
はて?そんなに Pull した覚えがないぞと思いながらも少し記憶を辿って自分が行った行為を振り返ってみます。
- Launch Template の設定を書き換えました。
- 新ノードグループが作成を試みました。
- 旧ノードグループが意図せず再起動が走りました。
- エラーになり失敗しました。
- Launch Template の設定を修正しました。1に戻る
そう Launch Template の設定を何にも考えずに書き換えまくったせいで
ノードグループの再起動が何度もかかってしまったわけですね~~
当然乗っている Pod も再起動がかかりイメージの Pull も連発してしまうと。
さらに全 Pod 数を数えてみると、、、
kubectl get pod --all-namespaces | wc -l 363
この中でいくつかの Pod はイメージキャッシュが無効になっていたようであえなく 100 を超えてしまったというわけです。はい。
※ イメージキャッシュとは?
取り敢えず現状はっきりしていることは、、
これから 6 時間待たないと Pull 出来ない
という事実です。
そんなに待ってられるかよ、ということで対策を取ります。
いくつか案が出ました。
- Docker アカウントを作成し、ログインして Pull する。(※ 匿名での Pull を止める)
- ECR に同イメージを Push して、そこから Pull する。
今回はより恒久的に安全な方法を取りたかったので後者の ECR に同イメージを Push して、そこから Pull する
を選びました。
※ ECR (Amazon Elastic Container Registry) とは?
これなら必要な IAM 権限さえあれば回数制限とか気にせず Pull 出来ます。
とは言え対象イメージ数が相当数ありましたので結構大変な作業でした。
一つ一つを Docker Hub からローカルに Pull して来てタグ付けして ECR に Push しないといけない。
※ これについては詳細を別途ブログにしようと思います。
教訓
そもそも Launch Template を弄る時は下記のような構成にすべきでした。
ちゃんとそれぞれのノードグループ用の Launch Template を用意する形ですね~~
これなら既存のノードに影響を与えない。
おしまい
今回のステージング環境での失敗もあり
後日の本番環境での作業は万全の状態で臨むことが出来ました。
結果ユーザー様に影響を与えるような大きなアクシデントなくアップデートを終えることが出来ましたとさ~
めでたしめでたし!
Da Vinci Studioでは、働く仲間を募集しています!
興味のある方は こちら か recruit@da-vinci-studio.net までご連絡ください。