开发K8s准入控制器前的准备工作:集群检查与项目搭建指南

前言

在上一篇文章中,我们规划了要开发一个自动注入Nginx Sidecar的Webhook。但在真正开始写代码之前,必须先做好充分的准备工作。

我曾经踩过一个坑:代码写完了,部署到集群却发现apiserver根本没有启用MutatingAdmissionWebhook插件,导致Webhook完全不生效。回过来检查配置、重新编译apiserver,浪费了大量时间。

今天就详细讲讲开发K8s准入控制器前的准备工作,包括集群环境检查、项目初始化、配置设计等。

第一步:检查集群准入配置

1.1 检查Admission Registration API

首先确认集群是否启用了准入控制的API:

kubectl api-versions | grep admission

期望的输出:

admissionregistration.k8s.io/v1
admissionregistration.k8s.io/v1beta1

说明

  • v1是稳定版本(Kubernetes 1.16+)
  • v1beta1是旧版本,建议用v1
  • 如果没有输出,说明集群版本太旧(<1.9),不支持Webhook

1.2 检查准入控制插件

确认apiserver启用了MutatingAdmissionWebhookValidatingAdmissionWebhook

# 方法1:查看apiserver进程参数
ps aux | grep kube-apiserver | grep enable-admission-plugins

# 方法2:如果是静态Pod
kubectl get pod -n kube-system -l component=kube-apiserver -o yaml | grep enable-admission-plugins

# 方法3:查看kube-apiserver帮助(如果在本地有apiserver二进制文件)
kube-apiserver -h | grep enable-admission-plugins

Kubernetes 1.20+默认启用的插件

NamespaceLifecycle, LimitRanger, ServiceAccount, TaintNodesByCondition, 
PodSecurity, Priority, DefaultTolerationSeconds, DefaultStorageClass, 
StorageObjectInUseProtection, PersistentVolumeClaimResize, RuntimeClass, 
CertificateApproval, CertificateSigning, CertificateSubjectRestriction, 
DefaultIngressClass, MutatingAdmissionWebhook, ValidatingAdmissionWebhook, 
ResourceQuota

关键插件

  • MutatingAdmissionWebhook:必须启用,用于Mutating Webhook
  • ValidatingAdmissionWebhook:必须启用,用于Validating Webhook

如果发现没有启用

# 修改apiserver启动参数
kube-apiserver \
  --enable-admission-plugins=...,MutatingAdmissionWebhook,ValidatingAdmissionWebhook

# 如果是kubeadm部署的集群,修改/etc/kubernetes/manifests/kube-apiserver.yaml

1.3 测试Webhook连通性

可以先创建一个简单的测试Webhook验证环境:

# 创建一个简单的nginx服务模拟Webhook
kubectl create deployment webhook-test --image=nginx
kubectl expose deployment webhook-test --port=443 --target-port=443

# 检查是否可以访问
kubectl get svc webhook-test

第二步:创建Go项目

2.1 初始化项目

# 创建项目目录
mkdir -p ~/projects/kube-mutating-webhook-inject-pod
cd ~/projects/kube-mutating-webhook-inject-pod

# 初始化Go模块
go mod init kube-mutating-webhook-inject-pod

# 创建项目结构
mkdir -p pkg deploy certs

2.2 添加依赖

# 添加K8s API依赖
go get k8s.io/api@latest
go get k8s.io/apimachinery@latest

# 添加YAML解析依赖
go get gopkg.in/yaml.v2

# 添加日志依赖(可选,也可以用标准库)
go get github.com/golang/glog

# 整理依赖
go mod tidy

生成的go.mod文件:

module kube-mutating-webhook-inject-pod

go 1.21

require (
	github.com/golang/glog v1.2.0
	gopkg.in/yaml.v2 v2.4.0
	k8s.io/api v0.28.0
	k8s.io/apimachinery v0.28.0
)

第三步:设计配置结构

3.1 配置文件设计

我们需要一个配置文件定义要注入的Sidecar容器。复用K8s原生的Container和Volume结构:

# config.yaml
containers:
  - name: sidecar-nginx
    image: nginx:1.25-alpine
    imagePullPolicy: IfNotPresent
    ports:
      - containerPort: 80
        protocol: TCP
    volumeMounts:
      - name: nginx-conf
        mountPath: /etc/nginx/conf.d
    resources:
      limits:
        cpu: 100m
        memory: 128Mi
      requests:
        cpu: 50m
        memory: 64Mi

volumes:
  - name: nginx-conf
    configMap:
      name: nginx-sidecar-config

设计说明

  • containers:要注入的sidecar容器列表(可以注入多个)
  • volumes:sidecar需要的volume
  • 使用K8s原生结构,便于验证和使用

3.2 Go结构体定义

// pkg/config.go
package main

import (
	corev1 "k8s.io/api/core/v1"
)

// Config 存储注入配置
type Config struct {
	Containers []corev1.Container `yaml:"containers"` // 要注入的容器
	Volumes    []corev1.Volume    `yaml:"volumes"`    // 要注入的volume
}

