ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

纯源码部署Kubernetes集群:手动签发证书与组件配置全记录

2026/10/6 3:43:49 拓冰建站 浏览量
纯源码部署Kubernetes集群:手动签发证书与组件配置全记录 我刚开始学k8s的时候走的也是kubeadm一键安装的路子命令敲下去二十分钟后集群就起来了。快是真的快但快完就傻了——证书在哪生成的静态Pod怎么编排的apiserver为什么一定要先连上etcd这些问题被工具包装得严严实实。后来我决定用纯源码部署的方式把所有组件手动过一遍在一台Rocky Linux上用官方发布二进制逐台启动etcd、kube-apiserver、controller-manager、scheduler、kubelet和kube-proxy手动签发证书、手写kubeconfig。这篇文章就是那次动手过程的完整记录。很多教程里说的“源码部署”实际指的是不经过包管理器、不依赖kubeadm自动初始化而是直接使用官方release包里编译好的二进制手动组装集群。如果你真想从GitHub拉源码自己go build思路也会在文中一并说明。这篇文章适合两类人一是刚学完k8s基础概念想搞懂组件之间到底怎么协作的二是公司内网环境不方便用kubeadm默认配置需要自己掌控每一项参数的人。1. 为什么我劝你手动做一次纯源码部署1.1 源码部署和kubeadm的差别到底在哪kubeadm把太多事情自动化了生成CA和各类证书、下发各组件kubeconfig、把控制面组件做成静态Pod放在/etc/kubernetes/manifests里、为kubelet配置bootstrap token。你得到的是一个能用的集群但集群内部长什么样它并不会主动告诉你。纯源码部署则完全相反。你得自己下载二进制自己决定每个组件放在哪个目录自己写systemd单元文件自己把证书放到指定位置。这听起来工作量翻倍但做完之后你会获得一个非常值钱的认知kubeadm初始化时那几行日志背后到底发生了多少事。对比项kubeadm方式纯源码部署方式证书签发自动生成默认1年有效期手动用cfssl/openssl签发有效期可控控制面组件静态Pod方式托管systemd服务方式托管kubelet接入bootstrap token自动批准可提前签发证书或手工配置bootstrap学习收益低遇到问题容易黑盒高每个组件职责一清二楚故障排查先查容器和静态Pod目录直接看systemd日志和二进制参数控制面组件采用systemd服务而不是静态Pod这是和kubeadm最大的不同。kubeadm把kube-apiserver等组件放在静态Pod里由kubelet拉起好处是自带“进程守护日志轮转”坏处是排查问题多绕了一层容器。纯源码部署直接用systemctl start kube-apiserver日志看journalctl哪个组件挂了、为什么挂一目了然。1.2 动手前的环境准备与版本选型环境方面我用的是一台Rocky Linux 9虚拟机内存建议至少4GCPU至少2核磁盘20G以上。如果你的机器配置太低apiserver和etcd可能启动没问题但后面跑CNI和CoreDNS时会非常吃紧。系统层面的准备工作按顺序列一下关闭swapswapoff -a并注释掉/etc/fstab里的swap行。配置时间同步chronyd确保节点时间准确。证书对时间戳极为敏感时间漂移会导致一连串报错。设置主机名和hosts映射比如master节点叫k8s-master工作节点叫k8s-node1ip到主机名的映射写入/etc/hosts。加载必要的内核模块主要是overlay和br_netfilter并设置net.bridge.bridge-nf-call-iptables等内核参数。版本选型上我建议不要盲目追最新。以我当时部署的v1.30.x稳定版为例你需要到kubernetes官方release页面下载对应的kubernetes-server-linux-amd64.tar.gz里面已经包含了编译好的kube-apiserver、kube-controller-manager、kube-scheduler、kubelet、kube-proxy和kubectl二进制。etcd需要单独下载选择etcd v3.5系列即可官方在CHANGELOG里会注明某个K8s版本对应的推荐etcd版本照着选就行。如果你非要自己从源码编译也不是不行。先把Go环境装好版本要符合K8s仓库里go.mod的要求然后克隆kubernetes仓库执行make WHATcmd/kube-apiserver这类命令单独编译某个组件。编译过程会拉大量依赖包耗时较长。生产环境里通常没必要自己编译官方二进制的构建参数已经经过安全加固直接用就好。2. 证书体系和kubeconfig是纯源码部署的“入场券”2.1 一整包证书都有谁k8s的内部通信几乎全部是HTTPS加双向TLS没有证书组件之间根本不认识。kubeadm自动生成的正是这整套东西。纯源码部署里这一步要自己完成。常见需要签发的证书清单如下证书用途常见CNca.crt / ca.key自建CA根证书签发下面所有证书kubernetes-caapiserver.crt / apiserver.keykube-apiserver对外提供HTTPS服务kube-apiserverapiserver-kubelet-client.crt / .keyapiserver访问kubelet时做客户端认证kube-apiserver-kubelet-clientetcd server证书etcd对外提供服务端认证etcd-serveretcd peer证书etcd节点间通信认证etcd-peeradmin.crt / admin.key管理员操作集群kubernetes-admincontroller-manager.crt / .keycontroller-manager访问apiserversystem:kube-controller-managerscheduler.crt / .keyscheduler访问apiserversystem:kube-schedulerkubelet.crt / .keykubelet访问apiserver以及对外提供10250服务节点名属于system:nodes组这些证书统一放在/etc/kubernetes/pki目录下。为什么要自己签一方面是为了控制有效期我一般直接签10年省去每年续期的焦虑另一方面是证书的SAN列表要严格按照你的集群IP来写kubeadm给的是默认值一旦你的apiserver走负载均衡或者有多网卡默认SAN就不够用了。2.2 用cfssl一次性签发全套证书签发工具我用cfssl比直接操作openssl命令要清晰得多。cfssl的工作方式是用JSON配置描述CSR请求批量生成也很方便。先创建一个CA配置文件ca-config.json定义证书的用途组合。这里有个细节要注意K8s要求apiserver证书同时具备服务端认证和客户端认证能力etcd证书除了serverAuth还需要peerAuth因为etcd节点之间既要提供服务也会作为客户端访问其他节点。cfssl的profile正好能区分这些场景我通常会预设两个profile一个叫kubernetes一个叫etcd。签发apiserver证书时CSR里的hosts字段必须包含所有你可能用来访问apiserver的地址127.0.0.1、master节点所有网卡IP、负载均衡VIP、以及Service ClusterIP的首地址。这个首地址默认是10.96.0.1也就是集群内部访问kubernetes.default.svc.cluster.local的地址。漏掉任何一个IP后期用该IP访问apiserver都会报证书校验失败。etcd证书签发时hosts要填入etcd集群所有节点的IP和域名单节点集群也要填上本机的IP和127.0.0.1。如果后面要扩容etcd集群最好现在就把可能的成员IP预留进去否则扩完节点证书就得重新签。实际签发过程中我踩过一个坑cfssl默认的CA配置里签名算法用的rsa密钥长度如果设成2048大集群性能没问题但K8s官方默认是2048保持默认即可。重点是ca-config.json里的expiry值一定要写长一点默认只有8760小时也就是一年我签过的生产集群因为忘了改这个字段一年后整集群信任链罢工教训相当深刻。2.3 手工生成kubeconfig的秘密证书是确认“你是谁”kubeconfig是告诉组件“你去哪儿找apiserver、用什么身份进”。kubeconfig看起来是一堆base64其实核心就是三个要素clusterapiserver的地址和CA证书。user客户端证书和私钥。context把cluster和user绑定在一起并可以指定默认命名空间。我生成admin.kubeconfig的思路很简单直接用kubectl config命令避免手写YAMLkubectl config set-cluster kubernetes \ --serverhttps://192.168.1.10:6443 \ --certificate-authority/etc/kubernetes/pki/ca.crt \ --kubeconfig/etc/kubernetes/admin.kubeconfig kubectl config set-credentials admin \ --client-certificate/etc/kubernetes/pki/admin.crt \ --client-key/etc/kubernetes/pki/admin.key \ --kubeconfig/etc/kubernetes/admin.kubeconfig kubectl config set-context adminkubernetes \ --clusterkubernetes --useradmin \ --kubeconfig/etc/kubernetes/admin.kubeconfig kubectl config use-context adminkubernetes \ --kubeconfig/etc/kubernetes/admin.kubeconfig为什么要这么做而不是手写文件无非是避免手写时把证书内容、私有指数等细节搞错。kubectl config命令会自动处理base64编码和字段拼接就算记不清kubeconfig的YAML结构命令也不会写错语法。其他组件的kubeconfig生成逻辑一样只是证书和用户名不同。注意controller-manager和scheduler的证书CN必须对应到特定的用户组因为K8s内置的RBAC规则是根据用户名前缀来授权的。controller-manager的用户名必须包含system:kube-controller-managerscheduler的用户名必须包含system:kube-scheduler否则这些组件即使拿着合法证书访问apiserver也会被权限系统拒绝。关于admin用户它的CN是kubernetes-admin并且通常会有system:masters用户组。这个组是超级管理员组自带全部权限。所以admin.kubeconfig一定要保护好权限比root还大。3. 先搭地基etcd、apiserver、controller-manager和scheduler3.1 把etcd先跑起来etcd是整个集群的状态仓库apiserver是唯一读写它的组件但etcd得先活着apiserver才能启动。对单机测试环境来说etcd可以用很简单的参数跑起来但纯源码部署的意义在于可控所以我仍然用systemd托管。我把etcd二进制解压到/usr/local/bin数据目录放在/var/lib/etcd然后写一个systemd单元文件关键参数这些--name节点名单机就写master。--data-dir数据目录。--listen-client-urls和--advertise-client-urls客户端访问地址apiserver连的就是这个。--listen-peer-urls和--initial-advertise-peer-urls节点间通信地址单节点也要写。--initial-cluster初始集群成员列表格式是节点名该节点的peer地址。--cert-file和--key-file服务端证书etcd对外提供HTTPS协议。--peer-cert-file和--peer-key-file节点间通信证书。--trusted-ca-fileCA证书用来校验客户端。--client-cert-authtrue启用客户端证书认证防止任意ip能读写etcd。启动后验证一下systemctl start etcd systemctl enable etcd ETCDCTL_API3 etcdctl --cacert/etc/kubernetes/pki/ca.crt \ --cert/etc/kubernetes/pki/etcd-server.crt \ --key/etc/kubernetes/pki/etcd-server.key \ --endpointshttps://127.0.0.1:2379 endpoint health这里有个容易出问题的点--initial-cluster只在集群第一次启动时生效。如果etcd因为配置改错了而失败你清理掉/var/lib/etcd下的数据重新启动这一节参数才会重新读取。很多人调试etcd时只重启服务不删数据结果一直报“成员已存在”之类的错。3.2 最重的角色kube-apiserverkube-apiserver是k8s的大脑参数最多涉及到的证书也最多。它把HTTP请求转成对etcd的读写还会调用kubelet获取节点状态所以它的kubeconfig指向自己即可——apiserver自己就是服务端不需要找别的apiserver。我用的systemd启动方式核心参数拆成几类理解存储类--etcd-servershttps://127.0.0.1:2379告诉apiserver数据存在哪儿。网络类--service-cluster-ip-range10.96.0.0/12定义Service的虚拟IP段。这个CIDR一定要和apiserver证书里的10.96.0.1对应上否则内部访问kubernetes.default服务时证书校验会出问题。安全类--client-ca-file/etc/kubernetes/pki/ca.crt校验所有客户端证书--tls-cert-file和--tls-private-key-file指定apiserver自己的HTTPS证书。ServiceAccount类--service-account-key-file/etc/kubernetes/pki/sa.pub和--service-account-signing-key-file/etc/kubernetes/pki/sa.key。apiserver拿私钥签发ServiceAccount的Tokencontroller-manager拿公钥验证Token这两个文件要配套不然Pod创建后挂载的Token无法通过校验。apiserver启动后先用curl做一次健康检查curl --cacert /etc/kubernetes/pki/ca.crt https://127.0.0.1:6443/healthz返回ok就说明apiserver本体没问题。如果你看到apiserver进程起来了但健康检查失败下一步一定是看它的标准输出重点找这几类关键词连不上etcd、证书SAN不匹配、参数里出现的CIDR或证书路径错位。网上那个高频报错“the api server is not healthy after 4m0.00747357s”我专门研究过。它本质上是kubeadm初始化后等待apiserver健康检查超时的提示。深挖下去最常见的病根是apiserver这个静态Pod启动失败而静态Pod启动失败的原因多数又逃不出这三类etcd没就绪、证书SAN里面缺了apiserver的访问地址、kubelet自身没把pause镜像拉起来。纯源码部署因为能看到每一个进程的journal日志反而更容易定位。3.3 controller-manager和scheduler通过kubeconfig干活这两个组件不需要对外暴露端口给用户它们只是拿着各自的kubeconfig去访问apiserver然后在自己内存里跑控制循环。controller-manager负责各种控制器节点控制器负责监控节点心跳Deployment控制器负责维持Pod副本数ServiceAccount控制器负责给命名空间创建默认账户。参数不复杂重点就三个--kubeconfig指定它访问apiserver的身份文件--leader-electtrue开启选主逻辑高可用部署时多副本同时运行但只有一个实例主导--allocate-node-cidrstrue配合--cluster-cidr让apiserver在节点注册时自动给每个节点分配一个Pod网段。scheduler更简单它只做一件事情监控新创建的、还没有绑定节点的Pod结合资源请求量、节点亲和性、污点容忍度等因素把Pod调度到合适的节点上。启动参数就是--kubeconfig和--leader-electtrue。这两个组件启动后你盯着日志看正常情况下应该是每隔几秒输出一次心跳级别的检查信息没有异常堆栈。等它们都活着控制面其实就具备对外服务的条件了。我在这个阶段习惯用kubectl get cs看一眼组件状态不过新版K8s已经弱化了这个命令我更倾向于直接查看各服务是否Running。4. 让节点真正活过来kubelet与kube-proxy4.1 kubelet的两种认证方式和关键参数kubelet是整个集群的“手脚”跑在每台节点上负责拉起Pod、上报节点状态、给apiserver提供运行时数据。它的认证方式有两种一是提前签发节点证书并把kubeconfig发给它二是用bootstrap token流程自动申请证书。纯源码部署我建议用第一种流程直接可控。kubelet启动有两个地方需要看仔细--kubeconfig指定它访问apiserver的凭证--config指定一个配置文件里面可以放cgroup驱动、容器运行时端点等杂项配置。核心参数我梳理一下--container-runtime-endpointunix:///run/containerd/containerd.sock告诉kubelet通过CRI协议连接containerd。这里顺便澄清一个常被混淆的概念kubelet并不直接使用DockerDocker只是容器运行时的一种实现通过CRI插件桥接。现在主流集群直接用containerd镜像构建仍然可以留在Docker里但运行时的角色已经解耦了。--cgroup-driversystemd必须和容器运行时的cgroup驱动保持一致。我的环境里containerd默认是systemd这里写成systemd如果两边不一致节点会一直处于NotReady状态。--node-ip指定节点上报给apiserver的IP多网卡环境下必须显式指定。--pod-infra-container-image指定pause镜像地址。这个镜像负责创建Pod的网络命名空间和PID命名空间是Pod存在的基石。国内环境建议提前拉到本地仓库并修改这个参数不然Pod创建时会卡在拉取镜像上。kubelet启动用的pause镜像是个老生常谈的坑。很多时候容器运行时已经配置了正确的沙箱镜像但kubelet这边还抱着编译时的默认值两边不一致就会导致Pod一直ContainerCreating。踩过这个坑之后我每次部署完都会在pod配置里显式注入kubelet.kubernetes.io/pod-infra-container-image注释或者直接在kubelet配置里写死沙箱镜像地址。4.2 生成kubelet的kubeconfig并启动节点生成kubelet的kubeconfig时有个语义必须先讲清楚kubelet的客户端证书CN必须等于节点的主机名并且证书必须属于system:nodes用户组。K8s的RBAC规则里system:nodes组里的成员才能以节点身份注册并获取权限。我用手动签发的方式客户端证书的CN就是k8s-master添加Osystem:nodes组织。这个组织字段不能乱写它直接决定apiserver是否认可这个节点的身份。启动kubelet后观察节点是否注册systemctl start kubelet journalctl -u kubelet -f kubectl get nodesmaster节点上如果能看到新节点但状态是NotReady不用慌大概率是CNI网络插件还没装。此时查看kubelet日志会出现“network plugin is not ready”之类的提示这个我在下一节专门说。4.3 kube-proxy的ipvs模式和Service验证kube-proxy负责实现Service的负载均衡逻辑。它监听apiserver中的Service和Endpoint变化然后在本机写入转发规则。转发模式有二种主流选择iptables和ipvs。我强烈推荐ipvs模式性能更好规则更清晰但前提是内核要加载ip_vs相关模块。启动kube-proxy前先加载内核模块并安装ipvsadmmodprobe ip_vs modprobe ip_vs_rr modprobe ip_vs_wrr modprobe ip_vs_shkube-proxy的启动参数同样很简单--kubeconfig指向apiserver--proxy-modeipvs指定模式。启动后验证一下规则是否生成ipvsadm -L -n如果没有输出规则先看内核模块是否加载再看kube-proxy日志里有没有“cant initialize ipvs: IPVS cant be used”之类的提示。验证Service是否工作我通常会起一个最简单的nginx deployment然后暴露NodePort或直接访问ClusterIP。从节点内部curl一下ClusterIP加端口能通说明apiserver、Endpoint控制器和kube-proxy整条链路都通了。这个阶段还有一个小提醒apiserver的--service-node-port-range默认是30000-32767如果你测试NodePort时端口选在了这个范围之外Service会创建失败。纯源码部署没有默认的准入控制帮你纠正这类参数全靠自己摸一遍默认值。5. 网络、DNS与常见的坑5.1 CNI选型与Pod互通到目前控制面和节点都能通信了但Pod之间还是孤岛——kubelet只是创建了Pod沙箱没给Pod接网线。这就要装CNI插件。选型上我比较了几种插件数据面优势注意事项FlannelVXLAN/host-gw部署简单适合学习功能简单不支持网络策略CalicoBGP/VXLAN性能好支持网络策略部署稍复杂模块多CiliumeBPF功能最强可观测性佳对新内核有要求入门门槛高第一次做纯源码部署我用Flannel把Pod网络跑通理解通信原理之后再换Calico研究网络策略。安装CNI有两个硬条件apiserver的--allocate-node-cidrs要开启--cluster-cidr要和CNI配置里写死的Pod网段一致。我统一规划Pod网段为10.244.0.0/16这样所有节点的Pod都能在同一个大网段内寻址。给Flannel准备好kubeconfig然后执行部署清单。等Flannel Pod启动完成后节点状态会从NotReady切换成Ready。验证方法很简单跨节点创建两个Pod互相ping一下对方Pod IP通畅就说明CNI链路没问题。如果你在生产环境里装Calico建议提前规划好BGP peer、IP池大小和跨网段路由。好消息是Calico官方清单里环境变量配置得很清楚照着填就行。坏消息是它默认开启了网络策略如果你还没创建任何策略可能会出现Pod间意外的访问限制排查起来需要一点耐心。5.2 CoreDNS和namespace的协作集群内的服务发现依赖CoreDNS。你在Pod里访问myservice.default.svc.cluster.localCoreDNS负责把它解析成ClusterIP。CoreDNS本身也是跑在Deployment里的。装好之后注意看它能不能调度到节点上以及它的Service ClusterIP是否和apiserver的--service-cluster-ip-range来自同一网段。有人图省事直接把CoreDNS的Service IP写死结果不小心写到了别的网段里整个集群的服务解析全部失效查起来非常隐蔽。这里顺带解释一下namespace的作用。CoreDNS放在kube-system命名空间系统组件也都放这里。namespace本质上是资源隔离的逻辑分组配合RBAC还可以做到“某个用户只能看某个namespace的资源”。你创建临时测试Pod时放在default就行不用动不动就用-A看全集群资源。5.3 排查实录apiserver不健康等问题的黄金诊断路径纯源码部署没有kubeadm帮你兜底报错都得自己扛。我整理了一份高频问题清单都是实操中真实遇到的现象直接原因排查方向apiserver健康检查失败etcd未就绪或连不上systemctl status etcdss -lntp证书校验失败访问apiserver就报x509错误证书SAN缺IP或主机时间漂移查看生成的证书SAN列表检查chronyd同步状态节点一直NotReadyCNI未装好kubelet日志搜“network plugin”kubectl get pods -n kube-systemkube-proxy不生效ipvs内核模块未加载lsmodPod一直ContainerCreating沙箱pause镜像拉不下来修改kubelet的--pod-infra-container-image参数或配置镜像仓库controller-manager卷到错误kubeconfig里的用户组不对检查签发证书时CN和O字段是否包含system:kube-controller-manager端口就是不通firewall或安全组没放行firewall-cmd --list-ports确认6443、2379、10250、30000端口关于“the api server is not healthy after 4m0.00747357s”这条报错我想多说几句。它在kubeadm场景下是初始化等待超时但根因通常在apiserver启动过程里。我遇到过的最多的是这两种一是apiserver没法连到etcd比如etcd的监听地址写成了只监听回环口而apiserver访问的是集群IP二是apiserver的证书SAN里漏掉了用来访问它的IP健康检查的请求走着不走就TLS失败。源码部署方式因为能看到所有进程的完整日志排查起来比容器方式还要快。排查问题的黄金路径我的习惯是“三步走”先看服务状态是否activesystemctl status kube-apiserver再看对应日志journalctl -u kube-apiserver -n 200最后看监听端口ss -lntp | grep 6443。这三个动作能解决八成问题。剩下的两成大多数是配置文件路径写错、证书权限不对、kubeconfig里server地址用了不能解析的主机名。安全加固方面至少做到这三条apiserver不要开--insecure-port默认走6443的TLSetcd必须启用--client-cert-auth没有客户端证书不能读写关闭apiserver的匿名访问--anonymous-authfalse。系统组相关的权限控制也要趁早理清system:masters是超级管理员不要轻易把普通账户塞进去。最后再分享一个小技巧纯源码部署最大的优势是每一行配置都握在自己手里。我建议你在部署过程中顺手写一份笔记记录每个组件用的systemd单元文件、关键启动参数、证书路径。后续无论是升级版本还是加节点翻笔记比翻记忆可靠得多。如果你在这个集群里继续实践可以考虑再部署一份Redis集群试试有状态应用的调度逻辑也可以试试把master节点组件拆成双副本验证高可用这会比跑在kubeadm集群里理解得更深刻。