☰
Docker环境下GitLab与PostgreSQL数据库分离部署实战
2026/10/19 6:17:25 网站建设 项目流程

1. 整体设计思路:为什么非要把数据库拆出来

先说个真实场景。某公司内部用GitLab管代码,一开始图省事,直接用官方镜像一把梭:

docker run -d \ --name gitlab \ --publish 8443:443 \ --publish 8080:80 \ --publish 8022:22 \ --restart always \ gitlab/gitlab-ce:latest

数据全靠容器内部存储,所有代码仓库、数据库、配置文件全和容器生命周期绑在一起。跑了大半年,痛点全来了:

一是升级迁移极其痛苦。GitLab小版本升级频繁,每次都要先备份再升级再验证,一旦容器异常,GitLab自带Omnibus的PostgreSQL容易数据不一致,恢复起来费时费力。

二是容器重建成本高。有次服务器重启,Docker daemon异常退出,GitLab容器起不来,吓得赶紧查数据卷,好在当时已经把关键目录挂载出来了,不然代码仓库全没。

三是资源利用率差。GitLab的PostgreSQL和Redis默认与Web进程挤在一个容器里,内存分配互相干扰——GitLab本身很占内存(尤其是跑CI Runner的时候),数据库进程一起来,内存立刻告急。

在实践过程中,分离部署的价值就体现出来了:

  • 数据库独立运维:PostgreSQL单独成容器,可以独立升级、独立备份、独立监控,不会因为GitLab应用层故障导致数据库连带挂掉。
  • 应用层随意折腾:GitLab容器可以随时销毁重建,只要数据库和仓库数据卷还在,拉起来就是原样。
  • 资源精细控制:数据库容器单独设置内存上限,避免和应用进程抢占资源。
  • 架构清晰:后续想迁移到云数据库(比如托管版的PostgreSQL),应用层几乎不用改,只需要改连接串和初始化逻辑。

这里要明确一个概念:“数据分离”不等于“数据库分离”。前者是把GitLab的数据目录(/var/opt/gitlab)挂载到宿主机磁盘,后者是把GitLab应用和PostgreSQL数据库运行在两个独立容器中,甚至数据库放在独立主机上。本文讲的是后者——两个容器,各司其职。

2. 环境准备与架构规划

2.1 硬件资源评估:别等OOM了才后悔

GitLab对内存的胃口在代码托管工具里是出了名的。官方要求是4GB内存起步,但实际用下来,跑一个团队级别的GitLab,8GB内存才比较舒服。数据库分离部署之后,资源分配可以更精细一些,建议按下面的底子配置:

资源最低配置推荐配置说明
内存4GB8GB+PostgreSQL 2GB,GitLab 4GB,剩余给系统
CPU2核4核+代码仓库多的时候,GitLab后台任务很吃CPU
磁盘50GB200GB+ SSD仓库数据增长很快,慢盘会拖累Git操作体验
SWAP2GB4GB内存峰值时兜底,但别指望靠它扛大流量

之前踩过坑,在一台2核4G的云服务器上跑分离部署,Unicorn和Sidekiq两个进程内存一上来,PostgreSQL直接被挤到SWAP,结果就是Git操作超时、页面打开极慢。后来加了内存才解决,实在没法加的话,至少要限制GitLab的并发进程数。

2.2 Docker环境准备

以Ubuntu 22.04为例,Docker安装其实没什么新鲜的,但有几个细节值得留意:

# 更新系统包索引 sudo apt-get update # 安装依赖 sudo apt-get install -y \ ca-certificates \ curl \ gnupg \ lsb-release # 添加Docker官方GPG key sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg # 添加软件源 echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 安装Docker引擎 sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

注意:不要图省事省略GPG key导入步骤,否则apt update会报KeyError,很多人在这卡了半天。

装完Docker后,再把当前用户加进docker组,避免每次sudo:

sudo usermod -aG docker $USER newgrp docker

顺便把Docker的日志轮转配置好,否则GitLab这种日志大户几个月就能写满磁盘:

{ "log-driver": "json-file", "log-opts": { "max-size": "20m", "max-file": "5" } }