3.3 配置加载函数

// pkg/config.go

import (
	"crypto/sha256"
	"fmt"
	"io/ioutil"

	"github.com/golang/glog"
	"gopkg.in/yaml.v2"
)

// loadConfig 从文件加载配置
func loadConfig(configFile string) (*Config, error) {
	// 读取配置文件
	data, err := ioutil.ReadFile(configFile)
	if err != nil {
		return nil, fmt.Errorf("failed to read config file: %w", err)
	}

	// 计算配置文件的hash,便于调试
	glog.Infof("New configuration: sha256sum %x", sha256.Sum256(data))

	// 解析YAML
	var cfg Config
	if err := yaml.Unmarshal(data, &cfg); err != nil {
		return nil, fmt.Errorf("failed to parse config: %w", err)
	}

	return &cfg, nil
}

第四步:搭建HTTPS服务器

Webhook必须使用HTTPS(Kubernetes要求),所以我们需要:

  1. TLS证书(后续会生成)
  2. HTTPS服务器

4.1 Webhook配置结构

// pkg/webhook.go
package main

import (
	"net/http"
)

// webhookServer Webhook服务器
type webhookServer struct {
	sidecarConfig *Config      // 注入配置
	server        *http.Server // HTTP服务器
}

4.2 命令行参数

// main.go
package main

import (
	"flag"
	"fmt"

	"github.com/golang/glog"
)

// webHookSvrOptions Webhook服务器选项
type webHookSvrOptions struct {
	port           int    // HTTPS监听端口
	certFile       string // TLS证书文件路径
	keyFile        string // TLS私钥文件路径
	sidecarCfgFile string // Sidecar配置文件路径
}

func main() {
	var runOption webHookSvrOptions

	// 解析命令行参数
	flag.IntVar(&runOption.port, "port", 8443, "Webhook server port.")
	flag.StringVar(&runOption.certFile, "tlsCertFile", "/etc/webhook/certs/cert.pem", 
		"File containing the x509 Certificate for HTTPS.")
	flag.StringVar(&runOption.keyFile, "tlsKeyFile", "/etc/webhook/certs/key.pem", 
		"File containing the x509 private key to --tlsCertFile.")
	flag.StringVar(&runOption.sidecarCfgFile, "sidecarCfgFile", "config.yaml", 
		"File containing the mutation configuration.")
	flag.Parse()

	// 加载配置
	sidecarConfig, err := loadConfig(runOption.sidecarCfgFile)
	if err != nil {
		glog.Errorf("Failed to load configuration: %v", err)
		return
	}
	glog.Infof("[sidecarConfig:%v]", sidecarConfig)
}

4.3 加载TLS证书

// main.go

import (
	"crypto/tls"
)

func main() {
	// ... 加载配置 ...

	// 加载TLS证书对
	pair, err := tls.LoadX509KeyPair(runOption.certFile, runOption.keyFile)
	if err != nil {
		glog.Errorf("Failed to load key pair: %v", err)
		return
	}
	glog.Infof("Loaded TLS certificate successfully")
}

4.4 创建HTTPS服务器

// main.go

import (
	"fmt"
	"net/http"
)

func main() {
	// ... 加载配置和证书 ...

	// 创建Webhook服务器实例
	webhooksvr := &webhookServer{
		sidecarConfig: sidecarConfig,
		server: &http.Server{
			Addr:      fmt.Sprintf(":%v", runOption.port),
			TLSConfig: &tls.Config{
				Certificates: []tls.Certificate{pair},
			},
		},
	}

	// 创建路由
	mux := http.NewServeMux()
	mux.HandleFunc("/mutate", webhooksvr.serveMutate)
	mux.HandleFunc("/health", webhooksvr.serveHealth)
	webhooksvr.server.Handler = mux

	glog.Infof("Starting webhook server on port %d", runOption.port)

	// 在goroutine中启动服务器
	go func() {
		// 注意:ListenAndServeTLS的第一个参数为空字符串,使用server.Addr
		if err := webhooksvr.server.ListenAndServeTLS("", ""); err != nil {
			glog.Errorf("Failed to listen and serve webhook server: %v", err)
		}
	}()
}

4.5 添加Handler

// pkg/webhook.go

import (
	"io"
	"net/http"

	"github.com/golang/glog"
)

// serveMutate 处理/mutate请求
func (ws *webhookServer) serveMutate(w http.ResponseWriter, r *http.Request) {
	// 读取请求体
	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, fmt.Sprintf("could not read request body: %v", err), http.StatusBadRequest)
		return
	}

	glog.Infof("Received mutation request: %s", string(body))

	// TODO: 解析AdmissionReview,构造响应
	// 这部分在下一篇文章中详细讲解
}

// serveHealth 健康检查
func (ws *webhookServer) serveHealth(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	w.Write([]byte("ok"))
}

4.6 优雅关闭

// main.go

