在 WSL2 中搭建 GitLab Community Edition
WSL2 本地 GitLab CI/CD 最小实验记录
从零搭建 GitLab CE、GitLab Runner,并逐步验证用户权限、Runner、Pipeline、Artifact、Manual Deploy、Environment、Protected Branch、Merge Request Pipeline、稀缺 Testbed 调度与 Release Pipeline。
1. 实验目标
这套实验并不是为了单纯“安装一个 GitLab”,而是希望在本地复现真实企业 CI/CD 中的一些核心概念:
- GitLab Self-Managed
- GitLab 用户、Group、Project 与权限
- GitLab Runner
- Shell Executor
- Pipeline / Stage / Job
- Artifacts
- Manual Deployment
- Environment
- Protected Branch
- Merge Request Pipeline
- MR-only Pipeline
- Runner Tag
- Runner Scope
- 稀缺测试资源调度
- Release Pipeline
实验环境:
1 | Windows |
GitLab 使用本地域名:
1 | http://gitlab.test:8929 |
2. 使用 Docker 部署 GitLab CE
创建目录:
1 | mkdir -p /svc/gitlab |
创建 compose.yml:
1 | services: |
启动:
1 | docker compose up -d |
检查:
1 | docker ps |
GitLab 内部实际上包含很多服务,例如:
1 | nginx |
因此 GitLab 容器启动速度明显慢于普通 Web 服务。
3. GitLab unhealthy 与 /dev/shm
第一次启动后曾出现:
1 | Up ... (unhealthy) |
但执行:
1 | docker exec -it gitlab gitlab-ctl status |
可以看到:
1 | postgresql run |
说明 GitLab 核心组件其实都已经启动。
日志中出现:
1 | writing value to /dev/shm/gitlab/sidekiq/... failed |
Docker 默认共享内存较小,因此在 Compose 中显式增加:
1 | shm_size: '256m' |
验证:
1 | docker exec gitlab df -h /dev/shm |
4. gitlab.local、gitlab.test 与域名解析
最开始使用:
1 | gitlab.local |
后来切换为:
1 | gitlab.test |
原因之一是 .local 通常和 mDNS 有特殊关系,而 .test 更适合作为测试域名。
Windows:
1 | C:\Windows\System32\drivers\etc\hosts |
添加:
1 | 127.0.0.1 gitlab.test |
然后:
1 | ipconfig /flushdns |
验证:
1 | ping gitlab.test |
GitLab 的:
1 | external_url |
也必须同步修改:
1 | external_url 'http://gitlab.test:8929' |
否则会出现一个很隐蔽的问题:
1 | Runner API URL |
最终 Runner 报:
1 | Could not resolve host: gitlab.local |
检查 GitLab 实际认为自己的 URL:
1 | docker exec gitlab \ |
必须输出:
1 | http://gitlab.test:8929 |
5. 创建普通用户,而不是一直使用 root
root 只用于 GitLab Instance 管理。
额外创建普通用户:
1 | Username: jason |
之后日常操作使用 jason。
创建 Group:
1 | devops-lab |
然后创建项目:
1 | devops-lab/ci-playground |
这样权限层次变成:
1 | GitLab Instance |
6. Windows / WSL 中区分 GitHub 与本地 GitLab Git Identity
Git 的:
1 | user.name |
决定的是 Commit Author。
可以使用 conditional include。
例如:
1 | # ~/.gitconfig |
然后:
1 | # ~/.gitconfig-gitlab |
验证:
1 | git config user.name |
注意:
1 | Commit Author |
这是两套独立机制。
7. GitHub 与 Local GitLab 使用不同 SSH Key
SSH alias 配置位于:
1 | ~/.ssh/config |
例如:
1 | Host github.com |
gitlab-local 只是本机 SSH alias,不是 DNS。
因此:
1 | ssh -T git@gitlab-local |
实际上等价于:
1 | HostName gitlab.test |
Clone 时:
1 | git clone \ |
而直接:
1 | git clone \ |
不会匹配:
1 | Host gitlab-local |
因此可能使用错误的 SSH Key,最终出现:
1 | Permission denied (publickey) |
8. 第一条 GitLab Pipeline
第一版 .gitlab-ci.yml:
1 | stages: |
Push 后:
1 | Git push |
但一开始所有 Job 都是:
1 | Pending |
原因:
1 | GitLab 没有可用 Runner |
9. GitLab Runner:Project Runner 与 Runner Scope
最开始创建的是:
1 | Project Runner |
它只属于:
1 | ci-playground |
新的:
1 | waterfall-ci-playground |
不会自动拥有它。
Runner Scope 可以分为:
1 | Project Runner |
概念上类似:
1 | Project Runner |
对于:
1 | devops-lab/ |
Group Runner 会更合理。
10. Tagged Job 与 Untagged Job
Runner 可以设置 tag:
1 | shell |
Job 也可以:
1 | tags: |
意思是:
1 | 这个 Job 必须找到包含 shell tag 的 Runner |
如果 Job 没有:
1 | tags: |
则属于:
1 | untagged job |
这时候 Runner 必须启用:
1 | Run untagged jobs |
否则即使 Runner:
1 | Online |
Job 仍然会:
1 | Pending |
因此:
1 | Runner Online |
还要经过 Runner 匹配。
11. User-mode Runner 与 System-mode Runner
第一次手工启动:
1 | gitlab-runner run |
日志:
1 | WARNING: Running in user-mode. |
配置文件:
1 | /home/gitlab-runner/.gitlab-runner/config.toml |
后来改成 system-mode。
systemd:
1 | systemctl cat gitlab-runner |
实际启动命令:
1 | /usr/local/bin/gitlab-runner run |
这说明:
1 | Config: |
启动:
1 | sudo systemctl enable gitlab-runner |
验证:
1 | sudo systemctl is-enabled gitlab-runner |
这样 WSL 启动后,Runner 也可以跟随 systemd 自动启动。
12. Shell Executor 与 Linux 文件权限
第一次 user-mode Runner 是在:
1 | /home/zxy/workspace/gitlab-test/ci-playground |
目录中启动的:
1 | gitlab-runner run |
于是默认 working directory 继承了当前目录,导致 Runner 创建:
1 | /home/zxy/workspace/gitlab-test/ci-playground/builds/ |
但执行用户是:
1 | gitlab-runner |
最终:
1 | rm: cannot remove ... Permission denied |
这说明 Shell executor 本质上就是:
1 | Runner |
因此:
1 | Linux ownership |
都会影响 CI。
System-mode Runner 使用:
1 | /home/gitlab-runner |
作为 working directory 后解决。
13. .bash_logout 导致 Prepare Environment 失败
System Runner 第一次执行 Job:
1 | Preparing environment |
Shell executor 会启动 login shell,因此会加载:
1 | .profile |
/home/gitlab-runner/.bash_logout 中的默认逻辑导致 CI shell 退出失败。
删除:
1 | sudo rm /home/gitlab-runner/.bash_logout |
后解决。
这说明 CI 执行失败不一定来自 .gitlab-ci.yml。
也可能来自:
1 | Runner host environment |
14. Artifact:Job 之间如何传递文件
Build:
1 | build-job: |
Test:
1 | test-job: |
重要的是:
1 | build-job |
build/app.txt 能出现在 test job 中,是因为:
1 | build |
而不是因为两个 Job 共用了同一个工作目录。
15. Manual Deployment
增加:
1 | deploy-job: |
Pipeline:
1 | build ✅ |
必须人工点击:
1 | Play / Run |
才执行 deploy。
这个就是最简单的:
1 | Manual Gate |
16. GitLab Environment
把 Deployment 和 Environment 关联:
1 | deploy-staging: |
GitLab:
1 | Operate |
会出现:
1 | staging |
这时 GitLab 开始知道:
1 | 哪个 commit |
17. Staging + Production
Pipeline:
1 | build |
Staging 自动:
1 | deploy-staging: |
Production manual:
1 | deploy-production: |
最终:
1 | main |
18. Protected Branch
为了模拟企业权限模型,创建:
1 | developer1 |
角色:
1 | Developer |
jason:
1 | Owner |
保护:
1 | main |
设置:
1 | Allowed to merge: |
于是 Developer:
1 | 可以: |
重要的是:
1 | git commit |
永远发生在本地。
GitLab 无法禁止你本地 commit。
真正被 Server 阻止的是:
1 | git push origin main |
19. MR Pipeline 与 Branch Pipeline
一开始:
1 | workflow: |
结果:
1 | feature push |
如果 MR 已存在,再 push:
1 | branch pipeline |
同一个 commit 被 CI 两次。
对于当前实验,这没有必要。
20. MR-only Pipeline
最终使用:
1 | workflow: |
于是:
1 | feature branch push |
这套逻辑非常清晰:
1 | 开发代码 |
21. CloudIQ 风格的持续部署模型
当前 ci-playground 模拟的是:
1 | feature |
这比较接近持续交付系统。
实际更复杂的系统中,源码 Pipeline 与 Deployment Pipeline 甚至可能是两套系统。
例如:
1 | Application Repository |
后续可以再通过 GitLab Multi-project Pipeline 模拟。
22. Waterfall / Unity / PowerStore 风格 Pipeline
另外建立:
1 | devops-lab/waterfall-ci-playground |
这个模型不同。
核心逻辑:
1 | feature branch |
这属于:
1 | PR / MR 是完整质量 Gate |
而不是:
1 | merge 后继续逐环境持续部署 |
23. Waterfall Pipeline:只允许 MR 与 Release Tag
1 | workflow: |
于是:
1 | push feature branch |
24. 模拟有限的真实 Testbed
Integration Job:
1 | integration-testbed: |
关键:
1 | resource_group: testbed-01 |
这表示:
1 | 同一时间只能有一个 Job 占用 testbed-01 |
例如两个 MR:
1 | MR A build ✅ |
A 完成后:
1 | MR B |
这可以模拟真实环境里:
1 | 昂贵物理机 |
25. 为什么 Deploy + Test 放在同一个 Job
如果写成:
1 | deploy-job |
即使两边都使用:
1 | resource_group: testbed-01 |
Deploy Job 结束后 resource 就被释放。
另一条 Pipeline 可能立刻:
1 | deploy 到同一台 testbed |
覆盖环境。
随后第一条 Pipeline 再测试时:
1 | 测试的已经不是自己部署的内容 |
因此如果需求是:
1 | 从部署开始 |
最简单的方法就是:
1 | deploy + integration test |
整个 Job 使用:
1 | resource_group |
26. Merge Gate
项目设置:
1 | Settings |
然后故意测试失败:
1 | - grep "THIS_WILL_FAIL" \ |
结果:
1 | build ✅ |
修复后:
1 | build ✅ |
这就是:
1 | CI 结果成为代码进入 main 的 Gate |
27. Official Release Pipeline
Merge 到 main 后:
1 | 不自动部署 |
到了 milestone 后打 Tag:
1 | git checkout main |
Release Job:
1 | release-package: |
因此:
1 | MR Pipeline |
28. 一个 YAML 小坑:script 中的冒号
曾出现:
1 | jobs:release-package:script config should be |
原因是:
1 | - echo "Release tag: $CI_COMMIT_TAG" |
其中:
1 | : |
可能被 YAML 解析器误认为:
1 | key: value |
因此更安全的写法:
1 | - 'echo "Release tag: $CI_COMMIT_TAG"' |
CI YAML 不是普通 shell script。
它首先必须经过:
1 | YAML parser |
29. 当前两套 Pipeline 模型
目前实验已经形成两个独立模型。
模型 A:持续交付
1 | feature |
适合:
1 | Web Service |
模型 B:大型产品 / Waterfall Release
1 | feature |
适合:
1 | 大型产品 |
30. Jenkins 与 GitLab CI 概念映射
通过实验后,可以做一个大致映射:
| Jenkins | GitLab |
|---|---|
| Jenkins Controller | GitLab |
| Jenkins Agent / Node | GitLab Runner |
| Node Label | Runner Tag |
| Jenkinsfile | .gitlab-ci.yml |
| Stage | Stage |
| Job / Step | Job / Script |
| archiveArtifacts | artifacts |
| input | manual job |
| Agent Pool | Runner Pool |
| Testbed Reservation | resource_group |
| PR Validation | MR Pipeline |
| Release Job | Tag Pipeline |
尤其是:
1 | GitLab Runner |
它本身并不复杂。
复杂的是:
1 | Runner 调度 |
31. 已遇到的真实故障
整个实验中实际遇到过:
1. GitLab Container Unhealthy
1 | /dev/shm |
相关。
2. gitlab.local 无法解析
1 | Windows hosts |
没有统一。
3. Runner Online 但 Job Pending
原因:
1 | Tagged / Untagged Runner mismatch |
4. Shell Executor Permission Denied
原因:
1 | gitlab-runner user |
权限不匹配。
5. Prepare Environment Failed
原因:
1 | /home/gitlab-runner/.bash_logout |
6. Runner Clone 失败
Runner:
1 | gitlab.test |
GitLab repository URL:
1 | gitlab.local |
7. SSH Clone Permission Denied
直接使用:
1 | gitlab.test |
没有命中:
1 | Host gitlab-local |
导致 SSH Key 不正确。
8. CI YAML Invalid
1 | echo "Release tag: ..." |
中的冒号触发 YAML 解析问题。
32. 当前实验覆盖范围
目前已经实际验证:
1 | GitLab CE |
还没有完成:
1 | Docker Executor |
这些可以作为下一阶段。
33. 最重要的认识
最开始看 GitLab Runner 时,很容易觉得:
Runner 不就是一台执行 Job 的机器吗?
实验之后会发现 Runner 本身确实很简单。
真正复杂的是:
1 | 哪台 Runner 执行? |
因此 JD 中单独写:
1 | GitLab Runner |
通常并不是要求:
1 | 会 gitlab-runner register |
而是希望候选人理解:
1 | CI Execution Infrastructure |
这也是 GitLab Runner 和 Jenkins Agent / Node 最接近的地方。
34. 下一阶段
下一阶段计划:
1 | Shell Executor |
然后再把 Cloud 风格 Pipeline 拆成:
1 | Application Project |
进一步模拟真实的 Deploy Portal 与多项目交付体系。