Git 底层原理与 GitLab 平台扩展实践

Git 底层原理与 GitLab 平台扩展实践 1. 核心底层原理 1.1 快照存储模型 两种存储模型 Git 在逻辑上记录项目快照:每次提交引用一棵代表项目状态的目录树,未变更的文件内容可以复用既有对象。传统差异模型则用基准内容与后续差异描述版本。 图是逻辑模型示意,不能据此断言 SVN 创建分支

Git 底层原理与 GitLab 平台扩展实践

1. 核心底层原理

1.1 快照存储模型

两种存储模型

Git 在逻辑上记录项目快照:每次提交引用一棵代表项目状态的目录树,未变更的文件内容可以复用既有对象。传统差异模型则用基准内容与后续差异描述版本。

差异存储与 Git 快照的概念对照

图是逻辑模型示意,不能据此断言 SVN 创建分支必定复制全量文件、切换必须重放全部补丁。SVN 的仓库内复制也可以很便宜;Git 的 packfile 在物理存储时同样可以使用增量压缩。

SHA-1 与对象标识

课件仓库采用 SHA-1 对象格式,对象 ID 为 40 位十六进制摘要。Git 也支持 SHA-256 对象格式,不能把所有仓库都限定为 SHA-1。

哈希算法将任意长度输入映射为固定长度摘要,具有以下用途和限制:

  • 输入变化通常会使摘要明显变化,用于定位对象和检查完整性。
  • 摘要不能直接还原原始输入,但它不是加密算法,也不等于内容保密。
  • 哈希存在碰撞可能,SHA-1 已存在实用碰撞攻击;不能称为数学上的全局唯一,更不能把哈希等同于防篡改签名。
  • Git 对象 ID 的输入包含对象类型、字节长度、空字节和内容,不是直接对裸文件执行 sha1sum 得到的值。

Blob 的摘要输入可以写成:

blob <内容字节数>\0<文件内容>

内容相同、对象格式相同的 Blob 可以复用,与文件名无关。文件名和树中的模式信息由 Tree 管理。

实操:修改内容后 Blob 改变

以下命令在已有课程实验仓库中执行,../git/ 是原实验相对路径:

cd ../git/
echo test > test.txt
git add test.txt
git ls-files --stage

原实验输出:

100644 cfc65046606c87a8cdfab760c2fce827d99f25bf 0   cache.txt
100644 0835e4f9714005ed591f68d306eea0d6d2ae8fd7 0   first.txt
100644 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 0   test.txt

修改并重新暂存:

echo test1 > test.txt
git add test.txt
git ls-files --stage
100644 cfc65046606c87a8cdfab760c2fce827d99f25bf 0   cache.txt
100644 0835e4f9714005ed591f68d306eea0d6d2ae8fd7 0   first.txt
100644 a5bce3fd2565d8f458555a0c6f42d0504a848bd5 0   test.txt

echo 写入的结尾换行也是内容的一部分。git add 已将新内容写成对象并更新索引,此时不必已经执行提交;git ls-files --stage 展示的是索引内容。

查看修改前后的对象:

git cat-file -p 9daeafb9864cf43055ae93beb0afd6c7d144bfa4
git cat-file -p a5bce3fd2565d8f458555a0c6f42d0504a848bd5

分别输出:

test
test1

结论:Blob 按内容寻址;相同内容的不同文件可以共享 Blob;修改内容并暂存后,索引中的 Blob 引用改变。Git 判断工作区是否变化还涉及索引缓存和文件元数据,并非每次操作都逐个重新哈希所有文件。

快照模型对分支、切换、合并和磁盘的影响

操作 原理与适用边界
创建分支 创建指向提交的引用,不复制项目全部内容,开销很小但不等于严格零成本。
切换分支 根据目标提交的树更新索引和工作区,通常无需依次重放所有提交;耗时仍受文件数量、差异大小、磁盘和过滤器等影响。
合并 通常寻找共同祖先,比较祖先与双方的变化,必要时处理冲突;不是只比较两个分支最新 Tree 就能完成所有合并。快进合并则主要移动引用。
对象复用 未变化的 Blob 和子树可被多次引用;packfile 还能压缩和以差异形式存放对象,逻辑快照与物理压缩并不矛盾。

1.2 四类核心对象

常规仓库的对象库位于 .git/objects,既可能是松散对象,也可能在其 pack/ 下。共享对象库、alternates 和其他仓库布局会改变实际位置。

1.2.1 Blob:文件内容

Blob 记录原始字节,不记录文件名、目录名或 Tree 中的模式。它既能存文本,也能存二进制内容。

