ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

自建Docker Registry实战:从安装到运维的完整指南

自建Docker Registry实战:从安装到运维的完整指南 1. 为什么要自己搭一个 Docker Registry很多刚接触 Docker 的朋友觉得镜像嘛直接 pull 下来用就是了干嘛还要费劲自己搭一个仓库这个想法在最开始没问题但等你真正跑起来几个服务、开始往生产环境部署的时候很快就会遇到几个绕不开的痛点。第一个痛点就是速度。我早期在某公司做内部平台的时候团队五六个人大家都从公网拉镜像。高峰期一个稍微大点的镜像比如带了一堆依赖的 Python 运行时拉一次十分钟起步还经常超时中断。后来我统计了一下一天下来光等镜像的时间就够写完一个小需求了。自己搭一个内网 Registry相当于在本地做了一个镜像缓存第一次从公网拉一次之后所有人都是从内网秒开。这个体感差异用过的都知道有多爽。第二个痛点是镜像的可见性和管理。你们公司有没有这种经历——某个同事在本地 build 了一个镜像传到某个公共平台别人根本不知道有没有更新也没法确认这个镜像是谁打的、里面装了什么。自己搭 Registry 之后所有镜像都集中在一个地方配合标签规范和清理策略整个团队的镜像资产一目了然。第三个痛点是合规和隔离。很多公司内部网络是隔离的生产环境根本不能访问外网需要有一个完全内网闭环的镜像分发通道。这种场景下自建 Registry 几乎是唯一解。那为什么选 Docker Registry 而不是其他方案比如 Harbor、Sonatype Nexus答案是看你处在什么阶段。如果你是一个三五人的小团队或者还在个人项目阶段Docker Registry 就是那个性能最轻、部署最快、资源占用最少的方案。Harbor 功能确实全但它自带数据库、UI 门户、RBAC 权限部署和维护成本都不低。Nexus 本身是个制品仓库也能存镜像但配置偏复杂对 Docker 的协议支持也没有原生 Registry 那么纯粹。我自己实践下来阶段一用 Docker Registry 跑通内网镜像分发阶段二如果团队规模大了、权限要求细了再平滑迁移到 Harbor。这个过渡路径很顺因为 Docker Registry 的数据格式是标准的 OCI 分发规范Harbor 可以直接接管已有的镜像存储。先从小东西开始永远比一上来就上重型方案稳妥。这篇文章我就把 Docker Registry 从安装、运行、配置到日常维护的整套流程捋一遍。里面有我踩过的坑也有能直接抄走的配置。你照着做最快十几分钟就能跑起来一个可用的内网镜像仓库。2. 安装前需要想清楚的三件事在敲那行docker run之前我建议你先停下来想三件事不然之后返工的几率很大。2.1 存储目录怎么规划Docker Registry 默认的存储路径在容器里的/var/lib/registry但容器本身就是个随时可能销毁的实体数据不能放在容器可写层里必须挂载到宿主机持久化目录。宿主机目录的选择有几个讲究。第一所在分区剩余空间要足够大镜像仓库会随时间膨胀建议至少预留 50GB 起步。第二如果你的宿主机有数据盘、系统盘之分一定要把镜像目录放在数据盘上别挤在系统盘里否则系统盘满了会连带影响整机运行。第三如果条件允许直接把目录放在 SSD 上镜像多了之后SSD 和机械盘在 push/pull 速度上的差距会非常明显。我在生产环境用的是/data/registry这个路径单独挂了一块数据盘。测试环境就直接用/opt/registry没那么多讲究但路径规划的思路是一样的。2.2 认证方式怎么选Docker Registry 本身默认是完全开放的只要端口暴露出去谁都能往里推镜像。这在纯内网、完全可信的环境里也许能接受但只要你的网络稍微信任边界模糊一点就必须加认证。认证方案主要有两种一种是htpasswd 基础认证这是 Registry 官方支持最简单、最直接的方式。用户名密码通过htpasswd工具生成存在一个文件里。适合小团队内部使用配置成本很低几分钟就能搞定。另一种是Token 认证属于更高级的方案比如对接公司现有的 LDAP 或 SSO 系统每次请求都去认证服务器换取临时 token。功能强大但配置复杂度高一般团队刚起步没必要上这个。我的建议是无论你现在觉得环境多安全都把 htpasswd 认证加上。原因很简单Docker Registry 一旦暴露在网络上又不设防别人可以直接把恶意镜像推给你你拉取的时候就可能中招。加一层密码认证就是加了一层非常必要的保险。2.3 单机还是分布式Docker Registry 的部署形态有两种常见路径单机模式就是直接在宿主机上跑一个容器数据目录挂本地磁盘适合部署量不大、单点故障可接受的场景。大多数团队起步阶段都是这个形态。分布式模式则是把存储后端切到对象存储或者云存储比如 S3、阿里云 OSS或者把数据放在共享文件系统上配合多个 Registry 实例做负载均衡。这个方案是为了高可用和大规模分发准备的。我见过不少团队一上来就想搞分布式结果运维成本比镜像分发本身还高。我的建议是先跑单机等真的出现容量瓶颈或者单点故障导致的不可接受停摆再去动架构。3. Docker Registry 安装与启动全流程环境假设你有一台 Linux 服务器装好了 Docker接下来我会把从拉取镜像到启动容器的每一步都过一遍。3.1 拉取镜像并准备配置目录Docker Registry 的官方镜像名是registry。这里有个大家都忽略的细节一定要指定具体的版本标签不要用latest。docker pull registry:2.8.3为什么要单独拿出来说因为 Registry 在过去有过几次比较大的版本变动latest标签跟着更新如果你哪次重新拉取或者重新部署拉到的镜像和之前的差异可能会让你莫名其妙。指定版本号比如2.8.3可以保证每次部署的行为完全一致。接下来创建配置目录和认证文件目录mkdir -p /data/registry mkdir -p /etc/docker/registry配置目录放config.yml认证目录放htpasswd文件。3.2 用 htpasswd 生成认证文件如果你之前从没生成过 htpasswd 文件可以先安装工具# Debian/Ubuntu apt-get install -y apache2-utils # CentOS/RHEL yum install -y httpd-tools然后生成认证文件比如创建一个用户叫dockeruserhtpasswd -Bc /etc/docker/registry/htpasswd dockeruser这里有个关键点-B参数一定要加。-B表示使用 bcrypt 算法加密密码Registry 在认证时默认要求 bcrypt。如果你不加-B用了默认的 MD5 算法后面 Registry 会直接报认证失败这是我踩过的第一个坑。如果后续要添加新用户注意去掉-c参数否则会整个文件覆盖htpasswd -B /etc/docker/registry/htpasswd anotheruser3.3 编写 config.yml 配置文件Registry 的配置通过config.yml控制。这个文件如果不存在Registry 会用内置默认配置启动。如果存在就以你提供的配置为准。下面是一份适合小团队起步的配置文件我逐行解释关键项version: 0.1 log: level: info fields: service: registry storage: filesystem: rootdirectory: /var/lib/registry delete: enabled: true http: addr: :5000 headers: X-Content-Type-Options: [nosniff] auth: htpasswd: realm: basic-realm path: /auth/htpasswd health: storagedriver: enabled: true interval: 10s threshold: 3storage.filesystem.rootdirectory容器内存储路径要和容器启动时挂载的目录对应。storage.delete.enabled允许删除镜像。默认是 false所以如果你选在 Web UI 上看不到删除按钮就检查一下这里。http.addr监听端口。我用的是 5000Docker Registry 的默认端口。auth.htpasswd.path认证文件在容器内的路径。注意我配的是/auth/htpasswd不是宿主机路径因为容器访问不到宿主机目录这里要用容器内的挂载点。health.storagedriver健康检查。这个比较重要Registry 会定期检查存储后端是否正常有问题会在日志里报警方便早期发现磁盘故障。3.4 启动容器并做端口映射配置文件就绪之后启动命令如下docker run -d \ --name registry \ --restartalways \ -p 5000:5000 \ -v /data/registry:/var/lib/registry \ -v /etc/docker/registry/config.yml:/etc/docker/registry/config.yml \ -v /etc/docker/registry/htpasswd:/auth/htpasswd \ registry:2.8.3逐个参数说明--restartalways容器挂了自动重启。Registry 这种基础设施不能宕机了不去管它。-v /data/registry:/var/lib/registry把宿主机存储目录挂载到容器内存储目录。-v /etc/docker/registry/config.yml:/etc/docker/registry/config.yml把配置文件挂载进去。-v /etc/docker/registry/htpasswd:/auth/htpasswd把认证文件挂载进去。注意这里宿主机目录和容器内路径可以不一样容器内路径要和 config.yml 里写的一致。启动完之后检查一下状态docker ps | grep registry docker logs registry --tail 50如果日志里没有报错说明 Registry 已经在跑了。3.5 本机自测 registry 是否可用先用 curl 简单验证一下服务有没有起来curl -u dockeruser:你的密码 http://localhost:5000/v2/如果返回{}说明 Registry 正常响应。如果提示 401说明认证生效了你的用户名密码有问题。然后做一次完整的 push/pull 验证。先把一个现有镜像打上 registry 的标签docker tag nginx:latest localhost:5000/nginx:latest然后登录docker login localhost:5000输入用户名密码提示Login Succeeded后推送镜像docker push localhost:5000/nginx:latest推送完成后在另一台机器上或者本机先把镜像删了拉取docker pull localhost:5000/nginx:latest如果一切顺利说明你手里已经有一个能用、带认证的内网镜像仓库了。4. 让其他机器能用上你的 RegistryRegistry 跑起来了问题也就来了——其他机器的 Docker 客户端连不上。这个环节是新手遇到问题最多的我把几种典型场景和排查方式都列出来。4.1 确认是否是 https/http 的问题Docker 客户端默认情况下只信任 HTTPS 的 Registry。除非你直接访问localhost否则走 HTTP 会被 Docker 直接拒绝。看下 Docker 客户端报错如果出现类似Error response from daemon: Get http://registry.example.com:5000/v2/: http: server gave HTTP response to HTTPS client说明客户端认为你是 HTTPS但服务端实际是 HTTP。解决办法是让 Docker 客户端把该地址视为 insecure registry。修改客户端的/etc/docker/daemon.json{ insecure-registries: [ registry.example.com:5000 ] }如果还有其他配置项把insecure-registries合并进去然后重启 Dockersystemctl restart docker注意重启 Docker 会把你所有正在运行的容器停下来生产环境操作前要评估一下影响。4.2 跨机器的地址替换很多人在本机测试没问题跨机器就拉取失败核心原因就是用了localhost这个地址。其他机器上的 Docker 解析不了你机器上的localhost它只会去找自己本机。正确做法是打标签的时候直接用能被对方访问到的地址比如你的服务器 IP 或者内网域名docker tag nginx:latest 192.168.1.100:5000/nginx:latest docker push 192.168.1.100:5000/nginx:latest内部团队使用我强烈建议申请一个内网域名比如registry.internal这样然后在每台机器的/etc/hosts里配上解析。用 IP 当然也能跑但域名有明显的好处后面迁移服务器、换 IP 的时候不用去每台机器改配置只要把 DNS 或 hosts 指到新地址就行。4.3 防火墙和端口连通性排查你在服务器本地怎么测试都通换台机器就提示 connection refused十有八九是防火墙把 5000 端口挡了。确认端口监听状态ss -lntp | grep 5000确认防火墙规则以 firewalld 为例firewall-cmd --permanent --add-port5000/tcp firewall-cmd --reload如果用的是云服务器还要去安全组里放行 5000 端口的入方向规则。这一步很多人忘因为本地网络没有这道关卡上云就卡住了。4.4 给 Registry 配上 HTTPS 的简便做法既然 Docker 客户端默认只信任 HTTPS那最彻底的办法就是直接给 Registry 配上 HTTPS。这样所有机器都无需设置 insecure-registry安全性和易用性同时满足。最方便的方案是让 Registry 直接用宿主机上已有的证书。假设你已经有了证书文件domain.crt和domain.key把它们放到宿主机目录比如mkdir -p /etc/docker/registry/certs cp domain.crt /etc/docker/registry/certs/ cp domain.key /etc/docker/registry/certs/然后修改 config.yml把 http 段换成 https 配置http: addr: :5000 tls: certificate: /certs/domain.crt key: /certs/domain.key重新启动容器时多挂载一个证书目录docker run -d \ --name registry \ --restartalways \ -p 5000:5000 \ -v /data/registry:/var/lib/registry \ -v /etc/docker/registry/config.yml:/etc/docker/registry/config.yml \ -v /etc/docker/registry/htpasswd:/auth/htpasswd \ -v /etc/docker/registry/certs:/certs \ registry:2.8.3配置了 HTTPS 之后所有客户端机器哪怕不在 insecure-registries 列表里也能正常登录和推送镜像。这是我在实际环境中最推荐的做法因为不用每台机器单独改 Docker 配置一劳永逸。5. 镜像仓库的日常运维与管理仓库跑起来只是开始日常运维才是重头戏。这一节我把镜像的推拉、删除、清理、备份等操作都过一遍全是我自己实践验证过的。5.1 镜像推送和拉取的正确姿势推送镜像时标签的格式会决定镜像的去向。标准格式是仓库地址/命名空间/镜像名:标签。举个例子我要把本地 Dockerfile 构建出来的应用镜像推到内网仓库docker build -t 192.168.1.100:5000/myapp/backend:v1.0 . docker push 192.168.1.100:5000/myapp/backend:v1.0这里myapp/backend是命名空间加镜像名v1.0是版本标签。推送完Registry 上就有了这个镜像。拉取的时候docker pull 192.168.1.100:5000/myapp/backend:v1.0拉取下来之后如果你想把镜像名变成更简洁的本地名再打一次标签docker tag 192.168.1.100:5000/myapp/backend:v1.0 backend:v1.0这样可以避免本地镜像名带一长串前缀看着清爽运行容器时也不用写那么长。5.2 镜像版本标签的规范建议镜像标签如果不做规范时间一长仓库里就全是latest、v1、final这种毫无辨识度的名字。我这里的团队约定了一套规则主版本号.次版本号.修订号-环境标识比如v1.2.0-prod生产环境发布的版本v1.2.0-test测试环境验证的版本v1.2.0-dev开发环境最新构建同时约定latest标签只能由 CI/CD 流水线在每次发布成功后自动更新不允许任何人手动往latest上乱推。这样大家看到latest就知道是当前最新的稳定构建而不是某个人本地随手打出来的包。5.3 删除镜像的正确方法Registry 的 HTTP API 本身就是支持删除镜像的但有前提条件配置文件里要开启storage.delete.enabled: true。删除镜像有一个先后顺序问题必须先删除 manifest再删除相应的 blob不然磁盘空间不会释放。先用 API 查看仓库里的镜像列表curl -u dockeruser:密码 https://registry.example.com:5000/v2/_catalog返回类似{repositories:[myapp/backend,myapp/frontend]}查看某个镜像的所有标签curl -u dockeruser:密码 https://registry.example.com:5000/v2/myapp/backend/tags/list再获取指定标签的 digest也就是镜像的唯一摘要curl -u dockeruser:密码 -H Accept: application/vnd.docker.distribution.manifest.v2json \ https://registry.example.com:5000/v2/myapp/backend/manifests/v1.2.0-prod在响应头里找Docker-Content-Digest返回的十六进制字符串就是 digest。然后调用删除接口curl -u dockeruser:密码 -X DELETE \ https://registry.example.com:5000/v2/myapp/backend/manifests/digest值删除之后还需要定期执行 GC 才能真正释放磁盘空间。GC 操作需要跑到容器内部执行docker exec registry registry garbage-collect /etc/docker/registry/config.yml执行完 GC之前删掉镜像残留的存储层会被清除磁盘空间才会真正释放出来。5.4 用脚本定期清理老旧镜像人工清理一两次可以长期这么干谁都受不了。我写了一个简单的清理脚本放在服务器上配合 crontab 定期跑。脚本思路是列出每个镜像的所有标签根据标签里的版本号排序把规定保留数量之外的镜像全部删除最后执行 GC。#!/bin/bash REGISTRYhttps://registry.example.com:5000 USERdockeruser PASS密码 KEEP_VERSION_COUNT5 # 获取所有仓库 REPOS$(curl -s -u $USER:$PASS $REGISTRY/v2/_catalog | python3 -c import sys,json; print(\n.join(json.load(sys.stdin)[repositories]))) for repo in $REPOS; do # 获取所有标签 TAGS$(curl -s -u $USER:$PASS $REGISTRY/v2/$repo/tags/list | python3 -c import sys,json; print(\n.join(json.load(sys.stdin)[tags]))) # 这里按版本号排序保留最新的 KEEP_VERSION_COUNT 个 STALE_TAGS$(echo $TAGS | sort -V | head -n -$KEEP_VERSION_COUNT) for tag in $STALE_TAGS; do # 获取 digest DIGEST$(curl -s -u $USER:$PASS -H Accept: application/vnd.docker.distribution.manifest.v2json \ $REGISTRY/v2/$repo/manifests/$tag -D - | grep -i Docker-Content-Digest | awk {print $2} | tr -d \r) # 删除 manifest curl -s -u $USER:$PASS -X DELETE $REGISTRY/v2/$repo/manifests/$DIGEST done done # 执行 GC 释放空间 docker exec registry registry garbage-collect /etc/docker/registry/config.yml脚本里用到了sort -V这是按版本号自然排序v1.9会排在v1.10前面不是简单按字典序。然后在 crontab 里配一下0 3 * * 6 /opt/scripts/cleanup_registry.sh /var/log/registry_cleanup.log 21每周六凌晨三点跑一次。注意 GC 执行时Registry 会短暂停顿所以安排在业务低峰期比较稳妥。5.5 数据备份与恢复镜像仓库里的数据本质上都是文件备份思路就是把存储目录整个打包。最常用的命令tar -czf registry_backup_$(date %Y%m%d).tar.gz -C /data registry恢复的时候把压缩包解压回/data目录然后重新启动容器即可。因为配置文件和镜像数据是分开挂载的恢复时先确认 config.yml 和 htpasswd 还在再确认数据目录解压完整。如果数据量特别大比如几百 GB用 tar 打包会很慢可以考虑用 rsync 增量同步到备份服务器rsync -avz --delete /data/registry/ backup-server:/data/registry/不管用哪种方式定期备份真的很重要。我见过有人不备份结果磁盘坏了镜像全没重建容器还得重新 build 每一个镜像痛苦程度谁经历谁知道。6. 常见问题与排查技巧实录这一节我把自己在运维过程中遇到比较多的和团队里其他同事问我的问题汇总一下直接给你解决办法。6.1 镜像推不上去提示认证失败现象docker push时提示unauthorized: authentication required。排查步骤确认你有没有执行docker login并且登录的是正确的仓库地址很多人登录了一个地址推送的目标地址又是另外一个。确认用户名密码是否输对htpasswd 是 bcrypt 加密的话输错密码肯定登录不了。确认 config.yml 里auth.htpasswd.path是否指向容器内正确的文件路径路径写错 Registry 根本读不到认证文件。确认容器启动时是否把 htpasswd 文件正确挂载进去了。可以进入容器手动检查文件是否存在docker exec -it registry ls -l /auth/htpasswd6.2 拉取镜像时提示 dial tcp connection refused现象docker pull时提示dial tcp 192.168.1.100:5000: connect: connection refused。排查步骤用telnet 192.168.1.100 5000在客户端机器上测一下端口通不通。如果不通说明中间的网络链路有问题。确认 Registry 容器在服务器上是否正常运行docker ps | grep registry。确认防火墙和安全组有没有放行 5000 端口我上面已经讲过。如果服务器上还有别的程序占用了 5000 端口Registry 启动会失败用ss -lntp | grep 5000可以确认。6.3 拉取镜像时提示 server gave HTTP response to HTTPS client现象报错信息里包含http: server gave HTTP response to HTTPS client。原因Docker 客户端默认用 HTTPS 去连你的仓库但你的仓库跑的是纯 HTTP。解决办法两个方向要么给 Registry 配上 HTTPS 证书要么在客户端 daemon.json 里添加insecure-registries配置。我推荐前者一劳永逸后面不用每台机器都改。6.4 磁盘空间被镜像文件占满现象服务器磁盘告警发现/data/registry目录特别大。解决思路用docker exec registry registry garbage-collect执行一次 GC把已删除镜像的残留数据清掉。如果 GC 之后空间还是不够就得从清理策略入手设置保留版本数自动清理老旧镜像。从根上解决就是给 Registry 换更大的数据盘或者迁移到对象存储。6.5 认证文件生成后 Registry 无法启动现象启动容器后日志里出现authtoken: htpasswd: invalid hashing algorithm for user dockeruser原因生成 htpasswd 的时候没有用-B参数默认生成的 MD5 格式 Registry 不认。解决办法重新用htpasswd -B生成文件覆盖掉原来的。6.6 镜像列表接口返回 401现象curl访问/v2/_catalog拿到 401。原因认证生效了但你没带用户名密码。解决办法curl 加-u参数即可curl -u dockeruser:密码 https://registry.example.com:5000/v2/_catalog6.7 删除镜像后磁盘空间没有释放现象通过 API 把镜像删了但du -sh /data/registry看磁盘占用没有变小。原因删除操作只是标记了 manifest 为不可用实际的数据层文件要等 GC 才会清理。解决办法执行garbage-collect命令。这算是 Registry 设计的一个特性不是 bug理解了就不会慌。7. 进阶方向从 Docker Registry 向更高阶方案演进如果你把上面的内容全部实践了一遍你的内网镜像分发体系已经能支撑一个几十人的团队日常用了。再往后走有几个方向可以根据实际情况考虑。第一个方向是给 Registry 加 UI。Docker Registry 原生没有界面查看镜像只能靠命令行 API。需要可视化的时候可以部署一个轻量的 UI 项目比如 Docker Registry Frontend 或者类似的第三方前端直接以容器方式跑起来让它跟 Registry 走同一个网络配置一下就能看到镜像列表、标签和大小也能找到删除等操作入口。对于不爱敲命令的同事来说这个 UI 还是有用的。第二个方向是权限模型升级。Docker Registry 的 htpasswd 认证是全局的所有人都用同一份凭据没法做精细的隔离。你们团队如果发展成多个项目组A 组的镜像不想让 B 组看到就需要上 Harbor 这种带 RBAC 权限控制的方案了。Harbor 可以创建多个项目每个项目单独设置访问权限还能跟 LDAP 打通统一账号体系不用每人都维护独立的 htpasswd 账号。第三个方向是 CI/CD 集成。自建 Registry 最大的价值就是和持续集成流水线无缝配合。每次代码合并流水线自动构建镜像并推送到 Registry部署环境直接从 Registry 拉取指定版本整个过程不需要人工介入。这个流程一旦跑顺交付效率会有质的提升。但不管你最后演进到什么方案前期从 Docker Registry 起步学到的这些存储结构、认证原理、GC 机制、镜像生命周期管理都是通用且不会浪费的。8. 最后补充几个容易被忽略的细节有几个很小但容易坑人的细节单独再说一下。第一是 Registry 日志。默认日志级别是 info如果你觉得日志太吵可以在 config.yml 里把log.level改成warn只输出警告和错误。排查问题时再临时改回 debug问题解决后记得改回来。第二是健康检查接口。Registry 在/v2/路径返回{}就代表服务正常。如果你有监控系统可以把这个接口做成监控项定时检测仓库是否存活。第三是端口冲突。5000 端口是 Registry 默认端口但有些环境里这个端口已经被其他服务占了。如果冲突把 config.yml 里http.addr改成别的端口同时调整启动命令里的端口映射。改的时候两个地方要一致不然配置不生效。第四是镜像命名空间。镜像仓库支持命名空间的层级结构比如auth/api-gateway和order/service可以放在同一个仓库里用命名空间区分业务模块。这个不额外配置直接推送带命名空间的镜像标签即可但建议提前规划命名规范免得后面乱。Docker Registry 本身并不复杂说白了一个容器加一份配置文件就能跑起来。但真正跑好它让团队所有人用得顺手、数据安全可靠是要在细节里下功夫的。这篇文章里的每一段基本都能对应到我实际运维中遇到过的场景你照着一路实践下来很快也能积累起自己的一套打法。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表