Strimzi: Kubernetes Operator for Apache Kafka と導入過程について

Background

先日、過去六ヶ月近く携わっていた Apache Kafka のプラットフォーム移行プロジェクトが完遂した。それまでは Confluent Cloud というマネージドサービスを使っていたのだが、Amazon EKS 上に構築した k8s クラスターに、Strimzi を利用して Kafka クラスターをデプロイする運用に切り替えた。

Web チームのみならず、検索から機械学習チームまでも巻き込んだ一大プロジェクトで、自分は半年間 SRE 側のメンバーとして、デザインから PoC の実装、モニタリングやアラートの構築から実際の移行作業まで一気通貫して担当してきたので、最後の本番アプリケーションが切り替えられたタイミングは、非常に感慨深かった。我々の Kafka 運用に対する経験不足から幾つかの試練も経験してきたが、それも含めて大きな経験ではあった。

プロジェクト移行が完了してから、チームメンバーとロックダウンが開けた地元のパブで、プロジェクトTシャツ*1を着ながら、祝賀会を開いたのは良い思い出となった。マネージドサービスから敢えて自前運用に切り替えることになった経緯や背景は色々あり、また別の機会(会社ブログなど)で紹介できたら良いとは考えている。

運用が本格化するこれからが本番とはいえ、せっかくの機会なので、ざっくりと Strimzi について振り返ってみたい。

What is Strimzi?

Strimzi とは、"Apache Kafka を運用するための Kubernetes Operator" だ。Strimzi に関する一連のソフトウェアは github.com/strimzi Organization で開発されており、特に Operator 本体は github.com/strimzi/strimzi-kafka-operator で開発されている。

2019/08/28 に CNCF Sandbox Project に採択*2されており、Red Hat からの Core Contributors/Maintainres が目立つ。

これは少し古いデータだが、2020/09/29 に公開されたブログ "Ingithst from the first Strimzi survery" によると、全部で 40 回答あったうちの 40% 以上が本番環境で利用している。そのうち 25% が我々同様 Amazon EKS 上で Kafka クラスターを運用している。

そもそも "Kubernetes Operator" とは何かというと、Custom Resource Definition(以下:CRD) を利用して、任意のアプリケーション群(ここでは Apache Kafka や Apache Zookeeper など)をデプロイ・運用するためのアーキテクチャパターンの一つ。

例えば、Apache Kafka を自分たちで k8s 上に展開するとした場合、Kafka クラスターのみならず、様々なサブコンポーネントもデプロイする必要がある。

  • Apache Kafka
  • Apache Zookeeper
  • Kafka Connect Cluster (if required)
  • Kafka Mirror Maker (if required)
  • Kafka Bridge (if required)
  • RBAC (Role, ClusterRole, binding resoures, etc.)
  • Load Balancers
  • components/configuration for Monitoring (e.g. prometheus JMX exporter)
  • components/configuration for ACL (e.g. users)

これら全てのアプリケーションをデプロイするために、一からフルスクラッチで YAML ファイルを書いていくのは、経験者でもない限り不可能に近い。コンテナ間のネットワーク設定や、mTLS および Client Certification の配布、Persistent Volume(以下:PV)の設定、Topic の設定など、他にもやることはたくさんある。

それらの設定を、Strimzi という Operator が提供する API(CRD schema という形で表現されている)を利用することで、ある程度シンプルに Kafka クラスターを運用できるようになる、ということだ。

もちろんこの抽象化には欠点もあって、Strimzi がサポートしていない機能は利用できない。例えば、Kafka v2.8.0 が 2021/04/19 にリリースされた。Zookeeper への依存をなくすという待望の KIP-500 が利用可能となった注目のリリースバージョン*3 ではあるが、Strimzi 側で Kafka v2.8.0 が利用可能となったのは、およそ一ヶ月後の 2021/05/13 にリリースされた v0.23.0 においてである。一ヶ月で Kafka のマイナーバージョンに対応したのはかなりありがたいが、とはいえラグがあることは否定できない。

また、もちろん Operator 側に不具合があればそれに制限を受けることになる。Java で書かれた Operator のソースコードを読みに行くこともままある。

それでもなお、自分たちで全てのサブコンポーネントを YAML で管理するよりは信頼性が高く、かつ CNCF に採択され他の会社でもプロダクションで使われている点、および将来の有望性・保守性を考慮して、Strimzi を利用するに至った。

How we built with Strimzi?

