はじめに
EXNOA プラットフォームインフラ部の角です。
私たちのチームではEKSを2年ほど前から運用しており、モニタリングやセキュリティ対策の強化を継続して行っています。
最近では2022年の4月初旬に、EKSの1.22へのアップグレードが提供されるようになり、われわれも1.22へのアップグレードを完了させました。
今回の投稿では、EKSを運用して得た知見を紹介したいと思います。
- 運用していて大変だったこと
- 運用で工夫していること
- セキュリティ対策
なお、対象のシステムの構成やモニタリングの取り組みについては過去の記事で紹介していますので、合わせてご覧ください。
- Amazon EKSの採用とAWS Well-Architected フレームワークの実践|inside.dmm.com
- Datadogによるクラウドネイティブなモニタリングの実践|inside.dmm.com
やっぱりEKS(Kubernetes)の運用は大変なのか?
まず前提として、私達のチームではEKS 1.22のバージョンのクラスターを利用しています。ノードの管理はマネージドノードグループを利用しており、Fargateは利用していません。
主観的な意見になってしまいますが、
「EKSの運用は、大変ではないものの、運用のための時間的な余裕は確保しておくべきである」というのが私の考えです。
下記の5つの項目を通して説明したいと思います。
- EKSの運用で行っている具体的な作業
- Nginx Ingressのバージョンアップ
- Blue, Green方式でのNodeの入れ替え作業
- EKSの運用で一番困ったこと
- EKS vs ECS
EKSの運用で行っている具体的な作業
EKSは、AWSが提供するKubernetesのマネージドサービスのことで、運用する上で主に4つの作業が定期的に必要になります。
- アップグレードするバージョンの非推奨と削除されるAPIを確認して、必要であれば新しいバージョンにマニフェストを移行
- EKSのクラスターのアップグレード (1クリックで完了)
- クラスターのバージョンに対応するAMIにノードを入れ替え
- 必要に応じてkube-proxy, cni-plugin, coredns, Nginx Ingress, ArgoCDなどのKubernetesのaddonのアップデート
※ EKS 1.22からはクラスターのアップグレードに要する時間が大幅に短縮されています。
クラスターをアップグレードさせる場合は、1から4の作業のすべてが必要になることが多いです。ノードやaddonに脆弱性やバグが見つかった場合に、3、4のみを実行するパターンもあります。EKSとKubernetesの基本的なことを理解しておけば、1から4のいずれも難易度が高い作業ではないと思います。
参考になる資料やブログの記事もたくさんあります。そのため、冒頭で「運用が大変ではない」ということを述べました。では、なぜ「運用のための時間的な余裕」を確保しておくべきなのでしょうか?
その理由は、EKSとKubernetesのaddonの機能アップデートに追随するためです。
EKSの機能アップデートを例にすると、マネージドノードグループやEKSアドオンの機能はEKSが登場した当初は存在しない機能でした。マネージドノードグループは、自前でオートスケーリンググループを作成してノードを管理する、セルフマネージド型ノードよりも簡単にノードを管理できる機能です。
例えば、マネージドノードグループを利用しておくとデフォルトでノードが終了するときにノードのDrainが自動で実行されます。
EKSアドオンは、kube-proxy、 coreDNS、 Amazon VPC CNI plugin for KubernetesなどのEKSを利用する上で必要不可欠なaddonを、コンソールやAPIを利用して簡単に、且つ、安全にアップデートさせることができる機能です。これらの機能は、運用中にオペレーションミスを防いだり、運用を容易にしたりするものなので、ぜひとも利用したい機能です。
しかし、これらの機能が登場する前は、セフルマネージド型ノードを利用しており、またマニフェストを管理しているGitのリポジトリでaddonのバージョンを管理していました。
そのため、マネージドノードグループやEKSアドオンの機能を使うために、Terraformを書き換えたり、addonの管理をGitでのマニフェスト管理からEKS Addonsへ移行したりなどの作業が必要になりました。
ほかにも、私がEKSを運用しているなかで
- EFS CSI Driver
- EBS CSI Driver
- AWS Load Balancer Controller
が続々とGAになりました。
古いバージョンのCSI Driverは新しいKubernetesのバージョンと互換性がない場合もあるので、定期的にバージョンアップする必要があります。
また、AWS Load Balancer ControllerはSpotインスタンスの中断通知を検知してALBのターゲットグループから切り離す機能があり、安全にSpotインスタンスを利用するために必要になるaddonです。
これらの更新作業をすることで、EKSを安定稼働させることでき、日々の運用コストを段々と減らすことにも繋がっています。
将来の運用コストを減らすためにEKSやKubernetesのaddonの機能アップデートに追随する必要があり、それらに対応するための「時間的な余裕」を確保しておくべきだと私は考えています。
Nginx Ingressのバージョンアップ
Kubernetesにインストールしているaddonのバージョンアップで、少し工夫が必要だった時のことを紹介します。
私達が運用しているEKSでは、Nginx Ingressを利用してホストヘッダーベースのリクエストのルーティングを行っています。
EKS1.21のバージョンまでは、Depricatedのステータスになっているstable/nginx-ingressのNginx IngressのChartを利用していましたが、EKSのバージョンを1.22に上げるためにingress-nginx/ingress-nginxのChartを利用するようにマイグレーションする必要がありました。
通常のNginx Ingressのバージョンアップであれば、helm upgradeやマニフェストを書き換えてApplyすることで、ダウンタイムなしでアップデートすることが可能です。
しかし、ingress-nginx/ingress-nginxへのマイグレーションは、マニフェストのイミュータブルな部分に変更があるので、そのままマイグレーションを実行するとDeploymentの再作成による予期しないダウンタイムが発生してしまいます。
ingress-nginx/charts/ingress-nginx at main · kubernetes/ingress-nginx|github.com
そのため、上記のREADMEに2通りのマイグレーション方法が説明されている通り、
- ダウンタイムを許容し、stable/nginx-ingressをuninstall後にingress-nginx/ingress-nginxをinstallする方法
- 既存のstable/nginx-ingressがデプロイされている状態で、ingress-nginx/ingress-nginxをインストールして古いIngressから新しいIngressへDNSで切り替える方法
のどちらかでマイグレーションを行う必要がありました。
弊社ではゲームプラットフォームを運用しているということもあり、基本的にダウンタイムは許容されないので、2の方法を選択することになります。またシステムの構成上、Terraformで構築しているALBからNodeport経由でNginx Ingressへトラフィックを流す構成にしているので、DNSの切り替えではなく以下の図のようなターゲットグループを付け替えるという方法で移行を行いました。
ターゲットグループで新旧のNginx Ingressの切り替えを行うことで、ダウンタイムは発生せず、もしも問題が発生したときの切り戻しも素早く対応可能でした。
Nginx Ingressは、クラスター内に複数のNginx Ingressをデプロイして新旧を入れ替える方法でアップデートが可能でした。
インプレースでのアップグレードに対応しているIngressがほとんどですが、利用するIngressのバージョンによってはクラスターを別に1セット構築してDNSで切り替える方法を利用する必要もでてくるかもしれません。
このように、Kubernetesにデプロイしているaddonのマイグレーションなども対応していく必要があり、内容によっては大掛かりになる場合があります。
Blue/Green方式でのNodeの入れ替え作業
EKSの運用でよく実施するオペレーションの1つに、ノードの入れ替え作業があります。私達が採用している安全にノードを更新するための方法を紹介したいと思います。
先程も説明したとおり、クラスターのアップグレードや新たな脆弱性が発見された時にノードを入れ替える必要があります。マネージドノードグループを利用している場合のノードの入れ替え方法は、主に2通りあります。
- マネージドノードグループのローリングアップデートの機能を利用する
- 新規にマネージドノードグループを作成して、古いノードから新しいノードへPodを退避させてから古いマネージドノードグループを削除する(Blue/Green方式)
私達のチームでは、後者を利用しています。
前者のノードの入れ替え時の動作は下記のドキュメントに記載の通り、ノードのスケールアウト、Podの退避、ノードのスケールインを繰り返して入れ替えを行うというものです。
メリット
- ノードの入れ替えで必要なEC2インスタンスの起動時間が最小限になりコスト効率が良い
- Podの退避やノードの削除などのオペレーションをAWS側にお任せできる
デメリット
- Podの退避のタイミングをコントロールできない
- Podがノードから正常に退避できなかった場合に、ノードの入れ替えが失敗する(強制的にPodを退避させ失敗を防ぐこともできる)
といったことが挙げられると思います。
私達のチームでは、Podの退避のタイミングがコントロールできないというデメリットが許容できず、またローリングアップデートで失敗した経験があるため、後者のBlue/Green方式での入れ替えを行っています。
手順としては、以下の通りです。
- 新しいAMIを指定したマネージドノードグループ(Green側)を古いノードグループ(Blue側)と同じスペックで作成する
- 手動でkubectl drain を実行して、Blue側からGreen側へPodを退避させる
- 古いノードグループ(Blue側)を削除する
メリット
- 確実にPodをNodeから退避できる(退避できない場合に、その場で応急処置ができる)
- Podの退避タイミングをコントロールできる
- 事前に必要なEC2インスタンスを全台起動するので、キャパシティ不足やインスタンスの起動数の上限に達してしまうことでのNodeの入れ替え作業の失敗を防ぐことができる
デメリット
- 一時的に、通常時の2倍のノードが必要になるのでコスト効率が悪い
- 手動でのPodの退避やノードのドレインが必要になる
私達が運用しているEKSのクラスターには、ステートフルなワークロードも動作しており、再起動のタイミングをコントロールしたい場合があります。
また、EBSをマウントしているPodが終了時にEBSをデタッチされないケースも経験したため、Blue/Green方式でのノードの入れ替えが運用に適していると考えました。
この方法を利用しているおかげもあり、EKSのアップグレードに伴うノードの入れ替え作業で失敗してしまい、障害は発生したことがありません。
EKSの運用で一番困ったこと
EKSを運用していて困ったことを1つ紹介したいと思います。
私が遭遇した不具合に、EFS CSI Driverを起因としたNodeのロードアベレージの急上昇という事象があります。
上のグラフの通りに、急激にロードアベレージが上昇するという事象が不定期に且つランダムなノードで発生していました。
ロードアベレージが上昇した結果、ノードがNotReadyのステータスになりPodが不安定になっていました。ノードやPodは冗長化されているので、直接的なサービス影響はありませんでしたが、なぜ突然ロードアベレージが急激に上昇するのか検討がつかずとても困っていました。
調査を進めた結果
- EFS CSI Driverを1.3にバージョンアップしてから事象が発生するようになった
- EFSをマウントしているPodが起動しているノードで事象が発生する
- 事象が発生した時に、EFS CSI Driverのノード上のPodを再起動するとロードアベレージが低下する
などのヒントが得られ、EFS CSI Driverに原因がありそうだと断定しました。
バージョンアップしてから事象が発生したのならすぐに原因が分かるのでは?と思うかもしれませんが、アップデートして2週間以上経過してから事象が発生したことで切り分けが難しくなっていました。
原因のあたりはつけることができたので、EFS CSI Driverをその時の最新の1.3.5にアップデートさせて事象が改善するか経過を観察していましたが改善しませんでした。
GithubのIssusで同じような問題に直面している人がおり、 2020年8月現在もOpenしたままです。EFS driver causing unusually high load average |github.com
事象が改善されない間は、ロードアベレージが上昇を検知してEFS CSI Driverを再起動するという運用でカバーしつつ問題の調査を継続させました。
そして、efs-utilsのv1.32.1が利用されるようになったEFS CSI Driverの1.3.7のバージョンにアップデートさせることで事象を改善させることができました。
上記のreleaseに記載があるように、v1.32.1にアップデートされたefs-utilsではハングしたstunnelプロセスを再起動するように修正されています。そのため、stunnelがハングしたことが原因でロードアベレージが急激に上昇してしまったのだと推測しています。
EKSはマネージドサービスですが、ワークロード次第でこのような不具合が発生することがあります。
紹介した事象のほかにも、ノードがNotReadyとReadyのステータスを繰り返してしまうというバグにも遭遇したことがあります。
EKSを運用していて一番困ることは情報が少ない不具合に遭遇することで、このような問題に対応できるような時間的余裕や経験も運用する上では必要だと思います。
EKS vs ECS
私達のチームでは、一部のプロダクトのサーバをFargateモードのECSを利用して運用しています。EKSとECSの両方を運用していてどちらの方が運用が容易なのかを述べたいと思います。
前提として私達のチームでは、1つのプロダクトでEKSとECSを併用するということは行っていません。まず、インフラレベルでのEKSとECSの間には大きな機能の差はないと考えています。
KubernetesのServiceリソースの機能もECSであれば、サービスディスカバリやInternal Loadbalancerを利用することで同じようなことができます。AWSとの統合という面で比較しても、両者ともEFSをマウントできたり、Step Functionsのワークフロー上でコンテナを実行できたりします。
あえて言うとすると、ECSでステートフルなワークロードを動かすのは難しいと思います。
つぎに、運用のしやすさを比較すると両者で大きな違いが出てくると思います。単純にECSのほうがEKSよりも運用しやすいという意見もあるかと思いますが、動作させるワークロードやサービスの数によって異なると私は考えています。機能が異なる疎結合なアプリケーションを1サービスから1~8、9サービスデプロイして運用することを考えてみると、ECSの方が運用しやすいでしょう。
私達のチームでは、FargateモードのECSを利用して4、5年になりますが、ほとんどメンテナス不要で運用コストをかけずに運用できています。
しかし、サービスの数が10サービス以上で、結合度も高くなることを考えるとEKSの方に軍配が上がると私は考えています。
その理由は、EKSの方が「規模の経済」が働きやすいからです。規模の経済とは、生産量や生産規模を高めることで単位当たりのコストが低減されるという意味の言葉です。クラウドの世界に当てはめると、サービスの数が増えれば増えるほど1サービスあたりのコストが低くなるということです。
イメージとしては、上の図のとおりです。構築や事前の学習コストがEKSの方が高いのは明らかでしょう。
サービス数が多くなるとサービス毎に分けられたデプロイパイプラインよりも、ArgoCDのような、全サービスで共通して利用可能なCDツールを利用してサービス(アプリケーション)を管理したほうが効率がよくなると思います。
また、サービス数が増えた時に必要なのがオブザーバビリティです。
ECSで多くのサービスを管理することを考えると、サービス毎の設定やログを確認する時に、ECSやEC2、CloudWatch、Cloud Mapなどのコンソールを跨いで情報を確認するのも辛くなってきます。
Kubernetesであれば、Deployment、Service、Ingress、Service Account、HPAなどの設定をChartとしてデプロイしておけば、ArgoCDの1画面上で各種設定とログを確認することができます。
オブザーバビリティに関するツール類もKubernetesの方が豊富です。さらに、ECSでサービス間の通信にInternal Loadbalancerを利用している場合は、Internal Loadbalancerの数が多くなり、金銭的なコストが増えるといったことも考えられます。
これらの理由から、サービス数が多くなる場合はEKSの方がかえって運用効率が良いと考えることもできます。
EKSの採用を検討している方へ
EKSを採用する当初は、デプロイさせるサービス数や事前検証の結果をもとに「EKSが楽に運用できそう」という仮定からスタートしましたが、2年の月日が経過して、EKSを選択したことを振り返ると、結果的に正しい判断だったように感じています。
EKSの機能アップデートへの追随やEFS CSI Driverの不具合に起因する運用コストを許容してもEKSのほうがよかったと思います。
- どのようなワークロードをどれくらいの規模で動かす予定なのか
- 各種のアップデートに追随する運用コストを許容しても、Kubernetesの恩恵を受けたいのか
を考えて、EKSを採用するかどうかを検討したらよいのではないかと思います。
セキュリティ対策の取り組み
セキュリティ対策についても以下のAWSが提供しているドキュメントを参考に取り組んでいます。
Introduction - EKS Best Practices Guides
セキュリティについては、以下の観点で取り組みを説明します。
- 開発者と運用者のアクセスコントロール
- コンテナセキュリティ
- 脆弱性管理
- セキュリティ監査
開発者と運用者のアクセスコントロール
私達のチームでは、できる限りkubectlを実行しなくていい環境を整備することでセキュリティレベルの向上を図っています。
開発者、運用者共にローカル端末からのkubectlの実行は不可能になっており、
- Datadog
- ArgoCD
の2つのツールを主に使って日々のオペレーションや確認作業を行っています。
コンテナのメトリクスやログはすべてDatadogへ集約しており、確認したい場合はDatadogのダッシュボードを見てもらうようにしています。
最近、Kubernetesのリソース可視化の機能もDatadogに追加されたので、Podのステータスだけでなく、CronJobが一時的に停止されているかの確認などもDatadogで確認することができます。
また、アプリケーションのデプロイやリスタートなどのオペレーションは、ArgoCDのUI上で可能になっています。
このように、日々のよく実行されるオペレーションは、kubectlを実行しなくても可能になっており、不要な権限を付与せずセキュアに安全にオペレーションができるようになっています。
しかし、kubectl execを実行したいケースやノードの障害でdrainの作業を行わなければならない場合があります。その時の為に、EKSのクラスターと同一VPC上に、kubectlを実行できるオペレーション専用のサーバを用意しています。
このサーバには、参照系とCronJobの再実行に必要なJobのcreate権限などの一部の権限のみ付与しており、想定されるオペレーションのみ可能なように設定しています。
さらに、scriptコマンドなどによる伝統的な方法で、だれがいつどのようなコマンドを実行し、どんな出力を得たかの履歴も残るようにしています。
このように、kubectlをできるだけ実行しなくてもいい環境と最小限の権限を付与することで、セキュリティレベルを高く保ちつつ、ミスの少ない運用ができるようにしています。
コンテナセキュリティ
コンテナセキュリティでは、以下のことを紹介したいと思います。
- コンテナをrootユーザで実行しない
- IAM Roles for Service Accounts (IRSA)を利用して、ノードの権限を最小にする
- Bottlerocket AMIの利用
- AWS WAFの利用
1つ目のコンテナをrootユーザで実行しないことは、有名なコンテナセキュリティのプラクティスの1つでもあります。自前でDockerfileを記述してコンテナをビルドしていれば、ほとんどのケースでrootユーザで実行するということはないでしょう。
しかし、HelmでKubernetesへインストールするaddonの一部は、デフォルトでrootユーザで実行されるものがあります。
ほとんどのChartでは、一般ユーザで起動するためのオプションが用意されていることが多いですが、自前でBuildしていないイメージを起動する時は注意が必要です。
2つ目は、ノードの権限を最小にするということです。
AWSのIAM RoleとKubernetesのService Accountを連携させる機能がEKSで提供されています。
PodからAWSのAPIへアクセスする時は、それを利用するのがベストプラクティスとされています。
ノードで利用しているIAM Roleに権限を付与することでもPodからAWSのAPIへ通信できるようになりますが、これはNode上のすべてのPodが同じIAM Roleの権限を持つことになるため、セキュリティ的によくありません。
特に、Amazon VPC CNI plugin for Kubernetesなどの、以前はノードの権限を利用していたPodもService Account経由で動作できるようになっているので、まだノードの権限で動作させているのであれば移行が推奨されています。
3つ目は、Bottlerocket AMIを利用するということです。
dockersimの廃止に伴い、containerdがデフォルトで使用されているAMIを試したかったこともありBottlerocket AMIを利用しています。
Bottlerocketはコンテナを実行するために最適化されたOSになり、EKS、 ECSのどちらでも利用することができます。
特徴としては、以下の通りです。
- 不要なパッケージがインストールされていない
- SELinuxがenforcingモードで実行されている
- dm-verifyでのルートファイルシステムの整合性を検証とRead-Onlyでのマウント
- /etcをtmpfsでマウントすることによるステートレス化
- integrityモードでカーネルロックダウンを設定
上記のような設定になっていることで、仮に悪意のあるプログラムがコンテナランタイムの脆弱性を利用して、コンテナからホストOSの権限を得ることができたとしても、影響を最小限にすることができます。
4つ目は、AWS WAFを利用することです。
コンテナと関係がない部分だと思うかもしれませんが、AWS WAFを利用することもコンテナのセキュリティレベルを上げることに役立ちます。
例えば、Log4jの脆弱性が発見された時に、AWS WAFのマネージドルールで脆弱性を利用した攻撃をブロックする機能がすぐに提供され、利用することができました。
また、AWS WAFではレートベースのルールも設定することができます。
この機能は、アクセス数が一定回数を超えたIPアドレスを一定期間ブロックする機能で、DDoS攻撃を緩和するのにとても役に立ちます。
DDoS攻撃が発生するとNginx Ingressや攻撃対象のPodの負荷が急激に上昇し、他のPodに影響が及ぶ恐れもあります。
オートスケーリングで攻撃をすべて受け切るという対策もできそうですが、ノードのスケールが追いつかない場合もあります。
また、Nginx IngressでもAWS WAFのレートベースのルールと同じような機能がありますが、CDNやLBのレイヤーでレート制限の設定を行うのが確実だと思います。
脆弱性管理
私達のチームで運用しているサービスは、ほぼすべてのアプリケーションがコンテナとして実行されています。
そのため、コンテナのImageをECRのEnhaced Scanの機能を利用してスキャンして脆弱性の管理をしています。
Enhanced Scanでは継続的なスキャンフィルターを有効にすることで、Inspectorの脆弱性データベースが更新された場合に、対象のリポジトリにScanを実行するように設定できます。
Amazon Inspector を使用した Amazon ECR プライベートレジストリでのコンテナスキャンの更新 | Amazon Web Services ブログ
Amazon Inspector がデータベースに新しい CVE を追加するたびに、設定された Amazon ECR のリポジトリにあるすべての対象コンテナイメージが自動的に再スキャンされます。
スキャンが実行されると、EventBridgeへスキャン結果のイベントが発行されます。
このイベントをLambdaを利用して、脆弱性の情報をSlackへ通知するようにしています。
しかし、ただイベントをSlackへ通知するだけでは、スキャンが実行される度に同じCVEの大量の通知がSlackへ飛ぶことになります。
そのため、検知された脆弱性の情報を一度DynamoDBに登録して、初回の検知以外はSlackへ通知しないという処理を入れています。
このように、通知のノイズを減らした上でSlackに脆弱性情報を通知することで、通知疲れを防いで確実に脆弱性の対応が可能なようにしています。
ちなみに、検証用のプロキシサーバやアクセスコントロールの部分で紹介したオペレーションサーバは、コンテナではなくEC2インスタンスで運用しており、Systems ManagerのPatch Managerの機能を利用して自動で脆弱性のあるパッケージをアップデートさせ管理を自動化しています。
セキュリティ監査
まず前提として、AWSアカウント全体に関する監査は別のチームがCloudTrail、 Config、 Security Hub、 GuardDutyなどを利用して行っています。
私達のチームでは、DatadogのSecurity Posture Managementの機能を利用してAWS、 Kubernetesの両方の監査を行っています。
この機能では、CIS-AWS-v1.3.0やCIS-Kubernetes-v1.5.1、PCI-v3.2.1などの各種コンプライアンスと、AWSやKubernetesの設定を比較して要件が満たせていないものを通知、または可視化してくれます。
この機能のおかげで新規のリソース作成や既存のリソースの更新時に、各種コンプライアンスを満たしているかチェックを自動で行って通知してくれるので監査のコストを削減することができます。
これからの取組み
8月11日に、EKS 1.23へのアップグレードが提供されたため対応予定です。
また、このAWSとEKSを中心としたシステムを
- より運用効率がよく
- よりセキュア
にしたいと考えています。
数年前と比べてKubernetesもEKSも成熟してきて、必要な機能は揃ってきたかと思います。
これまでは、各種のアップデートに追随することやモニタリングの強化を行って安定運用させることに注力していましたが、これからはより運用コストを削減できるようにしていきたいです。
例えば、障害試験や負荷試験の自動化を行いたいと思っています。
機能アップデートに追随するためにアップデートを行った結果、以前まで正常に動作していたものが、いつの時点からか正常に動作しなくなるということも考えられます。
Spotインスタンスの終了時に、5xxエラーが発生せずにPodの退避やTarget Groupからのdrainningが実行されているかなどを定期的にテストしたいと思っています。
また、セキュリティに関してもさらなる改善を行う予定です。
Kubernetesの1.25のバージョンでPod Security Policyが削除される予定となっています。
移行先としては、GatekeeperのようなPolicy as Codeを利用するか、Kubernetesの1.23でベータで提供されるPod Security Standardの機能を利用するかのどちらかになると思います。
私達のチームではきめ細やかなルールで制限するような要件はないため、Pod Security Standardsを利用することを検討しており、EKSを1.23にアップグレード後に検証予定です。
最後に
EXNOA プラットフォームインフラ部では、プラットフォームの安定運用やグロースに携わるインフラエンジニアを募集しています。
ご興味のある方は下記の募集要項をご覧のうえ、ぜひご応募ください。