1. 安装前的整体规划与版本选型
1.1 节点角色与硬件建议
开始动手安装K8s Cluster之前,我强烈建议你先拿一张纸把节点角色画出来。K8s集群最少要有两个角色:一个是控制平面节点,以前叫Master,负责API Server、调度器、控制器管理器这些大脑组件;另一个是工作节点,负责真正跑Pod。如果你只有一台机器,也可以装成单节点集群,控制平面和工作节点复用,但生产环境没人这么干,测试环境倒是可以。
硬件上的最低要求是控制平面节点2核2G,工作节点2核2G,实际跑起来这个配置非常紧张。我测试过,2G内存的节点装完系统、containerd、kubelet之后,可用内存剩不到1G,跑几个小Pod就告警。建议测试环境统一4核8G,这是比较舒服的起步配置。磁盘方面,系统盘至少50G,因为镜像、容器日志、Etcd数据都会持续占用空间,尤其Etcd,一旦集群规模上来,磁盘增长很快,建议单独挂一块盘给/var/lib/etcd或者按发行版默认路径规划。
网络方面,所有节点需要互通,主机名不能重,MAC地址不能伪造,这是kubeadm初始化时校验的硬性条件。还有就是确保节点之间关键端口能通,比如控制平面的6443、2379、2380,工作节点的10250、30000-32767。云服务器的话,安全组规则要提前放行;物理机或虚拟机就关掉防火墙,或者精确放通这些端口,不然排查起来很费劲。
1.2 版本选型与组件搭配
K8s版本迭代很快,选版本的原则是选稳定版,不要选刚发布的。以当前主流为例,建议选择1.27、1.28、1.29这些已经发布半年以上的版本,社区资料多,踩坑记录也多。更关键的是,K8s在1.24版本开始默认移除对Docker Shim的直接支持,如果你想继续用Docker Engine作为容器运行时,需要额外安装cri-dockerd适配器。我一般不推荐这么做,直接用containerd更符合官方方向,少踩坑。
组件搭配上,你脑子里要有一个时间线:容器运行时(比如containerd)在底层创建和管理容器,kubelet通过CRI协议与它通信;kubeadm是安装工具,负责初始化集群、生成证书、生成join命令;kubectl是客户端命令行工具,用来与API Server交互。这三个工具的版本建议保持一致,或者kubectl可以比集群版本高一个小版本,但不能低太多。比如集群是1.28,kubectl最好是1.28或1.29,不要用1.27去连1.28,有兼容性问题。
还有一个容易忽略的点:集群内网网段和Pod网段、Service网段不能冲突,需要在初始化之前规划好。K8s默认Service网段是10.96.0.0/12,Pod网段Flannel默认是10.244.0.0/16,Calico默认是192.168.0.0/16。如果你的物理或云内网正好用了这些网段,初始化时会直接报错,或者后续网络不通。所以提前规划这三个网段是必须的。
1.3 网络方案与存储方案选择
CNI插件选型是安装步骤里比较影响后续体验的一环。常见的有Flannel、Calico、Cilium。Flannel最简单,性能一般,适合测试和小规模环境;Calico支持网络策略,性能中上,采用BGP路由模式,生产环境用得最多;Cilium基于eBPF,功能强性能好,但内核要求高,对新手来说配置复杂一些。我建议测试环境用Flannel,生产环境用Calico,理由后面实战环节会展开。
存储方案在安装阶段不需要马上部署,但要提前想好。如果你跑的是无状态应用,那不需要考虑;如果有数据库、中间件这类需要持久化的,建议后面装一个存储控制器,比如Rook-Ceph、Longhorn,或者云厂商的云盘插件。这一步不影响kubeadm初始化,但影响你后续使用,所以规划时要留意。
2. 基础环境初始化:所有节点都要做的事
2.1 系统准备与内核模块
这里我以常见的Ubuntu 22.04和CentOS 7.9为例说明,但原理通用。先做基础配置,所有节点执行相同操作。
主机名设置,控制平面节点可以叫k8s-master,工作节点叫k8s-node1、k8s-node2。注意别用下划线,K8s对主机名要求是合法的DNS子域。然后配置hosts解析,把各节点的IP和主机名对应关系写进去,这样后期节点之间通信不用依赖DNS,避免DNS抖动导致的问题。
关闭swap是必须的,因为kubelet要求关闭swap才能正常工作。这个逻辑是为了保证内存配额对Pod有效,如果开启了swap,Pod的内存限制就形同虚设,调度器也无法准确预估可用内存。用swapoff -a临时关闭,然后注释掉/etc/fstab里的swap行,永久生效。注意有些云镜像默认没有swap,那就不用管。
内核参数和模块加载也很关键。需要加载br_netfilter模块,并设置net.bridge.bridge-nf-call-iptables等参数为1,否则iptables不会对桥接流量生效,Pod之间的通信会出现诡异问题。有些文档还会建议设置net.ipv4.ip_forward=1,这是开启IP转发,对于Pod出网访问和Service转发是必需的。我用一段命令整理如下:
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF sudo sysctl --system为什么需要overlay模块?因为containerd的默认Snapshotter就是overlayfs,overlay文件系统可以更高效地管理镜像层,不用这个模块,容器启动会失败。
2.2 安装并配置containerd
容器运行时我选containerd,理由前面说过。K8s从1.24开始原生支持containerd作为CRI运行时,不需要额外适配器,配置起来干净利落。
安装containerd有几种方式,最推荐的是直接安装docker-ce仓库里的containerd.io包,或者从GitHub下载官方二进制。这里我用Docker仓库方式,因为它在国内镜像源也容易同步。以Ubuntu为例:
sudo apt-get update sudo apt-get install -y containerd.io安装完成后,先备份默认配置,再生成基础配置:
sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml这里有一个必须改的配置项:SystemdCgroup。K8s为了确保资源配额稳定,官方建议容器运行时使用cgroupfs的systemd driver,与kubelet保持一致。所以在生成的config.toml里,找到SystemdCgroup = false,改成SystemdCgroup = true。如果不改,集群初始化一般也能过,但运行时可能出现内存限制不生效的问题,特别是节点压力大的时候会出诡异故障。
还有一个镜像加速配置,在config.toml里找到plugins."io.containerd.grpc.v1.cri".registry.mirrors这一段,添加镜像加速器地址。因为Kubernetes拉取的是google官方镜像,比如pause、etcd、kube-apiserver等,如果网络无法访问,初始化必失败。常用的办法是把镜像加速器或镜像仓库配到registry mirrors里,或者在kubeadm init时用--image-repository指定国内仓库。后面初始化章节会细说。
配置完成后重启containerd:
sudo systemctl daemon-reload sudo systemctl restart containerd sudo systemctl enable containerd检查状态用systemctl status containerd,确认是running。也可以用ctr version或者crictl version验证CRI接口是否正常。
2.3 安装kubeadm、kubelet、kubectl
这三个工具要从K8s官方软件源安装。以Ubuntu为例,需要先添加源和GPG key。注意不同版本源的URL可能不同,我是从官方文档复制的最新稳定源,里面包含了版本索引,用apt-cache policy可以查到可选的版本。
sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl gpg sudo curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list sudo apt-get update sudo apt-get install -y kubelet kubeadm kubectl sudo apt-mark hold kubelet kubeadm kubectlapt-mark hold是为了防止系统升级时自动升级这三个组件,导致集群版本不一致。测试环境无所谓,生产环境最好锁住版本。
如果你用的是CentOS,对应的源配置方法类似,只是用yum/dnf。装完之后,检查版本:
kubeadm version kubelet --version kubectl version --client三个版本应该一致。然后设置kubelet开机自启,但先不要启动服务。因为kubelet还没拿到初始配置,如果现在启动,它会反复报错重启。正确的做法是systemctl enable kubelet,先设置自启,等kubeadm init生成配置后再启动它。
这一步很多人会踩坑:提前systemctl start kubelet,结果看到一堆错误日志,以为装坏了。其实kubelet就是要等集群初始化后才有配置,它现在报错是正常的。
2.4 基础环境验证清单
完成上述操作后,建议做一个统一检查,避免后面初始化到一半才发现问题。我通常用这几条命令:
# 检查内核参数 sysctl net.bridge.bridge-nf-call-iptables net.ipv4.ip_forward # 检查swap状态 free -h | grep -i swap # 检查containerd crictl version # 检查kubelet是否enable systemctl is-enabled kubelet确保所有输出符合预期:两个内核参数都是1,swap显示为0,containerd有version输出,kubelet显示enabled。这一套检查下来,再进入初始化环节就很有底气了。
3. 控制平面初始化与工作节点加入
3.1 使用kubeadm init初始化控制平面
所有节点基础环境都准备好之后,先在控制平面节点上执行初始化。初始化之前,有几个参数必须明确:
--control-plane-endpoint:如果只有一个控制平面,写这台机器的IP或域名;如果做高可用,写负载均衡器的地址。--pod-network-cidr:指定Pod网段,必须和CNI插件要求的网段匹配。比如Flannel通常要求10.244.0.0/16,Calico默认192.168.0.0/16,具体看你装的CNI。--service-cidr:Service网段,默认10.96.0.0/12,一般不用改。--image-repository:指定镜像仓库地址。如果你能正常拉取google镜像,可以不指定;否则建议指定为registry.aliyuncs.com/google_containers这种国内的同步仓库,国内很多环境实测有效。
我在这里用containerd和Calico网络方案,Pod网段设为192.168.0.0/16,初始化命令如下:
sudo kubeadm init \ --control-plane-endpoint=192.168.1.10:6443 \ --pod-network-cidr=192.168.0.0/16 \ --service-cidr=10.96.0.0/12 \ --image-repository=registry.aliyuncs.com/google_containers注意--control-plane-endpoint如果是单节点,直接写本机IP加6443,不要写localhost,不然后面工作节点join时无法访问。如果这台机器有多个网卡,可能还需要--apiserver-advertise-address指定对外IP,否则kubeadm会默认选择一个网卡,可能选错。
执行过程中,kubeadm会先拉取所有需要的镜像:kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、pause、etcd、coredns。如果你配了镜像仓库,它会从指定仓库拉取。这个过程是耗时的,耐心等待,不要中断。如果中途报错,先看错误信息,最常见的几个:
- 报
[ERROR Port-6443]之类,说明端口被占用,用ss -lntp | grep 6443查一下。 - 报
[ERROR FileAvailable--etc-kubernetes-manifests],说明之前初始化过,需要kubeadm reset清掉。 - 报
[ERROR DirAvailable--etc-kubernetes-manifests],同样需要清理。
初始化成功后会输出一大段信息,包括:
- kubeadm join命令(分成控制平面和工作节点两种)
- kubectl配置文件的存放位置
- 以及一个token和ca-cert-hash
这段输出一定要保存下来,后面加入节点就要靠它。如果没保存,可以用kubeadm token create --print-join-command重新生成,但证书hash要从kubeadm ca certificate hash那里拿,稍微麻烦一点。
3.2 kubectl配置与认证
初始化完成后,当前用户还需要配置kubectl才能访问集群。kubeadminit会在/etc/kubernetes/admin.conf生成一个管理员配置文件。如果你当前是root,直接执行:
export KUBECONFIG=/etc/kubernetes/admin.conf但更好的做法是把配置复制到当前用户的kube目录下:
mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config配置好之后,运行kubectl get nodes,你会看到控制平面节点的状态是NotReady,这是因为还没装CNI网络插件。此时集群的API是可用的,但Pod无法被调度到节点上,因为节点网络还没打通。
3.3 安装CNI网络插件
控制平面初始化完,接下来必须马上装CNI插件。没有网络插件,coredns就会一直处于Pending状态,节点状态也永远是NotReady。
我习惯用Calico,因为生产环境用得最多。Calico的安装方式通常是从官方仓库拉取manifest文件,然后通过kubectl apply部署。以Calico v3.27版本为例:
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml但这里有个坑:Calico默认的Pod网段是192.168.0.0/16,而我初始化时Pod网段正好也设成了192.168.0.0/16,所以匹配。如果你初始化时用了别的网段,比如10.244.0.0/16,就必须修改calico.yaml中CALICO_IPV4POOL_CIDR这个环境变量,改成你的Pod网段,否则Calico的Pod和集群Pod网段不一致,网络直接废掉。
修改方法是在部署前把manifest下载到本地,用sed或编辑器替换:
wget https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml sed -i 's#192.168.0.0/16#10.244.0.0/16#g' calico.yaml kubectl apply -f calico.yamlCalico部署完成后,相关Pod会出现在kube-system命名空间,名字类似calico-node-xxxx、calico-kube-controllers-xxxx。等待这些Pod变成Running,然后kubectl get nodes,节点应该会变为Ready。
如果等了很久还是NotReady,排查方式:
kubectl get pods -n kube-system kubectl logs -n kube-system calico-node-xxxx我看过很多情况是Calico的veth网卡创建失败,或者BGP端口被系统防火墙拦截,检查节点之间的179端口是否互通。还有一点,有些云主机开启了源/目标检查,需要关闭,否则Calico的IPIP隧道包会被丢弃。
如果只是想快速测试网络,也可以用Flannel,部署更简单:
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.ymlFlannel要求Pod网段设为10.244.0.0/16,如果你初始化时不是这个网段,同样要改。另外Flannel的默认后端是VXLAN,对防火墙要求低一些,测试环境确实省心。
3.4 工作节点加入集群
控制平面Ready后,就可以把工作节点加进来了。回到工作节点,执行之前保存的kubeadm join命令。它的形态类似:
sudo kubeadm join 192.168.1.10:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash>如果你需要工作节点也参与控制平面,比如高可用场景,在控制平面节点上执行kubeadm init时会有额外一条命令,末尾多了--control-plane,那个执行逻辑稍有不同,一般测试环境不需要。
执行join之前,确保工作节点已经完成了第二节说的所有基础环境操作:关闭swap、加载内核模块、配置containerd、安装kubelet kubeadm。少一步,join都会失败。
执行join后,等待kubelet启动并注册节点。回到控制平面节点,用kubectl get nodes查看,工作节点状态会先显示NotReady,过一会变成Ready。如果一直显示NotReady,先看工作节点的kubelet日志:
journalctl -u kubelet -f常见的失败原因有:
- token过期:token默认24小时有效,如果超时了,在控制平面重新生成:
kubeadm token create --print-join-command - 证书hash不匹配:确认join命令里的ca-cert-hash正确,可以用
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex重新计算 - 节点主机名重复或hosts解析不对
- containerd配置不一致,导致kubelet无法通过CRI拉取pause镜像
工作节点加入后,建议在控制平面跑一个测试Pod验证跨节点通信,比如:
kubectl create deployment nginx --image=nginx kubectl expose deployment nginx --port=80 --type=NodePort然后查看Pod调度到哪个节点,访问NodePort端口,确认能通。这一步能有效检验CNI和kube-proxy是否正常。
4. 高可用与生产化注意事项
4.1 单控制平面集群的局限与高可用形态
如果只是测试环境,单控制平面完全够了。但如果你要搭一个稍微正式点的环境,高可用就必须考虑。K8s高可用主要解决两个问题:控制平面组件不宕机,以及Etcd数据不丢失。
高可用有两种常见形态:
- 堆叠式Etcd:Etcd与控制平面节点运行在一起,需要至少3个控制平面节点,Etcd也跟着有3个副本。优点是部署简单,缺点是Etcd和控制平面竞争资源。
- 外部Etcd:Etcd单独部署在不同机器上,控制平面节点再各自运行组件。适合大规模集群,但部署和维护复杂度高。
对于绝大多数中小规模生产环境,堆叠式Etcd足够。但从安装步骤上看,高可用和单节点的差异主要在三处:一是需要一个负载均衡器(可以是云LB也可以是HAProxy)来负载6443端口的API Server流量;二是kubeadm init时要加上--control-plane-endpoint指定LB地址;三是控制平面节点要执行两次join,第一次加集群,第二次用--control-plane标记为控制平面角色。
如果你准备搭多控制平面环境,建议在初始化前就看高可用文档,因为很多参数在初始化时就要定死,后期改起来非常痛苦。
4.2 证书续期与集群升级
kubeadm生成的证书有效期是1年,这个很多人都忽略。到了证书快过期时,kubectl会突然报错,提示证书过期或无法连接。所以在集群搭建完成后,就应该把证书续期机制纳入运维计划。
# 查看证书过期时间 kubeadm certs check-expiration # 手动续期 kubeadm certs renew all续期之后,需要重启控制平面组件,让新证书生效:
sudo systemctl restart kubelet如果是多控制平面节点,需要在每个控制平面节点上执行续期操作,同时更新kubeconfig配置文件:kubeadm init phase kubeconfig或直接用cp /etc/kubernetes/admin.conf ~/.kube/config。
升级集群时也要小心,基本原则是只能一个次要版本一个次要版本地升,不能跳版本。比如从1.27升到1.28,要先看官方文档确认兼容性,然后升级控制平面全部节点,再升级工作节点。升级前一定要备份Etcd,最简单的方式是用Etcd的快照功能:ETCDCTL_API=3 etcdctl snapshot save snapshot.db。
4.3 常见问题排查与速查表
我把实际踩过的高频问题整理成了一张表,方便你遇到问题快速对照:
| 现象 | 可能原因 | 排查命令 | 解决办法 |
|---|---|---|---|
kubectl get nodes显示NotReady | CNI插件未部署或配置网段不一致 | kubectl get pods -n kube-system | 部署正确的CNI,检查Pod网段 |
| 节点一直显示NotReady但CNI正常 | kubelet无法连接API Server | journalctl -u kubelet -f | 检查kubelet配置和证书 |
| CoreDNS Pending | 网络插件尚未就绪 | kubectl describe pod coredns-xxx -n kube-system | 等待CNI Pod Running |
| Pod一直ContainerCreating | containerd镜像拉不下来或配置错误 | crictl ps -a或crictl logs | 检查containerd镜像加速和CRI配置 |
| Service访问不通 | 防火墙或kube-proxy异常 | iptables -L -t nat、kubectl get pods -n kube-system -l k8s-app=kube-proxy | 放行端口或重启kube-proxy |
| kubeadm init失败,报端口占用 | 之前初始化过 | ss -lntp | kubeadm reset清干净 |
| join时报token过期 | 超时或重启集群 | kubeadm token list | 重新生成join命令 |
这张表是基础排查方向,生产环境问题往往更复杂,但至少能解决90%的安装阶段故障。
5. 实操心得与避坑技巧
5.1 我踩过的几个关键坑
第一次装集群,我用的是CentOS 7.9,当时遇到最头疼的问题是containerd的cgroup配置没有改成systemd,导致kubelet报failed to run Kubelet: misconfiguration: kubelet cgroup driver: "systemd" is different from docker cgroup driver: "cgroupfs"。虽然我用的是containerd不是docker,但错误原理一样。只要把containerd的SystemdCgroup改成true,然后重启containerd,问题立刻消失。所以这一步我每次都提前检查。
另一个坑是镜像拉取。不指定--image-repository时,kubeadm会尝试从registry.k8s.io拉镜像,在国内很容易超时。我后来习惯在初始化前先手动执行kubeadm config images pull验证一遍镜像是否能拉取,提前暴露网络问题,免得初始化到一半卡住。
还有一个坑是网络插件版本和K8s版本的匹配。Calico高版本对K8s版本有要求,如果kubeadm版本太老而Calico太新,API字段可能不兼容,导致部署资源时校验失败。所以在选版本时,建议先看Calico release notes,确认它支持的K8s版本范围。
5.2 搭建完成后立刻要做的事
集群Ready之后,别急着跑业务,先做几件收尾工作:
设置上下文默认命名空间,避免每次都要写-n:
kubectl create namespace dev kubectl config set-context --current --namespace=dev开启kubectl的自动补全,这能极大提升日常操作效率:
source <(kubectl completion bash) echo "source <(kubectl completion bash)" >> ~/.bashrc安装Helm。虽然不是必须,但后面部署任何应用,Helm的模板化能力能帮你省很多事。
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash监控组件建议也装上,最简单的就是Metrics Server,它是HPA、kubectl top命令的基础。部署方法:
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yamlMetrics Server启动后,kubectl top nodes就能看到CPU和内存使用了。
还有审计日志。如果你希望集群达到生产标准,开启API Server审计日志很有必要,但这里展开讲就太长了,至少你要知道有这个事,后续再补。
5.3 最后分享一个运维小技巧
等集群跑起来后,我会把初始化时的join命令保存到控制平面的/root/join.txt,同时写好一份简单的README,记录各节点IP、网段、版本号、证书过期时间。这看起来很土,但半年后你再去看集群,绝对会感谢自己当初写了这份文档。版本不记清楚,升级时两眼一抹黑;网段不记清楚,排查网络问题全靠猜。
另外,日常维护时多使用kubectl get events --sort-by='.lastTimestamp'去看集群事件,很多隐性问题在事件里会提前暴露,比如某个节点内存压力、镜像拉取失败、调度失败等。K8s本身有很强的自愈能力,但自愈不代表不记录,你要学会看事件,才能真正掌握集群的实时状态。
安装K8s Cluster这件事,本质上就是按照顺序把每个环节做对:规划、基础环境、容器运行时、初始化、网络、加入节点。只要每一步都理解它的作用和依赖关系,完全不需要背命令,遇到问题也能自己定位。希望这篇详细安装步骤能帮你在一次成功的基础上,真正理解K8s的运行逻辑。