基于Kubernetes的云操作系统:一键部署与Pod服务发布实战
Kubernetes 这门手艺学的人多敢从零手搓一套生产可用集群的人不多。我第一次装集群是三台虚拟机、一张 A4 纸写满证书路径折腾到凌晨两点才发现 kubelet 和容器运行时的 cgroup driver 对不上。后来接触到云操作系统这类产品形态才意识到很多痛苦其实是被重复造轮子制造出来的——它把 Kubernetes 控制面、CNI 网络、CSI 存储、Dashboard、镜像仓库、应用商店、账号体系打包成一个可离线分发的安装包一条命令拉起整个平台然后你面对的就不再是怎么装,而是怎么用。这篇内容想聊的就是这个一套基于 Kubernetes 构建的云操作系统它的内部分层是怎样的一键部署背后到底做了哪些事装起来之后怎么通过 Dashboard 把第一个 Pod 跑起来并发布成可访问的服务以及我在实际落地里踩过的那些坑。适合两类人看一类是刚学完 Kubernetes 核心概念、想找个能动手的环境练手的朋友另一类是团队里被搭集群这件事卡住、想快速拿到一个可交付平台的运维或后端同学。基础概念我会点到但不会写成教科书重点放在为什么这么设计和你动手时该注意什么。1. 云操作系统到底解决什么问题1.1 从裸装集群到开箱即用的痛点清单手动部署一套能用的 Kubernetes麻烦从来不在kubeadm init那一行命令上。真正的成本藏在前后两头前面是环境准备主机名、时间同步、内核参数、swap、防火墙规则、容器运行时每一项漏掉都可能在半小时后以一句语焉不详的报错找上门后面是平台配套集群起来了但 Dashboard 没装、Ingress 没有、存储类没有默认供应者、监控日志一律空白业务方问我的应用怎么对外访问你只能说再等等。把这些拆开看一套最小可用的平台大概包含七八个组件控制面apiserver、controller-manager、scheduler、etcd、容器运行时、CNI 网络插件、CSI 或本地存储供应者、CoreDNS、Metrics Server、Ingress Controller、Dashboard。每个组件都有自己的版本兼容矩阵和配置项任何一个选错版本轻则功能缺失重则集群起不来。云操作系统的价值就在于它把这套组合拳的版本匹配和默认参数替你定死了你拿到的是一个经过验证的套餐而不是十份各自的安装文档。我用过一个很形象的比喻裸装 Kubernetes 像是买散件攒电脑便宜、自由但你要自己确认内存频率和主板是否兼容云操作系统则是品牌整机开箱即用代价是某些角落的定制空间被压缩了。对绝大多数中小团队来说这个交换是划算的。1.2 云操作系统的分层架构与核心组件把这类产品拆开看从下往上大致是四层理解这个分层对后面排查问题特别有帮助因为故障现象往往能直接对应到某一层。第一层是基础设施层包括操作系统内核、容器运行时containerd 是现在的默认选择、kubelet 和 kube-proxy。这一层的问题通常表现为节点 NotReady、Pod 起不来、网络不通。第二层是编排层也就是 Kubernetes 控制面本身apiserver 负责所有请求入口etcd 存全量状态调度器决定 Pod 落到哪个节点。这一层的故障往往是整个集群失联比如 apiserver 的 6443 端口被占、etcd 磁盘 IO 打满。第三层是平台服务层网络插件、存储供应者、DNS、证书管理、镜像仓库都在这里。它决定了你的 Pod 能不能互相通信、PVC 能不能绑定成功。第四层是用户界面层Dashboard、账号权限、应用商店、命令行工具都归这一层也是普通用户唯一直接接触的部分。注意分层模型不是学术摆设。当 Dashboard 打不开时你要能判断是第四层的 Ingress 配置错了还是第三层的 Service 没通还是第二层的 apiserver 已经挂了。按层排查比盲目重启高效得多。1.3 自建集群与云操作系统的横向对比很多人纠结的点是我到底该自己搭还是用一个打包好的平台。下面这张表是我根据几个实际项目整理的对比可以直接对号入座。对比维度手动自建集群基于 Kubernetes 的云操作系统首次可用时间2 到 5 天取决于踩坑数量30 分钟到 2 小时组件版本兼容需要自行查兼容矩阵安装包内已锁定离线环境支持需自建镜像仓库并逐个推镜像通常内置离线镜像包默认可观测性全部需要自己补装多数自带监控面板定制灵活度极高任何参数可改中高主流发行版都留了扩展口升级维护手动逐节点滚动一般提供一键升级命令适合场景有专职平台团队、有特殊需求中小团队、快速交付、学习练手我个人的判断标准很简单如果团队里没人愿意长期维护这套集群那就别自己搭。Kubernetes 的运维成本不在安装而在日常的版本升级、证书轮换和故障响应这三件事才是真正吃人力的地方。2. 一键部署的底层逻辑拆解2.1 离线镜像打包与安装器的设计考量一键这两个字听起来很玄实际原理并不复杂。安装包本质上是一个巨大的压缩文件里面装着四类东西容器运行时和 kubelet 的二进制文件、所有系统组件的容器镜像以 tar 或 OCI 格式存放、Helm Chart 或 YAML 模板、以及一个负责渲染和执行这些模板的安装器程序。执行安装的时候安装器做的事情是有固定顺序的先把二进制定到目标路径并配置 systemd 服务然后把镜像加载进本地容器运行时的镜像仓库接着在第一个节点上执行集群初始化生成证书和 kubeconfig再把其他节点加入集群最后渲染并应用那些 YAML 模板把 Dashboard、Ingress、存储供应者这些平台组件装上去。为什么非要搞成离线包而不是从公网拉镜像两个原因。一是企业内网环境大多没有直连外网的条件二是从公共镜像仓库拉取几十个镜像网络抖动导致的失败率非常高而且每次部署都重复下载几百兆到几个 G 的流量纯属浪费。离线包把不确定性提前消化掉了这是它能做到一键的关键。实操心得拿到离线包后先校验一下哈希值再确认包内镜像列表里有没有架构不匹配的比如包里是 amd64 镜像你的机器是 arm64。这两步能挡掉相当一部分装到一半失败的尴尬。2.2 容器运行时与集群初始化的取舍容器运行时这块现在的标准答案基本就是 containerd。Docker 在 Kubernetes 1.24 之后已经不再作为直接支持的运行时虽然还能通过 cri-dockerd 绕过去但没必要给自己找麻烦。containerd 更轻、启动更快、和 kubelet 的对接也更干净。有一个非常容易翻车的细节kubelet 的 cgroup driver 必须和容器运行时一致。现在双方都默认用 systemd但如果你手动改过 containerd 的配置文件把它改成 cgroupfs而 kubelet 那边还是 systemd节点就会以SystemdCgroup 不匹配之类的理由拒绝启动。这个错我在两个项目里都见过排查起来还挺费时间因为日志信息不算直观。集群初始化前需要处理的内核参数我列一份清单这些在多数安装器里会自动做但知道它们存在很有必要# 关闭 swapkubelet 默认不接受开启 swap 的节点 swapoff -a sed -i /swap/s/^/#/ /etc/fstab # 开启网桥流量经过 iptables否则 Service 转发会出问题 modprobe br_netfilter cat /etc/modules-load.d/k8s.conf EOF br_netfilter overlay EOF # 内核转发与 iptables 相关参数 cat /etc/sysctl.d/k8s.conf EOF net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 fs.inotify.max_user_instances 8192 EOF sysctl --system解释一下最后那个max_user_instances。默认值通常是 128当节点上 Pod 数量多、且每个 Pod 里都有进程在监听文件事件时inotify 实例会被耗尽表现出来就是某些容器莫名其妙地启动失败日志里出现 too many open files。这个参数在官方的安装文档里往往不起眼但在实际生产中很关键我一般直接调到 8192。2.3 网络与存储的默认选型理由网络插件的选择主流就是 Calico 和 Cilium 两家。Calico 成熟稳定、文档齐全、出问题好查用的是 BGP 或者 IPIP 隧道转发对内核版本要求不高Cilium 基于 eBPF性能更好、可观测性更强但对内核版本有硬性要求一般建议 4.19 以上。云操作系统默认给的一般是 Calico理由很实在兼容性好不容易因为内核太老翻车。这个选择我在实际使用中是认可的毕竟平台的第一要务是能跑起来性能优化可以后面再谈。IP 网段的规划是另一个容易被忽略的点。默认的 Pod CIDR 常见是10.244.0.0/16Service CIDR 是10.96.0.0/12。如果你公司内网恰好用了10.96.x.x这个段就会出现从 Pod 里访问不了内网服务的诡异现象——因为流量被路由规则劫持到了集群内部。我遇到过一次追了整整一个下午才发现是网段冲突。注意部署前一定先确认内网网段尽量避开10.0.0.0/8、172.16.0.0/12、192.168.0.0/16这些常见段。如果实在避不开就在安装时显式指定 Pod CIDR 和 Service CIDR。存储方面这类平台通常会带一个基于本地磁盘的轻量供应者local-path 类型开箱即用给开发测试环境足够。生产环境要换成 NFS、Ceph 或者云厂商的块存储原因很简单本地盘的数据跟着节点走节点挂了 Pod 飘到别的机器上数据就找不到了。这一点在做有状态服务时绝对不能含糊。3. 从零到一把云操作系统跑起来的完整实操3.1 环境准备与资源规划先算资源。一套最小的高可用集群我建议三台控制面加两台以上工作节点。控制面每台 4 核 8G 起步etcd 对磁盘 IO 敏感务必用 SSD工作节点看业务我通常按每个 Pod 预留 0.5 核 512M来估算再留 30% 的余量。IP 规划我用一张表来固定这样后面配置证书和访问入口时不会乱用途规划值说明控制面节点192.168.10.11-13奇数台保证 etcd 仲裁工作节点192.168.10.21-22按业务量增减Pod CIDR10.244.0.0/16避开内网网段Service CIDR10.96.0.0/12避开内网网段apiserver 虚拟 IP192.168.10.10用 keepalived 或负载均衡器承载Ingress 入口192.168.10.100对外服务的统一入口主机名务必确保唯一并且能互相解析我在/etc/hosts里写死比依赖 DNS 稳定。时间同步用 chrony误差超过几秒就可能导致证书校验失败报错信息还特别难懂。3.2 执行一键安装与验证安装命令的形态各家略有不同但参数结构大同小异。以我最近用过的形式为例sudo ./deploy.sh \ --masters 192.168.10.11,192.168.10.12,192.168.10.13 \ --nodes 192.168.10.21,192.168.10.22 \ --pod-cidr 10.244.0.0/16 \ --service-cidr 10.96.0.0/12 \ --vip 192.168.10.10 \ --install-dashboard \ --install-ingress这个过程一般五到二十分钟取决于磁盘速度。第一次装的时候别急着走开盯着输出看出现黄字警告要留意很多警告虽然在最后被标成可忽略但背后可能是某个组件没装成功。装完之后立刻做三层验证这个顺序别乱# 第一层节点状态全部应该是 Ready kubectl get nodes -o wide # 第二层系统组件CoreDNS、网络插件、Ingress 都应该 Running kubectl get pods -A # 第三层集群信息确认版本和 Service 网段 kubectl cluster-info kubectl get svc -n default如果节点是 NotReady先去看journalctl -u kubelet -n 100 --no-pager十有八九是运行时没起来或者证书有问题。如果系统 Pod 卡在 Pending多半是资源不够或者有污点没容忍。3.3 部署第一个应用从 YAML 到可访问集群通了就该跑业务了。我习惯用一个 Nginx 做验证因为它足够简单出问题也容易定位。先写一个 DeploymentapiVersion: apps/v1 kind: Deployment metadata: name: demo-web namespace: default spec: replicas: 2 selector: matchLabels: app: demo-web template: metadata: labels: app: demo-web spec: containers: - name: nginx image: nginx:1.25-alpine ports: - containerPort: 80 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi这里有个细节值得说requests和limits一定要写。不写 requests调度器只能瞎猜容易出现节点超卖不写 limits某个 Pod 内存泄漏就能把整个节点拖垮。生产环境我还会给命名空间配 ResourceQuota从上面卡一道总闸。接着是 Service用 ClusterIP 先在集群内部验证连通性apiVersion: v1 kind: Service metadata: name: demo-web-svc namespace: default spec: selector: app: demo-web ports: - port: 80 targetPort: 80 protocol: TCP type: ClusterIP注意selector里的app: demo-web必须和 Deployment 的 Pod 模板标签完全一致这是最常见的Service 没有后端的原因。写完 apply 之后直接kubectl get endpoints demo-web-svc如果 endpoints 列表是空的就是标签没匹配上。最后加 Ingress 把服务暴露出去apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: demo-web-ingress namespace: default spec: ingressClassName: nginx rules: - host: demo.example.internal http: paths: - path: / pathType: Prefix backend: service: name: demo-web-svc port: number: 80访问时记得把域名解析到 Ingress 入口 IP否则只能靠curl -H Host: demo.example.internal这种方式验证。3.4 用 Dashboard 创建 Pod 并发布成服务对不熟悉命令行的同学Dashboard 是很好的入门入口。整个流程大概是这样第一步找到 Dashboard 的访问地址。通常是 Ingress 暴露的一个域名或者通过 NodePort 访问。拿到登录 token 的方式是查询 Dashboard 所在命名空间里的 Secretkubectl -n kubernetes-dashboard get secret \ $(kubectl -n kubernetes-dashboard get sa admin-user -o jsonpath{.secrets[0].name}) \ -o go-template{{.data.token | base64decode}}这一步在新版本里可能因为 Secret 自动生成机制的变化而不同如果没有对应的 Secret就自己用kubectl create token命令签一个短期 token。第二步进入 Dashboard 后右上角切换到目标命名空间然后点创建工作负载。这里有两种模式表单模式和 YAML 模式。表单模式适合新手但字段是精简过的很多高级配置填不了我推荐直接用 YAML 模式把上面那个 Deployment 的 YAML 粘进去效果和命令行完全一样。这也顺便回答了那个高频问题——Dashboard 里怎么创建 Pod你实际上创建的是 Deployment 这类控制器Pod 由它托管生成不建议手搓裸 Pod。第三步发布成服务。在 Dashboard 的服务页面点创建选择类型。集群内部访问选 ClusterIP临时测试选 NodePort生产对外选 Ingress 或 LoadBalancer。关键还是那一步给 Service 指定一个与 Pod 标签匹配的选择器。Dashboard 表单里会列出当前命名空间下已有的标签勾选即可比手写 YAML 少犯错。实操心得Dashboard 的事件面板是我用下来最值钱的页面。Pod 起不来、镜像拉不动、调度失败事件里往往一句话就说清了原因。比在命令行里翻kubectl describe的输出快很多。4. 核心概念落地Pod、Service、Ingress 怎么配合4.1 Pod 与工作负载控制器的分工Pod 是 Kubernetes 里最小的调度单位一个 Pod 可以装一个或多个容器共享网络和存储命名空间。多容器的典型用法是 sidecar比如主容器跑业务边车容器负责日志收集或流量代理。但裸 Pod 在生产里几乎不该出现原因有三节点故障时不会自动重建、不能滚动更新、副本数没法保证。正确的做法是用控制器托管日常就记住四类控制器适用场景关键特点Deployment无状态服务支持滚动更新和回滚StatefulSet有状态服务稳定的网络标识和存储DaemonSet节点级代理每个节点跑一份Job / CronJob批处理任务执行完成后退出我见过不少团队用 Deployment 跑数据库图省事结果扩容时多个副本抢同一份数据直接翻车。数据库这类有状态的老老实实用 StatefulSet 加持久卷。4.2 Service 与 Ingress 的流量路径Service 解决的是一组 Pod 的稳定访问问题。Pod 的 IP 会变Service 的 ClusterIP 不会。它靠标签选择器把流量转发到后端 Pod具体转发规则由 kube-proxy 以 iptables 或 IPVS 模式实现。IPVS 模式在服务数量多的时候性能优势明显一般超过一千个 Service 就该考虑切换。四种 Service 类型各有用途ClusterIP 只对内NodePort 在每个节点上开一个高端口LoadBalancer 需要底层支持ExternalName 是做 DNS 别名。Ingress 则是第七层的入口它本身不是一种 Service而是一套路由规则需要 Ingress Controller 去实现。流量路径大致是外部请求 → 负载均衡器或节点端口 → Ingress Controller → 按域名和路径匹配 → 转发到对应 Service → 后端 Pod。搞清这条链路排查外部访问不了就有方向了先看 Controller 有没有起来再看 Ingress 规则的 class 和 host 对不对最后看 Service 的 endpoints 是不是空的。4.3 命名空间与多租户隔离命名空间是逻辑隔离手段不是安全边界。真正要做到多租户隔离得组合使用几样东西第一是ResourceQuota限制每个命名空间的总 CPU、内存、Pod 数量和 PVC 数量防止某个团队把集群吃干净第二是LimitRange给没有声明 resources 的 Pod 设定默认值避免裸奔容器第三是NetworkPolicy控制命名空间之间的东西向流量默认拒绝更安全第四是RBAC把用户和 ServiceAccount 绑定到具体的角色上权限给到最小。这里有个容易忽视的点很多 CNI 插件默认不启用 NetworkPolicy只是接受配置但不下发规则。部署完之后一定要实际测一次否则你以为隔离了其实门户大开。5. 常见问题与排查技巧实录5.1 安装阶段的典型坑第一个坑是端口占用。apiserver 的 6443、kubelet 的 10250、etcd 的 2379 和 2380任何一个被占都会导致安装失败。装之前先ss -lntp扫一遍特别是有没有残留的旧集群进程。第二个坑是主机名重复。多台机器主机名都是localhost或者k8s-node加入集群时节点会互相覆盖。装之前统一改/etc/hostname并重启。第三个坑是防火墙和 SELinux。很多安装文档建议直接systemctl stop firewalld和setenforce 0但这是临时手段重启后失效。稳妥的做法是写入配置文件永久关闭或者按需放行端口。我一般选择永久关闭并记录在案因为在内网受控环境下用 NetworkPolicy 做细粒度管控比宿主机防火墙更合适。第四个坑是镜像拉取失败。如果安装器配置里既写了离线包又配了公共仓库地址可能会出现镜像地址被改写、本地找不到的怪现象。检查一下crictl images里到底有没有那些镜像比看安装日志更快。5.2 运行阶段故障速查表下面这张表是我这几年攒下来的基本覆盖了八成以上的日常问题现象可能原因排查命令处理方式节点 NotReady运行时挂掉或证书过期journalctl -u kubelet -n 200重启运行时检查证书有效期Pod 一直 Pending资源不足或有污点kubectl describe pod 名扩容节点或调整容忍度Pod CrashLoopBackOff应用启动报错kubectl logs 名 --previous看上一个容器实例的日志ImagePullBackOff镜像名错或凭据缺失kubectl describe pod 名检查 imagePullSecretService 无后端选择器不匹配kubectl get endpoints 名对齐标签Ingress 返回 404class 或 host 不匹配kubectl get ingress -A检查 ingressClassNamePVC 一直 Pending没有默认存储类kubectl get sc设置默认 StorageClassDNS 解析失败CoreDNS 异常kubectl logs -n kube-system -l k8s-appkube-dns检查 ConfigMap 和网络用这张表的时候有个技巧永远先看 Events。kubectl describe输出的最后一部分就是事件它会按时间倒序告诉你最近发生了什么很多问题不需要查文档答案就在那几行里。5.3 几个我印象比较深的实际问题有一次客户报集群里所有服务都访问不了但不是全部只有新部署的。我先看节点全 Ready再看 CoreDNS正常。最后发现问题出在 IP 地址池耗尽——Pod CIDR 配得太小只给了/24也就是 256 个地址实际 Pod 数量早就超了。新建的 Pod 拿不到 IP就一直 Pending。教训是Pod CIDR 起步至少/16别抠这点地址空间。还有一次更隐蔽某个服务白天正常凌晨定时任务跑的时候批量超时。查了半天发现是节点的 conntrack 表满了内核默认上限在连接数多的时候不够用。解决办法是调大nf_conntrack_max并缩短超时时间。这类问题不会出现在任何入门文档里只有在真实流量下才会暴露。第三个是镜像层。测试环境正常生产拉取超时原因是生产用的是私有仓库而某几个镜像被固定了完整的仓库地址改配置文件时漏改了一处。这种环境差异类问题最好的办法是把镜像地址全部抽到变量里统一管理而不是散落在各个 YAML 中。提醒给集群装监控不是可选项。很多莫名其妙的问题其实早就在指标里露出苗头了比如内存缓慢上涨、连接数持续走高。等到业务报警才去查成本高得多。6. 从能跑到好用平台能力的后续扩展6.1 监控告警与日志采集集群跑起来只是起点。第一件要补的是监控。指标采集用 Prometheus 系列的方案是主流节点层看 CPU、内存、磁盘、网络容器层看资源使用和重启次数控制面看 apiserver 的请求延迟和 etcd 的写入耗时。告警规则先配最关键的几条节点不可用、Pod 重启次数异常、磁盘使用率超过 80%、证书剩余有效期不足 30 天。日志这块分两条路容器标准输出用日志代理采集后送到集中存储便于全文检索应用内部的日志文件要么改造成标准输出要么挂持久卷单独处理。我的建议是尽早统一成标准输出运维成本最低。6.2 应用商店与 GitOps 落地云操作系统一般会带一个应用商店本质上是 Helm Chart 的可视化封装装中间件、数据库、消息队列都很快。它的价值在于把改 values 文件这件事变成了填表单降低了门槛。但要注意商店里的 Chart 版本往往滞后生产选型时还是要看官方的最新稳定版。更进一步的做法是 GitOps把所有部署清单放进代码仓库集群里的状态由控制器自动同步。好处是变更可追溯、回滚就是一次 revert、环境差异靠目录结构管理。这套流程一旦跑顺团队就不太会再手敲kubectl apply了。最后分享一个我在多个项目里验证过的经验先把一个最小的业务闭环跑通再谈平台能力的完善。很多人一上来就想把监控、日志、服务网格、CI/CD 全套配齐结果核心业务一个都没上线平台本身成了负担。先让一个真实的、有人用的服务在集群里稳定跑两周你会自然地发现下一个该补的是什么。这套基于 Kubernetes 的云操作系统我自己的使用感受是它把学习曲线最陡的那段给抹平了让你能在半天内从只有几台机器走到有一个能用的平台。但它省掉的是入门成本不是理解成本。Pod 为什么不建议裸跑、Service 的选择器为什么关键、Ingress 和 Service 的边界在哪这些问题还是得自己搞明白否则换个环境、换个发行版照样会卡住。工具能帮你省时间理解才能帮你省命。