git cat-file -p a5bce3fd2565d8f458555a0c6f42d0504a848bd5
git cat-file -t a5bce3fd2565d8f458555a0c6f42d0504a848bd5

依次输出:

test1
blob

1.2.2 Tree:目录层级和文件映射

Tree 条目保存模式、名称和对象 ID。git cat-file -p 展示的对象类型由模式解释而来;原始 Tree 二进制条目并不是逐字保存显示表格。

  • 普通文件指向 Blob。
  • 子目录指向另一棵 Tree。
  • 符号链接也以特定模式指向保存链接目标的 Blob。
  • 子模块使用 gitlink 模式引用提交。

每个 Commit 引用一个顶层 Tree,表示该提交的项目根目录快照。

查看原实验最新提交:

git log --oneline -1
6742f5a (HEAD -> master) add multi line
git cat-file -p 6742f5a
tree ef3692208c7c9b9e516258895f3f42b4686da7c4
parent d3ae33b7d73f92b1aa48e064493c012765b0f034
author sswang <sswang@magedu.com> 1782800118 +0800
committer sswang <sswang@magedu.com> 1782800118 +0800

add multi line

解析这棵树:

git cat-file -p ef3692208c7c9b9e516258895f3f42b4686da7c4
100644 blob cfc65046606c87a8cdfab760c2fce827d99f25bf    cache.txt
100644 blob 0835e4f9714005ed591f68d306eea0d6d2ae8fd7    first.txt

这里没有 test.txt,因为前面的 git add test.txt 只更新索引,并未修改已有提交。

100644 表示普通、非可执行文件的 Git 模式,不能理解成“文本文件固定权限”。普通可执行文件通常为 100755,Git 不保留完整 POSIX 权限、所有者和 ACL。

Commit(提交和元数据)
└── Tree(项目根目录快照)
    ├── Blob(cache.txt 的字节内容)
    └── Blob(first.txt 的字节内容)

文件名存在 Tree 中,Blob 只保存内容;同一个 Blob 可以被多棵树、多个名称引用。

1.2.3 Commit:提交元数据和父关系

Commit 包括:

  1. 顶层 Tree ID。
  2. 零个或多个父提交 ID:根提交没有父提交,普通提交通常一个,合并提交可以有多个。
  3. 作者姓名、邮箱和时间。
  4. 提交者姓名、邮箱和时间。
  5. 提交说明,以及签名等可选头部。

上面的 git cat-file -p 6742f5a 输出同时展示了 Commit 结构。历史依靠父引用形成有向无环图 DAG,不能概括为只支持一个父节点的单向链表。

1.2.4 Tag:轻量标签与附注标签

类型 底层含义
轻量标签 标签引用直接指向目标对象,不额外创建 Tag 对象。
附注标签 标签引用指向 Tag 对象;对象记录目标 ID、目标类型、标签名、创建者、时间和说明,可带签名。

标签通常用于标记发布提交,如 v1.0,但底层也能标记其他对象;标签并非技术上绝对不可修改,发布后保持不变属于团队约定及保护策略。

git tag -a v1.0 -m "版本1.0发布" 6742f5a
git cat-file -p v1.0

原实验输出:

object 6742f5a8394ff7a3711812c93a1c7611926ef0d3
type commit
tag v1.0
tagger sswang <sswang@magedu.com> 1782801760 +0800

版本1.0发布

1.3 对象 ID、HEAD 和分支引用

1.3.1 长短哈希

SHA-1 ID 使用 0–9、a–f 共 40 位十六进制字符。短 ID 必须能在当前对象库中唯一解析,常见显示长度为 7 位或更多,并不保证任意仓库固定 7~8 位足够。SHA-256 仓库的完整 ID 为 64 位十六进制。

对象类型、字节长度、空字节与内容共同参与摘要计算。重新计算摘要可发现对象损坏,但可信发布还需要合适的签名、来源和访问控制。

1.3.2 HEAD 的两种状态

在常规非裸仓库中:

  • 分支状态:.git/HEAD 通常保存 ref: refs/heads/master,它是 Git 符号引用,不等同于操作系统符号链接。
  • 分离 HEAD:HEAD 直接指向提交 ID,新的提交暂时不由普通分支名承接。

查看原实验:

git log --oneline
cat .git/HEAD
6742f5a (HEAD -> master, tag: v1.0) add multi line
d3ae33b 第二次提交
24f7f42 change first
ref: refs/heads/master

切到指定提交:

git checkout 24f7f42

原实验关键输出:

A       test.txt
注意:正在切换到 '24f7f42'。
您正处于分离头指针状态。
HEAD 目前位于 24f7f42 change first

Git 的完整提示说明:可以查看、修改和提交;切回分支后,这些新提交如果没有引用保留,后续可能被清理。需要保留时创建分支,例如:

git switch -c experiment

也可以通过 git switch - 返回前一位置;advice.detachedHead=false 只关闭建议提示,不改变分离 HEAD 的行为。

原实验继续检查 HEAD:

cat .git/HEAD
24f7f42f6520f51e604a93557e69581531ce2609

返回原有分支:

git checkout master
cat .git/HEAD
A       test.txt
切换到分支 'master'
ref: refs/heads/master

A test.txt 表示该实验的暂存新增文件被保留到切换后的状态,不代表所有未提交修改都能跨分支携带;发生覆盖冲突时,Git 会阻止切换。

分支状态下 git commit、git reset 主要更新当前分支引用,HEAD 仍符号引用该分支;分离状态下则直接更新 HEAD 的提交 ID。共享历史不要随意用 reset 改写。

1.3.3 分支引用存储

files 引用后端中,松散本地分支引用通常放在 .git/refs/heads/,文件内容是分支顶端提交 ID。引用也可以被打包到 packed-refs;使用 reftable 或 worktree 时布局还会不同。

引用空间 用途
refs/heads/ 本地分支。
refs/remotes/ 本地保存的远程跟踪引用,不是实时读取远端分支。
refs/tags/ 标签引用。

原实验在松散引用布局中执行:

cat .git/refs/heads/master
git branch dev
ls .git/refs/heads/
6742f5a8394ff7a3711812c93a1c7611926ef0d3
dev  master

跨不同引用存储布局查询时,优先使用 Git 命令:

git symbolic-ref HEAD
git show-ref --heads
git rev-parse master

2. GitLab 环境进阶

2.1 部署组件与配置

2.1.1 组件职责

不同版本、安装方式和可选功能会改变组件组合。下表区分核心服务、可选集成和旧组件,不能理解成每个安装包都会启用全部服务。

组件 职责与说明
NGINX Web 入口、TLS 终止、静态资源和反向代理;也可以使用外部入口。
GitLab Shell 处理 SSH Git 请求、权限检查并与 Gitaly 协作。授权密钥管理方式依部署配置,不一定靠固定公钥文件。
GitLab Workhorse 智能反向代理,处理上传、下载及部分 Git HTTP 请求,分担 Rails 压力。
Unicorn 历史版本中的应用服务器;现代 Linux package 通常使用 Puma。
Gitaly Git 仓库访问 RPC 服务,供应用和相关组件执行仓库操作。
Puma / GitLab Rails 托管 Rails 应用,处理页面、API、权限及业务请求。
PostgreSQL 保存用户、项目、权限、Issue、MR 等关系数据。
Redis 缓存、会话及 Sidekiq 队列等,实际用途由实例配置决定。
Sidekiq 执行异步任务,如通知、统计及后台处理。
GitLab Exporter 暴露部分 GitLab 指标。
Node Exporter 暴露主机 CPU、内存、磁盘和网络等指标。
Prometheus、Alertmanager 等 可选自监控组件;Grafana、Sentry、Jaeger 不能一概视为所有现代安装包内置并默认开启。
入站邮件 可通过受支持的邮件接收配置将回复同步为评论等;Linux package 的常见接收方式为 IMAP/Mailroom,不能简单称为“入站 SMTP”。
出站 SMTP 发送账号、MR 和其他通知邮件。
LDAP 可选目录服务身份认证集成,不等同于自动实现全部单点登录协议。
对象存储,如 MinIO 用于受支持的附件、产物、LFS 等对象存储场景;通常需要单独配置,不能认定内置并自动承载全部备份。
Container Registry 可选容器镜像仓库,与项目权限和命名空间集成。
GitLab Runner 单独安装、注册的 CI/CD 作业执行程序,可位于独立主机或集群,并非安装 GitLab 服务就自动可用。

2.1.2 安装形式

Omnibus 指官方一体化打包方案,现代文档常称 GitLab Linux package。它打包了大量依赖并统一管理配置,仍有操作系统、网络、存储和其他环境要求,也不包含所有外部集成。

方式 说明 适用场景
DEB / RPM Linux package 标准化打包、统一配置和服务管理。 常规自托管安装。
GitLab Docker 镜像 镜像内使用 Linux package 体系,必须规划持久化、端口和升级。 已有容器运维能力的环境或实验。
源码安装 自行管理依赖和服务,没有完全相同的一体化 gitlab-ctl 管理体验。 深度定制或特定部署需求。

