一套搞定!基于 Docker + Jenkins + Harbor 的国产多系统自动化编译流水线实战全纪录

导语:为什么要折腾这套流水线?

三月份我的饭搭子球搭子离职了,雪上加霜的是他的工作一部分也分给了我。别的还好说,就是每次提版本时打的包实在是太多了。如果全靠手动开虚拟机、拉 SVN 代码、敲 make 编译再打包,不仅耗时耗力,而且极易出错。

为了彻底解决这个"体力活",一劳永逸地提升交付效率,我决定从 0 到 1 搭建一套基于 Docker 容器化环境隔离 + Jenkins 并行调度 + Harbor 私有镜像仓 的完全自动化 CI/CD 流水线。

本文将复盘整个搭建全过程,并附上核心脚本与踩坑记录,希望能帮助到团队后续的维护同学,以及正在折腾构建系统的你。


核心架构概览

在这套流水线中,各个组件各司其职:

  • 基础环境层 (Docker) :利用 Docker 容器的轻量级和隔离性,为每个目标系统制作专属的纯净编译环境镜像,实现一台物理机同时跑多个不同的操作系统。
  • 存储分发层 (Harbor) :搭建企业内网私有仓库。统一存储和分发上述做好的基础镜像,方便团队复用(毕竟这些镜像的制作和配置还是比较费时间的)。
  • 调度控制层 (Jenkins) :作为流水线的"大脑",通过 SSH 连入编译节点,提供可视化的按需参数化构建,并控制多个系统的容器并发启动 ,最后统一收集编译产物(.deb / .rpm / run 包)。

一、 基石构建:从 ISO 系统镜像到 Docker 编译镜像

要用 Docker 编译国产系统代码,首先得有对应的基础镜像。由于公有云仓库往往缺乏特定版本(尤其是内网特定 SP 版本)的国产 OS 镜像,我们需要手动从光盘 ISO 镜像中"榨"出系统底包。

1. 手把手教你从 ISO 提取 Rootfs (根文件系统)

这部分网上的教程往往语焉不详,其实核心原理就是把 ISO 里自带的 Live 系统文件提取出来打包给 Docker。

Step 1:挂载 ISO 镜像 找一台 Linux 服务器,把下载好的系统镜像(比如麒麟 V10 的 ISO)放进去,创建一个挂载点并挂载:

Bash

bash 复制代码
mkdir -p /mnt/iso
mount -o loop Kylin-Desktop-V10-SP1.iso /mnt/iso

Step 2:寻找并解压 Squashfs 文件 进入挂载目录,你需要找到一个体积最大的核心系统压缩文件,通常叫 squashfs.img 或者 filesystem.squashfs(一般藏在 casper/LiveOS/ 目录下):

Bash

bash 复制代码
cd /mnt/iso/casper/  # 具体目录根据不同系统 ISO 可能不同

接下来,我们需要用 unsquashfs 工具把它解压出来(如果提示没有该命令,先执行 apt install squashfs-toolsyum install squashfs-tools 安装):

Bash

复制代码
unsquashfs filesystem.squashfs

执行完后,当前目录下会生成一个名为 squashfs-root 的文件夹,这就是一个完整的、原汁原味的 Linux 根目录(里面有 binetclib 等)。

Step 3:打包并导入 Docker 进入解压出来的根目录,将其打包并直接通过管道"喂"给 Docker:

Bash

arduino 复制代码
# 注意命令最后的点号 '.' 代表当前目录下的所有文件
tar -C squashfs-root -c . | docker import - my-kylin-base:v1

测试一下是否成功:docker run -it my-kylin-base:v1 /bin/bash,如果顺利进入终端,恭喜你,基础底包制作成功!清理战场:umount /mnt/iso

2. 制作完整编译环境镜像的两种姿势

拿到纯净版基础镜像后,需要安装 gcccmakesvn 等编译依赖(由于内网源的特殊性,这一步往往需要特别配置)。

