探討 Go 應(yīng)用程序在 Kubernetes 中的生命周期
本文深入探討了在 Kubernetes 中開發(fā) Go 應(yīng)用程序的最佳實(shí)踐,重點(diǎn)分析了 Pod 生命周期的不同階段以及 Kubernetes 終止信號的作用,以確保應(yīng)用程序的平穩(wěn)關(guān)閉,避免數(shù)據(jù)丟失或用戶體驗(yàn)中斷。通過對生命周期的詳細(xì)管理,可以有效地進(jìn)行更新和負(fù)載調(diào)整。
應(yīng)用程序生命周期的三個(gè)階段
- 應(yīng)用程序啟動
- 應(yīng)用程序運(yùn)行
- 應(yīng)用程序結(jié)束
在每個(gè)階段,都需要確保一切按計(jì)劃進(jìn)行。通常我們只關(guān)注應(yīng)用程序運(yùn)行階段,但在 Kubernetes 中管理所有三個(gè)階段同樣重要。
1. 應(yīng)用程序啟動
在 Kubernetes 中,Pod 被認(rèn)為是準(zhǔn)備就緒的,當(dāng)所有容器都準(zhǔn)備好接受請求時(shí)。這個(gè)過程通過 readiness 探針和 readinessGates 來實(shí)現(xiàn)。
readiness 探針的作用
readiness 探針用于指示服務(wù)是否準(zhǔn)備好接受請求。它通過定期檢查應(yīng)用程序的狀態(tài)來確保服務(wù)的可用性。以下是一個(gè) readiness 探針的定義示例:
readinessProbe:
httpGet:
scheme: HTTP
path: /ready
port: service
timeoutSeconds: 2
periodSeconds: 60
關(guān)鍵參數(shù):
? timeoutSeconds: 探針響應(yīng)的時(shí)間限制。
? periodSeconds: 探針調(diào)用的頻率。
2. 應(yīng)用程序運(yùn)行
在應(yīng)用程序運(yùn)行階段,Liveness 和 Readiness 探針用于監(jiān)控服務(wù)的健康狀態(tài)。Liveness 探針特別用于檢測應(yīng)用程序是否進(jìn)入死鎖狀態(tài),并決定是否需要重啟容器。
liveness 探針的定義示例
livenessProbe:
httpGet:
scheme: HTTP
path: /health
port: service
timeoutSeconds: 1
periodSeconds: 30
initialDelaySeconds: 60
重要注意事項(xiàng):
? 確保探針快速響應(yīng),避免因服務(wù)繁忙而導(dǎo)致不必要的重啟。
? 避免將探針與外部服務(wù)健康檢查結(jié)合使用,以免單一故障導(dǎo)致所有容器重啟。
3. 應(yīng)用程序結(jié)束
應(yīng)用程序結(jié)束階段涉及 Pod 的優(yōu)雅關(guān)閉。Kubernetes 通過發(fā)送 SIGTERM 和 SIGKILL 信號來管理 Pod 的終止過程。
Go 應(yīng)用程序的優(yōu)雅關(guān)閉示例
func main() {
// Step 1: start
startService := time.Now()
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
zerolog.DurationFieldUnit = time.Second
logger := zerolog.New(os.Stdout).With().Str("service", ServiceName).Timestamp().Logger()
config, err := NewConfig()
if err != nil {
logger.Fatal().Err(err).Msg("Unable to setup config")
}
mux := http.NewServeMux()
mux.HandleFunc("/health", healthHandler(logger))
mux.HandleFunc("/ready", readyHandler(logger))
mux.HandleFunc("/task", taskHandler(logger))
server := &http.Server{
Addr: ":" + config.ServicePort,
Handler: recovery(
http.TimeoutHandler(mux, config.HandlerTimeout, fmt.Sprintf("server timed out, request exceeded %s\n", config.HandlerTimeout)),
logger,
),
ErrorLog: log.New(logger, "", log.LstdFlags),
ReadTimeout: config.ReadTimeout,
WriteTimeout: config.WriteTimeout,
}
waitGroup := &sync.WaitGroup{}
// Run HTTP server
waitGroup.Add(1)
go func() {
defer waitGroup.Done()
errListen := server.ListenAndServe()
if err != nil && !errors.Is(errListen, http.ErrServerClosed) {
logger.Fatal().Err(errListen).Msg("server.ListenAndServe error")
}
}()
go func() {
<-ctx.Done()
logger.Info().Msg("HTTP server cancelled")
timeoutCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
errShutdown := server.Shutdown(timeoutCtx)
if errShutdown != nil {
logger.Error().Err(errShutdown).Msg("server.Shutdown error")
}
}()
waitGroup.Add(1)
go performTask(ctx, waitGroup, logger)
logger.Info().Dur("duration", time.Since(startService)).Msg("Service started successfully")
runningService := time.Now()
// Step 2: running
<-ctx.Done()
stop()
// Step 3: shutdown
logger.Info().Dur("duration", time.Since(runningService)).Msg("Gracefully shutting down service...")
startGracefullyShuttingDown := time.Now()
waitGroup.Wait()
logger.Info().Dur("duration", time.Since(startGracefullyShuttingDown)).Msg("Shutdown service complete")
}
完整示例可以在 GitHub 上找到。
結(jié)論
本文強(qiáng)調(diào)了在設(shè)計(jì)階段考慮失敗設(shè)計(jì)的重要性。計(jì)算機(jī)應(yīng)用程序必須能夠承受任何底層軟件或硬件的故障。通過優(yōu)雅降級和最終一致性等概念,可以提高應(yīng)用程序的可靠性和可用性。通過合理使用探針和優(yōu)雅關(guān)閉機(jī)制,開發(fā)者可以確保 Go 應(yīng)用程序在 Kubernetes 中的穩(wěn)定運(yùn)行和高效管理。