2.1.3 配置文件与默认值

查看显式配置:

grep -Ev '^[[:space:]]*(#|$)' /etc/gitlab/gitlab.rb

原课件仅显式设置:

external_url 'http://gitlab.example.com'

原来的 grep -Ev '#|^$' 会连含行内注释的有效配置一并排除;上面的写法仅排除整行注释及空行,仍只是便于阅读,不能代替 Ruby 配置解析。

查看配置样例中带 enable = true 的行:

grep "enable'] = true" /etc/gitlab/gitlab.rb

课件中的样例包含以下键,前置 # 原样保留:

# gitlab_rails['enable'] = true
# gitlab_rails['smtp_enable'] = true
# gitlab_rails['databases']['ci']['enable'] = true
# registry['enable'] = true
# gitlab_workhorse['enable'] = true
# puma['enable'] = true
# sidekiq['enable'] = true
# postgresql['enable'] = true
# redis['enable'] = true
# redis_master_role['enable'] = true
# redis_replica_role['enable'] = true
# nginx['enable'] = true
# logrotate['enable'] = true
# manage_accounts['enable'] = true
# pages_nginx['enable'] = true
# gitlab_kas['enable'] = true
# monitoring_role['enable'] = true
# prometheus['enable'] = true
# alertmanager['enable'] = true
# node_exporter['enable'] = true
# redis_exporter['enable'] = true
# postgres_exporter['enable'] = true
# gitlab_exporter['enable'] = true
# prometheus_monitoring['enable'] = true
# gitaly['enable'] = true
# redis_sentinel_role['enable'] = true
# sentinel['enable'] = true
# oak['components']['openbao']['enable'] = true

注释掉的示例不是当前生效配置,也不能证明功能默认开启。 未显式设置时使用该版本、部署角色及相关配置推导出的默认值。需要开关某项功能时,应按对应版本说明设置;不要把整段样例直接取消注释。

初始管理员密码可以在首次初始化前通过受支持的配置或环境变量提供:

gitlab_rails['initial_root_password'] = "ReplaceWithUniqueStrongPassword!"

容器等安装流程也支持 GITLAB_ROOT_PASSWORD 等入口,具体按安装方式核对。该选项不用于重置已存在账号;未指定时安装流程可能生成初始密码,不能说“新版必须手工指定才能登录”。密码需要满足实际版本与策略,示例密码不要复用于生产。

QQ 邮箱 SMTP 示例(465 使用隐式 TLS):

gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.qq.com"
gitlab_rails['smtp_port'] = 465
gitlab_rails['smtp_user_name'] = "1000000@qq.com"
gitlab_rails['smtp_password'] = "REPLACE_WITH_SMTP_AUTHORIZATION_CODE"
gitlab_rails['smtp_domain'] = "qq.com"
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = false
gitlab_rails['smtp_tls'] = true
gitlab_rails['gitlab_email_from'] = "1000000@qq.com"

原例同时开启隐式 TLS 与 STARTTLS,已改为 465 的隐式 TLS 组合。587 等提交端口通常使用 STARTTLS,应按邮件服务商要求重新选择,不能只换端口。邮箱授权码与登录密码不同。

2.1.4 端口解读

以下数字来自原实验,不是固定对外端口清单。应结合 ss 输出、生成配置及当前版本确定监听用途;指标端口不等于服务业务协议端口。

原例端口 正确理解
9236 常见为 Gitaly 的 Prometheus 指标端口;Gitaly RPC 常使用 Unix socket 或显式配置的 TCP 地址。不能把 9236 当作 Git 仓库 RPC 的通用入口。
9229 常见为 Workhorse 的指标监听;HTTP 上游通常使用 socket 或另设监听地址。
9122 常见为 GitLab SSHD 的指标端口,不能据此认定外部 SSH 流量自动转发到 9122。
80 external_url 使用 HTTP 且未改端口时的常见 NGINX Web 入口。HTTPS 通常为 443。
8092 常见为 Sidekiq 健康检查服务端口,不能概括成任务队列通信入口;Sidekiq 队列依赖 Redis。
8082 常见为 Sidekiq 指标端口。
8080 可能是配置中的 Puma HTTP 监听;实际也可能主要走 Unix socket,不能假定所有安装都相同。
8060 常见为 NGINX status 监听,不应默认解释成 Pages 对外入口;Pages 地址和端口依实际配置。
2222 原实验自定义 SSH 对外端口。需要操作系统 SSHD、GitLab SSHD 或容器映射真实监听;填写展示端口不会自动建立转发,普通 NGINX HTTP 代理也不会自动代理 SSH。

