プラットフォームの上でものを作るということ

Amazon EKS Advent Calendar 2019 の最終日です.

みなさまご存知の通り、AWS には Amazon ECS と Amazon EKS という2つのコンテナオーケストレーションに関するサービスがあります. ECS は2014年に発表された AWS ネイティブなコンテナオーケストレータ、EKS は OSS のコンテナオーケストレータである Kubernetes をマネージドな形で提供するサービスで、2017年に発表されました.

今日はこの Amazon ECS と Amazon EKS という2つのサービスについての話を書こうと思います.

// 読んでくださっているみなさまをミスリードしないための DISCLAIMER
本記事の著者は AWS に勤めています. また、この記事には僕個人の意見や想いも強くこもっています.

Amazon ECS と Amazon EKS の違いは?

良く聞かれる質問です. 実際には ECS がコンテナオーケストレーターであるのに対して EKS は Kubernetes クラスタそのものを管理するサービスなので、質問と比較の意図は ECS vs. Kubernetes ということになるでしょう.

すでにご存知の方も多いかもしれませんが、Kubernetes の持つ機能の多くが ECS には存在しません.

ECS には秘密情報管理の機能がありません。ECS には時間指定でのコンテナ実行機能はありません。One-off なコンテナ実行時の成功保証もやらなければ、いわゆる設定情報管理も ECS の責務の範囲外です.

これらは Kubernetes においてはすべて単体で実現可能です. Secrets(秘密情報管理)、Jobs(成功保証)、CronJobs(時間指定実行)、ConfigMaps(設定情報管理) といったリソースを利用することによって、ECS では実現できない上記のような機能性がカバーされています。これらの例から明らかな通り、横並びで◯×表を作ると間違いなく Kubernetes の方が多機能です.

ではこのような違いをもとに、EKS を選択することが正解なのか?

ここから先は、ECS vs. Kubernetes の単純比較では見えてこない内容について考察します.

プラットフォーム

我々がシステムを作るとき、そこには大なり小なりなんらかのプラットフォーム的な何かが存在します.

多くの場合、プラットフォームには思想や哲学が存在します. それらはユーザーがどのようにプラットフォームを使うかに色濃く反映され、一定の制約を生み、その制約は我々がシステムを構築する際のフレームワーク、もしくはガードレールのように機能することを期待されます.

例えば AWS はそんなプラットフォームの1つです.

先ほど ECS には Kubernetes の持つ機能性の一部が存在しないことに触れましたが、その理由は「各オーケストレータにとって何がプラットフォームなのか」に隠されています.

ECS はあくまでも AWS というプラットフォームの一部です. つまり、秘密情報管理や時間指定での処理実行、成功保証のためのリトライや設定情報管理といった機能は、ECS ではなくプラットフォームである AWS の持つ機能として他の場所に実装されています. 秘密情報管理を司る AWS Secrets Manager、時間指定での処理実行のための Amazon EventBridge (≒ Amazon CloudWatch Events) 、成功保証とリトライを可能にする AWS Step Functions、設定情報管理のための AWS Systems Manager の Parameter Store といったサービスがその役割を担っており、ECS にはこれらのサービスとのインテグレーションが実装されています. これにより、例えば我々は ECS で実行するコンテナから安全に秘密情報にアクセスできるようになっているわけです.

ECS には秘密情報管理の機能がありません. ECS には時間指定でのコンテナ実行機能はありません. One-off なコンテナ実行時の成功保証もやらなければ、いわゆる設定情報管理も ECS の責務の範囲外です.

先ほどのこの文章の “ECS” を “Lambda” に置き換えてみてください。すべてが Lambda においても存在するユースケースです.

これが AWS というプラットフォームの思想であり、哲学の1つです.

1つ1つのサービスにはそれぞれの責務の範囲が明確にあり、ユーザーはそれらをビルディング・ブロックとして組み合わせることでシステムを作っていく. 各 AWS サービスはそれぞれの API を呼ぶ形で疎結合に組み合わせることができ、一部のブロックを他のブロックに、例えば ECS から Lambda に置き換えられる. この思想と哲学が、AWS が幅広いユースケースをカバーすることを可能にし、ユーザーが AWS 上で持続可能なシステム構築と運用を行うことを可能にしていると言えます.
(少し話は逸れますが、AWS を使うユーザーが感じることがあると思われる、いわゆるマイクロサービスならではのツラみのようなものも、この思想と哲学によって副作用的に発生していると考えます)

AWS そのものがプラットフォームであり、ECS はそんなプラットフォームの機能の一部として、ユーザーがコンテナを使って解決したい課題を解くためのサービスという関係性です.

かたや、Kubernetes というソフトウェアは、ユーザー自身が独自の哲学に基づいてそんなプラットフォームを作り上げることを可能にするものです1. つまり、Kubernetes を AWS 上で実行するということは、Platform on Platform の状態を作り上げていることになり、そこには2つの思想と2つの哲学が親子関係を持っていることになります.

Kubernetes は本当に複雑で難しいのか?

