golang实现Kubernetes就绪检测的健康检查插件healthcheck的使用

Golang实现Kubernetes就绪检测的健康检查插件healthcheck的使用

为什么健康检查很重要

健康检查对于构建分布式系统中的弹性、自愈应用程序至关重要。它们提供:

  1. 自动恢复:在Kubernetes中,失败的健康检查会触发Pod自动重启
  2. 负载均衡集成:防止流量被路由到不健康的实例
  3. 优雅降级:当非关键服务失败时,应用程序可以优雅降级
  4. 操作可见性:健康端点提供系统状态的即时洞察
  5. 零停机部署:就绪检查确保新部署只有在完全初始化后才会接收流量

功能特性

  • 多种检查类型:基本(同步)、手动和后台(异步)检查
  • Kubernetes原生:内置/live/ready端点,遵循k8s约定
  • JSON状态报告:详细的健康状态和历史记录
  • 线程安全:具有适当同步的并发安全操作
  • 优雅关闭:正确清理后台检查和关闭信号

安装

go get -u github.com/kazhuravlev/healthcheck

快速开始

package main

import (
	"context"
	"errors"
	"math/rand"
	"time"

	"github.com/kazhuravlev/healthcheck"
)

func main() {
	ctx := context.TODO()

	// 1. 创建healthcheck实例
	hc, _ := healthcheck.New()

	// 2. 注册一个简单的检查
	hc.Register(ctx, healthcheck.NewBasic("redis", time.Second, func(ctx context.Context) error {
		if rand.Float64() > 0.5 {
			return errors.New("service is not available")
		}
		return nil
	}))

	// 3. 启动HTTP服务器
	server, _ := healthcheck.NewServer(hc, healthcheck.WithPort(8080))
	_ = server.Run(ctx)

	// 4. 在http://localhost:8080/ready检查健康状态
	select {}
}

健康检查类型

1. 基本检查(同步)

基本检查在调用/ready端点时按需运行。适用于:

  • 快速操作(<1秒)
  • 需要新鲜数据的检查
  • 低成本操作
// 数据库连接检查
dbCheck := healthcheck.NewBasic("postgres", time.Second, func (ctx context.Context) error {
  return db.PingContext(ctx)
})

2. 后台检查(异步)

后台检查在单独的goroutine中定期运行(在后台模式下)。适用于:

  • 昂贵的操作(API调用、复杂查询)
  • 有速率限制的检查
  • 可以使用稍微过时数据的操作
// 外部API健康检查 - 每30秒运行一次
apiCheck := healthcheck.NewBackground(
  "payment-api",
  nil, // 初始错误状态
  5*time.Second, // 初始延迟
  30*time.Second, // 检查间隔
  5*time.Second,  // 每次检查的超时时间
  func (ctx context.Context) error {
    resp, err := client.Get("https://api.payment.com/health")
    if err != nil {
      return err
    }
    defer resp.Body.Close()
    if resp.StatusCode != 200 {
      return errors.New("unhealthy")
    }
    return nil
  },
)

3. 手动检查

手动检查由您的应用程序逻辑控制。适用于:

  • 初始化状态(缓存预热、数据加载)
  • 断路器模式
  • 功能标志
// 缓存预热检查
cacheCheck := healthcheck.NewManual("cache-warmed")
hc.Register(ctx, cacheCheck)

// 在启动期间设置为不健康
cacheCheck.SetErr(errors.New("cache warming in progress"))

// 缓存预热完成后
cacheCheck.SetErr(nil)

最佳实践

1. 选择正确的检查类型

场景 检查类型 原因
数据库ping 基本 快速,需要新鲜数据
文件系统检查 基本 快速,本地操作
外部API健康 后台 昂贵,速率限制
消息队列深度 后台 指标查询,可以是过时的
缓存预热状态 手动 应用程序控制的状态

2. 设置适当的超时

// ❌ 不好: 超时太长会阻塞就绪状态。超时应小于k8s中的超时
healthcheck.NewBasic("db", 30*time.Second, checkFunc)

// ✅ 好: 短超时
healthcheck.NewBasic("db", 1*time.Second, checkFunc)

3. 正确使用状态码

  • 存活检查(/live): 应该几乎总是返回200 OK

    • 仅当应用程序处于不可恢复状态时才失败
    • Kubernetes将在失败时重启Pod
  • 就绪检查(/ready): 应在以下情况下失败:

    • 关键依赖项不可用
    • 应用程序仍在初始化
    • 应用程序正在关闭

