GitLab CI/CD 集成 Harbor 全面教程
你不只是要把镜像推上仓库,你要的是一套可审计、可复用、易维护的流水线。从管理员的实例级集成,到 Runner 构建方式、项目级 .gitlab-ci.yml 模板、安全合规、缓存加速和故障排查,这篇教程帮你把全链路打通。
环境与目标
- 目标: 代码提交后自动构建镜像并推送到 Harbor,支持版本追踪、缓存加速与安全策略。
- 组件:
- GitLab: 14+(CE/EE,或 GitLab.com)
- GitLab Runner: 14+,Docker executor 或 Kubernetes executor
- Harbor: 2.5+,启用 HTTPS
- 构建器: Docker(DIND/BuildKit)或 Kaniko(K8s)
- 网络/证书:
- HTTPS: 生产必须;Runner/开发机需信任 Harbor CA
- 连通性: Runner 能访问 Harbor;GitLab 能访问 Runner
管理员配置实例级 Harbor 集成
集中管理凭据,避免每个项目都手工维护账号密码。
管理后台设置
- 进入管理员区域: Admin Area → Settings → Integrations(部分版本在 Settings → Network → Container Registry)
- 添加 Registry:
- Name: Harbor
- URL: https://harbor.example.com
- API URL: https://harbor.example.com/api/v2.0
- Username: 建议使用 Harbor 机器人账号,如
robot$gitlab-ci - Password: 对应机器人 token(只赋予推送权限)
- Harbor 准备:
- 项目: 创建
my-project,默认私有 - 机器人账号: 绑定到该项目,权限最小化(Push)
- 项目: 创建
- 变量注入(如版本不支持自动注入则在 Group/Project 级创建):
CI_REGISTRY=harbor.example.comCI_REGISTRY_USER=robot$gitlab-ciCI_REGISTRY_PASSWORD=<robot token>
提示:机器人账号比通用用户更安全,便于轮换和审计。
Runner 与构建方式选择
不同构建器在安全性、性能和兼容性上存在取舍。
对比与建议
- Docker-in-Docker(DIND)
- 优点: 通用、易上手
- 缺点: 需要 privileged;缓存持久化差
- 适用: 快速落地、小团队或非高敏场景
- BuildKit(buildx)
- 优点: 更快、优秀缓存、多平台构建
- 缺点: 需配置 buildx;对旧镜像源兼容需验证
- 适用: 生产推荐,尤其追求速度与多架构
- Kaniko(K8s)
- 优点: 无需 Docker 守护进程,安全、云原生
- 缺点: 少量 Dockerfile 特性兼容性需评估
- 适用: Kubernetes Runner 或禁用特权环境
Docker executor 安装与注册
- 安装 Runner:
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash sudo apt -y install gitlab-runner - 注册 Runner:
sudo gitlab-runner register # 选择 executor: docker # 填默认镜像: docker:24.0 - config.toml(DIND 示例)
[[runners]]name = "docker-dind-runner"executor = "docker"[runners.docker]image = "docker:24.0"privileged = truetls_verify = falsevolumes = ["/cache","/etc/docker/certs.d:/etc/docker/certs.d:ro" # 注入 Harbor CA 证书]shm_size = 0- 证书注入: 将 Harbor CA 放入 Runner 节点
/etc/docker/certs.d/harbor.example.com/ca.crt,并挂载给作业容器。
- 证书注入: 将 Harbor CA 放入 Runner 节点
项目级 CI/CD 模板
根据 Runner 环境选择一套模板即可使用。镜像命名采用“项目/应用:标签”。
模板 A:DIND(通用)
stages:- build- pushservices:- name: docker:24.0-dindcommand: ["--mtu=1450"] # 云环境常用;可删除variables:DOCKER_HOST: tcp://docker:2375IMAGE_REGISTRY: $CI_REGISTRYIMAGE_REPO: my-project/my-appIMAGE_TAG: $CI_COMMIT_SHORT_SHACACHE_REF: "$IMAGE_REGISTRY/$IMAGE_REPO:buildcache"before_script:- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"build:stage: buildimage: docker:24.0script:- docker pull "$CACHE_REF" || true- docker build \--cache-from "$CACHE_REF" \-t "$IMAGE_REGISTRY/$IMAGE_REPO:$IMAGE_TAG" \-t "$IMAGE_REGISTRY/$IMAGE_REPO:latest" .- docker tag "$IMAGE_REGISTRY/$IMAGE_REPO:$IMAGE_TAG" "$CACHE_REF"push:stage: pushimage: docker:24.0script:- docker push "$IMAGE_REGISTRY/$IMAGE_REPO:$IMAGE_TAG"- docker push "$IMAGE_REGISTRY/$IMAGE_REPO:latest"- docker push "$CACHE_REF" || true
模板 B:BuildKit(更快,多平台)
stages: [push]variables:DOCKER_BUILDKIT: "1"IMAGE_REGISTRY: $CI_REGISTRYIMAGE_REPO: my-project/my-appIMAGE_TAG: $CI_COMMIT_SHORT_SHApush:stage: pushimage: docker:24.0before_script:- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"- docker run --privileged --rm tonistiigi/binfmt --install all || true- docker buildx create --use --name builder || docker buildx use builderscript:- docker buildx build \--platform linux/amd64 \--tag "$IMAGE_REGISTRY/$IMAGE_REPO:$IMAGE_TAG" \--tag "$IMAGE_REGISTRY/$IMAGE_REPO:latest" \--push \--cache-to type=registry,ref="$IMAGE_REGISTRY/$IMAGE_REPO:buildcache",mode=max \--cache-from type=registry,ref="$IMAGE_REGISTRY/$IMAGE_REPO:buildcache" \.
模板 C:Kaniko(K8s 无特权)
stages: [push]variables:IMAGE_REGISTRY: $CI_REGISTRYIMAGE_REPO: my-project/my-appIMAGE_TAG: $CI_COMMIT_SHORT_SHApush:stage: pushimage:name: gcr.io/kaniko-project/executor:latestentrypoint: [""]script:- mkdir -p /kaniko/.docker- |cat > /kaniko/.docker/config.json <<EOF{ "auths": { "${CI_REGISTRY}": { "username": "${CI_REGISTRY_USER}", "password": "${CI_REGISTRY_PASSWORD}" } } }EOF- /kaniko/executor \--dockerfile=Dockerfile \--context="$CI_PROJECT_DIR" \--destination="$IMAGE_REGISTRY/$IMAGE_REPO:$IMAGE_TAG" \--destination="$IMAGE_REGISTRY/$IMAGE_REPO:latest"
Harbor 端最佳实践
- 项目与命名:
- 命名规范:
project/app,避免顶层library滥用。 - 标签策略: 用
latest表示“当前主分支”,用vX.Y.Z标记版本,用commit_sha追溯构建。
- 命名规范:
- 机器人账号:
- 最小权限: 仅对指定项目授予 Push;不同环境分账号,便于隔离。
- 轮换: token 定期更换;在 GitLab 变量使用 masked + protected。
- 安全与质量:
- 漏洞扫描: 启用 Trivy,高危阻断(在流水线或 Harbor policy)。
- 镜像签名: 结合 Cosign/Notary v2;在部署阶段验签。
- 复制同步:
- 跨站点: Harbor 间配置 replication,提高就近拉取性能。
证书与网络
- 客户端信任 Harbor:
- Docker 客户端:
/etc/docker/certs.d/harbor.example.com/ca.crt - Runner 节点: 相同路径,并挂载给作业容器/DIND 服务。
- Docker 客户端:
- 自签名开发环境:
- 可用,但生产建议正式证书。
- Insecure Registry(不推荐):
- 在 Runner 的 Docker
daemon.json配置insecure-registries后重启;仅在受控内网临时使用。
- 在 Runner 的 Docker
常见故障与直击要点
- 登录 401
- 核查: 机器人账号是否绑定项目并具备 Push;变量拼写;Harbor 域名/端口一致。
- 证书 x509
- 核查: 证书链完整;CA 放置路径正确;DIND 服务也要注入证书。
- push denied
- 核查: 仓库路径
project/app是否存在;项目私有与权限配置是否正确。
- 核查: 仓库路径
- Runner 不支持特权
- 解决: 改用 BuildKit/ Kaniko;避免
privileged = true。
- 解决: 改用 BuildKit/ Kaniko;避免
- 缓存不生效
- 建议: 固化缓存镜像名;优化 Dockerfile 层(先拷贝清单文件再安装依赖)。
- 多架构失败
- 解决: 先安装
binfmt;确保基础镜像支持目标平台。
- 解决: 先安装
合规与加速建议
- 合规门禁:
- 分支策略: 仅
main推latest;仅 tag 推v*;MR 合并前构建但不推正式标签。 - SBOM: 用 Syft 生成 SBOM 并存档;安全审计可追溯。
- 分支策略: 仅
- 构建加速:
- 镜像缓存: BuildKit
--cache-to/--cache-from指向 Harbor。 - 依赖代理: NPM/PIP/Maven 配企业代理;减少外网开销。
- 镜像缓存: BuildKit
最小可用清单(复制即用)
- CI/CD 变量(组/项目级)
- CI_REGISTRY: harbor.example.com
- CI_REGISTRY_USER: robot$gitlab-ci
- CI_REGISTRY_PASSWORD:
- DIND 简版 .gitlab-ci.yml
stages: [build, push] services: [docker:24.0-dind] variables:DOCKER_HOST: tcp://docker:2375IMAGE_REGISTRY: $CI_REGISTRYIMAGE_REPO: my-project/my-appIMAGE_TAG: $CI_COMMIT_SHORT_SHA before_script:- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" build:stage: buildimage: docker:24.0script:- docker build -t "$IMAGE_REGISTRY/$IMAGE_REPO:$IMAGE_TAG" -t "$IMAGE_REGISTRY/$IMAGE_REPO:latest" . push:stage: pushimage: docker:24.0script:- docker push "$IMAGE_REGISTRY/$IMAGE_REPO:$IMAGE_TAG"- docker push "$IMAGE_REGISTRY/$IMAGE_REPO:latest"
