拓冰建站拓冰建站
首页 / 资讯中心 / 正文

从安装到生产部署:Kubernetes集群架构与运维实战

这几年我没少跟 Kubernetes 打交道从最初拿 kubeadm 搭个测试集群到后来一步一步把它推到生产环境踩过的坑基本能写成小册子。很多朋友问我Kubernetes 集群运维到底要干哪些事是不是照着官网跑一遍 kubeadm init 就算安装完成答案显然不是。安装只是起点生产部署背后是节点规划、高可用、网络、存储、可观测性和备份恢复这一整套事情。这篇文章适合准备自建集群的运维、正在从 Docker Compose 往 K8s 迁移的团队以及想在生产环境跑起有状态服务和 AI 推理服务的同学。我会把自己在 K8s 集群从安装到生产部署过程中的判断标准和实操命令完整写出来尽量把每个选择背后的原因也讲清楚。1. 先算清楚账Kubernetes 生产集群的资源与架构规划1.1 什么时候该从 Docker Compose 切换到 Kubernetes关于热搜里经常出现的“redis docker compose 生产环境部署”这类问题我不会一刀切说 Docker Compose 不能用反而在几十个容器以内、单机或少量节点、业务没有复杂调度需求的时候Compose 真香。它心智负担低一条docker compose up -d就能拉起 Redis、Nginx 和业务后端日志、网络、卷挂载都够用。但一旦你开始考虑多个后端实例、数据库主从切换、滚动发布、按流量扩容Compose 的短板就很明显没有内置服务发现、没有健康检查驱动的自动拉起、没有跨节点的网络策略也没有驱逐和重新调度机制。Kubernetes 把这些问题抽象成一套声明式 APICluster、Node、Pod 层层抽象。你告诉它期望状态是 3 个副本它会持续保证这个状态节点挂了Pod 会被调度到其他节点。这正是生产部署最需要的稳定性。所以判断标准很简单如果项目三五年都不会有超过一两台服务器的体量继续用 Compose 没问题如果业务要长成多副本、多环境、多集群尽早切 K8s。对比项Docker ComposeKubernetes 自建云托管 Kubernetes上手成本低中高中服务发现依赖固定容器名内置 Service/DNS内置 Service/DNS滚动发布手动或借助工具原生策略原生策略故障自愈基本没有有调度和重启机制有调度和重启机制运维成本低集群组件都要自己管控制面由云厂商负责1.2 节点规格和网段怎么设计生产集群最小规模我建议 3 台 Master 加 2 台 Worker。3 台 Master 不是因为有钱没处花而是 etcd 需要多数派才能正常工作quorum 是(n1)/23 台节点能容忍 1 台故障2 台节点一旦坏 1 台剩下 1 台没法形成多数派整个集群的写入和变更都会不可用。etcd 对磁盘 IO 和网络延迟非常敏感所以 Master 的数据盘最好用独立 SSD不要和系统盘混用也不要在 Master 上跑一堆业务 Pod。网段规划是很多人装完集群才发现的问题。三个网段不要重叠节点网段、Pod 网段、Service 网段。例如节点网段是192.168.1.0/24Pod CIDR 可以定成10.244.0.0/16Service CIDR 可以定成10.96.0.0/12。Pod 网段和 Service 网段是虚拟网络由 CNI 插件和 kube-proxy 分配地址如果和现有生产网段冲突后续路由、容器与外部互访都会出问题。10.244.0.0/16有 65536 个地址每个节点按/24分配能拿到 256 个地址足够支撑 256 个节点中小规模集群都非常宽裕。硬件上Master 建议 4C8G 起步etcd 盘至少 100GB SSDWorker 根据业务负载定但也不要无限压榨Kubernetes 调度需要给系统预留一部分资源比如默认给节点留 5% 的 CPU 和内存做系统保护。节点越多API Server 和 etcd 的并发压力越大控制面节点规格要比单机环境多留一些余量。2. kubeadm 安装与高可用控制面落地2.1 版本、运行时和镜像源的坑Kubernetes 的版本选择不是越新越好。官方通常维护最近的三个 minor 版本但生产环境还要考虑 Operator、CSI 驱动、Ingress Controller 的兼容性。我一般以应用依赖为准选择一个已经发布半年以上、社区案例相对多的稳定版本避免一上来就踩新版本的大坑。另外kubeadm对版本偏差有严格要求kubelet、kubeadm、kubectl 的版本要尽量统一控制面组件版本差不能超过一个小版本否则 join 节点时会提示版本不匹配。容器运行时我首选 containerd。它比 Docker Runtime 少一层 daemon占用资源更少也是 Kubernetes 现在主推的 CRI 运行时。安装 containerd 后有一个必改项配置文件里的SystemdCgroup要改成true。默认有些系统装完是false而 kubelet 默认使用 systemd cgroup driver两边不一致会导致 Pod 被反复重启。改完配置后systemctl restart containerd再用crictl info确认一下。镜像源方面可以用kubeadm config images list查看需要的镜像列表国内环境记得给 kubeadm 指定合适的imageRepository或者提前在每台节点上把镜像拉好、导出离线包导入不然初始化时容易卡在拉镜像这一步。2.2 控制面初始化参数逐一说清楚初始化前先把内核模块和系统参数准备好这一步很多人会跳过后面 CNI 网络插件起不来才回头排查。cat EOF /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay modprobe br_netfilter cat EOF /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables 1 net.ipv4.ip_forward 1 EOF sysctl --system然后执行初始化。注意参数不是随便填的kubeadm init \ --kubernetes-versionv1.29.0 \ --control-plane-endpointk8s-master-vip:6443 \ --pod-network-cidr10.244.0.0/16 \ --service-cidr10.96.0.0/12 \ --upload-certs--control-plane-endpoint必须填负载均衡地址或 VIP而不是单个节点 IP这样后面其他 Master join 时才能共用同一个入口。--pod-network-cidr和--service-cidr要和之前规划一致这两个网段初始化后改起来很麻烦基本只能重装集群。--upload-certs用于多 Master 场景它会自动把证书上传并分发方便后续控制面节点加入。初始化成功后kubeadm 会输出加入集群的命令。Worker 节点用 token 加入Master 节点还要追加--control-plane --certificate-key。这些 token 默认有有效期长时间不加入就要用kubeadm token create --print-join-command重新生成。网络插件我一般优先考虑 Calico 或 Cilium。Calico 兼容性好老内核也能跑Cilium 基于 eBPF网络性能和可观测性更强但要求内核版本相对较新。如果你的节点内核在 5.10 以上且团队愿意接触新东西Cilium 是很顺手的选择。无论选哪个CNI 的 Pod 网段必须和 kubeadm init 时的--pod-network-cidr保持一致否则节点会一直显示 NotReady。2.3 高可用不是“多复制几台 Master”很多人以为把 kubeadm init 在三台机器上各跑一遍就是高可用这是不对的。控制面里的 API Server 需要负载均衡Controller Manager 和 Scheduler 自己有选主机制多实例没问题但所有请求都打到 API Server不能只指向单台机器。自建集群最常用的方案是 keepalived haproxy 或者 kube-vip目的都是提供一个虚拟 IP同一时间只有一台节点持有漂移时自动切换。kube-vip 相对简单它本身也跑在 Kubernetes 里但有个先有鸡还是先有蛋的问题第一台 Master 初始化时必须先用一个临时 endpoint之后再部署 kube-vip 接管 VIP最后再让其他 Master 通过 VIP join。生产环境实际操作时我建议先把第一台控制面节点稳定跑起来确认 etcd 健康再部署 kube-vip最后逐台加入剩余 Master。还有个架构选择堆叠 etcd 还是外置 etcd。堆叠 etcd 是最常见的方案控制面节点上同时跑 etcd运维简单中小集群完全够用。外置 etcd 把 etcd 独立部署适合上千节点或需要 etcd 单独扩缩容的大型集群但需要额外维护一套 etcd 集群复杂度明显上升。大多数团队没必要一上来就外置先把堆叠 etcd 的磁盘性能和 Master 负载管好。3. 生产部署前必做的几层加固3.1 命名空间、配额和资源限制生产环境不能所有资源都堆在 default 命名空间里。我习惯按业务、环境、团队划分 Namespace比如biz、ai、middleware再配合 RBAC 控制谁能操作哪个 Namespace。只分命名空间还不够必须配置 ResourceQuota 和 LimitRange否则一个死循环的 Pod 可能把节点内存吃满导致节点 OOM 甚至整机 NotReady。apiVersion: v1 kind: ResourceQuota metadata: name: biz-quota namespace: biz spec: hard: requests.cpu: 8 requests.memory: 16Gi limits.cpu: 16 limits.memory: 32Gi persistentvolumeclaims: 10 --- apiVersion: v1 kind: LimitRange metadata: name: biz-limit namespace: biz spec: limits: - default: cpu: 500m memory: 512Mi defaultRequest: cpu: 100m memory: 128Mi type: Container上面这份 YAML 给biz命名空间设置了一个总配额容器没写资源限制时会自动套用 LimitRange 的默认值。我踩过的坑是开发环境有人把 requests 设得很大limits 很小导致 CPU 频繁被限流业务响应变慢。记住 Requests 和 Limits 是两回事Requests 是调度依据Limits 是运行上限CPU 是可压缩资源超过了会限流但不会杀进程内存是不可压缩资源超过了会被内核 OOM 杀掉。数据库这类内存敏感的服务limits 不要压得太死留出 Page Cache 和连接池的缓冲。3.2 存储与 Ingress别等业务上线再补救存储是 K8s 生产化里最容易拖后腿的一块。StatefulSet 里的数据必须用 PVCemptyDir只适合临时目录。自建环境最常见的做法是 NFS配置简单但性能和权限问题不少特别是数据库这类高 IO 场景NFS 很容易变成瓶颈。小团队如果短期内不想投入专门的存储团队可以先用 Longhorn 或云厂商的云盘 CSI稳定之后再考虑 Rook-Ceph 这类分布式存储。我自己一般按这个原则选有持久化需求、性能要求高、扩容频繁的服务优先给独立存储日志和临时文件丢得起就干脆不挂 PVC。业务入口我建议统一走 Ingress而不是每个服务都开 NodePort。NodePort 端口有限暴露面大也没有七层路由能力。Ingress Controller 生产环境至少要保证多副本最好落到不同节点上否则它所在节点一挂整个业务入口全断。证书管理我推荐 cert-manager可以自动签发和续期证书同时别忘了配置证书过期告警证书过期是生产事故里非常低级但常见的一类。3.3 可观测性从“能跑”到“能救”监控、日志、告警不是可选项。生产环境最少也要保证四个层面有数据节点资源、Kubernetes 对象状态、业务指标、日志。最省事的组合是kube-prometheus-stack它把 Prometheus、Grafana、Alertmanager、node-exporter、kube-state-metrics 打包在一起安装后基本能覆盖集群和节点的监控。告警规则不要一开始就追求多先把真正能救命的几条配上节点 NotReady、Pod 频繁重启、PVC 使用率超过 80%、证书 30 天内过期、etcd leader 变化频繁。日志侧小规模用 Loki 比较省资源大规模用 ES 或云日志服务都行。排障时先看kubectl get pod -o wide再看kubectl describe pod pod -n namespace里 Events最后看业务日志不要一上来就重启否则很多问题会被掩盖。3.4 备份策略etcd、PV 与恢复演练etcd 是整个集群的“控制面数据库”所有资源对象都存在里面。如果 etcd 数据损坏就算 Pod 还活着你可能也无法通过 kubectl 管理它们。所以 etcd 快照备份是必须做的。kubeadm 部署的 etcd 是静态 Pod备份时直接进入 etcd 容器或用本机 etcdctl 都可以ETCDCTL_API3 etcdctl --endpointshttps://127.0.0.1:2379 \ --cacert/etc/kubernetes/pki/etcd/ca.crt \ --cert/etc/kubernetes/pki/etcd/server.crt \ --key/etc/kubernetes/pki/etcd/server.key \ snapshot save /backup/etcd-snapshot-$(date %Y%m%d%H%M).db我建议至少 6 小时做一次 etcd 快照保留 7 到 30 天并且把快照文件放到独立存储不能只放在本地磁盘。更完整的备份还要覆盖 PVC 数据Velero 可以同时备份 K8s 资源对象和 PV 数据恢复时按 Namespace 过滤很方便。但备份只是第一步定期做恢复演练更重要。没有演练过的备份真到故障那天大概率会发现证书路径不对、存储插件缺失、快照文件损坏等一堆问题。4. 两类典型生产负载的部署实录4.1 有状态应用Redis 从 Compose 到 K8s 的迁移如果你之前用 Docker Compose 在单机跑 Redis现在要迁到 K8s不建议直接用 Deployment 包一个 Redis 容器。单实例 Redis 用 Deployment 加 PVC 问题不大但凡是主从复制、哨兵、Cluster 这类多副本架构就需要 StatefulSet。StatefulSet 会为每个副本生成稳定的 Pod 名称和独立 PVC配合 headless Service能保证每个 Redis 实例有固定的 DNS 地址这对主从识别和故障切换非常重要。apiVersion: apps/v1 kind: StatefulSet metadata: name: redis namespace: biz spec: serviceName: redis-headless replicas: 3 selector: matchLabels: app: redis template: metadata: labels: app: redis spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: topologyKey: kubernetes.io/hostname labelSelector: matchLabels: app: redis containers: - name: redis image: redis:7.0.12 ports: - containerPort: 6379 resources: requests: cpu: 500m memory: 1Gi limits: memory: 2Gi volumeMounts: - name: redis-data mountPath: /data volumeClaimTemplates: - metadata: name: redis-data spec: accessModes: [ ReadWriteOnce ] resources: requests: storage: 20Gi注意podAntiAffinity让 Redis 的不同副本尽量分散到不同节点避免一台宿主机挂了把整个缓存层带走。volumeClaimTemplates会为每个副本自动创建 PVC这是 Deployment 做不到的。密码一定通过 Secret 注入环境变量或配置文件不要写死在镜像里。如果要用 Redis Cluster建议直接用成熟的 Operator 或 Helm Chart纯手写 StatefulSet 还要处理 Cluster 初始化、槽位分配、故障转移运维成本很高。生产环境 Redis 的持久化策略也要想清楚AOF 和 RDB 的取舍取决于你能接受多少数据丢失而不是追求默认配置。4.2 GPU 调度与模型加载大模型推理能否稳定跑在 K8s 上生产环境大模型部署是最近特别热的话题但真正落地时核心难点不是模型推理框架而是 GPU 节点怎么接入 K8s 调度。第一步是在 GPU 节点装 NVIDIA device plugin它负责把nvidia.com/gpu作为一种扩展资源上报给 Kubelet。装好后给 GPU 节点打个标签比如gputrue再加一个污点nvidia/gputrue:NoSchedule这样普通 CPU 业务不会被调度到昂贵的 GPU 机器上。推理服务的 Deployment 里用 nodeSelector 选中 GPU 节点同时写 tolerations 通过污点。apiVersion: apps/v1 kind: Deployment metadata: name: llama-inference namespace: ai spec: replicas: 2 selector: matchLabels: app: llama template: metadata: labels: app: llama spec: nodeSelector: gpu: true tolerations: - key: nvidia/gpu operator: Exists effect: NoSchedule containers: - name: inference image: registry.example.com/llama-service:v1 resources: requests: nvidia.com/gpu: 1 limits: nvidia.com/gpu: 1GPU 是扩展资源requests 和 limits 必须一致不能只写 requests。模型文件通常很大直接打进镜像会让镜像体积达到几十 GB拉取和发布都很难受。更合理的做法是把模型文件放在独立 PVC 上容器启动时直接挂载或者用 InitContainer 先从对象存储把模型下载到 PVC主容器等模型就绪后再启动。这样发布代码版本时不会重新拉几十 GB 的模型层。水平扩容不要只盯着 CPUGPU 推理场景更关键的是显存利用率和推理 QPS。可以通过 Prometheus Adapter 暴露自定义指标再配置 HPA 按 GPU 利用率伸缩。但要注意 GPU 节点扩缩容是分钟级甚至更久如果底层资源没有提前预热光靠 HPA 扛不住流量突刺。生产上更稳妥的做法是容量规划前置模型上线前先压测出单卡 QPS再反推节点规模和副本数。4.3 发布、滚动更新与回滚的节奏生产环境发布Deployment 默认的 RollingUpdate 参数不一定好使。默认maxSurge和maxUnavailable都是 25%如果只有 2 个副本理论上是允许 1 个旧的先停掉的这对在线业务来说可能造成少量请求失败。我通常配置maxUnavailable: 0、maxSurge: 1保证发布期间旧版本一直有副本在服务新 Pod 先起来再把旧 Pod 换成新的。如果节点资源紧张maxSurge 会造成资源峰值要提前评估。PDB 是很多人会忽略的配置。节点维护、集群升级时PDB 能保证你的服务不会因为节点驱逐而一个副本都不剩。apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: biz-pdb spec: minAvailable: 2 selector: matchLabels: app: business健康检查的配置也直接影响发布稳定性。readinessProbe决定 Pod 是否接入 Service 流量livenessProbe决定容器要不要重启。两个探针不要完全一样initialDelaySeconds 也不要设太短。我曾经把一个 Java 应用启动就要 30 秒但 readiness 探针延迟设成了 5 秒结果 Pod 一直在被杀重启发布永远不成功。合理的做法是 readiness 的 initialDelay 短一点比如 5 秒周期 10 秒liveness 的 initialDelay 长一点比如 20 秒以上周期 20 秒给业务留足启动时间。回滚用kubectl rollout undo很快但业务回滚不等于数据回滚。如果新版本改了数据库结构或产生不兼容数据应用回滚后可能还要执行反向迁移。所以发布前必须有数据库兼容性检查镜像 tag 也要用不可变标识比如 commit SHA不要用 latest。否则你很难判断线上跑的是哪个版本回滚也就无从谈起。5. 安装部署与运维中的高频问题排查5.1 装完集群却跑不起来的常见原因kubeadm init 后 kubelet 起不来十有八九是 swap 没关或者 cgroup driver 不一致。虽然某些新版本对 swap 的支持有所调整但我在生产环境仍然坚持关闭 swapswapoff -a并注释掉/etc/fstab里对应的 swap 行。cgroup driver 的问题前面提过kubelet 使用的 systemd driver 要和 containerd 的SystemdCgroup配置一致不一致时节点会反复报kubelet is misconfigured或容器状态异常。CNI 插件 Pod 一直 CrashLoopBackOff常见原因是内核模块没加载或者 IP 转发没开启。先执行sysctl net.ipv4.ip_forward确认返回 1再检查br_netfilter有没有加载成功。Cilium 对内核版本有要求老内核上经常出现编译 eBPF 程序失败这时候换 Calico 是最快的选择不用纠结“高级”功能。Service 访问不通也是高频问题。按顺序排查先看kubectl get endpoints有没有 Endpoint没有就说明 Service 的 selector 和后端 Pod 标签对不上再看 kube-proxy 的 Pod 是否正常ipvsadm -Ln或iptables -L里有没有对应规则最后检查节点防火墙和安全组有没有放行 ServiceNodePort 或负载均衡端口。很多时候问题不在 K8s而在下一跳网络。5.2 故障速查表现象、排查命令和解决方向现象可能原因第一条排查命令解决方向Node 显示 NotReadykubelet 异常、资源不足、CNI 故障systemctl status kubelet/journalctl -u kubelet看日志检查磁盘和内存重启 kubeletPod 一直 Pending资源不足、污点、PVC 未绑定kubectl describe pod pod -n ns看 Events调整请求或调度约束Pod 频繁 CrashLoopBackOff启动命令失败、探针太激进kubectl logs pod --previous看退出日志修复启动依赖放宽探针镜像拉取一直是 ErrImagePull仓库不可达、tag 不存在、凭证失效crictl images/kubectl describe pod配置 imagePullSecret 或镜像仓库代理PVC 一直 PendingStorageClass 不存在或底层存储故障kubectl describe pvc创建 StorageClass检查 CSI 插件Ingress 不通ingress-controller 没起来、后端 selector 不匹配kubectl get ingress -A/kubectl logs -n ingress-nginx检查 IngressClass 和 Endpointetcd 选举频繁磁盘 IO 高、网络抖动、Master 过载查看 etcd 指标etcd_server_leader_changes_seen_total优化存储 IO减少 Master 负载排查网络建好集群后我习惯把所有节点的内核参数、挂载点、containerd 配置模板化出一台新机器直接执行同一套脚本避免每次手工操作引入差异。排障时优先用kubectl describe和journalctl看现场别凭感觉重启组件很多隐蔽问题重启后反而更难查。最后说句实在话我始终把“可恢复”放在“高性能”前面。Kubernetes 带来的不是复杂本身而是把复杂变成可描述、可重现、可恢复的能力。先把这条主线维护好从安装到生产部署自然就顺了。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门