4. 为错误添加上下文

func checkDatabase(ctx context.Context) error {
  if err := db.PingContext(ctx); err != nil {
    // 使用fmt.Errorf添加上下文。它将出现在/ready报告中
	return fmt.Errorf("postgres connection failed: %w", err)
  }

  return nil
}

5. 优雅关闭

// 创建healthcheck实例
hc, _ := healthcheck.New()

// 注册您的常规检查
hc.Register(ctx, healthcheck.NewBasic("database", time.Second, checkDB))

// 启动HTTP服务器
server, _ := healthcheck.NewServer(hc, healthcheck.WithPort(8080))
_ = server.Run(ctx)

// 在您的优雅关闭处理程序中
func gracefulShutdown(hc *healthcheck.Healthcheck) {
  // 将应用程序标记为正在关闭 - /ready将返回500
  hc.Shutdown()

  // 继续正常的关闭过程
  // - 停止接受新请求
  // - 完成现有请求
  // - 关闭数据库连接等
}

6. 监控检查

hc, _ := healthcheck.New(
  healthcheck.WithCheckStatusHook(func (name string, status healthcheck.Status) {
    // hcMetric可以是prometheus指标 - 这取决于您的基础设施
	hcMetric.WithLabelValues(name, string(status)).Set(1)
  }),
)

完整示例

package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"time"

	"github.com/kazhuravlev/healthcheck"
	_ "github.com/lib/pq"
)

func main() {
	ctx := context.Background()

	// 初始化依赖项
	db, err := sql.Open("postgres", "postgres://localhost/myapp")
	if err != nil {
		log.Fatal(err)
	}

	// 创建healthcheck
	hc, _ := healthcheck.New()

	// 1. 数据库检查 - 同步,关键
	hc.Register(ctx, healthcheck.NewBasic("postgres", time.Second, func(ctx context.Context) error {
		return db.PingContext(ctx)
	}))

	// 2. 缓存预热 - 手动控制
	cacheReady := healthcheck.NewManual("cache")
	hc.Register(ctx, cacheReady)
	cacheReady.SetErr(fmt.Errorf("warming up"))

	// 3. 外部API - 后台检查
	hc.Register(ctx, healthcheck.NewBackground(
		"payment-provider",
		nil,
		10*time.Second, // 初始延迟
		30*time.Second, // 检查间隔
		5*time.Second,  // 超时
		checkPaymentProvider,
	))

	// 启动健康检查服务器
	server, _ := healthcheck.NewServer(hc, healthcheck.WithPort(8080))
	if err := server.Run(ctx); err != nil {
		log.Fatal(err)
	}

	// 模拟缓存预热完成
	go func() {
		time.Sleep(5 * time.Second)
		cacheReady.SetErr(nil)
		log.Println("Cache warmed up")
	}()

	// 优雅关闭示例
	go func() {
		time.Sleep(30 * time.Second)
		log.Println("Initiating graceful shutdown...")
		hc.Shutdown() // /ready现在将返回500,停止新流量
		log.Println("Application marked as shutting down")
	}()

	log.Println("Health checks available at:")
	log.Println("  - http://localhost:8080/live")
	log.Println("  - http://localhost:8080/ready")

	select {}
}

func checkPaymentProvider(ctx context.Context) error {
	// 支付提供者检查的实现
	return nil
}

与Kubernetes集成

apiVersion: v1
kind: Pod
spec:
  containers:
    - name: app
      livenessProbe:
        httpGet:
          path: /live
          port: 8080
        initialDelaySeconds: 10
        periodSeconds: 10
        timeoutSeconds: 5
        failureThreshold: 3
      readinessProbe:
        httpGet:
          path: /ready
          port: 8080
        initialDelaySeconds: 5
        periodSeconds: 5
        timeoutSeconds: 3
        failureThreshold: 2

响应格式

/ready端点返回带有检查历史记录的详细JSON:

健康应用:

{
	"status": "up",
	"checks": [
		{
			"name": "postgres",
			"state": {
				"status": "up",
				"error": "",
				"timestamp": "2024-01-15T10:30:00Z"
			},
			"history": [
				{
					"status": "up",
					"error": "",
					"timestamp": "2024-01-15T10:29:55Z"
				}
			]
		}
	]
}

