将[GKE] Deployment的replicas设置为0,以便向Go应用程序发送操作系统信号SIGTERM通知

请在中文中进行释义,并仅提供一种选项:

题目

只是想确认一下实际情况会如此。

前提这个词的含义是指某件事情发生的前提条件,只提供一个选项如下:

条件

    • GCP環境は用意済み。

 

    • GCPローカル設定済み。(gcloudコマンドが使用できる状態になっている。)

kubectlコマンドが使用できる状態になっている。
GKEクラスタ作成済み。

开发环境

操作系统- Linux(Ubuntu)

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

谷歌云 (GCP)

$ gcloud version
Google Cloud SDK 312.0.0

# kubectl –> 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

实践

源码完整套装

请将以下内容用中文进行翻译,只需要一个选项:源。

Go语言

随便搭建一个Web服务器,在接收到操作系统信号(SIGTERM)时输出日志(GOT_NOTIFY)。
也可以在defer中设置日志,并在接收到操作系统信号时确认defer日志不会被输出。

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镜像。

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'

以下是用于构建上述内容的shell脚本。

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

gcloud builds submit --config cloudbuild.yaml .

部署设置

从容器注册表中获取Docker镜像。
有三个Pod。
容器端口为8080(虽然这次用不到)。

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

使用以下Shell进行部署:
根据上述,需要使用我正在使用的GCP项目的ID,该ID可以从本地环境的gcloud命令中获取,但是查询如何指定GCP项目ID而不直接在k8s的Yaml文件中写入(可能可以通过ConfigMap或Secret来实现,但最好是简便方式)很麻烦,所以使用sed来替换。

#!/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 数

#!/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镜像并存储到容器注册表)

$ ./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。

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

当查看容器日志时,可以发现每个Pod上都有应用程序启动和操作系统信号等待启动的日志。

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,而应该另外创建一个用于接收操作系统信号(SIGTERM)的Goroutine来进行处理。

广告
将在 10 秒后关闭
bannerAds