写入/etc/docker/daemon.json,重启Docker生效:

sudo systemctl restart docker

2.3 目录规划:把关键数据全部落在宿主机

分离部署之前,先规划好目录结构。GitLab有三个关键数据目录:

  • /etc/gitlab:配置文件
  • /var/opt/gitlab:核心数据,含仓库、数据库数据
  • /var/log/gitlab:运行日志

虽然数据库单独拆出去了,但GitLab自身的仓库数据、备份文件还是要落盘。我的目录规划是:

/data/ ├── gitlab/ │ ├── config/ # GitLab配置文件(挂载到 /etc/gitlab) │ ├── data/ # GitLab数据目录(挂载到 /var/opt/gitlab) │ ├── logs/ # GitLab日志目录(挂载到 /var/log/gitlab) │ └── backup/ # 备份文件目录(挂载到 /var/opt/gitlab/backups) └── postgresql/ └── data/ # PostgreSQL数据目录(挂载到 /var/lib/postgresql/data)

为什么要单独建一个backup目录?因为挂载到/var/opt/gitlab/backups之后,备份操作可以直接写到宿主机,不需要进容器找文件,后续做异地备份、定时任务都会方便很多。

目录权限也要注意,PostgreSQL的data目录需要给容器内的postgres用户写入权限。我一般直接用chown -R 999:999(PostgreSQL官方镜像默认UID是999),或者更稳妥的方式是启动容器后用--user参数指定,但新手建议直接用默认UID,省得出权限问题。

2.4 网络规划:固定IP还是端口映射?

GitLab需要暴露三个端口:

  • 443:HTTPS访问
  • 80:HTTP访问
  • 22:SSH Git操作

端口映射方式有两种思路。一种是传统做法,直接把宿主机的端口映射到容器:

-p 8443:443 -p 8080:80 -p 8022:22

但这样有个问题:用户访问GitLab时要带端口号,git clone时SSH也要写非标准端口,体验不太好。

另一种做法是用Docker NAT网络+宿主机端口转发。我实际操作中比较推荐创建一个自定义网络,然后用固定IP的方式让容器之间直接通信:

docker network create gitlab-net --subnet=172.20.0.0/16

PostgreSQL容器分配固定IP,GitLab容器通过这个IP连接数据库。这样好处是:

  1. 容器重建后IP不会变(必须显式指定)
  2. 容器间通信不走端口映射,少一层NAT损耗
  3. 后续加Redis容器、Runner容器,都可以在同一个网络里自由通信

3. 数据库分离部署完整实操

3.1 版本选型:别用最新的,用最稳的

先明确版本搭配,这里直接说结论:

GitLab版本和PostgreSQL版本是绑定的。每个GitLab版本都有对应的PostgreSQL大版本要求。比如GitLab 16.x对应PostgreSQL 13/14,GitLab 15.x对应PostgreSQL 12/13,等等。

我去查阅了GitLab官方文档中的版本配套关系,整理成下表(基于常见实践的通用建议,具体以官方文档为准):

GitLab大版本推荐PostgreSQL版本说明
GitLab 16.xPostgreSQL 13 或 14新部署首选14
GitLab 15.xPostgreSQL 12 或 1312还在维护,13比较稳
GitLab 14.xPostgreSQL 12老版本可以用,但不推荐新装

选PostgreSQL版本时不要选最新的(当前最新的大版本),因为GitLab官方没有完整测试过最新PostgreSQL版本,可能存在兼容性问题。选GitLab大版本默认支持的版本就好,这也是官方Omnibus打包所用的版本。

另外,GitLab版本也建议选最新的CE稳定版,不要选RC版本。直接在Docker Hub上拉最新的gitlab/gitlab-ce:latest标签其实并不推荐,最好锁定一个具体版本,比如gitlab/gitlab-ce:16.11.3-ce.0,这样后续升级时可控性更强。

锁定版本的好处是:生产环境别追新,等出了两三个补丁版本再升,能省去很多因上游bug导致的修复时间。

3.2 部署独立PostgreSQL容器

PostgreSQL容器是这次改造的重头戏。我的做法是直接用官方的postgres:13镜像,不装任何额外工具,保持最小化。