正在关闭的应用:

{
	"status": "down",
	"checks": [
		{
			"name": "postgres",
			"state": {
				"status": "up",
				"error": "",
				"timestamp": "2024-01-15T10:30:00Z"
			}
		},
		{
			"name": "__shutting_down__",
			"state": {
				"status": "down",
				"error": "The application in shutting down process",
				"timestamp": "2024-01-15T10:30:05Z"
			},
			"history": null
		}
	]
}

更多关于golang实现Kubernetes就绪检测的健康检查插件healthcheck的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang实现Kubernetes就绪检测的健康检查插件healthcheck的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang实现Kubernetes就绪检测的健康检查插件

Kubernetes中的健康检查(Health Check)是确保应用可靠性的重要机制,主要包括就绪检测(Readiness Probe)和存活检测(Liveness Probe)。下面我将介绍如何使用Golang实现一个健康检查插件,特别是就绪检测功能。

基本概念

就绪检测(Readiness Probe)用于确定容器是否准备好接收流量。当Pod的所有容器都准备就绪时,Kubernetes才会将流量路由到该Pod。

实现方式

在Golang中,我们通常可以通过HTTP端点或命令行方式来实施健康检查。下面是一个完整的HTTP端点实现示例:

package main

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

var isReady bool

func main() {
	// 初始状态为未就绪
	isReady = false

	// 模拟应用启动过程
	go func() {
		time.Sleep(10 * time.Second) // 模拟10秒启动时间
		isReady = true
		fmt.Println("应用已准备就绪")
	}()

	// 设置HTTP路由
	http.HandleFunc("/healthz", healthz)
	http.HandleFunc("/readyz", readyz)

	// 启动HTTP服务器
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	fmt.Printf("服务器监听端口 %s\n", port)
	http.ListenAndServe(fmt.Sprintf(":%s", port), nil)
}

// 存活检测端点
func healthz(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	w.Write([]byte("OK"))
}

// 就绪检测端点
func readyz(w http.ResponseWriter, r *http.Request) {
	if isReady {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("OK"))
	} else {
		w.WriteHeader(http.StatusServiceUnavailable)
		w.Write([]byte("Not Ready"))
	}
}

Kubernetes部署配置

在Kubernetes部署文件中配置就绪检测:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        image: my-app:latest
        ports:
        - containerPort: 8080
        readinessProbe:
          httpGet:
            path: /readyz
            port: 8080
          initialDelaySeconds: 5  # 容器启动后5秒开始检查
          periodSeconds: 5        # 每5秒检查一次
          failureThreshold: 3     # 连续3次失败才认为未就绪
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 20

高级实现

对于更复杂的场景,可以实现一个健康检查管理器:

package health

import (
	"sync"
)

type Checker interface {
	Check() bool
	Name() string
}

type HealthCheck struct {
	checkers []Checker
	mu       sync.RWMutex
}

func New() *HealthCheck {
	return &HealthCheck{}
}

func (h *HealthCheck) AddChecker(c Checker) {
	h.mu.Lock()
	defer h.mu.Unlock()
	h.checkers = append(h.checkers, c)
}

func (h *HealthCheck) IsReady() bool {
	h.mu.RLock()
	defer h.mu.RUnlock()

	for _, checker := range h.checkers {
		if !checker.Check() {
			return false
		}
	}
	return true
}

// 示例检查器
type DatabaseChecker struct{}

func (d *DatabaseChecker) Check() bool {
	// 实现数据库连接检查
	return true
}

func (d *DatabaseChecker) Name() string {
	return "database"
}

使用建议

  1. 初始延迟:设置合理的initialDelaySeconds,确保应用有足够时间启动
  2. 检查频率periodSeconds不宜过短,避免给应用带来压力
  3. 失败阈值failureThreshold应根据应用特性调整
  4. 端点保护:生产环境中应保护健康检查端点,避免暴露敏感信息
  5. 日志记录:记录健康检查失败情况,便于排查问题

总结

通过Golang实现Kubernetes就绪检测相对简单,核心是提供一个HTTP端点或执行命令来报告应用状态。合理的健康检查配置可以显著提高应用的可靠性和Kubernetes集群的稳定性。

回到顶部