当前位置:首页 > 技术 > LINUX > 正文内容

基于 debootstrap 和 busybox 构建 mini ubuntu

watrt6年前 (2018-10-13)LINUX14090

最近的工作涉及到服务器自动安装和网络部署操作系统,然后使用 ansible 和 saltsatck 进行配置并安装 openstack 。

难点在于服务器的自动安装,由于不单只是通过 PXE 安装服务器,还需要能够安装时进行分区、配置网卡等工作,因此需要在开始安装前,必须先收集服务器的硬件信息。

调研了一下目前的开源项目中,提供此类功能的有 tinycorelinux 、 puppet razor-el-mk 可做类似的工作。tinycorelinux 是个很好的工具,整个系统在 PXE 之后在内存中执行,可在里面加上简单的 agent 完成任务报告的工作;razor 是 puppet 绑定在一起用的,el-mk 基于 centos ,它在里面装了 razor 的 agent,使用 facter 进行硬件信息收集。

这些方案的基本思路都是相通的,首先通过 PXE 下载 microkernel ,然后直接在内存中执行,启动网卡,运行 agent 并向服务器汇报信息,并接收来自服务器的命令。基本的技术原理都是 PXE + linux initramfs ,根据不同的需要向 initramfs 中加硬件驱动。

仔细研究了一下之后,发现用 debootstrap + busybox 工具做这样的小系统会更加简单,有以下的优点:

debootstrap 生成的小 ubuntu 能方便使用 apt 安装额外的工具

可直接把驱动模块拷贝到小镜像内使用

定制脚本非常简单容易

整个小系统在不安装额外的软件和内核模块的情况下,为 100 M 左右,并可加入 busybox 后裁减到 40-50 M(包含完整的基础库)。在安装了 python3 (完整的 python3 ),可裁减到 110 M 左右。

在开始前,可能需要先了解一下 initramfs 的原理,可看 http://www.iteedu.com/os/linux/mklinuxdiary/ch3initrd/index.php 。从本质上看,initramfs 就是一个经过裁减过的 linux 系统的完整文件系统(去掉 kernel 、删掉没有用的软件),然后在内存中展开,并执行程序(/init /sbin/init)。

一个最简单的 mini ubuntu

在 ubuntu 14.04 上,用 debootstrap 生成一个 minibase 的 ubuntu 系统,其中包含了整个基本的文件系统和 apt 工具(不包含 kernel) ::

sudo debootstrap --variant=minbase trusty mini \
    http://mirrors.aliyun.com/ubuntu/

这个地方用了 aliyun 的 mirror ,也可换成 163 或其它的 mirror 。可以使用 chroot 切换进去,用 dpkg 查看已安装的软件 ::

sudo chroot mini dpkg -l# 或sudo chroot mini /bin/bash

为了让它能作为 initramfs 被 kernel 启动,需要在根目录下放一个 init 文件,在 init 文件中写上启动过程(文件系统挂载、执行 /sbin/init等),以下是从 initramfs-tools 工具里面抄了一部分出来(/usr/share/initramfs-tools/init),由于不挂载硬盘上的 root 分区,因此减少了很多代码。

cat << EOF | sudo tee mini/init#!/bin/sh[ -d /dev ] || mkdir -m 0755 /dev
[ -d /root ] || mkdir -m 0700 /root
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
[ -d /tmp ] || mkdir /tmp
mkdir -p /var/lock
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

grep -q '\<quiet\>' /proc/cmdline || echo "Loading, please wait..."# Note that this only becomes /dev on the real filesystem if udev's scripts# are used; which they will be, but it's worth pointing outif ! mount -t devtmpfs -o mode=0755 udev /dev; then
       echo "W: devtmpfs not available, falling back to tmpfs for /dev"
       mount -t tmpfs -o mode=0755 udev /dev
       [ -e /dev/console ] || mknod -m 0600 /dev/console c 5 1
       [ -e /dev/null ] || mknod /dev/null c 1 3fimkdir /dev/pts
mount -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts || truemount -t tmpfs -o "noexec,nosuid,size=10%,mode=0755" tmpfs /run
mkdir /run/initramfs# compatibility symlink for the pre-oneiric locationsln -s /run/initramfs /dev/.initramfs# Set modprobe envexport MODPROBE_OPTIONS="-qb"# 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/nullfiexec /sbin/init
EOF

sudo chmod +x mini/init