创建数据目录并设置权限:

mkdir -p /data/postgresql/data chown -R 999:999 /data/postgresql/data

然后启动PostgreSQL容器:

docker run -d \ --name gitlab-postgresql \ --network gitlab-net \ --ip 172.20.0.10 \ -e POSTGRES_PASSWORD=YourStrongPassword \ -e POSTGRES_DB=gitlabhq_production \ -e POSTGRES_USER=gitlab \ -v /data/postgresql/data:/var/lib/postgresql/data \ --restart always \ --memory 2g \ --cpus 2 \ postgres:13

几个环境变量的作用:

  • POSTGRES_PASSWORD:数据库超级用户密码,这里我们顺手创建了gitlab用户,但实际是默认postgres用户的密码
  • POSTGRES_DB:要创建的默认数据库名。GitLab连接数据库时默认使用的库名是gitlabhq_production
  • POSTGRES_USER:默认创建的超级用户名。但实际部署中,我更建议用POSTGRES_USER=postgres,然后在初始化后单独为GitLab创建专用账号,避免GitLab运行时用超级用户权限

说道这里重点来了——GitLab官方对权限要求比较苛刻,它需要操作数据库的用户具备CREATEDB权限(用于创建测试数据库跑迁移),但不建议给超级用户权限。所以正确的做法是:

  1. 先以postgres超级用户启动容器
  2. 容器启动后进入容器,创建GitLab专用用户和数据库
  3. 启动GitLab时使用这个专用账号

实际操作中,我喜欢在PostgreSQL容器启动后,执行以下初始化脚本(基于我部署时的通用实践):

docker exec -it gitlab-postgresql psql -U postgres -c "CREATE USER gitlab WITH CREATEDB SUPERUSER PASSWORD 'GitLabDBPassword';" docker exec -it gitlab-postgresql psql -U postgres -c "CREATE DATABASE gitlabhq_production OWNER gitlab;"

那SUPERUSER要不要加?在GitLab官方文档中,GitLab要求数据库用户必须具备超级用户权限(否则部分迁移脚本无法执行)。实际测试下来,没有SUPERUSER权限时,GitLab初始化过程中会报Permission denied to create extension之类的错误,很多扩展(如pg_trgm)需要超级用户权限安装。所以这里必须加上SUPERUSER权限,但密码一定要设成强密码,仅限内网访问。

PostgreSQL容器启动时,还要注意时区问题。GitLab默认使用UTC时间,如果希望系统时间和本地时间一致,需要在PostgreSQL容器里设置时区:

-e TZ=Asia/Shanghai

顺手一起加上,省得后面日志时间对不上。

3.3 部署GitLab容器并连接外部数据库

数据库容器跑起来之后,再来部署GitLab容器。这是整个流程中参数最多的环节。

先创建目录:

mkdir -p /data/gitlab/{config,data,logs,backup} chown -R 1000:1000 /data/gitlab

然后启动GitLab容器:

docker run -d \ --name gitlab \ --network gitlab-net \ --ip 172.20.0.11 \ -p 443:443 \ -p 80:80 \ -p 22:22 \ -e GITLAB_OMNIBUS_CONFIG=" external_url 'http://gitlab.example.com'; gitlab_rails['db_adapter'] = 'postgresql'; gitlab_rails['db_encoding'] = 'unicode'; gitlab_rails['db_host'] = '172.20.0.10'; gitlab_rails['db_port'] = '5432'; gitlab_rails['db_username'] = 'gitlab'; gitlab_rails['db_password'] = 'GitLabDBPassword'; gitlab_rails['db_database'] = 'gitlabhq_production'; gitlab_rails['backup_path'] = '/var/opt/gitlab/backups'; postgresql['enable'] = false; redis['enable'] = false; " \ -v /data/gitlab/config:/etc/gitlab \ -v /data/gitlab/data:/var/opt/gitlab \ -v /data/gitlab/logs:/var/log/gitlab \ -v /data/gitlab/backup:/var/opt/gitlab/backups \ --shm-size 256m \ --restart always \ gitlab/gitlab-ce:16.11.3-ce.0

这里有几个参数要重点解释,都是踩过坑才知道关键的:

postgresql['enable'] = false和redis['enable'] = false:这是分离部署的开关。GitLab的Omnibus默认会尝试启动自带的PostgreSQL和Redis,必须把这两个关掉,否则容器启动时会因为5432或6379端口冲突报错。

但注意:GitLab依赖Redis,官方Omnibus默认自带Redis,我们这里关掉Redis之后,GitLab内部是不是就无法工作了?这里要说明一下——GitLab的Redis是支撑Sidekiq队列、缓存和会话的核心组件,不能直接关掉。在分离部署实践中,Redis也需要单独部署或让GitLab继续用自带Redis。那上述配置里我写了redis['enable'] = false,这个前提下必须再准备一个独立Redis容器。为了避免范围太大,我们这里先聚焦“数据库分离”,Redis可以继续用GitLab容器内部自带的(删掉那行redis['enable'] = false,或者改为redis['enable'] = true),只把数据库分离出去。如果你要一并做Redis分离部署,那就再起一个Redis容器,设置redis['enable'] = false并配置gitlab_rails['redis_host']等相关参数。

实际上我之前做的一次完整分离包括Redis,但为了一个主题讲透,本文先把数据库分离做干净。若你的GitLab容器保留了内置Redis,那配置里就不要加redis['enable'] = false,也别加gitlab_rails['redis_host']。方向明确,数据库分离即可。

external_url:这是对外访问地址。如果用IP访问,就写http://你的服务器IP,如果有域名就写域名。这个地址写错会导致GitLab页面上的clone地址错误、OAuth回调失败。

--shm-size 256m:这个参数容易被忽略。GitLab的Nginx和Puma会用到/dev/shm,默认64MB可能不够,大文件提交时会出现奇怪的网络错误。加上这个参数能避免很多诡异的间歇性问题。

端口映射:443、80、22这三个端口正常情况下应该都映射出去。如果宿主机22端口已经被SSH占用了,就需要把宿主机的其他端口映射到容器的22(比如-p 8022:22),同时修改GitLab配置里的gitlab_rails['gitlab_shell_ssh_port']。但在专用于GitLab的机器上,我建议直接占22端口,否则团队成员clone仓库时都要写端口号,很烦。

启动命令执行完后,容器不会立刻可用。第一次启动过程中GitLab会做很多初始化工作:

  • 生成密钥和证书
  • 运行数据库迁移
  • 初始化默认配置

这个过程在性能一般的机器上可能要3到5分钟。可以通过日志观察进度:

docker logs -f gitlab

看到类似下面这样的日志,说明初始化完成,可以访问了:

GitLab configured successfully!

3.4 初始化验证:确认数据库是真的分离了

容器起来后,别急着开心,先做几项验证,确保数据库真的连到了外部PostgreSQL,而不是还在用容器内自带的(虽然理论上关了自带就不会有这个问题,但操作失误时有发生)。

第一步,检查GitLab容器内是否有PostgreSQL进程:

docker exec gitlab ps aux | grep postgres

如果返回空,说明GitLab确实没有启动内置PostgreSQL,符合预期。这里加一句,如果你在容器内看到了postgres相关进程,检查一下是不是监控工具或Sidekiq的子进程,因为正常情况下GitLab容器内部是不应该跑独立的PostgreSQL服务的。

第二步,在PostgreSQL容器里确认GitLab创建了哪些数据库:

docker exec gitlab-postgresql psql -U postgres -c "\l"

正常会看到gitlabhq_production和gitlabhq_test(测试环境数据库)两个库,其中gitlabhq_test是迁移时创建的临时测试库。如果只看到一个,说明GitLab初始化可能还没跑完,等一会儿再看。

第三步,确认GitLab能正常操作数据库。进入PostgreSQL容器,切换到gitlabhq_production库,随便查一张表:

docker exec gitlab-postgresql psql -U gitlab -d gitlabhq_production -c "\dt"

能列出表就说明连接没问题。常见的情况是Connection refused或密码错误,这时候按第五部分的排查思路去看。

