初探 Docker 部署 GitLab
GitLab 自托管这事,我前后折腾了整整两天。第一天在裸机上装 GitLab CE,装到一半各种依赖问题、系统包冲突,差点没把服务器搞崩。第二天换思路,直接用 Docker 跑官方镜像,从拉镜像到能正常创建仓库、push 代码,加起来不到半个小时。这篇文章就是把我验证过的部署方式、踩坑记录和关键参数全部整理出来,给想自建代码仓库的朋友做个参考。
不管你是个人开发者想搭一个私有仓库,还是小团队需要一个内网代码托管平台,用 Docker 部署 GitLab 都是目前省心的方案之一。镜像开箱即用,数据通过 Volume 持久化,升级、回滚、迁移都很方便,不用去关心那些底层依赖包到底装没装对。下面我从方案对比讲起,一步一步把整套流程拆开讲清楚。
1. 为什么选 Docker 部署 GitLab:方案对比与思路拆解
1.1 裸机安装 GitLab 的核心痛点
先说我在裸机上踩的坑。GitLab 是基于 Ruby on Rails 的复杂应用,底层依赖 nginx、postgresql、redis、gitaly 等一整套服务,官方虽然提供了集成好的 omni 安装包,理论上一条命令就能装好,但实际跑起来远没那么顺。
我遇到的最典型问题就是系统环境冲突。服务器上原本跑着别的 Web 服务,GitLab 自带的 nginx 要监听 80 端口,一上来就跟现有服务打架。想把 GitLab 的 nginx 关掉用已有的反向代理,又得去读一堆配置项,改了之后还要gitlab-ctl reconfigure重新生成配置,整个过程对新手非常不友好。
另外就是升级问题。裸机安装的 GitLab 升级路径是固定的,不能跨大版本直接跳,否则数据迁移脚本会出问题。有一次我从 13.x 升到 14.x,升级过程中 postgresql 没起来,整个应用直接 502,排查了半天发现是磁盘空间不够。这类问题在裸机上很常见,因为所有依赖都安装在系统层,任何偏差都可能连锁引爆。
1.2 Docker 容器化方案的核心优势
Docker 部署最直观的好处是把整个 GitLab 环境打包在一个镜像里。nginx、postgresql、redis、gitlab-workhorse 这些组件全部封装在容器内部,与宿主机隔离,不存在依赖包冲突的问题。
数据持久化通过 Volume 挂载解决。GitLab 的所有数据落在宿主机目录里,容器删了重建,数据还在。这带来了两个巨大优势:第一,升级就是替换镜像版本后重新创建容器;第二,回滚就是把镜像标签换回旧版本再启动一次。
还有一点是资源隔离。GitLab 官方建议最低内存 2GB,我实际测试下来,小团队用 4GB 内存跑 CE 版本整体还算流畅。容器方案可以精确控制内存、CPU 限制,还能查看容器实时资源占用,比在裸机上看系统全局负载要直观得多。
1.3 镜像选型:官方镜像还是第三方镜像
GitLab 官方提供了gitlab/gitlab-ce和gitlab/gitlab-ee两个镜像,CE 是社区免费版,EE 是企业版带更多高级功能。生产环境我直接用 CE 就够了,CE 包含了项目管理、代码托管、CI/CD、代码审查这些日常高频功能。
社区里也有一批第三方镜像,比如之前比较流行的人人皆知的那个 all-in-one 镜像,把 GitLab 和 Docker Registry 等常用服务打包在一起。但我实测下来,第三方镜像的问题在于升级维护节奏跟官方不完全同步,出问题去查 issue 时,很多坑都没人踩过,最终还得自己看官方文档。所以我的建议很直接:除非你有特殊需求,否则尽量用官方镜像。官方镜像本身就是基于 Omnibus 包构建的,和裸机安装的核心逻辑一致,但省掉了所有环境层面的麻烦。
2. 环境准备与参数规划:部署前要考虑清楚的事
2.1 服务器配置与系统要求
先聊资源。GitLab CE 官方建议是 4GB 内存起步,2GB 是勉强能跑的最低水平。我实测 2GB 内存跑起来后,GitLab 的 Sidekiq 后台任务和 Gitaly 一起跑,内存占用轻松超过 1.5GB,操作界面会有明显的卡顿感。如果你的团队只有几个人,4GB 内存的机器能比较流畅地跑起来,再多人的话建议 8GB 或以上。
硬盘按代码仓库大小来估。Git 仓库天生会膨胀,一个活跃项目的仓库两三 GB 很常见,再加上 GitLab 自带的备份、CI 缓存、日志,我建议至少预留 50GB 可用空间。如果是企业级使用场景,单独挂一块数据盘给 GitLab 的 Volume 目录是最稳的做法。
操作系统方面,Ubuntu 20.04+ 和 CentOS 7.9+、Debian 11 都是经过验证的。内核版本只要支持 Docker 20.10 以上即可,基本没什么特殊要求。
2.2 Docker 与 Docker Compose 安装
这里不贴所有操作系统的安装细节了,Ubuntu 系的典型流程是:
# 安装依赖 sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg # 添加 Docker 官方仓库并安装 sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin安装完之后确认一下 Docker 版本和 Compose 插件:
docker --version docker compose version我建议从这一步就养成习惯:用 Docker Compose 管理 GitLab,而不是裸docker run。Compose 的好处是部署配置以文件形式保存,换机器、迁移环境时直接拷贝一份docker-compose.yml就能原样复现。这个优势在部署早期就体现出来了。
提示:如果服务器本身有旧版 Docker,先卸载再装新版。我遇到过一次旧版 Docker 和 docker-compose-plugin 版本不匹配,导致 compose 指令一直提示找不到命令,排查了十几分钟,最后重装才解决。
2.3 端口规划:80、22 冲突是最常见的问题
GitLab 容器内部实际上监听了三个端口:80(HTTP)、443(HTTPS)、22(SSH)。如果你直接用默认映射,把宿主机的 80、22 直接映射到容器,就会出现一个尴尬局面——宿主机自己要跑 nginx、要接受 SSH 登录,端口全被 GitLab 占了。
我给团队部署时的端口方案是:
| 服务 | 宿主机端口 | 容器端口 | 说明 |
|---|---|---|---|
| HTTP 访问 | 8088 | 80 | 团队习惯用 8088 访问 GitLab 页面 |
| SSH 推送 | 2222 | 22 | 避免和宿主机 SSH 登录冲突 |
| HTTPS(可选) | 8443 | 443 | 生产环境走 HTTPS 时用 |
这个方案的优势在于宿主机原有的服务完全不受影响,SSH 登录照旧用 22 端口,GitLab 走独立的高位端口。缺点是要让团队成员记住一个非标准端口,后续配合external_url和 SSH 端口配置可以把它变成透明的,后面会细说。
2.4 目录规划与数据卷设计
GitLab 官方镜像文档明确了三个数据目录需要挂载出来:
/etc/gitlab:配置文件目录,含gitlab.rb等/var/opt/gitlab:实际数据目录,仓库、数据库、git 用户数据都在这里/var/log/gitlab:日志目录
我通常在一个统一目录下创建三个子目录,命名清晰方便备份:
mkdir -p /srv/gitlab/config mkdir -p /srv/gitlab/data mkdir -p /srv/gitlab/logs把这三个目录挂进容器后,容器可以随时销毁重建,数据安然无恙。这是理解容器化 GitLab 运维的基石:容器是耗材,数据才是资产。
3. 完整部署流程与关键参数解析
3.1 用 docker run 拉起第一版 GitLab
先给一个能最快跑起的docker run命令,让你在一分钟内看到 GitLab 界面:
docker run --detach \ --name gitlab \ --restart always \ --publish 8088:80 \ --publish 2222:22 \ --volume /srv/gitlab/config:/etc/gitlab \ --volume /srv/gitlab/data:/var/opt/gitlab \ --volume /srv/gitlab/logs:/var/log/gitlab \ --shm-size 256m \ --env TZ=Asia/Shanghai \ gitlab/gitlab-ce:latest这里每个参数都有讲究:
--detach:后台运行,终端关掉容器照样跑。--restart always:宿主机重启后容器自动拉起,服务不人工干预就能恢复。实测这一条对稳定性帮助极大。--publish 8088:80:把容器内 GitLab 的 HTTP 端口映射到宿主机 8088。端口冲突问题在上面已经聊过。--publish 2222:22:把容器内的 SSH 端口映射到宿主机 2222,避免抢占宿主机的 SSH 登录端口。--shm-size 256m:给容器的/dev/shm分配 256MB。这个参数容易被忽略,但很重要。GitLab 内部使用 Puma 处理并发请求,postgresql 的共享内存也依赖/dev/shm,默认的 64MB 在某些并发场景下会触发 OOM 甚至导致 GitLab 界面打不开。我一开始没加这个参数,高峰期进程直接被杀,加了之后就没再出现。--env TZ=Asia/Shanghai:把容器时区设为东八区。不设的话,GitLab 的日志时间和仓库提交时间会显示 UTC,和团队实际时间对不上,代码提交记录看着非常别扭。
3.2 推荐方案:用 docker-compose 管理编排
docker run适合验证,但正式使用我强烈建议切到 Docker Compose。两者管理逻辑差距很大:Compose 把启动参数、持久化映射、环境变量都固化在docker-compose.yml里,换机器时一条命令就能拉起整套环境,而docker run需要手工把命令重新敲一遍。
我的docker-compose.yml长这样:
version: "3.8" services: gitlab: image: gitlab/gitlab-ce:latest container_name: gitlab restart: always hostname: gitlab.example.local ports: - "8088:80" - "2222:22" - "8443:443" volumes: - /srv/gitlab/config:/etc/gitlab - /srv/gitlab/data:/var/opt/gitlab - /srv/gitlab/logs:/var/log/gitlab environment: TZ: Asia/Shanghai GITLAB_ROOT_PASSWORD: "YourStrongPassword123!" shm_size: "256m"启动命令:
docker compose up -d说几个这里的关键点:
hostname 参数。Compose 里的hostname会直接影响 GitLab 生成的仓库克隆地址。比如这里设置了gitlab.example.local,那么创建项目后复制到的克隆地址就是http://gitlab.example.local:8088/...。这个值建议和你规划的访问域名保持一致,否则后面改起来比较麻烦。
GITLAB_ROOT_PASSWORD 环境变量。这是初始 root 管理员密码,只在第一次初始化时生效。如果容器已经启动过再改这个环境变量,密码不会变。注意密码强度要符合 GitLab 要求,至少 8 位且包含大小写和数字,否则初始化会直接失败。
环境变量 TZ。这个在上面已经说过,最好从第一次部署就带上。
如果第一次部署时没有设置 GITLAB_ROOT_PASSWORD,GitLab 会把随机初始密码写进/etc/gitlab/initial_root_password文件,这个文件有效期只有 24 小时,必须在有效期内登录后手动改密码。我后面会专门讲忘了密码怎么办。
3.3 首次初始化与登录验证
容器启动后,GitLab 内部要做数据库初始化、服务编排等一系列工作,第一次启动比较耗时。我实测用机械硬盘要 5 到 8 分钟,SSD 快一些 3 分钟左右。判断是否就绪有两种办法:
# 查看容器状态 docker ps | grep gitlab # 查看初始化日志,看到 "gitlab Reconfigured!" 字样基本就是好了 docker logs -f gitlab也可以直接访问http://服务器IP:8088,能看到登录页说明已经就绪。没就绪时通常提示 502,这时候别慌,去查日志确认 running 状态。
第一次登录用用户名root,密码就是你设的 GITLAB_ROOT_PASSWORD 或者initial_root_password文件里的内容。登录后第一件事是改密码,并创建自己的管理员账号。如果团队里有其他人要一起用,我建议在 GitLab 里建好组和几个普通账号,不要让所有人都共用 root,权限管理会失控。
3.4 配置 external_url 与 SSH 端口:让克隆地址不踩坑
GitLab 默认生成的仓库克隆地址,和部署方式强相关。如果部署时什么都没配,克隆地址会变成容器内的地址,其他人根本访问不了。这类问题非常常见,尤其是第一次用 Docker 部署的人。
在gitlab.rb里需要配两个东西:external_url和 SSH 端口。
进容器改配置:
docker exec -it gitlab /bin/bash vi /etc/gitlab/gitlab.rb在文件里找到或添加:
external_url 'http://gitlab.example.local:8088' gitlab_rails['gitlab_shell_ssh_port'] = 2222external_url决定了 HTTP 克隆地址的前缀。我这里写的是http://gitlab.example.local:8088,实际使用时要换成你的域名或服务器 IP。
gitlab_shell_ssh_port这行特别重要,它告诉 GitLab 客户端 SSH 克隆时连的是 2222 端口而不是默认的 22。如果你不配这行,哪怕宿主机的 2222 正确映射到了容器的 22,GitLab 生成的 SSH 克隆地址仍然是git@gitlab.example.local:group/project.git,后面没带端口,客户端默认连 22 端口,结果自然是连接失败。
改完之后执行:
gitlab-ctl reconfigure这个命令会重新生成 nginx、gitlab-workhorse 等组件的配置并重启相应服务。如果想立即生效,也可以执行一次gitlab-ctl restart。
注意:
gitlab.rb是很典型的"改完必须重新配置"的服务。手动去改 nginx 配置是没有意义的,下一次 reconfigure 会被覆盖。所有自定义配置都要写进gitlab.rb,再通过gitlab-ctl reconfigure下发。
3.5 数据持久化原理与本机备份恢复实操
容器删了数据还在,靠的是 Volume 映射。原理很简单:容器内/var/opt/gitlab目录的内容实际落在宿主机/srv/gitlab/data里。这意味着你可以用docker compose down把容器完全停掉删除,数据一点不丢。
但真正需要重视的是备份。GitLab 的备份分为两部分:
应用数据备份(仓库、数据库、上传文件):
docker exec -t gitlab gitlab-backup create配置文件备份(gitlab.rb等关键配置):
docker exec -t gitlab gitlab-ctl backup-etc每次执行gitlab-backup create会在容器内生成一个带时间戳的 tar 包,默认目录是/var/opt/gitlab/backups。这个目录默认不随 Volume 挂载出来,所以备份文件本身还在容器里,容器没了备份也没了。因此我强烈建议把 backups 目录也挂载到宿主机,在 docker-compose.yml 里加一行:
volumes: - /srv/gitlab/config:/etc/gitlab - /srv/gitlab/data:/var/opt/gitlab - /srv/gitlab/logs:/var/log/gitlab - /srv/gitlab/backups:/var/opt/gitlab/backups这样备份文件直接落在宿主机目录,配合系统层面的定时 rsync 到异地,就形成了完整的备份链路。
恢复操作如下:
# 先进入维护模式或至少停掉关键服务再恢复 docker exec -t gitlab gitlab-ctl stop puma docker exec -t gitlab gitlab-ctl stop sidekiq # 恢复指定备份,BACKUP 参数是不带 _gitlab_backup.tar 前缀的时间戳 docker exec -t gitlab gitlab-backup restore BACKUP=1723456789_2024_08_12_16.9.1 # 恢复后重启所有服务 docker exec -t gitlab gitlab-ctl restart恢复过程会覆盖当前数据库和仓库数据,这个操作务必在确认备份文件完整的前提下执行,还原错了可没有后悔药。
4. 实操过程与核心环节调试记录
4.1 修改 root 密码:忘记密码时的标准操作
GitLab 用久了很容易忘 root 密码,特别是当 initial_root_password 文件过了 24 小时失效后。这时候不能靠gitlab-rails直接改数据库,因为 GitLab 的密码是加密存储的,正确姿势是通过 Rails Runner 调用官方接口。
新版 GitLab(16.x 及以上)提供了更直接的方式:
docker exec -it gitlab gitlab-rake "user:reset_password[root, YourNewPassword123!]"如果你的版本不支持这个命令,就用经典的 Rails Runner 方式:
docker exec -it gitlab gitlab-rails runner "user = User.find_by_username('root'); user.password = 'YourNewPassword123!'; user.password_confirmation = 'YourNewPassword123!'; user.save!"执行完后确认输出为true,说明密码更新成功。这两条命令都是在容器内对 GitLab 应用层进行操作,不要直接去改数据库表,否则密码加密格式对不上,还是登不进去。
4.2 容器内服务异常排查:从 502 到状态恢复
GitLab 内部装了 nginx、puma、sidekiq、postgresql、redis、gitaly 等多个服务,任何一个挂掉都会导致界面异常或者功能不可用。最常见的现象是登录页面能打开,但一操作就报 502。
排查路径我总结为三步:
第一步,看容器状态和容器主进程是否正常:
docker ps docker stats gitlab如果容器已经退出,检查是不是 OOM 导致。看宿主机内存:
free -h第二步,进容器看内部服务状态:
docker exec -it gitlab gitlab-ctl status正常输出里每个服务后面都是run和对应的 PID,如果有服务显示down,用gitlab-ctl tail 服务名查看具体日志。
我实际遇到最多的问题是 postgresql 起不来,典型原因就是磁盘空间不足或者内存不足。先df -h看磁盘,再gitlab-ctl restart postgresql重启数据库服务。
第三步,如果所有服务都显示 run,但 GitLab 页面还是 502,检查是不是 nginx 反向代理连不上 puma。执行:
docker exec -it gitlab gitlab-ctl tail nginx如果看到大量连接拒绝,多半是 puma 没起来或者内存不够反复退出。GitLab 的 502 排查核心逻辑就一句话:只要 postgresql 正常、redis 正常、puma 正常,基本不会 502。
4.3 端口占用导致启动失败:从错误提示定位
Docker 部署 GitLab 最常见的启动失败原因就是端口冲突。你规划的宿主机 8088 端口可能早就被别的服务占了,docker compose up -d时会提示:
Error starting userland proxy: listen tcp4 0.0.0.0:8088: bind: address already in use解决办法两种:换端口,或者清理占用。先查占用进程:
ss -tlnp | grep 8088如果是自己之前起的服务,直接在 compose 文件里换一个端口即可。比如把"8088:80"改成"18088:80",再docker compose up -d。
如果排查后发现是 Docker 容器自身冲突,比如之前有个失败的容器还占着端口,直接docker rm -f 容器名清掉再重启。
4.4 容器升级与版本回滚:操作顺序很关键
Docker 部署 GitLab 的一大优势是升级简单,但简单不代表可以乱来。我的标准流程是:
备份当前数据:
docker exec -t gitlab gitlab-backup create docker exec -t gitlab gitlab-ctl backup-etc拉取新版本镜像:
docker pull gitlab/gitlab-ce:16.11.1-ce.0更新 compose 文件里的镜像版本号,然后重新创建容器:
docker compose up -d容器启动后,GitLab 会自动执行升级迁移。这个阶段可能会出现多次自动重启,需要耐心等待。查看日志确认 reconfigure 完成后,再验证页面和仓库功能。
回滚的操作逻辑相反:把 compose 文件里镜像版本改回旧的,重新docker compose up -d。但由于数据库在升级过程中可能已经做了结构迁移,回滚到旧版本时如果数据库不兼容,容器会起不来。这也就是为什么我反复强调:升级前一定要完整备份,出了问题才能有退路。
经验:GitLab 官方跨大版本升级有一定限制,比如 16.x 升 17.x 前最好先升到 16.x 的最新 patch 版本。用 Docker 镜像时注意不要一次跳太多版本,跨大版本升级失败率远高于小版本升级。
5. 常见问题与优化建议速查
5.1 常见问题排查表
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 首次访问 502 | 初始化还没完成 | 等待几分钟,docker logs -f gitlab观察 |
| 页面能开但 push 失败 | SSH 端口未配置 | 设置gitlab_shell_ssh_port并 reconfigure |
| 容器反复重启 | 内存不足 / shm 太小 | 检查free -h,加大--shm-size |
| 克隆地址显示容器内地址 | external_url 未配置 | 修改gitlab.rb里的 external_url |
| 登录提示密码错误 | 初始密码过期 | 用 Rails Runner 重置密码 |
| 磁盘空间越来越小 | 日志和备份累积 | 配置日志轮转和备份保留时间 |
| 升级后页面异常 | 版本跨度过大 | 查日志,必要时恢复备份 |
5.2 定时备份与日志轮转:维护的正确姿势
GitLab 本身没有自动定时备份能力,需要用系统 crontab 配合实现。宿主机上添加定时任务,每天凌晨备份:
# 每天凌晨 2 点执行备份 0 2 * * * docker exec -t gitlab gitlab-backup create >/dev/null 2>&1 # 每周一凌晨 3 点备份配置文件 0 3 * * 1 docker exec -t gitlab gitlab-ctl backup-etc >/dev/null 2>&1除此之外,GitLab 还有备份保留机制,在gitlab.rb里设置:
gitlab_rails['backup_keep_time'] = 604800单位是秒,604800 就是 7 天。超过 7 天的备份文件会被后续备份任务自动清理,避免磁盘被备份撑爆。
日志轮转用 Docker 本身的 log rotation 更方便。在docker-compose.yml里加:
logging: driver: json-file options: max-size: "50m" max-file: "3"这样单个日志文件超过 50MB 会自动轮转,最多保留 3 份,日志目录不会无限膨胀。我之前的服务器就是因为没配这个,看了下/var/log/gitlab,半年时间吃掉了将近 10GB 空间。
5.3 内存优化和运行时参数调整
GitLab CE 在 4GB 内存的机器上运行,建议关闭一些用不到的功能:
在gitlab.rb中,可以减小 puma 进程数:
puma['worker_processes'] = 2如果团队用不到监控功能,可以关掉 Prometheus:
prometheus_monitoring['enable'] = false关掉 Prometheus 能省不少内存,我实测大概能省出 300MB 左右。还有 gitlab-exporter 和 node-exporter 这类附属监控进程,在不影响核心功能的前提下也可以关:
gitlab_exporter['enable'] = false改完统一gitlab-ctl reconfigure生效。内存紧张时这些配置能释放不少余量,让 GitLab 在低配服务器上也能跑得相对平稳。
5.4 安全加固:访问控制与防火墙配置
GitLab 部署完成后,安全这块不能裸奔。最基本的几件事:
防火墙只放开需要的端口。如果你是云服务器,在安全组规则里只放行 8088(HTTP)和 2222(GitLab SSH),其他端口不对公网开放。同时开启 fail2ban 对 SSH 和 GitLab 登录尝试做拦截,能省掉很多爆破骚扰。
GitLab 自身也可以配置登录保护。在 Admin Area 里开启 Two-Factor Authentication 强制要求,或者配置 GitLab 自带的可锁账户策略:连续 10 次登录失败锁定 30 分钟。这些在管理面板里点点鼠标就能完成。
如果端口无法改变,建议给 Web 服务配上 HTTPS。用 Docker 部署时可以映射 8443 端口对应容器 443,然后在gitlab.rb里设置证书路径:
nginx['ssl_certificate'] = "/etc/gitlab/ssl/gitlab.example.local.crt" nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/gitlab.example.local.key"证书文件放到/srv/gitlab/config/ssl/目录下,就会自动出现在容器内的/etc/gitlab/ssl/。
6. 从部署到运维:我的一些经验总结
在实际操作 Docker 部署 GitLab 的过程中,我最大的体会就是:容器化解决了环境问题,但数据备份和版本规划永远不能依赖容器本身。以下几点是我经过几次踩坑后沉淀的运维习惯:
第一,部署的第一天就配置好备份,不要拖到出问题才后悔。最好的方式就是上面说的,backups 目录单独挂出来,配合 crontab 每天定时备份。我给自己定的规矩是:备份文件至少保留 7 天且必须在宿主机上能直接看到。第二,升级前先读官方升级路径文档,不要盲目把镜像标签改成 latest。确定了目标版本和升级路径再动手,升级完成后马上去验证项目和用户数据,而不是直接认为没问题。第三,external_url和 SSH 端口这类配置,在部署时就要想清楚,不要等团队用起来才发现克隆地址全是错的,那时候改配置还得让所有人重新同步。
最后再分享一个实用小技巧:每次改完gitlab.rb执行gitlab-ctl reconfigure后,顺手执行一下gitlab-ctl status,确认所有服务都是run状态再结束。这几十秒的检查,能帮你避免很多"改完配置才发现某个服务挂了"的尴尬局面。