K8s集群搭建与Web服务部署:kubeadm实战全流程指南
2026/9/17 2:37:11 网站建设 项目流程

直接开写。这篇博文围绕"K8s 集群搭建与 Web 服务部署"这个经典实操场景展开,所有内容都限定在技术落地层面。

1. 搭一套集群前,先想清楚这三件事

先说结论:很多人搭 K8s 集群翻车,根本不是执行命令出了问题,而是动手之前没把目标想清楚。我之前带过几个刚接触容器编排的同事,他们一上来就急着找 kubeadm 的初始化命令,结果有的卡在镜像拉取,有的把单节点集群当生产环境用,后面加节点时折腾半天,最后只能推翻重建。

这次我把整个"K8s 集群搭建 + Web 服务部署"的过程完整走了一遍,从服务器规划、kubeadm 初始化、网络插件选型,到把 Web 应用容器化、编写 Deployment 和 Service、再到配置探针和自动伸缩,整套流程有踩坑也有验证,整理出来给正准备上手的人一个可复现的路径。

动手之前,建议先确认三件事:

第一,集群规模到底搞多大?很多人觉得学习嘛,一台机器就够了。我的建议是至少三台节点,一个 master、两个 worker。理由很直接:K8s 的调度器、控制器、etcd 这些核心组件设计上就是为多节点协作服务的,单节点虽然能跑,但你永远体会不到 Pod 在不同节点间调度、Node 宕机后工作负载被重新拉起这些核心场景。如果你是拿它做生产前的验证,三节点是性价比最高的起点。

第二,发行版和版本怎么选?目前社区用得最集中的就是 kubeadm,它把 K8s 各组件的部署过程封装成了标准流程,可控性强,也方便你理解底层原理。至于版本,一定要选当前稳定版本里最新的 patch 版本。别问为什么,问就是 K8s 的 bug 修复非常频繁,选太老的版本等于把已知的坑全踩一遍。

第三,网络方案选哪个?这可能是除了容器运行时之外,集群搭建中最容易出问题的环节。目前社区用得最多的是 Calico 和 Flannel。Flannel 简单,vxlan 模式在大多数网络环境都能跑通,适合入门;Calico 功能更强,支持 NetworkPolicy,性能也好一些,但配置复杂度稍高。如果只部署 Web 服务,不做复杂的网络安全策略,Flannel 完全够用;如果想深入玩策略,直接上 Calico。下面我写的这套流程用的是 Calico,因为它的 BGP 模式在跨节点通信上更稳定。

硬件配置这块,参考我的实测数据:

节点角色CPU内存磁盘操作系统
master2 核4G40GUbuntu 22.04 LTS
worker12 核4G40GUbuntu 22.04 LTS
worker22 核4G40GUbuntu 22.04 LTS

这套配置跑一个 Web 应用加数据库,再用 JMeter 做压测,整体是很稳的。如果你的应用本身很吃资源,建议把 worker 的内存提到 8G,K8s 对内存的需求比 CPU 敏感得多。

2. kubeadm 初始化阶段最容易翻车的几个细节

2.1 基础环境配置:swap、转发和 cgroup 驱动

我见过太多人忽略基础环境配置,直接跳去执行 kubeadm init,然后在各种诡异报错里折腾半天。这部分的两个关键点一定要先处理干净。

第一个是关闭 swap。Kubernetes 从设计上就假设节点上没有 swap,因为一旦允许使用 swap,Pod 的内存配额和节点压力驱逐就失去意义了。执行命令:

# 立即关闭 sudo swapoff -a # 永久关闭:注释掉 /etc/fstab 中 swap 相关的那行 sudo sed -i '/swap/s/^/#/' /etc/fstab

第二个是加载内核模块并配置网络转发。K8s 的 Pod 网络通信依赖 iptables 转发,而 kube-proxy 的很多模式也需要相应内核模块支持。需要让 br_netfilter 模块生效,同时把 IPv4 转发参数打开:

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