2.2 管理命令

下面区分 Shell、Ruby 控制台和 psql 元命令,不能把混合示例整体粘贴到一个终端解释器。

2.2.1 统一服务、配置和日志管理

gitlab-ctl
gitlab-ctl check-config
gitlab-ctl show-config
gitlab-ctl reconfigure
gitlab-ctl start
gitlab-ctl stop
gitlab-ctl restart
gitlab-ctl status
gitlab-ctl service-list
gitlab-ctl tail
gitlab-ctl tail nginx
gitlab-ctl stop nginx
gitlab-ctl start nginx
gitlab-ctl restart nginx
命令 用途与限制
gitlab-ctl 列出本机版本支持的子命令。
check-config 检查版本支持的配置问题,尤其弃用配置;不是通用的 Ruby 语法校验器,也不保证配置能运行。
show-config 展示配置渲染结果,可能包含敏感信息;不能仅凭展示成功断言运行中进程已应用。
reconfigure 读取 gitlab.rb,生成配置并收敛所需资源,按需要重载或重启服务。
start / stop / restart 管理全部或指定组件进程;restart 不代替重新生成配置。
status / service-list 检查服务状态和受管服务名。
tail [服务] 跟踪全部或指定服务的日志。

reconfigure 与 restart 不等价:修改配置一般先检查对应版本要求,再执行 reconfigure。其收敛过程中是否重载或重启取决于资源状态、配置变化及版本实现,不能保证“配置没改就绝不重启任何服务”。

排查时先看 status 与对应 tail,再结合 check-config 和配置修改决定是否需要 reconfigure;不要把 reconfigure 当作无副作用的第一步。管理员终端通常需要 root 或 sudo 权限。

Rails 控制台和数据库入口

Shell 中启动:

gitlab-rails console

Ruby 控制台内重置指定账号密码:

user = User.find_by!(username: 'root')
user.password = 'ReplaceWithUniqueStrongPassword!'
user.password_confirmation = 'ReplaceWithUniqueStrongPassword!'
user.save!

数据库入口:

gitlab-rails dbconsole
gitlab-psql

gitlab-psql 封装 Linux package 自带 PostgreSQL 的连接参数,dbconsole 使用 Rails 数据库配置;多数据库或外置数据库时不能保证两者目标和参数完全等价。正确命令是 gitlab-psql,不是 gitlab-pgsql。

备份与恢复

课件使用的 Rake 接口:

gitlab-rake gitlab:backup:create
gitlab-rake gitlab:backup:restore BACKUP=备份标识

现代 Linux package 通常推荐封装命令:

gitlab-backup create
gitlab-backup restore BACKUP=备份标识

备份标识采用实际备份文件去掉 _gitlab_backup.tar 后的部分,不一定只有时间戳。应用数据备份不等于整机完整备份:/etc/gitlab/gitlab.rb、gitlab-secrets.json、TLS 资料及外部对象存储等必须按官方备份范围另行处理。恢复需要匹配版本及版本类型,并按恢复文档处理写入服务和数据覆盖。

邮件测试、初始密码、版本和日志

启动控制台后执行 Ruby 邮件测试:

gitlab-rails console
Notify.test_email('test@example.com', 'GitLab测试标题', '测试内容').deliver_now

该调用会真实发送邮件,只应在计划测试时运行。

cat /etc/gitlab/initial_root_password
gitlab-rake gitlab:env:info
cat /opt/gitlab/version-manifest.txt

初始密码文件只在对应初始化方式中存在,通常按安装流程在后续 reconfigure 时清理;文件不存在不表示没有管理员账号。原例 gitlab-ctl --version 不作为跨版本可靠的 GitLab 应用版本查询,改用环境信息或安装版本清单。

日志轮转由受管的 logrotate 服务及其配置负责。原例 gitlab-ctl logrotate 不是可一概依赖的标准子命令;应通过当前版本帮助、logrotate 配置和运行日志确认轮转方式,不能把轮转等同于立即清空日志。

2.2.2 命令演示

列出受管服务:

gitlab-ctl service-list

进入 PostgreSQL:

gitlab-psql

psql 内操作:

help
\l
\db
\q
  • help:帮助。
  • \l:列出数据库;原课件写成 \db,已修正。
  • \db:列出表空间。
  • \q:退出。

原实验退出时出现:

could not save history to file "/root/.psql_history": 权限不够