まずは Proof Of Concept(以下:PoC)を検証環境である sandbox cluster にデプロイすることを目指した。Kafka クラスターのデプロイ自体は、Strimzi CRD のおかげで非常に簡単であった。続けて、Confluent Cloud からデータを持ってくるために Kafka MirrorMaker 2.0(以下:MM2)をデプロイをし、動作検証を行った。

続けて、モニタリング基盤とログ基盤の検証を行った。JMX exporter および Prometheus servers を別の Namespace にデプロイし、すでに本番利用されている別のモニタリング用 EKS Cluster*4からの Service Discovery の設定を行い、すでに利用されているモニタリング基盤およびアラート基盤とシームレスに統合できることを確認した。

ログ基盤については、Kinesis Stream/Kinesis Firehose/Lambda および Amazon Elasticsearch(以下:AES)ですでに構築済みであった社内の EKS クラスター向けログ基盤を利用した。利用にあたっては、Fluentd を Kafka と同じ EKS クラスターに DaemonSet としてデプロイし、awslabs/aws-fluent-plugin-kinesis を利用して Kinesis Stream に PutRecords するだけで、Elasticsearch からログが閲覧できる状態を作り上げることができた。その際、fabric8io/fluent-plugin-kubernetes_metadata_filter を利用して k8s のメタデータ*5を付与することで、ログの検索体験を向上させている。

また、PoC の作成と並行して DesignDoc を作成していった。内容としては、本移行プロジェクトを実施するための大義・目標の他、代替案であるアーキテクチャとの比較検討、目標 SLI/SLO、コスト試算、Backup/Restore および Networking について記載してある。

最後に、PoC の実装終盤で、簡単な Performance Tooling ができる仕組みも整えた。Kafka Tools において提供されている kafka-consumer-perf-test および kafka-producer-perf-test をベースとしている。パフォーマンステストの内容を YAML で定義し、pods を同クラスターにデプロイする。実行結果を STDOUT に書き出し、google/mtail を利用してメトリクスに変換している。それを Prometheus/Grafana を利用しダッシュボードで閲覧可能な状態にした。sandbox 環境で本番想定のスループットを現状の設定で捌けることを確認している。

最終的に、sandbox 環境に構築したものと同様の構成を、本番クラスター(および準本番クラスター)にデプロイした。EKS クラスター自体は cookpad/terraform-aws-eks で管理している。どちらかというと Opinionated な API を提供しており、我々のユースケースに特化している分、新しいクラスターを最小限の設定でデプロイできる。

k8s 周りの CI/CD は鋭意改善中だが、現時点では Jenkins/CodeBuild を利用し、CI では kubectl apply --dry-run --validate および kustomize build && kubectl diff による変更差分の確認をし、CD では master ブランチに反映された時点で kubectl apply を実行している。

How do we access Strimzi?

ネットワークの実現方法については、Strimzi からも幾つかの案が提供されている。

第一に、NodePort を利用する方法*6。追加でデプロイするプロセスが皆無なので、保守性に優れ、実装コストもほぼ無い。しかしながら、VPC 内とはいえポートを直接公開するセキュリティ上の懸念、および直接ポートをアクセス可能にすることによる抽象度の低さを懸念し、不採用とした。

第二に、Ingress を利用する方法*7。例えば、NGINX Ingress Controller などを Ingress Service として稼働させるイメージだ。ただし、該当アーキテクチャには同リンクにも示唆されている通りパフォーマンス上の懸念があり、直近では問題とはならないものの、目標 SLI/SLO および中長期的なサービスの成長を加味して、不採用とした。

So Ingress might not be the best option when most of your applications using Kafka are outside of your Kubernetes cluster and you need to handle 10s or 100s MBs of throughput per second

第三案に、Loadbalancer を利用する方法*8が紹介され、最終的にはこの案を採択した。Kafka brokers ごとに一台 Network Loadbalancer(以下:NLB)をデプロイし、さらに Bootstrap Connection*9 のためにもう一台 NLB を稼働させるアーキテクチャだ。一般的にパフォーマンス十分である NLB を利用でき、かつそれぞれの NLB でのモニタリング基盤も CloudWatch が最初から利用できる。自分たちで Ingress Controller を保守する第二案と比べ、保守性にも優れている。唯一の欠点がインフラコストだが、試算によると Kafka brokers は三台であり、合計四台の NLB を稼働させた場合のコスト試算では十分に許容範囲内であった。

