Upgrade to Pro — share decks privately, control downloads, hide ads and more …

コンテナうまみつらみ〜Kubernetes初心者がEKSと格闘した1年を振り返る / cont...

コンテナうまみつらみ〜Kubernetes初心者がEKSと格闘した1年を振り返る / container_umami_tsurami

「Dockerちょっと触ったことある」程度の2年目エンジニアが突然EKSのプロジェクトリーダーになってからこれまでの話をしようと思っています
サービスの成長拡大しながら現状どんな状態なのか赤裸々に、ある程度アーキテクチャや具体的な数値交えつつ
本番稼働開始に至るまでの苦労話(実際に僕が学んでみてどうだったか含め)
本番稼働してみてから遭遇した課題等々(スケール周り、監視周り、などなど)
現在の課題(可観測性、Istio、ManagedNodegroup/Fargate)

ta-dadadada

January 23, 2020
Tweet

More Decks by ta-dadadada

Other Decks in Technology

Transcript

  1. 自己紹介 多田 吉克(@ta_dadadada / Tada Yoshikatsu) • (株)いい生活 サービスプラットフォーム開発部エンジニア •

    物理学科の修士課程修了 • いい生活に新卒で入社、もうすぐ丸3年 • 2年目までは平凡なアプリケーションエンジニアだった(前フリ) ◦ API の機能改修や品質改善(クエリの高速化とか)やっていた(主に Python) 2
  2. Contents • ことの起こり • 今稼働しているモノ • 稼働までの苦労 ◦ コンテナを作る、 CI/CD、監視、Envoy

    つらい • 稼働してからの苦労 ◦ HPA、スループット改善、 Pod のスケーリングとの格闘、監視系運用のつらみ • これからの課題 3
  3. プロダクトの概要 • 当社の主力製品(いい物件One)から/へのデータ連携を行う外部連携用シス テムの新規構築 ◦ 物件広告情報(テーブルデータ、画像データ)の取り出しと、エンドユーザからの問い合わ せ情報を取り込み ◦ いい物件One のバックエンド

    API (Python) はデスクトップアプリケーション用に作り込まれ た(レガシーな)システムのため外部公開するには使いづらく、かつ月一回程度メンテナン スタイムが存在してしまうため、より可用性の高いシステムを別途構築 ◦ 画像部分とユーザからの問い合わせ部分は既存プロダクトがあったため、新規に作り込んだ のは広告情報の部分 • さっくり言えば、鎖国気味の既存システムに接続するオープンな使いやすい APIを作ろう!という話 8
  4. EKSでデプロイできるまで • eksctl はあまり使わずクラスタは CloudFormation で作成 ◦ クラスタ作成までは当社 CTO の

    素振り を後追いしたのでスムーズだった • 「とりあえずデプロイできる」段階までも苦労はあまりなかった ◦ アプリケーションをコンテナで包んで deployment 書くだけならば、 正直見様見真似でもどうにでもなったので、そこから改善していった • サービスの公開には ALB Ingress Controller を利用 ◦ k8s の外側のリソースを(あまり)気にせずに LB 立てられて便利だった 15
  5. 使いやすいコンテナに仕上げる工夫 • 設定値を注入可能にする ◦ アプリケーションの設定値を環境変数から読むようにする定番戦略に加えて、アプリケー ションのコードレポジトリ側にデフォルトの設定値を持たせた ▪ 変数なくてもコンテナ単体で動作する状況の方が開発でも使いやすい ◦ k8s

    側から環境変数や起動時引数与えることで上書きできるよう設計、非機能テストの段階 でのチューニングが k8s ファイルの書き換えで済むため、かなり容易になった ◦ 設定値が無理なく自然にコード管理されている喜び・・・ • コマンドクエリ責務分離 (CQRS) パターン を拡張して適用 ◦ 同じデータモデルを扱うサービスでも更新系(コマンド)と参照系(クエリ)を 別のマイクロサービス ≒ Deployment として扱う ◦ 更新と参照で別の言語・フレームワークを使うことが比較的容易に可能 ◦ 更新と参照では負荷傾向が全く違うこともあるので、分離することで 細密なチューニングができるようになった 16
  6. CI/CD • 実行環境は既存の社内 CI サーバ(Drone.io) と AWS CodeBuild を併用 ◦

    コードレポジトリ(GItLab)がオンプレにあり既存のものを使うほうが楽な場面とそうでな い部分があるため • nightly ビルド + デプロイ ◦ develop ブランチに対してビルド&プッシュを定時実行 ◦ ImagePullPolicy: Always にして kubectl rollout を CodeBuild から実行しステージング環境に 自動デプロイ ◦ タグ付け起因の stable バージョンのビルド&プッシュ • 機能テスト(ふるまいテスト)と性能テストを開発環境対して定期実行 ◦ テストコード自体は Drone.io でビルドして ECR にアップし、 CodeBuild で実行 • リリースサイクルの高速化に貢献 17
  7. 監視系 • メトリクスは Prometheus で収集、 Grafana で可視化 ◦ promethus-operator by

    Helm でサクッと構築、修正も殆どなかった • ログは Fluent Bit + Firehose + Splunk ◦ Splunk App で CloudWatch Logs からとる方法もあるが、取り込まれるまでの時間差がある ため、 Splunk Http Event Collector(HEC) で送信して回避する今の構成に • Prometheus のメトリクス長期保存は挫折 ◦ InfluxDB(時系列DBで、 Prometheus の Long-Term Storage として使える) を EC2 に構築 して試した ◦ 大量のメトリクスを1台で捌き切るのに無理がありすぐ死ぬ ▪ 開発環境での検証段階で死んだ ◦ クラスタ化などを考えている余裕がなかったため、断念 ◦ Thanos? 18
  8. Envoy つらかった • Pod を app + sidecar(Envoy) で構成しサービスメッシュを実現、 Istio

    は 使っていない ◦ はじめはコンフィグの勘所がわからず ▪ Envoy のメリットを頭ではわかっていていても書くのがつらい ▪ そもそも設定項目が膨大で初見だと心が折れる ▪ yaml でかける点、ドキュメントは結構充実している点、最悪 Envoy のソースコード見 ればなんとかなる点は救いだった ◦ やってるうちになんとか読み書きできるようになっていったので、慣れ ▪ サービスメッシュについては、ちょうどこのあたり考えているときに聞いて大変参考に なりました • サービスメッシュは本当に必要なのか、何を解決するのか | AWS Summit Tokyo 2019 19
  9. ClusterIP の場合① Envoy Envoy:10.0.0.2 Envoy: 10.0.0.1 Envoy: 10.0.0.3 Service: ClusterIP

    • Egress の Envoy は PodIP を直接は知らない(Service が LB する) 21
  10. • Pod が死んでも Service が unhealthy と判断するまではルーティングされる • 「偶に」リクエストが失敗する &

    Egress Envoy が Sevice 自体を Circuit Breaking するかも ClusterIP の場合② Envoy Envoy:10.0.0.2 Envoy: 10.0.0.1 Envoy: 10.0.0.3 Service: ClusterIP 22
  11. • Headless Service では直接 Envoy が IP アドレスを知っている Headless Service

    の場合① Envoy Envoy:10.0.0.2 Envoy: 10.0.0.1 Envoy: 10.0.0.3 Service: Headless 23
  12. • Pod が死んでも Envoy 自身が検出して即 Circuit Breaking できる Headless Service

    の場合② Envoy Envoy:10.0.0.2 Envoy: 10.0.0.1 Envoy: 10.0.0.3 Service: Headless 24
  13. Service: ClusterIP の罠② • まとめると、 ◦ 接続性の問題は LB が2段あることだった ▪

    LB が Envoy と k8s Service の2段あり、Envoy の方が細かく health check しているも のの、 Service の遅い health check 律速でしか配送先の切り替えが起きないため ▪ Service を Headless 化することで LB を Envoy に任せることで安定した • Envoy to Envoy の接続以外でも同じことが起こる ◦ 例えば RDS に Write/Read Endpoint を通して接続するとき、実際の IP は RDS 側が払い出す ◦ Envoy にコネクションを任せてしまうと使えない IP address を掴んだままになったりする ◦ 結局 Envoy は通さずアプリケーションでコネクション管理している ◦ Amazon RDS Proxy にちょっと期待 複数の LB を挟んでしまう場合、いかに死を素早く伝達できるか 25
  14. コンテナ化の恩恵 • プロジェクトが動き出してから、同じ EKS 内に移設することが 決まったサービスがいくつかあった ◦ Elastic Beanstalk で動作していた

    Python 製 API ◦ オンプレ環境で動作していた Python 製 API • コンテナ化さえやってしまえばなんとかなる!で実際乗り切れた ◦ 新規開発していたプロダクトとは利用しているフレームワークやバージョンの違いなども あったが、最小限の調整でやりきることができた 26
  15. Pod へのリソース割当① HPA(Horizontal Pod Autoscaler) 大暴れ • 同時実行数の微増減ですぐにスケールアウト/インするピーキーな状態 ◦ Pod

    の CPU 割り当てが不足 -> すぐにスケールアウトの閾値を超えてしまっていた ◦ Pod の限界性能はだいぶ余裕があったので無駄な素ケース ▪ テスト段階でリソース使用傾向や限界性能をきちんと把握できていなかった • 最大キャパシティをしっかりテストして測る ◦ 十分なCPUを割り当てた上で、性能劣化が起きるより前にスケールするよう調整 29
  16. Pod へのリソース割当② • 例)cpu使用量 200m くらいか ら性能劣化するケース ◦ 早めにスケールアウトさ せ、上限は余裕をもたせる

    30 apiVersion: apps/v1 kind: Deployment .. resources: limits: memory: 128Mi cpu: 700m requests: memory: 64Mi cpu: 150m --- --- apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler spec: targetCPUUtilizationPercentage: 80
  17. Pod へのリソース割当③ • CPU 不足は気づきにくい ◦ CPU 不足は性能劣化という形で現れる ◦ 性能評価を

    cpu: limits 設定した状態でやってしまうとベースラインを勘違いする ◦ 見ているメトリクスの解像度が足りていないこともある ▪ exporter の設定次第だが、15s 程度の解像度だと、もっとショートタイムの CPU バー ストが観測できない = 本当はもっと CPU 必要なことに気づけない • memory: limits をケチるとすぐに OOM Kill を食らう ◦ 「あれ、このコンテナ手元では起動したのに・・・」の原因の8割はメモリ不足(経験則) ◦ コンテナが死ぬため気づきやすくはある ◦ threading や async で処理をしている場合、同時実行数増やすことで同様の問題が起きるこ とも まずは limits 設定せずに性能評価してみること 31
  18. 同時実行数をいかに稼ぐか② • 起動設定で素朴にマルチプロセス/スレッドにできるフレームワーク/処理系 であれば、 1Pod の許容量をあげる手が使える ◦ 1Pod に対するリソース割当量は増加するのでコストは増えるかも ◦

    マルチ化や非同期化はオーバーヘッドで性能劣化する可能性もある ◦ コンテナでマルチプロセスすることの良し悪し 33 Pod process /thead process /thead process /thead
  19. 同時実行数をいかに稼ぐか③ • Pod自体をスケールさせる(≒プロセスを増やす) ◦ Pod が増えるのでコストはかかる、プロビジョニングまでの時差もある ◦ デフォルトの CPU 使用率を元にしたスケールだと機能不足な場合も多い

    ▪ リクエスト着弾数やDBコネクション数でスケールさせるには、カスタムメトリクスを利 用する必要がある ▪ ↑の状況を CPU バウンドに落とし込めているとチューニングしやすい • とりあえずで Pod 数にものを言わせた解決できるのは k8s の強み 34 Pod process /thead Pod process /thead Pod process /thead
  20. Podは死んでもリクエストは来る① パターンはいろいろある • Pod の死亡検知が間に合わず ALB がリクエストを配送され GateWay Error •

    アプリケーションの処理中に Pod Termination になり、 Envoy が先に死亡し た結果 Egress 通信ができず処理失敗する • コンテナ作成後のアプリケーション初期化処理の途中でリクエストが配送さ れ処理失敗する 35
  21. Podは死んでもリクエストは来る② • ALB のヘルスチェックが 最小 5s 間隔なので、検知が間に合わない ◦ クラスタ内に追加で Ingress

    を作り、 ALB -> Ingress -> Service の構成にすることを検討中 ◦ Nginx Ingress か Envoy Ambassador あたりが選択肢 • アプリケーションの状態を Readiness Probe に対応させる • コンテナ起動/停止順序を制御する ◦ コンテナの起動順序は非自明(大事) ◦ ライフサイクルフック(postStart/preStop) + Volume Mount を活用 ▪ 起動/停止完了するまで Sleep させる ▪ 秒数指定する場合 Sleep は 5-10s くらいが無難、preStop では猶予時間は 30s ◦ Istio 使ってもできるわけではなさそう ◦ k8s での対応時期も不明 ▪ https://github.com/kubernetes/kubernetes/pull/79649 36
  22. ログとメトリクス、多すぎ • k8s はコンポーネントが多いのでログも莫大 ◦ 現行本番で 4GB/day ◦ Splunk は

    ログ取得量/day でのライセンスなのでログ量を収める必要があった ◦ FIrehose + Lambda で正常応答系ログの一部をフィルタリング ▪ fluent-bit でもフィルタ可能 • メトリクスも膨大 ◦ Prometheus のキャパシティ不足 ◦ いったんはスケールアップで対応した ◦ 分散構成という手はあるが・・・ 37
  23. 可観測性 • ログを一部削ってしまっている問題 • Prometheus が不安定な問題 • トレーシングもやっていきたいという思いがある ◦ 一番導入しやすいのは

    AWS X-Ray だが、メトリクス・ログ・トレーシングで見る場所が割れ てしまうなどの課題があるため、監視系全体の再設計が必要と感じている • このまま自前での監視系運用を続けていくべきか?というのは大きな悩み 40
  24. • EC2 インスタンスやクラスタバージョンの管理 ◦ AMIの更新(結構高頻度)、 EKS のバージョンアップ • 稼働中 Pod

    の激増に伴い、ノードが不足する未来が見えつつある ◦ 3AZ × 最大 6 Nodes = 最大 18 Nodes となる Auto Scaling Group でクラスタを構成 ◦ Auto Scaling Group はスケールイン時にインスタンス配置が AZ 非対称になる可能性があ り、単純に Node 数の上限値を増やす解決はしたくない ▪ ノードのスケールアップを適宜行い、ノードの絶対数が増えすぎないように調整する手 はある クラスターマネジメント① 42
  25. クラスターマネジメント② • Managed Node Group ◦ EC2 の管理をしなくて済む ◦ スケールイン時の問題が払拭されるわけではないので、増えていく

    Node 必要数にどう立ち 向かうかは考えなくてはならない • Fargate どうするか ◦ 導入には Fargate 向けに根本的に構成見直す必要がある ▪ CNI に完全には対応していない、 PersistentVolume 使えない など(まだ)できないこ ともある ▪ DaemonSet 使えなくなるのは痛い(監視系コンポーネントなど Sidecar 化する必要) ◦ EC2 と Fargate 併用せざるを得ない気がし、「on EC2 での自然な書き方」と 「on Fargate に最適化した書き方」のテンプレートが出来上がって教育と管理のコストがやばそう 43
  26. チーム体制と教育 • EKS できるエンジニアは社内でもほんのひと握り ◦ どちらかといえば自習によって追いついてきている人々 • AWS も k8s

    もしっかり理解しないといけない ◦ デプロイするだけなら k8s だけ勉強しとけばいいかもしれないが・・・ • 重要な設定は yaml ファイルの1行にさらっと書かれてたりする ◦ どう伝えるか? • エンジニアの責務範囲を限定する ◦ アプリケーションエンジニアはコンテナ化までを考えればよい、という環境にしたい ◦ k8s エンジニアの負担はあまり変わらない気もする ◦ 結局 AWS や k8s のレイヤーまで理解しないと Production Ready なアプリケーションにはな らないと思うので、完全に分離してしまうのもどうか?という経験から来る個人的な思いは ある 44
  27. まとめ(所感) • コンテナ化すればなんとかなる世界は幸せ ◦ 間違いなく開発サイクルは高速化していると感じる • プロダクション運用してみないとわからないつらみもたくさんある ◦ マイクロサービス化含めてコンポーネントを細かく分割したことによる数の暴力に泣きがち ◦

    今の課題感の多くは、EKS にしたことによる苦労よりも、より良いアーキテクチャや可観測 性を目指した結果の苦労なので、辛くも楽しい • ただのアプリケーションエンジニアだったころに比べて圧倒的に多くの経験 値を得られた 45
  28. ALB の Pod 死亡検知遅れ① • ALB は Pod の IP

    を直接保持し、自身でヘルスチェックする 47 ALB Envoy:10.0.0.2 Envoy: 10.0.0.1 Envoy: 10.0.0.3 Service: Headless 10.0.0.1, 10.0.0.2, 10.0.0.3
  29. ALB の Pod 死亡検知遅れ② • Pod 死亡時に ALB のヘルスチェック=検知が間に合わずリクエストが配送さ れる

    48 ALB Envoy:10.0.0.2 Envoy: 10.0.0.1 Envoy: 10.0.0.3 Service: Headless 10.0.0.1, 10.0.0.2, 10.0.0.3
  30. ログの奔流① • aws-for-fluent-bit で全ログ収集していた • k8s はとにかくログが多い ◦ 現行本番環境で 4GB/day

    くらい ◦ アプリケーションログに加えて envoy のログ ◦ 監視系 agent や k8s-system 系のログも馬鹿にならない • 一日のログ取得可能量に制限があった ◦ Splunk は日毎のログ量=ライセンス のため ◦ Splunk でなくてもログ取り込み量はコストに直結する部分ではあるはず 51
  31. ログの奔流② • 本当に必要なログ以外はカットした ◦ 5xx 応答やアプリケーションログが見れない状態はクリティカルにまずい ◦ 一部の正常応答アクセスログなどをフィルタして落とす • Firehose

    を利用していたため、 Lambda を挟んでログをフィルタリング ◦ Chalice(Python) でさっと作ってデプロイ ▪ 秒速で Lambda 関数作りたいときに Chalice は強い ▪ 今のところ困っていないが Go で書き直したほうが性能は出る & バイナリにできるので 取り回しはしやすいのかも ◦ Lambda でやることで 仕組み自体は fluent-bit 経由以外のログにも適用でき汎用的に使える • fluent-bit の plugin でフィルタ ◦ k8sの中で完結させたいならこちらがおすすめ • フィルタすることで落ちた可観測性は課題 52
  32. 不安定な Prometheus • Prometheus から応答がない ◦ マイクロサービスの種類、 Pod 数ともに増大しておりメトリクスの絶対量も膨大に ◦

    特に Node がスケールしたタイミングは一気にメトリクスが増えるので不安定な傾向 ◦ これを1台の Prometheus で処理することに限界があった ▪ 稼働中はよくても、なにかの拍子に Prometheus の Pod が死ぬと、起動時にはメモリ 不足でいつまで経っても起動しなくなってしまう、という問題にも悩まされた • 一旦はワーカノードのスケールアップで対応 ◦ 常にリソースが必要というよりは瞬間的なものなので t3 系がおすすめ • Prometheus の場合、メトリクス収集対象分割という手段はある ◦ Prometheus 自体を分散させることで負荷を軽減させる作戦 ◦ 設定はそれなりに複雑で作り込む必要がある ◦ Prometheus 自前運用していくコストを払えるか? 53