这是客户端无法写入历史文件,不代表数据库退出失败。原因通常是进程以其他系统用户运行,而历史路径仍落在 /root;Linux package 常使用 gitlab-psql 等专用账号,不能仅凭提示断言是 git 用户。应核对实际进程身份、环境和 PSQL_HISTORY,不要为了消除提示放宽 /root 权限。

2.3 Group 与 Project

2.3.1 组织关系

Group(组织/项目组)
├── ProjectA
├── ProjectB
└── 子 Group
    └── ProjectC

Group 是组织项目和成员的命名空间;Project 是代码仓库及相关协作功能的载体。组可嵌套,但有平台限制和权限约束,不应称为无限层级。

Group 提供:

  1. 批量管理成员与角色,权限可向子组和项目继承。
  2. 汇总查看有权限访问的 Issue、MR、看板及流水线等。
  3. 组级 CI/CD 变量、Runner 等资源;共享模板也可借助受控仓库和 include 复用,具体功能受版本及许可证影响。
  4. 统一路径,如 http://gitlab.example.com/group/project。

Project:

  1. 保存代码、分支、Wiki、Issue、CI 配置等。
  2. 直接属于一个个人或组命名空间;不能同时归属两个父组,但可按权限策略共享给其他组。
  3. 有独立的地址、成员设置及受保护分支策略。

权限继承:组权限向下继承,不向上反推。直接项目成员身份可以提高权限,但不能用项目中更低的角色覆盖掉其继承的较高组权限;实际有效权限还受自定义角色、共享及保护规则影响。

2.3.2 两种组织规范

按业务线:

mall
├── mall-web
├── mall-api
└── mall-admin

商城团队加入 mall 后,可以统一分配这组项目的角色。

按团队:

backend-team
├── user-service
├── order-service
└── pay-service

团队实践:

  • 正式业务通常放在组织 Group 中,便于交接和权限治理,避免依赖个人命名空间。
  • 嵌套层级保持清楚,例如优先控制在三层以内;这是组织建议,不是平台固定上限。
  • 可见性有私有、内部、公开等类型,具体可用类型取决于部署;项目可见性受父组及实例限制,不能简单说新项目必定继承完全相同的可见性。
  • 可由平台管理员按命名规范创建正式组,再指定业务 Owner;也可通过受控的组创建权限授权负责人,不必日常共用 root 账号。
  • 为重要组保留可交接的负责人和管理员接管流程。业务负责人离职不会自动删除 GitLab 上的组或代码,但可能造成管理和权限交接问题。

Owner 与 Maintainer 不同:Owner 管理组成员和高权限组设置,删除组还受实例、保护和延迟删除等策略影响;Maintainer 常用于项目日常管理,不能因此假定可管理一切组级设置。平台管理员的兜底能力也要结合实例管理员模式等配置理解。

2.4 密码遗失处理

2.4.1 场景

  • 普通用户可以在登录页面通过邮件重置,前提是邮箱、出站邮件及账号认证方式支持。
  • 管理员无法通过邮件恢复时,可以在有服务器管理权限的环境通过 Rake 或 Rails 控制台处理。
  • 初始管理员通常用户名为 root,但可改名;不能把“root 永远是 ID 1”作为所有实例的可靠定位规则。必须先确认目标身份。

2.4.2 Rake 交互重置

不预先指定用户名:

gitlab-rake 'gitlab:password:reset'
Enter username: root
Enter password:
Confirm password:
Password successfully updated for user with username root.

指定用户名,密码仍交互输入:

gitlab-rake 'gitlab:password:reset[root]'
Enter password:
Confirm password:
Password successfully updated for user with username root.

启动可能需要较长时间。完成后使用浏览器验证目标账号,并留意外部认证、锁定状态和双因素认证等因素;重置密码不等于绕过所有其他认证要求。

2.4.3 Rails 控制台

gitlab-rails console -e production

原课件环境输出,保留作为历史实验记录,不代表当前安装版本:

Ruby:          ruby 3.3.10 (2025-10-23 revision 343ea05002) [x86_64-linux]
GitLab:        18.11.2 (7bf35534e4b) FOSS
GitLab Shell:  14.49.0
PostgreSQL:    17.8
[ booted in 37.93s ]
Loading production environment (Rails 7.2.3)
gitlab(prod)>

按用户名查找;已改名时替换为确认过的用户名:

user = User.find_by!(username: 'root')
user.id
user.username

课件中该用户实际为 #<User id:1 @root>,因此本实例也可通过以下方式定位,但 ID 必须已经核实:

user = User.find(1)

设置新密码并保存:

user.password = 'ReplaceWithUniqueStrongPassword!'
user.password_confirmation = 'ReplaceWithUniqueStrongPassword!'
user.save!

