Docker容器中的GitLab与宿主机共享 SSH 22端口
在利用 Docker 部署 GitLab 时,最棘手的问题之一往往是 SSH 服务的配置。
通常有两种解决方案,但它们都有明显的缺陷:
- 修改 GitLab 容器的 SSH 端口(例如映射到
2222):这会导致克隆/推送地址变成ssh://git@hostname:2222/group/project.git,不仅地址不美观,而且对习惯了标准 SSH 地址的用户不够友好。 - 将宿主机 SSH 端口改为其他端口,把宿主机 22 端口独占给 GitLab 容器:这虽然让 GitLab SSH 地址保持了标准格式,却给系统管理员远程管理宿主机带来了麻烦(需要指定非默认端口)。
本文将介绍一种优雅且安全的架构方案:共享宿主机 SSH 服务并直通 Docker 容器内的 GitLab。
这意味着,你可以保持宿主机 SSH 服务正常运行在 22 端口,同时对于 git 用户的连接,系统会自动动态重定向到 Docker 容器内,无需占用额外的宿主机端口,用户拉取和推送代码的地址依然是标准的 git@hostname:group/project.git。
1. 核心架构与工作原理
本方案的核心思想是:“宿主机认证并路由,容器内部响应与执行”。
当用户通过 SSH 访问宿主机时,整个交互流程如下:
关键步骤解析:
- 动态公钥查询:宿主机并不在本地维护
git用户的authorized_keys文件。而是通过 SSH 的AuthorizedKeysCommand选项,在用户尝试登录时,实时调用脚本去 GitLab 容器内查询用户的公钥。 - 强制命令执行(Force Command):GitLab 容器返回的公钥包含限制前缀
command="/opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell key-XXX"。当客户端匹配该公钥时,宿主机的 SSH 只允许执行此命令,从而限制了用户的 shell 行为。 - 指令无缝桥接:宿主机上部署了一个同名包装脚本,它接收到 GitLab 的 key ID 后,通过带有环境变量保留的
sudo运行docker exec进入容器,将执行流无缝移交给容器内的实际gitlab-shell引擎。
2. 核心脚本与配置文件详解(路径、权限与内容)
为了实现无缝路由,我们需要在宿主机上部署 3 个脚本、1 个 sudoer 配置以及 /etc/ssh/sshd_config 配置。请在对应目标路径下创建以下文件并配置相应权限。
1) gitlab-authorized-keys-check — 宿主机公钥查询桥接器
- 目标路径:
/usr/local/sbin/gitlab-authorized-keys-check - 权限配置命令:
chmod 755 /usr/local/sbin/gitlab-authorized-keys-check chown root:root /usr/local/sbin/gitlab-authorized-keys-check - 作用:当有人尝试连接时,宿主机的 SSHD 服务会以
root权限执行该脚本。脚本通过调用docker exec将查询请求转发给容器内的 GitLab 自带检查工具。
文件内容:
#!/bin/bash
set -euo pipefail
CONTAINER="gitlab"
exec /usr/bin/docker exec -i \
--user git \
-e HOME=/var/opt/gitlab \
-e USER=git \
-e LOGNAME=git \
"$CONTAINER" \
/opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell-authorized-keys-check "$@"
2) gitlab-shell — 宿主机 Shell 包装器
- 目标路径:
/opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell - 权限配置命令:
mkdir -p /opt/gitlab/embedded/service/gitlab-shell/bin/ chmod 755 /opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell chown root:root /opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell - 作用:由于 GitLab 返回的公钥指令包含了容器内的执行路径
/opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell,我们必须在宿主机上伪造一个同名代理脚本,负责将执行请求通过sudo交接给真正的直通脚本。
文件内容:
#!/bin/bash
exec /usr/bin/sudo /usr/local/sbin/gitlab-shell-docker-exec "$@"
3) gitlab-shell-docker-exec — 安全过滤与容器直通执行器
- 目标路径:
/usr/local/sbin/gitlab-shell-docker-exec - 权限配置命令:
chmod 755 /usr/local/sbin/gitlab-shell-docker-exec chown root:root /usr/local/sbin/gitlab-shell-docker-exec - 作用:接收来自包装器的
key-XXX参数,对参数格式进行严格校验,防止任意命令注入宿主机,校验通过后调用docker exec携带环境变量直通运行容器内真正的gitlab-shell。
文件内容:
#!/bin/bash
set -euo pipefail
CONTAINER="gitlab"
if [ "$#" -lt 1 ]; then
echo "Missing GitLab key id" >&2
exit 1
fi
for arg in "$@"; do
case "$arg" in
key-*|username-*)
;;
*)
echo "Invalid GitLab shell argument: $arg" >&2
exit 1
;;
esac
done
exec /usr/bin/docker exec -i -u git \
-e SSH_ORIGINAL_COMMAND="${SSH_ORIGINAL_COMMAND:-}" \
-e SSH_CONNECTION="${SSH_CONNECTION:-}" \
-e SSH_CLIENT="${SSH_CLIENT:-}" \
-e GIT_PROTOCOL="${GIT_PROTOCOL:-}" \
-e HOME=/var/opt/gitlab \
"$CONTAINER" \
/opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell "$@"
4) gitlab-shell-docker — Sudo 提权与环境变量保留配置
- 目标路径:
/etc/sudoers.d/gitlab-shell-docker - 权限配置命令:
chmod 440 /etc/sudoers.d/gitlab-shell-docker chown root:root /etc/sudoers.d/gitlab-shell-docker - 作用:允许宿主机
git用户免密以root身份运行直通脚本,并在sudo切换时强制保留SSH_ORIGINAL_COMMAND、SSH_CONNECTION、SSH_CLIENT以及GIT_PROTOCOL环境变量。
文件内容:
Defaults:git !requiretty
Defaults:git env_keep += "SSH_ORIGINAL_COMMAND SSH_CONNECTION SSH_CLIENT GIT_PROTOCOL"
git ALL=(root) NOPASSWD: /usr/local/sbin/gitlab-shell-docker-exec *
5) sshd_config 追加配置 — SSHD 服务过滤与路由配置
- 目标路径:
/etc/ssh/sshd_config(追加于文件尾部) - 权限配置命令:系统默认(通常为
chmod 600,chown root:root) - 作用:针对
git用户进行匹配限制,不读取宿主机本地授权公钥文件,完全使用gitlab-authorized-keys-check脚本获取临时公钥,且彻底禁用 TTY 及端口转发。
追加配置:
AcceptEnv GIT_PROTOCOL
Match User git
AuthorizedKeysFile none
AuthorizedKeysCommand /usr/local/sbin/gitlab-authorized-keys-check git %u %k
AuthorizedKeysCommandUser root
PasswordAuthentication no
PubkeyAuthentication yes
PermitTTY no
AllowTcpForwarding no
X11Forwarding no
AllowAgentForwarding no
3. 安装与配置步骤
通过以下精简步骤即可完成全部配置:
第一步:在宿主机创建 git 用户
该用户专用于处理宿主机接收到的 SSH 代码拉取与推送连接。
useradd git
第二步:部署脚本与配置文件
参考本文第二章节的路径、所有权及权限配置命令,在宿主机对应位置直接创建以下 4 个文件并写入对应源码,完成权限配置:
- 宿主机公钥查询桥接器:
/usr/local/sbin/gitlab-authorized-keys-check - 宿主机 Shell 包装器:
/opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell - 安全过滤与容器直通执行器:
/usr/local/sbin/gitlab-shell-docker-exec - Sudo 提权配置文件:
/etc/sudoers.d/gitlab-shell-docker
第三步:配置宿主机 SSH 并重启服务
- 打开宿主机的
/etc/ssh/sshd_config文件,将第二章第 5 小节的Match User git配置块追加到文件末尾。 - 重启宿主机 SSH 服务使配置生效:
systemctl restart sshd
4. 环境验证(部署测试容器)
为了验证该环境,我们需要在 Docker 中启动 GitLab 容器。此处提供一个测试容器的启动脚本。
- 目标路径:
/opt/gitlab/run-gitlab.sh(或者/var/gitlab/run-gitlab.sh) - 权限配置命令:
mkdir -p /opt/gitlab # (或 mkdir -p /var/gitlab) chmod 755 /opt/gitlab/run-gitlab.sh chown root:root /opt/gitlab/run-gitlab.sh - 特别注意:脚本中的
GITLAB_HOST=192.168.2.220仅为示例,在实际配置中,请务必修改为您自己宿主机的实际 IP 地址,否则外部访问会无法正确寻址。
测试环境部署脚本:
#!/bin/bash
script_dir=$(cd $(dirname $0);pwd)
# 定义宿主机上保存 GitLab 数据的根目录(默认为脚本所在目录)
GITLAB_HOME=$script_dir
# 定义部署的 GitLab 版本
GITLAB_VERSION=19.1.0
# 特别注意:请将此处的 IP 修改为您自己宿主机的实际 IP 地址
GITLAB_HOST=192.168.2.220
mkdir -p $GITLAB_HOME
docker run --detach \
--hostname gitlab.example.com \
--env GITLAB_OMNIBUS_CONFIG="external_url 'http://$GITLAB_HOST:8929'" \
--publish 8929:8929 \
--name gitlab \
--restart always \
--volume $GITLAB_HOME/config:/etc/gitlab \
--volume $GITLAB_HOME/logs:/var/log/gitlab \
--volume $GITLAB_HOME/data:/var/opt/gitlab \
--shm-size 256m \
gitlab/gitlab-ce:${GITLAB_VERSION}-ce.0
运行该脚本后,GitLab CE 将开始在后台运行。容器正常启动且初始化完毕后,您即可在 GitLab 网页端添加个人公钥,并在客户端通过传统的 git clone git@<您的宿主机IP>:<group>/<project>.git 命令进行测试。
5. 方案的优势与总结
- 零端口污染:无需在宿主机上映射额外的 SSH 端口(如 2222),也无需修改宿主机自身的 SSH 服务端口。
- 标准 URL:用户克隆或推送代码时,使用最符合直觉的
git@<host-ip>:<group>/<project>.git地址。 - 极佳的安全隔离:
- 宿主机
git用户被限制为不能申请 TTY、不能进行端口转发。 gitlab-shell-docker-exec进行了严格的参数前缀校验,杜绝了参数注入风险。- 所有核心 Git 操作和文件读写完全在 Docker 容器内部进行,即使发生安全漏洞,也无法轻易穿透到宿主机环境。
- 宿主机
通过该方案,您可以完美调和“Docker 容器隔离性”与“宿主机端口一致性”的矛盾,为团队提供优雅、高效的私有化 Git 托管服务。

浙公网安备 33010602011771号