稼働しているサービスの紹介

石田:実際に作成した構築物の紹介をします。Kubernetesクラスタは以下のルールに則って構築しました。クラスタはdevelop環境、staging環境、production環境で分割し、既存のセキュリティグループベースのアクセス制限でも対応できるようにしました。

ワーカーノードへのSSHは通常利用の範囲では不必要なので原則不要とし、ワーカーノードのkubeletやDocker Engine、その他ミドルウェア起因の障害も障害と考えられる場合のみ、セッションマネージャー経由でSSHでログイン可能にしました。kubectlが打てる作業サーバーは既存のユーザー管理の方法に合わせるためdevelop、stagingの開発環境とproduction環境で分けることにしました。

また、ワーカーノードは、開発環境はスポットインスタンス100パーセントに設定、夜間帯はワーカーノードを停止するように設定しています。production環境でも50パーセントのスポットインスタンスにして、ランニングコストの抑制をしています。副産物的ではありますが、いつシャットダウンするかわからないスポットインスタンスを混ぜることで、障害に強いアプリケーション開発を意識できているかと思われます。

OPENREC.tv全体のインフラ構成で表すとKubernetesは赤で囲った領域に追加されました。

Kubernetesの構成管理や運用に関しては、クラスタ自体の構築やAWSリソースの管理にはTerraform、マニフェストの管理にはKustomize、CIにAWS CodeBuild、CDにはArgoCDを使用しています。

次にKubernetesで稼働しているサービスを紹介します。バックエンドサービスやCI/CD、監視などエコシステムを支えるサービスを合わせると以下のサービスたちが、現在本番で稼働しています。バックエンドサービス3つ、ALB Ingress Controller、ExternalDNS、ArgoCD、Fluentd、Fluent Bit、Kube-Prometheus、Cluster Autoscaler、Horizontal Pod Autoscaler、Metrics-server、Kubernetes Dashboardになります。

リリースのスケジュールの変更などにより、当初予定していた機能の実装が見送られたため稼働しているサービスが想定よりもまだ少ないですが、リソース集約の観点からDocker ComposeやVM方式で稼働していたサービスの移行も予定しています。

個人的におすすめのコントローラー

ここで、稼働しているサービスおよび個人的におすすめのコントローラーを紹介します。今回バックエンドサービスの実装にはKotlin、フレームワークにはKtorを採用しました。性能検証および負荷分散の観点からメモリのlimit、requestは4096ミリバイト、CPUは4コアを割り当てています。EC2でいうならc5.xlarge相当になります。また環境変数はapplication.ymlに含めてargsディレクティブで指定しています。

使用しているフレームワークのKtorの紹介を軽く挟みます。Kotlinの特徴であるコルーチンを利用した非同期処理に最適化されたWebフレームワークになります。マイクロサービスの文脈やサーバーサイドKotlinに対する技術的好奇心から導入してみました。

ALB Ingress Controller。文字通りALBをIngress Resourceにできるコントローラーです。アノテーションで従来指定しているサブネット、セキュリティグループやSSL証明書、ヘルスチェックパスなどを設定できます。ロードバランサを挟みたいだけなら従来のタイプロードバランサ経由のCLBも利用可能ですが、柔軟性の観点からこちらを使用することをおすすめします。

ExternalDNS。DNSレコードをKubernetesのリソースとして管理できます。特筆事項としてAWS Route 53、Google Cloud DNS、Azure DNSやCloudflareなど対応しているDNSサーバーの豊富さが挙げられます。個人的には一番好きなコントローラーです。

Cluster Autoscaler。ワーカーノードをPodの配備状況に応じて自動にスケールしてくれます。最小・最大稼働台数を指定しておけば自動でスケールアウト・スケールインをしてくれます。

Horizontal Pod Autoscaler。Podのスケールアウト・スケールインを自動で実施してくれます。リソース使用率に基づくスケーリングポリシーとネットワークのアウトバンドトラフィックなど、カスタムメトリクスなスケーリングポリシーを使用可能です。

リリースフローについて