这里有个小坑,有些云厂商的安全组或者本机防火墙会拦截 netfilter 相关的流量,如果你发现集群搭建后 Pod 之间网络不通,优先查这两个模块是不是都正常加载了。

2.2 容器运行时:containerd 的配置坑

K8s 从 1.24 版本开始彻底移除了 dockershim,所有节点都必须用实现了 CRI 规范的运行时,比如 containerd、CRI-O。containerd 是目前最主流的选型,它能直接运行 Docker 构建出来的镜像,兼容性也最好。

安装 containerd 很简单,但配置里有个关键点经常让人掉坑:默认的 config.toml 里没有启用 SystemdCgroup。K8s 官方推荐的容器运行时配置,要求 cgroup 驱动设置为 systemd,这是因为 kubelet 本身用 systemd 管理进程,如果容器运行时用 cgroupfs,双驱动会导致资源统计和 Pod 驱逐逻辑出错。

正确的配置方式:

# 生成默认配置 sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml

然后编辑 config.toml,找到 SystemdCgroup 参数,把它改成 true:

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] ... [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] SystemdCgroup = true

如果你在国内网络环境,推荐在这个配置文件里把 sandbox_image 参数同步替换一下,这个后面会专门说。

改完配置后重启 containerd:

sudo systemctl restart containerd

还有一个细节:不要同时安装 Docker 和 containerd。因为 Docker 也内置了 containerd,如果两边版本不一致,会导致 kubelet 连不上正确的 CRI socket,报错信息还不明显,排查起来非常浪费时间。要么只装 containerd,要么只装 Docker(Docker 安装后自带 containerd,直接用它的 CRI socket 即可)。

2.3 kubeadm init 的完整参数与镜像拉取问题

这一步是整个集群搭建的核心。在 master 节点上执行初始化命令,我给的参数是实测可用的:

sudo kubeadm init \ --kubernetes-version=v1.29.2 \ --pod-network-cidr=10.244.0.0/16 \ --apiserver-advertise-address=192.168.1.10

三个参数分别说明一下:

  • --kubernetes-version:指定版本,确保和 kubeadm 的版本一致。
  • --pod-network-cidr:Pod 网段,必须和你后续选择的网络插件相匹配。Flannel 默认用 10.244.0.0/16,Calico 默认也可以用这个网段,但如果你自定义了网段,Calico 配置也要同步改。这里踩过的坑是:有人随手填了个 192.168.0.0/16,结果和宿主机的局域网冲突,Pod 之间通信全乱套。
  • --apiserver-advertise-address:master 节点的内网 IP,记得别绑错网卡。

初始化过程中,kubelet 需要从 gcr.io 拉取一堆核心镜像(kube-apiserver、kube-controller-manager、kube-scheduler、etcd、pause)。如果网络无法直接访问这些镜像源,这一步会卡住很久然后报错。

解决思路是先把镜像改个标签,从能访问的镜像仓库拉下来,再 retag 成 kubeadm 期望的镜像名。用一条脚本就能搞定:

kubeadm config images list --kubernetes-version=v1.29.2

先看看到底需要哪些镜像,然后从国内源拉取。以阿里云为例:

for IMAGE in kube-apiserver:v1.29.2 kube-controller-manager:v1.29.2 kube-scheduler:v1.29.2 kube-proxy:v1.29.2 pause:3.9 etcd:3.5.12-0 coredns:v1.11.1 do sudo ctr -n k8s.io images pull registry.cn-hangzhou.aliyuncs.com/google_containers/$IMAGE sudo ctr -n k8s.io images tag registry.cn-hangzhou.aliyuncs.com/google_containers/$IMAGE registry.k8s.io/$IMAGE done

如果你的 containerd 版本比较新,也可以用ctr -n k8s.io这个命名空间,这个是 containerd 为 CRI 预留的目录,kubelet 默认从这个命名空间拉镜像。

