ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Kubernetes 私有镜像仓库实战:从 Harbor 搭建到节点拉取全链路

Kubernetes 私有镜像仓库实战:从 Harbor 搭建到节点拉取全链路 我在多个生产环境里帮团队落地过 Kubernetes 集群其中“镜像到底放哪”这个问题几乎是每个初建集群的团队都会卡住的点。直接用 Docker Hub 吧拉取慢、有配额限制尤其在内网环境里根本没法用自己随便搭个 Registry 呢又缺权限管理、镜像清理这些正经仓库该有的能力。这篇文章就围绕标题里的“Kubernetes 专项搭建私有 Harbor 本地仓库”这件事把从环境准备到 K8s 节点拉取镜像的完整链路拆开讲清楚包括为什么选 Harbor、证书目录怎么配、containerd 怎么对接、以及 KubeKey 装集群时怎么把离线包推进 Harbor 这类实操细节。适合正在搭 K8s 环境、或者在搞内网部署流水线的运维和开发同学参考。1. 为什么 Kubernetes 环境需要私有 Harbor先扯一个很多新手会踩的误区我明明有 Docker Hub也有阿里云镜像加速为什么还要折腾一套私有 Harbor如果你只是在本机 docker run 一个 nginx那确实用不上。但一旦上了 Kubernetes事情就变了。K8s 的调度是“分散”的你的 Pod 可能被调度到集群里的任意一台 Node 上。这意味着集群里的每一台机器都得能拉到你业务用的那个镜像。如果你把镜像放在 Docker Hub 的私有仓库里每个节点都得配置账号密钥还受限于 Docker Hub 的拉取频率限制——我记得匿名用户每 6 小时只能拉 200 次一个稍微大点的集群、发布频繁一点分分钟就被限流。再说内网环境压根连不上外网这时候你必须在集群内部有一个所有节点都能访问的镜像源。那为什么不是随便开一个带认证的 RegistryHarbor 的价值在于它不只是“存镜像”的它有基于项目的权限隔离有镜像漏洞扫描用的 Trivy有镜像签名和审计日志还有回收策略。我们在实际运维中最常用的功能是“镜像保留策略”——比如“保留每个项目最近 30 个 tag其余自动清理”这功能能让你在长期迭代中不用手工去清磁盘。生产环境里磁盘被 /var/lib/docker 或 /var/lib/containerd 撑爆几乎是每个集群都遇到过的故障Harbor 的清理策略能缓解这个问题。还有一个很实际的原因Harbor 天然就是为 K8s 设计的。K8s 的 kubelet 在拉取私有仓库镜像时支持用 imagePullSecrets 指定 docker-registry 类型的 Secret而 Harbor 的 Robot Account机器人账号机制正好可以给每个项目创建最小权限的专用拉取账号。这让“开发能推送、节点能拉取、权限互不干扰”这件事变得非常顺滑。我在选型时也对比过 Nexus 的 Docker 仓库。Nexus 是通用制品库也能存镜像但它的 UI 和权限模型偏开发库风格容器镜像的 Tag 管理、层layer清理之类的体验都远不如 Harbor。如果团队已经在用 Nexus 管 Maven 或 npm 包那顺带用一下也行但如果你是专门为 K8s 搭镜像仓库Harbor 是更省心的选择。2. 环境准备、版本选型与安装包获取2.1 硬件与系统要求先聊聊硬件。Harbor 本身算不上重它由多个容器组成nginx、portal、registry、redis、postgres/database、trivy、core 等模块但如果你启用了漏洞扫描模块内存占用会明显上涨。我常用的评估标准是2 核 4G 起步4 核 8G 比较舒服再高看并发。如果你只是自己测试、不启用扫描2G 内存也能跑起来就是 docker ps 的时候看到一堆容器会觉得有点挤。操作系统方面CentOS 7 是我见过的最常见的部署环境但 CentOS 7 默认的内核版本是 3.10跑 Docker 20.10 和 Harbor 都没问题。这里有一个前置条件机器务必能正常访问外网来拉取 Harbor 的依赖镜像除非你打算一台完全离线的机器去导入离线包。正常情况下我们用离线安装包安装 Harbor因为它体积大约在 600MB 到 1GB 左右包含了所有 Harbor 组件镜像安装时直接 load 进去非常省事。2.2 Docker 与 Docker Compose 的版本选择Harbor 的安装底层依赖两个东西Docker 引擎和 Docker ComposeV2 版本用docker compose子命令V1 版本是docker-compose二进制。我用的是 Docker 20.10.17 Docker Compose v2.12.2这套组合在 CentOS 7 上很稳。Docker 20.10 是兼容性非常好的版本支持 containerd 快照器、也支持配置 registry-mirror是 Harbor 官方测试覆盖最成熟的版本。这里有个要注意的细节Harbor 离线安装包里的install.sh脚本会检测 docker 和 docker-compose 是否可用。如果你装的是 Docker Compose V2它检测的是docker compose version如果是 V1它检测的是docker-compose version。为了少踩坑我建议直接用 V2 插件形式安装并且确认docker compose version能正常输出。装 Docker 的步骤不复杂但有几个点容易出错。CentOS 7 上不要直接yum install docker那个版本是 1.13太老了Harbor 装了也容易出诡异问题。正确姿势是先装 yum-utils然后配置 Docker 官方 yum 源或国内镜像源再安装 docker-ce、docker-ce-cli、containerd.io。# 卸载旧版本 sudo yum remove -y docker docker-client docker-common docker-engine # 安装依赖工具 sudo yum install -y yum-utils # 配置国内 Docker 源这里用阿里云镜像站 sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # 安装指定版本20.10.17 sudo yum install -y docker-ce-20.10.17 docker-ce-cli-20.10.17 containerd.io # 启动并设置开机自启 sudo systemctl start docker sudo systemctl enable docker装完记得确认一件事docker info | grep Cgroup Driver如果显示的是cgroupfs而你的 K8s 是用 systemd 的 Cgroup Driver后续会有冲突。尽早在/etc/docker/daemon.json里统一改成exec-opts: [native.cgroupdriversystemd]。这个细节如果不处理后面 KubeKey 初始化预检的时候会直接给 warning。2.3 Harbor 离线安装包获取Harbor 的 GitHub Releases 页面github.com/goharbor/harbor/releases会提供两种包在线安装包harbor-online-installer-v2.x.x.tgz和离线安装包harbor-offline-installer-v2.x.x.tgz。区别很好理解在线包体积小安装时现场从 Docker Hub 拉取镜像离线包把所有镜像都打进了一个harbor.v2.x.x.tar.gz压缩文件里安装脚本会直接 docker load。生产环境我建议你下载离线包因为安装过程更可控而且后续如果要多台机器部署这个离线包可以复用。版本选型上Harbor 2.8.x、2.9.x、2.10.x 都是当前主力版本我个人推荐 2.9.x 或 2.10.x。2.10 开始默认启用了新的 Webhook 和 SBOM 相关功能但整体稳定性和 2.9 差别不大。版本号尽量别太激进因为 Harbor 大版本升级涉及数据库 schema 迁移很麻烦不是必要情况不值得折腾。下载完成后解压tar -xzf harbor-offline-installer-v2.9.4.tgz cd harbor解压后目录里会有一个harbor.yml.tmpl模板文件。注意Harbor 安装脚本只认harbor.yml所以你必须先复制一份出来再做修改cp harbor.yml.tmpl harbor.yml3. Harbor 核心配置与 HTTPS 证书方案3.1 harbor.yml 中最关键的配置项harbor.yml是安装的“命门”里面每一项都直连安装结果。我不建议你用默认配置直接跑因为默认 hostname 是reg.mydomain.com你要是不改装完连 UI 都访问不了。先看最核心的几项# 对外服务的访问地址。可以是 IP也可以是域名。 hostname: 192.168.1.100 # HTTP 配置默认是 80 端口 http: port: 80 # HTTPS 配置生产环境强烈建议启用 https: port: 443 certificate: /data/cert/harbor.crt private_key: /data/cert/harbor.key # 管理员初始密码 harbor_admin_password: Harbor12345 # 数据库密码 database: password: root123 max_idle_conns: 50 max_open_conns: 100 # 数据持久化目录 data_volume: /data/harbor其中hostname这个地方有个隐藏坑配成 IP 也可以但 Harbor 会根据这个值生成访问 URL 和 Docker 登录地址。如果你是纯内网用、没有内部 DNS 服务直接写 IP 是最稳的比如192.168.1.100后续所有节点都通过这个 IP 访问仓库。如果有内部域名那就写域名比如harbor.internal.example.com这样后续给 K8s 节点签证书时更方便。3.2 自签名证书的生成与信任配置Kubernetes 节点上 prefer 的镜像拉取方式默认走 HTTPS。Harbor 如果只开了 HTTPkubelet 拉镜像时会因为“HTTP 协议不被支持”而直接拒绝除非你给 containerd 配置了skip_verify或者在/etc/docker/daemon.json里加了insecure-registries。所以我的建议是给 Harbor 配 HTTPS哪怕用的是自签名证书。这比让每台节点在 containerd 里跳过 TLS 校验更安全也更符合生产习惯。我们在内网环境没有公共 CA 签发证书所以选择自建 CA 然后签发一张服务器证书。脚本大致长这样# 1. 生成 CA 私钥 openssl genrsa -out ca.key 4096 # 2. 生成 CA 根证书 openssl req -x509 -new -nodes -sha256 -days 3650 \ -subj /CNHarbor Local CA \ -key ca.key -out ca.crt # 3. 生成 Harbor 服务器私钥 openssl genrsa -out harbor.key 2048 # 4. 生成证书签发请求CSR openssl req -new \ -subj /CN192.168.1.100 \ -key harbor.key -out harbor.csr # 5. 创建扩展文件把 IP 和域名都加到 SAN 里这一步特别关键 cat ext.cnf EOF subjectAltName IP:192.168.1.100, DNS:harbor.internal.example.com EOF # 6. 用 CA 签发服务器证书 openssl x509 -req -in harbor.csr \ -CA ca.crt -CAkey ca.key -CAcreateserial \ -days 825 -sha256 -extfile ext.cnf -out harbor.crt第 5 步不加 SAN 是这里最高频的翻车点现在很多客户端Go 语言写的 Docker、containerd认证时会校验 SAN 字段只有 CNCommon Name会被直接拒绝报错通常是 “x509: certificate relies on legacy Common Name field”。把生成的ca.crt、harbor.crt、harbor.key放到统一目录例如/data/cert/。然后在harbor.yml里将 HTTPS 段的两个路径指过去。这里我提醒一下安装脚本install.sh会检查这两个路径的文件是否存在如果路径写错它会在prepare阶段报错退出。3.3 执行安装脚本配置好harbor.yml之后执行sudo ./install.sh这个脚本会做几件事加载离线包里的 Docker 镜像、生成 docker-compose.yaml、创建 Harbor 依赖的 volume 和网络、然后启动全部容器。整个过程大概持续 1-3 分钟取决于磁盘速度。装完验证一下docker ps你应该能看到harbor-core、harbor-db、harbor-portal、harbor-registry、nginx、redis等容器处于 Up 状态。然后在浏览器访问https://192.168.1.100用admin/Harbor12345登录如果没改默认密码的话。如果只是内网测试不想每次访问浏览器都提示证书不可信可以把这个自建的ca.crt加入你本机或客户端的信任列表。至于生产环境后续如果上了公网域名建议直接换正规 CA 签发的证书流程一样只是把自签名的harbor.crt内容换成 CA 签发的那份即可。4. 与 Kubernetes 节点拉取链路全打通4.1 节点侧配置 containerd 信任自签名证书Harbor 一旦启用 HTTPS 且证书是自签名的集群里的每个 kubelet 节点在拉镜像时就会面临一个信任问题。K8s 1.26 及之后的版本默认容器运行时是 containerd它不再像旧版那样依赖 Docker 引擎。所以你要在每一台 Node 上修改/etc/containerd/config.toml告诉它“访问某个地址时要使用哪些证书或者干脆跳过校验”。拿 KubeKey 创建的集群举例KubeKey 在初始化集群时已经给各节点装好并配置了 containerd配置里默认的 snapshotter 是overlayfssandbox 镜像是pause:3.9之类。我们需要在/etc/containerd/config.toml中找到[plugins.io.containerd.grpc.v1.cri.registry]这一段按下面方式配置[plugins.io.containerd.grpc.v1.cri.registry] [plugins.io.containerd.grpc.v1.cri.registry.configs] [plugins.io.containerd.grpc.v1.cri.registry.configs.192.168.1.100] [plugins.io.containerd.grpc.v1.cri.registry.configs.192.168.1.100.tls] ca_file /etc/containerd/harbor-ca.crt cert_file /etc/containerd/harbor-cert.crt key_file /etc/containerd/harbor-cert.key [plugins.io.containerd.grpc.v1.cri.registry.headers] X-Forwarded-Proto https如果只是内网快速验证尤其是证书里 SAN 正好覆盖了你的 Harbor 地址的情况下你也可以配置insecure_skip_verify true来跳过验证[plugins.io.containerd.grpc.v1.cri.registry.configs.192.168.1.100.tls] insecure_skip_verify true但我的建议是skip_verify 只用于临时测试。一旦你要接 CI/CD 流水线镜像名里包含地址、端口、项目名任何拼写错误都很难排查略过验证会让“我到底连对了没”这个问题更模糊。而且安全审计时裸奔的 registry 绝对是第一个被质疑的点。配置完以后务必重启 containerdsudo systemctl restart containerd然后可以在节点上手工拉一次镜像试试crictl pull 192.168.1.100/library/nginx:1.26如果这一步报错一定先怀疑证书信任和地址访问问题用curl -v https://192.168.1.100/v2/看看服务端证书是否正常返回。4.2 如果节点上是 Docker 和 kubelet 组合如果你的集群还是老式的 Docker 运行时K8s 1.26 及以上的 kubelet 不再推荐直接对接 Docker但 containerd 封装了 dockershim 的场景偶尔还有或者是 Docker 环境中调试那么配置路径是/etc/docker/daemon.json。格式如下{ insecure-registries: [192.168.1.100], registry-mirrors: [] }重启 Docker 后用 docker login 验证docker login 192.168.1.100 -u admin -p Harbor12345insecure-registries一旦配置docker 会以明文 HTTP 或非信任证书访问仓库。这里请注意一旦一个地址被列进 insecure-registries它就不会再走系统信任链而是无条件信任该地址的证书。所以这个配置也有限制条件仅用于内网可信网络。4.3 创建 Namespace、项目与镜像推送Harbor 的逻辑模型里“项目Project”是第一层命名空间。比如我推一个镜像进 Harbor最终镜像名是192.168.1.100/k8s-apps/backend:v1.0.0其中k8s-apps就是 Harbor 里的一个项目名。在 Harbor UI 中手动创建项目或者在测试环境中你也可以用 admin 账户直接创建。我习惯的做法是给每个业务线建一个独立项目比如library对应 Docker Hub 官方镜像的收藏项目、bigdata、middleware然后为每个项目创建独立 Robot 账号。Robot 账号是一串类似projectnamerobotname$token的凭证它只能访问特定项目做特定操作拉取或推送比共享 admin 密码安全得多。从本地向 Harbor 推镜像的标准动作是# 先给镜像打个带 Harbor 地址前缀的 tag docker tag nginx:1.26 192.168.1.100/k8s-apps/nginx:1.26 # 登录 docker login 192.168.1.100 -u admin -p Harbor12345 # 推送 docker push 192.168.1.100/k8s-apps/nginx:1.26然后到 Harbor UI 的k8s-apps项目仓库页里就能看到这个镜像了。4.4 kubelet 拉取私有仓库镜像imagePullSecrets 配置镜像推到 Harbor 之后K8s 节点默认无法直接拉取私有项目里的镜像因为需要认证信息。K8s 官方支持的方案是在 Namespace 里创建一个docker-registry类型的 Secret然后在 Pod 模板里通过imagePullSecrets引用它。创建 Secret 的命令kubectl create secret docker-registry harbor-registry-secret \ --docker-server192.168.1.100 \ --docker-usernamerobot$k8s-apps \ --docker-password从Harbor Robot账号里复制出的token \ --docker-emaildevopsexample.com \ -n your-namespace注意docker-username里的$符号有特殊含义命令行里不加引号会被 shell 转义掉导致用户名解析不正确。建议给整段参数加单引号。然后在 Deployment 里声明spec: template: spec: imagePullSecrets: - name: harbor-registry-secret containers: - name: app image: 192.168.1.100/k8s-apps/backend:v1.0.0如果你集群里的每个命名空间都要拉镜像一个个创建 Secret 太繁琐了。可以把 Secret 创建在kube-system或某个公共命名空间里然后用--namespace参数在多个命名空间里复制一份或者用 Kubernetes 的Sealed Secrets、External Secrets这类工具统一管理。我这里多说一句别把 admin 密码放到 Secret 里Robot 账号最小权限原则才是正道。5. KubeKey 离线部署时怎么把 tar.gz 包里的镜像推到 Harbor这个是我在标题相关热词里看到的也是很多人会遇到的场景。KubeKeykk是 KubeSphere 生态里的集群部署工具它支持离线部署会先把集群需要的所有镜像导出成一个tar.gz格式的 artifact 包。你下载到一个.tar.gz之后如果内网已经搭好了 Harbor并不需要把每个节点都导入这个 artifact而是可以先把镜像解包出来、推到 Harbor然后让 KubeKey 直接从 Harbor 拉。这样集群节点本身不再需要保存离线包流程度会顺很多。KubeKey 比较新的版本里有个kk artifact image push子命令可以直接把 artifact 里的镜像推送到一个 registry。用法大致是kk artifact image push \ --artifact ./kubesphere-offline-v3.4.1.tar.gz \ --registry 192.168.1.100 \ --username admin \ --password Harbor12345 \ --project library它会自动解包、登录、给镜像重打 tag 并推送。这里有个细节KubeKey 内置的推送逻辑会把镜像推送到192.168.1.100/project/原始镜像名这种格式下而不是保留原始第三方前缀比如docker.io、registry.k8s.io这一点在你之后用kk create cluster --with-kubesphere时需要同步修改镜像的 rewrite 规则或临时 tag否则集群拉取不到镜像。如果你的 KubeKey 版本比较旧、没有image push子命令那就走手动流程解压 artifacttar -xzf kubesphere-offline-v3.4.1.tar.gz找里边的 images 列表一般有个images目录或manifest文件用docker load -i xxx.tar把镜像载入本地 Docker逐个docker tag把镜像名改成192.168.1.100/library/xxx:tagdocker push推上去这个方法虽然繁琐但胜在可控。你可以在中途过滤掉不需要的镜像只推必要的。另外有个常见情况artifact 里有些镜像的 tag 是v1.26.0这种没有时间戳的格式推到 Harbor 时同一个名字会被覆盖。这时你需要给镜像打好带构建号或日期的 tag避免后续回滚时找不到历史版本。Harbor 里对应的项目配置里打开“自动打标签”功能也行但我们一般更倾向于在推送时就把版本控制好。实操里还有一个经验KubeKey 部署集群时会在节点上预执行 preflight 检查日志里出现[init] using kubernetes version: v1.26.0 [preflight] running pre-flight checks是正常的。如果 preflight 阶段卡住或者报错先检查节点能否访问 Harbor 的 443 端口、能否成功解析 Harbor 域名或 IP还有 containerd 配置是否已经生效。因为 KubeKey 拉镜像的组件其实也是走 containerd 接口containerd 到 Harbor 的链路不通你 artifact 推上去也是白搭。6. 常见问题与排查技巧实录6.1 Harbor 安装时报 “The nginx container is not running”Harbor 装完后docker ps看到 nginx 容器反复重启多半是证书或端口冲突。优先看日志docker logs harbor-nginx如果是 “configuration file /etc/nginx/nginx.conf test failed”一般是harbor.yml里配置的证书路径不对或证书内容异常。重新检查一下/data/cert/下的证书文件权限确保 644 权限Docker 容器内用户才能读。6.2 x509 证书信任报错节点上执行 crictl pull 时如果报类似x509: certificate signed by unknown authority说明 containerd 不认你这个 CA。要么把 CA 文件拷贝到节点推荐要么临时 skip_verify。之前我遇到过一种很隐蔽的情况证书 SAN 里 IP 写的是192.168.1.100但 Harbor 实际监听在127.0.0.1节点解析 Harbor 域名时走 DNS 解析到了别的 IP导致证书校验必然失败。所以配置域名时DNS 解析正确性和 SAN 内容必须完全对得上。6.3 docker login 失败401 UnauthorizedHarbor 的 Robot 账号密码串是类似eyJhbGciOiJSUzI1NiIsImtpZCI6...的一大串直接拿来 docker login 时会因为里面包含特殊字符被 shell 处理掉。我推荐的做法是先把密码写到一个临时文件里然后docker login 192.168.1.100 --username robot$app --password-stdin harbor-pwd.txt如果是 UI 上创建的用户确认你登录的是正确的项目而不是整个 Harbor 系统的 admin 权限。Harbor 的项目成员权限有项目管理员、开发者、访客等角色访客只能拉取不能推送这也会导致 docker push 时 401。6.4 K8s 节点无法拉取镜像ImagePullBackOff排障标准三步走kubectl describe pod pod-name看 Events 里最底部的具体报错crictl pull image-name手工在节点上拉一次看是否同样是 x509 或 401 错误确认 Secret 的 docker-server 地址与镜像前缀完全一致比如 Secret 里写的是192.168.1.100但 YAML 里镜像名写的是harbor.internal.example.com/...那肯定拉不到这几类问题我在现场排查时遇到过次数不少有八成以上是“证书没配好”或“仓库地址不一致”这两个原因。6.5 Harbor 磁盘被撑满Harbor 的数据默认存在data_volume目录日积月累肯定会被镜像占满。Harbor 本身不自动清理过期 tag所以定期执行 GCGarbage Collection和 Retention 策略是运维 Harbor 的基本功。在 Harbor 的“管理”里可以配置自动清理任务也可以用脚本定期通过 API 触发 GC。我一般会写一个 cron 任务每天凌晨触发一次 Harbor 的 GC 接口同时根据项目保留策略自动清理旧镜像。有一点要提醒GC 时 registry 会临时变成只读模式最好放在业务低峰期执行。7. 一些实操层面的个人体会搭建私有 Harbor 这个事说起来一行 docker run 就够了但真正落地牵扯到的链路很长——从证书体系、到容器运行时配置、到 K8s 的认证机制每一环都是独立知识域。你不用一次性搞懂所有原理照着文档能跑通是最重要的跑通之后再去想为什么。因为只有先把链路搭起来你才有实际环境去验证证书信任机制、镜像拉取策略这些概念。我自己刚开始接触时最大的感悟是“镜像仓库是集群的基础设施不是开发工具”。它的稳定性直接影响线上发布所以像 Harbor 本身的数据备份/data/database和/data/registry一定要放到可靠的存储上有条件就挂在独立数据盘或 NAS 上别跟系统盘挤在一起。后面节点重启、磁盘扩容、甚至迁移机房时你都会庆幸自己当初做了这个决定。最后再分享一个小技巧Harbor 的配置修改后不用重新执行整个 install.sh直接跑docker-compose down -v不行——-v会删数据千万慎用。正确做法是改完harbor.yml后执行sudo ./install.sh --with-trivy --with-notary或者直接docker-compose up -d重新创建配置容器。我建议把harbor.yml视作重要资产纳入 git 管理每次改完记录变更原因这个习惯能在你排查疑难杂症时省下好几小时。
返回列表