gitlab-ce 的简单使用
gitlab-ce 的简单使用
简单认识 gitlab-ce
以下理解是个人理解,细节的正确性请做测试验证 😃。
请求流程:
# HTTP/HTTPS 请求
客户端 (浏览器/终端)
|
| HTTP/HTTPS 请求 (如克隆仓库、访问网页)
v
外部 Nginx 反向代理
|
| 转发动态请求到 Workhorse
v
GitLab Workhorse (处理静态资源/文件上传)
|
| 1. 静态资源直接响应
| 2. 动态请求转发到 Puma
| ┌───────────────┐
| │ │
v v v
Puma (Rails 应用) Gitaly (Git 操作)
| │ │
| │ 业务逻辑处理 │ 处理 git clone/push 等操作
| │ (用户认证/API) │ (读取/写入仓库数据)
| │ │
v v v
PostgreSQL (存储元数据) 仓库文件系统
(用户/项目配置/权限) (实际代码存储)
|
| 3. 后台任务 (如邮件发送)
v
Sidekiq (异步任务队列)
|
v
Redis (缓存/任务队列存储)
# GitLab Shell
GitLab Shell(SSH) --> Gitaly(权限验证后操作仓库)
|
| 1. 认证用户
| 2. 权限验证
| ┌───────────────┐
| │ │
v v v
SSH 客户端 Gitaly (权限操作)
| │ │
| │ 校验用户权限 │ 执行 git 操作
| │ (公钥/密钥) │ (读取/写入仓库数据)
| │ │
v v v
仓库文件系统 PostgreSQL (存储权限)
(用户/项目权限) (实际代码存储)
# Registry 镜像仓库访问
Registry 容器镜像仓库
|
| 1. 存储镜像元数据
| 2. 镜像访问(如拉取、推送)
| ┌───────────────┐
| │ │
v v v
Nginx 反向代理 PostgreSQL (镜像元数据)
(镜像配置/权限) (镜像文件存储)
GitLab 各组件通过数据交互和功能协作实现整体运行,其核心组件的依赖关系及作用:
- 前端与反向代理层 Nginx(默认启用)
- 作为反向代理,处理外部 HTTP/HTTPS 请求,转发至后端服务(如 GitLab Workhorse、Puma)。
- 负责 SSL 证书管理、请求路由和静态资源(如图片、CSS)的直接响应。
- GitLab Workhorse
- 接收 Nginx 转发的动态请求(如 API、Webhook),处理文件上传、反向代理至 Puma,并管理与 Gitaly 的通信。
- Puma(Ruby 应用服务器)
- 运行 GitLab Rails 应用,处理业务逻辑(如用户认证、项目管理、API 接口)。
- 依赖 Redis 和 PostgreSQL 存储数据,通过 GitLab Workhorse 与 Gitaly 交互。
- GitLab Shell
- 处理 SSH 协议的 Git 操作(如克隆、推送代码),通过配置文件(config.yml)与 GitLab Rails 通信,验证用户权限。
- 数据存储与缓存层
- PostgreSQL:存储 GitLab 的元数据(用户信息、项目配置、权限关系等)。
- Puma 通过 ActiveRecord 访问数据库,部分组件(如 Sidekiq)也依赖其数据。
- Redis:缓存会话数据、计数器、任务队列(Sidekiq),提升访问速度。
- Puma、Sidekiq、GitLab Workhorse 均需连接 Redis。
- PostgreSQL:存储 GitLab 的元数据(用户信息、项目配置、权限关系等)。
- Git 仓库处理核心
- Gitaly(Git 仓库管理服务)负责处理所有 Git 操作(克隆、拉取、推送),优化大仓库性能。
- Puma、GitLab Shell、GitLab Workhorse 通过 UNIX socket 或 TCP 连接 Gitaly,避免直接操作文件系统。
- Gitaly(Git 仓库管理服务)负责处理所有 Git 操作(克隆、拉取、推送),优化大仓库性能。
- 辅助服务与扩展
- Sidekiq(后台任务处理器)异步处理耗时任务(如邮件发送、CI/CD 流水线调度),依赖 Redis 存储任务队列,从 PostgreSQL 获取任务数据。
- Registry(容器镜像仓库)若启用,需独立运行并连接 PostgreSQL 存储镜像元数据,通过 Nginx 暴露访问端口。
Workhorse 和 Puma 是两个核心组件:
- GitLab Workhorse
- 高性能反向代理和请求处理器,用 Go 语言开发。
- 请求过滤与转发:静态资源直接响应(如图片),接收 Nginx 转发的动态请求(如 API、Webhook、文件上传),过滤非法请求后转发给 Puma。
- 文件处理优化:处理大文件上传(如 LFS 对象),支持断点续传,避免 Puma 直接处理导致的性能瓶颈。
- 与 Gitaly 通信:负责 Git 操作(如克隆、拉取)的网络请求,直接对接 Gitaly 服务。
- 安全控制:管理会话 cookie、校验请求头,防止恶意访问。
- 高性能反向代理和请求处理器,用 Go 语言开发。
- Puma
- Ruby 应用服务器,运行 GitLab Rails 应用的容器,用 Ruby 语言开发。
- 业务逻辑处理:执行 Ruby 代码,处理用户认证、项目管理、页面渲染等核心业务逻辑。
- 数据交互:访问 PostgreSQL 数据库和 Redis 缓存,生成响应内容(如 HTML、JSON)。
- 多进程/线程模型:通过配置
worker_processes和threads实现并发处理,提升请求吞吐量。
- Ruby 应用服务器,运行 GitLab Rails 应用的容器,用 Ruby 语言开发。
Puma 作为应用服务器,必须通过与 Workhorse 相同的协议(TCP 或 Unix Socket)响应请求:
- 多节点集群,使用 TCP 通信:
- 客户端 --> Workhorse (TCP port_a)
- Workhorse --> Puma (TCP port_b)
- 配置
gitlab_workhorse['auth_backend'] = "http://puma_ip:puma_port"。
- 配置
- Puma 处理请求并返回响应。
- 单节点部署,优先使用 Unix Socket,避免网络协议栈开销,安全性更好(通过文件权限控制访问):
- 客户端 --> Workhorse (Unix Socket)
- Workhorse --> Puma (同一个 Unix Socket)
- 配置
gitlab_workhorse['auth_socket'] = "workhorse_and_puma_are_using_the_same_unix_socket"。
- 配置
- Puma 处理请求并返回响应。
协议要求一致的原因:
- 底层 API 不兼容,两者接口不同,无法跨协议通信:
- TCP 连接使用
connect(IP, PORT)系统调用。 - Unix Socket 使用
connect(PATH)系统调用。
- TCP 连接使用
- 数据传输方式不同:
- TCP 数据包需经过网络协议栈(IP、TCP 层),包含源/目标 IP 和端口信息。
- Unix Socket 直接在文件系统中传输数据,无需网络协议头。
- 地址解析机制不同:
- TCP 依赖 DNS 或 IP 地址解析。
- Unix Socket 依赖文件系统路径解析。
常见的 5XX 错误通常是由于 Puma 服务未启动或配置错误导致的,需要注意的是,Puma 服务启动得比较慢,尤其是机器资源比较吃紧时,重启 GitLab 服务后通常需要等待数分钟才能正常响应请求,表现是一开始返回 500 错误后面恢复正常。
安装 gitLab-ce
Docker 部署 gitLab-ce:
sudo mkdir -p /data/gitlab/{data,config,logs}
sudo chown -R 998:998 /data/gitlab
sudo chmod -R 755 /data/gitlab
# GitLab 官方镜像默认会以 root 启动,但内部服务会自动切换到 git 用户
# --memory-swap 如果和 --memory 设为 相同值,表示禁用容器使用交换分区(推荐,避免 swap 拖慢性能),如果不设置,默认是 --memory 的 2 倍(比如 -m 2g 会允许 4g 总内存)。
# 上面这部分只是个人认识,具体细节还需要查看官方文档
docker run -d \
--name gitlab-ce \
-e TZ="Asia/Shanghai" \
--restart always \
--memory=3.5g \
--memory-swap 3.5g \
--memory-reservation 2.5g \
--cpus=1 \
-p 8080:8181 \
-p 2222:22 \
-p 8081:5000 \
-v /data/gitlab/data:/var/opt/gitlab \
-v /data/gitlab/config:/etc/gitlab \
-v /data/gitlab/logs:/var/log/gitlab \
gitlab/gitlab-ce:16.10.0-ce.0
# 关闭容器,修改配置 /data/gitlab/config/gitlab.rb
# 以下配置仅供参考,随着 gitlab-ce 版本的更新会存在差异,具体参数根据需求做调整
: <<'NICETOMEETYOU'
# 10.1.0.6 是 gitlab 容器的宿主机 IP
# http://10.1.0.6:8080/ -> 容器内 workhorse 端口 8181 -> 容器内 puma 端口 8080(这里怕混淆的话,调整为其它端口)
external_url 'http://10.1.0.6:8080/'
gitlab_rails['gitlab_ssh_host'] = '10.1.0.6'
gitlab_rails['gitlab_shell_ssh_port'] = 2222
gitlab_workhorse['enable'] = true
gitlab_workhorse['listen_network'] = "tcp"
gitlab_workhorse['listen_addr'] = "0.0.0.0:8181"
gitlab_workhorse['auth_backend'] = "http://localhost:8080"
# 配置 Gitaly 超时时间(单位:秒)
gitlab_rails['gitaly_timeout'] = 60
# 配置 Sidekiq Gitaly 超时时间(单位:秒)
sidekiq['gitaly_timeout'] = 60
# puma 配置
puma['enable'] = true
puma['worker_timeout'] = 60
puma['worker_processes'] = 1
puma['min_threads'] = 1
puma['max_threads'] = 1
puma['listen'] = '127.0.0.1'
puma['port'] = 8080
# 禁用 puma socket 监听
puma['socket'] = ''
puma['somaxconn'] = 10240
puma['per_worker_max_memory_mb'] = 512
nginx['enable'] = false
sidekiq['concurrency'] = 5
# 禁用非核心服务
prometheus['enable'] = false
alertmanager['enable'] = false
gitlab_exporter['enable'] = false
node_exporter['enable'] = false
postgres_exporter['enable'] = false
redis_exporter['enable'] = false
mattermost['enable'] = false
gitlab_pages['enable'] = false
gitlab_rails['service_desk_enabled'] = false
# 禁用 Elasticsearch
gitlab_rails['elasticsearch_enabled'] = false
# registry 配置,暂时忽略
...
NICETOMEETYOU
外部可配置一个 Nginx 反向代理来访问,并且配置 SSL 证书:https://registry.yourdomain.com:8689,配置可参考后面的 Registry 中的 Nginx 反向代理。
Nginx 反向代理使用的 SSL 证书可使用 Certbot 之类的工具来申请、延期证书。
几个简单的问题排查命令:
# 实时查看日志
docker logs -f gitlab-ce
# 查看初始 root 密码
sudo cat /data/gitlab/config/initial_root_password
# Password: JBEdFMJuYoW3VDnDUPR8BNL+5ENyZP2jTsdVWtAzswM=
# 进入容器排查问题
docker exec -it gitlab-ce bash
# 进入容器后
> gitlab-ctl status
# 实时查看 puma 日志
> tail -f /var/log/gitlab/puma/current
# 浏览器端创建/删除分支报 4:Deadline Exceeded
# 查看所有 GitLab 服务状态(重点关注 gitaly/redis/postgresql/puma)
> gitlab-ctl status
# 实时查看 Gitaly 日志(分支操作的底层日志)
> gitlab-ctl tail gitaly | grep -i "deadline\|timeout\|error"
# 实时查看 Puma 日志(前端请求超时)
> gitlab-ctl tail puma | grep -i "deadline\|timeout\|error"
# 查看 Redis 日志(确认是否仍有连接异常)
> gitlab-ctl tail redis | grep -i "EOF\|error"
# 查看 PostgreSQL 日志(比如分支元数据存储超时)
> gitlab-ctl tail postgresql | grep -i "timeout\|lock\|error"
启用 Registry 功能
内部测试时可能会用到 GitLab 内置的 Container Registry 功能。
按照我自己的经验,使用 Registry 通常需要会使用一个外部的 nginx 反向代理,将外部请求转发到 GitLab 内置的 Container Registry 服务,另外由于需要与其他系统集成比如在 K8s、Jenkins 等中使用,通常需要给 Registry 配置一个域名来访问,并且该域名配置使用 SSL 证书。
修改 gitlab.rb:
# 10.1.0.6 是 gitlab 容器的宿主机 IP
# registry_external_url -> 容器内 registry 端口 5000
registry_external_url 'http://10.1.0.6:8081/'
gitlab_rails['registry_enabled'] = true
gitlab_rails['registry_host'] = "10.1.0.6"
registry['enable'] = true
registry['registry_http_addr'] = "0.0.0.0:5000"
registry_nginx['enable'] = false
registry_external_url:对外公开访问的 Registry 地址,这里我后面会另外套一层 Nginx 反向代理(配置 SSL),将外部请求转发到容器内的 registry 服务。- 用户浏览器和 Docker CLI 使用。
gitlab_rails['registry_enabled']:GitLab Rails 应用(即 GitLab 网页界面和 API),控制 GitLab 是否集成 Container Registry 功能。true:启用集成,gitlab 项目页面显示 “Container Registry” 菜单,允许用户通过 GitLab 管理镜像。- 项目地址 --> 部署 --> Container Registry
https://git.yourdomain.com:8688/dev/demo/container_registry- 这里的
https://git.yourdomain.com:8688是 GitLab 的访问地址。
- 这里的
- 项目地址 --> 部署 --> Container Registry
false:禁用集成,gitlab 项目页面隐藏 Registry 菜单,无法通过 GitLab 访问或推送镜像(即使 Registry 服务仍在运行)。
gitlab_rails['registry_host']:与registry_external_url的 IP 保持一致。registry['enable']:GitLab 内置的 Docker Registry 服务(独立于 GitLab Rails 应用),控制是否在当前 GitLab 服务器上运行 内置的 Container Registry 服务。true:启动内置 Registry 服务,监听指定端口(在这里是10.0.0.13:5000),存储镜像文件。false:关闭内置 Registry 服务,适用于使用 外部 Registry(如 Harbor、Docker Hub 或自建 Registry)的场景。
registry['registry_http_addr']:Registry 服务监听地址。registry_nginx['enable']:是否启用 Nginx 反向代理。- 这里设置为
false,因为我使用了外部 nginx 反向代理。
- 这里设置为
配置 nginx:
upstream gitlab-registry {
server 10.1.0.6:8081 fail_timeout=0;
keepalive 64;
}
map $http_upgrade $connection_upgrade_gitlab_ssl {
default upgrade;
'' close;
}
server {
listen 8689 default ssl;
server_name registry.yourdomain.com;
server_tokens off; ## Don't show the nginx version number, a security best practice
ssl_certificate /usr/local/nginx/conf/ssl/registry.yourdomain.com/fullchain1.pem;
ssl_certificate_key /usr/local/nginx/conf/ssl/registry.yourdomain.com/privkey1.pem;
# GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs
ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 5m;
client_max_body_size 0;
client_body_buffer_size 16k;
client_body_timeout 600;
location / {
gzip off;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
proxy_buffering off;
# proxy_set_header Host $http_host;
proxy_set_header Host $host:$server_port; # 传递代理域名和端口
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Ssl on;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade_gitlab_ssl;
proxy_redirect off;
# WebSocket 支持(用于 CI/CD 终端和实时功能)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://gitlab-registry$request_uri;
}
# 静态资源优化
location ~ ^/(assets|system|uploads)/ {
proxy_cache off;
proxy_buffering off;
expires max;
access_log off;
add_header Cache-Control public;
proxy_pass http://gitlab-registry$request_uri;
}
error_page 404 /404.html;
error_page 422 /422.html;
error_page 500 /500.html;
error_page 502 /502.html;
error_page 503 /503.html;
location ~ ^/(404|422|500|502|503)\.html$ {
root /home/git/gitlab/public;
internal;
}
access_log /data/logs/nginx_logs/gitlab_registry_proxy_8689_access.log;
error_log /data/logs/nginx_logs/gitlab_registry_proxy_8689_error.log;
}
登录 Gitlab 对应的项目管理界面,针对当前项目生成新令牌:
- 访问对应的项目地址
http://10.1.0.6:8080/dev/demo.git/https://git.yourdomain.com:8688/dev/demo.git
- 设置 -> 个人访问令牌 -> 添加新令牌
- 令牌名称:gitlab-ci-token,令牌:glpat-fxxxx
- 选择角色:Developer
- 选择范围:
-api
-read_api
-read_repository
-write_repository
-read_registry
-write_registry
简单的测试:
# 登录 GitLab Registry
docker login 10.1.0.6:8081 -u gitlab-ci-token -p glpat-fxxxx
# 推送镜像到 GitLab Registry
docker push 10.1.0.6:8081/dev/demo/hello-k8s:v1
若在 K8s 中连接使用 Registry,则需要使用对应的 Nginx 反向代理地址 registry.yourdomain.com:8689:
# 测试使用 SSL 连接 Registry 服务
openssl s_client -connect registry.yourdomain.com:8689 -servername registry.yourdomain.com
# 简单测试,若返回权限问题说明配置正确,无需关心是否能返回镜像列表
curl -v -k https://registry.yourdomain.com:8689/v2/_catalog
# 登录 GitLab Registry
docker login registry.yourdomain.com:8689 -u gitlab-ci-token -p glpat-fxxxx
# 构建测试镜像
docker build -f Dockerfile.test -t registry.yourdomain.com:8689/dev/demo/hello-k8s:v2 .
# 推送镜像到 GitLab Registry
docker push registry.yourdomain.com:8689/dev/demo/hello-k8s:v2
# 修改为 GitLab Registry 地址生成新的镜像拉取密钥
GITLAB_REGISTRY="registry.yourdomain.com:8689"
GITLAB_USERNAME="gitlab-ci-token"
GITLAB_PAT="glpat-fxxxx"
SECRET_NAME="gitlab-registry-secret"
# 在 K8s 中创建镜像拉取密钥
kubectl create secret docker-registry $SECRET_NAME \
--docker-server=$GITLAB_REGISTRY \
--docker-username=$GITLAB_USERNAME \
--docker-password=$GITLAB_PAT \
--docker-email="xxx@example.com"
# 测试从 GitLab Registry 拉取镜像
tee test-gitlab-registry.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: test-gitlab-registry
spec:
containers:
- name: hello-k8s
image: registry.yourdomain.com:8689/dev/demo/hello-k8s:v2
imagePullSecrets:
- name: gitlab-registry-secret
EOF
kubectl apply -f test-gitlab-registry.yaml
# 查看 Pod 状态
kubectl get pods test-gitlab-registry
# NAME READY STATUS RESTARTS AGE
# test-gitlab-registry 1/1 Running 6 (40s ago) 3m35s
GitLab Registry 宿主机的代理问题导致登录失败的排查
由于 Docker 客户端机器通常需要访问 Dockerhub 官网,针对这个配置了全局代理,这个代理会导致本机 Docker 客户端无法直接连接到 GitLab Registry,以下简单的排查思路可供参考。
使用令牌登录 Registry 报错:
$ docker login registry.yourdomain.com:8689
Login did not succeed, error: Error response from daemon: Get "https://registry.yourdomain.com:8689/v2/": EOF
Username (testuser): testuser
Password:
Error response from daemon: Get "https://registry.yourdomain.com:8689/v2/": EOF
在 Nginx 代理服务器上使用 sudo tcpdump -i any port 8689 -n 以及查看代理 nginx 的访问日志,没有看到任何响应,说明代理 nginx 没有收到请求。
使用 strace 追踪 docker 进程:
$ sudo strace -e trace=openat,read,network -s 1024 -f -p $(pgrep dockerd)
# Docker 客户端向 api.moby.localhost 发送认证请求(POST /v1.49/auth),认证信息包含用户名 testuser 和 GitLab 访问令牌(glpat-*),目标仓库为 registry.yourdomain.com:8689。
[pid 76329] read(21, "POST /v1.49/auth HTTP/1.1\r\nHost: api.moby.localhost\r\nUser-Agent: Docker-Client/28.1.1 (linux)\r\nContent-Length: 103\r\nContent-Type: application/json\r\n\r\n{\"username\":\"testuser\",\"password\":\"glapt-xxxx\",\"serveraddress\":\"registry.yourdomain.com:8689\"}\n", 4096) = 253
# Docker 尝试查找私有仓库的自定义证书(/etc/docker/certs.d/registry.yourdomain.com:8689),返回 ENOENT 错误,说明未找到证书文件。
[pid 76329] openat(AT_FDCWD, "/etc/docker/certs.d/registry.yourdomain.com:8689", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
# 创建 TCP 套接字(socket),准备连接代理服务器。
[pid 76329] socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 25
# 代理服务器地址为 10.0.0.1:1080,EINPROGRESS 表示非阻塞连接正在进行中。
[pid 76329] connect(25, {sa_family=AF_INET, sin_port=htons(1080), sin_addr=inet_addr("10.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress)
[pid 76329] read(21, 0xc000bd8a01, 1) = -1 EAGAIN (Resource temporarily unavailable)
# getsockopt(SO_ERROR, [0]) 返回 0,表示连接成功。
[pid 76329] getsockopt(25, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
# getpeername 确认对端地址为代理服务器 10.0.0.1:1080。
[pid 76329] getpeername(25, {sa_family=AF_INET, sin_port=htons(1080), sin_addr=inet_addr("10.0.0.1")}, [112->16]) = 0
# 本机地址为 10.0.0.11:53482,显示客户端使用的临时端口。
[pid 76329] getsockname(25, {sa_family=AF_INET, sin_port=htons(53482), sin_addr=inet_addr("10.0.0.11")}, [112->16]) = 0
# 禁用 Nagle 算法
[pid 76329] setsockopt(25, SOL_TCP, TCP_NODELAY, [1], 4) = 0
# 启用 keepalive
[pid 76329] setsockopt(25, SOL_SOCKET, SO_KEEPALIVE, [1], 4) = 0
# 30 秒无活动发送探测包
[pid 76329] setsockopt(25, SOL_TCP, TCP_KEEPIDLE, [30], 4) = 0
# 探测包间隔 15 秒
[pid 76329] setsockopt(25, SOL_TCP, TCP_KEEPINTVL, [15], 4) = 0
# 最多发送 9 次探测包
[pid 76329] setsockopt(25, SOL_TCP, TCP_KEEPCNT, [9], 4) = 0
# SOCKS5 响应头,表示认证方法协商(0 表示“无认证”)。
[pid 76329] read(25, "\5\0", 2) = 2
# 客户端选择“用户名/密码认证”(方法 2,但代理返回支持 0)。
[pid 76329] read(25, "\5\0\0\1", 4) = 4
# 认证方法列表长度(0x30 即 48 字节)。
[pid 76329] read(25, "\0\0\0\0\48", 6) = 6
# -1 表示调用失败,EAGAIN 是具体错误原因,意为 “资源暂时不可用”。
# 在非阻塞 I/O场景中(上面网络套接字设置了 SOCK_NONBLOCK 标志),此时文件描述符中没有可读取的数据,系统不会阻塞等待,而是立即返回 EAGAIN,告诉进程 “现在没数据,稍后再试”。
[pid 76329] read(25, 0xc000276d80, 576) = -1 EAGAIN (Resource temporarily unavailable)
# 读取 fd=25 返回值为 0,表示读取到文件结束(EOF)。
# 对于网络套接字,这通常意味着对方关闭了连接(如 TCP 连接正常关闭,此时读取返回 0 表示没有更多数据,且连接已终止),而对于普通文件,0 表示已读到文件末尾。
[pid 76329] read(25, "", 576) = 0
# 另一个 fd 同样表示读取到 EOF
[pid 76329] read(21, "", 4096) = 0
可以看到由于代理 10.0.0.1:1080 的原因导致登录异常。
使用 curl 走 SOCKS5 代理登录 Registry:
$ curl -v --proxy socks5h://10.0.0.1:1080 https://registry.yourdomain.com:8689/v2/
* Trying 10.0.0.1:1080...
* TCP_NODELAY set
* SOCKS5 communication to registry.yourdomain.com:8689
* SOCKS5 connect to registry.yourdomain.com:8689 (remotely resolved)
* SOCKS5 request granted.
* Connected to 10.0.0.1 (10.0.0.1) port 1080 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/certs/ca-certificates.crt
CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to registry.yourdomain.com:8689
* Closing connection 0
curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to registry.yourdomain.com:8689
可以确认是由于 SOCKS5 代理导致登录 Registry 异常。
修改 /etc/docker/daemon.json 将域名 *.yourdomain.com 加到 no-proxy 中,然后重启 docker 服务。
{
"data-root": "/data/docker",
"proxies": {
"http-proxy": "socks5://10.0.0.1:1080",
"https-proxy": "socks5://10.0.0.1:1080",
"no-proxy": "*.yourdomain.com,.example.org,127.0.0.0/8"
},
"dns": ["127.0.0.1", "8.8.8.8", "8.8.4.4"]
}
再次测试:
$ docker login registry.yourdomain.com:8689
Authenticating with existing credentials... [Username: testuser]
i Info → To login with a different account, run 'docker logout' followed by 'docker login'
Login Succeeded
需要注意的是,在很多场景下不能完全把代理关闭,因为很多时候还是需要访问 dockerhub 等官方仓库,关闭代理后使用 strace 追踪进程 dockerd 会发现访问 dockerhub 等官方仓库会异常:
[pid 77960] accept4(4, {sa_family=AF_UNIX}, [112->2], SOCK_CLOEXEC|SOCK_NONBLOCK) = 21
# Docker 客户端通过 /run/docker.sock 与守护进程通信。
[pid 77960] getsockname(21, {sa_family=AF_UNIX, sun_path="/run/docker.sock"}, [112->19]) = 0
[pid 77960] accept4(4, 0xc0004499d4, [112], SOCK_CLOEXEC|SOCK_NONBLOCK) = -1 EAGAIN (Resource temporarily unavailable)
[pid 77960] read(21, 0xc0003aa000, 4096) = -1 EAGAIN (Resource temporarily unavailable)
# 客户端先发送健康检查请求,确认 Docker 守护进程是否可用。
[pid 77960] read(21, "HEAD /_ping HTTP/1.1\r\nHost: api.moby.localhost\r\nUser-Agent: Docker-Client/28.1.1 (linux)\r\n\r\n", 4096) = 92
[pid 77960] read(21, 0xc0003aa000, 4096) = -1 EAGAIN (Resource temporarily unavailable)
# 客户端发送搜索请求到 Docker API,目标为搜索 mysql 镜像。
[pid 77960] read(21, "GET /v1.49/images/search?term=mysql HTTP/1.1\r\nHost: api.moby.localhost\r\nUser-Agent: Docker-Client/28.1.1 (linux)\r\nX-Registry-Auth: e30=\r\n\r\n", 4096) = 139
[pid 77960] openat(AT_FDCWD, "/etc/docker/certs.d/docker.io", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
[pid 77960] socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 25
# 创建文件描述符 25,用于 DNS 查询。
[pid 77960] setsockopt(25, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
# 向本地 DNS 服务器 127.0.0.53(通常是 systemd-resolved)发送查询请求,解析 index.docker.io。
[pid 77960] connect(25, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, 16) = 0
[pid 77960] getsockname(25, {sa_family=AF_INET, sin_port=htons(58696), sin_addr=inet_addr("127.0.0.1")}, [112->16]) = 0
[pid 77960] getpeername(25, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, [112->16]) = 0
[pid 77960] read(25, 0xc000c7a000, 1232) = -1 EAGAIN (Resource temporarily unavailable)
[pid 77960] read(21, 0xc000bea071, 1) = -1 EAGAIN (Resource temporarily unavailable)
[pid 77960] socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 29
[pid 77960] setsockopt(29, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
[pid 77960] connect(29, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, 16) = 0
[pid 77960] getsockname(29, {sa_family=AF_INET, sin_port=htons(20314), sin_addr=inet_addr("127.0.0.1")}, [112->16]) = 0
[pid 77960] getpeername(29, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, [112->16]) = 0
[pid 77960] read(29, 0xc000c7aa00, 1232) = -1 EAGAIN (Resource temporarily unavailable)
[pid 77960] read(25, "_<\201\200\0\1\0\1\0\0\0\1\5index\6docker\2io\0\0\34\0\1\300\f\0\34\0\1\0\0\0\30\0\20*\3(\200\3611\0\203\372\316\260\f\0\0%\336\0\0)\377\326\0\0\0\0\0\0", 1232) = 72
[pid 77960] read(29, "\345\342\201\200\0\1\0\1\0\0\0\1\5index\6docker\2io\0\0\1\0\1\300\f\0\1\0\1\0\0\0?\0\4l\240\246\375\0\0)\377\326\0\0\0\0\0\0", 1232) = 60
[pid 77960] socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 25
[pid 77960] setsockopt(25, SOL_IPV6, IPV6_V6ONLY, [0], 4) = 0
[pid 77960] setsockopt(25, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
# Docker 尝试通过 IPv6 连接 index.docker.io(IP:2a03:2880:f131:83:face:b00c:0:25de),但因网络配置或防火墙限制,IPv6 不可达(ENETUNREACH),随后会回退到 IPv4。
[pid 77960] connect(25, {sa_family=AF_INET6, sin6_port=htons(53), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "2a03:2880:f131:83:face:b00c:0:25de", &sin6_addr), sin6_scope_id=0}, 28) = -1 ENETUNREACH (Network is unreachable)
[pid 77960] socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 25
[pid 77960] setsockopt(25, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
# 进程通过文件描述符 25(UDP 套接字)连接到 IP 为 108.160.166.253、端口 53(DNS 服务默认端口)的服务器,连接成功(返回 0)。
# 这是向 DNS 服务器发送域名解析请求(如解析 index.docker.io),108.160.166.253 是 Cloudflare 的公共 DNS 服务器之一。
[pid 77960] connect(25, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("108.160.166.253")}, 16) = 0
# 获取当前进程(客户端)的套接字信息:使用 IP 172.20.0.11、端口 10106(随机分配的临时端口),操作成功(返回 0)。
[pid 77960] getsockname(25, {sa_family=AF_INET, sin_port=htons(10106), sin_addr=inet_addr("172.20.0.11")}, [112->16]) = 0
# 获取对端(DNS 服务器)的套接字信息:IP 108.160.166.253、端口 53,操作成功(返回 0)。
[pid 77960] getpeername(25, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("108.160.166.253")}, [112->16]) = 0
# 创建一个新的 TCP 套接字(文件描述符 25),准备通过 TCP 连接到目标服务器(此处为 Docker Hub)。
[pid 77960] socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 25
# 通过新创建的 TCP 套接字(描述符 25)连接到 IP 108.160.166.253、端口 443(HTTPS 默认端口),返回 -1 EINPROGRESS。
[pid 77960] connect(25, {sa_family=AF_INET, sin_port=htons(443), sin_addr=inet_addr("108.160.166.253")}, 16) = -1 EINPROGRESS (Operation now in progress)
[pid 77960] socket(AF_INET6, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 29
[pid 77960] setsockopt(29, SOL_IPV6, IPV6_V6ONLY, [0], 4) = 0
[pid 77960] --- SIGURG {si_signo=SIGURG, si_code=SI_TKILL, si_pid=77672, si_uid=0} ---
[pid 77960] connect(29, {sa_family=AF_INET6, sin6_port=htons(443), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "2a03:2880:f131:83:face:b00c:0:25de", &sin6_addr), sin6_scope_id=0}, 28) = -1 ENETUNREACH (Network is unreachable)
[pid 77960] read(21, "", 4096) = 0
# 客户端读取到 EOF,关闭连接。

浙公网安备 33010602011771号