WESEEK Tech Blog

WESEEK のエンジニアブログです

コスト7割減!Kubernetes本番サービス環境の運用ノウハウ

この記事は、 2021/4/22 に行われた WESEEK Tech Conference の内容です。

Google Compute Engine のプリエンプティブルインスタンスを利用したクラウド利用料を節約するお話をしました。 Google Cloud Platform 上で稼働している GROWI.cloud を実例に上げつつ、節約額、稼働率の運用実績についてもご紹介しました。

目次

開始早々タイトル訂正

f:id:skomma:20210427155111j:plain

イベント開催を告知した時点では

「コスト8割減!Kubernetes本番サービス環境の運用ノウハウ」

というタイトル名でしたが、本資料を作成し、改めて価格を比較してみたところ以下のタイトルが適切であることが判明したため、タイトルを訂正しました(笑)

「コスト一部8割7割減!Kubernetes本番サービス環境の運用ノウハウ」

GROWI.cloud とは?

GROWI.cloud の利用状況

2021/04/14 現在、以下のような利用状況です。

  • 総 GROWI 数: 1022
    • うち約 20 % が課金プラン利用の GROWI
  • GROWI が載っているノード数: 32
    • うちプリエンプティブルノード数は 27 台

利用技術

アプリ

f:id:skomma:20210427163157j:plain

インフラ

f:id:skomma:20210427163215j:plain

なぜ GKE を利用しているか

以下の 4 点から、GROWI.cloud では GKE を採用しました。

  1. 利用実績があった
    • GROWI.cloud 開始前の別プロジェクトで利用していた
    • そのプロジェクトでは、アプリ・ミドルを載せるだけのシンプルな構成だった
    • GROWI.cloud では、もっといろいろな機能にチャレンジしている
  2. master がマネージドである
    • 自前で master を構築・運用するのは大変
    • ただ、これが原因でなかなかトラブルが解消されなかった例も…
      • トラブルの詳細は、5/27 の本イベントで話される予定です!
  3. ノードの増減が簡単である
    • GUI/CLI で簡単に増減できる
    • OS、kubernetes node として動かすためのディスクイメージが用意されているため、そこを考える必要がなくなる
    • オートスケールもできる
  4. ノードプールという単位で、ノードの構成が変えられる
    • これにより、ノード/プリエンプティブルの有効無効の切り替え、オートスケールの有無の切り替えなど、ノードの設定を塊ごとに管理できる
    • 1 つの GKE クラスタに様々な設定のノードを混在させることが可能となる

コストを下げたい!

上述の通り、GKE はクラウド上で Kubernetes クラスタを稼働させるのに、いろいろな便利な機能が付加されています。
しかし、GROWI.cloud のように SaaS 型で利用者が増えるたびにノードを増やす必要がある場合、料金はうなぎ上りに増えていきます…

そこで、どうにか安く利用する方法がないか模索したところ、今回ご紹介するプリエンプティブルノーにたどり着きました。

プリエンプティブルノードとは?

cloud.google.com

  • Google Compute Engine のノードの 1 種類
  • インスタンスタイプで、通常のノードと比較して約 70 %割引で使える
  • ただし、最大 24 時間しか連続で稼働できない
    • 「最大」なので、24 時間以内の停止ももちろんありうる
    • AWS だと EC2 スポットインスタンスが同じ概念の位置する
    • が、利用者同士でインスタンスの売買ができるほど賢くはない

プリエンプティブルノードの使いどころ

  • 突然停止しても問題ないワークロードが載っているノード

これらのアプリを、プリエンプティブルノード上で稼働させると利用料を抑えることができます。

GROWI.cloud クラスタ内のノード構成

大きく以下の括りでノードを構成しています。

  1. growi.cloud のシステムコンポーネントが載るノード
    • growi.cloud アプリ
    • GROWI を作るための workflow engine(brigade.js)
    • 管理用アプリ
  2. GROWI が載るノード
  3. GROWI の DB をバックアップするスクリプトが動くノード
    1. のうち特に分離しておきたいコンポーネントが載るノード
    2. メモリを極端に必要とするミドルウェアなど

上記のうち、2./3. についてプリエンプティブルノードを利用しています。

ノードの振り分け方

Kubernetes 上にある以下の機能を利用して実現しています。

  1. Node Affinity
  2. Taint/Toleration

Node Affinity

  • Kubernetes が Pod をスケジュールする際に、スケジュールされる先のノードを指定する機能
  • Pod 上の設定として記載する
  • ノードはノード名で指定するのではなく、key/value のラベルで設定する
  • 具体的な YAML 例(app=growi-app というラベルが指定されているノードに必ずスケジュールする)
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: app
            operator: In
            values:
            - growi-app

※参考 kubernetes.io

Taint/Toleration

  • Taint
    • 許容(tolerate)できない Pod のスケジュール/実行を禁止する機能
    • Node 側に、key/value/effect を設定する
    • 具体的な YAML
