基於 debootstrap 和 busybox 構建 mini ubuntu


基於 debootstrap 和 busybox 構建 mini ubuntu

最近的工作涉及到服務器自動安裝和網絡部署操作系統,然后使用 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 工具做這樣的小系統會更加簡單,有以下的優點:

  1. debootstrap 生成的小 ubuntu 能方便使用 apt 安裝額外的工具
  2. 可直接把驅動模塊拷貝到小鏡像內使用
  3. 定制腳本非常簡單容易

整個小系統在不安裝額外的軟件和內核模塊的情況下,為 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 out
if ! 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 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"

# 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

exec /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 $i
done

注意,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)"
done
extra_apps=
for i in $applets; do
    if ! sudo chroot mini which $i > /dev/null; then
        extra_apps="$extra_apps /bin/$i "
    fi
done

function 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/sh
mount -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 /run
echo "Loading modules..."
# hotplug with mdev
echo /bin/mdev > /proc/sys/kernel/hotplug
mdev -s
# coldplug: load devices supporting modules
find /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 -qa
exec /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/init
tty1::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    arch
1.4M    crypto
11M     drivers
8.1M    fs
1.8M    lib
12M     net
832K    sound
508K    ubuntu
20K     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 /mnt
mkdir -p /mnt/mnt
exec switch_root /mnt /sbin/init

需要注意的是,由於根分區切換, /proc /sys /dev /tmp 等目錄需要進行額外的處理,要么把該目錄用 mount -o move 到 mnt 下,或者在 mini/fstab 文件中定義掛載點。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM