Windows Docker 环境搭建 GitLab CI/CD 流水线:从零到一的完整实践
引言
GitLab CI/CD 是持续集成与持续部署工具之一。它天然集成在 GitLab 代码仓库中,无需额外搭建 Jenkins 等第三方 CI 平台,开箱即用。本文将记录在 Windows 环境下使用 Docker 部署 GitLab 和 GitLab Runner,并成功跑通一条完整 CI/CD 流水线的全过程,包括环境搭建、原理剖析、踩坑与解决,希望能为同样在 Windows 上进行 DevOps 实践的开发者提供一份可复用的参考。
一、环境准备
1.1 基础环境
- 操作系统:Windows(Docker Desktop 运行于 WSL 2 后端)
- 容器运行时:Docker Desktop
- 目标技术栈:Java + Maven 多模块项目
1.2 部署 GitLab 服务端
使用 Docker 一键拉起 GitLab CE 社区版容器:
bash
docker run -d --name gitlab \
--shm-size=1g \
-p 8012:8012 \
-e GITLAB_OMNIBUS_CONFIG="external_url 'http://host.docker.internal:8012';" \
gitlab/gitlab-ce:latest
关键参数说明:
--shm-size=1g:增加共享内存大小,避免 GitLab 运行时因内存不足而崩溃。-p 8012:8012:将容器内 8012 端口映射到宿主机 8012 端口,此处将默认的 80 端口改为 8012。external_url:设置 GitLab 的外部访问地址。host.docker.internal是 Docker Desktop 在 Windows/Mac 下提供的特殊域名,容器内可通过它访问宿主机;Linux 下需要额外配置或改用宿主机 IP。- 示例中未挂载数据卷,生产环境需要挂载。完整部署流程可参考《使用 Docker 部署 GitLab》。
1.3 部署 GitLab Runner
Runner 是 GitLab CI/CD 的执行引擎,负责拉取并执行流水线任务。
bash
docker run -d --name gitlab-runner \
-v //var/run/docker.sock:/var/run/docker.sock \
gitlab/gitlab-runner:v18.8.0
核心挂载说明:
-v //var/run/docker.sock:/var/run/docker.sock:将宿主机的 Docker 套接字挂载到 Runner 容器内。在 Windows 下,//var/run/docker.sock实际对应 Windows 命名管道\\.\pipe\docker_engine。该挂载让 Runner 容器内的 Docker 客户端能够与宿主机 Docker 守护进程通信,从而启动 CI/CD 任务容器。- Linux 下可直接使用
-v /var/run/docker.sock:/var/run/docker.sock。
1.4 注册 Runner
bash
docker exec -it gitlab-runner gitlab-runner register \
--non-interactive \
--url http://host.docker.internal:8012 \
--registration-token xxxx \
--description "local-docker-runner" \
--executor docker \
--docker-image maven:3.9-eclipse-temurin-17-alpine \
--docker-pull-policy if-not-present
参数说明:
--non-interactive:非交互式注册,通过命令行一次性完成配置。--url http://host.docker.internal:8012:指定 GitLab 服务器地址。host.docker.internal在 Windows/Mac 下可解析到宿主机;若在 Linux 宿主机上运行 Docker,可改用http://172.17.0.1:8012或使用--network host模式。--registration-token xxxx:注册令牌,从 GitLab 项目或组的 Settings → CI/CD → Runners 页面获取。--description "local-docker-runner":Runner 的描述性名称,便于在后台识别。--executor docker:使用 Docker 执行器,每个 CI Job 会启动一个全新的 Docker 容器运行,结束后销毁。--docker-image maven:3.9-eclipse-temurin-17-alpine:默认基础镜像,包含 Maven 3.9 和 Java 17。--docker-pull-policy if-not-present:镜像拉取策略,优先使用本地镜像,本地不存在时才从 Docker Hub 拉取,可显著加快本地流水线速度。

