Kubernetes 不是为了让一个应用「能跑起来」才出现的。一台机器、一个服务,systemd 或 Docker Compose 就够。真正需要它的场景通常是:机器多、服务多、发布勤、要自愈、还要把资源放到同一套调度里。
一句话:把一组机器抽象成资源池,让应用按声明持续运行。 下面用一个订单服务走完这条路——先说集群从哪来,再说 YAML 怎么变成进程。
什么时候才值得上
只有一个 order-api、一台服务器、偶尔发一次版,上 K8s 多半是给自己加税。税单包括 etcd、证书、网络插件和升级。
机器一多、副本一多、还要滚动发布和故障自愈,编排才开始划算。它不管你的 Go 或 Java 源码,只认镜像和资源对象。
编译这个仓库,不等于装好了集群
常见判断对了一半:K8s 本体就是 kubernetes/kubernetes 这个仓库,拉下来 make,再额外装 etcd 和 containerd,是不是就装好了?编出来的确实是 Kubernetes 自己;能调度 Pod 的系统,还差一整层装配。
编出来的是一组进程
make 不会给出「一个 K8s」。它给出的是以后要分别拉起来的二进制:
kube-apiserver:唯一对外的 HTTP API,对象校验完写入 etcdkube-controller-manager:看 etcd 里的期望状态,去凑 ReplicaSet、EndpointSlice 这些kube-scheduler:给未绑定的 Pod 填nodeNamekubelet:本机执行者,Watch 分给自己的 Pod,再叫容器运行时kube-proxy:Watch Service / EndpointSlice,改本机转发规则kubectl:客户端;kubeadm:把上面这些装到一起的工具
它们是控制面和节点代理,不是虚拟机管理程序,也不自己跑容器、不自己存数据、不自己给 Pod 插网线。存储和运行时被故意留在仓库外面,所以 etcd、containerd 必须另装——误解通常就停在这一步。
停得太早了。etcd 是 apiserver 的磁盘:没有它,API 起得来也写不进对象。containerd 是 kubelet 对面的 CRI:kubelet 只对 Unix socket 发 gRPC,不 fork 容器。这两样是最显眼的外部依赖,不是全部依赖。
只补 etcd 和 containerd,仍然对不上
把五个 kube-* 加上 etcd、containerd 全部启动,得到的是一堆互相不理的进程,不是集群。缺的每一项,对应一种具体的挂法。
证书。 组件默认 HTTPS + 客户端证书。没有集群 CA,kubelet 连不上 apiserver,kubectl 也连不上。kubeconfig 不是源码的一部分,是装配时签出来的通行证:里面是 API 地址、CA、客户端证或 token。手搓意味着给 apiserver、kubelet、controller、scheduler、admin 各签一套,CN 和组织还不能随便填。
runtime 插座。 containerd 装上不等于 kubelet 会用。kubelet 要 --container-runtime-endpoint=unix:///run/containerd/containerd.sock(或等价配置),containerd 自己还要打开 CRI 插件。对不上,Node 一直 NotReady,事件是 runtime 没就绪。
CNI。 kubelet 建完 Pod 沙箱,网口交给 CNI:/opt/cni/bin 里的插件二进制,加上 /etc/cni/net.d 里的配置。没有这一步,Pod 卡在 ContainerCreating,没有 IP。Calico、Cilium、Flannel 都是别的项目。kubeadm init 成功之后节点仍是 NotReady,官方第一句就是去装网络插件——这就是「编译加 etcd 加 containerd 还不够」的现场证据。
CoreDNS。 order-api 写 redis:6379,靠的是集群 DNS,不是 apiserver 内置功能。CoreDNS 以普通 Deployment 跑在集群里,解析 *.svc.cluster.local。没它,IP 直连也许行,服务名一定挂。它也不在 make 的产物里。
pause。 每个 Pod 先起一个 pause 容器,占住网络命名空间,业务容器再往里加。镜像一般是 registry.k8s.io/pause,kubernetes 仓库不打包它。runtime 拉不到,沙箱创建直接失败。
内核转发。 kube-proxy 改的是宿主机 iptables / IPVS / nftables。ip_forward 没开、网桥不把流量交给 iptables,Service 的 ClusterIP 就是摆设。这不是 Kubernetes 源码能替你打开的。
参数和顺序。 apiserver 要 --etcd-servers,kubelet 要 kubeconfig 和 runtime socket,controller / scheduler 要指到 apiserver。这些不是 make 写死的默认值。先起 kubelet 再起 apiserver,也只是报错重试,不会自动变成集群。
设计如此:存储、运行时、网络可替换。代价就是「安装」比「编译」重——重在接接口,不在再下一个巨型安装包。
| 有了什么 | 仍然缺什么 |
|---|---|
kube-apiserver 等二进制 |
谁签证书、谁填启动参数 |
| etcd | 只解决对象持久化 |
| containerd | 还要 CRI 插座、pause 镜像 |
| (到这里常以为装完了) | CNI、CoreDNS、内核转发、启动编排 |
开发机怎么跑,生产怎么装
源码里的 hack/local-up-cluster.sh 就是承认这一点的脚本:本机 make 出二进制,然后 hack/install-etcd.sh、用 cfssl 发测试 CA、按顺序起 etcd 和控制面、装一份简易 CNI、再 apply CoreDNS。前提是 containerd 已经在听那个 socket。单机、证书随便、进程挂在脚本下面,不是生产。
生产几乎从不从这仓库往节点交叉编译。包管理器里的 kubeadm、kubelet、kubectl 还是这组预编译二进制;kubeadm init / join 负责证书、控制面静态 Pod、kubelet 配置、节点入群。CoreDNS 它会部署,CNI 仍然要你选一个装。托管集群(EKS / GKE / AKS)把这一层藏掉,你只拿到 kubeconfig。
kubeadm 自己也是这个仓库 make 出来的。它不重新编译 Kubernetes,它把上面那些接口填上。
所以「安装 K8s」是两层:拿到二进制(编译或下载);把进程、证书、运行时、CNI、DNS 拼成能调度 Pod 的系统。第二层才叫集群。下面说的 Node、拓扑、kubeadm join,全是在做这一层。
先有服务器,再有 Node
Node 是 API 里的对象,不会凭空变出一台机器。真实顺序是:买机器或申请云主机 → 装操作系统 → 配网络、磁盘、防火墙和时间同步 → 装 containerd、kubelet、kubeadm → 加入集群 → API Server 里出现 Node。
kubelet 向 API Server 注册,机器才变成可调度的 Node。不是 kubectl apply 一份 node.yaml 就会自动创建服务器。机器由云平台、Terraform、Ansible 或人准备;K8s 只在机器已经存在之后,把它们纳入调度。
职责也是先逻辑、后落盘。控制面跑 kube-apiserver、kube-scheduler、kube-controller-manager,以及 etcd;Worker 跑 kubelet、containerd、kube-proxy、CNI 和业务 Pod。开发可以把它们塞进一台机器;生产通常分开。省下来的机器钱,最后容易变成事故复盘时间。
一套常见拓扑:三台控制面、三台 Worker。kubeadm init 起第一台控制面,kubeadm join ... --control-plane 补另外两台,剩下的 kubeadm join 当 Worker。证书、kubeconfig、静态 Pod、节点入群都在这一步完成;业务镜像和 order-api 的 Deployment 不在这里。托管 K8s、k3s、kind 走另一条装配路径,API 一样。开发者最终提交的都是资源对象。
业务交付物是镜像,不是源码
假设 order-api 监听 8080、连 Redis。K8s 不读仓库里的业务代码。标准链路是:源码 → 二进制 → OCI 镜像 → 推仓库 → Deployment 引用 image → kubelet 拉取 → runtime 起容器。
docker build -t registry.example.com/order-api:v1 .
docker push registry.example.com/order-api:v1
Docker 只是常见的构建工具,Podman、Buildah、Kaniko 一样能出 OCI 镜像。可以绕开镜像去挂本地文件,但不适合当常规生产交付:不好维护,也不好回滚。
配置用 ConfigMap,副本用 Deployment,稳定入口用 Service:
apiVersion: v1
kind: ConfigMap
metadata:
name: order-api-config
data:
LOG_LEVEL: info
REDIS_ADDR: redis:6379
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-api
spec:
replicas: 3
selector:
matchLabels:
app: order-api
template:
metadata:
labels:
app: order-api
spec:
containers:
- name: order-api
image: registry.example.com/order-api:v1
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: order-api-config
---
apiVersion: v1
kind: Service
metadata:
name: order-api
spec:
selector:
app: order-api
ports:
- port: 80
targetPort: 8080
kubectl apply -f order-api.yaml
正式发布通常走流水线:提交 → 测试 → 构建推镜像 → 更新 YAML 或 Helm values → CI/CD / Argo CD 同步 → 看 Deployment 是否 Ready。Dashboard、Lens、Rancher 适合看状态和排查,不适合当生产发布按钮。手点很快,背锅也快。
apply 之后,谁在干活
Deployment、Service、ConfigMap 由人、CI 或 GitOps 写入 API Server。后面的对象不是人一个个 apply 出来的:
| 对象 / 字段 | 谁写 |
|---|---|
| Deployment / Service / ConfigMap | 用户或发布系统 |
| ReplicaSet | Deployment controller |
| Pod | ReplicaSet controller |
Pod.spec.nodeName |
scheduler |
Pod.status |
kubelet |
共同点:最终都经 API Server 进 etcd。 链路是 Deployment → ReplicaSet → Pod → scheduler 绑到某台 Worker → 那台机器上的 kubelet 调 runtime 起进程。
Scheduler 选节点时看 CPU、内存、Ready、亲和性、污点、存储和拓扑,最后只写一个字段,例如 nodeName: worker-2。真正 CreateContainer 的是 worker-2 上的 kubelet,不是调度器远程 SSH。一个 Pod 不会跨 Node;一台 Worker 上可以同时跑 order-api、payment-api 和 Redis 多个 Pod。
kubelet 通过 HTTPS Watch API Server;对 containerd 走 CRI gRPC(Unix socket);CNI 是它拉起的本地插件进程;kube-proxy 自己 Watch Service / EndpointSlice,改 iptables 或 IPVS。只有访问 API Server 的组件才需要 kubeconfig。CNI 和 CSI 是节点上的插件,不是再开一条 HTTP 到控制面。
三副本不一定均分。三台 Worker 规格不同时,scheduler 可能把三个 Pod 拆开,也可能因为污点或资源不够把两个放同一台。期望状态是「三个 Ready」,不是「每台刚好一个」。
从机器到进程,可以收成一条链:准备服务器 → 装配集群(证书、etcd、kubelet、CNI、DNS)→ 构建并推送镜像 → apply YAML → controller 造出 ReplicaSet 和 Pod → scheduler 选节点 → kubelet 调 runtime → order-api 在某台 Worker 上跑起来。
编译给的是那组二进制;安装把它们拼成集群;应用侧提交的是镜像和声明。开发者改的是期望状态,控制器和 kubelet 负责把机器上的进程凑过去。