なお、第三案の発展形として、一つの NLB を使い回す方法*10も提案されている。第三案をより複雑にさせる代わりに、ロードバランサーのコストを抑えようとするものだ。この案は、デプロイ周りの自動化実装コスト、および保守性を考慮して、不採択となった。

Why Strimzi?

そもそもの大前提として、マネージドサービスを使うか、自分たちで Kafka クラスターを運用するか、という意思決定がある。

マネージドサービスを使う場合、Confluent Cloud の他、Amazon MSK などが選択肢としてあげられる。始めに断っておくが、Confluent Cloud は良いサービスである。マネージドサービスを引き続き利用するという選択肢をとるのであれば、Confluent Cloud は引き続き第一の候補にあがるであろう。サポートも手厚いし、流石に Apache Kafka のコア開発者が在籍しているだけあって、Confluent Cloud が出している Confluent Replicator などの代替サービスの質も高い。

したがって、Confluent Cloud から Strimzi に乗り換えたのには、自分たちで Kafka クラスターを運用する方向性に全社で舵を切ったから、という理由が大きい。本体アプリのベースが徐々に Kafka を利用し始めることになってきており、重要性が増してくる中で、データのバックアップの柔軟性であったり、Kafka クラスターの設定の柔軟性やメトリクスの獲得であったり、ネットワーキング周りのアーキテクチャの柔軟性であったりと、保守コスト・実装コストをかけてでも Kafka クラスターを中心としたイベントドリブンなアーキテクチャを加速させていきたい、というモチベーションによる部分が大きい。

そこで、自分たちで Kafka クラスターを運用する場合、k8s の経験とスキルを持ったメンバーが多い点、および我々の中長期的戦略を考えてプラットフォームは k8s になり、全社的に利用していることから AWS EKS を選択するのは自然の流れであった。そこで Strimzi を使うことにしたのは、先に述べたとおり、Operator Patterns を利用することによる恩恵のほうが欠点を上回ったからである。

なお、Confluent Operator という選択肢もあったが、当該ソフトウェアを利用するためにはエンタープライスプランに入る必要もあり、といった事情もある。一度 Confluent Operator の実装は拝んでみたいものだ。

Conclusion

以上、Strimzi および導入過程について振り返ってみた。簡単に振り返るつもりだったが、思い入れのあるプロジェクトだったせいか、それなりに長い文章となってしまった。

今回のプロジェクトは、単純に Kafka や Strimzi について技術的見地を得られたのみならず、Global SRE に来てから、現地のメンバーと、かなり密に協調しながら、厳しい納期のなかで完遂させなければならないものだったため、かなり経験値を得ることができた。特に、同じプロジェクトをやりとげたメンバーには頭が上がらない。困ったときに頼りになる同僚や、思っても見ない角度から適格なツッコミを入れてくれる仲間、金曜日夜からのインシデント対応の中でも場を和ませながら対応していくテックリードなど、素晴らしいメンバーに囲まれている。これからの運用知見も楽しみである。

*1:プロジェクト期中で、メンバーの士気を盛り上げるために SRE のほかメンバーが提案し、実際に発注までされたネタTシャツ

*2:https://www.cncf.io/sandbox-projects/, https://strimzi.io/blog/2019/09/06/cncf/

*3:ただしプロダクションでの利用はまだ推奨されていないことに注意

*4:Prometheus の長期ストレージとしての Thanos や Alertmanager 、Grafana などが稼働している

*5:pods や nodes の情報

*6:https://strimzi.io/blog/2019/04/23/accessing-kafka-part-2/

*7:https://strimzi.io/blog/2019/05/23/accessing-kafka-part-5/

*8:https://strimzi.io/blog/2019/05/13/accessing-kafka-part-4/

*9:Kafka では、Producers/Consumers いずれも、実際に書き込み・読み込みをする対象の Partition を持つ Kafka brokers と、TCP レイヤーで独自に定義された Kafka binary protocol を利用し直接通信するのだが、書き込み・読み込みをする Partition がどの brokers に存在しているかをクライアントに知らせるために、最初に Metadata API を利用した Node Discovery を行う必要がある。そのメタデータは全ての brokers が返却できるのだが、そのいずれかに問い合わせをする必要がある。その最初の接続のために利用されるエントリーポイント的役割

*10:https://strimzi.io/blog/2020/01/02/using-strimzi-with-amazon-nlb-loadbalancers/

2021-06-29