こんにちは。GMOアドマーケティングのmizkichです。
この記事では、「GKE(Google Kubernetes Engine)上のClusterやPodをCronJobで定期的に削除する方法」について紹介します。
開発環境など24h/365dでの運用が必要ない環境では、夜間や週末にサーバ台数を減らすことで、費用を大きく削減出来ます。
私が担当しているシステムでは、少し乱暴なスクリプトでこれを実現しているため、その方法を紹介させて頂きます。
目的
弊社の開発環境では、開発者が自由に新規のクラスタを作る事が出来ます。
クラスタを1つ作る毎に平均5台以上のGCEインスタンスが起動し、1日約800円xクラスタの数の費用が発生します。
だれも居ない週末にクラスタの数だけ費用が発生してしまうため、強制的にこれを削除する仕組みを作りました。
仕様
弊社開発環境のクラスタには、開発者が自由に作る開発用クラスタとお客様にも公開している検証用クラスタの2種類あります。
開発者が自由に作る開発用のクラスタは、毎日22時に強制的に削除します。
お客様も利用する検証用のクラスタは、金曜の22時に強制的に削除します。
弊社の開発者は11時出社が多いので、サーバの起動時間を半分以下に削減できる想定です。
設計・検証
Kubernetesの仕組みであるCronJobsを起動し、GKEの内部から自分自身を含むクラスタを削除する方式にしました。
検証環境はゆるめの権限設定にしているので、GKE内のPodは親であるクラスタを操作する権限を持っていました。
この案の前に、GKEの外部からCloud Functionsでクラスタを削除する方式も試したのですが、この方式はどうしてもGKEへのアクセス権設定が上手く行かず、、
クラスタを削除出来ず、調査の時間も取れなかったので廃案にしています。
実装(検証環境用)
GKEからクラスタを削除する案だと、禁断のコマンド “gcloud container clusters get-credentials ENV_CLUSTER_NAME“が通ります。
属するクラスタの全権限が取得できてしまうため、”gcloud container clusters delete ENV_CLUSTER_NAME”でクラスタを削除出来てしまいます。
※ GCEの権限設定に依存します。 権限の緩い開発環境だから利用できる方法です。
そのため、必要なのはgcloud sdkが入ったDockerfileとCronJob用のYamlだけでした。
gcloud sdk入りのDockerfileは以下の通りです。
1 2 3 4 5 6 7 8 |
FROM google/cloud-sdk:225.0.0-alpine RUN apk update \ && apk add bash \ && apk add tzdata \ && cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \ && apk del tzdata \ && rm -rf /var/cache/apk/* |
イメージサイズが最小になるようにAlpineを利用しています。
タイムゾーンを日本にするために、tzdataを一時的に導入し削除しています。
CronJobの設定は以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
apiVersion: batch/v1beta1 kind: CronJob metadata: name: cronjob-terminate-cluster-weekend spec: # 22:00(JST) Friday schedule: "0 13 * * fri" concurrencyPolicy: Replace suspend: false jobTemplate: spec: backoffLimit: 1 template: spec: containers: - name: cronjob-terminate-cluster image: asia.gcr.io/project_name/image_name:001 imagePullPolicy: Always command: - /bin/sh - -c - gcloud container clusters get-credentials ENV_CLUSTER_NAME --zone=ENV_ZONE_NAME && gcloud container clusters delete ENV_CLUSTER_NAME --zone=ENV_ZONE_NAME --async --quiet restartPolicy: Never |
scheduleに設定する時刻はUTCです。日本時間から9時間を引いた時刻を設定します。
あとは、commandにはgcloudのclusters get-credentialsとclusters delete指定するだけでした。
Yamlにはクラスタ名やゾーン名は直接記述せず、”kubectl apply -f “を実行するShell Script内で文字列置換する方式としました。
1 2 3 4 5 6 7 8 9 |
#!/bin/bash SCRIPT_DIR=$(cd $(dirname $0); pwd) source $SCRIPT_DIR/environments.sh YAML_PATH=${SCRIPT_DIR}/cronjob CURRENT_ZONE_NAME=`get_zone_name` CURRENT_CLUSTER_NAME=`get_cluster_name` cat $YAML_PATH/cronjob-terminate-cluster-at-weekend.yaml | sed s/ENV_ZONE_NAME/$CURRENT_ZONE_NAME/ | sed s/ENV_CLUSTER_NAME/$CURRENT_CLUSTER_NAME/ | kubectl apply -f - |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/bin/bash #environment.sh get_zone_name() { IFS='_' set -- `kubectl config current-context` echo $3 } get_cluster_name() { IFS='_' set -- `kubectl config current-context` echo $4 } |
実装(開発環境用)
クラスタを毎日削除するので、scheduleだけ検証環境と異なる設定になります。
1 2 3 4 5 6 7 8 |
apiVersion: batch/v1beta1 kind: CronJob metadata: name: minerva-cronjob-terminate-cluster-at-everyday spec: # 22:00(JST) everyday schedule: "0 13 * * *" <以下略> |
まとめ
Kubernetesを使った開発や検証では、開発者が扱うシステムの規模が大きく、費用も大きくなる傾向があります。
CronJobを用いたクラスタ削除の方式なら、開発者各自がCronJobが実行される時刻を変更できるため、その面ではCloud Functionsを使うより良いと感じました。
今回の方式ではGCEのデフォルトサービスアカウント設定を利用しています。
デフォルト権限設定を含め、実際の運用ではセキュリティホールを意識した実装をしていただければ幸いです。
Full Stack Engineer @ GMO AD Marketing, Inc.
DMP(Data Management Platform)の開発運用担当してます。
(Java x Ruby x Kubernetes x BigQuery x Hazelcast x etc)
マイブームは犬と車と登山とスノボ。