初始化成功后会有一段提示文字,按它给的命令配置 kubeconfig:

mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config

然后保存 worker 节点加入集群的 token 命令,一般长这样:

kubeadm join 192.168.1.10:6443 --token xxx --discovery-token-ca-cert-hash sha256:xxx

注意 token 有效期默认是 24 小时,如果过期了,在 master 上执行kubeadm token create --print-join-command重新生成一条。

2.4 安装 Calico 网络插件的顺序问题

很多人在 kubeadm init 之后,马上执行kubectl get nodes,发现节点还是 NotReady,就慌了。其实这是正常的,集群节点从 NotReady 变成 Ready,依赖网络插件正常工作

Calico 的安装方式很直接:

curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml kubectl apply -f calico.yaml

等待几十秒后,检查 Pod 状态:

kubectl get pods -n kube-system

重点看 calico-node 是否成功运行。如果卡在 ImagePullBackOff,大概率是国内网络拉不了 docker.io 的镜像,需要把 calico.yaml 里的 image 地址改成国内镜像源。

还有个容易忽视的点:Calico 默认把 Pod 网段定为 192.168.0.0/16,但上面 kubeadm init 我用了 10.244.0.0/16,所以需要修改 calico.yaml 里 CALICO_IPV4POOL_CIDR 的配置,让它和 pod-network-cidr 保持一致。这是 flannel 和 calico 最容易因为网段不一致导致跨节点通信失败的经典问题

3. 从一个 Web 镜像到集群内可访问的服务

3.1 为什么用 Deployment 而不是直接跑一个 Pod

网络插件就绪后,集群基础环境就通了。下一步是把 Web 服务部署上去。这里先明确一个设计原则:生产环境下绝不直接创建 Pod,而是通过 Deployment 这类工作负载资源管理 Pod。

原因很简单:Deployment 帮你管好了 Pod 的整个生命周期。比如 Deployment 会保证指定数量的副本始终运行,某个节点挂了,它会自动在其他节点重建 Pod;升级镜像版本时,它可以滚动更新而不中断服务。这些能力对 Web 服务来说几乎是必须的。

这里用 Nginx 作为 Web 服务示例,因为它足够轻量、无论你是前端项目还是反向代理需求,它都是最常出现的身影。先写一个 Deployment 的 YAML:

apiVersion: apps/v1 kind: Deployment metadata: name: web-nginx labels: app: web spec: replicas: 3 selector: matchLabels: app: web template: metadata: labels: app: web spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80 readinessProbe: httpGet: path: /healthz port: 80 initialDelaySeconds: 3 periodSeconds: 5 livenessProbe: httpGet: path: /healthz port: 80 initialDelaySeconds: 10 periodSeconds: 10

创建命令:

kubectl apply -f web-nginx.yaml

这里有三个关键点值得展开:

副本数设置:replicas 设为 3,意味着 3 个 Pod 会被调度到至少 2 个 Worker 节点上(如果节点资源充足,通常分布在 3 个节点)。这样即使某个节点宕机,另外两个节点的 Pod 仍然在服务,再配合 Service 做负载均衡,Web 服务的可用性就立起来了。

readinessProbe 和 livenessProbe 的区别:通俗地解释,readinessProbe 决定这个 Pod 是否应该接收流量。如果检查失败,kubelet 会把 Pod 从 Service 的 Endpoints 里摘掉,请求不会转发给它,但容器不会重启。livenessProbe 则决定要不要重启容器。如果接口彻底卡死,没有任何响应,livenessProbe 检查失败就会强制重启容器,让服务恢复。这两个探针的配合是 K8s 自愈能力的关键,建议写 Web 服务 YAML 时都配好。

为什么探针路径要设计成 /healthz:这是工程上的习惯。让应用提供独立的健康检查接口,和业务接口分开,避免健康检查被业务逻辑污染。比如你有一个接口偶发耗时较长,如果探针走的是它,就可能误判 Pod 不健康。

