1
0

More than 3 years have passed since last update.

[GKE] Deploymentのreplicasを0にしてGoアプリにOSシグナル SIGTERM を通知

Posted at

お題

表題の通り。
実際の動きとしてそうなることを確認したかっただけ。

前提

  • GCP環境は用意済み。
  • GCPローカル設定済み。(gcloudコマンドが使用できる状態になっている。)
  • kubectlコマンドが使用できる状態になっている。
  • GKEクラスタ作成済み。

開発環境

# OS - Linux(Ubuntu)

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.5 LTS (Bionic Beaver)"

# gcloud

$ gcloud version
Google Cloud SDK 312.0.0

# kubectl

$ kubectl version
Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.9", GitCommit:"4fb7ed12476d57b8437ada90b4f93b17ffaeed99", GitTreeState:"clean", BuildDate:"2020-07-15T16:18:16Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"17+", GitVersion:"v1.17.12-gke.2502", GitCommit:"974eff7a63e05b7eb05c9aded92fae8a3ce14521", GitTreeState:"clean", BuildDate:"2020-10-19T17:01:32Z", GoVersion:"go1.13.15b4", Compiler:"gc", Platform:"linux/amd64"}

# バックエンド

# 言語 - Golang

$ go version
go version go1.15.2 linux/amd64

実践

ソース一式

ソース

Golang

適当にWebサーバを立てておいて、OSシグナル(SIGTERM)を受信したらログ(GOT_NOTIFY)を吐く。
deferでもログを仕込んでおいて、OSシグナル受信時に、deferのログは出ないことも確認する。

main.go
package main

import (
    "fmt"
    "net/http"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    fmt.Println("APP_START")
    defer fmt.Println("DEFER")

    // OSシグナル(SIGTERM)の受信を待ち受ける Goroutine
    go func() {
        fmt.Println("BEFORE_NOTIFY")
        q := make(chan os.Signal, 1)
        signal.Notify(q, syscall.SIGTERM)
        <-q
        fmt.Println("GOT_NOTIFY")

        os.Exit(-1)
    }()

    // 適当にHTTPサーバーを立ち上げておく
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        if _, err := fmt.Fprint(w, "Hello"); err != nil {
            fmt.Printf("HANDLE_ERROR_OCCURRED: %+v", err)
        }
    })
    if err := http.ListenAndServe(":8080", nil); err != nil {
        fmt.Printf("SERVE_ERROR_OCCURRED: %+v", err)
    }

    fmt.Println("APP_END")
}

Dockerfile

何の変哲もないマルチステージビルドなDockerfile。

FROM golang:1.15 as builder
WORKDIR /app
COPY . .
RUN go mod download
RUN CGO_ENABLED=0 GOOS=linux go build -mod=readonly -v -o server

FROM gcr.io/distroless/base
COPY --from=builder /app/server /server
CMD ["/server"]

Cloud Build設定

DockerイメージはContainer Registryを使う。

cloudbuild.yaml
steps:
  - name: 'gcr.io/cloud-builders/docker'
    args: [ 'build', '-t', 'gcr.io/$PROJECT_ID/golang-app-try01', '.' ]
images:
  - 'gcr.io/$PROJECT_ID/golang-app-try01'

上記を使ってビルドする用のシェルは下記。

build.sh
#!/usr/bin/env bash
set -euox pipefail
SCRIPT_DIR=$(dirname "$0")
cd "${SCRIPT_DIR}"

gcloud builds submit --config cloudbuild.yaml .

デプロイ設定

Container RegistryからDockerイメージを取得する。
Podは3つ。
コンテナポートは8080(別に今回は使わないけど)。

deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: golang-app-try01
spec:
  replicas: 3
  selector:
    matchLabels:
      app: golang-app-try01
  template:
    metadata:
      labels:
        app: golang-app-try01
    spec:
      containers:
        - name: golang-app-try01
          image: gcr.io/MY_GCP_PROJECT_ID/golang-app-try01
          ports:
            - containerPort: 8080

上記を使ってデプロイするシェルは下記。
自分が使っているGCPプロジェクトのIDが必要で、それ自体はローカル環境でgcloudコマンドから拾えるのだけど、
k8sのYamlに直接書かずにGCPプロジェクトIDを指定する方法(※ConfigMapやSecret経由ならできるのかもだけど、出来れば手軽に)を調べるのが面倒だったので、sed で書き換え。

deploy.sh
#!/usr/bin/env bash
set -euox pipefail
SCRIPT_DIR=$(dirname "$0")
cd "${SCRIPT_DIR}"

project=$(gcloud config get-value project)
if [[ -z "${project}" ]]; then
  echo -n "need project"
  exit 1
fi
echo "${project}"

sed -i -e "s/MY_GCP_PROJECT_ID/${project}/" deployment.yaml

kubectl apply -f deployment.yaml

sed -i -e "s/${project}/MY_GCP_PROJECT_ID/" deployment.yaml

Pod数を書き換えるためのシェル

replica_n.sh
#!/usr/bin/env bash
set -euox pipefail
SCRIPT_DIR=$(dirname "$0")
cd "${SCRIPT_DIR}"

num=${1:-}

if [ -z "${num}" ]; then
  echo -n "input replicas number: "
  read num
fi

kubectl scale deployment golang-app-try01 --replicas="${num}"

動作確認

アプリのビルド(Dockerイメージを作成してContainer Registryに格納)

$ ./build.sh 
++ dirname ./build.sh
+ SCRIPT_DIR=.
+ echo .
.
+ cd .
+ gcloud builds submit --config cloudbuild.yaml .
Creating temporary tarball archive of 6 file(s) totalling 1.7 KiB before compression.
 ・
 ・
 ・
DONE
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

ID                                    CREATE_TIME                DURATION  SOURCE                                                                                   IMAGES                                        STATUS
6452c516-cfbf-4497-b536-378023cbc34d  2020-11-03T19:29:14+00:00  29S       gs://XXXXXXXX_cloudbuild/source/1604431752.38075-ccb069fbb0d0413382dc79d42e5c618a.tgz  gcr.io/XXXXXXXX/golang-app-try01 (+1 more)  SUCCESS

screenshot-console.cloud.google.com-2020.11.04-05_33_09.png

GKEにデプロイ

$ ./deploy.sh 
++ dirname ./deploy.sh
+ SCRIPT_DIR=.
+ echo .
.
+ cd .
 ・
 ・
 ・
+ kubectl apply -f deployment.yaml
deployment.apps/golang-app-try01 created
 ・
 ・
 ・

Podが3つ。

$ kubectl get deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
golang-app-try01   3/3     3            3           4m19s

この時点でコンテナログを見ると、3つのPodそれぞれで、アプリ起動時とOSシグナル待受開始のログが出ていることがわかる。

screenshot-console.cloud.google.com-2020.11.04-04_52_58.png

Pod数を0に変更

$ ./replica_n.sh 0
++ dirname ./replica_n.sh
+ SCRIPT_DIR=.
+ echo .
.
+ cd .
+ num=0
+ '[' -z 0 ']'
+ kubectl scale deployment golang-app-try01 --replicas=0
deployment.apps/golang-app-try01 scaled

OSシグナル受信時のログ(GOT_NOTIFY)がそれぞれのPodのログとして出た。
deferで仕込んでいた方のログ(DEFER)は出ない。

screenshot-console.cloud.google.com-2020.11.04-04_53_28.png

まとめ

GKEに載せるなら、アプリ停止時に確実に処理させたい内容は defer でなく、OSシグナル(SIGTERM)受信用の Goroutine を別途立てて対応。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0