第四步,浏览器访问http://服务器IP,设置管理员密码,首次登录后创建项目,上传一个测试文件,用git clone拉取下来,确认读写路径全通。

4. 运维细节:备份、恢复与版本升级

4.1 备份策略:两条腿走路,缺一不可

数据库分离之后,备份策略会变得更清晰,但也要更细心,因为应用和数据库的备份是两个独立动作,时间点要对齐。

GitLab官方提供了一套备份工具,运行在GitLab容器内:

docker exec -t gitlab gitlab-backup create

这个命令会备份以下内容:

  • Git仓库(裸仓库)
  • 数据库快照(通过GitLab的backup工具dump)
  • 上传的附件
  • CI/CD流水线历史数据

备份文件默认生成在/var/opt/gitlab/backups目录下,也就是我们挂载到宿主机/data/gitlab/backup的目录。文件名格式是1692835200_2023_08_24_16.11.3_gitlab_backup.tar。

但这里有个大坑——GitLab的backup工具在执行时会同时备份它自己认识的那个数据库。数据库分离后,backup工具是不是还会尝试连接我们已经单独部署的PostgreSQL?实际上大家不必担心:GitLab backup会以GitLab配置中指定的数据库连接信息(db_host、db_username等)去连接外部PostgreSQL,然后执行数据库转储。只要gitlab_rails['db_*']配置正确,backup工具就能正常从外部数据库备份数据。不会再去连一个“自带数据库”,因为内置数据库已经被禁用了。

所以备份操作在GitLab容器内一条命令搞定,但PostgreSQL容器自身我们还是要做一层物理备份,原因有两个:

  1. GitLab的backup工具备份的是逻辑数据,某些情况下(比如表损坏)导出的也可能是不完整的数据
  2. 数据库容器本身如果损坏,光有逻辑备份还不够,恢复时要先重建数据库环境,再做恢复,耗时更长

所以我的策略是双保险:

  • GitLab层:每天执行gitlab-backup create,生成完整备份tar包
  • PostgreSQL层:每天执行pg_dump或pg_basebackup,生成数据库级备份

PostgreSQL物理备份脚本(写入cron定时任务):

#!/bin/bash # /usr/local/bin/pg_backup.sh DATE=$(date +%Y%m%d_%H%M%S) BACKUP_DIR=/data/backups/postgresql mkdir -p $BACKUP_DIR docker exec gitlab-postgresql pg_dump -U gitlab -d gitlabhq_production -F c -f /tmp/gitlab_${DATE}.dump docker cp gitlab-postgresql:/tmp/gitlab_${DATE}.dump $BACKUP_DIR/ docker exec gitlab-postgresql rm -f /tmp/gitlab_${DATE}.dump # 清理30天以前的备份 find $BACKUP_DIR -name "*.dump" -mtime +30 -delete

关于备份时间点:GitLab backup和pg_dump要错开执行,避免两者同时在数据库上跑大查询相互影响。建议GitLab backup在凌晨2点,pg_dump在凌晨4点。

4.2 恢复实操:从零到一完整还原

恢复操作比备份复杂得多,尤其是数据库分离之后,顺序不对就是各种报错。

灾难场景:GitLab服务器系统盘损坏,需要在新机器上恢复服务。

操作步骤如下:

第一步,部署基础环境。重复第二部分的Docker安装和目录创建。

第二步,启动PostgreSQL容器(不要启动GitLab),用备份数据还原:

docker run -d \ --name gitlab-postgresql \ --network gitlab-net \ --ip 172.20.0.10 \ -e POSTGRES_PASSWORD=YourStrongPassword \ -v /data/postgresql/data:/var/lib/postgresql/data \ --restart always \ postgres:13

如果/data/postgresql/data里已经有完整数据文件(物理备份方式恢复),那么容器起来数据就在。但如果是用pg_dump的逻辑备份恢复,方法如下:

