Sunday 27 March 2016

Build Arch Linux base image for Docker

As there is no official Docker Hub build for Arch Linux I thought this would be a good oppertunity to find out how images are created from scratch for OS’s. IMHO its good practice to create your own images to ensure you know exactly the providence of what is installed in your container.
As a starting point I used the mkimage-arch.sh script on the Docker github contrib area.
Script must be run as root
if [ “$(id -u)” != “0” ]; then
printf "This script must be run as root\n" 
exit 1
fi
There are two required packages : expect + pacstrap which can be installed with :
$ pacman -S arch-install-scripts expect
The script checks for this as follows :
hash pacstrap &>/dev/null || {
    printf "Could not find pacstrap. Run pacman -S arch-install-scripts"
    exit 1
}

hash expect &>/dev/null || {
    printf "Could not find expect. Run pacman -S expect"
    exit 1
}
The || is similar to && except it tells the shell only to evaluate the expression after it when the first expression fails.

The hash command affects the way the current shell environment remembers the locations of utilities found. If run without any parameters it shows the path of all commands run since the hash was last reset (hash -r) e.g.
$ hash
hits    command
   1    /usr/bin/git
   1    /usr/bin/vim
   3    /usr/bin/cat
   1    /usr/bin/touch
   1    /usr/bin/mv
   1    /usr/bin/mkdir
   3    /usr/bin/man
  13    /usr/bin/ls
The hash table is a feature of bash that prevents it from having to search $PATH every time you type a command by caching the results in memory. For this use case we use it as a way to test if the command is available.
Next we set the language as UTF-8 :
export LANG="C.UTF-8"
Create a temporary root file system with mktemp :
ROOTFS=$(mktemp -d /tmp/rootfs-archlinux-XXXXXXXXXX)
mktemp creates a temporary directory (or file) based on the template provided to randomize the name. Each X value is replaced with a random string.

Set permissions
chmod 755 "$ROOTFS"
Define the packages to not install for minimal image
PKGIGNORE=(
    cryptsetup
    device-mapper
    dhcpcd
    iproute2
    jfsutils
    linux
    lvm2
    man-db
    man-pages
    mdadm
    nano
    netctl
    openresolv
    pciutils
    pcmciautils
    reiserfsprogs
    s-nail
    systemd-sysvcompat
    usbutils
    vi
    xfsprogs
)
Expanding an array without an index only gives the first element. The $IFS is a special shell variable which stands for Internal Field Separator.
IFS=','
PKGIGNORE="${PKGIGNORE[*]}"
unset IFS
printf "%s""\nPackages not to be installed : $PKGIGNORE\n"
Set pacman.conf to provided conf file
PACMAN_CONF='./arch-docker-pacman.conf'
Define the mirror for pacman :
PACMAN_MIRRORLIST='Server = https://mirrors.kernel.org/archlinux/\$repo/os/\$arch'
Set basic variables to create image
PACMAN_EXTRA_PKGS=''
EXPECT_TIMEOUT=60
ARCH_KEYRING=archlinux
DOCKER_IMAGE_NAME=archlinux
Export pacman mirror
export PACMAN_MIRRORLIST
Use expect to auto reply to pacstrap
expect <<EOF
    set send_slow {1 .1}
    proc send {ignore arg} {
        sleep .1
        exp_send -s -- \$arg
    }
    set timeout $EXPECT_TIMEOUT

     spawn pacstrap -C $PACMAN_CONF -c -d -G -i $ROOTFS base haveged $PACMAN_EXTRA_PKGS --ignore $PKGIGNORE
    expect {
        -exact "anyway? \[Y/n\] " { send -- "n\r"; exp_continue }
        -exact "(default=all): " { send -- "\r"; exp_continue }
        -exact "installation? \[Y/n\]" { send -- "y\r"; exp_continue }
    }
EOF
Remove manual files to save space
arch-chroot "$ROOTFS" /bin/sh -c 'rm -r /usr/share/man/*'
Use haveged to generate random numbers and feed linux random device. You must run pacman-key –init before first using pacman; the local keyring can then be populated with the keys of all official Arch Linux packagers with pacman-key –populate archlinux.
arch-chroot "$ROOTFS" /bin/sh -c "haveged -w 1024; pacman-key --init; pkill haveged; pacman -Rs --noconfirm haveged; pacman-key --populate $ARCH_KEYRING; pkill gpg-agent"
Set local timezone to UTC
arch-chroot "$ROOTFS" /bin/sh -c "ln -s /usr/share/zoneinfo/UTC /etc/localtime"
Set locale to ‘en_US.UTF-8 UTF-8’
echo 'en_US.UTF-8 UTF-8' > "$ROOTFS"/etc/locale.gen
arch-chroot "$ROOTFS" locale-gen
Set pacman mirrorlist
arch-chroot "$ROOTFS" /bin/sh -c "echo $PACMAN_MIRRORLIST > /etc/pacman.d/mirrorlist"
udev is a device manager for the Linux kernel. Udev primarily manages device nodes in the /dev directory and also handles all user space events raised while hardware devices are added into the system or removed from it, including firmware loading as required by certain devices.
udev doesn’t work in containers, rebuild /dev
DEV=$ROOTFS/dev
rm -rf "$DEV"
mkdir -p "$DEV"
mknod -m 666 "$DEV"/null c 1 3
mknod -m 666 "$DEV"/zero c 1 5
mknod -m 666 "$DEV"/random c 1 8
mknod -m 666 "$DEV"/urandom c 1 9
mkdir -m 755 "$DEV"/pts
mkdir -m 1777 "$DEV"/shm
mknod -m 666 "$DEV"/tty c 5 0
mknod -m 600 "$DEV"/console c 5 1
mknod -m 666 "$DEV"/tty0 c 4 0
mknod -m 666 "$DEV"/full c 1 7
mknod -m 600 "$DEV"/initctl p
mknod -m 666 "$DEV"/ptmx c 5 2
ln -sf /proc/self/fd "$DEV"/fd
tar root file system and import image to Docker.
tar --numeric-owner --xattrs --acls -C "$ROOTFS" -c . | docker import - "$DOCKER_IMAGE_NAME"
Options for tar used :
--numeric-owner  Always use numbers for user/group names.
--xattrs         Enable extended attributes support
--acls           Enable the POSIX ACLs support
-C               Change to directory
-c               Create new archive 
Test new image
docker run --rm -t $DOCKER_IMAGE_NAME echo Success
Delete temp root file system
rm -rf "$ROOTFS"
Running the script should finish with
tar: ./etc/pacman.d/gnupg/S.gpg-agent: socket ignored
sha256:82b0356924efb95e5483417dd4f0cef85fce7afbacb75c18632de7bc45edd796
Success
Script available on my docker repository on github.

No comments:

Post a Comment