プラットフォームの親子関係の話に進む前に、「Kubernetes は難しい/複雑」という言説に一度スポットライトを当てます.

‪Kubernetes のデプロイメントモデルはその利用者にとってシンプルで美しく、システム構築を一貫性を持って実現することに圧倒的な強みがあります2。そして、そこにレバレッジを効かせたカスタムリソースやカスタムコントローラによるオブジェクトの管理は本当に素晴らしく、Kubernetes をコンテナオーケストレータとして唯一無二の存在にしているのは‪まさにこの部分だと考えます. ‬Kubernetes の API を自分たちのワークフローやワークロードに合わせて拡張しながらも、全てにおいて一貫性ある挙動を示す自分たちのためのプラットフォームが作れるわけです. 大興奮です.

世の中の Kubernetes の多機能性を取り扱った話から考えるとにわかに信じ難いかもしれませんが、素の Kubernetes はこの一貫性あるデプロイメントモデルを実現する機能に加え、少しの便利機能をもった非常にシンプルで簡単なソフトウェアです. 素の Kubernetes では、ノードのオートスケールどころかコンテナのオートスケーリングさえも簡単には実現できません. しかし、各種 OSS ソフトウェアを「一貫性のある方法で」インストールすることであっという間にこれらの機能を利用できるようになります.
(当然ですが、これはあくまでも Kubernetes クラスタの利用者としての視点であり、Kubernetes クラスタの構築・運用者の視点ではありません)

Kubernetes の重要な魅力がここに詰まっています. モダンかつ一貫性のある機能の拡張方法と、多くの便利な機能性が OSS として提供されるエコシステム. コマンド一撃で ready-to-use な CI/CD パイプラインが出来上がったり、ML 基盤が構築できたりする. この一貫性ある体験と拡張性の高さ、簡単さに興奮し、Kubernetes というソフトウェアに bet していくことを決めたユーザーも数多くいると思います.

Kubernetes クラスタの利用者にとって、そこにはなんらの複雑性も難しさもありません. むしろその一貫性や体験は ECS よりも明らかにシンプルで簡単です (少なくとも僕はそう感じています) . GitHub に公開された OSS の README に載っている kubectl apply ~ コマンドをコピペして実行すると即座にその OSS ツールが自分のクラスタで動く感動は、ECS ではなかなか得にくいものでしょう.

ではなぜ Kubernetes を難しいという人が(こんなにも)いるのか.
(いませんかね?僕はいると思うので、その前提で話を進めます)

プラットフォームとしての Kubernetes の難しさ

Kubernetes クラスタの運用コストがどうこうホゲホゲ、というやつは聞き飽きた方も多いと思いますので、ここでは言及しません. ちょっと毛色が違う話をします.

「Immutable Infrastructure」や「Infrastructure as Code」という言葉を聞いたことがある方は多いと思いますが、クラウドの登場に合わせるようにして、インフラはイミュータブルに保ちつつコードで表現するもの、という常識が広まりつつあります. 作ったインフラに手を入れながら大事に大事に育てるのではなく、ゼロからインフラを即座に作れるコードを持ち、そこに手を入れることで、インフラのイミュータブル性を維持する考え方です. この考え方のもとでは、原則としてインフラリソースは短命に保たれ、環境の再現可能性を高める方向を目指します.
(なぜイミュータブルな方が良いのか、なぜ環境の再現可能性が大事なのか、という話はここでは本筋からずれるのでスキップします)

さてそんなイミュータブルやらコードやらですが、Kubernetes を使うとこれらの実現も容易になります. YAML を書いて kubectl apply すればその定義された状態に収束してくれるので、Kubernetes クラスタの上で動くものを全て YAML で表現できるようになります. 変な使い方をしない限りは、イミュータブル性についてはコンテナが担保してくれます.

では Kubernetes クラスタそのものはどうでしょうか.

AWS でインフラを構築・運用する方であれば、Kubernetes クラスタそのものも EKS を利用してコードで管理し、いつでも同様のクラスタセットアップを再現できる形を取りたいはずです.
もしそれらが実現できれば、Kubernetes クラスタの構築からその上で動くシステム・コンポーネント、アプリケーションに至るまで、システムを構成する全ての要素をイミュータブルかつコードでの管理下に置くことができる可能性が高まるからです. 加えて、Kubernetes クラスタの再構築が容易であるということはクラスタの寿命を短命にできます. それにより、インフラの長寿命化による謎多い闇の誕生や事故発生の可能性を低くできると考えられるため、Kubernetes クラスタそのものをコード管理しようとすることは自然な流れかと思います.

一方、AWS で EKS を使って Kubernetes クラスタを実行するとなると、AWS のその他のマネージドサービスも併用したくなるでしょう. ALB, RDS などはその分かりやすい代表例です. そして、これらの AWS リソースも含めて、Kubernetes の一貫性あるデプロイメントモデルに乗っかる形でシステムを構築したいとなると、AWS の API ではなく、Kubernetes の API を使って、Kubernetes の哲学に沿って作成・管理したくなってきます.