# 先创建数据库和用户 docker exec -it gitlab-postgresql psql -U postgres -c "CREATE USER gitlab WITH CREATEDB SUPERUSER PASSWORD 'GitLabDBPassword';" docker exec -it gitlab-postgresql psql -U postgres -c "CREATE DATABASE gitlabhq_production OWNER gitlab;" # 从备份文件恢复 docker exec -i gitlab-postgresql psql -U gitlab -d gitlabhq_production < /data/backups/postgresql/gitlab_20240101_030000.dump

第三步,启动GitLab容器,但先不要访问。立即执行GitLab备份恢复:

docker exec -it gitlab gitlab-backup restore BACKUP=1692835200_2023_08_24_16.11.3

这里需要说明的是,执行gitlab-backup restore时,GitLab要求服务处于停止状态才能恢复。但我们容器方式部署其实不需要太严格,只要保证没有外部请求在写数据即可。实操中我会先把GitLab容器停一下:

docker stop gitlab docker start gitlab # 让容器内部服务都起不来?不对,这样又会自动全部启动

这里有个细节:docker stop gitlab会停止容器内的所有进程。再docker start gitlab之后,容器内所有Omnibus服务(包括web、sidekiq等)都会自动启动,执行restore时正好冲突。

正确做法是:

docker exec -it gitlab gitlab-ctl stop puma docker exec -it gitlab gitlab-ctl stop sidekiq docker exec -it gitlab gitlab-backup restore BACKUP=1692835200_2023_08_24_16.11.3 docker exec -it gitlab gitlab-ctl start

gitlab-ctl stop puma和stop sidekiq停掉Web服务和后台任务队列,让数据库不再有任何写入。然后恢复备份,最后再启动服务。

恢复完成后,还需要恢复配置文件、密钥等。/etc/gitlab目录里的gitlab-secrets.json尤其重要——如果这个文件丢失,即使数据恢复了,用户密码、2FA验证、CI/CD变量都会失效。所以你必须在平时就把整个/data/gitlab/config目录备份好,最好也放进定时任务里。

4.3 版本升级:小步快跑,禁止跳版本

数据库分离部署之后,GitLab升级容易很多:因为PostgreSQL不用动,只需要升级GitLab容器。

但GitLab升级有个铁律:不能跨大版本跳跃式升级。比如从15.x直接升到16.x是有风险的,官方只保证相邻小版本的兼容性。实际操作中,我建议按以下步骤走:

  1. 备份当前版本(在GitLab容器内执行gitlab-backup create)
  2. 修改镜像标签为下一个版本号,重建容器
  3. 登录页面验证核心功能(登录、创项目、push/pull、跑CI)
  4. 确认没问题后再继续升下一版

比如升级路径:16.11.3→16.11.4→16.12.0→16.12.1,每个小版本间隔观察几天,确认稳定再升。

数据库分离部署对升级的额外好处是:升级失败的恢复成本极低。只需要把旧的GitLab镜像标签换回来,数据库数据完全不受影响,因为升级前后用的是同一个PostgreSQL。而以前一体化部署时,升级会连数据库结构一起改,回滚时要连数据库一起恢复,风险大增。

5. 常见问题与排查技巧实录

5.1 数据库连接失败:Connection refused

现象:GitLab容器启动后,日志疯狂报Failed to connect to database: Connection refused。

排查步骤:

  1. 确认PostgreSQL容器是否在运行:docker ps | grep postgresql
  2. 确认两个容器在同一个网络:docker network inspect gitlab-net,看看172.20.0.10和172.20.0.11是否都在
  3. 确认GitLab容器内能ping通数据库容器IP

从实际经验来看,这个错误90%的情况是网络配置不一致导致的:要么PostgreSQL容器没有指定固定IP,导致重启后IP变了但GitLab配置里还写的是旧IP;要么两个容器不在同一个自定义网络里。

所以强烈建议:每个容器启动前,先规划好固定IP,写进启动命令里。我一般把网络规划写成一个笔记,贴在服务器终端附近,或者写进Makefile,防止时间久了忘记。

5.2 密码问题:认证失败

现象:GitLab日志报FATAL: password authentication failed for user "gitlab"。

排查步骤:

  1. 用同样的账号密码手动连一次数据库,确认密码是否正确:
docker exec -it gitlab-postgresql psql -U gitlab -d gitlabhq_production -h 127.0.0.1
  1. 确认GitLab配置里的密码没有特殊字符被转义错误。比如密码里如果有$、'、\,在GITLAB_OMNIBUS_CONFIG里转义不当就会被截断或解析错误

这里给个建议:数据库密码要么用纯字母数字,要么用环境变量文件传参。我踩过一次坑,密码里带了个#,YAML解析直接把后面的内容当成注释了,排查了整整一个下午。后来学乖了,数据库密码一律用a-zA-Z0-9,复杂度靠长度保证,比如G1tLab_Db_P@ss这种级别就够用。

5.3 内存虚高:GItLab容器老占4GB+内存

现象:刚部署完,docker stats一看,gitlab容器内存占用4GB+,怀疑是不是分离了个寂寞。

原因分析:GitLab的内存大头主要有:

  • Puma(Ruby Web服务器)— 默认会fork多个worker

  • Sidekiq(后台任务队列)— 常驻内存

  • Prometheus(监控系统)— 系统监控组件

分离部署数据库之后,省掉的是PostgreSQL的内存(约500MB-1GB),但GitLab应用层本身的内存消耗还是很可观的。

优化手段:

在GITLAB_OMNIBUS_CONFIG里加上:

puma['worker_processes'] = 2; puma['min_threads'] = 4; puma['max_threads'] = 8; sidekiq['max_concurrency'] = 5; prometheus_monitoring['enable'] = false;

如果不做监控,直接把Prometheus关掉能省200MB内存。Puma的worker数建议等于CPU核心数,线程数看并发量,团队小的话4-8个线程完全够。

另外,PostgreSQL容器也建议加--memory 2g --memory-swap 2g限制,避免它抢占过多宿主内存。

5.4 备份文件太多,磁盘告急

现象:df -h一看磁盘使用率飙升,发现/data/gitlab/backup目录堆满了备份tar包。

原因:GitLab默认不做历史备份清理,每天一个tar包,每个几百MB到几GB,一个月就是几十GB。

解决方案:写清理脚本,或者直接在GitLab里配置自动清理。

我习惯用系统cron来做:

0 2 * * * docker exec -t gitlab gitlab-backup create >> /var/log/gitlab_backup.log 2>&1 30 3 * * * find /data/gitlab/backup -name "*.tar" -mtime +7 -delete

保留7天基本上是均衡方案:既能应对“昨天改坏了代码今天想恢复”,又不会囤太多历史备份文件。如果想保留月度备份,可以在每周日额外保留一份,放到另一个目录。

5.5 Docker重启后容器未自动启动

现象:服务器重启后,GitLab和PostgreSQL容器都没起来。

原因:大概率是启动命令里没写--restart always,或者Docker服务本身没设置开机自启。

解决方案:

  • 所有容器必须加上--restart always
  • 确认Docker服务开机自启:sudo systemctl enable docker

还要注意:PostgreSQL容器必须比GitLab容器先启动。虽然GitLab有自动重连机制,启动时连不上数据库会等一会儿重试,但最好还是通过docker的depends_on(如果用了docker-compose)或让PostgreSQL先启动来避免没必要的等待。

我之前没有用--restart always参数,结果一次机房断电后所有服务全部手动重启,白白浪费了一个上午。后来把启动命令全部改成带--restart always的版本,运维轻松很多。

5.6 问题排查速查表

问题现象可能原因快速排查命令
页面打不开Nginx未启动 / 端口被占用docker logs gitlab | grep nginx
Clone报错SSH端口映射错误ssh -p 22 git@你的IP测试连通
登录后500错误数据库连接异常 / secrets文件丢失docker logs gitlab | grep ERROR
磁盘暴涨日志未轮转 / 备份未清理du -sh /data/gitlab/logs /data/gitlab/backup
CI Runner注册失败令牌错误 / 网络不通docker exec gitlab gitlab-rails runner "puts Gitlab::CurrentSettings.current_application_settings.runners_registration_token"

6. 升级到docker-compose管理:换个姿势更省心

如果你觉得上面一长串docker run命令太啰嗦,强烈建议把分离部署迁到docker-compose管理。配置文件即代码,启动一次搞定,不会忘参数。而且升级时只需要改镜像标签,比docker run命令重建容器更清晰。

