ARTICLE DETAIL

资讯详情

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

Harbor企业级镜像仓库部署与容器运行时安全加固实战

Harbor企业级镜像仓库部署与容器运行时安全加固实战 我们继续“容器运行时机制”这个系列。今天是第 18 篇标题里写得很直接Harbor 企业级部署与安全。前几篇我们把镜像分发、容器生命周期、底层内核机制都拆过一遍但很多朋友在实际落地时会卡在同一个地方——代码在开发机跑得好好的一旦要面对多人协作、多环境交付、安全和合规要求Docker Hub 和单机 docker pull 这种方式根本撑不住。这时候就需要一个自建的、带完整权限模型和扫描能力的镜像仓库。这篇文章我不会只贴安装命令。我会把“运行时机制”和“企业部署”串起来讲镜像从 push 进 Harbor到被生产节点 pull再到 runc 把容器拉起来这条链路上每个环节的安全点分别在哪里然后给出一套基于 CentOS 7 的 Harbor 完整部署流程和排障经验。适合正在搭内网镜像仓库、准备上生产环境或者刚接手公司容器平台运维的同学。1. 先把运行时机制的链路捋清楚镜像到底是怎么跑起来的1.1 Docker 三层组件与一次 docker pull 的旅程很多人理解 Docker 就是“一个守护进程”其实现代 Docker 引擎是三层协作你敲的 CLI 客户端、负责接收请求的 dockerd 守护进程、以及真正管理容器的 containerd。containerd 再调用更底层的 runc 去完成容器进程的创建。用一句话概括CLI 是遥控器dockerd 是调度中枢containerd 是管家runc 才是那个真正去内核里申请 PID、挂载文件系统的执行者。当你执行 docker pull harbor.lab.local/library/nginx:1.27 时完整路径是这样的CLI 把请求发给 dockerddockerd 先检查本地是否已有对应 digest 的镜像层没有就携带你 login 时拿到的 token 去访问 Harbor 的 token 服务拿到拉取凭证然后请求镜像的 manifest再按 manifest 里列出的层列表逐个下载 blob 并校验 sha256 摘要。整个过程中Harbor 里的 registry 组件负责存储和分发它并不直接管理容器只负责“把镜像字节可靠地交出去”。理解了这条链路就明白为什么企业镜像仓库必须在权限和传输上做文章任何一环被绕过比如有人直接通过内网暴露的 registry 端口匿名拉取或者镜像被篡改后摘要对不上后续容器运行时就会把“带病”的镜像当成基础事实来用。这也是我在后面第 4 节反复强调认证与扫描的原因。1.2 隔离与限制的内核基础Namespace、Cgroup、OverlayFS容器并不是“轻量虚拟机”它只是共享宿主机内核的一组进程。所谓隔离靠的是 Namespace所谓限制靠的是 Cgroup而镜像那种“分层、可叠加、互相共享”的能力靠的是 OverlayFS 这类联合文件系统。每个容器创建时runc 会为它建立新的 PID、Network、Mount、UTS、IPC 等 Namespace让容器内进程误以为自己独占了一套操作系统同时通过 Cgroup 把 CPU、内存、IO、PID 数量限制在指定范围内。这也是为什么容器里执行 top 只能看到自己的进程为什么容器内关网卡不影响宿主机。OverlayFS 就更有点意思了。镜像的所有层是只读的 lowerdir容器启动后内核会挂一个可写的 upperdir通过 merged 视图把上下两层叠加起来。你往容器里写文件其实写在上层删除基础镜像里的文件也只是在上层生成一个 whiteout 标记。这套机制带来了极高的存储利用率和秒级启动但也提醒我们镜像层越多拉取和校验的开销越大构建时应当尽量合并 RUN 指令减少无谓层数。正因为隔离是 Namespace 层面的“逻辑隔离”而不是 Hypervisor 级别的“硬件隔离”容器逃逸风险始终存在。内核漏洞一旦被利用攻破一个容器就可能影响宿主机。所以安全不能只靠运行时隔离还得叠加 Capabilities、seccomp、只读根文件系统这些“纵深防御”手段这部分我会在第 4 节展开。1.3 从运行时视角看企业私有镜像仓库的三个价值从“运行时机制”的角度回头看企业搭私有仓库比如 Harbor并不是为了“私有”两个字好看而是解决三个实际问题。第一是可信分发。线上节点 pull 镜像时必须能验证“这个镜像是我们自己的构建产物没被中间人换过”。私有仓库加 TLS、加摘要校验才能保证运行时拿到的镜像字节是可信的。第二是性能与隔离。内网拉镜像的带宽和时延远好于公网还能避免生产环境对公网仓库的依赖不同项目之间通过项目级隔离避免互相污染。第三是审计与合规。谁在什么时间 push 了哪个镜像、谁从哪个节点 pull 过、漏洞扫描结果如何这些记录在出事时是定位问题的关键证据。Harbor 正好把这三件事做成了开箱即用的产品能力。2. Harbor 为什么能承担企业级镜像仓库的角色2.1 官方 Registry 与 Harbor 的差距Docker 官方提供的 registry 镜像本质上只是一个“存储 API”的实现它能接收 docker push 和 docker pull但除此之外几乎什么都没有没有图形界面没有项目管理没有基于角色的权限控制镜像漏洞扫描更是想都别想。你可以用 htpasswd 给它加一个简单的基本认证但那只能是“全有或全无”没办法做到“这个团队只能读那个团队只能写”。Harbor 是在 Docker Distribution也就是官方 registry 的内核外面包了一层完整的企业能力Web UI、RBAC 权限模型、LDAP/OIDC 接入、复制策略、漏洞扫描、垃圾回收、审计日志。对运维来说最大的感受是终于能回答老板的问题——“仓库里有哪些镜像、谁传的、安不安全”。在部署层面Harbor 的 offline installer 把所有依赖镜像打进一个压缩包内网环境也能直接装这也是很多企业选择它的现实原因。2.2 Harbor 的核心组件与一次推送的数据流Harbor 看着是一个服务实际上是一组容器协同工作。核心组件包括nginx 负责入口和 TLS 终结harbor-core 提供 API、认证、项目和权限管理harbor-registry 就是 Docker Distribution负责镜像 blob 的实际存储registryctl 负责配置管理和 GCjobservice 处理复制、扫描、Webhook 这类异步任务背后还有 PostgreSQL 存元数据Redis 做缓存和队列如果开启了扫描功能还会有 trivy-adapter 去调 Trivy 扫描器。一次 docker push 的数据流是客户端先登录拿 JWT token再通过 nginx 把镜像层上传到 registryregistry 把元数据写入 PostgreSQL并触发 jobservice 的扫描任务。所以排查 push 慢或者扫描不执行时要分别看 registry 日志、数据库状态和 jobservice 日志而不是只盯着某一个容器。多组件部署的代价就是排障面变宽好处是每个环节都可以单独扩展比如把 PostgreSQL 和 Redis 换成外部高可用实例registry 存储挂到 S3 或 OSS 上Harbor 就能从单机方案平滑走向生产级。2.3 部署形态与版本选型Compose、Helm、离线包Harbor 官方提供三种主流部署方式docker-compose 方式、Helm 方式跑在 Kubernetes 上、以及对应的在线/离线安装包。我的建议很明确如果你的目标是“快速在内网搭一个能用的仓库团队几十人以内”用 offline installer docker-compose 就够了这也就是第 3 节要演示的路径如果你们容器平台已经在 Kubernetes 上需要多副本、滚动升级、对接云存储那就走 Helm把 PostgreSQL 和 Redis 外置registry 存储指向对象存储Harbor 本身可以做到无状态化。版本选型上Harbor 目前主流是 2.x 系列建议大家选当前 2.x 里最新的稳定小版本而不是追 beta。这里要特别注意Harbor 2.x 对 Docker 客户端版本有隐性要求太老的 Docker17.x 之前在拉取新版 manifest 时会报 manifest unknown后面排障部分我会再提。3. CentOS 7 上部署 Harbor 的全流程实操3.1 环境准备Docker Engine 与 Compose 版本核对我下面的实操环境是 CentOS 7.9内核 3.10Docker Engine 版本是 24.xCompose 用的是 docker compose 插件v2。机器配置建议最低 4 核 8G磁盘独立挂载给 Harbor 的数据目录最好不要和系统盘共用因为镜像层会非常占空间。先做版本核对这一步卡住的人不少docker version docker compose version如果你的环境里只有 docker-composev1Harbor 的 install.sh 也能识别但为了后续维护方便我还是建议装 Compose v2 插件。另外记得把/data/harbor这个数据盘做好规划后面配置里会用到。最后关闭或正确配置防火墙、SELinux这一步要特别小心别一上来 setenforce 0 就完事后面排障部分我会讲正确做法。3.2 生成 HTTPS 证书并编写 harbor.yml生产环境里我强烈建议直接上 HTTPS不要图省事用 http。原因很简单镜像在传输过程中如果被篡改摘要校验虽然能发现但那已经是“事后发现”而 TLS 能从源头保证链路不被中间人窃听和注入。内网环境没有正规 CA 证书的话可以用私有 CA 签一套证书。# 1. 生成私有 CA openssl genrsa -out ca.key 4096 openssl req -x509 -new -nodes -sha256 -days 3650 \ -subj /CNharbor.lab.local \ -key ca.key -out ca.crt # 2. 为 Harbor 域名签发服务端证书 openssl genrsa -out harbor.lab.local.key 4096 openssl req -new -sha256 \ -subj /CNharbor.lab.local \ -key harbor.lab.local.key -out harbor.lab.local.csr openssl x509 -req -in harbor.lab.local.csr \ -CA ca.crt -CAkey ca.key -CAcreateserial \ -days 3650 -out harbor.lab.local.crt下载离线安装包并解压后把模板文件复制一份重点修改以下几段curl -L -o harbor-offline-installer-v2.9.4.tgz \ https://github.com/goharbor/harbor/releases/download/v2.9.4/harbor-offline-installer-v2.9.4.tgz tar -xzf harbor-offline-installer-v2.9.4.tgz cd harbor cp harbor.yml.tmpl harbor.ymlharbor.yml 里最核心的配置项hostname: harbor.lab.local http: port: 80 https: port: 443 certificate: /opt/harbor/certs/harbor.lab.local.crt private_key: /opt/harbor/certs/harbor.lab.local.key harbor_admin_password: AdminLab2025 database: password: DbLab2025 data_volume: /data/harbor trivy: ignore_unfixed: false skip_update: false jobservice: max_job_workers: 10每个配置都要知道为什么这么写。hostname 必须和证书里的 CN 一致否则客户端做 TLS 校验会失败harbor_admin_password 默认是 Harbor12345正式环境落地第一件事就是改成强密码不要拿着默认密码上线data_volume 要指向独立数据盘不能放根目录下随手建的文件夹trivy 的 skip_update 保持 false让扫描器定期更新漏洞库离线环境才需要额外处理。数据库密码在首次 install 之前就要定好安装完成后再去改要动一堆容器配置和环境变量非常容易把自己绕进去。3.3 执行安装脚本并验证服务状态确认配置没问题后执行离线安装我建议把扫描组件一起装进去sudo ./install.sh --with-trivy安装脚本会依次加载离线镜像、生成 docker-compose 编排文件、启动所有服务。看到 “Harbor has been installed and started successfully” 就说明基本成了。此时检查一下容器状态docker compose ps正常会看到 nginx、harbor-core、harbor-db、harbor-jobservice、harbor-portal、registry、registryctl、redis、trivy-adapter 这些容器都处于 Up 状态。浏览器访问 https://harbor.lab.local用 admin 和刚才配置的密码登录能进 UI 就算部署成功。如果是在安装过程中报错最常见的是数据目录权限和端口占用。端口问题好解决把 harbor.yml 里的 http.port 或 https.port 改掉重装即可。权限问题多半是 SELinux 在作怪下面排障章节会专门讲。3.4 客户端接入与镜像推送验证服务端装好了客户端开发机或其他 Docker 节点还要能信任我们的私有 CA。这里有两种方式第一种简单但安全性弱在/etc/docker/daemon.json里把 Harbor 地址加进 insecure-registries然后重启 Docker。第二种推荐的方式把 CA 证书放到 Docker 的证书信任目录。mkdir -p /etc/docker/certs.d/harbor.lab.local cp ca.crt /etc/docker/certs.d/harbor.lab.local/ca.crt systemctl restart docker然后登录并推送一个测试镜像docker login harbor.lab.local -u admin --password-stdin docker tag nginx:1.27-alpine harbor.lab.local/library/nginx:1.27-alpine docker push harbor.lab.local/library/nginx:1.27-alpinelibrary 是 Harbor 内置的公开项目适合放基础镜像。推送成功后去 UI 的“镜像仓库”页面能看到这个镜像项目里还会出现扫描报告。这一步跑通你的私有镜像分发主链路就通了。3.5 日常运维备份、升级与垃圾回收部署完成只是一切的开始。Harbor 的元数据全在 PostgreSQL 里镜像 blob 在 data_volume 的 registry 目录下所以要备份就得两个都考虑。我习惯在低峰期用 docker exec 进入 harbor-db 容器执行 pg_dump 导出库同时用 rsync 把 registry 目录同步到备份盘。只备份其中一个恢复时都会出问题。垃圾回收GC用来清理那些已经没有 manifest 引用的孤立 blob。Harbor 2.x 可以在 UI 的“系统管理 → 垃圾回收”里配置定时任务也可以调 API 触发。这里有一条经验执行 GC 前务必确认没有复制任务正在跑如果有跨实例的复制某个 blob 可能正被另一个实例引用GC 掉之后远端就拉不到了。线上出过这种事故不要踩。升级方面Harbor 的官方升级路径是把新版本离线包下载到原目录重新执行 install.sh它会读取已有配置做原地升级。升级前先备份数据库和存储目录并且去看官方 Release Notes 里标注的升级注意事项跨大版本升级比如 1.x 到 2.x必须先走中间版本。4. 镜像仓库安全与容器运行时的加固体系4.1 身份认证与权限模型本地账号、LDAP 与 Robot 账号Harbor 的权限模型核心是“项目”。每个项目可以设公开或私有成员角色分为项目管理员、开发人员、访客等。开发人员能 push访客只能 pull这种粒度已经能覆盖绝大多数团队的协作需求。系统管理员则拥有全局管理权限账号要慎发最好控制在两三个人手里。当团队人数上来以后我建议直接接 LDAP/AD 或 OIDC。Harbor 配置 LDAP 后用户不用在 Harbor 里单独建号直接用公司账号登录离职禁用也自动生效。配置时几个关键参数是 ldap_url、ldap_base_dn、ldap_uid 和组成员过滤先在 UI 的“认证设置”里点“测试”按钮验证连通性再保存。还有一个非常实用的能力是 Robot Account。它专门给 CI/CD 流水线用——Jenkins 或 GitLab Runner 需要 push 镜像、生产节点需要 pull 镜像都不该用个人账号因为个人账号有离职、密码过期的问题。创建 robot 账号时可以限定到某个项目、设置过期时间甚至可以做成“只读拉取”或“只写推送”。我见过很多团队把 admin 密码直接写进 Jenkins 构建脚本这是比漏洞扫描严重得多的风险务必改成 robot 账号。4.2 供应链防线漏洞扫描、内容信任与 Webhook镜像安全的第一道防线是漏洞扫描。Harbor 集成的是 Trivy推送镜像后它会分析系统软件包和依赖库对照 CVE 库给出漏洞列表和严重等级。我建议在项目的“配置”里开启“推送镜像后自动扫描”并且设置拦截策略高危及以上漏洞不允许拉取或者不允许从该仓库向生产环境复制。注意拦截策略要在项目配置里明确勾选不是默认就生效的。第二道防线是内容信任与签名。镜像签名解决的是“这个镜像确实是我们构建的、没被篡改”的问题。Harbor 1.x 时代用 Docker Content Trust Notary现在更常见的是用 cosign。构建机用私钥给镜像签名生产节点在拉取时校验签名没有合法签名的镜像直接拒收。配置 DOCKER_CONTENT_TRUST1 环境变量后docker pull 会自动要求签名校验这招在供应链攻击频发的当下非常有价值。第三道防线是 Webhook。Harbor 可以把“镜像推送完成”“扫描完成”“复制完成”等事件推给内部的 CI/CD 平台比如推送 test 环境镜像通过扫描后自动触发生产环境的部署审批。这样安全策略就不是人肉管理而是接入自动化流程实实在在提高了响应速度。4.3 运行时加固Capabilities、seccomp、只读根文件系统镜像进了仓库不代表万事大吉容器运行时的加固同样重要。默认情况下 Docker 会给容器分配一整套 Linux capabilities其中很多普通应用根本用不到。最小权限原则在容器世界的落地就是显式丢弃多余能力docker run -d \ --cap-dropALL \ --cap-addNET_BIND_SERVICE \ --security-opt seccomp/etc/docker/seccomp.profile.json \ --read-only \ --tmpfs /tmp:rw,size64m \ --memory512m --cpus0.5 --pids-limit100 \ nginx:1.27-alpine上面这条命令里的每个参数都值得展开说。--cap-dropALL 把容器进程的所有 Linux 能力清空然后只加回监听低端口需要的 NET_BIND_SERVICE普通 Web 服务有这一个就够。seccomp 参数指定一个 seccomp 配置文件内核会在系统调用入口拦截高风险调用Docker 默认的 seccomp profile 已经挡了四十多个系统调用想更严格可以自己维护。--read-only 把根文件系统设为只读应用想写文件就只能写到挂载的 volume 或 tmpfs这能挡住“容器被入侵后写后门文件”这类动作。资源限制则避免单个容器吃光宿主机内存和 CPU——之前排查过一个 Java 容器内存飙高拖垮整台机器的问题根因就是没设 memory limit。还有两个建议Docker daemon 可以开启用户命名空间重映射userns-remap把容器里的 root 映射成宿主机上的非特权用户即使容器逃逸拿到 root在宿主机层面也只是普通用户另外生产环境绝对不要用 --privileged 运行容器这就等于把宿主机内核能力全部交出去了。4.4 镜像本身的卫生基础镜像、多阶段构建与摘要锁定安全不只在运行时镜像生产源头就要干净。我见过太多基础镜像里面塞着编译器、调试器、SSH 客户端这些组件对运行毫无用处却扩大了攻击面。建议尽量用精简基础镜像比如 alpine、distroless能用多阶段构建就把构建工具留在构建阶段最终运行镜像只保留可执行文件和运行库。CI 阶段就应该做扫描。Harbor 的扫描是入库后的事更好的习惯是流水线里直接跑 trivy image 命令扫描出高危依赖就让构建失败这样“坏镜像”根本没机会进 Harbor。另外不要在镜像里写死密钥环境变量可以通过 docker inspect 看到正确的做法是用 Docker secrets、K8s Secret 或 HashiCorp Vault 这类方案注入。还有一个容易被忽略的习惯生产环境不要天天拉 latest 标签latest 是可变的今天 pull 的和明天 pull 的可能完全不是一个东西。正确的做法是给镜像打不可变版本号再激进一点就锁定 digest 拉取docker pull harbor.lab.local/library/appsha256:xxxx。用摘要拉取可以保证每次部署的二进制内容完全一致排查问题也会容易得多。5. 部署与使用中的高频问题排查实录5.1 安装阶段的问题我在多个环境里装 Harbor最常见的失败点是三个端口被占、数据目录权限、SELinux。端口被占很好判断报错信息会直接说 bind: address already in use。用 ss -lntp 看是哪个进程占了 80/443要么停掉冲突服务要么改 harbor.yml 里的端口。改端口后注意客户端访问也要带新端口。SELinux 的问题就隐蔽一些。CentOS 7 默认 enforcingHarbor 的容器挂载数据卷时会被 SELinux 策略拦截表现为 core 或 db 容器反复重启、日志里出现 Permission denied。我建议的正规做法是对数据目录打上容器文件上下文标签chcon -Rt svirt_sandbox_file_t /data/harbor用 setenforce 0 只是临时调试手段重启后失效而且在生产环境关闭 SELinux 会让整个主机的安全基线下降不该作为长期方案。5.2 登录与拉取镜像的问题客户端 docker login 时报 x509: certificate signed by unknown authority基本就是证书信任没配好。回到 3.4 节把 ca.crt 放到 /etc/docker/certs.d/harbor.lab.local/ 下重启 Docker问题就解决了。千万不要为了省事直接加 insecure-registries那等于关掉了 TLS 校验镜像在传输链路上被掉包你都发现不了。还有一种情况是报 server gave HTTP response to HTTPS client这说明你客户端访问的协议和 Harbor 实际监听的不一致检查一下访问地址是 http 还是 https以及 nginx 端口是否被自定义改过。登录时提示 unauthorized先确认账号密码再看 robot 账号有没有勾选对应项目权限robot 账号默认不是“全能”的。如果生产节点能解析域名但拉取超时检查节点到 Harbor 的网络连通性和防火墙策略。容器网络不通这类问题先在外围用 curl 验证 Harbor 的 443 端口通不通再进容器里测逐层缩小范围别直接怀疑内核。5.3 存储、扫描与 GC 的坑push 大镜像时报 413 或连接被重置优先怀疑 nginx 反代层。Harbor 自带的 nginx.conf 一般默认允许大请求体但如果你在 compose 里 override 过 nginx 配置或者前面还有一层负载均衡比如公司统一入口的 nginx/LVS就要去查那层的 client_max_body_size 和超时时间。我曾经排查过一个 2GB 的镜像推不上去的问题最后发现是外层统一网关默认只允许 1MB 请求体。扫描结果一直是空的大概率是 Trivy 的漏洞库没更新。Harbor 的 trivy-adapter 需要联网拉取 CVE 数据库离线环境里这一步会静默失败。解决办法是在有网的环境把漏洞库下载好再传给 Harbor 配置好的离线更新目录具体路径和方式以官方文档为准。一句话总结离线部署 Harbor 不难离线部署“带扫描功能的 Harbor”才是真正要提前规划的。GC 相关的坑我在 3.5 节已经提过这里再强调一次GC 不是“顺手清理磁盘”的日常操作。它清理的是没有任何 manifest 引用的 blob如果复制任务还在运行某些 blob 可能刚被远端实例引用就被本地 GC 掉了远端后续拉取会直接 404。生产环境做 GC 前先确认没有复制任务、没有正在推送的镜像。5.4 高频问题速查表症状可能原因快速处理install.sh 阶段容器反复重启数据目录权限或 SELinuxchcon -Rt svirt_sandbox_file_t /data/harbor80/443 端口被占用宿主机已有 Web 服务改 harbor.yml 端口并重装docker login 报 x509 unknown authority客户端未信任私有 CA拷贝 ca.crt 到 /etc/docker/certs.d/ 对应目录docker login 报 HTTP response to HTTPS client访问协议与端口配置不一致检查 http/https 和端口映射push 大镜像 413外层网关或 nginx 限制请求体调大 client_max_body_size扫描结果为空Trivy 漏洞库未更新离线同步 CVE 数据库拉取旧镜像报 manifest unknownDocker 客户端版本过老升级 Docker 到 20.x 以上PostgreSQL 容器频繁重启磁盘写满或数据卷权限检查磁盘空间和 /data 权限这里再补充一个通用的排查姿势任何组件异常先 docker compose logs 看对应容器日志Harbor 的核心服务日志在容器内也有输出路径。我曾经被一个“Harbor 打不开 UI”的问题困住半天最后发现是 harbor-core 日志里有一行 Redis 连接超时而 Redis 容器一直在重启。日志永远是最靠谱的线索比瞎猜配置高效得多。部署完 Harbor 之后我习惯做一次“模拟故障演练”手工 kill 掉 harbor-core 容器观察编排是否自动拉起把数据盘临时只读验证监控是否告警用 robot 账号跑一遍 push/pull确认 CI 流程不会断。这套动作做完心里对这个仓库系统才算有了底。容器平台这东西方案设计得再漂亮最终还是要靠一次次实际操作把问题逼出来这也是我个人在所有环境里踩过无数坑之后最深的体会。
返回列表