方式 A:交互式构建 (docker run + commit) ------ 适合前期摸索(快速但不规范-我是用的这种-因为还要进去配源)

  • 操作 :直接拉起容器进终端 docker run -it my-kylin-base:v1 /bin/bash
  • 配置 :在里面手动敲 apt update、安装依赖、配置环境变量(感谢之前同事提供的内网源配置)。
  • 保存 :退出容器后,找到刚退出的容器 ID,执行 docker commit <容器ID> 10.x.x.x:8001/library/kylin-build:v1 保存为新镜像。
  • 评价:操作直观,所见即所得;但过程不可复现,如果未来换源或者升级依赖,还得从头再来,俗称"黑盒镜像"。

方式 B:声明式构建 (Dockerfile) -推荐

  • 操作 :找个空目录,编写 Dockerfile 脚本。将内网源配置文件提前放在同级目录,通过 COPY 拷入。

    Dockerfile

    sql 复制代码
    FROM my-kylin-base:v1
    # 替换内网源(假设当前目录有 sources.list)
    COPY sources.list /etc/apt/sources.list
    RUN apt-get update && apt-get install -y gcc g++ cmake subversion
    ENV LANG=zh_CN.UTF-8
    CMD ["/bin/bash"]
  • 构建docker build -t 10.x.x.x:8001/library/kylin-build:v2 .

  • 评价:属于理想很丰满现实很骨感那种,前期写起来需要多次调试比较麻烦,不过一旦摸索出完美的配置,后边可以复用起来。


二、 弹药库:Harbor 仓库的搭建与使用

为了让内网的多台编译机都能快速拉取镜像,搭建私有 Harbor 仓库是必经之路。

1. 详细搭建与 harbor.yml 配置

从 Harbor 官方下载离线安装包(offline installer)并解压后,最重要的一步就是修改配置文件。

  1. 复制模板文件:cp harbor.yml.tmpl harbor.yml

  2. 使用 vim 编辑 harbor.yml,重点修改以下几个地方:

    YAML

    yaml 复制代码
    # 1. 主机名:必须改成你服务器的真实内网 IP,千万不要用 localhost 或 127.0.0.1
    hostname: 10.*.*.*
    ​
    # 2. HTTP 端口:默认 80,如果被占用了可以改,比如改成 8001
    http:
      port: 8001
    ​
    # 3. HTTPS 配置:【关键避坑】因为我们是纯内网环境,没有搞 SSL 证书,
    # 所以必须把 https: 及其下面的 port, certificate, private_key 这几行全部注释掉或删掉!
    # https:
    #   port: 443
    #   certificate: /your/certificate/path
    #   private_key: /your/private/key/path
    ​
    # 4. 管理员密码:设置一个你能记住的密码
    harbor_admin_password: YourStrongPassword
    ​
    # 5. 数据存放路径:一定要改到一个磁盘空间足够大的目录
    data_volume: /data/harbor_data
  3. 运行准备脚本生成配置:./prepare

  4. 一键拉起所有服务:./install.sh

2. 镜像的上传与拉取

  • 致命踩坑 :因为我们用的是无证书的 HTTP 部署,必须在所有需要连接 Harbor 的宿主机(包括你的编译机)上的 /etc/docker/daemon.json 中配置信任该仓库:

    JSON

    json 复制代码
    {
      "insecure-registries": ["10.*.*.*:8001"]
    }

    配置完别忘了重启 Docker 服务:systemctl restart docker

  • 上传流程

    Bash

    markdown 复制代码
    # 1. 登录 Harbor (输入你配置的 admin 账号密码)
    docker login 10.*.*.*:8001
    # 2. 按规范打标签 (镜像名必须以你的仓库地址开头)
    docker tag kylin-build:v2 10.*.*.*:8001/library/kylin-build:v2
    # 3. 推送镜像到仓库
    docker push 10.*.*.*:8001/library/kylin-build:v2

三、 大脑中枢:Jenkins 的离线部署与节点接入

1. 外网制作 Jenkins 离线镜像 (包含核心插件)

因为目标服务器断网,如果直接在内网装原生 Jenkins,你会被无数个安装失败的插件逼疯。最优雅的方式是在有网的电脑上,把 Jenkins 连同我们需要的插件,一起打包成离线 Docker 镜像。

在有网的电脑上操作: 新建一个 Dockerfile,填入以下内容:

Dockerfile

bash 复制代码
FROM jenkins/jenkins:lts
# 切换为 jenkins 用户下载插件
USER jenkins
# 提前下载这几个核心流水线、SSH 节点和 SVN 密码管理必备的插件
RUN jenkins-plugin-cli --plugins "workflow-aggregator ssh-slaves blueocean subversion credentials-binding"