そのようなことを可能にする代表例の1つが ALB Ingress Controller3 です. この ALB Ingress Controller を Kubernetes クラスタにインストールすると、YAML を書いて kubectl apply ~ するだけで Kubernetes のデプロイメントモデルに沿う形で ALB リソースが作成できます. 同じように RDS やその他の AWS リソースについても、同様のことを実現するための AWS Service Operator4 という OSS が存在します.

つまり、ALB Ingress Controller や AWS Service Operator のようなツールを Kubernetes クラスタにインストールしておけば、AWS のリソースまで含めて全てを Kubernetes の哲学に基づく形で用意できる世界が実現できるわけです.

しかし、ここからが難しくなります. ALB Ingress Controller を例に考えてみましょう.

Kubernetes クラスタの中にインストールされた ALB Ingress Controller を使って ALB を管理しているということは、その ALB のライフサイクルは Kubernetes クラスタそのもののライフサイクルの影響を受けます. ある Kubernetes クラスタの Ingress として作成された ALB は、その Kubernetes クラスタが消えてしまうと一緒に削除されます. Kubernetes クラスタというプラットフォームに依存する形で作成された ALB ですので、プラットフォームが消えれば当然一緒に消えます. これ自体は自然な話です.

しかし、果たしてこの ALB は本当に Kubernetes クラスタと一緒に消えて欲しいものでしょうか?RDS の場合はどうでしょうか?おそらく、これらのリソースについては、多くの方が Kubernetes クラスタそのものよりも長い寿命をイメージするのではないかと思います.

AWS というプラットフォームにおいては、多くのケースにおいてライフサイクルが異なるリソースは独立した形で作成・管理することが一般的です. 例えば CloudFormation のスタックは VPC と RDS では別のスタックにするでしょうし、ALB と ECS リソース(タスクやサービス)の作成も別のスタックで行うことが多いでしょう. それぞれのリソースを、将来の更新や削除のライフサイクル、そしてどのように運用するかに基づいて独立して管理できるようにし、運用の容易性を高めたり、あるリソースの更新が周りに影響を与える可能性を低くしたり、という塩梅です. 冒頭に書いたマイクロサービス的なつらみの対価として得られるメリットは、この独立したリソース管理こそその代表例と言えます.

話が少し逸れたので戻します.

Kubernetes というプラットフォームの哲学に沿ってシステムを構築すれば、素晴らしく一貫性のある自動化とその体験が手に入ります. しかし、Kubernetes というプラットフォーム自体の運用容易性を高める目的で Kubernetes クラスタ自体を短命なものにしようとすると、そのクラスタから作成された長寿命なクラスタ外リソース(ALB や RDS)のライフサイクルとの整合性が取れず、奇妙な運用をやることになってしまう.

そのような悩ましい現実を直視した結果、ALB や RDS は AWS の API で、コンテナで動かすものは Kubernetes の API で、というような、一貫性に欠ける構築・運用方法を選ぶことが往々にしてあります. あるいは Kubernetes クラスタを短命かつ継続的に再作成することは諦め、長寿命なクラスタとしての運用を選択することで、AWS リソース作成と管理まで含めて Kubernetes の一貫性ある体験の中で完結させることにプライオリティを置く方もいます. しかしその選択の結果、数多くのアプリケーションが同居しながら動く大規模クラスタに育ってしまい、ちょっとしたクラスタ障害がそこで動く多くのアプリケーションに影響を与えるようなことになってしまうのは想像に難くありません.

このような状況と前提条件の中で、どういう決断をするのか. 何が自分たちと自分たちのシステムにとってベストなのかをどうやって判断するのか. これが EKS(Kubernetes) が ECS と比較して『難しい』と言われる正体、その理由の1つだと僕は理解しています.

プラットフォームの上でものを作るということ

プラットフォームの上でシステムを作るということは、そのプラットフォームの思想や哲学に沿う形でシステムを作るということを意味します。それにより、そのプラットフォームの持つ機能性や特徴を活かすことができます. 逆に言うと、その思想や哲学に基づかない、プラットフォームの敷いたレールを外れた形でものを組み上げようとすると、歪みやアンチパターンを巻き込んだ構造物が仕上がります。

そして、ECS と EKS の選択には「プラットフォームの選択」という側面があります。これはつまり、この選択が “How you (want to) build, run and operate your systems” を決定する重要な要素になることを意味します。

みなさんはどのようなシステムを、どう作り、どう運用していきたいですか?

2020年も楽しくやっていきましょう💪


  1. YouTube: “DevOpsDays Seattle 2018: Kubernetes is a Platform Platform”, by Joe Beda [return]
  2. 「クラスタ内の全てのリソースを Control/Reconciliation loop の管理下に置くことで、一貫性のあるクラスタ状態の収束方法が実現された」などと表現されたりしますね [return]
  3. GitHub: AWS ALB Ingress Controller [return]
  4. GitHub: AWS Service Operator. このリポジトリは v2 プロジェクトとして現在進行中のため、まだちゃんとした中身はありません. このプロジェクトの前身であるバージョン v1 は、アーカイブされてはいるもののこちらのリポジトリで内容をご覧いただけます. [return]