基于 Jenkins 搭建针对内核的 CI

背景简述

  • 目标内核分支:4.4、4.18、4.19、5.10
  • 根文件系统:最后一版非 Stream 的 CentOS 8
  • 架构概述:
    • 制作一份统一的原始 rootfs 镜像;
    • 为每个内核版本各自设立一个 Job,各自拷贝一份原始 rootfs 运行 VM,随后在 VM 内执行编译内核、安装内核、运行测试套以及其他特定内核版本所需的动作。

Jenkins 配置手册

使用清华开源镜像站

Jenkins 有普通版(每周一更)和 LTS 版(每 12 周一更),安装包(以 Ubuntu 为例)在清华源上分别保存在 debian/debian-stable/ 上。下载下来直接安装即可,不用配 apt 啥的,主要是网络代理太麻烦。

安装后参考手册的指导 进行后续操作,如安装一些依赖,启动 Jenkins 服务并检查其状态等。

更新插件要用到的 update-center.json 在清华源的 updates/ 目录中。

Jenkins 是以 jenkins 用户的身份执行各种命令的,由于启动虚拟机涉及 root 权限,因此要将该用户加入 sudo 群组,并(与后者冲突)在 /etc/sudoers 文件中指明其不需要密码:NOPASSWD。参考这篇博客 。(暂时未掌握非根用户启动虚拟机的方法,若有求告知)

参考链接:

虚拟机设置避坑

QEMU 网络相关

总的来说,QEMU 的网络设置包含两部分:

  1. 虚拟机中的虚拟网络设备(virtual network device),也称虚拟网卡(emulated NIC)

    着重在虚拟机中模拟真实的网络设备,可由参数 -device 配置。

  2. 在主机侧与前者交互的网络后端(network backend)

    负责在主机网络与虚拟机网卡之间作网络报文的转接和交换,由参数 -netdev 配置。

此外,如果并不关心有关虚拟网卡和网络后端的过多细节,还可以使用 -nic 参数进行极简配置。关于这部分的参考材料(优先级从高到低排序):

一开始的想法是【tap 网络后端 + 子网 IP 直连】,但最终采用了【user 网络后端 + SSH 端口映射】的方案。后者在同样满足功能要求的前提下,配置起来更简单,可拓展性也更好。

SSH 远程执行命令

执行命令本身很简单(链接 ):

1# Single command
2ssh user1@server1 command1
3
4# Multiple commands with heredoc.
5# The limit string should be quoted for expanding remote variables
6ssh -T user1@server1 <<'EOL'
7	command1
8	command2
9EOL

但有很多坑要躲:

  • 远程关机并且返回值正常的方法(链接 ):

    1# 直接运行 poweroff 返回值会异常,导致 Jenkins 判定 Job 失败
    2shutdown --poweroff +1
    
  • 不询问“是否加入 known_host”直接建立 SSH 连接的方法(链接 ):

    1# ~/.ssh/config
    2Host localhost 127.0.0.1
    3    StrictHostKeyChecking no
    

    或者在建立 SSH 链接时加 -o 参数:

    1ssh -o StrictHostKeyChecking no user@host
    

脚本代码

仅贴出相对重要的脚本,不完整,仅供参考。

  • install-rootfs.sh:利用发行版安装光盘来安装根文件系统。

     1#!/bin/bash
     2
     3INSTALL_ISO=CentOS-8.5.2111-x86_64-dvd1.iso # CentOS-7-x86_64-Everything-2009.iso
     4ROOTFS_IMG=new.qcow2
     5
     6sudo qemu-system-x86_64 \
     7	-m 4096 \
     8	-boot d \
     9	-enable-kvm \
    10	-cdrom $INSTALL_ISO \
    11	-hda $ROOTFS_IMG \
    12	-vnc :12 \
    13	-daemonize
    
  • vm-update-kernel-reboot.sh:在主机侧启动虚拟机,在其上构建并安装新内核后重启。

     1#!/bin/bash
     2
     3source ./util-common.sh # take input $1 and $2
     4
     5DRIVE=rootfs-$KERNEL_VER.qcow2
     6if [ ! -e $DRIVE ]; then
     7	cp $PUBLIC_DIR/rootfs.qcow2 $DRIVE
     8fi
     9
    10sudo qemu-system-x86_64 \
    11	-name $VM_NAME \
    12	-m 4096 \
    13	-smp 16 \
    14	-hda $DRIVE \
    15	-enable-kvm \
    16	-k en-us \
    17	-nic user,hostfwd=tcp::$SSH_PORT-:22 \
    18	-daemonize
    19# For debugging
    20# VNC_DISPLAY=":$((10+$SERIAL))"
    21	# -vnc $VNC_DISPLAY \
    22	# -append "root=/dev/sda1 vga=ask" \
    23	# -initrd rootfs-images/x86.cpio.gz \
    24
    25source ./util-check-connection.sh
    26
    27ssh -i $PUBLIC_DIR/generic_ci_key root@localhost -p$SSH_PORT<<EOL
    28bash -ex ~/build_kernel.sh $KERNEL_VER
    29shutdown --reboot +1
    30EOL
    

LSM 启动过程
Makefile 实例参考