恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Kubernetes私有镜像仓库搭建与containerd拉取配置实践

  • 首页
  • 资讯中心
  • /
  • Kubernetes私有镜像仓库搭建与containerd拉取配置实践

相关资讯

Recova:面向自主机器人操作的、由智能体引导的故障恢复 2026/10/20 17:26:56
STL容器选型与迭代器失效:从原理到工程避坑实践 2026/10/20 17:21:56
三电平NPC逆变器SVPWM仿真:扇区划分、小矢量与中点电位平衡 2026/10/20 17:21:56

最新资讯

pywinauto实战:Windows GUI自动化测试与控件定位指南
Scratch编程素材合集免费下载:从找素材到用素材的完整指南
Cowork插件系统:重构组织工作流的神经突触
棒球“1号位”:投手如何在WBC中主宰比赛
风光联合场景生成:Copula建模与Kmeans削减的MATLAB实战
AI在建筑行业落地全解析:从生成式设计到施工管理

今日推荐

高频变压器实战经验总结:磁芯选型、绕组工艺与故障排查全解析
U-Boot移植的关键:深入理解Kconfig配置系统与依赖关系
步进电机原理与调试全解析:从步距角到丢步排查

本周热门

基于GMM的语音识别系统原理与MATLAB实现详解
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

Kubernetes私有镜像仓库搭建与containerd拉取配置实践

