当我们的服务从启动,到可以对外提供服务,需要经历哪些过程?
如果在服务启动的过程中,外部的服务访问了该服务,会出现什么情况?
当服务在滚动升级中,服务该如何平滑升级而不会影响服务的正常功能?
服务的健康检查功能该如何编写,有哪些需要注意的点?

接下来,我们来逐步讨论这个问题。

一、服务启动不是马上能对外提供服务的

我们在 k8s 中运行的服务,常常会依赖于 Redis、MySQL、MongoDB,业务依赖的第三方服务等。
服务启动的时候,可能需要完成一些操作,比如

  • 缓存数据的加载: 比如从 MySQL 中读取缓存数据放到 Redis 中
  • 数据库表结构的初始化或者表记录变更:

根据代码转化成对应的 表结构,比如自动生成 table
业务变更需要执行的 sql 语句,比如 django 的 migration

  • 和第三方服务 API 进行交互: 比如将自己注册到 nacos 中

服务的启动时间,从 1s以内,到几十s,都有可能。

当我们的服务还没有准备好,但是服务在 k8s 中已经显示为 Ready 状态(表示已经正常运行了),那么外部的流量就会进来,可能造成服务报错或者业务故障,这不是我们期望的。

二、健康检查功能的设计

2.1 健康检查功能应该是全局的

2.2 健康检查功能应该是非阻塞的

不能因为执行了健康检查功能而影响自身业务服务

2.3 健康检查功能应该要能体现服务的健康状况

2.4 健康检查功能最好是无验证的

2.5 健康检查功能应该只体现自身服务的健康状况

三、使用健康检查

apiVersion: v1 
kind: Pod
metadata:
  name: pod-http-readiness  
spec:
  containers:
  - name: app
    image: nginx
    readinessProbe:     # 设置就绪探针
      httpGet:         
        path: /hello   # 检查/hello路径的可访问性 
        port: 80       
      initialDelaySeconds: 5  # 容器启动后首次探测等待5秒
      timeoutSeconds: 2      # 探测超时时间2秒 
      periodSeconds: 10       # 每10秒探测一次
apiVersion: v1 
kind: Pod
metadata:
  name: pod-exec-readiness  
spec:
  containers:
  - name: app
    image: nginx
    readinessProbe:     # 设置就绪探针
      exec:
        command: ["/bin/cat", "/app/start.txt"]  # 执行cat命令检查文件存在性
      initialDelaySeconds: 5  # 容器启动后首次探测等待5秒
      timeoutSeconds: 2      # 探测超时时间2秒  
      periodSeconds: 10       # 每10秒探测一次
apiVersion: v1 
kind: Pod
metadata:
  name: pod-tcp-readiness  
spec:
  containers:
  - name: app
    image: nginx
    readinessProbe:     # 设置就绪探针
      tcpSocket:      
        port: 80       
      initialDelaySeconds: 5  # 容器启动后首次探测等待5秒
      timeoutSeconds: 2       # 探测超时时间2秒  
      periodSeconds: 10       # 每10秒探测一次

四、编写健康检查接口

package main

import (
  "fmt"
  "log"
  "net/http"
  "os"
  "time"
)

func main() {
  http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
    time.Sleep(5 * time.Second)
    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, "ok")
  })

  http.HandleFunc("/ready", func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, "ok")
  })

  log.Println("listen on :8080")
  log.Fatal(http.ListenAndServe(":8080", nil))
}