1 这是什么,解决什么问题
先说场景。北汽 N60 F19 项目是 MTK8676 平台的智能座舱工程,涉及三条构建线:
| 构建线 | 平台 | 产物 |
|---|---|---|
| Yocto(SOS + Tbox) | MTK8676 | Kernel module (.ko)、vmlinux、lk.elf、Tbox 驱动 |
| Android U | MTK8676 | system.img、vendor.img、kernel symbols |
| Android B | MTK8676 | libc.so、libnl.so、iotop 等共享库 |
每次 Daily Build 跑完,Jenkins 会把这些产物 rsync 到两台裸 HTTP 文件服务器上,然后飞书机器人把下载链接丢到群里。
这套流程跑了半年,问题也攒了半年:
- 没版本元数据——SOC 包和 OTA 包靠文件名里的时间戳区分,你没法搜"S020.015 的所有 Debug 构建"
- 没权限控制——知道 HTTP 地址就能下载,供应商、内部、测试全混在一起
- 没校验——下载完没人验证文件完整性,损坏了也不知道
- 磁盘规划混乱——同一块
/data盘上混着 Jira NFS 附件、旧发布包、新制品,哪天手滑rm -rf能哭一天
Artifact Keeper 是 Rust 写的开源制品仓库(MIT,866 star,1673 commits),Docker Compose 部署,支持 45+ 包格式。对我这个场景,核心价值是 Generic 仓库——不管你传的是 .zip、.img、.ko 还是 .elf,它都当通用制品存,同时自动记录版本、时间、SHA-256。
对比 JFrog Artifactory($500/月起)和 Sonatype Nexus,Artifact Keeper 完全免费,空闲只占 ~800 MB 内存。
2 环境与拓扑
2.1 服务器
| 项目 | 配置 |
|---|---|
| OS | Ubuntu 24.04 LTS |
| CPU | 8 核 |
| 内存 | 16 GB |
| 系统盘 | 60 GB |
| 数据盘 | 45 TB,挂载 /data |
2.2 网络拓扑
外网 ──→ Nginx (10.177.100.25, 443) ──→ Artifact Keeper (10.177.100.30:30080)
│ │
│ 通配符证书 *.bitech-auto.com │ LDAPS:636 ──→ 域控 DC
│ │
└── Jira Node1/Node2 └── HTTP 代理 150.158.137.210:8964
jira.bitech-auto.com (拉 GitHub / Docker Hub 用)
Nginx(10.177.100.25)已承载 jira.bitech-auto.com,持有 bitech-auto.com 企业通配符证书。新增子域名 artifacts.bitech-auto.com 直接复用同一份证书,不需要额外申请。
2.3 /data 目录隔离(⚠️ 重点)
/data/
├── shared_1/ ← ⛔ Jira NFS 存储(生产环境,存放附件)
├── baic_n60/ ← ⛔ 已有发布包
├── lost+found/ ← 文件系统元数据
└── artifact-keeper/ ← ✅ 新建,Artifact Keeper 全部数据
├── data/ ← 制品存储
├── .env ← 环境变量(LDAP/密码/代理)
└── docker-compose.yml
Artifact Keeper 容器 volume 只挂 /data/artifact-keeper/ 下的子目录,任何情况下都不触碰 shared_1 和 baic_n60。Docker Compose 编排里没有这两个路径的映射,物理隔离。
2.4 端口
| 端口 | 服务 | 绑定 | 用途 |
|---|---|---|---|
| 30080 | AK Web | 127.0.0.1:30080 | 仅 Nginx 反代访问 |
| 443 | Nginx | 10.177.100.25 | 公网 HTTPS |
| 636 | LDAPS | 出站 | 域控认证 |
30080 绑 127.0.0.1 而非 0.0.0.0,外部只能经 Nginx 进来。
3 Docker 部署
3.1 前提:配置代理
10.177.100.30 在大陆,直连 GitHub 和 Docker Hub 慢到超时。用现有的 HTTP 代理(150.158.137.210:8964):
# Git 代理
git config --global http.proxy http://150.158.137.210:8964
git config --global https.proxy http://150.158.137.210:8964
# Docker 代理(systemd drop-in)
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf << 'EOF'
[Service]
Environment="HTTP_PROXY=http://150.158.137.210:8964"
Environment="HTTPS_PROXY=http://150.158.137.210:8964"
Environment="NO_PROXY=localhost,127.0.0.1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
# 验证
sudo docker pull hello-world
NO_PROXY 里排除了所有内网地址,避免访问 Jira、域控、Nginx 时也走代理。
3.2 安装 Docker
sudo apt update && sudo apt upgrade -y
# 官方源
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
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-buildx-plugin docker-compose-plugin
3.3 创建部署目录
sudo mkdir -p /data/artifact-keeper/{data,certs}
sudo chown -R sup1whu:sup1whu /data/artifact-keeper
cd /data/artifact-keeper
3.4 docker-compose.yml
version: "3.8"
services:
backend:
image: artifactkeeper/artifactkeeper:latest
container_name: ak-backend
restart: unless-stopped
ports:
- "127.0.0.1:30080:8080"
env_file:
- .env
volumes:
- ./data:/var/lib/artifactkeeper
# ★ 域控自签证书才需要下面这行
# - ./certs/ad-ca.crt:/usr/local/share/ca-certificates/ad-ca.crt:ro
environment:
- TZ=Asia/Shanghai
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/api/v1/health"]
interval: 30s
timeout: 10s
retries: 3
就一个容器。Artifact Keeper v1.5 把 PostgreSQL 和 OpenSearch 内置了,不需要额外编排。这对 8C/16G 的机器是好事——不需要养三个容器。
3.5 .env
sudo tee /data/artifact-keeper/.env << 'ENVEOF'
# ── 管理员(首次启动设) ──
# 注意:部署完立即改掉默认密码
ADMIN_PASSWORD=***
# ── 存储 ──
STORAGE_PATH=/var/lib/artifactkeeper
# ── JWT 签名密钥 ──
# 生成方式:openssl rand -hex 32
JWT_SECRET=***
# ── LDAP(Windows Active Directory) ──
LDAP_URL=ldaps://dc01.bitech-auto.com:636
LDAP_BASE_DN=DC=bitech-auto,DC=com
LDAP_BIND_DN=CN=artifact-svc,OU=Service Accounts,DC=bitech-auto,DC=com
LDAP_BIND_PASSWORD=***
LDAP_USER_FILTER=(sAMAccountName={username})
LDAP_SYNC_INTERVAL=3600
# ── 组同步(可选) ──
LDAP_GROUP_FILTER=(member={user_dn})
LDAP_GROUP_BASE_DN=OU=Groups,DC=bitech-auto,DC=com
LDAP_GROUP_ATTRIBUTE=cn
ENVEOF
几个关键点:
ldaps://——Windows AD 不能用标准 LDAP(389 端口),那是明文,必须 LDAPS(636 端口走 TLS)sAMAccountName——AD 的用户名属性,不是 Linux LDAP 的uid,填错一个字连不上- 绑定账号
CN=artifact-svc,...——找 IT 在域控上建一个只读服务账号,别用域管理员
启动:
cd /data/artifact-keeper
sudo docker compose pull # 走代理
sudo docker compose up -d
# 确认 LDAP 连通
sudo docker compose logs backend | grep -i ldap
# 正常输出:LDAP connection established to ldaps://dc01...
3.6 首次登录
- 浏览器打开
https://artifacts.bitech-auto.com - 用默认
admin+ 你设的ADMIN_PASSWORD登录 - 进 Admin Panel → Users → 把域账号提为 Admin
- 立即改掉 admin 默认密码
之后团队用域账号登录就行。首次登录自动在 AK 本地创建用户记录,默认角色是 User。Admin 按需提权。
4 Nginx 反代(通配符证书)
在 Nginx 服务器(10.177.100.25)上配置:
# /etc/nginx/sites-available/artifacts
upstream artifact_keeper {
server 10.177.100.30:30080;
keepalive 16;
}
server {
server_name artifacts.bitech-auto.com;
listen 443 ssl http2;
ssl_certificate /etc/nginx/certs/bitech-auto.com.fullchain.pem;
ssl_certificate_key /etc/nginx/certs/bitech-auto.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
# ★ 大文件上传
client_max_body_size 8g;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
# ★ 禁用缓冲——大文件直通不落磁盘
proxy_buffering off;
proxy_request_buffering off;
location / {
proxy_pass http://artifact_keeper;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
server {
listen 80;
server_name artifacts.bitech-auto.com;
return 301 https://$host$request_uri;
}
sudo ln -s /etc/nginx/sites-available/artifacts /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
两个细节:
- 通配符证书用的是已有的文件路径,不需要 Let’s Encrypt 或 certbot
proxy_buffering off对大文件上传很重要——Nginx 不会把 4 GB 的 SOC 包先缓存到自己的磁盘上再转发,而是边收边传
5 创建仓库 + 准备 CI Token
Web UI:Repositories → Create Repository:
- 类型:Generic
- 名称:
baic-n60-f19
然后 Admin Panel → API Tokens → Create Token,给 Jenkins 用:
- 名称:
jenkins-baic-n60 - 权限:
Read + Write,限定仓库baic-n60-f19 - Token 值记下来 → 存到 Jenkins 凭据管理
⚠️ Token 只显示一次,丢了只能删除重建。
6 CI/CD 集成:改 Jenkins Pipeline
6.1 原流程 vs 新流程
原来:
Jenkins → rsync → 10.179.48.149:81(HTTP 文件服务器)
112.31.22.151(KO 文件服务器)
└→ 飞书机器人发下载链接
现在:
Jenkins → curl PUT → https://artifacts.bitech-auto.com/api/v1/repos/baic-n60-f19/artifacts/...
└→ 飞书机器人发 AK 链接
6.2 实际产物清单
北汽 N60 F19 的单次 Daily Build 产出:
| 产物 | 命名模式 | 类型 | 大小 |
|---|---|---|---|
| SOC 包 | BAIC_N60F19_{version}_{variant}_DB_SOC_{timestamp}.zip | Yocto+Android 全量编译产物 | 2-4 GB |
| OTA 包 | BAIC_N60F19_{version}_{variant}_DB_{timestamp}_OTA.zip | 差分 OTA 升级包 | 500 MB-1.5 GB |
| 华为 U 盘包 | 2-TCIE00198585{version}N60AS19-F19ALL-{timestamp}.zip | 给华为的刷机 U 盘包 | ~2 GB |
| J6B/J6M U 盘包 | 2-TCIE00207292{version}N60AS19-F19ALL-{timestamp}.zip | 给 J6B/J6M 平台 | ~2 GB |
| KO 文件包 | BAIC_N60F19_{version}_{variant}_DB_SOC_{timestamp}_KO.zip | 内核模块、设备树 | ~50 MB |
KO 文件包打包了 Yocto SOS 侧(vmlinux、lk.elf、kernel extension)、Yocto Tbox 侧(MTK 驱动模块、conninfra/gps/wifi 内核模块)、Android U/B 侧(kernel symbols、系统库)的散文件。
6.3 Pipeline 改动要点
新增环境变量(放在 pipeline 顶层):
environment {
AK_URL = 'https://artifacts.bitech-auto.com'
AK_REPO = 'baic-n60-f19'
AK_TOKEN = credentials('ak-api-token') // Jenkins 凭据 ID
}
上传 SOC 包(替代 rsync → 48.149:81):
sh """
curl -X PUT \\
-H "Authorization: Bearer ***" \\
--upload-file /data/products/.../BAIC_N60F19_...zip \\
--connect-timeout 30 \\
--max-time 1800 \\
--retry 3 \\
"${AK_URL}/api/v1/repos/${AK_REPO}/artifacts/BAIC_N60F19_...zip/${VERSION}_${VARIANT}"
"""
5 处 rsync 全部替换为 curl PUT:
- SOC 包 → AK
- OTA 差分包 → AK
- 华为 U 盘包 → AK
- J6B/J6M U 盘包 → AK
- KO 文件包 → AK
飞书通知:下载链接从 http://10.179.48.149:81/... 改为 https://artifacts.bitech-auto.com/repos/baic-n60-f19/artifacts/...
完整 Pipeline 脚本已生成到 /home/herui/pipline-artifact-keeper.txt(415 行,核心逻辑不变,只换了上传目标和通知链接)。
7 LDAP 权限分层
有了 Windows AD 域账号后,按角色分层:
| AD 组 | AK 角色 | 权限 |
|---|---|---|
| SW-Developers | User | Read + Write 所有制品 |
| CI-Service | API Token(限定 baic-n60-f19) | Write 该仓库 |
| QA-External | Viewer | 只读下载 |
| IT-Admins | Admin | 全权限 |
外部供应商(如果后续给)只放 QA-External 只读组。想看某个包?可以。想改?没门。
8 防火墙规则
物理防火墙上需放行的流量:
| 源 | 目标 | 端口 | 用途 |
|---|---|---|---|
| 10.179.85.0/24(Jenkins 网段) | 10.177.100.25 | 443 | 上传制品 |
| 10.177.100.30 | 域控 IP | 636 | LDAPS 认证 |
| 10.177.100.30 | 150.158.137.210 | 8964 | Docker 拉镜像(出站) |
| 外部供应商网段(如需要) | 10.177.100.25 | 443 | 下载制品 |
WAF(物理防火墙已购买)覆盖的是应用层攻击(SQL 注入、XSS、路径穿越等),但对制品内容的防篡改没有作用——那需要 AK 自身的 artifact signing 功能来兜底。
9 运维
# 日常
cd /data/artifact-keeper
sudo docker compose logs -f # 看日志
curl http://127.0.0.1:30080/health # 健康检查
# 升级
sudo docker compose pull
sudo docker compose up -d
# 备份——30 天循环
#!/bin/bash
BACKUP_DIR=/data/backups/artifact-keeper
mkdir -p $BACKUP_DIR
rsync -av --delete /data/artifact-keeper/data/ $BACKUP_DIR/data/
find $BACKUP_DIR -mtime +30 -delete
备份脚本放 cron(0 3 * * *),凌晨 3 点跑。2 TB 的制品空间,我这 45 TB 盘够存半年日备份不打紧。
10 实测数据
空闲:
CONTAINER CPU % MEM USAGE
ak-backend 0.3% 128 MiB
上传 2 GB SOC 包:
CONTAINER CPU % MEM USAGE
ak-backend 12% 256 MiB
上传耗时约 45 秒(千兆内网),比 rsync 直推慢了 3 秒(rsync ≈ 42 秒),差在 HTTP PUT 没有 rsync 的 delta 传输。但大文件场景下这个差距可忽略。
11 小结
| 维度 | 之前 | 之后 |
|---|---|---|
| 存储 | rsync → HTTP 目录 | Artifact Keeper Generic 仓库,有版本元数据 |
| 认证 | 裸露 HTTP | 域账号 + LDAPS,按组分配权限 |
| 下载 | 知道 URL 就能下 | AK 链接 + Token 控制 |
| 校验 | 无 | SHA-256 自动记录 |
| 隔离 | 和 Jira NFS 混用 /data | 独立子目录,容器绑死不越界 |
| 分发 | 飞书贴裸链 | 飞书贴 AK 链接,有审计 |
| 成本 | 0 | 0(MIT 协议) |
从"共享目录 + 飞书扔链接"到 AK,改了 5 个 curl,部署不到半小时。Generic 仓库不挑格式——Jenkins 吐出什么 zip 我就往里塞什么。对于我们这个场景,这就够了。