本稿はJCB Advent Calendar 2024の12月12日の記事です。
JCBデジタルソリューション開発部PFチームの島﨑です。
JDEPではKubernetesを用いたシステム運用において、開発・Quality Assurance・Stage・Productionの4段階構成を採用しています。
各環境は1リージョンシングルクラスタで構成しています。
しかし、アプリケーション数の増加に伴い、占有試験の需要が増加しました。
そのため現在検証環境(=Stage)では、試験用に複数のノードプールを用意しています。
今回はJDEPで分割して使用しているノードプールのノードのスケールの自動化を導入した背景と結果について紹介していきたいと思います。
そもそもなぜノードプールを分割しているのか
JDEPでは現在以下のような場合でノードプールが分かれています。
- 複数のアプリケーションチーム(以下APLチーム)が同時利用する本番相当の検証環境のノードプール
- 性能試験等の環境専有用途で利用するノードプール
- 障害試験実施用途で利用するノードプール
性能試験の日程が重なった際に影響を避けるためにノードプールを分けることで以下のメリットが挙げられます。
コミュニケーションコストの削減:
複数のAPLチームが様々な試験を1つの環境で実施するのでスケジュール調整などのコミュニケーションコストが増大していたが、ノードプールを分割することで各チームが別の占有環境で試験を実施できるようになった他の環境への影響排除:
性能試験や障害試験の用途で使用しているノードプールで大きな負荷がかかった場合でも、APLチームで使用している本番相当の検証環境のノードプールや他の試験用のノードプールには影響を及ぼさないため、各チームがそれぞれのリソースを安心して最大限に活用できる
このようなメリットからJDEPではノードプールを分割して利用する方法を採用しています。
自動化導入前の各チームの運用作業
これまでは、APLチームが使用したい期間のGoogleカレンダーに登録し、PFチームが毎朝デイリーで予約状況を目視して手作業でノードプールの設定を変更していました。
例:
設定変更としては以下のように行っていました。
- 使用予定がある場合、ノードプールのオートスケール設定である最小ノード数の設定を1にする
- 使用予定がない場合、ノードプールのオートスケール設定である最小ノード数の設定を0にしてノードを削除
最小ノード数の設定が0かつノードが0の場合はノードが1以上にスケールアウトが実行されないため、ノードが存在しない状態にすることでコスト削減を計っていました。
課題
この運用方法では以下のような課題がありました。
- 運用負荷の増大:
毎朝カレンダーを確認し、必要があればノード数を手動で調整する必要があり、運用負荷がかかっていました。 - ノードのスケール調整のタイムラグ:
人手による操作のため、ノードのスケールアウト・スケールインにタイムラグが発生し、リソースの最適化ができていませんでした。
自動化した内容
APLチームの運用方法は変更しないことを考慮して、Googleカレンダーの予約情報をAPIで取得し、取得した時間に合わせて自動設定できるように構成しました。
使用したリソースはGoogle CloudのCloud Scheduler(以下Scheduler)とCloud Run functions(以下GCF)です。
Schedulerで30分おきにGCFを起動します。
Cloud Run functionsの内容
初めに現在時刻から前後1時間後のGoogleカレンダーの登録情報を確認します。
Googleカレンダーからの取得方法としては以下のように取得しています。
import os from datetime import datetime, timedelta from google.oauth2 import service_account from google.auth.transport.requests import Request from googleapiclient.discovery import build # 現在から明日の同じ時間までの間のイベントを取得 current_date = datetime.now() print("[current_date]: {}".format(current_date)) # 関数実行時の"時間"の取得と実行時間の作成(カレンダーの時刻より1時間前後を作成) time_to_add = timedelta(hours=1) current_time_one_hour_ago = current_date - time_to_add current_time_one_hour_later = current_date + time_to_add current_time_one_hour_ago_set = current_time_one_hour_ago.strftime('%Y-%m-%dT%H:%M:%SZ') current_time_one_hour_later_set = current_time_one_hour_later.strftime('%Y-%m-%dT%H:%M:%SZ') impersonation_creds = getCredentials() service = build( "calendar", "v3", credentials=impersonation_creds, cache_discovery=False ) # Call the Calendar API events_result = ( service.events() .list( calendarId=data["calendarID"], timeMin=current_time_one_hour_ago_set, timeMax=current_time_one_hour_later_set, maxResults=10, singleEvents=True, orderBy="startTime", ) .execute() ) events = events_result.get("items", [])
Googleカレンダーでノードプールの使用があるかどうかで以下のように動作します。
現在指定のノードプールのノード数が0の場合
- 1時間後に指定のノードプールのカレンダー登録がされている場合:
「ノードプールのNode Taintsのeffectの設定をNO_SCHEDULEにする」 かつ 「ノードプールのオートスケールをオンにし、オートスケール設定の最小ノード数の設定を1にする」 - 1時間後に指定のノードプールのカレンダー登録がされていない場合:
設定変更なし
ノードプールのNode Taintsのeffectの設定をNO_SCHEDULEにすることで、マッチしたPodのみスケジューリングされて立ち上がるようになります。
その後、オートスケール設定の最小ノード数の設定を1にすることでノードがスケールアウトされます。
1時間後の値を参照している理由としては万が一にAPI取得等で処理が失敗したとしても30分間隔でリトライできるようにしています。
エラー検知の仕組みも導入しているため、そちらでもエラー発生時の対応は可能です。
現在指定のノードプールのノード数が1以上場合
- 1時間前に指定のノードプールのカレンダー登録が終わっている場合:
「ノードプールのオートスケール設定をオフにする」 かつ 「ノードプールのNode Taintsのeffectの設定をNO_EXECUTEにする」 - 1時間前に指定のノードプールのカレンダー登録が続いている場合:
設定変更なし
ノードプールのオートスケール設定をオフにすることでノード数が0になります。しかし、そのままノードプールのNode Taintsのeffectの設定をNO_SCHEDULEにすると、再度podが立ち上がってしまうため、ノードプールのNode Taintsのeffectの設定をNO_EXECUTEにすることで、マッチしないpodが即座にノードから削除されるようになります。
1時間前の値を参照している理由としてはAPLチームが急遽試験時間の延長を設定した場合にも対応できるように考慮して設計しました。
自動化の導入結果
上記自動化施策を導入した結果、毎朝の確認は不要になり、作業コストの削減ができました。エラー検知の仕組みも、現在まで発報実績もなく継続して運用できています。
また、自動化する前は使用する場合、短い時間でカレンダーを入れていても終日でノード数が1以上になっていたため、余計な費用がかかっていました。
以下は性能試験や障害試験用途の場合に使用するノードプール4つの起動時間になります。8月頭ぐらいから今回の自動化を導入しているのですが、APLチームの使用可否によっては増減があるものの、30分ごとのチェックによるスケーリングによって全体の起動時間が抑えられて、コスト削減に繋げることができました。
まとめ
GKEのノードプールを分割し、Cloud SchedulerとCloud Run functionsを用いてノードのスケールを自動化することで、運用コストとノードプール費用の削減を実現できました。
今後も運用改善や費用削減につながる自動化についても検討を進めていきたいと思います。
終わりに
最後に、JCBでは我々と一緒に働きたいという人材を募集しています。 詳しい募集要項等については採用ページをご覧下さい。