执行编译并导出镜像:

Bash

perl 复制代码
docker build -t my-jenkins-offline:v1 .
docker save -o jenkins_offline.tar my-jenkins-offline:v1

在内网服务器上操作:jenkins_offline.tar 传进内网并导入:

Bash

css 复制代码
docker load -i jenkins_offline.tar
# 启动时务必注意宿主机端口冲突(比如常见的 8080 被占用,换成 8088)
docker run -d --name jenkins --restart=always -p 8088:8080 -v /data/jenkins_home:/var/jenkins_home my-jenkins-offline:v1

打开网页后,直接选择"跳过插件安装" (因为我们已经提前打在镜像里了),创建管理员账号即可进入主页。

2. 接入编译节点 (SSH Node)

Jenkins 自身不干重活,需要通过 SSH 将任务下发给真实的飞腾/x86 编译物理机。

  • 核心配置 :在节点管理中,选择 Launch agents via SSH,填入编译机 IP 和普通账号(如 zhongfu)凭据。一定要将 Host Key Verification Strategy 设置为 Non verifying 以免因为找不到 SSH 指纹报错。
  • 高频踩坑 1 (Java 版本冲突) :Jenkins 需要向节点推送 remoting.jar 通讯程序,较新的 Jenkins 要求节点机器必须有 Java 17 。老机器自带的 Java 8 会直接导致连接失败。解决方法是下载免安装版的 JDK17 离线包解压到编译机,并在 Jenkins 节点设置的 Advanced -> JavaPath 中精准指定新版 Java 路径(例如 /usr/local/jdk-17/bin/java)。
  • 高频踩坑 2 (Docker 权限) :使用普通账号连接节点跑代码时,必须将该账号加入 docker 组 (sudo usermod -aG docker zhongfu),否则 Jenkins 一调 Docker 就会报 permission denied 惨遭拒绝。

四、 核心双引擎:双脚本解析

整个自动化的灵魂在于"内外两层"脚本的完美配合。

1. 外层控制权:Jenkinsfile (声明式流水线)

这是运行在 Jenkins 上的 Groovy 脚本,负责可视化按需调度、并发控制和 SVN 密码保护

Groovy

php 复制代码
pipeline {
    agent { label 'build-server' } // 精准空降到你配置好的编译机节点
    parameters {
        // 提供 Web 界面上的可视化勾选框和输入框,想编哪个勾哪个
        string(name: 'B_VERSION', defaultValue: 'V0.*.*.*', description: '打包版本号')
        booleanParam(name: 'BUILD_KYLIN', defaultValue: true, description: '编译: debian')
        booleanParam(name: 'BUILD_UOS', defaultValue: true, description: '编译: UOS')
    }
    environment {
        // 安全获取 SVN 密码,通过 Jenkins 的 Credentials 管理,绝不把明文密码写在脚本里
        SVN_CREDS = credentials('svn-account') 
    }
    stages {
        stage('多系统并行编译') {
            parallel { // 并行语法,瞬间拉满机器 CPU,极大缩短总时间
                stage('debian') {
                    when { expression { params.BUILD_KYLIN } } // 读取界面参数,决定是否执行
                    steps {
                        // 强烈建议加上 #!/bin/bash,强制使用完整 Bash 环境
                        sh """#!/bin/bash
                            cd /data/build/kylin
                            # 使用 sed 将 Jenkins 变量动态注入配置文件
                            # 注意:由于 Groovy 和 Bash 变量语法的冲突,这里 sed 最外层使用单引号最稳妥
                            sed -i 's|^VERSION=.*|VERSION="${params.B_VERSION}"|g' config.ini
                            bash auto_build.sh
                        """
                    }
                }
                // ... 平行复制其他系统 stage
            }
        }
        stage('产物归档') {
            steps {
                // 自动收集各系统生成的安装包,方便在网页端一键下载
                archiveArtifacts artifacts: '**/out/*.deb', allowEmptyArchive: true
            }
        }
    }
}

2. 内层苦力工:auto_build.sh (底层打包脚本)

