PXE技术:基于 Ubuntu Server 构建 BootOS 内存操作系统

1,623 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 7 天,点击查看活动详情

基于 Ubuntu Server 构建 BootOS 内存操作系统

BootOS 简介

BootOS (也称 ramos ),是一个基于内存的操作系统,系统启动后全部加载到内存中运行,不依赖磁盘存储设备,因此可以对硬件层级进行一系列的操作。如:针对 RAID、OOB、BIOS等硬件的配置或升级。

功能特点

  • 镜像体量小,安装和启动速度快。
  • 基于内存,系统运行启动过程中不会对磁盘造成写入,系统重启后即消失。
  • 支持自定义脚本或程序,对硬件设备进行信息收集或配置操作。

工作原理

  • BootOS 由 Linux 系统内核(vmlinuz)内存文件系统(initfamfs) 共同组成。
  • 由 [[Linux 系统引导启动流程]] 可知,BootLoader 程序在加载完内核镜像后,会将 initramfs 内存文件系统解压到 RAM 中,并将控制器移交给内核,然后内核会通过 initramfs 内存文件系统加载硬件驱动,最后在切换挂载到实际的根文件系统上,并运行 Systemd 。
  • 因此,内核在切换实际根文件系统前,用于加载硬件驱动的系统环境就是内存操作系统。

BootOS 使用

构建 BootOS 镜像

1. 准备工作

  • 虚拟机软件:VMware
  • 系统镜像:ubuntu-20.04.4-live-server-amd64.iso

2. 虚拟机的系统网络配置

  • 安装 Ubuntu 20.04 系统:
    • 新建 VMware 虚拟机,通过 ubuntu-20.04.4-live-server-amd64.iso 镜像文件,安装 Ubuntu 20.04 系统,作为构建 BootOS 镜像的系统环境。
  • 配置虚拟机网络:
    • 为虚机添加两个网络适配器,一个配置 NAT 用于访问公网下载软件包,另一个配置仅主机模式(我这里选择 VMnet2),并设置子网及掩码 10.0.0.0 255.255.255.0,关闭 VMnet2 的 DHCP 功能,用于配置 PXE 引导环境。
    • 虚机系统装好后,配置 PXE Server 的静态IP:
    root@server:/etc/netplan# cat 02-netcfg.yaml 
    # This file describes the network interfaces available on your system
    # For more information, see netplan(5).
    network:
      version: 2
      renderer: networkd
      ethernets:
        ens34:
          addresses: [10.0.0.4/24]
        
    root@server:/etc/netplan# netplan apply
    

3. 配置构建目录

# iso 镜像挂载目录,output 目录存放 vmlinuz 及 封装好的 initramfs 文件
$ mkdir -p /mnt/build_initramfs/{iso,output}

# 挂载 iso 镜像
$ mount -o loop /root/ubuntu-20.04.4-live-server-amd64.iso /mnt/make_initramfs/iso/

# 拷贝及解压 initramfs 文件
$ cd /mnt/build_initramfs
$ cp -a iso/casper/filesystem.squashfs .
$ unsquashfs filesystem.squashfs
$ mv -f squashfs-root initramfs

目的:基于 iso 提供的 initramfs 进行自定义的构建。

4. 切换内存文件系统

# 将系统 /dev (设备文件目录) 和 /run (运行时数据) 目录,
# 绑定(可理解为映射)到 initramfs 对应的目录(构建 bootos 时需要依赖正常系统)
$ mount --bind /dev initramfs/dev
$ mount --bind /run initramfs/run

# 切换到内存文件系统
$ chroot initramfs

# 为内存系统挂载 /proc (内存、进程信息) /sys (设备和驱动信息) /dev/pts (伪终端设备访问) 虚拟文件目录
$ mount none -t proc /proc
$ mount none -t sysfs /sys
$ mount none -t devpts /dev/pts

目的:构建内存文件系统环境,便于后面对系统进行配置操作。

参考资料:mount 命令 device 指定 none 的含义

5. 初始化配置

# 设置环境变量
$ export HOME=/root
$ export LC_ALL=C

# 配置 apt 仓库源
$ echo "deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse" > /etc/apt/sources.list
$ echo "deb http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse" >> /etc/apt/sources.list
$ echo "deb http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse" >> /etc/apt/sources.list
$ echo "deb http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse" >> /etc/apt/sources.list
$ echo "deb http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse" >> /etc/apt/sources.list

