1. 从零搭建一套可用的容器编排环境:这套组合到底解决了什么问题
很多团队在容器化落地的中段都会卡在同一个坎上:单机跑容器没问题,一旦要上多节点、要可视化管理、要私有镜像仓库、要一键部署业务,环境就变得七零八落。我这次要分享的这套组合——k8s 集群 + kuboard 管理面板 + harbor 私有仓库 + yml 编排文件——本质上就是给中小团队搭一套"能看、能管、能拉、能跑"的完整闭环。它解决的不是某一个点的问题,而是把"集群怎么建、界面怎么看、镜像从哪来、业务怎么发"这四件事串成一条线。
先说清楚这套东西各自扮演什么角色。k8s(Kubernetes)是底座,负责调度容器、维持副本数、做服务发现;kuboard是长在 k8s 之上的可视化管理面板,把 kubectl 那套命令行操作翻译成图形界面,降低团队上手门槛;harbor是私有镜像仓库,解决"镜像不能放公网、内网要能稳定拉取"的诉求;yml 文件则是把业务部署声明化的载体,一份文件描述清楚要几个副本、用什么镜像、暴露什么端口。四者配合,才是一套能交付给团队日常使用的环境。
这套方案适合谁?我的判断是三类人最受益:一是刚接触 k8s、被一堆命令行劝退的运维新手;二是需要给团队搭一套内网可复现环境的后端负责人;三是想把本地验证过的镜像快速推到内网集群跑起来的小型项目组。如果你已经在用云厂商托管集群,这套自建流程同样值得走一遍,因为踩过的坑在托管环境里往往以另一种形式重现,理解底层逻辑能省下大量排查时间。
需要提前说明的是,下面涉及的具体 IP、节点名、镜像名全部用占位符表示,你替换成自己环境的真实值即可。整套流程我在多个内网环境复现过,核心步骤稳定,但细节上的坑不少,我会在每一段把"为什么这么做"和"哪里容易翻车"都讲透。
2. 整体设计与方案选型:为什么是这套组合而不是别的
2.1 集群形态的选择逻辑
搭 k8s 第一步是决定用什么形态。常见有三条路:云厂商托管集群、kubeadm 自建、以及各种轻量发行版(如 k3s)。我这次选的是kubeadm 自建,原因很实在——内网环境往往没有公网出口,托管集群的控制面访问受限;而 k3s 虽然轻,但默认组件裁剪较多,遇到需要完整 API 行为的场景(比如某些 CRD、某些 admission webhook)容易出兼容问题。kubeadm 是官方维护的引导工具,组件齐全、文档透明,出问题能查到根因,适合作为团队长期维护的底座。
节点规划上,我建议至少1 个控制面 + 2 个工作节点。有人会问单节点行不行?技术上可行,但单节点失去了"编排"的意义,而且控制面和工作负载混跑时,资源争抢会让调度行为失真,排查问题时你会分不清是业务问题还是环境问题。三节点是最小可信环境,能验证副本调度、节点亲和、滚动更新这些真实行为。
这里有个容易被忽略的点:容器运行时。新版 k8s 已经移除了对 Docker 作为运行时的直接支持,走的是 CRI 接口。我选containerd,因为它是 k8s 生态里最贴合 CRI 标准的实现,配置简单、资源占用低。如果你之前习惯docker ps看容器,切到 containerd 后要用crictl ps,这是第一个需要适应的习惯变化。
2.2 为什么管理面板选 kuboard
k8s 自带 dashboard 也能用,但功能偏基础,多命名空间管理、工作负载详情、日志查看这些日常高频操作体验一般。kuboard 的优势在于它把"以应用为中心"的视角做得很顺——你能直接看到某个微服务的所有副本、配置、事件,而不是在一堆资源类型之间来回跳。对于团队协作场景,它的权限模型和界面友好度明显更实用。
选 kuboard 还有个现实考量:部署方式简单。它本身就是一个可以跑在 k8s 里的应用,用一份 yml 就能拉起,不需要额外维护一套独立服务。这种"自己管自己"的方式虽然有点递归的意味,但实际很省心,集群起来后面板跟着起来,运维成本低。
2.3 harbor 的定位与选型理由
镜像仓库这块,公网 Docker Hub 有拉取限流和内网访问不稳定的问题,自建是刚需。harbor 相比 registry 原生方案,多了项目管理、RBAC 权限、镜像扫描、复制策略这些企业级能力。对中小团队来说,最直接的价值是"按项目隔离镜像"——不同业务线的镜像放在不同 project 下,权限清晰,不会互相污染。
harbor 的部署我倾向用它的offline installer(离线安装包),因为内网环境拉不了外网镜像,离线包把依赖都打好了,解压改配置就能起。这里要提醒:harbor 默认用 https,自签证书在 k8s 拉镜像时会因为证书不受信而失败,这个坑后面会专门讲怎么绕。
2.4 yml 编排文件的核心作用
最后一块是 yml。k8s 的一切部署最终都落到声明式配置上,一份 Deployment + Service 的 yml 就描述了一个可运行的服务。为什么强调"从 harbor 拉镜像再执行 yml"这个顺序?因为 yml 里写的image字段指向 harbor 地址,如果仓库没准备好、镜像没推上去,apply 之后 Pod 会一直卡在 ImagePullBackOff。所以正确顺序是:仓库先通、镜像先推、再 apply 编排文件。这个顺序看似常识,但实际项目里因为顺序颠倒浪费的时间非常多。
3. 环境准备与核心细节:那些决定成败的前置条件
3.1 系统层的前置配置
在装任何组件之前,所有节点(控制面和工作节点)都要做一批系统级配置,这些是 k8s 稳定运行的地基,漏一个后面就可能出诡异问题。
第一是关闭交换分区。k8s 的调度器假设节点内存是可预测的,swap 会让内存压力判断失真,导致 OOM 行为不可控。执行swapoff -a只是临时生效,还要注释掉/etc/fstab里的 swap 行,否则重启后复活。我见过有环境忘了改 fstab,跑了一周后节点重启,swap 回来,Pod 开始莫名被驱逐,排查了半天。
第二是内核参数。需要开启网桥的 iptables 转发,并确保br_netfilter模块加载。具体是往/etc/modules-load.d/k8s.conf写br_netfilter和overlay,再往/etc/sysctl.d/k8s.conf写net.bridge.bridge-nf-call-iptables=1、net.ipv4.ip_forward=1等。这些参数的作用是让容器网络和宿主机网络能正确互通,缺了会导致 Pod 之间 ping 不通但宿主机能通这种"半通"状态,非常难查。
第三是时间同步。集群里证书、token、事件时间戳都依赖时钟一致,节点间时间偏差过大会导致证书校验失败、join 失败。内网没有外网 NTP 的话,至少保证所有节点指向同一个内网时间源。
注意:以上配置要在所有节点执行,不是只做控制面。工作节点漏配是新手最常见的失误。
3.2 容器运行时的安装要点
containerd 的安装我建议直接用系统包管理器装,版本选稳定版即可。装完后关键在配置文件/etc/containerd/config.toml,需要确认两件事:一是SystemdCgroup要设为true,因为现在主流发行版都用 systemd 管理 cgroup,不匹配会导致资源限制不生效;二是镜像加速或私有仓库的配置,如果 harbor 是 http 的,要在这里加insecure_registries或者在 k8s 的运行时配置里声明。
这里有个实操细节:containerd 的配置改完后要systemctl restart containerd,但重启会杀掉正在跑的容器。所以在集群还没起来时改最安全,等业务跑起来再改就要考虑滚动影响。我的习惯是在 join 集群之前把所有运行时配置定稿,避免后期返工。
3.3 kubeadm 初始化控制面的参数取舍
kubeadm init的参数里,最需要想清楚的是pod 网段(--pod-network-cidr)。这个值必须和后面要装的网络插件配置一致,比如用 Calico 默认192.168.0.0/16,用 Flannel 默认10.244.0.0/16。两者不一致的后果是 Pod 拿到 IP 但跨节点不通,表现为"同节点 Pod 能通、跨节点不通",这是网络问题里最经典的坑。
另一个参数是--apiserver-advertise-address,要填控制面节点的内网 IP,不能填 127.0.0.1,否则工作节点 join 时连不上。如果节点有多块网卡,一定要确认填的是工作节点能访问到的那块。
初始化完成后,kubeadm 会输出 join 命令,这条命令里的 token 有效期默认 24 小时。如果隔天才加节点,token 过期了要重新生成,用kubeadm token create --print-join-command一条命令搞定,不用慌。
3.4 网络插件的选择与验证
网络插件我选Calico,理由是它在网络策略(NetworkPolicy)支持上更完整,而且 BGP 模式在纯内网环境性能好。装完之后验证不能只看 Pod 是否 Running,要做三件事:一是kubectl get nodes确认所有节点 Ready;二是kubectl get pods -n kube-system确认网络插件 Pod 全 Running;三是实际起两个跨节点的测试 Pod 互相 ping,这一步才是真验证。很多人止步于前两步,结果业务一上就发现跨节点不通。
4. 实操过程:从集群到业务跑通的完整链路
4.1 控制面初始化与节点加入
控制面初始化的完整命令大致如下(IP 和网段替换成你的):
kubeadm init \ --apiserver-advertise-address=192.168.x.10 \ --pod-network-cidr=192.168.0.0/16 \ --image-repository=registry.aliyuncs.com/google_containers--image-repository这个参数很关键,因为默认会去拉k8s.gcr.io的镜像,内网环境基本拉不动。换成国内可访问的镜像源能省掉大量等待。初始化成功后,按提示配置 kubectl 的 kubeconfig:
mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config然后装 Calico,apply 官方 yml 即可。等工作节点用 join 命令加入后,kubectl get nodes应该看到三个节点全部 Ready。这一步的验收标准很明确:三节点 Ready + 网络插件 Pod 全 Running + 跨节点 Pod 互通,三者缺一不可。
4.2 kuboard 的部署与访问
kuboard 的部署就是 apply 一份 yml,它会创建自己的命名空间、ServiceAccount、Deployment 和 Service。部署完用kubectl get svc -n kuboard看暴露的端口,通常通过 NodePort 访问。首次登录需要拿一个 token,用kubectl get secret相关命令从对应 secret 里取出来。
这里有个实操心得:kuboard 的 Service 默认可能是 ClusterIP,内网其他机器访问不到,需要改成 NodePort 或者配 Ingress。我一般直接改 NodePort,简单直接,团队内网访问足够用。改完记得确认防火墙放行了对应端口段。
登录进去后,第一件事是导入集群。kuboard 需要一份有足够权限的 kubeconfig 或 token 来连接集群,用 admin 的配置最省事,但生产环境建议单独建一个受限的 ServiceAccount,遵循最小权限原则。这个细节很多人图省事直接用 admin,后面权限审计时就是隐患。
4.3 harbor 的安装与项目初始化
harbor 用离线包安装,解压后编辑harbor.yml,重点改三处:hostname填内网 IP 或域名、harbor_admin_password设强密码、data_volume指定数据目录(建议挂独立磁盘)。改完执行./install.sh,它会拉起一堆容器(harbor 自身也是容器化的)。
装完后浏览器访问http://<harbor-ip>,用 admin 登录,先新建一个 project,比如叫myapp。这个 project 就是镜像的命名空间,后面推镜像的地址格式是<harbor-ip>/myapp/<image>:<tag>。
关键一步:配置 k8s 节点信任 harbor。如果 harbor 是 http 的,containerd 默认拒绝拉取,需要在每个节点的 containerd 配置里把 harbor 地址加到insecure_registries,然后重启 containerd。如果是 https 自签证书,则要把 CA 证书放到节点的信任目录(如/etc/containerd/certs.d/<harbor-ip>/ca.crt)。这一步不做,后面 apply yml 必然 ImagePullBackOff,而且报错信息往往只说"证书错误"或"http response to https client",不熟悉的人会绕很久。
4.4 镜像推送与 yml 编排执行
镜像推送的标准流程是:本地docker tag成 harbor 地址格式,docker login登录 harbor,docker push推上去。推完在 harbor 界面能看到镜像和 tag,这一步是"仓库通了"的实证。
然后是 yml。一份最小可用的 Deployment + Service 大致长这样:
apiVersion: apps/v1 kind: Deployment metadata: name: myapp namespace: default spec: replicas: 2 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: myapp image: 192.168.x.20/myapp/myapp:v1 ports: - containerPort: 8080 --- apiVersion: v1 kind: Service metadata: name: myapp-svc spec: selector: app: myapp ports: - port: 80 targetPort: 8080 type: NodePortimage字段必须写全 harbor 地址 + project + 镜像名 + tag,少一段就拉不到。replicas: 2是为了验证调度,两个副本应该分布在不同节点(如果资源够)。apply 之后用kubectl get pods -o wide看 Pod 落在哪个节点、IP 是多少,用kubectl describe pod看事件,这是排查的第一手资料。
验收标准:Pod 全部 Running + Service 有正确 Endpoints + 通过 NodePort 能访问到业务。三步都过,这条链路才算真正打通。
5. 常见问题与排查技巧实录
5.1 镜像拉取失败的几种典型形态
ImagePullBackOff 是这套流程里最高频的问题,但根因分好几种,得对症下药。第一种是地址写错,比如 harbor 地址拼错、project 名大小写不对(harbor 的 project 名是大小写敏感的);第二种是认证失败,私有 project 需要 imagePullSecret,yml 里没配 secret 就会 401;第三种是证书/协议不匹配,http 仓库被当 https 拉,或者自签证书不受信。
排查顺序我总结成一句话:先看 describe 的 Events,再看节点上手动 crictl pull。kubectl describe pod的 Events 会明确告诉你"unauthorized"还是"certificate"还是"not found",比瞎猜快得多。如果 describe 信息不够,直接上节点执行crictl pull <完整镜像地址>,报错更原始更直接。
| 现象 | 可能原因 | 处理方向 |
|---|---|---|
| unauthorized | 缺 imagePullSecret 或密码错 | 创建 secret 并在 yml 引用 |
| certificate signed by unknown authority | 自签证书未受信 | 把 CA 放到节点信任目录 |
| http response to https client | 仓库是 http 但按 https 拉 | 配置 insecure_registries |
| manifest unknown | tag 不存在或 project 名错 | 核对 harbor 界面实际 tag |
5.2 跨节点网络不通的定位方法
如果 Pod 起来了但跨节点访问失败,先确认网络插件 Pod 是否全 Running,再看节点路由。Calico 环境下可以用calicoctl看 BGP 邻居状态,或者简单粗暴地在两个跨节点 Pod 里互相ping和curl。我遇到过一次是节点防火墙把 BGP 的 179 端口挡了,插件 Pod 看着 Running 但邻居没建立,跨节点就是不通。所以装完网络插件一定要做跨节点实测,别信"Pod Running 就等于网络好"。
5.3 kuboard 连不上集群的排查
kuboard 导入集群失败,常见是 kubeconfig 里的 server 地址填了127.0.0.1或localhost,而 kuboard 是跑在 Pod 里的,它访问127.0.0.1访问的是自己。正确做法是把 server 改成控制面的内网 IP 或集群内 Service 地址。另外 token 权限不足也会导致导入后看不到资源,检查对应 ServiceAccount 的 RBAC 绑定。
5.4 几个我踩过的实操坑
第一个坑:harbor 装完没改默认密码,团队多人用同一个 admin,后面想追溯谁推的镜像完全没辙。建议装完立刻建独立用户,按 project 授权。
第二个坑:yml 里没写 namespace,默认落到 default,多个业务混在一起,后面清理时容易误删。养成习惯,每个业务一个 namespace,yml 里显式声明。
第三个坑:replicas 设了但节点资源不够,Pod 一直 Pending。用kubectl describe pod看 Events 会提示"Insufficient cpu/memory",这时候要么加资源要么降副本,别硬等。
第四个坑:改了 containerd 配置忘了重启,配置不生效还以为配错了。改完systemctl restart containerd是必须动作,且要确认重启后systemctl status containerd是 active。
6. 这套环境后续怎么用得更顺
环境跑通只是起点,真正让团队用起来还得做几件事。一是把 yml 纳入版本管理,每次变更走 git,配合kubectl apply -f做可追溯部署;二是给 kuboard 配好权限分级,开发只能看自己 namespace,运维有全局视角;三是harbor 配镜像清理策略,不然磁盘很快被历史 tag 撑满,这个我吃过亏,一个项目半年堆了几百 GB 的旧镜像。
还有一个我个人的习惯:每次环境重建都写一份 checklist,把系统配置、运行时、网络插件、仓库信任这些前置项列清楚,照着勾。因为这套流程步骤多、依赖强,靠记忆迟早漏项,而漏一项的代价往往是几小时的排查。这份 checklist 我现在已经迭代到十几项,新同事照着走一遍基本能独立搭起来。
最后分享一个小技巧:验证整条链路是否真的通,别只看 Pod Running,直接kubectl exec进 Pod 里curl一下 harbor 地址和外部服务,能通才算数。Running 只代表容器起来了,不代表网络和依赖都就绪,这个区别在实际运维里非常关键。