这是真正跑在宿主机目录里,负责拉起 Docker 容器执行脏活累活的脚本。 核心逻辑非常干脆:

  1. svn update 更新最新代码。
  2. 启动对应系统的纯净镜像:docker run --rm -v /data:/workspace 10.*.*.*:8001/library/kylin-build:v2 bash -c "cd /workspace && make"
  3. 将编译出的文件打包成 .deb.rpm 或最终压制成自解压的 run 包。

五、 血泪总结:实战中的高频避坑指南

在打通这整条自动化链路的过程中,我踩过了几个极具代表性的坑,这里全盘托出,帮后人避雷:

坑一:Docker TTY 限制导致的"秒挂假成功"

  • 现象:将编译脚本接入 Jenkins 并行执行时,任务 0.1 秒就结束并亮起绿灯提示成功,但去目录一看根本没生成包。
  • 原因 :脚本里写了 docker run -it。后台自动化执行时没有分配真实的物理终端(TTY),Docker 一看没终端直接崩溃。又因为脚本里写了管道符 | tee log,管道吞掉了 Docker 崩溃的错误码,导致外层判定成功。
  • 解法 :后台自动化跑 Docker 绝对不能带 -it 参数 ,直接用 docker run --rm;并且在执行脚本开头加上 set -o pipefail,确保管道中任何一步报错都能直接炸出来,杜绝"假成功"。

坑二:底层 Bash 的 Bad substitution 报错

  • 现象 :Jenkins 执行 sh """ ... """ 代码块时,明明 Bash 语法没问题,却报 Bad substitution 错误。
  • 原因 :Jenkins 的 sh 步骤默认调用的是目标系统极其简陋的 /bin/sh(通常是指向 dash 的软链接),它根本不支持 ${数组[@]} 等高级字符串替换语法。
  • 解法 :在 sh """ 代码块内的第一行,紧贴着写上 #!/bin/bash 作为文件头,强制 Jenkins 使用功能完整的 Bash 解释器。

坑三:著名的"Root 幽灵" (文件权限冲突)

  • 现象 :Jenkins 跑着跑着,在执行 chmod 或者清理旧文件时,爆出红色的 不允许的操作 (Operation not permitted)
  • 原因 :前期手动测试时你可能用了 root 账号去 svn checkout,或者 Docker 容器内部(默认是 root 身份)编译产生的文件落盘到了挂载的宿主机目录。这导致文件归属权全变成了 root。等正式跑的时候,Jenkins 以普通用户(zhongfu)身份进来,想改 root 老大的东西直接被系统保安拦死。
  • 解法 :如果权限乱了,最彻底的解决办法是用 root 账号在宿主机终端执行一波终极大法:chown -R zhongfu:zhongfu /data/你的工作目录,把文件的控制权统统抢回给 Jenkins 的运行账号。

结语

从过去手动挨个进虚拟机敲命令,到如今只需在 Jenkins 页面上"勾选想要的系统,填入版本号,点下运行,喝口茶的功夫(需要四十分钟 哈哈哈 不过要快六倍 还不会出错),安装包就全自动吐出来了",这种工业化流水的效率飞跃是极其震撼的。

目前实现了一台机器可以打6个系统的子系统包,另一台机器打所有最终的run包,由于都是jenkins的重复使用,就没在文中赘述,希望这种思路可以帮到大家。

相关推荐
谁在黄金彼岸2 小时前
MariaDB Docker容器权限配置问题分析与解决方案
后端·docker·容器
ywlovecjy2 小时前
macOs安装docker且在docker上部署nginx+php
nginx·macos·docker
白鸽梦游指南2 小时前
docker仓库的工作原理及搭建仓库
java·docker·eureka
cyber_两只龙宝2 小时前
【Docker】Docker的原生网络介绍
linux·运维·docker·云原生·容器
jwlee013 小时前
Docker Compose
docker·容器·eureka
H_老邪3 小时前
Docker 反向代理部署方案
运维·docker·容器
小陈工3 小时前
Python Web开发入门(一):虚拟环境与依赖管理,从零搭建纯净开发环境
开发语言·前端·数据库·git·python·docker·开源
SimonKing3 小时前
紧急自查!Apifox被投毒,使用者速看:你的Git、SSH、云密钥可能已泄露
java·后端·程序员
李子焱4 小时前
第二节:n8n私有化部署全攻略(基于 Docker)
运维·docker·容器