发布时间:2026/10/20 17:26:56
Kubernetes私有镜像仓库搭建与containerd拉取配置实践 我们经常会在 Kubernetes 里碰到一种很尴尬的局面应用代码、编排模板都准备好了唯独镜像还在 Docker Hub 或者某个外部仓库里。一旦集群网络限制、拉取超时、版本覆盖Pod 就卡在ImagePullBackOff看事件全是和镜像拉取有关。这篇文章里的完整链路就是围绕这个问题展开的先在内网 Kubernetes 中部署一个registry:2.7.1的私有镜像仓库再把 containerd 的拉取链路打通最后让应用 Pod 通过 imagePullSecrets 正常从私有仓库拉镜像并跑起来。整个过程中我会把关键配置、命令和踩过的坑都摊开来讲适合已经把 Kubernetes 基础组件玩熟、但对“私有镜像仓库 containerd 运行时”这套组合还比较陌生的同学参考。1. 为什么要在 Kubernetes 里自建 Registry1.1 镜像来源不可控带来的麻烦大多数项目刚开始都直接image: nginx:latest开发者机器上随便 pull 一下看起来没啥问题。但真正进入交付或测试环境后镜像来源就变成了一个隐患公共仓库的访问可能超时网络隔离环境根本到不了外网团队里某个人重新 push 了同名 tag旧版本就再也找不回来。更不要说有些场景要求镜像内部包含内部依赖、基础包、配置文件这些东西放在外部仓库既不安全也不好审计。在 Kubernetes 的视角里镜像从哪个仓库来本质上只取决于两件事节点上的运行时能不能访问到那个地址以及拉取时是否携带了合法的凭证。只要把这两个问题解决镜像从自建仓库拉和从公共仓库拉对 kubelet 来说没有区别。正因为这样私有仓库的价值才非常明显它把“镜像来源”这件事收拢到自己的基础设施里变成一个内网地址。1.2 为什么还要坚持“私有库优先”我身边不少同事会觉得环境里已经装了 Harbor 或者别的制品仓库为什么还要单独折腾一个 Docker Registry其实在很多场景下一个轻量级的 registry 镜像反而更合适它只是一个纯 Go 写的 HTTP 服务内存占用小部署逻辑简单也方便团队内部临时起一个隔离仓库给某个专项使用。没有额外的数据库依赖存镜像就是本地目录里的块文件配合 Kubernetes 的 PVC 就能完成持久化。另外“私有库优先”的好处不只在拉取速度上。当所有镜像都在内网以后节点上的 containerd 只需要配置一个信任域和一套认证信息天然减少了对公网的依赖也让团队能够对镜像做统一的清点、备份和清理。2. 部署前的架构与版本选择2.1 containerd、Kubelet 和 Registry 三者如何配合正常的 Pod 调度流程是kubelet 收到 Pod 定义后发现镜像不存在或策略需要拉取就把这个任务交给位于本机的 CRI 运行时 containerd。containerd 根据config.toml里的 registry 配置决定去哪个地址拉镜像并根据 kubelet 传过来的镜像拉取 Secret 决定用哪组账号。这个链路里有两个独立配置一个是 containerd 对 registry 的全局配置包括证书、认证、镜像加速或非安全跳过校验另一个是 Pod 里的imagePullSecrets它会在 kubelet 调用 containerd 时作为拉取凭证传入。很多人只配置了其中一个导致另一个环节一直静默失败。以这次要部署的 registry 2.7.1 为例图中虽然没有复杂组件但需要注意的依赖点不少DNS 能解析仓库地址节点能连通仓库端口TLS 证书要受信任认证信息要和 htpasswd 文件完全一致。这四个条件缺一个Pod 事件里就会露出对应的报错。2.2 选用 Registry 2.7.1 的取舍初看这个版本会觉得 2.7.1 已经有些年头了。它和后续版本的核心差异主要集中在安全补丁、存储驱动细节和镜像索引处理上。如果完全从零开始我通常会建议直接上官方当前稳定版或者用成熟的制品仓库。但这里有一个现实原因很多正在运行的存量项目、内部依赖脚本、甚至 CI 流水线都是围绕2.7.1这个固定 tag 设计和验证过的升级版本意味着要重新过一遍兼容性测试。所以在下文中我按 2.7.1 来部署重点在于把“私有仓库 containerd 拉取”这条链路跑通。如果你后续要升级只需要切换镜像 tag并注意新版本对配置项格式要求更严格其余思路不受影响。2.3 硬件、网络和存储准备部署前我一般会先确认三件事集群节点的 /etc/hosts 或内网 DNS 里能不能解析registry.internal这个地址所有节点是否都能访问仓库的 5000 端口包括在防火墙和节点安全组里放通存储层是否有可用的 PVC因为镜像数据一旦落到本地磁盘Pod 调度到别的节点就不可见了也会让灰度发布失去意义。对于小规模测试普通的节点磁盘加 PVC 就够了镜像量大或者并发推送高就要考虑独立存储或对象存储后端。这里先按常规的 Kubernetes 持久化方案来。3. 在 Kubernetes 中部署 Registry 2.7.13.1 预先准备证书与认证文件私有仓库一旦在集群内部暴露至少要解决两个问题传输加密和访问控制。最简单的方式是生成一个自签名证书并在所有节点的 containerd 配置里信任这个 CA。证书的 Common Name 要和镜像仓库地址保持一致否则后续拉取时证书校验会失败。openssl req -newkey rsa:2048 -nodes -x509 -days 365 \ -subj /CNregistry.internal \ -addext subjectAltNameDNS:registry.internal,DNS:localhost,IP:127.0.0.1,IP:192.168.1.100 \ -keyout tls.key -out tls.crt这里的192.168.1.100是我示例里的某个节点 IP实际使用时要换成节点地址或虚拟 IP。证书生成好以后创建两个 Secret一个给 registry 进程自己用的 TLS 证书一个给 htpasswd 认证文件。htpasswd 文件的生成比较常见可以直接起一个临时的 registry 容器docker run --rm --entrypoint htpasswd registry:2.7.1 -Bbn admin Admin123 htpasswd-B表示使用 bcrypt 算法这在 Registry 2.7.1 中是被支持的。生成后检查文件内容不要出现多余的空行或 Windows 的 CRLF否则后续登录时会出现很难排查的 401 错误。kubectl create namespace registry kubectl create secret tls registry-tls --certtls.crt --keytls.key -n registry kubectl create secret generic registry-auth --from-filehtpasswd -n registry3.2 持久化存储规划Registry 的核心数据都写在/var/lib/registry下。如果只是临时验证可以直接用 emptyDir但容器重启数据就没了镜像也要重新 push非常难受。实际项目里我建议直接声明 PVC并且尽量把容量规划放宽一点。一个简单的 PVC 示例apiVersion: v1 kind: PersistentVolumeClaim metadata: name: registry-data namespace: registry spec: accessModes: - ReadWriteOnce resources: requests: storage: 100Gi这里的 accessModes 用的是 ReadWriteOnce因为 Registry 副本数一般不需要太多单副本加 PVC 在大多数中小规模环境里完全够用。如果你需要多副本就要考虑共享存储或者对象存储方案。3.3 Deployment 与相关资源落地Registry 本身没有特别复杂的配置核心是几个环境变量。REGISTRY_AUTH 控制认证方式REGISTRY_HTTP_TLS_CERTIFICATE 和 REGISTRY_HTTP_TLS_KEY 指定证书位置REGISTRY_STORAGE_DELETE_ENABLED 决定是否允许通过 API 删除镜像。下面是一个完整的 Deployment 示例我把它放在 namespaceregistry下apiVersion: apps/v1 kind: Deployment metadata: name: registry namespace: registry labels: app: registry spec: replicas: 1 selector: matchLabels: app: registry template: metadata: labels: app: registry spec: volumes: - name: data persistentVolumeClaim: claimName: registry-data - name: certs secret: secretName: registry-tls - name: auth secret: secretName: registry-auth containers: - name: registry image: registry:2.7.1 ports: - containerPort: 5000 name: registry env: - name: REGISTRY_AUTH value: htpasswd - name: REGISTRY_AUTH_HTPASSWD_REALM value: Registry Realm - name: REGISTRY_AUTH_HTPASSWD_PATH value: /auth/htpasswd - name: REGISTRY_HTTP_TLS_CERTIFICATE value: /certs/tls.crt - name: REGISTRY_HTTP_TLS_KEY value: /certs/tls.key - name: REGISTRY_STORAGE_DELETE_ENABLED value: true volumeMounts: - name: data mountPath: /var/lib/registry - name: certs mountPath: /certs readOnly: true - name: auth mountPath: /auth readOnly: true livenessProbe: tcpSocket: port: 5000 initialDelaySeconds: 15 periodSeconds: 10 readinessProbe: tcpSocket: port: 5000 initialDelaySeconds: 5 periodSeconds: 5Registry 的默认端口是 5000不要随意改成其他端口否则后面 Push 和 Pull 的地址要跟着变容易造成混乱。再创建一个 Service。这里我用了 NodePort方便在集群节点上直接测试也让 containerd 可以通过一个稳定地址访问apiVersion: v1 kind: Service metadata: name: registry-svc namespace: registry spec: selector: app: registry ports: - port: 5000 targetPort: 5000 nodePort: 30050 type: NodePort3.4 从集群外访问并验证服务Deployment 和 Service 创建后先验证 5000 端口是否正常kubectl get pods -n registry -o wide curl -k https://192.168.1.100:30050/v2/返回 JSON 里有{}或者一个空目录列表都属于正常。如果直接返回 401说明认证已经生效只是当前还有提供账号。用账号密码再请求一次curl -k -u admin:Admin123 https://192.168.1.100:30050/v2/_catalog看到{repositories:[]}就说明认证配置没问题。接下来把测试镜像推上去docker login registry.internal:5000 -u admin -p Admin123 docker tag nginx:1.21.6 registry.internal:5000/demo/nginx:1.21.6 docker push registry.internal:5000/demo/nginx:1.21.6这里的registry.internal在推送时如果解析不到可以在执行命令的机器上临时写一条 hosts 记录指向节点 IP。4. 配置 containerd 从私有 Registry 拉镜像4.1 先在宿主机解决证书信任containerd 默认不会信任自签名证书。有不少人遇到 Pod 事件里报x509: certificate signed by unknown authority原因就是节点不认这证书导致拉取直接失败。如果你的环境相对固定可以把证书分发到每个节点并更新系统证书库cp registry.crt /usr/local/share/ca-certificates/registry.crt update-ca-certificates但这里有个细节containerd 的 gRPC CRI 插件并不完全依赖系统证书库有些版本还是会校验失败。所以最稳妥的做法还是在 containerd 的 registry 配置里明确指定 CA 文件路径。4.2 修改 containerd 配置先确认 containerd 的配置位置常见的是/etc/containerd/config.toml。不同版本结构稍有差异我以 containerd 1.6.x 和 CRI 插件常见配置为例。在config.toml中找到[plugins.io.containerd.grpc.v1.cri.registry]这一段加入如下配置[plugins.io.containerd.grpc.v1.cri.registry] [plugins.io.containerd.grpc.v1.cri.registry.mirrors] [plugins.io.containerd.grpc.v1.cri.registry.mirrors.registry.internal:5000] endpoint [https://registry.internal:5000] [plugins.io.containerd.grpc.v1.cri.registry.configs] [plugins.io.containerd.grpc.v1.cri.registry.configs.registry.internal:5000.tls] ca_file /etc/containerd/registry.crt insecure_skip_verify false [plugins.io.containerd.grpc.v1.cri.registry.configs.registry.internal:5000.auth] username admin password Admin123这里把认证信息直接写进了节点配置意味着这台节点上的 containerd 进程对所有指向该 registry 的拉取请求都会自动带上这个账号。如果你的安全要求比较高不希望在节点上明文保存密码那就可以不配置auth只配置证书然后让 Pod 通过 imagePullSecrets 传凭证。需要注意的是containerd 1.7 之后更推荐使用/etc/containerd/certs.d/registry-host/hosts.toml的方式来管理仓库配置。每个仓库对应一个目录配置更灵活。下面是一个 hosts.toml 的示例server https://registry.internal:5000 [host.https://registry.internal:5000] ca /etc/containerd/registry.crt client /etc/containerd/registry-client.crt key /etc/containerd/registry-client.key如果你还在用旧版本那上面提到的config.toml里的registry.mirrors和registry.configs就是主要入口。两套方案不建议混着写否则配置会互相覆盖难以排查。4.3 手动用 crictl 验证拉取改完配置后我建议先不要直接去创建 Pod而是先在节点上用crictl手动拉一次镜像。这样能快速把 containerd 侧的问题和 kubelet 侧的问题分开。crictl pull --creds admin:Admin123 registry.internal:5000/demo/nginx:1.21.6 crictl images | grep registry.internal如果这一步能成功说明证书、认证、网络都是通的。如果这里就报错就没必要立刻去看 Pod 事件直接在节点上排查更高效。有些环境里没有 crictl也可以用ctr验证但ctr默认不走 CRI 插件路径不完全一致。我实际调试时更喜欢用 crictl因为 Kubelet 底层也是通过 CRI 接口调用 containerd和crictl pull的路径更接近。4.4 重启 containerd 的注意事项修改配置后自然要重启 containerd。但这里我要泼一盆冷水不要在业务高峰期直接对所有节点执行systemctl restart containerd。虽然 containerd 重启后能恢复大部分运行中的容器但在重启过程中节点上的已有 Pod 可能会短暂失去响应如果遇到重启后挂载或网络插件初始化异常影响范围会扩大。我通常的做法是挑一个低峰窗口先对一个节点操作验证没问题后再推送到其他节点。如果集群里维护时间有限可以考虑先让节点进入 cordon 状态把 Pod 驱赶走再重启 containerdkubectl cordon node-1 systemctl restart containerd kubectl uncordon node-1这个步骤视集群规模和容器数量灵活取舍但至少要知道改这台节点的 containerd 配置影响的是这台节点上所有使用 containerd 的容器拉取行为而不只是某一个 Pod。5. 让业务 Pod 使用私有镜像5.1 创建 docker-registry 类型的 Secret在 Kubernetes 中Pod 拉取私有仓库镜像的凭证主要通过 Secret 传递。Secret 的类型推荐用kubernetes.io/dockerconfigjson它内部保存的是 Docker 客户端能识别的 config.json 结构。最常见的方式是直接用 kubectl 命令创建kubectl create secret docker-registry regcred \ --docker-serverregistry.internal:5000 \ --docker-usernameadmin \ --docker-passwordAdmin123 \ --namespacedemo要注意--docker-server必须和镜像地址里的主机部分严格一致包括端口。比如镜像写的是registry.internal:5000/demo/nginx:1.21.6那么--docker-server就应该是registry.internal:5000不能写成https://registry.internal:5000也不能漏掉端口。否则 containerd 在拉取时找不到匹配的凭证最终报 401。如果不太确定 Secret 内容是否正确可以查看kubectl get secret regcred -n demo -o yaml看到 type 是kubernetes.io/dockerconfigjson并且data[.dockerconfigjson]有内容就基本没问题。5.2 把它挂到一个 Deployment我现在创建一个简单的 Deployment镜像直接使用刚才推送上去的应用镜像apiVersion: apps/v1 kind: Deployment metadata: name: nginx-demo namespace: demo spec: replicas: 2 selector: matchLabels: app: nginx-demo template: metadata: labels: app: nginx-demo spec: imagePullSecrets: - name: regcred containers: - name: nginx image: registry.internal:5000/demo/nginx:1.21.6 ports: - containerPort: 80这里最关键的一行就是imagePullSecrets。如果不加即使 containerd 配置了全局认证某些场景下也会因为 kubelet 没有主动传入 Secret导致拉取失败。加了以后kubelet 会把这组凭证透传给 containerdcontainerd 才可能在请求私有仓库时完成认证。创建完成后观察 Podkubectl get pods -n demo -w kubectl describe pod nginx-demo-xxxxx -n demo如果能看到事件里出现Successfully pulled image registry.internal:5000/demo/nginx:1.21.6就说明整个链路已经通了。进入容器确认一下应用确实在运行kubectl exec -it nginx-demo-xxxxx -n demo -- nginx -v5.3 namespace 隔离与默认 ServiceAccount 的便捷处理Secret 本身是 namespace 级别的资源所以regcred在 demo 命名空间创建后只能给 demo 命名空间里的 Pod 使用。如果 test、prod 多个命名空间都需要从同一个仓库拉镜像需要分别创建。为避免每个 Pod 都重复写imagePullSecrets可以把 Secret 挂到默认 ServiceAccount 上kubectl patch serviceaccount default -n demo -p {imagePullSecrets: [{name: regcred}]}这样这个命名空间里新创建的 Pod只要没有显式覆盖 imagePullSecrets都会自动携带这份凭证。这个方法在开发环境里很省事但在多租户隔离要求高的场景还是建议显式声明避免权限扩散。6. 排查专用与踩坑实录6.1 常见症状与快速定位实际操作中最让人头疼的不是配置写错而是线索分散在多个组件里。我整理了一个排查表格遇到问题可以按这个顺序查现象常见根因优先排查位置Pod 一直 ImagePullBackOff私有仓库地址无法访问、凭证错误、证书不被信任kubectl describe pod在节点上 crictl pull 复现事件里报 x509 unknown authoritycontainerd 不信任自签证书检查节点的 CA 路径、hosts.toml 或 config.toml 的 tls 配置事件里报 401 Unauthorized镜像拉取 Secret 和仓库地址不匹配检查 regcred 的 docker-server 是否和镜像地址一致事件里报 HTTP response from HTTPS serverregistry 用了 HTTPcontainerd 默认走 HTTPS在 hosts.toml 或 config.toml 里配置 insecure_skip_verify或统一启用 TLS明明推送成功Pod 还是报找不到镜像镜像 tag 写错、namespace 名不对用 crictl images 确认 registry.internal:5000 前缀和 tag 是否存在6.2 最容易踩的三个坑第一个是证书 SAN 问题。很多人生成了自签证书Common Name 写对了但在 containerd 配置里也指定了 CA 文件拉取还是报证书校验失败。原因往往是证书的 SAN 里没有包含节点访问时使用的地址。比如推送时用registry.internal:5000但 containerd 目标地址解析成了192.168.1.100:30050证书里没有这个 IP自然校验不过。所以生成证书时要提前把会用到的 DNS 和 IP 都加进 SAN。第二个是 containerd 全局认证和 imagePullSecrets 冲突。有这样一类情况节点配置里写了 admin 账号Pod 里也写了 imagePullSecrets但账号密码并不一致。containerd 会优先使用 Pod 传过来的凭证如果这组凭证是错的即使节点上的全局账号是对的拉取也会失败。调试时不要想当然认为“节点上已经能拉Pod 肯定也能拉”先确认 Secret 的账号密码确实匹配。第三个是 htpasswd 文件的细节。生成 htpasswd 时如果用了不带-B的参数Registry 2.7.1 在解析 bcrypt 之外的哈希时可能不识别最终表现为登录时返回 401而看 registry 容器日志又不会主动报出明确错误。我建议生成后先手动 curl 一下带认证的请求确认认证信息本身没问题再往 containerd 和 Secret 上复用。6.3 独家的实操检查清单每次遇到 Pod 拉取失败我会按这个顺序执行kubectl describe pod xxx看 Events 里的原始报错在报错节点上执行crictl pull registry.internal:5000/demo/nginx:1.21.6确认是否可以复现如果容器 runtime 报的是证书问题用openssl s_client -connect registry.internal:5000 -showcerts验证证书链检查/etc/containerd/config.toml是不是同时存在 hosts.toml如果有两套配置建议只保留一套用kubectl get secret regcred -o go-template{{.data.\.dockerconfigjson | base64decode}}检查 Secret 内容如果还是查不到就去看 containerd 日志journalctl -u containerd --since 10 minutes ago | grep -i pull。这套检查其实不复杂但比胡乱猜测有效得多。很多问题最后落点都不是 Pod 模板而是节点运行时配置。7. 最后说点真实的经验在这个部署过程里最让我印象深刻的其实是“配置链路远比想象中长”。一个简单的 Pod 拉镜像涉及证书、DNS、节点配置、镜像前缀、Secret 命名空间任何一个环节对不上都会报出模棱两可的错误。如果你问我在实际运维中怎么提高成功率我的体会是先不要让问题跑到 kubelet 那一层才被发现而是先把 containerd 侧的拉取用 crictl 验证干净再谈 Pod 调度。另外一个小建议私有仓库的地址尽量固定不要因为某个节点 IP 变了就频繁改镜像前缀。仓库域名和证书一起锁定后续所有节点上的 containerd 配置就只需要维护一套。等这套链路稳定以后再考虑往上叠加镜像清理、备份、制品管理都会轻松很多。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号