当 GRUB 载入 kernel 和 initramfs 后,kernel 会把 initramfs 在内存中展开,然后执行其根目录下的 init ,也就是上面的脚本。以上的脚本会执行 mount 工作,准备好目录结构,然后执行 /sbin/init 转换入 ubuntu 的初始化过程(system-v init ,upstart , systemd,用 udev 自动创建设备文件等)。

修改 ubuntu 的启动配置,进行自动登录 ::

for i in $(find mini/etc/init -type f -name "tty*"); do
   sed -e "s|/sbin/getty -8|/sbin/getty --autologin root -8|" -i $idone

注意,minibase 中没有包含内核模块和硬件驱动,需要从 kernel 或本机中把模块 copy 进去,这里通过 apt-get 下载一个 linux-image 并解压得到内核模块 ::

sudo apt-get download linux-image-$(uname -r)
sudo rm -rf linux-image-$(uname -r)
sudo dpkg -x $(find . -type f -name "linux-image-$(uname -r)*.deb" | head -n 1) linux-image-$(uname -r)
sudo cp -af linux-image-$(uname -r)/lib mini/

然后生成模块依赖关系 ::

sudo chroot mini depmod

清理一下 ::

sudo chroot mini apt-get clean

现在整个 initramfs 已经准备好,可以打包,并 copy 到 boot 目录 ::

(cd mini; sudo find . | sudo cpio -o -H newc | sudo gzip -9 > ../initramfs-mini.gz)
sudo cp -f initramfs-mini.gz /boot

重启电脑,在 grub 目录处按 c 进入 cmdline ,用以下的命令引导(可用 tab 补全) ::

linux /vmlinux-<xxxxx>initrd /initramfs-mini.gz
boot

boot 之后,会由 kernel 解压 initramfs-mini.gz ,然后很快进入到熟悉的 ubuntu 命令提示中。这个基本的 linux 能运行常见的操作,除了还缺少像 ping 之类的工具(可通过 apt-get 安装),直接在内存中执行,与平常使用无异(由于所有的文件都在内存中,加载命令的速度应该更加快一点)。

使用 busybox 替换基本命令并裁减

上面生成的小系统已经基本可用了,但如果还想继续减少体积,以供在内存较少的机器上运行,那么还可以继续进行裁减,最重要的步骤就是用 busybox 处理大部分的工作,甚至包括设备驱动的加载和热插拔。

在 minbase 的基础上安装 busybox-staic ::

sudo chroot mini apt-get -y --no-install-recommends install \
    busybox-static

定义 apps 和 extra_apps 变量来保存 busybox 所支持的命令,定义函数用于用 bubybox 替换原来的命令 ::

applets=$(mini/bin/busybox --list)
apps=for i in $applets; do
    apps="$apps $(sudo chroot mini which $i)"doneextra_apps=for i in $applets; do
    if ! sudo chroot mini which $i > /dev/null; then
        extra_apps="$extra_apps /bin/$i "
    fidonefunction fix_missing() {    for i in $apps $extra_apps; do
        if ! test -f mini/$i; then
            sudo ln -sf /bin/busybox mini/$i
        fi
    done}

然后,开始大扫除,清理可被直接删除的包 ::

sudo chroot mini apt-get -y --force-yes purge adduser busybox-initramfs \
    cpio ifupdown initramfs-tools initscripts initramfs-tools-bin \
    iproute2 locales mountall makedev plymouth procps upstart libprocps3 \
    libcgmanager0 libusb-1.0-0 usbutils libdbus-1-3 libnih-dbus1 libdrm2 \
    libjson-c2 libjson0 kmod libkmod2 module-init-tools file libmagic1 \
    libnih1 libplymouth2 pciutils libudev1 udev
fix_missing

强制去掉不需要的包 ::

sudo chroot mini /bin/sh -c 'echo "Yes, do as I say!" |\
    apt-get -y --force-yes purge diffutils findutils hostname'fix_missing
sudo chroot mini /bin/sh -c 'echo "Yes, do as I say!" |\
    apt-get -y --force-yes purge -y --force-yes login libmount1 mount\
        grep gzip sed mount'fix_missing
sudo chroot mini /bin/sh -c 'echo "Yes, do as I say!" |\
    apt-get -y --force-yes purge -y --force-yes sysvinit-utils lsb-base\
        e2fsprogs e2fslibs bsdutils libblkid1 libuuid1 passwd tzdata insserv'fix_missing
sudo chroot mini /bin/sh -c 'echo "Yes, do as I say!" |\
    apt-get -y --force-yes purge ncurses-base ncurses-bin'sudo chroot mini /bin/sh -c 'echo "Yes, do as I say!" |\
    apt-get purge coreutils'fix_missing

