🍣

PodをCronJobで停止・起動する

2023/07/26に公開

テスト環境などでFargateなどを利用している場合、Podが夜間や休日に起動しているとお金がかかるため停止したいということがあるかと思います。
そういった際に利用できるCronJobで停止・起動をスケジュール化する方法についてです。

Shellなどでも実現できると思いますが、複数のDeploymentのReplicasを変更したいため、client-goを利用してコードを書いていきます。

ツール(イメージの作成)

概要

利用するライブラリは以下になります。

  • client-go(v0.27.3)
  • gopkg.in/yaml.v3(v3.0.1)

実現方法は、config.yamlに以下のような設定を書けば、deploymentを指定したreplicasに変更するようにします。

config.yaml
deployments:
  - namespace: auth
    name: auth-deployment
    replicas: 0

コード

main.go
package main

import (
	"log"
)

func main() {
	log.Println("start")

	config := getConfig()
	for _, v := range config.Deployments {
		scale(&v)
	}

	log.Println("end")
}
config.go
package main

import (
	"log"
	"os"

	"gopkg.in/yaml.v3"
)

type Deployment struct {
	Namespace string
	Name      string
	Replicas  int
}

type Config struct {
	Deployments []Deployment
}

func getConfig() Config {
	config := Config{}
	b, _ := os.ReadFile("config.yaml")

	err := yaml.Unmarshal(b, &config)
	if err != nil {
		log.Panic(err)
	}
	return config
}
cluster-operator.go
package main

import (
	"context"
	"log"

	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

func scale(deploymentConfig *Deployment) {
	clusterConfig, err := rest.InClusterConfig()
	if err != nil {
		log.Panic(err.Error())
	}

	clientset, err := kubernetes.NewForConfig(clusterConfig)
	if err != nil {
		log.Panic(err.Error())
	}
	deploymentsClient := clientset.AppsV1().Deployments(deploymentConfig.Namespace)
	s, err := deploymentsClient.GetScale(context.TODO(), deploymentConfig.Name, v1.GetOptions{})
	if err != nil {
		log.Panic(err)
	}
	sc := *s
	sc.Spec.Replicas = int32(deploymentConfig.Replicas)

	_, err = deploymentsClient.UpdateScale(context.TODO(), deploymentConfig.Name, &sc, v1.UpdateOptions{})
	if err != nil {
		log.Panic(err)
	}
	log.Printf("namespace:%s, deployment:%s scaled to %d", deploymentConfig.Namespace, deploymentConfig.Name, deploymentConfig.Replicas)
}
# Build
FROM golang:1.20-buster As Build

WORKDIR /app

COPY go.mod ./
COPY go.sum ./
RUN go mod download

COPY *.go ./

RUN go build -o /deployment-scaler

# Deploy
FROM gcr.io/distroless/base-debian11:latest

WORKDIR /

COPY --from=build /deployment-scaler /deployment-scaler

USER nonroot:nonroot

ENTRYPOINT ["/deployment-scaler"]
イメージの作成
$ docker build -t deployment-scaler:1.0.0 ./

マニフェストの作成

実際にJobを作成し確認する

まずはJobで簡単に確認します。

job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: deployment-scaler
spec:
  template:
    spec:
      restartPolicy: Never
      serviceAccountName: deployment-scaler-service-account
      containers:
      - name: deployment-scaler
        image: deployment-scaler:1.0.0
        volumeMounts:
        - mountPath: /config.yaml
          subPath: config.yaml
          name: config-volume
          readOnly: true
      volumes:
      - name: config-volume
        configMap:
          name: deployment-scaler-config
  backoffLimit: 4
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: deployment-scaler-config
  
data:
  config.yaml: |
    deployments:
    - namespace: default
      name: web-nginx
      replicas: 0
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: deployment-scaler-service-account
rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: deployment-scaler-role
rules:
  - apiGroups:
      - "apps"
    resources:
      - deployments/scale
    verbs:
      - get
      - list
      - patch
      - update

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: deployment-scaler-role-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: deployment-scaler-role
subjects:
  - kind: ServiceAccount
    name: deployment-scaler-service-account
    namespace: default

実行

$ k get pods
NAMESPACE            NAME                                                 READY   STATUS      RESTARTS       AGE
default              web-nginx-669489d776-wwnqn                           1/1     Running     2 (42m ago)    4d19h
$ k apply -f rbac.yaml
clusterrolebinding.rbac.authorization.k8s.io/deployment-scaler-role-binding created
$ k apply -f job.yaml
job.batch/deployment-scaler created
configmap/deployment-scaler-config created
serviceaccount/deployment-scaler-service-account created
$ k get pods
NAMESPACE            NAME                                                 READY   STATUS              RESTARTS       AGE
default              deployment-scaler-9zr8w                              0/1     ContainerCreating   0              2s
default              web-nginx-669489d776-wwnqn                           0/1     Terminating         2 (43m ago)    4d19h

$ k logs deployment-scaler-9zr8w
2023/07/25 20:47:48 start
2023/07/25 20:47:48 namespace:default, deployment:web-nginx scaled to 0
2023/07/25 20:47:48 end

CronJobを作成する

今度は実際にCronJobで確認します。

configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: deployment-scaler-start-config
  
data:
  config.yaml: |
    deployments:
    - namespace: default
      name: web-nginx
      replicas: 1
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: deployment-scaler-stop-config
  
data:
  config.yaml: |
    deployments:
    - namespace: default
      name: web-nginx
      replicas: 0
serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: deployment-scaler-service-account
cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: deployment-scaler-stop
spec:
  schedule: "55 20 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: Never
          serviceAccountName: deployment-scaler-service-account
          containers:
          - name: deployment-scaler
            image: deployment-scaler:1.0.0
            volumeMounts:
            - mountPath: /config.yaml
              subPath: config.yaml
              name: config-volume
              readOnly: true
          volumes:
          - name: config-volume
            configMap:
              name: deployment-scaler-stop-config
      backoffLimit: 4
---
apiVersion: batch/v1
kind: CronJob
metadata:
  name: deployment-scaler-start
spec:
  schedule: "56 20 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: Never
          serviceAccountName: deployment-scaler-service-account
          containers:
          - name: deployment-scaler
            image: deployment-scaler:1.0.0
            volumeMounts:
            - mountPath: /config.yaml
              subPath: config.yaml
              name: config-volume
              readOnly: true
          volumes:
          - name: config-volume
            configMap:
              name: deployment-scaler-start-config
      backoffLimit: 4

実行結果

5時54分
$ k get pods 
NAME                         READY   STATUS    RESTARTS   AGE
web-nginx-669489d776-dkjvn   1/1     Running   0          74s
5時55分
$ k get pods 
NAME                                    READY   STATUS      RESTARTS   AGE
deployment-scaler-stop-28171975-phjs4   0/1     Completed   0          1s
5時56分
$ k get pods 
NAME                                     READY   STATUS      RESTARTS   AGE
deployment-scaler-start-28171976-6swjz   0/1     Completed   0          16s
deployment-scaler-stop-28171975-phjs4    0/1     Completed   0          76s
web-nginx-669489d776-j7rh7               1/1     Running     0          16s

今回のコード

https://github.com/uc4w6c/deployment-scaler

Discussion