夜間にAWS EKSのノードを0にしてコストを削減する
このエントリーは一休.com Advent Calendar 2024の4日目の記事になります。
EKSのコストを削減したい。この場合、ノードのコストをどう削減するか、ということになります。Spot Instanceを活用する方法があり得ますが、ノードが急停止したときの対策や、Spot Instanceを引き当てられなかったときの準備等、考えなければならないことが多いです。もちろんSavingsPlan購入をするという手もあるんですが、事業状況等を踏まえると、オンデマンドの利点を享受したいという場合もあるでしょう。
そこで、単純に夜間帯にEKSのノードを0台にする、という方法で、EC2インスタンスのランニングコストの削減を実現してみます。もちろん、本番環境でこのようなことはできません。テスト環境や開発者向けのクラスタなら、夜間帯に動いていなくても大きな問題にはならないでしょう。
前提は以下。
- EKSのKubernetesのバージョンは最新
- ノードグループはmanagedである。
- eksctl使い、cluster.yamlという名前のyamlを使ってクラスタを定義し、ノードグループを管理している。
- このyamlは、Githubのリポジトリで管理されている。
- cluster名は、
test-cluster
実現方法は以下。
- このリポジトリのGithub Actionsで、workflowをスケジュール実行することで、23:00に0台にする。そして、翌7:00に復帰させる。
- 具体的には以下。
- 23:00に、以下を実行する。
-
aws eks update-nodegroup-config
を実行し、ノードグループにNO_EXECUTE
のtaint を付与する。-
NO_EXECUTE
を付与せずに、0台にスケールインしても、cluster autoscalerが各種リソースをスケジュールするのに必要な必要な台数に自動的に戻してしまいます。
-
-
eksctl scale nodegroup
を実行し、nodes-minを0に、nodesを0にする。
-
- 7:00に、以下を実行する。
-
aws eks update-nodegroup-config
を実行し、23:00に付与したノードグループに付与したtaintのNO_EXECUTE
の を削除する。 -
eksctl scale nodegroup
を実行し、元の台数に戻す
-
- 23:00に、以下を実行する。
- ノードグループ名や復帰するときのの台数は、cluster.yamlから取得する
Github ActionsのWorkflowの定義
name: change eks cluster node group size
on:
workflow_dispatch:
inputs:
mode:
description: "mode"
required: true
type: string
workflow_call:
inputs:
mode:
required: true
type: string
jobs:
update-minsize:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install eksctl
run: |
ARCH=amd64
PLATFORM=$(uname -s)_$ARCH
curl -sLO "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_$PLATFORM.tar.gz"
tar -xzf eksctl_$PLATFORM.tar.gz -C /tmp && rm eksctl_$PLATFORM.tar.gz
sudo mv /tmp/eksctl /usr/local/bin
- name: create removing NO_SCHEDULE taints commands
if: ${{ (inputs.mode == 'revive') }}
uses: mikefarah/yq@master
with:
cmd: yq e '.managedNodeGroups[] | "aws eks update-nodegroup-config --cluster-name test-cluster --nodegroup-name \(.name) --taints \\"removeTaints={key=cost,value=true,effect=NO_EXECUTE}\\""' ./cluster.yaml > taints_exec.sh && chmod +x taints_exec.sh
- name: create node group revive commands
if: ${{ (inputs.mode == 'revive') }}
uses: mikefarah/yq@master
with:
cmd: yq e '.managedNodeGroups[] | "eksctl scale nodegroup --cluster=test-cluster --name=\(.name) --nodes-min=\(.minSize) --nodes=\(.minSize)"' ./cluster.yaml > exec.sh && chmod +x exec.sh
- name: create adding NO_SCHEDULE taints commands
if: ${{ (inputs.mode == 'zero') }}
uses: mikefarah/yq@master
with:
cmd: yq e '.managedNodeGroups[] | "aws eks update-nodegroup-config --cluster-name test-cluster --nodegroup-name \(.name) --taints \\"addOrUpdateTaints={key=cost,value=true,effect=NO_EXECUTE}\\""' ./cluster.yaml > taints_exec.sh && chmod +x taints_exec.sh
- name: create node group 0 scale in commands
if: ${{ (inputs.mode == 'zero') }}
uses: mikefarah/yq@master
with:
cmd: yq e '.managedNodeGroups[] | "eksctl scale nodegroup --cluster=test-cluster --name=\(.name) --nodes-min=0 --nodes=0"' ./cluster.yaml > exec.sh && chmod +x exec.sh
- name: modify nodegroups taints
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.AWS_REGION }}
run: ./taints_exec.sh
- run: sleep 90 # nodegroupがactiveになるまで待つ
- name: Update minSize for each node group
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.AWS_REGION }}
run: ./exec.sh
nodecontrol.yamlという名前でこのWorkflowを保存します。このWorkflowは再利用されることを意図して定義しています。利用側はmode
という入力を渡します。modeがzero
なら、0台にスケールインする。modeが revive
なら、元の台数に復帰する、という使い方を想定します。
Workflowでは、コマンドを生成し、それを実行しています。mode
が、zero
なら、0台にスケールインするためのコマンドを作成し、実行します。modeが revive
なら、元の台数に復帰するためのコマンドを生成し、実行します。
コマンドの生成というのは、yqを使って、cluster.yamlを解析し、定義されているノードグループから情報を抽出して、shファイルに保存することを示しています。
例えば、ノードグループにtaintを付与するには、以下のコマンドを実行する必要があります。
aws eks update-nodegroup-config --cluster-name test-cluster --nodegroup-name <ノードグループ名> --taints "addOrUpdateTaints={key=cost,value=true,effect=NO_EXECUTE}"
ここで、ノードグループ名を、cluster.yamlから取得することで、ノードグループの定義が変わっても、workflowを直す必要がなくなります。yqを使うと以下。
yq e '.managedNodeGroups[] | "aws eks update-nodegroup-config --cluster-name test-cluster --nodegroup-name \(.name) --taints \\"addOrUpdateTaints={key=cost,value=true,effect=NO_EXECUTE}\\""' ./cluster.yaml > taints_exec.sh && chmod +x taints_exec.sh
NO_EXECUTE
taintの削除や、eksctlを使ったスケールイン/アウトコマンドも同様です。
また、taintの操作と、スケールの操作の間に、90秒sleepしています。これは、taintの操作が完了し、ノードグループがactiveになるのを待っています。
少し簡略化していますが、cluster.yamlの定義は以下の通り。
managedNodeGroupsのname
とminSize
をyqで抽出して、復帰時の台数を指定しています。
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: test-cluster
region: ap-northeast-1
version: "1.31"
vpc:
id: "vpc-xxxxx"
cidr: "10.0.0.0/16"
subnets:
private:
ap-northeast-1a:
id: "subnet-aaaaaa"
cidr: "10.0.1.0/22"
ap-northeast-1c:
id: "subnet-bbbbbb"
cidr: "10.0.2.0/22"
ap-northeast-1d:
id: "subnet-ccccc"
cidr: "10.0.3.0/22"
managedNodeGroups:
- name: managed-ng-01
amiFamily: AmazonLinux2
instanceType: c5.xlarge
desiredCapacity: 7
minSize: 7
maxSize: 8
- name: managed-ng-02
amiFamily: AmazonLinux2
instanceType: m5.2xlarge
desiredCapacity: 2
minSize: 2
maxSize: 4
あとは、呼び出し元のWorkflowを定義します。以下は、ノードグループを0台にスケールインための定義です。
name: zero scale in
on:
workflow_dispatch:
schedule:
- cron: '0 14 * * *' #JST 23時に0台にする
jobs:
zeroscalein:
name: zero scale in
uses: ./.github/workflows/nodecontrol.yaml
with:
mode: 'zero'
secrets: inherit
以下は、ノードグループを元の台数に戻すための定義です。
name: revive
on:
workflow_dispatch:
schedule:
- cron: '0 22 * * *' # JST 07:00にnodeの台数を元に戻す
jobs:
zeroscalein:
name: revive
uses: ./.github/workflows/nodecontrol.yaml
with:
mode: 'revive'
secrets: inherit
これで実際に動かしてみると、夜間帯にtest-clusterのノードグループのEC2が0台になっていることが確かめられました。
一休では、ともに良いサービスをつくっていく仲間を募集中です。クラウドインフラの運用やSREに興味がある方はぜひ募集ください。
カジュアル面談もやっています。
Discussion