# 禁自启一些服务
$ systemctl disable apt-daily.timer
$ systemctl disable apt-daily-upgrade.timer
$ systemctl disable apt-daily.service
$ systemctl disable apt-daily-upgrade.service
$ systemctl disable systemd-resolved.service

# 停止一些服务
$ systemctl stop systemd-resolved
$ systemctl stop apt-daily.timer
$ systemctl stop apt-daily-upgrade.timer
$ systemctl stop apt-daily.service
$ systemctl stop apt-daily-upgrade.service

# 卸载无用软件 (注意:卸载哪些应用取决于,后面自定义时自己是否用的到)
$ apt purge -y ufw snapd apparmor cloud-init cloud-guest-utils git git-man pollinate && apt autoremove -y

# 升级系统软件
$ apt update && apt dist-upgrade -y

# 禁用安装推荐的包
$ echo 'APT::Install-Recommends "false";' > /etc/apt/apt.conf.d/00-DisableInstallRecommends

参考资料:理解update、upgrade和dist-upgrade的区别

6. 安装需要的软件

# 必备的软件(可能)
$ apt-get install kmod bash-completion openssh-server

# 自己需要的软件包 (我只安装了 smartctl 、sshpass 用于演示)
$ apt-get install smartmontools sshpass

目的:根据自己个人需求进行安装

7. 下载 vmlinuz

# 获取可用的内核版本信息
$ KERNEL_VERSION=$(apt-cache madison linux-image-generic | head -1 | awk '{print $3}' | awk -F '.' '{print $1"."$2"."$3"-"$4}')

# 下载最高版本的内核文件
$ apt download linux-image-${KERNEL_VERSION}-generic

# 解压内核文件
$ dpkg -x ./linux-image* linux-image-${KERNEL_VERSION}

# 拷贝 vmlinuz 内核文件
$ cp -af linux-image-${KERNEL_VERSION}/boot/vmlinuz-${KERNEL_VERSION}-generic /vmlinuz

# 添加权限并清理环境
$ chmod +r /vmlinuz
$ rm -rf linux-image-*

# 安装内核 modules 
$ apt install -y $(apt search linux-modules | egrep -o "linux-modules-${KERNEL_VERSION}-generic")

8. 配置网络接口

# 配置网卡 DHCP 动态获取地址
$ vim /etc/systemd/network/80-dhcp.network 
[Match]
Name=eth0

[Network]
DHCP=yes

# 使用 mac 地址作为 DHCP 客户端的标识符:
# 解决由于 bootos 的 machine-id 重复,导致系统启动时 dhcp 获取 ip 地址重复的问题。
[DHCP]
ClientIdentifier=mac 

# 设置开机自启动
$ systemctl enable systemd-networkd.service

9. 设置密码和主机名

# 设置密码
$ echo "root:123456"|chpasswd 

# 设置主机名
$ echo "bootos" >/etc/hostname 

10. 配置 SSH 参数

# 去掉一些 ssh 登录相关信息的 pam 权限
$ sed -i '/pam_motd.so/d' /etc/pam.d/sshd
$ sed -i '/pam_mail.so/d' /etc/pam.d/sshd

# ssh 登录相关参数
$ echo "UseDNS no" >> /etc/ssh/sshd_config
$ echo "PermitRootLogin yes" >> /etc/ssh/sshd_config
$ echo "    StrictHostKeyChecking no" >> /etc/ssh/ssh_config
$ echo "    UserKnownHostsFile /dev/null" >> /etc/ssh/ssh_config

11. 自定义操作及配置

结合自己的需求进行操作和配置,比如:配置 python 环境,或者是其他服务环境、配置等需求,配置计划任务等等。

12. 添加 init 文件

操作系统启动后,会依次加载 init 文件,而首先加载的是 /init 文件来运行,我们可以通过这个脚本,来获取 PXE 配置文件中,APPEND 添加的变量参数(支持自定义)。

$ cat >> /init << EOF
#!/bin/sh
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
mount -t proc -o nodev,noexec,nosuid proc /proc

# Some things don't work properly without /etc/mtab.
ln -sf /proc/mounts /etc/mtab

mount -t devtmpfs -o mode=0755 udev /dev
if [ $? -ne 0 ]; then
    mount -t tmpfs -o size=64k,mode=0755 tmpfs /dev
    [ -e /dev/console ] || mknod -m 0600 /dev/console c 5 1
    [ -e /dev/null ] || mknod /dev/null c 1 3