下面是一个实践过的compose配置,可以直接参考(这个配置同时分离了PostgreSQL,Redis还是继续用GitLab内置的):

version: '3.8' services: postgresql: image: postgres:13 container_name: gitlab-postgresql restart: always networks: gitlab-net: ipv4_address: 172.20.0.10 environment: POSTGRES_PASSWORD: YourStrongPassword POSTGRES_DB: gitlabhq_production POSTGRES_USER: gitlab TZ: Asia/Shanghai volumes: - /data/postgresql/data:/var/lib/postgresql/data mem_limit: 2g cpus: 2 gitlab: image: gitlab/gitlab-ce:16.11.3-ce.0 container_name: gitlab restart: always depends_on: - postgresql networks: gitlab-net: ipv4_address: 172.20.0.11 ports: - "443:443" - "80:80" - "22:22" environment: GITLAB_OMNIBUS_CONFIG: | external_url 'http://gitlab.example.com' gitlab_rails['db_adapter'] = 'postgresql' gitlab_rails['db_encoding'] = 'unicode' gitlab_rails['db_host'] = '172.20.0.10' gitlab_rails['db_port'] = '5432' gitlab_rails['db_username'] = 'gitlab' gitlab_rails['db_password'] = 'GitLabDBPassword' gitlab_rails['db_database'] = 'gitlabhq_production' gitlab_rails['backup_path'] = '/var/opt/gitlab/backups' postgresql['enable'] = false puma['worker_processes'] = 2 puma['min_threads'] = 4 puma['max_threads'] = 8 volumes: - /data/gitlab/config:/etc/gitlab - /data/gitlab/data:/var/opt/gitlab - /data/gitlab/logs:/var/log/gitlab - /data/gitlab/backup:/var/opt/gitlab/backups shm_size: '256m' mem_limit: 4g networks: gitlab-net: driver: bridge ipam: config: - subnet: 172.20.0.0/16

使用方式:

docker-compose up -d

这个配置我把内存限制加上了,后端数据库2GB,GitLab 4GB,总共6GB,2GB留给系统。如果机器只有8GB内存,刚好够用。

补充一点:mem_limit是docker-compose v2/v3的写法,旧版是mem_limit: 4g也可以,不用纠结太多,能用就行。

7. 我对分离部署的几点实操心得

数据库分离这套方案改造完成也稳定运行一段时间了,总结几个我踩过坑之后才明白的道理:

第一,分离不是目的,可维护才是目的。如果你只有一个人用GitLab,那完全没必要做数据库分离,一体化部署反而更省心。但如果是团队级使用、有明确的备份恢复要求、或者打算后续迁移到云数据库,那分离部署就值得投入。

第二,PostgreSQL容器版本一旦定型,尽量别动。GitLab升级只需要拉新镜像,数据库版本升级相对繁琐(要先备份、再升PG、再验证兼容性),所以刚开始选PG版本时,可以稍微选老一档的稳定版本,这样GitLab升级到更高级版本时,PG版本还能在兼容范围内。

第三,固定IP方案比服务发现更可靠。虽然docker-compose里有服务名可以直接当hostname用,但我们在宿主机上还可能跑监控脚本、备份脚本、其他工具,这些脚本需要稳定访问数据库,固定IP比服务名更方便。这也是为什么我在本文中一路坚持用IP而不是容器名称。

第四,无论怎么折腾,定时备份任务一定要落地。部署形态可以随便换,但“每天有人看到备份成功的日志”才是运维的地基。我把备份脚本写进crontab之后,还加了一个健康检查:如果备份文件连续两天没有更新,钉钉机器人推送告警。这样即使备份命令失败了,也能第一时间发现。

这套架构后续想再加东西也方便:Prometheus监控加一个容器,Runner再加一个容器,都在同一个gitlab-net网络里互通,互不干扰。对我的团队来说,这套办法解决了之前GitLab数据库和应用互相拖累的问题,而且迁移成本极低——只要数据目录和配置文件还在,随时能把服务拉起来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询