次にリリースフローについて説明します。CI/CDにはAWSのCodeBuildとArgoCDを使用しており、develop環境とstaging環境では原則としてdevelopブランチ、stagingブランチにマージされたら即座に反映されるようになっています。開発環境では開発者がデプロイに関して意識することなくコードの反映ができるようになっています。本番はさすがに事故などの心配があるので手動でロールアウトを実施するフローになっています。

サービスの一つひとつがGitのリポジトリとシンクしており、逐次状態変更に対する監視が走って、万が一差分がある場合はOutOfSyncと表示されます。

ArgoCDでは対象のバックエンドサービスだけでなく、ALB Ingress ControllerやPrometheusなども管理しています。最新のバージョンに上げたい場合でもマニフェストを書き換え、プッシュするだけで反映される手軽さをクラスタ全体で享受することが可能です。

今回構築したKubernetesの環境から受けられる恩恵をまとめます。アプリケーションエンジニアはサーバーやインフラを意識することなく開発ができるようになりました。既存では不完全で、またデプロイ内容が不透明になりがちだったCI/CDフローを完全に実現できました。

160個にも及んだ検証issue

プロジェクトを進めるにあたって直面した問題たちを説明いたします。今までの説明からとくに不都合なく構築が進んだように思われるかもしれませんが、もちろん検証が順調だったわけではありません。知識不足や既存インフラ部分に対する理解不足などから生じる修正や方針の転換などもありました。構築する前から決めなければならないこと、検証項目などわかっているだけで以下にある問題に対応する必要がありました。

ネットワーク設計、性能検証、構成管理、デプロイ、障害対応などとなります。これだけの検証を1人で行うのは現実的ではなかったので、チームで分担してissueを切り検証しました。issueの総数は160個に上りました。

また、Kubernetesにどんどん慣れてもらうために検証環境に誰でも操作でき、何をインストール・デプロイしてもよいクラスタ、通称「魔窟クラスタ」を構築し、コマンドやマニフェストを身近に感じてもらうような環境作りをしました。

次に実際に直面した問題を紹介いたします。

IPアドレス枯渇問題

IPアドレス枯渇問題。開発環境のKubernetesクラスタを作成するにあたって、事故を防ぐために既存のVPCから独立してKubernetes専用のVPCを作ることにしました。OPENREC.tvでは慣習的にサブネットを24ビットで切って運用しています。初期状態で動かすコンテナ群が少なかったことなどから、とくに考えずにいつもの通りに24ビットに切って構築しました。

このとき、アロケーションは1サブネットあたり251アドレスで、3サブネット切ったので753IPアドレスでした。ワーカーノードに採用したインスタンスタイプはc5.4xlarge、m5.4xlargeでした。インスタンスの仕様からネットワークインタフェースの最大アタッチ数は8、ネットワークインタフェース1つあたりにアロケートできるIPは30個であることがわかります。

ここから1ワーカーノードあたり240IPアドレス消費することがわかります。先ほど言ったようにIPアドレスは753しかありません。この計算だとどんなにがんばってもワーカーノード4台しか起動しないことになり、実際4台以上の起動はできませんでした。また4台目のワーカーノードもIPアドレスが足りず、コンピューティングリソースは十分余裕があるのにPodが起動しないといった状態になりました。

VPCを新しいもので作成していることやKubernetesの特性に鑑みて、少し抵抗はあったのですが20ビットで切りなおし、さらに倍の6サブネットをワーカーノード用にしました。約24,000IPアドレスがアロケーションできたため、今のところ十分であると判断しています。注意点としてはいつもの感覚でサブネットを切るとすぐに足りなくなる点になります。

DNSレコードの管理責任

DNSレコードの管理責任について。DNSレコードの管理責任についても既存と変更する必要がありました。今回バックエンドサービスに払い出されるドメインはExternalDNSを使用して作成されます。従来はドメインを動的に払い出すようなことはしていなかったためすべて手動で管理していたのですが、今回は自動で書き変わります。