二、GitLab CI/CD 核心原理
2.1 Runner 的工作机制:拉取而非推送
在项目根目录下编写 .gitlab-ci.yml 文件定义流水线。代码推送到 GitLab 后,GitLab 解析该文件并生成任务,放入队列。
Runner 主动轮询 GitLab 服务器:每隔几秒向 GitLab API 发送请求,询问是否有待执行任务。若有则领取并执行,否则继续等待。这种拉取模式的优势在于:
- Runner 可部署在内网,无需 GitLab 主动穿透防火墙。
- GitLab 短暂重启后,Runner 可在下一轮询周期自动重试。
2.2 Docker 执行器的工作流程
当 Runner 领取到一个 Job 后,Docker 执行器按以下步骤执行:
- 准备阶段:创建并启动所需的服务容器。
- 预作业阶段:克隆代码仓库、恢复缓存、下载前一阶段的制品。
- 作业阶段:在用户指定的 Docker 镜像中执行构建脚本,如
mvn package。 - 后作业阶段:创建缓存、上传制品到 GitLab。
Runner 通过数据卷挂载将代码目录共享给 Job 容器。
三、流水线配置实战
3.1 多阶段流水线设计
将流水线拆分为编译、测试、打包、部署四个阶段,实现完整链路。
yaml
stages:
- compile
- test
- package
- deploy
variables:
MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2"
cache:
key: maven-cache
paths:
- .m2/
compile:
stage: compile
image: maven:3.9-eclipse-temurin-17-alpine
script:
- mvn clean compile
artifacts:
paths:
- '**/target/classes/'
- '**/target/generated-sources/' # 如有代码生成器
expire_in: 1 day
test:
stage: test
image: maven:3.9-eclipse-temurin-17-alpine
script:
- mvn test
dependencies:
- compile
artifacts:
when: always
reports:
junit: '**/target/surefire-reports/TEST-*.xml'
paths:
- '**/target/surefire-reports/'
expire_in: 6 days
package:
stage: package
image: maven:3.9-eclipse-temurin-17-alpine
script:
- mvn package -DskipTests
dependencies:
- compile
- test
artifacts:
paths:
- '**/target/*.jar'
- '**/target/*.war'
expire_in: 1 week
rules:
- if: '$CI_COMMIT_BRANCH == "master"'
deploy:
stage: deploy
image: maven:3.9-eclipse-temurin-17-alpine
script:
- echo "开始部署当前构建产物..."
- ls -la app/target/*.jar
# 实际部署命令示例:
# - scp **/target/*.jar user@server:/deploy/path/
# - mvn deploy -DskipTests
dependencies:
- package
rules:
- if: '$CI_COMMIT_BRANCH == "master"'
说明:代码推送到 GitLab 仓库或发起合并请求时,会触发 CI/CD 流水线。示例中仅 master 分支执行打包和部署。
四、踩坑实录与解决方案
问题一:Runner 注册成功但 Job 执行时无法访问宿主机 Docker
1.现象:Runner 注册正常,但实际运行 Job 时报错。
ERROR: Failed to remove network for build error=networksManager is undefined
WARNING: Preparation failed: getting docker info: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
2.原因:GitLab Runner 容器无法访问宿主机 Docker daemon。Linux 容器内需要 Unix socket 文件,但默认路径下没有可用的 socket,因为启动 Runner 容器时未挂载宿主机 socket。
3.解决:启动 Runner 容器时挂载 Docker socket。Windows Docker Desktop 下推荐写法:
bash
-v //var/run/docker.sock:/var/run/docker.sock
注意 Windows 路径开头使用双斜杠 //,避免被 shell 或 Docker CLI 转义。
问题二:无法下载镜像文件
1.现象:
Failed to pull image with policy "always": Error response from daemon: failed to resolve reference "docker.io/library/maven:3.9-eclipse-temurin-17-alpine": failed to do request: Head "https://registry-1.docker.io/v2/library/maven/manifests/3.9-eclipse-temurin-17-alpine"
2.原因:Runner 通过宿主机 Docker 启动 Job 容器时,无法从 Docker Hub 下载 Maven 镜像。
3.解决:先通过镜像加速站或代理将镜像下载到本地,然后在注册 Runner 时添加 --docker-pull-policy if-not-present,优先使用本地镜像(默认always, 笔者使用的docker镜像网站为:https://docker.aityp.com/)。
若 Runner 已注册,可修改其配置文件:
bash
docker cp gitlab-runner:/etc/gitlab-runner/config.toml config.toml
编辑 config.toml,在 [runners.docker] 段中添加或修改:
toml
[runners.docker]
image = "maven:3.9-eclipse-temurin-17-alpine"
pull_policy = "if-not-present"
保存后复制回容器并重启:
bash
docker cp config.toml gitlab-runner:/etc/gitlab-runner/config.toml
docker restart gitlab-runner
问题三:Runner 注册成功但 Job 执行时无法从 GitLab 拉取代码
1.现象:Runner 执行 Job 时无法拉取代码,因为 GitLab 生成的仓库地址不可达。
2.原因:GitLab 启动时使用了 external_url "http://localhost:8012",该地址仅对宿主机有效,Runner 容器无法通过 localhost 访问 GitLab。
3.解决:
方案一:重新构建 GitLab 容器,将 external_url 改为容器可识别的宿主机地址:
bash
-e GITLAB_OMNIBUS_CONFIG="external_url 'http://host.docker.internal:8012';"
方案二:进入 GitLab 容器,编辑 /etc/gitlab/gitlab.rb,修改:
ruby
external_url 'http://host.docker.internal:8012'
然后重新配置并重启:
bash
gitlab-ctl reconfigure
gitlab-ctl restart
问题四:多模块项目 Artifacts 上传失败
1.现象:mvn package 构建成功,但 artifacts 上传时报错:
WARNING: target/*.jar: no matching files
2.原因:项目为 Maven 多模块结构,JAR 包生成在子模块目录中,例如 app/target/app-0.1.0-SNAPSHOT.jar。而 artifacts 配置的 target/*.jar 只匹配根目录下的 target/。
3.解决:使用递归通配符 **/target/*.jar,匹配所有层级子目录中的 JAR 包。同样,编译阶段的 target/classes/ 也应改为 **/target/classes/。
yaml
artifacts:
paths:
- '**/target/*.jar'
五、进阶:模拟 Merge Request 流程
基础流水线跑通后,模拟真实的 MR 协作流程:
- 创建功能分支:
git checkout -b dev - 提交代码:
git push origin dev - 发起 MR:在 GitLab 网页端创建从
dev到master的合并请求 - 自动触发流水线:MR 创建后,GitLab 自动针对源分支运行 CI 流水线
- 合并后自动部署:MR 合并入
master后,触发deploy阶段,完成自动部署
MR 不仅是合并代码的入口,更是质量卡点,通过 CI 流水线自动验证代码质量,避免问题代码进入主分支。

六、总结与展望
通过本次实践,从零开始在 Windows 环境下搭建了一套完整的 GitLab CI/CD 系统,核心收获包括:
- Runner 采用拉取模式,主动轮询 GitLab 领取任务。
- Docker 执行器的作业流程为:准备 → 预作业(克隆代码、恢复缓存)→ 作业(执行构建)→ 后作业(上传制品)。
- Windows 下 Docker 套接字的特殊写法:
//var/run/docker.sock对应命名管道\\.\pipe\docker_engine。 - 多模块项目应使用递归通配符
**/target/*.jar匹配子模块制品。
后续可探索的方向包括:使用 Kubernetes 执行器实现 Runner 弹性伸缩、配置分布式缓存支持多 Runner 共享依赖、集成安全扫描实现 DevSecOps。
希望这份实践记录能为你的 GitLab CI/CD 实践提供帮助。如遇到问题,欢迎在评论区交流讨论。
愿你我都能在各自的领域里不断成长,勇敢追求梦想,同时也保持对世界的好奇与善意!