Ansible and the Pre-Container Arts

Before containerization made it so easy to prepare images for virtualization, it was quite an art to prepare custom ISO images to boot from CD. Later these images were used to boot virtual machines from. In other words, ISO images were precursors of container images.

It is so that I had a couple of unfortunate run-ins with the Windows Docker client. Even when not running any containers, the Windows memory manager would hand it as much memory as possible slowing whatever I was busy with. I hence banned the Windows Docker client from my machine. 

Please do not get me wrong. I do not hate Docker — just its Windows client.

This step forced me to move back in time. I started running virtual machines directly on Hyper-V, the Windows Hypervisor. Forming Kubernetes clusters on Windows then became a happy hobby for me as can be seen from my past posts published here at DZone.

Shoemaker, Why Do You Go Barefoot?

After following the same mouse clicks to create virtual machines on Hyper-V Manager for many an hour, I realized that I am like a shoemaker who goes barefoot: I build a DevOps pipeline for an hourly rate, but waste time on mouse clicks? 

Challenge accepted. I duckduckgo'd and read that it is possible to create virtual machines using PowerShell. It did not take a week to have a script that creates a new virtual machine as can be seen here. A sister script can start a virtual machine that is turned off.

An Old Art Rediscovered

This was great, but I realized I was still doing mouse clicks when installing Ubuntu. To automate this looked like a tougher nut to crack. One has to unpack an ISO image, manipulate it some way or another, and then package it again taking care to leave whatever instructs a computer how to boot intact.

Fortunately, I found an excellent guide on how to do just this. The process consists of three steps:

  1. Unpack the Ubuntu ISO boot image.
  2. Manipulate the content:
    1. Move the master boot record (MBR) out.
    2. Specify what users normally do on the GUI and customize what is installed and run during installation. This is done using a subset of Ubuntu's Cloud-init language. See here for the instructions I created.
    3. Instruct the bootloader (Grub, in this case) where to find the custom boot instructions and to not wait for user input. Here is the Grub config I settled on.
  3. Package it all using an application called Xorriso. 

For the wizards of this ancient craft, Xorriso serves as their magic wand. It has pages of documentation in something that resembles a spell book. I will have to dirty my hands to understand fully, but my current (and most likely faulty) understanding is that it creates Boot partitions, loads the MBR copied out, and does something with the Cloud-init-like instructions to create an amended ISO image.

Ansible for the Finishing Touches

Although it was with great satisfaction that I managed to boot Ubuntu22 from PowerShell without any further input from me, what about the next time when Ubuntu brings out a new version? True DevOps mandates to document the process not in ASCII, but in some script ready to run when needed. Ansible shows its versatility in that I managed to do just this in an afternoon. The secret is to instruct Ansible that it is a local action. In other words, do not use SSH to target a machine to receive instruction, but the Ansible Controller is also the student:

YAML
 
- hosts: localhost
  connection: local


The full play is given next and provides another view of what was explained above:

YAML
 
# stamp_images.yml

- hosts: localhost
  connection: local
  become: true
  vars_prompt:
    - name: "base_iso_location"
      prompt: "Enter the path to the base image"
      private: no
      default: /tmp/ubuntu-22.04.4-live-server-amd64.iso

  tasks:
    - name: Install 7Zip
      ansible.builtin.apt:
        name: p7zip-full
        state: present

    - name: Install Xorriso
      ansible.builtin.apt:
        name: xorriso
        state: present

    - name: Unpack ISO
      ansible.builtin.command: 
        cmd: "7z -y x {{ base_iso_location }} -o/tmp/source-files"

    - name: Copy boot partitions
      ansible.builtin.copy:
        src: /tmp/source-files/[BOOT]/
        dest: /tmp/BOOT
  
    - name: Delete working boot partitions
      ansible.builtin.file:
        path:  /tmp/source-files/[BOOT]
        state: absent
  
    - name: Copy files for Ubuntu Bare
      ansible.builtin.copy:
        src: bare/source-files/bare_ubuntu
        dest: /tmp/source-files/

    - name: Copy boot config for Ubuntu bare
      ansible.builtin.copy:
        src: bare/source-files/boot/grub/grub.cfg
        dest: /tmp/source-files/boot/grub/grub.cfg

    - name: Stamp bare image
      ansible.builtin.command: 
        cmd: xorriso -as mkisofs -r   -V 'Ubuntu 22.04 LTS AUTO (EFIBIOS)'   -o ../ubuntu-22.04-wormhole-autoinstall-bare_V5_1.iso   --grub2-mbr ../BOOT/1-Boot-NoEmul.img -partition_offset 16   --mbr-force-bootable   -append_partition 2 28732ac11ff8d211ba4b00a0c93ec93b ../BOOT/2-Boot-NoEmul.img   -appended_part_as_gpt   -iso_mbr_part_type a2a0d0ebe5b9334487c068b6b72699c7   -c '/boot.catalog'   -b '/boot/grub/i386-pc/eltorito.img'     -no-emul-boot -boot-load-size 4 -boot-info-table --grub2-boot-info   -eltorito-alt-boot   -e '--interval:appended_partition_2:::'   -no-emul-boot .
        chdir: /tmp/source-files

    - name: Copy files for Ubuntu Atomika
      ansible.builtin.copy:
        src: atomika/source-files/atomika_ubuntu
        dest: /tmp/source-files/

    - name: Copy boot config for Ubuntu Atomika
      ansible.builtin.copy:
        src: atomika/source-files/boot/grub/grub.cfg
        dest: /tmp/source-files/boot/grub/grub.cfg

    - name: Stamp Atomika image
      ansible.builtin.command:
        cmd: xorriso -as mkisofs -r   -V 'Ubuntu 22.04 LTS AUTO (EFIBIOS)'   -o ../ubuntu-22.04-wormhole-autoinstall-atomika_V5_1.iso   --grub2-mbr ../BOOT/1-Boot-NoEmul.img -partition_offset 16   --mbr-force-bootable -append_partition 2 28732ac11ff8d211ba4b00a0c93ec93b ../BOOT/2-Boot-NoEmul.img   -appended_part_as_gpt   -iso_mbr_part_type a2a0d0ebe5b9334487c068b6b72699c7   -c '/boot.catalog'   -b '/boot/grub/i386-pc/eltorito.img'     -no-emul-boot -boot-load-size 4 -boot-info-table --grub2-boot-info   -eltorito-alt-boot   -e '--interval:appended_partition_2:::'   -no-emul-boot .
        chdir: /tmp/source-files        


Note the magic of the Xorriso command used here to prepare two images: one with support and one without support for Kubernetes.

The only caveat is to have a machine installed with Ansible to run this play from. The output from the above play can be downloaded from here and pre-install a very recent version of Ansible.

Conclusion

This post went retro, but it is important to revisit where things started to gain an understanding of why things are the way they are. Windows and containers, furthermore, do not mix that well and any investigation into ways to make the days of developers better should be welcomed. 

I referred to part of the code, but the full project can be viewed on GitHub.

 

 

 

 

Top