3.2 Service 实现负载均衡和服务发现

Deployment 管理好了 Pod,但 Pod 的 IP 地址是动态变化的,容器重启后 IP 就换了。这时候需要一个稳定的访问入口,就是 Service。

Service 通过 selector 关联到一组 Pod,然后自动把流量负载均衡到这些 Pod 的 IP 上。对应的 YAML:

apiVersion: v1 kind: Service metadata: name: web-nginx-service spec: selector: app: web ports: - protocol: TCP port: 80 targetPort: 80 type: ClusterIP

Service 的 type 有几种,根据访问场景选择:

Service 类型适用场景访问方式
ClusterIP集群内部访问通过 Service 名或 ClusterIP
NodePort外部访问,适合测试环境通过节点IP:NodePort
LoadBalancer公有云环境通过云负载均衡器
IngressHTTP/HTTPS 域名访问,生产常用通过 Ingress Controller

如果你只是想快速验证服务是否工作,可以先改成 NodePort:

kubectl edit service web-nginx-service # 把 type 改成 NodePort

然后查看分配到的高端口:

kubectl get svc web-nginx-service

浏览器访问任意节点IP:30080就能看到 Nginx 的欢迎页。

但说实话,NodePort 只适合临时验证。生产环境对外暴露 HTTP 服务,几乎都是走 Ingress。不仅是功能更丰富(域名路由、TLS 终止、限流),而且不用给每个服务单独分配一个端口,维护成本低很多。

下面第三节里我用 Ingress 做了一个完整的对外访问链路说明。

4. Ingress 对外访问链路:从域名到 Pod 的完整路径

4.1 为什么集群内通信不代表对外可用

Service 的 ClusterIP 只能在集群内部访问,外部用户没法直接用这个 IP。NodePort 虽然开了一个端口给外部,但它有几个天然缺陷:NodePort 范围有限(默认 30000-32767)、端口和服务的绑定关系靠人工记忆、无法实现基于域名的路由。

这时候 Ingress 的价值就出来了。它工作在七层,可以根据域名和路径把请求路由到不同的 Service。比如同一个 IP 下,api.example.com路由到后端 API 服务,www.example.com路由到前端页面服务。

注意一个重要概念:Ingress 本身不处理流量,它只是一堆路由规则。真正干活的,是 Ingress Controller,常见的有 Nginx Ingress Controller、Traefik、HAProxy Ingress。Controller 会读取 Ingress 规则,然后动态更新 Nginx 的配置,实现流量转发。

4.2 Nginx Ingress Controller 的部署与配置

安装 Nginx Ingress Controller,官方提供 Helm 包,这里直接用 manifests 方式,方便理解组件构成:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.1/deploy/static/provider/cloud/deploy.yaml

部署完成后,查看 Pod:

kubectl get pods -n ingress-nginx

然后写一个 Ingress 资源:

apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: web-ingress spec: ingressClassName: nginx rules: - host: web.example.com http: paths: - path: / pathType: Prefix backend: service: name: web-nginx-service port: number: 80

创建后,查看 Ingress 是否生效:

kubectl get ingress

注意,web.example.com是个示例域名,实际部署时改成你自己的域名,同时在 DNS 解析里把它指向 Nginx Ingress Controller 所在节点的 IP(如果 controller 部署为 LoadBalancer,则指向负载均衡器 IP)。

配置完成后,本地想快速验证,可以临时改下 hosts 文件:

echo "192.168.1.20 web.example.com" >> /etc/hosts

然后访问http://web.example.com,流量路径是:

浏览器 -> DNS解析 -> 节点IP -> Nginx Ingress Controller -> Service -> Pod

这个链路中,最容易出问题的是 Ingress Controller 的 Pod 访问方式。如果 controller 部署方式是 NodePort,你需要访问 NodePort 对应端口;如果 LoadBalancer,则需要一个外部负载均衡器分配 IP。云环境一般用 LoadBalancer,自建集群为了方便可以改成 NodePort 或者直接 hostNetwork。

