この記事はOpenSaaS Studio Advent Calendar 2019の8日目の記事。
オートスケーリングの現実
KubernetesにはHorizonal Pod Autoscaler(HPA)という便利な仕組みがあって、CPUやメモリ使用率やカスタムメトリクスによってPodの数を増減できる。とはいえPodだけ増えても増えた分のPodを配置できるだけのコンピューティングリソースが無いと意味がない。GKEであればPodを配置できるだけのリソースがなければ、Nodepoolのサイズを拡大してくれる(Quotaの確認も忘れるなよ!)。
しかし、弊社のプロダクトはメディアやゲーム、広告だったり色々あるわけで、そのほとんどがB・C問わず大規模なユーザーを抱えている。オートスケーリングは欠かせない仕組みだが、コンピューティングリソースが上昇してから発火するものなので、基本的に瞬間的なスパイクには耐えられない。スパイクの3分後とかに潤沢なリソースが整っても時既に遅し。
オートスケールのスケジュール
幸いにも、メディアだったらプッシュ通知やCM起因だったり、AbemaTVのこの時間の番組がやばい!とか、ゲームはイベントの開始等で需要を予測できる。全てを見越して最初から超潤沢な構成にするという手法ももちろんあるが、皆が寝静まってる時間帯にそこまでのリソースを使うのはコスト的に忍びない。
というわけで、当方が「手っ取り早く」やってるオートスケールのスケジュール方法を軽く紹介する。雑にyamlだけ貼るが、雰囲気はつかめると思う。
まずはHPAを操作するためのServiceAccountやClusterRoleを作る(NamespacedなRoleでも良い)。
apiVersion: v1
kind: ServiceAccount
metadata:
name: hpa-scheduler
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: hpa-scheduler
rules:
- apiGroups:
- autoscaling
resources:
- horizontalpodautoscalers
verbs:
- get
- list
- patch
- watch
- apiGroups:
- apps
resources:
- deployments
- deployments/scale
verbs:
- get
- update
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: hpa-scheduler
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: hpa-scheduler
subjects:
- apiGroup: ""
kind: ServiceAccount
name: hpa-scheduler
namespace: kube-system
次にCronJobを作り、適当な時間に発火させるようにする。
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: hpa-scaleout
labels:
app.kubernetes.io/name: hpa-scaleout
spec:
schedule: "0 9 * * *" # 18:00+0900
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 5
failedJobsHistoryLimit: 5
jobTemplate:
spec:
template:
metadata:
labels:
app.kubernetes.io/name: hpa-scaleout
spec:
serviceAccountName: hpa-scheduler
containers:
- name: hpa-scheduler
image: lachlanevenson/k8s-kubectl:v1.14.7
command:
- "/bin/sh"
- "-c"
- |
kubectl -n istio-system patch hpa/istio-ingressgateway -p '{"spec":{"minReplicas":50}}'
restartPolicy: Never
作成したServiceAccountを食わせ、kubectl
を実行できるコンテナを用意。その中で、istio-ingressgatewayのminReplicas
を変更してスケールアウトする(必要に応じてmaxReplicasを上げてもよい)。スケールインも同じようなCronJobを用意すれば良い。
HPAでdesiredなPodが増え、Nodeが足りない場合はPodがPendingになるので、それをフックにNodePoolのサイズがあがる。規模が大きくなるとスケール完了も遅くなるので、NodeもHPA発動前にある程度の量を確保できていた方がよい。
というわけで、GKEのNodePoolも次のようにCronJobでどうにかできる。HPAの5分前とかで十分。
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: nodepool-scaleup
labels:
app.kubernetes.io/name: nodepool-scaleup
spec:
schedule: "55 8 * * *" # 17:55+0900
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 5
failedJobsHistoryLimit: 5
jobTemplate:
spec:
template:
metadata:
labels:
app.kubernetes.io/name: nodepool-scaleup
spec:
containers:
- name: nodepool-scaleup
image: google/cloud-sdk:272.0.0-alpine
command:
- "/bin/sh"
- "-c"
- |
gcloud auth activate-service-account --key-file=/secrets/gcp/credentials.json
gcloud config set container/cluster $(CLUSTER_NAME)
gcloud container node-pools update $(TARGET_NODE_POOL) --region $(TARGET_REGION) --min-nodes 50
yes | gcloud container clusters resize $(CLUSTER_NAME) --node-pool $(TARGET_NODE_POOL) --region $(TARGET_REGION) --num-nodes 50 --async
env:
- name: CLUSTER_NAME
value: your-cluster-name
- name: TARGET_NODE_POOL
value: your-nodepool-name
- name: TARGET_REGION
value: asia-northeast1
volumeMounts:
- name: gcp-credentials
mountPath: /secrets/gcp
readOnly: true
restartPolicy: Never
volumes:
- name: gcp-credentials
secret:
secretName: nodepool-scaleup-gcp-credentials
クラスタの中で、クラスタよりスコープが外にあるクラウドの設定をいじることの気持ち悪さが残るが、これでシュッと時間帯でのスケールができる。プレウォーミングみたいなものだ。
CRD作成中
この手の手法使ったの個人的には2プロダクト目だし、もうCRD化した方が良いので現在片手間でCRDを作っている(OSS化するのでもうちと待ってな)。時間帯もそうだが、もう少しインテリジェントなスケールの機構がどうしても必要。