import (
	"context"
	"os"
	"os/signal"
	"syscall"
)

func main() {
	// ... 启动服务器 ...

	// 监听系统信号,实现优雅关闭
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
	
	// 等待信号
	<-signalChan

	glog.Infof("Got OS shutdown signal, shutting down webhook server gracefully...")

	// 优雅关闭服务器
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	
	if err := webhooksvr.server.Shutdown(ctx); err != nil {
		glog.Errorf("Server shutdown error: %v", err)
	}
}

第五步:本地测试准备

5.1 创建测试证书(临时)

在正式部署前,可以用自签名证书本地测试:

# 生成私钥
openssl genrsa -out certs/server.key 2048

# 生成证书签名请求
openssl req -new -key certs/server.key -out certs/server.csr \
  -subj "/CN=localhost/O=Test"

# 生成自签名证书
openssl x509 -req -days 365 -in certs/server.csr \
  -signkey certs/server.key -out certs/server.crt

5.2 创建测试配置

# test-config.yaml
containers:
  - name: sidecar-nginx
    image: nginx:alpine
    ports:
      - containerPort: 80

volumes: []

5.3 本地运行测试

# 编译
go build -o webhook-server .

# 运行(使用测试证书)
./webhook-server \
  --port=8443 \
  --tlsCertFile=certs/server.crt \
  --tlsKeyFile=certs/server.key \
  --sidecarCfgFile=test-config.yaml

# 测试健康检查
curl -k https://localhost:8443/health
# 输出: ok

第六步:容器化准备

6.1 Dockerfile

# Dockerfile
FROM golang:1.21-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o webhook-server .

FROM alpine:latest
RUN apk --no-cache add ca-certificates

WORKDIR /
COPY --from=builder /app/webhook-server /webhook-server

# 创建证书和配置目录
RUN mkdir -p /etc/webhook/certs /etc/webhook/config

EXPOSE 8443

ENTRYPOINT ["/webhook-server"]

6.2 构建镜像

# 构建
docker build -t nginx-sidecar-injector:v1.0 .

# 测试运行
docker run -d \
  -p 8443:8443 \
  -v $(pwd)/certs:/etc/webhook/certs \
  -v $(pwd)/test-config.yaml:/etc/webhook/config/config.yaml \
  nginx-sidecar-injector:v1.0 \
  --sidecarCfgFile=/etc/webhook/config/config.yaml

准备工作清单

在开始写核心业务逻辑前,确保完成了以下检查:

  • 集群启用了admissionregistration.k8s.io/v1 API
  • 集群启用了MutatingAdmissionWebhookValidatingAdmissionWebhook插件
  • 创建了Go项目并初始化了模块
  • 设计了配置文件结构和加载逻辑
  • 搭建了基本的HTTPS服务器框架
  • 实现了配置加载功能
  • 实现了健康检查接口
  • 创建了Dockerfile用于容器化
  • 本地测试可以正常运行

常见问题排查

问题1:kubectl api-versions没有admissionregistration

原因:集群版本太旧(<1.9)

解决:升级Kubernetes版本

问题2:加载证书失败

Failed to load key pair: open /etc/webhook/certs/cert.pem: no such file or directory

原因:证书路径错误或证书不存在

解决

# 检查证书文件是否存在
ls -la certs/

# 确保路径正确
./webhook-server --tlsCertFile=./certs/server.crt --tlsKeyFile=./certs/server.key

问题3:端口被占用

Failed to listen and serve webhook server: listen :8443: bind: address already in use

解决

# 查找占用端口的进程
lsof -i :8443

# 杀掉进程或更换端口
./webhook-server --port=8444

问题4:配置文件解析失败

Failed to load configuration: failed to parse config: yaml: unmarshal errors

原因:YAML格式错误

解决:使用YAML验证工具检查配置文件

总结

完成准备工作后,项目结构应该是:

kube-mutating-webhook-inject-pod/
├── go.mod
├── go.sum
├── main.go              # 主程序入口
├── config.yaml          # 配置文件
├── pkg/
│   └── webhook.go       # Webhook逻辑(待实现)
├── certs/               # 证书目录
│   ├── server.crt
│   └── server.key
├── deploy/              # 部署文件(待创建)
└── Dockerfile

现在基础框架已经搭建好了,下一篇文章我们将实现核心的Mutation逻辑——解析AdmissionReview、构造JSON Patch、返回响应。

下一步

准备好环境后,就可以开始实现核心的Webhook逻辑了:

  1. 解析AdmissionReview请求
  2. 构造Pod Patch
  3. 返回AdmissionResponse
  4. 部署到K8s集群

你准备好了吗?

  1. 你的集群启用了MutatingAdmissionWebhook吗?
  2. 你是如何管理项目依赖的?Go Modules还是其他工具?
  3. 你在搭建HTTPS服务器时遇到过什么问题?

求助与交流

如果你在准备工作中遇到了问题,欢迎在评论区交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

加倍巴巴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值