4.3 实测流量转发和常见故障排查

我用 curl 实测了一下:

curl -I http://web.example.com

返回 200 说明链路通。如果返回 404,先查 Ingress Controller 的日志:

kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx

常见的排查思路是:先确认 Pod 正常,再确认 Service 的 Endpoints 里有 Pod 的 IP,最后确认 Ingress 规则的 host 和 path 匹配。从底层往上层查,效率最高。

另外一个容易踩的坑是Ingress Controller 部署在 master 节点且有 Taint,如果集群只有 master 节点能部署 controller,需要给节点去掉 Taint 或者给 controller 单独加 Toleration。前几年有过版本需要额外配置,新版 Ingress Controller 默认加了容忍度,至少不会卡在这一步。

5. 压测之前必须做好的稳定性验证

5.1 探针和资源配置:让 Pod 更抗造

集群部署好了,Web 服务也通了,但先别看压测工具开跑,有几件基础工作一定要先做,否则压测一上去,Pod 就会被 OOMKilled 或者频繁重启,你根本分不清是应用问题还是平台问题。

第一,给容器设置资源请求和限制。这是 K8s 使用中最重要的配置之一,没有资源限制的容器,可能把节点内存吃满,影响同节点其他 Pod。给 Nginx 容器加配置:

resources: requests: memory: "64Mi" cpu: "100m" limits: memory: "128Mi" cpu: "200m"

这里要理解 requests 和 limits 的区别。requests 是调度时的依据,告诉调度器这个 Pod 至少需要这么多资源;limits 是运行时上限,超过这个上限,CPU 会被限流,内存如果超了直接 OOMKilled。

在写 Nginx 部署时,如果你没有配置 resources,Pod 的内存占用会一直增长,节点出现压力时最先被驱逐的就是这种"无限资源"的 Pod。

第二,加上优雅终止时间。默认情况下,kubelet 在 Pod 需要终止时,会先发送 SIGTERM 信号,等 30 秒(terminationGracePeriodSeconds),如果没退出再发 SIGKILL。对于 Web 服务,30 秒通常够完成存量请求的处理。如果你的应用本身有优雅停机逻辑(比如主动关闭数据库连接池),可以适当缩短这个时间,但一般保持默认即可。

Nginx 的优雅停机其实天然支持:当 kubelet 发来 SIGTERM,它会停止接收新连接,等现有的连接处理完再退出。所以 Nginx 容器基本不会出现请求中断的问题。

第三,检查 Pod 是否跨节点分布。用命令看下:

kubectl get pods -o wide

如果发现 3 个 Pod 都挤在同一个节点,说明调度策略需要调整。比如加上podAntiAffinity,让相同应用的 Pod 尽量分散到不同节点,避免单点故障:

affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app: web topologyKey: kubernetes.io/hostname

5.2 手动故障演练:node 宕机和 Pod 被杀

稳定性验证不只是看看配置,手动故障演练是我的习惯。在压测之前,做两个实验:

实验一:杀掉一个 Web Pod,看 Deployment 是否自动恢复。

kubectl delete pod -l app=web kubectl get pods -w

可以看到旧的 Pod Terminating,新的 Pod Pending -> ContainerCreating -> Running,整个过程大概几秒到几十秒。期间因为有 3 个副本,剩余 2 个仍然在服务,外部访问不受影响。

实验二:直接停掉一个 worker 节点。在节点上执行sudo shutdown -h now,等一两分钟后在 master 上执行:

kubectl get nodes kubectl get pods -o wide

K8s 有一个判定节点失联的时间窗口,默认 40 秒左右才把节点标记为 NotReady,然后再过一段时间会将其上的 Pod 标记为终止并重新调度到可用节点。这个时间平台上是自动处理的,但在自建集群,如果你希望更快地触发故障转移,可以调低 Node controller 的判定参数,比如--node-monitor-grace-period从默认 40 秒改成 20 秒,不过这属于集群级参数修改,不建议一开始就动。

