Bu örnekte, bir Golang uygulamasını nasıl temiz bir şekilde kapatabileceğimizi göstereceğiz. Bu örnek şu ana kadar birçok kişi tarafından iyi bilinmektedir. Ancak iyi bilinmeyen şey, bu çözümün yerel ortamda olduğu gibi Kubernetes ortamında da çalışıp çalışmadığıdır. Hepsi, Kubernetes pod/deployment yapılandırmasındaki terminationGracePeriodSeconds seçeneğine bağlıdır. Kısa kesecek olursak, context timeout değerinin terminationGracePeriodSeconds seçeneğinin değerinden daha az olduğundan emin olun. Varsayılan olarak, yapılandırmazsanız 30 saniyeye ayarlıdır, böylece context timeout değeriniz n < 30 olmalıdır.


Uygulamamız, uzun süreli isteklerin/bağlantıların tamamlanmasına 40 saniye izin veriyor. Daha sonra terminationGracePeriodSeconds60 olarak ayarladık. Bu, uygulamamızın Kubernetes tarafından vaktinden önce öldürülmesini önleyecektir. Normalde bu değeri yapılandırmamak ve bağlam zaman aşımını 10 saniye gibi bir şeye ayarlamak sorun oluşturmaz. Aşağıdaki günlükleri göreceksiniz.


Yapı


├── Makefile
├── deploy
│   └── k8s
│   ├── deployment.yaml
│   └── service.yaml
├── docker
│   └── dev
│   └── Dockerfile
└── main.go

Dosyalar


Makefile


.PHONY: run
run:
go run -race main.go

.PHONY: docker-push
docker-push:
docker build -t you/api:latest -f ./docker/dev/Dockerfile .
docker push you/api:latest
docker rmi you/api:latest
docker system prune --volumes --force

.PHONY: k8s-deploy
k8s-deploy:
kubectl apply -f deploy/k8s/deployment.yaml
kubectl apply -f deploy/k8s/service.yaml

Dockerfile


FROM golang:1.15-alpine3.12 as build

WORKDIR /source
COPY . .

RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o bin/main main.go

FROM alpine:3.12 as run

COPY --from=build /source/bin/main /main

ENTRYPOINT ["./main"]

main.go


package main

import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)

func main() {
// Create HTTP router.
rtr := http.NewServeMux()
rtr.HandleFunc("/", home)

// Create HTTP sever.
srv := &http.Server{
Addr: ":8080",
Handler: rtr,
}

// Start HTTP server.
go func() {
if err := srv.ListenAndServe(); err != nil {
log.Println("http server: listen and serve:", err)
}
}()
log.Println("app: started")

// Listen on application shutdown signals.
listener := make(chan os.Signal, 1)
signal.Notify(listener, os.Interrupt, syscall.SIGTERM)
log.Println("app: received a shutdown signal:", <-listener)

// Allow live connections a set period of time to complete.
ctx, cancel := context.WithTimeout(context.Background(), time.Second*40)
defer cancel()

// Shutdown HTTP server.
if err := srv.Shutdown(ctx); err != nil && err != http.ErrServerClosed {
log.Println("app: dirty shutdown:", err)
return
}

log.Println("app: clean shutdown")
}

// A dummy endpoint that simulates a long running request/connection/process.
func home(w http.ResponseWriter, _ *http.Request) {
log.Println("sleeping!")
time.Sleep(time.Second * 50)
log.Println("woke up!")

_, _ = w.Write([]byte("welcome home"))
}

service.yaml


apiVersion: v1
kind: Service

metadata:
name: api-service

spec:
type: NodePort
selector:
app: api
ports:
- protocol: TCP
port: 80
targetPort: 8080

deployment.yaml


apiVersion: apps/v1
kind: Deployment

metadata:
name: api-deployment
labels:
app: api

spec:
replicas: 1
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: golang
image: you/api:latest
ports:
- containerPort: 8080
terminationGracePeriodSeconds: 60

Test


Canlı bağlantı/istek olmadığında veya olduğunda ancak erken tamamlandığında, pod günlükleri dağıtım yeniden başlatıldıktan sonra aşağıdaki gibi görünecektir. Uygulama güzelce kapatıldı ve tüm talepler yerine getirildi.


$ kubectl logs -f pod/api-deployment-66cb684477-7tjrf
2021/02/14 20:33:59 app: started
2021/02/14 20:34:17 sleeping!
2021/02/14 20:34:34 app: received a shutdown signal: terminated
2021/02/14 20:34:34 http server: listen and serve: http: Server closed
2021/02/14 20:35:07 woke up!
2021/02/14 20:35:08 app: clean shutdown

Uzun süren bir bağlantı/istek olduğunda, dağıtım yeniden başlatıldıktan sonra pod günlükleri aşağıdaki gibi görünecektir. Uygulama kesintiye uğradı ve uzun süredir devam eden istek yerine getirilmedi.


$ kubectl logs -f pod/api-deployment-6f6d994c98-vv8jt
2021/02/14 20:26:13 app: started
2021/02/14 20:28:01 sleeping!
2021/02/14 20:28:10 app: received a shutdown signal: terminated
2021/02/14 20:28:10 http server: listen and serve: http: Server closed
2021/02/14 20:28:50 app: dirty shutdown: context deadline exceeded

Uzun süren bir bağlantı/istek olduğunda ve terminationGracePeriodSeconds değeri, context timeout süresi olan 40 saniyeden az olduğunda, pod günlükleri dağıtım yeniden başlatıldıktan sonra aşağıdaki gibi görünecektir. Pod öldürüldü, bu yüzden bir "shutdown" kaydı bile yok!


$ kubectl logs -f pod/api-deployment-b8d5dc94c-wqb6s
2021/02/14 20:14:55 app: started
2021/02/14 20:16:26 sleeping!
2021/02/14 20:16:32 app: received a shutdown signal: terminated
2021/02/14 20:16:32 http server: listen and serve: http: Server closed