预期保存成功返回 true,策略不满足时 save! 会报告校验错误。

原课件还演示可选属性:

user.password_automatically_set = false
user.save!

它表示密码是否由系统自动设置,不能一概当成关闭所有“首次登录改密”策略的通用开关;仅在对应版本和流程确有需要时设置。若在上面保存密码后才补设此属性,必须再次保存;也可以将属性赋值移到前面的 save! 之前。

退出控制台:

quit

2.5 GitLab CI/CD

2.5.1 基本流程

管理员一次性准备:

  1. 安装独立的 GitLab Runner,选择合适执行器。
  2. 使用对应版本的 Runner 创建及认证流程关联实例、组或项目。
  3. 设置标签,如 shell-runner,并检查在线状态、可用范围、是否允许无标签任务及保护限制。

开发者在仓库根目录编写 .gitlab-ci.yml,定义阶段、作业、脚本和规则并纳入版本控制。根目录是默认位置,GitLab 也支持配置自定义 CI 配置路径。

推送功能分支的命令形式:

git push origin feature/example

满足配置时:

  1. GitLab 读取配置及其 include,按 workflow: rules 等决定是否创建流水线。
  2. 按作业规则选择本次要运行的作业。
  3. 匹配可用 Runner;Runner 获取并执行作业,不能将整个流程理解为 GitLab 无条件主动下发给任何在线 Runner。
  4. Runner 按配置准备执行环境、获取源码并运行脚本,日志回传页面。
  5. 后续阶段通常等待前面阶段完成;同阶段作业可并行,needs 等配置还可改变阶段等待关系。

脚本的非零状态通常导致作业失败,但 allow_failure、when、重试及脚本如何处理退出码会影响最后状态;不能概括为任意一条内部命令非零就必然使整个流水线失败。

在【构建 → 流水线】查看结果:绿色通常代表成功,红色通常代表失败,也可能出现等待、跳过、手动作业、取消等状态。

2.5.2 MR 与 CI 的关系

MR 为合并前评审提供代码差异及流水线结果。开启相应合并检查后,可以要求流水线成功再允许合并,同时保留人工评审。

创建 MR 不会在所有默认配置下无条件自动生成 MR 流水线。 配置需要通过 rules 或对应支持方式匹配 merge_request_event,并允许该流水线来源。未配置 MR 支持的普通分支流水线与真正的 MR 流水线必须区分。

典型过程:

  1. 开发者推送功能分支,可能触发分支流水线。
  2. 创建 MR;若配置允许,触发 MR 流水线。
  3. 向 MR 源分支继续推送时,按规则更新并运行相应流水线。
  4. CI 通过且评审完成后,执行合并。
  5. 目标分支发生更新时,按规则决定是否运行主干流水线,例如主干测试、发布或部署作业。

常规 MR 流水线基于源分支内容运行,不能说它自动测试了源分支与目标分支合并后的结果。要验证合并结果,应核对 merged results pipelines 或 merge trains 等功能及版本支持。

合并时是否删除源分支只决定合并后源引用是否清理,并不直接决定目标分支流水线是否运行。合并也不必总产生一个新的 merge commit:快进、squash 等策略会改变提交历史。

2.5.3 只在 MR 阶段执行作业

demo_job:
  stage: test
  tags: [shell-runner]
  script:
    - echo "仅MR阶段执行,合并后master不运行此作业"
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

test 属于默认阶段;如显式定义 stages,要确保其中包含 test。这里的规则限制的是该作业,不保证同一项目里其他作业不会创建或运行主干流水线。

Runner 的标签集合需要包含作业要求的全部标签,而非两个标签列表必须完全相等。没有匹配 Runner、Runner 离线、保护限制或暂停,都可能导致 pending。

2.5.4 避免分支与 MR 重复流水线

同一次源分支更新可能同时满足分支和 MR 流水线条件,但这依赖具体配置,不是所有无规则配置默认都会额外运行一条 MR 流水线。

常见策略:

  • 仅在 MR 运行准入测试。
  • MR 测试通过后,主干更新仅运行必要的主干校验及发布任务。
  • 在流水线层通过 workflow: rules 切换分支流水线和 MR 流水线,避免重复创建。

以下补充配置保留无 MR 时的分支流水线,有 MR 后切换为 MR 流水线:

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push"'
      when: never
    - if: '$CI_COMMIT_BRANCH'

test_job:
  stage: test
  tags: [shell-runner]
  script:
    - echo "运行本次分支或MR测试"
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH'