ExternalDNS起因の不具合によるレコード消失などの事故を防ぐため、今回はExternalDNSが操作できるDNSのホストゾーンのスコープを絞ることにしました。具体的にはDNSの権限委譲を利用して、特定サブドメインのネームサーバーを別のネームサーバーおよびホストゾーンで管理しています。またExternalDNSのDNSレコード更新ポリシーで、upsert-onlyを指定して削除しない状態で稼働させています。

DNSレコードをTerraformやCloudFormationで管理している場合も、今回の手法は有用でないかと思われます。

SQL発行されすぎ問題

またKubernetesには直接関係ない問題なのですが、使用していたExposedというO/Rマッパーがクエリ発行のためにSET autocommitなど、余計なSQLを発行してしまい実行速度に問題が生じていました。ただExposedのissueにも書かれているのですが、作者はオートコミットを有効にすること自体に反対なので今後も対応される気配はありません。

しかしこのままでは性能問題が解決しないので、独自にオーバーライドするクラスを作成し、SET autocommitなどのSQLを発行する挙動を抑制しました。代償として気軽にバージョンを上げられなくなってしまいましたが、がんばって追従していく方針で開発を進めています。

前述した通り、今回はアプリケーションフレームワークにKtorを採用しました。DBのコネクション数やスレッド数がデフォルト値のままだったため、CPUやメモリを使い切らないのにスループットが上がらない問題が生じました。これら設定値に関しては負荷試験を通じて適切な値を指定することで解決しました。

負荷試験に関しては今回のKubeFestにて弊社藤井が「EKS x Locustで構築する大規模負荷試験環境」という内容でLTを実施予定なので、ぜひ見てもらえればと思います。

今後の展望

今後の展望です。特段の問題も発生せず安全稼働していることから、今後の展望としてはリソース集約を目的とした既存サービスの移行を計画しています。Kubernetesを使った技術的挑戦という観点から、比較的ステートフルな通信をするWebsocketクラスタをKubernetes上で構築することを計画しています。

まとめになります。最終的なインテグレーションテストやデバッグを経て、無事に2019年12月上旬にリリースできました。2019年6月にプロジェクトを開始したので約半年でリリースできたことになります。性能的にも申し分なく、導入に関しては良いスタートを切れたと思います。

今までの発表からKubernetesを使ってはいるものの、そこまでKubernetes固有の機能を使っているわけではないなと感じた方も多いかもしれません。カスタムリソースを使用してこそのKubernetesであるという考えも理解できます。

しかしどんなに先進的かつクラウドネイティブな手法を手に入れたとしても、運用が回らなければ意味がありません。それよりも段階的に導入し、開発組織にKubernetesの開発手法や文化を浸透させ、Kubernetesでしかできないような機能を徐々に入れていくのが、自然かつ負担のないやり方ではないかと考えています。

行きつく先としてKubernetesを囲む豊富なエコシステムを利用し、今までになかったユーザー体験をもたらせるように考えるのが、サービス開発がメインミッションの開発組織におけるKubernetes導入の意義・効能だと考えます。

Kubernetesは基本的に使うのであれば導入コストはかかりますが難しくはありません。Kubernetesにおけるデプロイ体験は非常に重要なので、Gitブランチ運用やデプロイフローは開発メンバーで自然に議論していくことが不可欠です。「開発組織に分化を作る」と言いましたが、開発メンバーの全員がKubernetesに詳しくなれと言っているのではなく、Kubernetesを透過的に扱いサービス開発に集中できる環境を提供するのが好ましいと考えます。

Kubernetesを用いてどのようなユーザー体験を提供できるのかを常に考える必要があります。またちょっと話が逸れるのですが、新しく入ってきた新卒のメンバーがGitのフロー整備などや性能検証を実施してくれたので、思い切って任せるとけっこういい方向に進むのでおすすめです。

最後になりましたが、サイバーエージェント、CyberZでは一緒に働いていただける方を探しています。ご興味のある方はぜひお気軽にご連絡をください。

以上で発表を終了します。ご清聴ありがとうございました。