这里还可以继续清理,把一些不需要的 lib 都去掉。

然后清理 apt 的缓存 ::

sudo chroot mini apt-get clean
sudo rm -rf mini/usr/share/locale/*
sudo rm -rf mini/usr/share/man/*
sudo rm -rf mini/usr/share/doc/*
sudo rm -rf mini/var/log/*
sudo rm -rf mini/var/lib/apt/lists/*
sudo rm -rf mini/var/cache/*
sudo rm -rf mini/etc/rc*

由于在清理的过程中已经去掉了 udev 、 system-v init 、 upstart 等,需要一个支持 busybox 的新 init 脚本。在 ubuntu 里面原来的 udev 本身支持初始化加载驱动和设备插拔,但是 busybox 的 mdev 只在 hotplug 的时候调用,网上的很多资料也没有提到怎样在初始化时加载驱动模块,搜了一下 busybox 的邮件,找到了直接查找 /sys/devices 目录加载驱动模块的方式 http://lists.busybox.net/pipermail/busybox/2009-April/068894.html ::

cat > mini/init << EOF#!/bin/shmount -t proc -o nodev,noexec,nosuid proc /proc
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
ln -sf /proc/mounts /etc/mtab
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
mkdir -p /dev/pts
mount -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts
mount -t tmpfs -o "noexec,nosuid,size=10%,mode=0755" tmpfs /runecho "Loading modules..."# hotplug with mdevecho /bin/mdev > /proc/sys/kernel/hotplug
mdev -s# coldplug: load devices supporting modulesfind /sys/devices -name modalias | xargs -r cat | xargs -r modprobe -qa# extra modules[ -f /etc/modules ] && cat /etc/modules | grep -v "^[[:blank:]]#" |\
    xargs -r modprobe -qaexec /sbin/init
EOF
chmod +x mini/init

修改使用 busybox 的 init ::

ln -sf /bin/busybox mini/bin/sh
ln -sf /bin/busybox mini/sbin/init

busybox 的 init 需要一个 inittab 文件描述初始化过程 ::

cat > mini/etc/inittab << EOF::sysinit:/etc/init.d/rcS::ctrlaltdel:/sbin/reboot::shutdown:/sbin/swapoff -a::shutdown:/bin/umount -a -r::restart:/sbin/inittty1::respawn:/bin/sh
tty2::askfirst:/bin/sh
tty3::askfirst:/bin/sh
tty4::askfirst:/bin/sh
EOF
touch mini/etc/init.d/rcS
chmod +x mini/etc/init.d/rcS

同样,加入 kernel 模块 ::

sudo apt-get download linux-image-$(uname -r)
sudo rm -rf linux-image-$(uname -r)
sudo dpkg -x $(find . -type f -name "linux-image-$(uname -r)*.deb" | head -n 1) linux-image-$(uname -r)
sudo cp -af linux-image-$(uname -r)/lib mini/
sudo chroot mini depmod

打包,并 copy 到 boot 目录 ::

(cd mini; sudo find . | sudo cpio -o -H newc | sudo gzip -9 > ../initramfs-mini.gz)
sudo cp -f initramfs-mini.gz /boot

重启,在 grub 目录处按 c 进入 cmdline ::

linux /vmlinux-<xxxxx>initrd /initramfs-mini.gz
boot

在这一步,得到的整个目录的大小为 80 M 左右(包含 kernel 模块),但是还可以继续进行裁减。

深入裁减

用 du -hs * mini 查看整个目录,可看到目前占用空间最多的就是内核的模块目录了,看一下内核的模块目录,差不多占了一半的空间。

以下是内核模块的占用空间情况(mini/lib/modules/4.4.0-31-generic) ::

2.6M    arch1.4M    crypto11M     drivers8.1M    fs1.8M    lib12M     net832K    sound508K    ubuntu20K     virt

net 里面包括了 ceph 、netfilter 、openvswitch、atm(如果你只是一个普通程序员,可能你一辈子都不会碰到这货)、x2、appletalk 等等,都是可以删除的。

其它的看情况办,记得删完之后,重新检查依赖关系 ::

chroot mini depmod

mini/usr/lib 目录也很大,里面有些库是可以删的,像 mini/usr/lib/x86_64-linux-gnu/gconv 里面的一大堆编码库,看不过去的删掉一些。libdb-5.3.so 也很大,没有用 man 等是可以删掉的。

这个阶段删东西就不能用 apt-get 了,最多用一下 dpkg ,并且强制清理掉,最后可以把 apt 也清理了。到这个阶段就没有什么技术问题,只要有足够的细心和耐心,就能弄到一个足够小的系统。

其它

还有一些其它方法可进行 linux 的定制,如 LFS http://linuxfromscratch.org/ 、 buildroot https://buildroot.org/ 等。

如果想把这个小 linux 保存到硬盘中,也很好办。整个 mini copy 到一个单独的分区上,加载根目录所在的分区为根分区,在 init 脚本通过 switch_root 切换到 /sbin/init ,假如在 /sda3 ::

# 模块加载完成之后 ... mount /dev/sda3 /mntmkdir -p /mnt/mntexec switch_root /mnt /sbin/init

需要注意的是,由于根分区切换, /proc /sys /dev /tmp 等目录需要进行额外的处理,要么把该目录用 mount -o move 到 mnt 下,或者在 mini/fstab 文件中定义挂载点。

mkdir /opt/rootfs -p
apt-get install debootstrap -y
apt-get install qemu-user-static -y
cd /opt/
#debootstrap --foreign --verbose --arch=armhf  stretch rootfs http://ftp2.cn.debian.org/debian 
debootstrap --foreign --verbose --arch=armhf stretch rootfs http://ftp.de.debian.org/debian
###挂载###
mount --bind /dev   /opt/rootfs/dev/
mount --bind /sys   /opt/rootfs/sys/
mount --bind /proc   /opt/rootfs/proc/
mount --bind /dev/pts /opt/rootfs/dev/pts/
cp /usr/bin/qemu-arm-static /opt/rootfs/usr/bin/
chmod +x /opt/rootfs/usr/bin/qemu-arm-static
###解压###
LC_ALL=C LANGUAGE=C chroot rootfs /debootstrap/debootstrap --second-stage --verbose
###可以在这个时候, 安装任何东西###
LC_ALL=C LANGUAGE=C chroot rootfs
###删除qemu-arm-static, 深藏功与名###
rm rootfs/usr/bin/qemu-arm-static
###卸载###
umount /opt/rootfs/dev/
umount /opt/rootfs/sys/
umount /opt/rootfs/proc/
umount /opt/rootfs/dev/pts/
cd /opt/rootfs/
tar cvzf ../debian9.9.rootfs.gz .


分享给朋友:

相关文章

使用buildroot构建根文件系统

使用buildroot构建根文件系统

使用buildroot构建根文件系统buildroot可用于构建小型的linux根文件系统。大小最小可低至2M,与内核一起可以放入最小8M的spi flash中。buildroot中可以方便地加入第三方软件包(其实已经内置了很多),省去了手工交叉编译的烦恼。下载安装首先安装一些依赖,比如linux头文件:apt-get install linux-headers-$(uname -r)然后下载安装:wget https://buildroot.org/do...

制作荔枝派Zero开发板(全志V3s) TF/SD卡启动盘

制作荔枝派Zero开发板(全志V3s) TF/SD卡启动盘

0. 前言近几天买了一块荔枝派0开发板,以及官方配的480×272的屏幕。让我记录一下入坑与采坑过程。1. u-boot的编译git clone https://github.com/Lichee-Pi/u-boot -b v3s-current cd u-boot make ARCH=arm LicheePi_Zero_480x272LCD_defconfig make ARCH=arm CROSS...

Linux系统信息查看命令大全

Linux系统信息查看命令大全

最近看了一些Linux命令行的文章,在系统信息查看方面学到不少命令。想起以前写过的一篇其实Linux这样用更简单,发现这些系统信息查看命令也可以总结出一篇小小的东西来了。另外这里还有非常多的命令,可以作为参考。系统# uname -a               # 查看内核/操作系统/CPU信息# head -n 1 /etc/issue   # 查看操作系统版本# cat /proc/cpuinfo  ...

荔枝派nano(f1c100s)的SPI-Flash系统编译创建全过程

荔枝派nano(f1c100s)的SPI-Flash系统编译创建全过程

前言本文的目标是创建一个运行在SPI-Flash上的精简系统,附带填一些前人没有提及的坑。在开始之前,请先通读官方教程的即食部分(U-Boot)、Linux编译和SPI-Flash系统的创建部分的教程,并搭建好编译工具链。以下我假设你已经按照上面的教程下载好了U-Boot和Linux内核,并且到Buildroot的官网下载好了Buildroot(但没按教程创建config文件)。SPI-Flash的分区结构以下是我这里的分区结构。你可以自由的分配后面两个分区的大小。ID  S...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。