fi

mkdir /dev/pts
mount -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts || true
mount -t tmpfs -o "noexec,nosuid,size=10%,mode=0755" tmpfs /run
mkdir /run/initramfs

# compatibility symlink for the pre-oneiric locations
ln -s /run/initramfs /dev/.initramfs

# Set modprobe env
export MODPROBE_OPTIONS="-qb"
find /sys/devices -name modalias | xargs -r cat | xargs -r modprobe -qa

# mdadm needs hostname to be set. This has to be done before the udev rules are called!
if [ -f "/etc/hostname" ]; then
    /bin/hostname -b -F /etc/hostname 2>&1 1>/dev/null
fi

# Read the parameters of APPEND in pxelinux.cfg($SERVER_ADDR 变量就是自定义变量)
if [[ -n "${SERVER_ADDR}" ]]; then  
    echo "${SERVER_ADDR}" > /opt/SERVER_ADDR  
fi

exec /sbin/init
EOF
$ chmod +x /init

13. 配置 rc.local

# ubuntu 默认不启用 rc.local 的自启功能脚本,需要手动配置
$ ln -s /lib/systemd/system/rc-local.service /etc/systemd/system/rc-local.service
$ touch /etc/rc.local
$ chmod +x /etc/rc.local

# 可以结合自己的业务需求,配置自定义命令或脚本。(以下为示例伪代码)
$ cat >> /etc/rc.local << EOF
/bin/bash /path/to/file >> /var/log/file.log 2>&1

14. 清理环境,退出initramfs文件系统

# 清理安装包
$ apt clean && apt autoclean && apt autoremove -y

# 退出 initramfs, 取消挂载
$ umount /proc
$ umount /sys
$ umount /dev/pts
$ export HISTSIZE=0
$ exit

# 取消文件映射
$ umount initramfs/dev
$ umount initramfs/run

# 清理文件缓存
$ rm -rf initramfs/usr/share/locale/*
$ rm -rf initramfs/usr/share/man/*
$ rm -rf initramfs/usr/share/doc/*
$ rm -rf initramfs/var/log/*
$ rm -rf initramfs/var/cache/*
$ rm -rf initramfs/var/lib/apt/lists/*
$ rm -rf initramfs/root/.bash_history 

15. 镜像打包

$ cd /mnt/build_initramfs/initramfs

# 提取出 vmlinuz 内核文件
$ mv -f vmlinuz ../output/

# 镜像打包
$ find . | cpio -o -H newc | gzip -9 > ../output/initramfs.gz

# 查看打包结果
$ ls ../output/
vmlinuz
initramfs.gz

16. 后续镜像维护

$ cd /mnt/build_initramfs/initramfs

$ mount --bind /dev initramfs/dev
$ mount --bind /run initramfs/run

$ chroot initramfs

$ mount none -t proc /proc
$ mount none -t sysfs /sys
$ mount none -t devpts /dev/pts

# 设置环境变量
$ export HOME=/root
$ export LC_ALL=C

# 维护操作


# 退出环境
$ umount /proc
$ umount /sys
$ umount /dev/pts
$ export HISTSIZE=0
$ exit

$ umount initramfs/dev
$ umount initramfs/run

# 重新打包
$ cd /mnt/build_initramfs
$ mkdir backup
$ mv output/initramfs.gz backup/initramfs.gz.bak

$ cd initramfs
$ find . | cpio -o -H newc | gzip -9 > ../output/initramfs.gz

引导 BootOS 启动

1. 配置引导启动环境

过程略,详见:PXE 技术:PXE 自动装机技术分享

2. 修改 pxelinux.cfg 配置

$ cp /mnt/build_initramfs/output/{vmlinuz,initramfs.gz} /var/lib/tftpboot/

$ cat > /var/lib/tftpboot/pxelinux.cfg/default <<EOF 
DEFAULT BootOS

LABEL BootOS
  MENU DEFAULT
  KERNEL /vmlinuz
  INITRD /initramfs.gz
  APPEND root=/dev/ram0 ro biosdevname=0 net.ifnames=0 SERVER_ADDR=10.0.0.4
  IPAPPEND 2 # 让 BootOS 启动时使用 pxe client 对应的网卡作为eth0
EOF

3. 启动测试虚拟机

新建虚拟机,选择 VMnet2 网卡适配器,启动虚拟机,等待 pxe 自动安装 BootOS。

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 7 天,点击查看活动详情