taints:
- effect: NoExecute
  key: app
  value: growi-app
  • Toleration
    • Taint が設定されたノードへ Pod をスケジュールするために、許容する機能
    • Pod 側に設定する
    • 具体的な YAML
tolerations:
- key: app
  operator: Equals
  value: growi-app
  effect: NoExecute

※参考 kubernetes.io

GROWI 作成時のフロー

フリープラン利用者が GROWI を起動するときのフローを例に、実際の動きをご紹介します。

f:id:skomma:20210427163524j:plain

まず、GROWI.cloud UI 上でユーザが GROWI を作成すると、

f:id:skomma:20210427163530j:plain

GROWI.cloud アプリからワークフローエンジンである brigade.js へ REST で作成リクエストが飛びます。

f:id:skomma:20210427163539j:plain

brigade.js では helm で GROWI マニフェストのデプロイを試みます。

f:id:skomma:20210427163552j:plain

helm によりデプロイされたマニフェストを元に、Kubernetes(kube-scheduler) がスケジューリング先ノードを決定します。

f:id:skomma:20210427163606j:plain

まず、Node Affinity で指定されたノードのラベルを比較し、真ん中のノードがスケジュール先候補として選ばれます。
フリープランはプリエンプティブルノードに配置されるプランのため、cloud.google.com/gke-preemptible=true をラベルとして持っているノードのみが選択されるようになっています。

f:id:skomma:20210427163618j:plain

その後、Node の taints と Pod 内の tolerations を比較し、スケジュール先が決定されます。

f:id:skomma:20210427163625j:plain

晴れて、GROWI.cloud 利用者が GROWI を利用できるようになります!

f:id:skomma:20210427170121j:plain

同様のフローで、通常ノードに配置されるべきスタンダートプラン利用者の GROWI は、通常のノードにスケジュールされます。

プリエンプティブルノードの罠と現時点での対策

コストが下がってみんなハッピー、というわけではなく、それなりに運用でカバーしないといけない点が出てきました。

必ず 24 時間動くとは限らない

公式ドキュメントに記載されてますね、はい…

ライト・バリュープランについては、以下のように対策を行いました。

  • 1 度ノードの再起動がかかると、5~10分程度 GROWI がダウンする
    • フリープランは SLO を設定していないため、問題ない
  • ライト・バリュープランに関しては、プリエンプティブルノードを利用していて安価だが、SLO 付き
    • いつ落ちるかわからないノードだと SLO まで稼働率を上げられない…
    • -> GROWI Pod を冗長化して対応した
    • 冗長化した後、稼働率の向上に成功

常に在庫があるとは限らない

これまでの経験上、1年に1回程度は発生するようです。

在庫不足に陥ったときは、日本近辺のリージョン(オーストラリア、シンガポールなど)でもプリエンプティブルノードが起動しなかったことを確認しました。
ML などで大量にノードを奪ってく人がいるんでしょうか…

そのようなケースについて、以下の対策を行いました。

  • affinity 内の nodeSelectorTerms 内に、在庫が足りなかったときのための設定を追加しておく
    • nodeSelectorTerms は array で指定でき、OR 判定になる
  • 実際に、プリエンプティブルノードが異常に DOWN したら、人手で適合するラベル/taint をつけた緊急配備用のノードプールを作成する
    • 新たに作成したノードに GROWI Pod が載る
    • プリエンプティブルノードを試しに起動してみて、起動できそうだったら緊急配備用のノードを drain する

f:id:skomma:20210427163754j:plain

通常スケジュール用の nodeSelectorTerm に加え、緊急配備用の nodeSelectorTerm を予め設定しています。

時々腐ったノードが出てくることがある(再現性不明)

載っている GROWI Pod が長期間(30分以上) healthy にならない事象が出るノードが時々出てくることがあります。
この事象に対しては、まだ効果的な対策が打てていない現状です。

再現性不明のため、アラートで長期間 down している GROWI が出てきたら、アラート発生時にエンジニアが手動対応しています。
具体的には、問題のあるノードを drain して、都度載っている GROWI を別ノードに移動しています。

ここも将来的には、ノード上のリソース利用状況を見つつ、自動化していきたい分野です。

プリエンプティブルノードの実績値

費用比較

割引適用なし

  • GROWI プリエンプティブルノード数: 28(2021/04/21 現在)
  • 通常ノードの場合: USD 2,902.86 per 1 month
  • プリエンプティブルノードの場合: USD 870.86 per 1 month

通常ノードを利用している分を単純にプリエンプティブルノードに置き換えると、約 70% の削減が可能です。

3年継続割引利用時

  • 通常ノードの場合: USD 1,306.29 per 1 month
  • プリエンプティブルノードの場合: USD 870.86 per 1 month

