两个月前我接到一个任务搭一套 openEuler 系统上的 Kubernetes 学习测试环境并且把 Harbor 私有镜像仓库也一起部署好。这个需求听起来很常规但真正上手后我发现网上多数教程要么以 Ubuntu 或 CentOS 为主线要么只讲 K8s 和 Harbor 中的某一个很少有资料把 openEuler 这个前提、容器运行时选型、Harbor 证书信任、K8s 镜像拉取这条链路完整串起来。所以这篇指南就从实际操作的角度把我在虚拟机上跑通的全过程拆开讲目标是让只有基础 Linux 经验的人也能跟下来。这篇文章的场景默认是学习测试不是生产高可用架构。我会按照一台 master、一至两台 worker、Harbor 独立部署在 master 主机上的方式来做。除开安装命令我会解释每个关键步骤背后的原因比如为什么 K8s 1.24 以后还需要 Docker、为什么 Harbor 要重新跑一次 prepare、为什么 containerd 会不认自签证书。学习环境最怕的不是慢而是照着敲完命令后遇到问题不知道从哪里排查所以理解原理比复制命令更重要。1. openEuler 基础环境规划版本、虚拟机参数和系统初始化1.1 openEuler 版本怎么选三台虚拟机如何分配openEuler 的版本策略里有 LTS 和创新版本两类学习测试我建议优先选 LTS 的 SP 版本。LTS 版本维护周期长、软件源稳定、踩坑后搜到的资料也比较多创新版本特性更新更快但作为学习测试环境没必要拿自己的实验平台去追新。以我这次为例openEuler 22.03 LTS SP4 就足够覆盖 K8s 和 Harbor 的部署需求后续升级不会太折腾。如果你准备在 VMware Workstation 里安装虚拟机客户机操作系统类型选择“Other Linux 5.x 64-bit”或相近的内核选项即可。openEuler 的内核主线通常是 5.10 或 6.6VMware 识别上不会有大问题。这里需要注意安装完成后第一件事不是配置 K8s而是确认网卡能稳定联网、时区正确、软件源可用否则后面每一步都可能卡在莫名其妙的依赖问题上。我采用的拓扑如下你可以根据物理机内存适量缩减角色主机名规划 IP建议配置主要组件master 节点k8s-master192.168.100.1014C / 8G / 100Gkubeadm、kubelet、containerd、Harborworker 节点 1k8s-node1192.168.100.1024C / 8G / 50Gkubelet、containerdworker 节点 2k8s-node2192.168.100.1034C / 8G / 50Gkubelet、containerdHarbor 需要存储镜像master 节点的磁盘规划到 100G 会比较宽裕。如果只需要跑通流程三台虚拟机可以减到两台甚至一台机器同时作为 master 和 worker 也行。单机方案下kubeadm init后用kubectl taint nodes --all node-role.kubernetes.io/control-plane-去掉 master 的调度限制即可但这样就少了节点加入集群的练习机会我还是建议至少准备两台。1.2 openEuler 安装完成后的配置清单openEuler 的最小化安装是推荐选择学习 K8s 时图形桌面并不是必需品反而会占用大量内存。系统装完后我按顺序处理了下面几项配置固定 IP 和主机名。学习环境里 IP 漂移是最隐蔽的坑之一因为我后面所有证书、kubeadm 配置、Harbor 地址都会绑定这个 IP 和主机名。三个节点分别改/etc/hostname并将三行映射写进各自的/etc/hosts保证互相解析正常。关闭交换分区。Kubernetes 1.28 之后虽然允许在 swap 开启的情况下运行 kubelet但为了复现最常见的官方学习路径建议直接swapoff -a同时注释掉/etc/fstab里的 swap 条目避免重启后又自动挂载。关闭或放宽 SELinux。openEuler 默认启用 SELinux确实更安全但 kubeadm 和容器运行时对 SELinux 的策略要求比较严格学习环境下我不想陷入各种 AVC denial 的排错中直接把/etc/selinux/config设为SELINUXpermissive。K8s 能跑起来之后你有兴趣再开回来研究也来得及。配置可用的 yum 源。openEuler 安装好之后默认源在/etc/yum.repos.d/openEuler.repo中。一般官方源可以正常访问但如果你发现dnf makecache很慢或者某些软件包不在主源里请先去 openEuler 的镜像站列表里挑一个当前网络能够稳定访问的地址全局替换掉repo.openeuler.org域名即可。另外 openEuler 的 EPOL 扩展仓库里包含不少容器相关软件包我建议把 EPOL 和 update 源都开着后续在节点上找docker-engine、containerd这类包时会更顺。做完这些基础配置后我建议立刻给虚拟机打一个快照。快照是学习环境里最值得养成的习惯。后面不管是 K8s 集群初始化失败还是 Harbor 配置搞乱一键回滚比手动清理干净得多。2. Docker 与 Containerd 的关系学习环境为什么两者都要装2.1 Kubernetes 1.24 之后Docker 不是必须的但 Harbor 需要很多新同学第一次配置 K8s 时会困惑网上老的教程说要用 Docker新的教程又说 K8s 从 1.24 开始移除了 dockershim直接使用 containerd 作为运行时那到底装哪个简单解释一下Kubernetes 通过 CRI容器运行时接口与运行时通信containerd 直接实现 CRI所以 kubelet 原生对接 containerd 是最简路径。而 Docker 本身并不直接实现 CRI它需要额外部署一个 cri-dockerd 适配器才能被 K8s 使用。正因为这样官方推荐默认运行时是 containerd。但我们的链路里还有 Harbor。Harbor 官方安装包里其实是一套 docker compose 编排它需要 Docker 引擎或兼容环境来运行自己的容器。所以在这套学习环境里每个集群节点安装 containerd 负责跑 K8s 的 Podmaster 节点额外安装 Docker 负责跑 Harbor两者各司其职互不干扰。你不用担心 Docker 和 containerd 会抢占容器资源它们各自管理自己的容器和镜像只共用宿主机的内核和网络命名空间。2.2 containerd 配置中有两个容易踩的默认值openEuler 安装 containerd 后如果直接启动服务kubelet 大概率会在预检时给你报 cgroup driver 的问题。原因是 containerd 的默认配置里SystemdCgroup是 false而 openEuler 这种 systemd 发行版上kubelet 默认也使用 systemd cgroup driver两边不一致就没办法正常管理 Pod 资源。我用以下方式生成并修改配置mkdir -p /etc/containerd containerd config default | tee /etc/containerd/config.toml打开/etc/containerd/config.toml找到SystemdCgroup那一项从 false 改成 true[plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] SystemdCgroup true第二个容易出问题的地方是沙箱镜像 pause。containerd 默认的 pause 镜像地址是registry.k8s.io/pause:3.x。如果你的实验环境访问公网镜像仓库有问题kubeadm init时会卡在拉取 gcr.io 镜像这一步。这时可以先在另一台能正常访问公网的机器上把镜像保存成 tar 包传入节点后通过以下命令导入到 containerd 的 k8s.io 命名空间中ctr -nk8s.io images import pause.tar coredns.tar etcd.tar注意-nk8s.io这个参数不能省。kubelet 通过 CRI 拉取镜像时只会读取 containerd 中 k8s.io 命名空间内的镜像。如果你用ctr images import而不加参数镜像会导入到默认命名空间kubelet 依然会提示找不到镜像。这个细节平时不容易发现但一旦离线部署它是最常见的拦路虎之一。3. kubeadm 部署集群从初始化到 worker 节点加入3.1 三个二进制版本必须统一不要随手 yum install 最新版Kubernetes 集群对版本一致性要求较高尤其 kubeadm、kubelet、kubectl 三个二进制要尽量保持同一个版本。如果 kubeadm 是 1.29kubelet 却是 1.28初始化时虽然能通过部分检查后续升级或节点加入时会出现各种兼容性问题。安装前确定一个版本号比如 1.28.5然后通过包管理工具安装指定版本。给 kubelet 设置开机自启但此时先不要启动服务因为还没有集群配置kubelet 启动会一直报错等待中属于正常现象不用担心。网络方面三个节点需要加载br_netfilter内核模块并设置桥接流量转发参数否则 kubelet 会警告 iptables 无法处理桥接流量modprobe br_netfilter cat EOF | 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 sysctl --system这些不是玄学K8s 的 Service 通过 iptables 规则做流量转发如果宿主机不开启桥接流量经过 iptables 的选项Pod 之间以及 Service 的访问就会遇到随机超时。3.2 kubeadm init 参数怎么定token 过期怎么办在 master 节点执行初始化时我使用的参数组合是kubeadm init \ --apiserver-advertise-address192.168.100.101 \ --pod-network-cidr10.244.0.0/16 \ --kubernetes-versionv1.28.5--apiserver-advertise-address是集群 API Server 的监听地址必须写 master 的内网 IP不能写成 127.0.0.1否则 worker 节点无法加入。--pod-network-cidr是给 Pod 分配的网段这个网段不能和你的宿主机网段冲突我这里用 10.244.0.0/16目的是配合后面 Calico 的默认配置。初始化成功后把管理配置文件复制到当前用户目录下mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config然后会看到一段kubeadm join命令里面带了 token 和证书 hash。如果当时没保存之后可以用kubeadm token create --print-join-command重新生成。token 默认 24 小时过期学习环境里常常隔天回来想加一台节点结果发现 token 失效了这很正常重新生成即可。worker 节点加入时确保节点上的容器运行时已经启动并配置好。如果 join 一直卡住通常是网络插件还没安装或者节点无法访问 master 的 6443 端口。3.3 网络插件选择Calico 比 Flannel 更适合学习集群初始化完成后Master 节点会处于 NotReady 状态这时候要安装 Pod 网络插件。Calico 和 Flannel 各有优势Flannel 部署极简只负责给 Pod 提供网络Calico 则基于 BGP 实现自带 NetworkPolicy 支持学到的知识在生产环境更通用。我给学习环境选了 Calico虽然它对内核模块有要求但 openEuler 默认内核都满足。Calico 的安装方式很直接把官方 manifest 文件下载到本地后执行kubectl apply -f calico.yamlCalico 默认使用 IPIP 模式能够自动读取 kubeadm 初始化时传入的--pod-network-cidr。如果网络和 CIDR 有出入后面 Pod 内互相访问会不通。装完后用kubectl get pods -n kube-system观察等calico-node和coredns变成 Running再查看节点状态kubectl get nodes此时 master 应该是 Ready。如果你的实验环境里防火墙规则比较复杂建议在学习阶段直接关闭 firewalld 或把 6443、10250、30000-32767 这几个端口放通。生产环境当然可以做精细化收口但学习环境把时间花在查防火墙规则上并不值得。4. Harbor 部署与证书一次把 HTTP 迁移到 HTTPS 的完整闭环4.1 Harbor 安装包的选择和目录规划Harbor 官方提供了在线安装包和离线安装包。学习环境建议下载离线包里面已经包含 Harbor 镜像部署过程不会因为网络问题中断。离线包解压后主要文件就几个harbor.yml.tmpl是配置模板prepare是配置生成器install.sh是部署脚本。在动手之前先在 master 上规划一个数据目录。Harbor 会把数据库、Redis、镜像存储、证书全部写到这个目录里和 Harbor 安装包所在目录要分开便于将来备份和升级。我的建议是至少 50G 以上空间因为 Docker 镜像越来越大学习过程也不会只用一两个小镜像。Harbor 默认使用 80 和 443 端口但我知道很多人的环境里端口早就被其他服务占了。为了避免冲突可以改成 8080 提供 HTTP、8443 提供 HTTPS然后统一用 8443 和集群内部交互这样后续 containerd 配置起来也方便。4.2 自签 CA 应该怎么签才能让所有节点都认这是整个部署链路里最容易让人迷糊的一段。Harbor 需要一个 HTTPS 证书而这个证书又不能随便用 IP 自签一下就行因为后续 containerd、docker、curl 访问 Harbor 时都会校验证书链。如果你的 Harbor 证书不是由受信任的 CA 签发就得手动把签发该证书的 CA 证书加入所有需要拉镜像的节点。我的做法是在 master 上创建一个内网 CA然后用这个 CA 给 Harbor 签一张带 SAN 的服务器证书。这样集群里每个节点只需要信任这一个 CA 根证书就能间接信任 Harbor 的服务器证书。生成 CA 和服务器证书的参考过程如下:mkdir -p /data/certs cd /data/certs # 生成 CA 私钥和根证书 openssl genrsa -out ca.key 4096 openssl req -x509 -new -nodes -key ca.key -subj /CNharbor-ca -days 3650 -out ca.crt # 生成 Harbor 服务器私钥和 CSR openssl genrsa -out harbor.key 2048 openssl req -new -key harbor.key -subj /CN192.168.100.101 -out harbor.csr因为我的 Harbor 是用 IP 地址访问证书的 SAN 里必须包含这个 IP。新建一个harbor.ext文件写入下面的扩展配置basicConstraintsCA:FALSE keyUsage digitalSignature, nonRepudiation, keyEncipherment subjectAltName alt_names [alt_names] IP.1 192.168.100.101 DNS.1 k8s-master然后用 CA 签发服务器证书openssl x509 -req -in harbor.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ -out harbor.crt -days 1825 -extfile harbor.ext很多旧教程会直接用 openssl 的-subj签一个只包含 CN 的证书没有 SAN 扩展。这在老旧环境可能能用但新版容器运行时会直接拒绝报x509: cannot validate certificate for IP because it doesnt contain any IP SANs。SAN 扩展这个问题我建议一步到位写进生成流程。4.3 Harbor 的 HTTP 改 HTTPS为什么改了配置却总是不生效把证书放到/data/certs后编辑harbor.yml。默认模板里 HTTP 和 HTTPS 是同时启用的我不建议把 HTTP 完全关掉保留一个 8080 端口做本地调试很方便。关键 HTTPS 部分如下hostname: 192.168.100.101 http: port: 8080 https: port: 8443 certificate: /data/certs/harbor.crt private_key: /data/certs/harbor.key这里要强调一个很多教程不讲的执行细节修改harbor.yml后不能直接docker compose up -d因为 Harbor 的容器编排配置不是启动时现读 yml 的而是由prepare脚本把 yml 和证书信息渲染成最终配置。如果你只改 ymlHarbor 的 nginx 容器仍然用旧配置运行证书自然也不会更新。正确的操作流程是docker compose down -v ./prepare docker compose up -d如果看到日志里提示证书相关错误大概率是证书路径写错或者证书和私钥不匹配。先检查/data/certs目录下的文件权限保证 harbor 用户或当前运行用户有读取权限。然后重新执行一遍上面的三段流程。Harbor 起来后docker ps应该能看到 nginx、core、registry、portal、database、redis 等容器。用curl -k https://192.168.100.101:8443能返回页面内容说明 Harbor 系统本身已经接受 HTTPS 请求。4.4 让节点上 Docker 和 Containerd 信任自签证书Harbor 自己的 HTTPS 通了不代表集群节点能直接拉镜像。每个节点向 Harbor 发起 TLS 连接时会校验 Harbor 的证书是否由本机信任的 CA 签发。所以要把刚才生成的ca.crt复制到每一个需要访问 Harbor 的节点上然后让系统级 CA 信任它。openEuler 和 CentOS 类似信任新增 CA 的方式是cp ca.crt /etc/pki/ca-trust/source/anchors/ update-ca-trust这之后curl 和 wget 访问 Harbor 就不会报证书错误了。但 Docker 的校验有自己的证书目录它不只看系统信任还会去看/etc/docker/certs.d/registry/下的证书。所以在需要执行 docker login 的机器上还要单独建目录mkdir -p /etc/docker/certs.d/192.168.100.101:8443 cp ca.crt /etc/docker/certs.d/192.168.100.101:8443/ca.crt路径里的地址端口必须和你要 login 的地址完全一致。如果 docker login 写的是 8443 端口这里目录名也要带 8443。很多人忽略这一点配置了系统信任后 docker login 仍然报unknown authority就是卡在这个目录结构上。对于 containerd信任方式则是在配置文件里声明 registries。业界常见做法是直接把 Harbor 地址加到config.toml的 hosts 区但如果你已经让系统信任了 CAcontainerd 会读取系统信任链不需要额外指定 insecure 跳过验证。这一点我不会建议你为了图省事把 Harbor 配成insecure_skip_verify因为学习环境的本意就是掌握正常的信任链路跳过验证等于绕过了这次练习中最有价值的部分。5. 让 Kubernetes 从 Harbor 拉镜像私有仓库的完整使用链路5.1 把普通镜像转存到 Harbor 私有项目集群跑起来、Harbor 也正常之后真正验证链路的方式是让 K8s 从一个非公网的镜像地址启动 Pod。为了方便演示我先在 master 节点上拉一个 nginx 镜像再把它推到 Harbor 的项目里。Harbor 默认自带一个公开项目library如果把镜像放到公开项目里K8s 节点不需要凭证就能拉取。还有一类项目是私有的需要配置 imagePullSecret 才能访问。学习时我建议两个都试一遍。推送镜像的基本流程是docker pull nginx:1.26 docker tag nginx:1.26 192.168.100.101:8443/demo/nginx:1.26 docker login 192.168.100.101:8443 -u admin docker push 192.168.100.101:8443/demo/nginx:1.26这里要提前在 Harbor UI 上创建一个demo项目并且把访问级别设为私有否则 push 的时候 Harbor 会告诉你项目不存在或者没有权限。5.2 私有项目拉取镜像必须配置 imagePullSecret如果项目是私有的K8s 节点直接拉镜像会收到 401 或 403 错误。创建 Secret 的方式如下kubectl create secret docker-registry harbor-secret \ --docker-server192.168.100.101:8443 \ --docker-usernameadmin \ --docker-password你的密码这里有个细节--docker-server必须和 Pod yaml 里 image 的地址前缀完全一致不能写harbor.local却在镜像地址里写 IP否则 Secret 不匹配拉取同样失败。一个最小验证用的工作负载可以是apiVersion: apps/v1 kind: Deployment metadata: name: nginx-test spec: replicas: 1 selector: matchLabels: app: nginx-test template: metadata: labels: app: nginx-test spec: imagePullSecrets: - name: harbor-secret containers: - name: nginx image: 192.168.100.101:8443/demo/nginx:1.26 ports: - containerPort: 80执行kubectl apply -f nginx-test.yaml后通过kubectl get pods观察状态。如果 Pod 能进入 Running说明整个链路全部打通containerd 发起拉取、走 HTTPS 访问 Harbor、Harbor 完成凭证校验、返回镜像层。如果一直 ImagePullBackOff用一个加长执行时间的命令查看容器事件原因通常会在事件里写得很清楚kubectl describe pod nginx-test常见错误无非三种地址写错、Secret 缺失或 Server 名不一致、节点不信任 Harbor 证书。照着排查逻辑很快就能定位到问题。5.3 如果想把 kubeadm 初始化时的组件镜像也放到 Harbor这套环境未来还可以进一步演进把 kubeadm 初始化 K8s 集群时需要的 kube-apiserver、kube-controller-manager、etcd、coredns、pause 等镜像全部转存到 Harbor 的library项目里。这样以后每次搭建新集群不需要在节点上依赖公网镜像仓库直接通过--image-repository192.168.100.101:8443/library参数指向 Harbor 即可。这个演进方向对学习 CI/CD 非常有帮助。一旦你手里有了一套能自主提供 K8s 组件镜像的 Harbor你就在本地构建了一套完整的“镜像中转站”。后续在 Jenkins 里执行 docker build、push 到 Harbor、再由 K8s 拉取部署整个链路都能在隔离网络里跑通和真实企业环境的工作方式几乎一致。6. 装完以后别急着删虚拟机一些维护心得和常见坑6.1 Dashboard 的安装和访问方式很多同学搭好集群后的下一个需求就是装 Kubernetes Dashboard。官方推荐使用 recommended.yaml执行 apply 即可。Dashboard 默认使用 HTTPS而且外部直接通过 NodePort 暴露并不安全学习阶段我建议用 port-forward 的方式kubectl -n kubernetes-dashboard create token admin-user kubectl -n kubernetes-dashboard port-forward svc/kubernetes-dashboard 8080:443浏览器访问https://127.0.0.1:8080用上面那条命令生成的 token 登录。如果你访问时浏览器提示证书错误多半是 Dashboard 的自签证书不受信任用 port-forward 时不会经过真正的域名证书校验加一个例外继续即可。Dashboard 能正常看到 Node 和 Pod 状态后对基础监控和命名空间管理会直观很多。6.2 定期清理镜像和数据目录避免把磁盘打满学习环境最容易忽略的运维问题是磁盘空间。Harbor 的镜像仓库有个特点默认回收策略不删除未引用的上传层级即使你在 UI 上删除了某个镜像实际存储文件的清理还需要定时执行 Harbor 的垃圾回收任务。我在连续测试一个月后发现 master 节点磁盘突然从 60G 降到 5G排查近半小时才意识到是 Harbor 不断积累的镜像层把磁盘占满了。日常维护建议至少包含三件事把镜像 tag 和 repositoy 都命名清楚不要全部丢在latest里定期用docker system prune清理 node 上没有任何容器引用的镜像缓存在 Harbor 里手动执行垃圾回收或配置定时清理。如果你还想做得更专业可以把 Harbor 的数据目录单独挂载一个虚拟磁盘这样不会因为磁盘满而影响整个虚拟机。6.3 整个环境快速重建的备份逻辑学习环境免不了反复折腾但也不是每次都要从 openEuler 安装开始。我的备份策略分两层虚拟机层面打 VMware 快照可以秒级回到集群初始化前的干净状态Harbor 数据层面则定期把/data/harbor目录打成 tar 包或者只备份/data/harbor/database和/data/harbor/secret两个关键子目录。以后如果换了机器把备份拷到新环境的相同路径重新执行docker compose up -dHarb