/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}
最近把自己用 Ansible + Argo CD + Prometheus + Ollama + Go 搭的那套「K8s SRE 智能运维助手」从头到尾复盘了一遍。复盘时发现一个有意思的现象:整个技术栈里,C 开头的组件、协议、对象名密度异常高——从容器运行时到网络插件,从 K8s 原生对象到 Go 编译参数,几乎每一层都有它们的身影。
这篇文章把项目里真正用到过的 C 开头名词全部捞出来,写清全称、作用、以及我在项目里的实际用法。按「运行时 → 网络 → K8s 原生对象 → 工程协议」四层拆开,方便面试和复习时对照。
一、一张总览表
| 运行时 | containerd | containerd | K8s 节点上跑容器的底层运行时 |
| 运行时 | CRI | Container Runtime Interface | K8s 与容器运行时之间的标准接口 |
| 运行时 | crictl | CRI CLI(kubernetes-sigs) | 直接跟 CRI 运行时对话的排错命令 |
| 运行时 | ctr | containerd CLI | containerd 自带的镜像 / 容器管理命令 |
| 运行时 | cgroup | Control Group | Linux 用来限制容器 CPU / 内存的内核机制 |
| 运行时 | CGO | C Go bindings | Go 调用 C 代码的机制,本项目用 CGO_ENABLED=0 关它 |
| 网络 | CNI | Container Network Interface | K8s 与网络插件之间的标准接口 |
| 网络 | Calico | Project Calico | 本项目实际选用的 CNI 网络插件 |
| 网络 | CoreDNS | CoreDNS | 集群内部 DNS 服务,Service 域名解析靠它 |
| 网络 | CIDR | Classless Inter-Domain Routing | 无类域间路由,用来划 Pod 网段 |
| K8s 对象 | client-go / Clientset | official Go client for Kubernetes | 我那只 Go SRE Agent 操作 K8s API 的 SDK |
| K8s 对象 | CRD | Custom Resource Definition | PrometheusRule / ServiceMonitor 这类自定义资源 |
| K8s 对象 | ConfigMap | Config Map | K8s 里存放非敏感配置的对象 |
| K8s 对象 | ClusterRole | Cluster Role | 集群级 RBAC 权限定义 |
| K8s 对象 | ClusterRoleBinding | Cluster Role Binding | 把 ClusterRole 绑给 ServiceAccount |
| K8s 对象 | ClusterIP | Cluster IP | K8s Service 的默认虚拟 IP 类型 |
| K8s 对象 | Context | context.Context | Go 标准库,用来传递取消 / 超时信号 |
| 工程 | CI / CD | Continuous Integration / Delivery | 持续集成 / 持续交付,Argo CD 落地 CD 段 |
| 工程 | curl | Command Line Request URL | 命令行 HTTP 客户端,排查 Ollama / Prometheus 接口全靠它 |
| 工程 | Chart | Helm Chart | Helm 把一堆 K8s YAML 打成的可复用包 |
二、容器运行时层:让 Pod 真正跑起来
1. containerd
全称:containerd(本身就是名字,不是缩写)
是干什么的:一个开源的、工业级的容器运行时,负责镜像管理、容器生命周期、存储挂载和网络 namespace。K8s 从 1.24 开始把 dockershim 删了,现在 kubelet 直接通过 CRI 跟 containerd 这类运行时对话。
项目里怎么用:在 Ansible 的 common 角色里,我用 dnf 装 containerd.io,然后执行:
containerd config default > /etc/containerd/config.toml
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml
把 cgroup driver 切成 systemd——这一步必须跟 kubelet 的 cgroup driver 一致,否则容器会反复重启,是新手最常见的坑之一。
2. CRI —— Container Runtime Interface
全称:Container Runtime Interface(容器运行时接口)
是干什么的:K8s 定义的一套 gRPC 标准接口,位于 kubelet 和真正的容器运行时之间。有了它,kubelet 不用关心底下是 containerd、CRI-O 还是别的,只要对方实现了 CRI,就能被 K8s 接管。
项目里怎么用:我没有手写 CRI 代码,但 kubelet 跟 containerd 之间的所有通信都走它。排错时用 crictl 直接跟 CRI 层对话,可以绕开 kubectl 看到 kubelet 视角下的真实容器状态。
3. crictl
全称:CRI CLI(Kubernetes SIG 维护的命令行工具)
是干什么的:直接跟 CRI 运行时对话的客户端,相当于「kubelet 视角下的 docker ps」。当 Pod 一直 ContainerCreating 或 CrashLoopBackOff,kubectl 看到的信息不够细时,就轮到它上场。
项目里怎么用:
crictl images # kubelet 视角看到的镜像
crictl ps -a # 所有容器(包括已经退出的)
crictl logs <id> # 容器日志
4. ctr
全称:containerd CLI(containerd 自带的命令行)
是干什么的:containerd 自己的管理工具,跟 crictl 不同——它直接操作 containerd 的命名空间,不经过 CRI。
项目里怎么用:这是本项目一个关键环节。因为实验环境没有中央镜像仓库,我用 Docker 构建 sre-agent:v1.0 后:
docker save sre-agent:v1.0 -o sre-agent-v1.0.tar
ctr -n k8s.io image import sre-agent-v1.0.tar
重点是 -n k8s.io:kubelet 通过 CRI 看的是 containerd 的 k8s.io 命名空间,不写这个参数,镜像导到默认 namespace 里,kubelet 根本看不见,Pod 照样 ImagePullBackOff。
5. cgroup
全称:Control Group(控制组)
是干什么的:Linux 内核的一个机制,用来把一组进程放到一个「组」里,给这个组限制 CPU、内存、磁盘 IO、PID 数等资源。K8s 里每个 Pod 的 requests / limits,最终都是通过 cgroup 落到节点内核上的。
项目里怎么用:配合 SystemdCgroup = true,让 systemd 作为 cgroup 管理器。这样 systemd、kubelet、containerd 三方在同一个 cgroup driver 下协作,不会出现「kubelet 以为限了 1G,containerd 以为没限」的错位。
6. CGO
全称:C Go bindings(Go 调用 C 代码的机制)
是干什么的:默认情况下,Go 编译涉及系统调用的部分(比如 DNS 解析、用户信息查询)可能会链接系统的 C 库(glibc),产出动态链接的二进制。
项目里怎么用:我的 Dockerfile 里有一行关键编译参数:
RUN CGO_ENABLED=0 GOOS=linux go build -o /sre-agent .
- CGO_ENABLED=0:禁用 CGO,强制纯 Go 实现,产出完全静态链接的二进制。
- 为什么要这么做?因为基础镜像是 alpine,它用的是 musl libc 而不是 glibc,动态链接会导致运行时库不匹配。
- 静态链接之后,二进制不依赖任何外部库,甚至可以直接换成 scratch 空镜像,体积从几百 MB 砍到十几 MB。
三、网络层:让 Pod 之间、Pod 与外部能通信
7. CNI —— Container Network Interface
全称:Container Network Interface(容器网络接口)
是干什么的:跟 CRI 齐名的另一个 K8s 标准接口,定义了「一个网络插件应该长什么样」。只要插件实现了 CNI 规范,kubelet 在创建 Pod 时就会调用它,给 Pod 分配 IP、接好网线、配好路由。
项目里怎么用:我装的 Calico 就是一个 CNI 插件。kubeadm init 时传了 –pod-network-cidr=10.244.0.0/16,这个网段就是给 CNI 插件用的。
8. Calico
全称:Project Calico(本身是项目名,不是缩写)
是干什么的:主流 CNI 插件之一,用 BGP 路由的方式给每个 Pod 分配 IP,实现跨节点 Pod 互通。同时自带 NetworkPolicy 能力,可以在 IP 表之上做细粒度的网络访问控制。
项目里怎么用:
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.3/manifests/calico.yaml
装完之后,kubectl get pods -n kube-system 里会看到 calico-node(以 DaemonSet 形式跑在每个节点上)和 calico-kube-controllers。这俩 Pod 不 Running,整个集群的 Pod 网络就是断的。
9. CoreDNS
全称:CoreDNS(本身是项目名)
是干什么的:K8s 集群内部的 DNS 服务器。Service 创建后会得到一个 DNS 名(比如 ollama-service.ai-services.svc.cluster.local),Pod 之间要靠它做服务发现。
项目里怎么用:它是 kubeadm init 后自动以 Deployment 形式装在 kube-system 命名空间里的。我在排错时踩过一个大坑:Argo CD 的 repo-server Pod 因为 DNS 解析不了 github.com,所有应用状态都变成 Unknown。最后通过修 CoreDNS 的上游配置才解决——这让我意识到 GitOps 工具自身的网络连通性,是整个自动化体系的基石。
10. CIDR —— Classless Inter-Domain Routing
全称:Classless Inter-Domain Routing(无类域间路由)
是干什么的:一种表示 IP 网段的标准写法,比如 10.244.0.0/16 表示从 10.244.0.0 到 10.244.255.255 的整整 65536 个 IP。
项目里怎么用:在 Ansible inventory 里定义:
[all:vars]
pod_network_cidr = "10.244.0.0/16"
然后 kubeadm init –pod-network-cidr={{ pod_network_cidr }} 传给集群,Calico 就从这个大池子里给 Pod 分 IP。这个网段不能跟节点物理网段、NodePort 网段冲突,否则会出现诡异的路由黑洞。
四、K8s 原生对象与 SDK:Agent 跟集群对话的语言
11. client-go / Clientset
全称:official Go client for Kubernetes
是干什么的:K8s 官方出的 Go 语言 SDK。kubernetes.Clientset 是它的核心对象,实例化之后就能用它去 list / get / watch / delete Pod、读日志、查事件——我那只 SRE Agent 就是靠它干活的。
项目里怎么用:
import "k8s.io/client-go/kubernetes"
clientset, _ := kubernetes.NewForConfig(config)
// 感知:读 Pod 详情和日志
pod, _ := clientset.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{})
// 行动:删 Pod 触发 Deployment 重建
clientset.CoreV1().Pods(namespace).Delete(ctx, podName, metav1.DeleteOptions{})
并且我改造了客户端初始化逻辑,优先 rest.InClusterConfig() 读 Pod 内挂载的 ServiceAccount Token,失败再回退本地 kubeconfig——同一份代码既能本地调试,又能丢进集群里跑。
12. CRD —— Custom Resource Definition
全称:Custom Resource Definition(自定义资源定义)
是干什么的:K8s 的扩展机制。默认 K8s 只认识 Deployment、Service 这些内置对象;但你可以用 CRD 「发明」自己的对象类型,比如 PrometheusRule、ServiceMonitor、Application(Argo CD 的)。
项目里怎么用:我写的告警规则:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
这里的 PrometheusRule 就是 kube-prometheus-stack 通过 CRD 注册进来的。Argo CD 的 kind: Application 同理。
13. ConfigMap
全称:Config Map
是干什么的:K8s 里存放非敏感配置的对象,把配置从镜像里解耦出来,挂载进 Pod 当文件或环境变量用。
项目里怎么用:本项目没有显式写 ConfigMap(因为 Agent 的 URL 和模型名直接硬编码在常量里了——这是我复盘时标记的一个改进点:应该挪到 ConfigMap,做到改配置不重新打镜像)。
14. ClusterRole / ClusterRoleBinding
全称:Cluster Role / Cluster Role Binding(集群角色 / 集群角色绑定)
是干什么的:K8s RBAC 体系里的两个对象。
- ClusterRole:定义一组权限(能 get/list/watch 哪些资源、能不能 delete)。
- ClusterRoleBinding:把这组权限绑给某个 ServiceAccount / User / Group。
项目里怎么用:我给 SRE Agent 配了最小权限:
rules:
– apiGroups: [""]
resources: ["pods", "pods/log", "events"]
verbs: ["get", "list", "watch"]
– apiGroups: [""]
resources: ["pods"]
verbs: ["delete"]
只让它看 Pod、日志、事件,并且只能删 Pod——不能改 Deployment、不能碰 Secret。这是「最小权限原则」的实践:Agent 被打爆了,攻击者能拿到的也只有这点权限。
15. ClusterIP
全称:Cluster IP
是干什么的:K8s Service 的默认类型。会给 Service 分配一个集群内部的虚拟 IP,只能在集群内部访问,配合 CoreDNS 用域名调用。
项目里怎么用:Ollama、Prometheus、Grafana 我都显式改成了 NodePort 以便从外部浏览器访问,但 Agent 在 Pod 内部调用 Ollama 时,最终走的还是 ClusterIP + DNS 这条内部路径——这也是我前面提到那个「硬编码 NodePort 导致 Pod 内调用静默失败」bug 的根因。
16. context
全称:context.Context(Go 标准库包)
是干什么的:Go 官方用来在 API 边界之间传递取消信号、超时和请求域数据的工具。client-go 的所有方法第一个参数都是 ctx context.Context。
项目里怎么用:
pod, err := clientset.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{})
context.TODO() 是先占个位;生产环境应该传带超时的 context.WithTimeout,防止 kube-apiserver 卡住时整个 Agent 跟着挂死。
五、工程与协议层:贯穿整个项目的基础设施
17. CI / CD
全称:Continuous Integration / Continuous Delivery(持续集成 / 持续交付)
是干什么的:
- CI:代码合并后自动跑测试、构建镜像。
- CD:把构建好的产物自动部署到目标环境。
项目里怎么用:我没有搭完整的 CI 流水线(用 Git 手动 push),但 CD 段完全交给了 Argo CD——Git 仓库就是唯一真相源,Argo CD 自动把 Git 里的 YAML 同步到集群,还开了 selfHeal:谁要是敢手动 kubectl patch 改线上,它会立刻把状态纠回 Git 里的样子。
18. curl
全称:Command Line Request URL(命令行 URL 请求工具)
是干什么的:最常用的命令行 HTTP 客户端。排查任何 HTTP 接口是否通、返回什么,第一反应就是 curl -v 或 curl -s。
项目里怎么用:排错三板斧:
curl -s http://<prom>:9090/api/v1/alerts | jq '.data.alerts[] | select(.state=="firing")'
curl -s 'http://<prom>:9090/api/v1/query?query=up'
curl -s http://${PODIP}:11434/api/tags
以及最初装 Helm 也是用 curl 下载官方脚本。它看起来不起眼,但没有 curl,整个 SRE 排错链路会慢十倍。
19. Helm Chart
全称:Helm Chart(Helm 的打包格式)
是干什么的:Helm 把一个应用涉及的一堆 K8s YAML 加上 Go 模板和 values.yaml 打成一个标准化包,就像 apt 的 .deb、yum 的 .rpm。
项目里怎么用:Prometheus 那一套就是用 kube-prometheus-stack 这个 Chart 装的:
helm install prometheus-stack prometheus-community/kube-prometheus-stack \\
–namespace monitoring –create-namespace –version 57.2.0
一个 Chart 下来,Prometheus、Alertmanager、Grafana、operator、node-exporter、kube-state-metrics 全齐了。对于有状态、社区已经有成熟方案的应用,直接用 Chart 比手写 YAML 高效太多。
六、复盘时还提到,但本项目没真部署的 C 开头名词
这几个名词在「生产环境应该怎么改进」那一段出现过,但没有真的在我的实验集群里部署,列出来防止以后混淆:
| CSI | Container Storage Interface | 实验环境没有云盘也没有 StorageClass,我用 hostPath PV 顶了;上生产才需要 CSI 动态供给 |
| CronJob | Cron Job | 我的 Agent 是常驻 Deployment,靠 for 循环轮询;真要做定时清理任务才会用 CronJob |
| Ceph | Ceph Distributed Storage | 太重,单机三台虚机扛不动;改进方向里会换成 Longhorn 或 OpenEBS LocalPV |
七、写在最后
把这一串 C 开头名词串起来看,会发现它们其实是一条完整的链:
kubelet 通过 CRI 调用 containerd,containerd 通过 CNI(Calico)给 Pod 插网线,靠 CoreDNS 做服务发现,用 cgroup 限制资源;Pod 里跑着我那个用 client-go 写的 SRE Agent,Agent 通过 curl 调 Prometheus 和 Ollama,再用 ClusterRole 授权自己删故障 Pod;整个部署由 Helm Chart 打包、Argo CD 按 GitOps 同步。