巧用 Overlay2 或 Bind 快速重構(gòu) ISO 鏡像
筆者之前在字節(jié)跳動的時候是負責 PaaS 容器云平臺的私有化部署相關(guān)的工作,所以經(jīng)常會和一些容器鏡像打交道,對容器鏡像也有一些研究,之前還寫過不少博客文章。比如 深入淺出容器鏡像的一生[1]、overlay2 在打包發(fā)布流水線中的應(yīng)用[2] 等等。
自從換了新工作之后,則開始負責 超融合產(chǎn)品[3] 集群部署相關(guān)工作,因此也會接觸很多鏡像,不過這個鏡像是操作系統(tǒng)的 ISO 鏡像而不是容器鏡像 ??。雖然兩者都統(tǒng)稱為鏡像,但兩者有著本質(zhì)的區(qū)別。
首先兩者構(gòu)建的方式有本質(zhì)的很大的區(qū)別,ISO 鏡像一般使用 mkisofs 或者 genisoimage 等命令將一個包含操作系統(tǒng)安裝所有文件目錄構(gòu)建為一個 ISO 鏡像;而容器鏡像構(gòu)建則是根據(jù) Dockerfile 文件使用相應(yīng)的容器鏡像構(gòu)建工具來一層一層構(gòu)建;
另外 ISO 鏡像掛載后是只讀的,這就意味著如果想要修改 ISO 鏡像中的一個文件(比如 kickstart 文件),則需要先將 ISO 鏡像中的所有內(nèi)容復(fù)制到一個可以讀寫的目錄中,在這個讀寫的目錄中進行修改和重新構(gòu)建 ISO 操作。
╭─root@esxi-debian-devbox ~/build
╰─# mount -o loop CentOS-7-x86_64-Minimal-2009.iso /mnt/iso
mount: /mnt/iso: WARNING: device write-protected, mounted read-only.
╭─root@esxi-debian-devbox ~/build
╰─# touch /mnt/iso/kickstart.cfg
touch: cannot touch '/mnt/iso/kickstart.cfg': Read-only file system
在日常工作中經(jīng)常會對一些已有的 ISO 鏡像進行重新構(gòu)建,重新構(gòu)建 ISO 的效率根據(jù)不同的方式也會有所不同,本文就整理了三種不同重新構(gòu)建 ISO 鏡像的方案供大家參考。
常規(guī)方式
以下是按照 RedHat 官方文檔 WORKING WITH ISO IMAGES[4] 中的操作步驟進行 ISO 重新構(gòu)建。
- 首先我們下載一個 ISO 文件,這里以 CentOS-7-x86_64-Minimal-2009.iso[5] 為例,下載好之后將它掛載到本地 /mnt/iso 目錄下;
╭─root@esxi-debian-devbox ~/build
╰─# mount -o loop CentOS-7-x86_64-Minimal-2009.iso /mnt/iso
mount: /mnt/iso: WARNING: device write-protected, mounted read-only.
- 將 ISO 里的所有文件復(fù)制到另一個目錄
╭─root@esxi-debian-devbox ~/build
╰─# rsync -avrut --force /mnt/iso/ /mnt/build/
- 進入到該目錄下修改或新增文件,然后重新構(gòu)建 ISO 鏡像
# 使用 genisoimage 命令構(gòu)建 ISO 鏡像,在 CentOS 上可以使用 mkisofs 命令,參數(shù)上會有一些差異
╭─root@esxi-debian-devbox ~/build
╰─# genisoimage -U -r -v -T -J -joliet-long -V "CentOS 7 x86_64" -volset "CentOS 7 x86_64" -A "CentOS 7 x86_64" -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -no-emul-boot -o /mnt/CentOS-7-x86_64-Minimal-2009-dev.iso .
Total translation table size: 124658
Total rockridge attributes bytes: 55187
Total directory bytes: 100352
Path table size(bytes): 140
Done with: The File(s) Block(s) 527985
Writing: Ending Padblock Start Block 528101
Done with: Ending Padblock Block(s) 150
Max brk space used a4000
528251 extents written (1031 MB)
# 給 ISO 鏡像生成 md5 校驗
╭─root@esxi-debian-devbox ~/build
╰─# implantisomd5 /mnt/CentOS-7-x86_64-Minimal-2009-dev.iso
Inserting md5sum into iso image...
md5 = 9ddf5277bcb1d8679c367dfa93f9b162
Inserting fragment md5sums into iso image...
fragmd5 = f39e2822ec1ae832a69ae399ea4bd3e891eeb31e9deb9c536f529c15bbeb
frags = 20
Setting supported flag to 0
對于 ISO 鏡像比較小或者該操作不是很頻繁的情況下按照這種方式是最省事兒的,但如果是 ISO 鏡像比較大,或者是在 CI/CD 流水線中頻繁地重新構(gòu)建鏡像,每次都要 cp 復(fù)制原 ISO 鏡像的內(nèi)容確實比較浪費時間。那有沒有一個更加高效的方法呢 ???
經(jīng)過一番摸索,折騰出來兩種可以避免使用 cp 復(fù)制這種占用大量 IO 操作的構(gòu)建方案,可以根據(jù)不同的場景進行選擇。
overlay2
熟悉 docker 鏡像的應(yīng)該都知道鏡像是只讀的,使用鏡像的時候則是通過聯(lián)合掛載的方式將鏡像的每一層 layer 掛載為只讀層,將容器實際運行的目錄掛載為讀寫層,而容器運行期間在讀寫層的所有操作不會影響到鏡像原有的內(nèi)容。容器鏡像掛載的方式使用最多的是 overlay2 技術(shù),在 overlay2 在打包發(fā)布流水線中的應(yīng)用[6] 和 ??深入淺出容器鏡像的一生 中咱曾對它進行過比較深入的研究和使用,對 overlay2 技術(shù)感興趣的可以翻看一下這兩篇博客,本文就不再詳解其中的技術(shù)原理了,只對使用 overlay2 技術(shù)重新構(gòu)建 ISO 鏡像的可行性進行一下分析。
- 首先是創(chuàng)建 overlay2 掛載所需要的幾個目錄
╭─root@esxi-debian-devbox ~
╰─# mkdir -p /mnt/overlay2/{lower,upper,work,merged}
╭─root@esxi-debian-devbox ~
╰─# cd /mnt/overlay2
- 接著將 ISO 鏡像掛載到 overlay2 的只讀層 lower 目錄
╭─root@esxi-debian-devbox /mnt/overlay2
╰─# mount -o loop /root/build/CentOS-7-x86_64-Minimal-2009.iso lower
mount: /mnt/overlay2/lower: WARNING: device write-protected, mounted read-only.
- 使用 mount 命令掛載 overlay2 文件系統(tǒng),掛載點為 merged 目錄
╭─root@esxi-debian-devbox /mnt/overlay2
╰─# mount -t overlay overlay -o lowerdir=lower,upperdir=upper,workdir=work merged
╭─root@esxi-debian-devbox /mnt/overlay2
╰─# cd merged
- 新增一個 kickstart.cfg 文件,然后重新構(gòu)建 ISO 鏡像
╭─root@esxi-debian-devbox /mnt/overlay2/merged
╰─# echo '# this is a kickstart config file' > kickstart.cfg
╭─root@esxi-debian-devbox /mnt/overlay2/merged
╰─# genisoimage -U -r -v -T -J -joliet-long -V "CentOS 7 x86_64" -volset "CentOS 7 x86_64" -A "CentOS 7 x86_64" -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -no-emul-boot -o /mnt/CentOS-7-x86_64-Minimal-2009-dev.iso .
Total translation table size: 124658
Total rockridge attributes bytes: 55187
Total directory bytes: 100352
Path table size(bytes): 140
Done with: The File(s) Block(s) 527985
Writing: Ending Padblock Start Block 528101
Done with: Ending Padblock Block(s) 150
Max brk space used a4000
528251 extents written (1031 MB)
- 掛載新的 ISO 鏡像驗證后發(fā)現(xiàn)確實可行
╭─root@esxi-debian-devbox /mnt/overlay2/merged
╰─# mount -o loop /mnt/CentOS-7-x86_64-Minimal-2009-dev.iso /mnt/newiso
mount: /mnt/newiso: WARNING: device write-protected, mounted read-only.
╭─root@esxi-debian-devbox /mnt/overlay2/merged
╰─# cat /mnt/newiso/kickstart.cfg
# this is a kickstart config file
mount --bind
前面講到了使用 overlay2 的方式避免復(fù)制原鏡像內(nèi)容進行重新構(gòu)建鏡像的方案,但是 overlay2 對于不是很熟悉的人來講還是比較復(fù)雜,光 lowerdir、upperdir、workdir、mergeddir 這四個文件夾的作用和原理就把人直接給整不會了。那么還有沒有更為簡單一點的方式呢?
別說還真有,只不過這種方式的用途比較局限。如果僅僅是用于修改 ISO 中的一個文件或者目錄,可以將該文件或目錄以 bind 掛載的方式將它掛載到 ISO 目錄目錄對應(yīng)的文件上。
原理就是雖然 ISO 目錄本身是只讀的,但它里面的文件和目錄是可以作為一個掛載點的。也就是說我把文件 A 掛載到文件 B,并不是在修改文件 B,這就是 Unix/Linux 文件系統(tǒng)十分奇妙的地方。同樣運用 bind 掛載的還有 docker 的 volume 以及 pod 的 volume 也是運用同樣的原理,以 bind 的方式將宿主機上的目錄或文件掛載到容器運行對應(yīng)的目錄上。對于修改只讀 ISO 里的文件/目錄我們當然也可以這樣做。廢話不多說來實踐驗證一下:
- 首先依舊是將 ISO 鏡像掛載到 /mn/iso 目錄
╭─root@esxi-debian-devbox ~/build
╰─# mount -o loop CentOS-7-x86_64-Minimal-2009.iso /mnt/iso
mount: /mnt/iso: WARNING: device write-protected, mounted read-only.
- 接著創(chuàng)建一個 /mnt/files/ks.cfg 文件,并寫入我們需要的內(nèi)容
╭─root@esxi-debian-devbox ~/build
╰─# mkdir -p /mnt/files
╭─root@esxi-debian-devbox ~/build
╰─# echo '# this is a kickstart config file' > /mnt/files/ks.cfg
- 接著以 mount --bind 的方式掛載新建的文件到 ISO 的 EULA 文件
╭─root@esxi-debian-devbox /mnt/build
╰─# mount --bind /mnt/files/ks.cfg /mnt/iso/EULA
╭─root@esxi-debian-devbox /mnt/build
╰─# cat /mnt/iso/EULA
# this is a kickstart config file
- 可以看到原來 ISO 文件中的 EULA 文件已經(jīng)被成功替換成了我們修改的文件,然后再重新構(gòu)建一下該 ISO 鏡像
╭─root@esxi-debian-devbox /mnt/iso
╰─# genisoimage -U -r -v -T -J -joliet-long -V "CentOS 7 x86_64" -volset "CentOS 7 x86_64" -A "CentOS 7 x86_64" -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -no-emul-boot -o /mnt/CentOS-7-x86_64-Minimal-2009-dev.iso .
- 然后我們再重新掛載新的 ISO 文件驗證一下是否可以
╭─root@esxi-debian-devbox /mnt/iso
╰─# mkdir /mnt/newiso
╭─root@esxi-debian-devbox /mnt/iso
╰─# mount -o loop /mnt/CentOS-7-x86_64-Minimal-2009-dev.iso /mnt/newiso
mount: /mnt/newiso: WARNING: device write-protected, mounted read-only.
╭─root@esxi-debian-devbox /mnt/iso
╰─# cat /mnt/newiso/EULA
# this is a kickstart config file
驗證通過,確實可以!不過這種方式很局限,比較適用于修改單個文件如 kickstart.cfg,如果是要新增文件那還是使用上文提到的 overlay2 的方式更為方便一些。
收獲
雖然 ISO 鏡像和容器鏡像二者有著本質(zhì)的差別,但對于只讀和聯(lián)合掛載的這些特性二者可以相互借鑒滴。
不止如此 overlay2 這種聯(lián)合掛載的特性,還可以用在其他地方。比如我有一個公共的 NFS 共享服務(wù)器,共享著一些目錄,所有人都可以以 root 用戶并以讀寫的權(quán)限進行 NFS 掛載。這種情況下很難保障一些重要的文件和數(shù)據(jù)被誤刪。這時候就可以使用 overlay2 的方式將一些重要的文件數(shù)據(jù)掛載為 overlay2 的 lowerdir 只讀層,保證這些數(shù)據(jù)就如容器鏡像一樣,每次掛載使用的時候都作為一個只讀層。所有的讀寫操作都在 overlay2 的 merged 那一層,不會真正影響到只讀層的內(nèi)容。
草草地水了一篇博客,是不是沒有用的知識又增加了 ??
推薦閱讀
- overlayfs.txt[7]
- Docker 存儲驅(qū)動—Overlay/Overlay2「譯」[8]
- 聊一聊 ISO 9660[9]
- WORKING WITH ISO IMAGES[10]
- overlay2 在打包發(fā)布流水線中的應(yīng)用[11]
- mount 命令之 --bind 掛載參數(shù)[12]