从零拆解Kubernetes控制平面:用Docker和Go还原API Server与etcd交互本质
很多工程师在初次接触Kubernetes时,都会被其复杂的控制平面组件搞得晕头转向。API Server、etcd、Controller Manager这些名词在文档里反复出现,但仅靠阅读理论资料很难真正理解它们如何协同工作。本文将带你用Docker容器和简单的Go程序,亲手搭建一个迷你Kubernetes控制平面,通过"造轮子"的方式逆向理解这些核心组件的工作机制。
1. 实验环境搭建:容器化组件模拟
我们先从最基础的环境准备开始。这个实验不需要完整的Kubernetes集群,只需要Docker和Go语言环境就能复现核心交互流程。
# 启动一个单节点etcd容器 docker run -d --name my-etcd \ -p 2379:2379 \ -e ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 \ -e ETCD_ADVERTISE_CLIENT_URLS=http://localhost:2379 \ quay.io/coreos/etcd:v3.5.0这个etcd容器将作为我们模拟Kubernetes的键值存储后端。接下来我们准备一个简化的API Server模拟程序:
package main import ( "context" "log" "net/http" "go.etcd.io/etcd/clientv3" ) type APIServer struct { etcdClient *clientv3.Client } func NewAPIServer() *APIServer { cli, err := clientv3.New(clientv3.Config{ Endpoints: []string{"localhost:2379"}, }) if err != nil { log.Fatal(err) } return &APIServer{etcdClient: cli} } func (s *APIServer) HandlePodCreate(w http.ResponseWriter, r *http.Request) { // 模拟处理Pod创建请求 _, err := s.etcdClient.Put(context.Background(), "/pods/default/nginx", "nginx-pod-data") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Write([]byte("Pod created")) } func main() { apiServer := NewAPIServer() http.HandleFunc("/pods", apiServer.HandlePodCreate) log.Fatal(http.ListenAndServe(":8080", nil)) }这个简化版的API Server实现了最核心的功能:接收HTTP请求并将数据写入etcd。虽然真实的Kubernetes API Server要复杂得多,但这个模拟程序已经包含了最本质的交互模式。
2. etcd数据模型深度解析
Kubernetes将所有集群状态都存储在etcd中,理解其存储结构是掌握控制平面工作原理的关键。让我们通过几个实验来观察典型的数据组织方式。
Kubernetes在etcd中的核心路径结构:
/registry/pods/<namespace>/<pod-name>- Pod资源/registry/services/<namespace>/<service-name>- Service资源/registry/deployments/<namespace>/<deployment-name>- Deployment资源/registry/events/<namespace>/<event-name>- 事件信息
我们可以用etcdctl工具直接查询这些数据:
# 查询所有Pod资源 docker exec my-etcd etcdctl get /registry/pods --prefix # 写入一个模拟的Deployment docker exec my-etcd etcdctl put /registry/deployments/default/nginx-deployment \ '{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"nginx-deployment"},"spec":{"replicas":3}}'etcd数据版本控制机制:
Kubernetes严重依赖etcd的版本控制功能来实现watch机制。每个修改都会产生一个新的修订版本(revision),客户端可以监听特定版本的变更:
// 监听Pod资源变化 watchChan := etcdClient.Watch(context.Background(), "/registry/pods/", clientv3.WithPrefix()) for resp := range watchChan { for _, ev := range resp.Events { log.Printf("Event type: %s, Key: %s, Value: %s\n", ev.Type, ev.Kv.Key, ev.Kv.Value) } }这个watch机制是Controller Manager等组件感知集群状态变化的基础。
3. API Server认证授权全流程实验
API Server是Kubernetes的网关,所有请求都要经过认证(Authentication)、授权(Authorization)和准入控制(Admission Control)三个阶段。我们来模拟实现这个流程。
3.1 认证(Authentication)实验
创建一个模拟ServiceAccount认证的中间件:
func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("Authorization") if token != "Bearer system:serviceaccount:default:default" { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } next(w, r) } }然后修改API Server的路由:
http.HandleFunc("/pods", AuthMiddleware(apiServer.HandlePodCreate))3.2 授权(RBAC)实验
在etcd中存储RBAC规则:
docker exec my-etcd etcdctl put /registry/roles/default/pod-creator \ '{"rules":[{"apiGroups":[""],"resources":["pods"],"verbs":["create"]}]}' docker exec my-etcd etcdctl put /registry/rolebindings/default/pod-creator-binding \ '{"subjects":[{"kind":"ServiceAccount","name":"default","namespace":"default"}],"roleRef":{"kind":"Role","name":"pod-creator"}}'然后在API Server中添加授权检查:
func (s *APIServer) checkAuthorization(user, verb, resource string) bool { resp, err := s.etcdClient.Get(context.Background(), fmt.Sprintf("/registry/rolebindings/default/%s", user)) // 简化实现,实际Kubernetes有更复杂的RBAC评估逻辑 return err == nil && len(resp.Kvs) > 0 }4. 控制器工作原理实践
Controller Manager中的各种控制器通过监听etcd中的资源变化来驱动集群达到期望状态。我们来模拟一个简单的Deployment控制器。
首先创建一个控制器程序监听Deployment和ReplicaSet的变化:
func (c *DeploymentController) Run() { deployChan := c.watch("/registry/deployments/") rsChan := c.watch("/registry/replicasets/") for { select { case event := <-deployChan: c.syncDeployment(event) case event := <-rsChan: c.syncReplicaSet(event) } } } func (c *DeploymentController) syncDeployment(event clientv3.Event) { // 解析Deployment // 检查对应的ReplicaSet是否存在 // 如果不存在就创建 _, err := c.etcdClient.Put(context.Background(), "/registry/replicasets/default/nginx-rs", `{"spec":{"replicas":3}}`) if err != nil { log.Printf("Failed to create ReplicaSet: %v", err) } }这个简化版的控制器实现了Deployment控制器的核心逻辑:监听Deployment变化,并确保对应的ReplicaSet存在且符合期望状态。
5. 完整交互流程实验
现在让我们把这些组件串联起来,观察一个完整的Pod创建流程:
- 用户通过kubectl发送创建Pod请求
- API Server接收请求并验证认证信息
- API Server检查RBAC授权规则
- 通过验证后将Pod数据写入etcd
- 调度器监听Pod变化,为未调度Pod选择节点
- Kubelet监听已调度到本节点的Pod,启动容器
我们可以用cURL模拟这个过程:
# 模拟kubectl请求 curl -X POST http://localhost:8080/pods \ -H "Authorization: Bearer system:serviceaccount:default:default" # 查看etcd中的Pod数据 docker exec my-etcd etcdctl get /registry/pods --prefix通过这些实验,你应该对Kubernetes控制平面各组件的协作有了更直观的理解。虽然真实的Kubernetes实现要复杂得多,但核心原理与我们这个简化模型是一致的。