【祝日対応版】GCEインスタンスの自動停止/自動起動でサーバー費を節約しよう
メタサイトエンジニアのYKです。
突然ですが、日常生活で水を使い終えたらどうしていますか?水を止めているのではないでしょうか。ガスコンロを使い終えたときはどうでしょうか?ガス(火)を止めていますよね?
では、サーバーは?
24時間365日稼働しているシステムだから止めることはできない、という声が聞こえてきそうですが、テスト環境はどうでしょうか?
様々なコストが上がっている中、少しでもコストを抑えるべく、祝日に対応したサーバーの自動停止/自動起動の仕組みを導入したので、今回はその方法を紹介したいと思います。
何故サーバーを止めるのか?
サーバー費節約のためと言ってしまえばそれまでなのですが、もう少し具体的な理由を挙げると次の2点です。
-
円安ドル高によるサーバー費の高騰
Google Cloud Platform(GCP)やAmazon Web Services(AWS)、Microsoft Azureといったクラウドサービスを利用する場合、いずれのサービスも米国企業のため利用料は米ドル基準になります。
そのため、日本円基準で考える場合は米ドル/円の為替レートに左右されます。
少し前と比べれば多少は落ち着きましたが、まだまだ円安ドル高の状況は続いているのでサーバー費は高止まりしています。
-
サーバー数が多い
クロス・マーケティンググループ全体では様々なシステムがあり、その殆どが本番環境だけでなくテスト環境もあります。
つまり、その分だけサーバーが存在することになります。これらのサーバーにはクラウドサービスを利用しているためサーバーを起動しているだけでも利用料は発生しますが、その一方でテスト環境は業務時間外に使用することは殆どない状況です。
このような事情があり、費用節約のためにテスト環境のサーバーを「使わないときは止める」ようにしたい、という思惑がありました。
サーバー費の節約効果は?
以下の内容は本記事執筆時点の内容です。最新の料金については、料金ページをご確認ください。
「使わないときは止める」ので、なんとなくサーバー費を節約できそうなことはイメージが沸くと思いますが、節約効果はどのくらいになるのかを具体的にイメージできる人は少ないと思います。
そこで、Google Compute Engine(GCE)で今回紹介する設定を反映した場合に、どの程度の節約効果が見込めるのかを試算してみたいと思います。
1ヵ月は30日、VM インスタンスのマシンタイプ、自動停止/自動起動の設定は以下で試算します。
設定項目 | 設定値 | |
---|---|---|
VMインスタンスの設定 | リージョン | asia-northeast1(東京) |
マシンタイプ | n1-standard-1 | |
仮想CPU数 | 1 | |
メモリ | 3.75GB | |
料金(米ドル) | $0.0610 / hour | |
自動起動の設定 | 曜日 | 月曜日~金曜日 |
時間 | 9:00 | |
自動停止の設定 | 曜日 | 月曜日~金曜日 |
時間 | 22:00 |
GCEでは継続利用割引(SUD)があり、1ヵ月の総時間(24h × 1ヵ月の日数)のうち実際に使用した時間に応じて割引が適用されるので、上記のVMインスタンスで1ヵ月を30日(24h × 30日 = 720h)とした場合は以下のようになります。
使用時間の割合 | 使用時間 | 継続利用割引 | |
---|---|---|---|
割引率 | 割引後料金 | ||
0 ~ 25% | 0 ~ 180h | 0% | $0.0610 |
25 ~ 50% | 180 ~ 360h | 20% | $0.0488 |
50 ~ 75% | 360 ~ 540h | 40% | $0.0366 |
75 ~ 100% | 540 ~ 720h | 60% | $0.0244 |
また、祝日の日数は年によって変わりますが、現時点では年間で16日が「国民の祝日」として法律で定められているため、1ヵ月あたりは平均で1.3日になります。
1ヵ月のうち、土曜日、日曜日は8日、祝日は1日、平日は21日とすると、自動停止/自動起動をした場合は1ヵ月の起動時間は273h(平日の起動時間:13h × 平日の日数:21日)で$15.51、それに対して停止しなかった場合は720h(24h × 30日)で$30.73になり、約半額程度になります。
もし、上記の試算と異なる条件で試算したい場合は、Google社がGoogle Cloud 料金計算ツールを公開していますので、そちらを使うと簡単に試算できます。
GCEインスタンスの自動停止/自動起動方法
GCEインスタンスの自動停止/自動起動は、インスタンス スケジュールやCloud Functionsを使用した方法を既に多くの先人が紹介していますが、日本の祝日に対応した方法は少ない気がします。
無いなら作ってしまえば良い、ということで、以下のような処理フローで作ってみました。
Cloud Schedulerで祝日判定はできそうになかったので、Cloud Functionsで祝日判定をします。
肝心の祝日の情報については、内閣府のHPの「国民の祝日」についてに祝日の一覧がCSVで公開されているので、そちらを使用します。
また、年末年始や会社の創立記念日のような、祝日ではない休日にも対応できるようにしました。
「祝日まで考慮しなくても良いから、とにかく簡単、手軽に導入したい」という場合は、インスタンス スケジュールを使用するのが簡単だと思います。以下で設定方法が紹介されていますので、そちらをご確認ください。
VM インスタンスの起動と停止をスケジュールする | Compute Engine ドキュメント | Google Cloud
事前準備
GCEインスタンスを停止、起動することになるので、Webサーバー(NginxやApache)やDB(MySQLやPostgreSQL、MariaDB)といった、システムの動作に必要なサービスがあればサーバー起動時にサービスが自動起動するように設定変更しておいてください。
Cloud Functions/Cloud Pub/Subの作成
-
「Cloud Functionsの概要ページ」 > 「ファンクションを作成」
-
「基本」情報を入力する
設定項目 設定値 備考 環境 第 1 世代 関数名 schedule-gce-instance リージョン asia-northeast1 自動起動/自動停止をするGCEインスタンスと同じリージョン設定を推奨。 -
「トリガー」を設定(Cloud Pub/Subの作成)
-
トリガーの情報を入力する
設定項目 設定値 備考 トリガーのタイプ Cloud Pub/Sub Cloud Pub/Sub トピック 次の手順で設定します 失敗時に再試行する チェックを付ける 失敗時に再試行させたい場合のみ選択してください。 -
「Cloud Pub/Sub トピック」 > 「トピックを作成する」
-
「トピックID」を入力 > 「作成」
設定項目 設定値 備考 トピックID gce-instance-event -
「保存」でトリガー設定を保存する
-
-
「ランタイム」 > 「ランタイム環境変数」の設定をする
名前 設定値の例 備考 TZ Asia/Tokyo タイムゾーンを設定します。
後続の手順で作成するスケジューラーと同じタイムゾーンになるようにしてください。UNIQUE_HOLIDAY 1/2,1/3,12/29,12/30,12/31 「国民の祝日」以外で設定したい休日を設定します。
フォーマットはm/dで設定し、複数指定する場合はカンマ(,)で列挙してください。 -
「次へ」をクリック
-
「コード」を設定する
-
「ランタイム」/「ソースコード」/「エントリ ポイント」
設定項目 設定値 備考 ランタイム Node.js 18 ソースコード インライン エディタ エントリ ポイント scheduleGceInstance -
「index.js」のコードを設定する
const axios = require('axios'); const compute = require('@google-cloud/compute'); const instancesClient = new compute.InstancesClient({fallback: 'rest'}); /** * Compute Engineインスタンスの起動・停止処理 */ exports.scheduleGceInstance = async (event, context, callback) => { try { process.env.TZ = 'Asia/Tokyo'; const project = await instancesClient.getProjectId(); const payload = _validatePayload(event); let instances = payload.instance.split(','); let callback_message = ''; let message = ''; if (!await isHoliday() || payload.processing == 'stop') { for (let instance of instances) { const options = { project, zone: payload.zone, instance: instance, }; if (payload.processing == 'start') { await instancesClient.start(options); message = 'Successfully started instance ' + instance; } else if (payload.processing == 'stop') { await instancesClient.stop(options); message = 'Successfully stopped instance ' + instance; } callback_message += message + '\n'; } } callback(null, callback_message); } catch (err) { console.log(err); callback(err); } }; /** * request payloadのバリデート処理 * * @param object event * @return json payload */ const _validatePayload = event => { let payload; try { payload = JSON.parse(Buffer.from(event.data, 'base64').toString()); } catch (err) { throw new Error('Invalid Pub/Sub message: ' + err); } if (!payload.zone) { throw new Error("Attribute 'zone' missing from payload"); } else if (!payload.instance) { throw new Error("Attribute 'instance' missing from payload"); }else if (!payload.processing) { throw new Error("Attribute 'processing' missing from payload"); } return payload; }; /** * 祝日判定処理 * @returns bool true:祝日, false:平日 */ async function isHoliday() { let systemDate = new Date(); // カレンダーに無い独自の祝日を判定する if (process.env.UNIQUE_HOLIDAY.split(',').some((value) => { let holiday = value.split('/').map(e => parseInt(e)).join('/'); return holiday === [(systemDate.getMonth() + 1), systemDate.getDate()].join('/'); })) { return true; } // 内閣府のHP 「国民の祝日」について(https://www8.cao.go.jp/chosei/shukujitsu/gaiyou.html) // に掲載されているCSVファイルをHTTPリクエスト(GET)で取得する const response = await axios.get("https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv"); // 祝日を判定する const datePattern = /^(\\d+(\/|\-|\.)\\d+(\/|\-|\.)\\d+).*/; const dateSplitter = /\/|\-|\./; if (response.data.split(/\r\n|\r|\n/g).some((value) => { let holiday = value.replace(datePattern, '$1').split(dateSplitter).map(e => parseInt(e)).join('/'); return holiday === [systemDate.getFullYear(), (systemDate.getMonth() + 1), systemDate.getDate()].join('/'); })) { return true; } return false; }
-
「package.json」のコードを設定する
{ "name": "schedule-gce-instance", "version": "1.0.0", "private": true, "engines": { "node": ">=12.0.0" }, "dependencies": { "@google-cloud/compute": "^3.0.0", "googleapis": "^89.0.0", "axios": "^1.0.0-alpha.1" } }
-
-
「デプロイ」をクリック
Cloud Schedulerの作成
GCEインスタンス起動用/停止用のスケジューラー各1つを作成します。
-
「Cloud Scheduler ページ」 > 「ジョブを作成」
-
「スケジュールを定義する」を設定する
用途 設定項目 設定値 備考 起動用 名前 startup-gce リージョン asia-northeast1 (東京) 自動起動/自動停止をするGCEインスタンスと同じリージョン設定を推奨。 頻度 0 9 * * 1-5 月曜日~金曜日の9:00。
任意の設定でOKです。タイムゾーン 日本標準時(JST) タイムゾーンを設定します。
Cloud Functionsの設定と同じタイムゾーンになるようにしてください。停止用 名前 shutdown-gce リージョン asia-northeast1 (東京) 自動起動/自動停止をするGCEインスタンスと同じリージョン設定を推奨。 頻度 0 22 * * 1-5 月曜日~金曜日の22:00。
任意の設定でOKです。タイムゾーン 日本標準時(JST) タイムゾーンを設定します。
Cloud Functionsの設定と同じタイムゾーンになるようにしてください。 -
「実行内容を構成する」を設定する
用途 設定項目 設定値 備考 起動用 ターゲット タイプ Pub/Sub Cloud Pub/Sub トピック gce-instance-event メッセージ本文 {"zone":"asia-northeast1-a","instance":"instance-name1,instance-name2","processing":"start"} zone:対象のGCEインスタンスのゾーン
instance:対象のGCEインスタンス名(複数の場合はカンマ(,)区切りで指定)停止用 ターゲット タイプ Pub/Sub Cloud Pub/Sub トピック gce-instance-event メッセージ本文 {"zone":"asia-northeast1-a","instance":"instance-name1,instance-name2","processing":"stop"} zone:対象のGCEインスタンスのゾーン
instance:対象のGCEインスタンス名(複数の場合はカンマ(,)区切りで指定) -
「オプションの設定を行う」を設定する
こちらの内容は必要に応じて設定してください。
-
「作成」をクリック
GCEインスタンスの自動停止/自動起動の結果は?
GCEインスタンスの自動停止/自動起動の設定をした結果は・・・。
2023年4月16日~2023年5月20日
日 | 月 | 火 | 水 | 木 | 金 | 土 |
---|---|---|---|---|---|---|
4/16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 昭和の日 |
30 | 5/1 | 2 | 3 憲法記念日 | 4 みどりの日 | 5 こどもの日 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
CPU使用率やメモリ使用率のグラフが平日の9:00~22:00以外は消えているため、期待通りの挙動(平日の9:00~22:00の間だけ起動)が実現できていることがわかります。
GCEインスタンス1つ、1ヵ月分だけでみるとサーバー費の削減量は少なく見えますが、「塵も積もれば山となる」という諺もあるように対象のGCEインスタンスが複数ある場合や長期間続けた場合の総額で見たら無視できない金額になるかもしれません。
少しでもコストを抑えたい方にとって、今回紹介した方法が少しでも役に立てば良いなと思っています。