I was in charge of the PaaS container cloud platform for private deployment related work, so I often deal with some container images, and have some research on container images, and have written many blog articles before.

Since I got a new job, I started to be responsible for “hyper-converged products” cluster deployment related work, so I will also come into contact with a lot of mirrors, but this mirror is the ISO image of the operating system rather than container images 😂. Although both are collectively called mirrors, there are fundamental differences between the two.

First of all, there is a fundamental difference between the two ways of building, ISO images generally use mkisofs or genisoimage and other commands to build a directory containing all the files of the operating system installation as an ISO image; while container image construction is based on the Dockerfile file using the appropriate container image builder to build layer by layer.

In addition, ISO images are read-only when mounted, which means that if you want to modify a file in the ISO image (such as a kickstart file), you need to copy all the contents of the ISO image to a read-write directory first, and then modify and rebuild the ISO in this read-write directory.

1
2
3
4
5
6
╭─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

The efficiency of rebuilding ISO images varies according to different ways, and this article has compiled three different options for rebuilding ISO images for your reference.

General method

Here is the official RedHat documentation WORKING WITH ISO IMAGES for ISO rebuilding.

  • First we download an ISO file, here as CentOS-7-x86_64-Minimal-2009.iso as an example, and after downloading it, mount it in the local /mnt/iso directory.

    1
    2
    3
    
    ╭─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.
    
  • Copy all files from the ISO to another directory

    1
    2
    
    ╭─root@esxi-debian-devbox ~/build
    ╰─# rsync -avrut --force /mnt/iso/ /mnt/build/
    
  • Go to the directory and modify or add files, then rebuild the ISO image

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    # 使用 genisoimage 命令构建 ISO 镜像,在 CentOS 上可以使用 mkisofs 命令,参数上会有一些差异
    ╭─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
    

If the ISO image is small or the operation is not very frequent, this way is the least expensive, but if the ISO image is large or the image is rebuilt frequently in the CI/CD stream, it is really a waste of time to cp the contents of the original ISO image every time. So is there a more efficient way to do it 🤔️

After a bit of poking around, I’ve come up with two build options that avoid the need for cp replication, which takes up a lot of IO operations and can be chosen depending on the scenario.

overlay2

If you are familiar with docker image, you should know that the image is read-only. When you use the image, each layer of the image is mounted as read-only layer by joint mounting, and the directory where the container is actually running is mounted as read-write layer. The most used way of container image mounting is overlay2 technology. This article will not explain in detail the technical principles, only the use of overlay2 technology to reconstruct the feasibility of the ISO image to analyze.

  • The first step is to create a few directories needed for the overlay2 mount

    1
    2
    3
    4
    
    ╭─root@esxi-debian-devbox ~
    ╰─# mkdir -p /mnt/overlay2/{lower,upper,work,merged}
    ╭─root@esxi-debian-devbox ~
    ╰─# cd /mnt/overlay2
    
  • Next, mount the ISO image to the read-only layer lower directory of overlay2

    1
    2
    3
    
    ╭─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.
    
  • Use the mount command to mount the overlay2 file system, with the merged directory as the mount point

    1
    2
    3
    4
    
    ╭─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
    
  • Add a kickstart.cfg file, then rebuild the ISO image

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    ╭─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)
    
  • After mounting the new ISO image and verifying that it does work

    1
    2
    3
    4
    5
    6
    
    ╭─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

We talked about using overlay2 to avoid copying the original image content to reconstruct the image, but overlay2 is still complicated for people who are not very familiar with it, just the role and principle of the four folders lowerdir, upperdir, workdir, mergeddir will not be straightforward. So is there a simpler way?

Don’t say there is, but the use of this way is more limited. If you just want to modify a file or directory in the ISO, you can mount it to the corresponding file in the ISO directory with a bind mount.

The principle is that although the ISO directory itself is read-only, the files and directories within it can be used as a mount point. This means that I am not modifying file B when I mount file A to file B. This is the wonderful thing about the Unix/Linux file system. The same principle is also used in bind mounts for docker’s volume and pod’s volume, where a directory or file on the host is mounted to the directory where the container is running in the same way as bind. We can certainly do the same for modifying files/directories in read-only ISOs. Without further ado, let’s verify this in practice.

  • The first step is still to mount the ISO image to the /mn/iso directory

    1
    2
    3
    
    ╭─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.
    
  • Then create a /mnt/files/ks.cfg file and write the content we need

    1
    2
    3
    4
    
    ╭─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
    
  • Then mount the new file to the ISO EULA file with mount -bind

    1
    2
    3
    4
    5
    
    ╭─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
    
  • You can see that the EULA file in the original ISO file has been successfully replaced with the file we modified, and then rebuild the ISO image

    1
    2
    
    ╭─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 .
    
  • Then we’ll remount the new ISO file to verify that it works

    1
    2
    3
    4
    5
    6
    7
    8
    
    ╭─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
    

Verified, it works! However, this approach is very limited and is more suitable for modifying single files such as kickstart.cfg, if you want to add new files then it is more convenient to use the overlay2 approach mentioned above.

Summary

Although ISO images and container images are fundamentally different, they can learn from each other in terms of read-only and co-mounting features.

Not only that, but the co-mounting feature of overlay2 can be used in other places. For example, I have a public NFS share server that shares some directories, and everyone can do NFS mounts as root with read and write permissions. In this case it is difficult to protect some important files and data from being deleted by mistake. In this case, you can use overlay2 to mount some important files and data as a read-only layer of lowerdir of overlay2 to ensure that these data are used as a read-only layer every time they are mounted, just like container images. All read and write operations are performed on the merged layer of overlay2 and do not really affect the contents of the read-only layer.