我实测过:杀掉一个 worker 节点后,部署在它上面的 Pod 大概在 1-2 分钟内,就被重新调度到存活节点上运行。这期间服务因为有其他副本在支撑,可用性没有中断。对 Web 服务来说,这个自愈能力就是 K8s 能承载高并发压测的底气。

5.3 水平自动伸缩:高并发下的扩容机制

压测必然会带来流量高峰,光靠固定副本数抗压,要么浪费资源,要么扛不住。这时候就轮到 HPA(HorizontalPodAutoscaler)登场。

HPA 根据资源指标动态调整副本数。比如配置一个基于 CPU 利用率的伸缩策略:

kubectl autoscale deployment web-nginx --cpu-percent=50 --min=3 --max=10

这条命令的意思是:当 Pod 的平均 CPU 使用率超过 50% 时,自动增加副本数,最多增加到 10 个;低于 50% 时,逐渐缩回,最少保持 3 个。

但要注意,HPA 要工作,必须要有 metrics-server 提供指标数据。如果没装,执行kubectl get hpa会看到failed to get cpu utilization之类的错误。安装 metrics-server:

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

有几个老版本会在 TLS 证书校验上出问题,新版本基本都处理好了。安装完成后等一两分钟,执行:

kubectl top nodes kubectl top pods

能看到 CPU 和内存数据,说明 metrics-server 正常了。

实测 HPA 的效果:用 JMeter 发一波压力请求,CPU 飙升超过 50% 后,执行kubectl get pods -w,能看到 Pod 数量逐渐增加,从 3 个升到 6 个、8 个,最终稳定在 10 个以内。压力下去后,Pod 数量又会慢慢缩回 3 个。

有两点值得注意:

  • HPA 的伸缩有冷却时间,防止指标抖动导致副本数频繁变化,所以不会立刻缩容,一般默认 5 分钟左右。压测时不要看到 CPU 降了就以为 HPA 失效。
  • 如果压测脚本本身的并发量足够大,HPA 扩容速度可能赶不上流量增长,这时需要结合应用的实际承载能力做预热或者设置最小副本数,不能完全依赖自动伸缩。

6. 这趟实操下来,我对高可用部署的三点体会

集群跑了一轮,Web 服务也承载了压测流量,这套部署的稳定性得到了验证。最后记录一下这段时间实操下来最深的三个体会。

第一,K8s 的复杂度是分散的,但最有价值的恰恰是这些分散的细节。从系统参数、容器运行时配置、网络插件选型、镜像加速到探针、资源限制、HPA,每个环节单独看都不难,但串起来就是一个完整的可靠性链条。很多人觉得 K8s 难,难的不是某个技术点,而是这些点之间的关联。所以我一直建议,就算有云厂商的一键集群,也要自己完整用 kubeadm 搭一次,把底层机制跑通。

第二,Web 服务部署应该"先画架构图,再写 YAML"。我当时在部署时先明确了访问链路:用户 -> Ingress -> Service -> Pod -> 容器,然后才动手写文件。这样每个资源的定位就很清晰,不会一个 YAML 里堆砌大量字段,出问题时定位也好找。对于服务化的应用,这个思路尤其重要。

第三,压测之前,一定要先做故障演练。我在部署后模拟过节点宕机、Pod 被杀、网络波动等情况,看着 K8s 自动恢复,心里才有底。如果没有这个步骤,直接在压测时遇到 Pod 异常,很难判断是平台不稳定还是应用扛不住压力。实际压测下来,这套集群在高并发下表现稳定,Web 服务通过 HPA 自动扩容扛住了流量冲击,整个链路没有出现连接中断或大量超时。

如果你正准备做类似的集群部署,可以按这套流程走一遍。中间遇到任何问题,优先查 kubelet 和 containerd 的日志,大部分坑都能在日志里找到答案。祝你一次搭通。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询