3年継続割引利用の通常ノードをプリエンプティブルノードに置き換えると、約 33% の削減が可能です。

GROWI.cloud 全体

  • 通常ノード(3年確約利用適用)だけで構成した場合
    • USD 2,303.26 per 1 month
  • GROWI が載るノードに、プリエンプティブルノードを利用した場合
    • USD 1,867.84 per 1 month

GROWI.cloud クラスタで利用しているノード全体のうち、プリエンプティブルを許容する GROWI が載るノードを プリエンプティブルノードに置き換えると、約 20% の削減ができました。

稼働率

実際の稼働率

  • 対象期間: 2021/04/14 ~ 2021/04/21
    • GROWI.cloud 全体: 99.53%
    • 通常ノード上の GROWI: 99.97%

プリエンプティブルノードを利用した場合でも、Pod を冗長化すると稼働率を大幅に向上させることができました!

プリエンプティブルノードの実際の再起動回数

  • 対象期間: 2021/04/14 ~ 2021/04/21
    • プリエンプティブルノード数: 28
    • 平均再起動回数: 8.64 回 (7回~12回)
    • 稼働時間ワースト3: 00:06:47, 00:08:21, 00:12:56

平均を取ると、ほぼ 1 日 1~2 回再起動するノードがほとんどでした。
しかし、稼働時間のワーストを取ると、やはり稼働時間が保証されていないノードであることを示すように、時々 10 分前後で再起動されるノードもいました。

まとめ

プリエンプティブルノードの活用方法

  • プリエンプティブルノードとは?
    • 最大 24 時間稼働するノード
      • 24 時間以内に再起動することもありうる
    • 以下のような用途向け
  • GROWI.cloud では以下の用途で活用している
    • バックアップスクリプトを動かすため
    • フリー/ライト/バリュープランの GROWI を動かすため
      • ライト/バリュープランの GROWI については Pod を冗長化することで稼働率が向上しました

今後

  • GROWI の稼働率向上(案)
    • プリエンプティブルノードでもノード再起動前に意図的に Pod or ノードの再起動を自動的に実施し、稼働率を向上させる
    • プリエンプティブルノード再起動時にノード上に載っている GROWI の起動タイミングを自動的にずらす
      • GROWI は起動時に一番 CPU を使うため、同一ノードに乗る GROWI 数を減らすことで、起動時間短縮を狙う
  • プリエンプティブルノード再起動時の 503 画面でユーザに原因をわかりやすくする
    • 現状 GROWI.cloud 上の GROWI 詳細画面では出るが、GROWI アクセスだけではわからないので、ユーザに不親切な状況です…

著者プロフィール

今間 俊介

株式会社WESEEK / バックエンドエンジニア

ISP に 2 年弱勤務した後、2013 年 3 月に WESEEK へ join。
現在は、GROWI.cloud などプロジェクト問わず、Kubernetes を中心としたインフラ/アプリの設計・構築・運用に携わる。
最近の業務時間外は、子供の面倒見、家事手伝い。

株式会社WESEEKについて

株式会社WESEEKは、システム開発のプロフェッショナル集団です。

【現在の主な事業】

  1. 通信大手企業の業務フロー自動化プロジェクト
  2. ソーシャルゲームの受託開発
  3. 自社発オープンソースプロダクト「GROWI」「GROWI.cloud」の開発

GROWI

GROWIは、Markdown記法でページを記述できるオープンソースWikiシステムです。

【主な特徴】

  • テキストも図表もどんどん書ける、強力な編集機能
  • チーム拡大に迅速に対応できる管理者向け機能を提供
  • 充実した機能・サポートでエンタープライズにも対応

GROWI.cloud

GROWI.cloudOSSのGROWIを専門的知識がなくても簡単に運用・管理できる、法人・個人向けの商用サービスです。

大手SierISPから中小企業・大学などの教育機関まで幅広くご利用いただき、さらに個人や大学サークルでもご利用いただいています。

【導入事例記事】
インターネットマルチフィード株式会社様 growi.cloud

株式会社HIKKY(VR法人HIKKY)様 growi.cloud

WESEEK Tech Conference

WESEEK Tech Conferenceは、株式会社WESEEKが主催するエンジニア向けの勉強会です。
月に2回ほど、WESEEKに所属するエンジニアが様々なテーマで発表を行う予定です。

次回は、4/22(木) 19:00~20:00に開催予定です。 ArgoCDを使ったGitOpsの利用について、インターンでエンジニアをしている岡がお話します。

現在、connpassやTECH PLAYで参加受付中です。皆様のご参加をお待ちしております! weseek.connpass.com TECH PLAYはこちらから

一緒に働く仲間を募集しています

東京の高田馬場オフィス、大分にある別府サテライトオフィスにてエンジニアを募集しております。
中途採用だけではなく、インターンシップも積極的に受け入れています!

詳しい募集要項は、弊社HPの採用ページからご確認ください。