作业也显式匹配 MR 来源,避免只打开 workflow 后,默认排除 MR 的无规则作业让流水线缺少任务。最后一条分支规则也允许以分支为目标的定时、API、触发器及页面启动,并非只允许 push;如需仅 push,应同时限制 CI_PIPELINE_SOURCE。标签没有 CI_COMMIT_BRANCH,本示例不覆盖标签发布流水线,需要时另加明确规则。workflow 控制流水线创建,作业级 rules 控制作业,二者要配合。

2.5.5 常见触发来源

来源 说明
Git push 分支或标签推送,受规则及跳过策略约束。
MR 创建或更新 必须配置支持 MR 流水线。
页面手动运行 用户通过页面启动,并满足权限与规则。
定时计划 按流水线计划运行,依赖计划状态、拥有者权限及规则。
API / 触发令牌 来源类型依所用接口不同,受授权与规则约束。

GitLab 服务负责编排,Runner 执行用户作业;仅安装 GitLab、没有可用 Runner,不会自动具备运行这些脚本的能力。

2.6 导入项目

2.6.1 打开项目和导入入口

进入左上角导航中的【项目】,查看已有项目,再选择【新建项目】。以下截图使用原课件界面,菜单位置会随版本变化。

项目浏览入口

创建项目时选择导入项目

如果导入页面没有可用来源,需要管理员检查实例设置。

尚未启用导入来源的界面

进入管理员区域的【设置 → 通用 → 可见性和访问控制 / 导入与导出设置】等对应位置,选择需要的导入来源并保存。不是普通用户个人设置就能启用实例导入功能。

管理员配置导入来源

课件勾选了 GitHub 和 Gitea。Gitea 导入器针对 Gitea,不保证兼容 Gitee。 两者是不同产品,Gitee API 不能直接假定为 Gitea API;后续截图中确实出现了兼容性错误。

2.6.2 Gitee 私人令牌

登录 Gitee,在头像菜单进入【设置 → 安全设置 → 私人令牌】,选择【生成新令牌】。

Gitee 私人令牌列表入口

按用途填写描述、有效期,并只勾选导入所需的最小权限;不能笼统地认为所有默认权限都合适。

创建令牌及权限配置

按页面提示完成账号密码验证。

创建令牌前进行身份验证

令牌通常只完整显示一次,创建后复制到安全的凭据存储。原截图保留了课堂演示令牌,文字不再重复其值;截图中出现的令牌应视为已经公开,不可继续作为有效凭据使用。

令牌生成成功及一次性显示

确认并关闭,再查看令牌列表。

创建后的令牌列表

私人令牌授权某个平台的访问,不会让另一个平台自动兼容它的 API。

2.6.3 原课件 Gitea 导入尝试

回到 GitLab 项目导入页面:

启用来源后的导入入口

选择 Gitea,课件在该表单尝试填入 https://gitee.com 及令牌。这里记录的是一次失败尝试,不是推荐的 Gitee 导入方法。

Gitea 导入表单中的地址与凭据

点击【列出您的 Gitea 仓库】,仓库较多时可使用过滤。

筛选待导入的仓库

目的命名空间优先选择团队 Group,例如 devops,再点击【导入】,避免默认放到管理员个人空间。

选择 devops 命名空间

原实验随后出现 API 兼容性错误:

Gitea 导入器连接 Gitee 后出现兼容性错误

如果源平台确为 Gitea,应使用受支持版本的 Gitea 地址和对应令牌;源平台是 Gitee 时,转用下述 Git URL 方式迁移代码。

2.6.4 使用 Repository by URL 导入代码

管理员在导入设置中启用 Repository by URL 并保存。

启用 Repository by URL

再次进入导入项目页面,选择【仓库 URL】。

选择仓库 URL 导入

填写源仓库真实的 Git clone URL、目标项目名称及目标 Group,如 devops。私有仓库按当前界面支持的方式提供只读凭据,并确认 GitLab 服务端能连接源站。

填写源仓库 URL 和目标命名空间

创建项目后等待后台导入完成。原例约等待 5 秒,实际时间取决于仓库大小、网络和后台负载,并非固定时限。

仓库正在导入

导入完成后检查项目页面、分支和标签;核对目标可见性、默认分支及成员权限。

导入成功后的项目页面

Repository by URL 主要迁移 Git 仓库内容,不保证迁移 Gitee 的 Issue、PR、成员、CI 配置的运行环境或其他平台元数据;LFS 和子模块也要按实际源目标支持分别核对。不要把“仓库导入成功”当作整个平台完整迁移。

参考资料

评论