An image meant to build without network access must contain every
package build checks for, and that list differs by distribution, version
and architecture and changes between releases. build --containerfile
writes a Containerfile, or with --apptainer an Apptainer definition
file, generated from the same package lists, including -p, -a and
--selinux, so it cannot drift from what build expects. The OS is that of
the -s distribution or of the build system, as for a build. --arch names
the matching build platform and --from replaces the default base image.
-r is refused, the image installs from the repositories of its base
image.
The generated EL file installs full coreutils instead of the
coreutils-single of container bases, and the Ubuntu file cleans the apt
cache.
For sites that already build images elsewhere, for example in CI from a
Dockerfile, imgutil build -s also takes a container image: a SIF file,
an OCI or Docker archive, an OCI layout directory, or a podman reference
with its transport, such as docker:// or containers-storage:. Files and
directories are recognized by their content, and any other source is a
distribution as before. The image is extracted into the scratch
directory, with podman or, for a SIF file, from its squashfs partition.
It is then completed the way build does: the confluent initramfs
configuration, the default package list with the package manager and
repositories of the image, and an initramfs for every kernel. The result
works with exec and pack like any built tree, and the build info names
the image as its source.
When the image already has every package of the list, the package
manager is not run, so an image prepared in CI builds without network
access. Missing packages are named before installing them. The
architecture comes from the image, and a requested --arch is checked
before extracting it. -r is refused because the image brings its own
repositories. With --arch, the matching variant of an image for several
architectures is used. Other than a local image, podman pulls or loads
the image into a temporary storage that is removed afterwards, so the
images and tags of the system stay as they are.
Container base images are not bootable systems, so the build also
unmasks the units they mask (systemd-remount-fs, getty.target,
systemd-logind and others) and removes policy-rc.d, reporting each
change, drops the boot entries kernel-install writes (they carry the
command line of the build system, installing to disk writes its own),
and with --selinux labels the tree with full contexts. Ubuntu images get
ubuntu-minimal, which debootstrap installs but container bases lack.
EL9, Ubuntu 24.04 and SUSE 16 or later images are supported, as long as
imgutil has a package list for the release. Older releases and
architectures other than x86_64 and aarch64 are refused.
Without pipelining every task copies the module to the node over sftp,
then makes it executable, runs it and removes it, each a separate ssh
operation. With pipelining the module is fed to the interpreter over
the ssh session, so a task takes one operation and nothing is written
to the node's temporary directory.
Pipelining breaks become through sudo with requiretty, which rarely
applies here: plays connect as root and drop become unless become_user
is set. Setting ANSIBLE_PIPELINING or ANSIBLE_SSH_PIPELINING still
overrides this default.
Setting ANSIBLE_SSH_ARGS replaces ansible's ssh_args instead of adding
to them, so plays ran without the default ControlMaster and
ControlPersist options. Every task then opened a new ssh connection
and authenticated again.
Start from ansible's default ssh_args when ANSIBLE_SSH_ARGS is not
set. An ANSIBLE_SSH_ARGS from the environment is still extended as
before.
The default 'max' cpu of qemu-user emulates SVE and pointer
authentication, which aarch64 binaries use and which is expensive to
emulate. Default QEMU_CPU to neoverse-n1 for aarch64 builds and exec,
unless it is already set. neoverse-n1 needs qemu 7.1 or later, the
documentation says how to choose another cpu.
Building an EL10 image with mlnx-ofa_kernel-dkms and doca-ofed-userspace
on an EL10 x86_64 host with qemu 10.2:
DOCA build Total
aarch64, master 3270 s (54.5 min)
aarch64, branch 2399 s (40 min), −27%
x86_64, native 251 s (4 min)
Older qemu-user-static, such as qemu 8.2 of Ubuntu 24.04, emulates
pointer authentication much more slowly, so builds there can get up to
ten times faster.
suse does not allow group readable ssh keys, so
keysign must be setuid root for host based authentication.
Also, chkstat got renamed, try to use the new name if possible.
An empty machine-id in the image only works with deployment scripts that
set one per node. With older ones, which ship separately from imgutil,
systemd made up a new machine-id on every diskless boot and a disk install
got a second set of boot entries. The deployment now replaces whatever
machine-id the image carries, so leave it in the image.
Captured images carried the machine-id of the source system, and images
built with imgutil carry the one generated in the build root, so every
node deployed from a profile shared it. That breaks anything keyed on it,
such as DHCP DUIDs and journal identities.
Capture now stores an empty /etc/machine-id. Diskless boot on EL and SUSE
writes a stable per-node one derived from the node name and the DMI
product UUID, as Ubuntu already does, using openssl since the initramfs
has no sha512sum. Install to disk copies that machine-id into the target
and renames the boot loader entries from the image to it. Otherwise
kernel-install in post.sh adds a second set of entries under another ID.
Plays run by the deployment server get the target node's attributes in
the host variable 'confluent', nested at each dot of the name, e.g.
confluent['net']['ib0']['ipv4_address']. Secrets and unset attributes
are left out, and values are never templated.
Newer ansible-core only takes a stdout callback name and no longer
templates untrusted YAML. Attach the results collector directly, load
plays trusted through the DataLoader, and report a crashed play
process as a failed task.
A diskless boot of a captured profile only gets the profile's kernelargs,
not the kernel arguments of the source. Sources relying on some, such as
net.ifnames=0 for their interface names, then came up with different
interface names than the source and an install to disk of the same
image. Say so in the generated profile.yaml and after the capture, and
end the file with a newline so appended settings do not run into the
kernelargs line.
post.sh wrote installedargs into /etc/default/grub but only ran
update-grub when it had also added a console, so grub.cfg kept the old
command line until some later grub update.
Capture copies an initramfs-tools hook and conf.d/confluent.conf into the
source to build the deployment initramfs, but only ever removed the EL
dracut module afterwards. With MODULES=list left in place, the source's
next initramfs update was limited to the hook's driver list, and a source
captured again carried both files into the image, where rebuilding the
initramfs on install to disk shrank the clones' initramfs the same way.
Remove them after capture and leave them out of the image.
post.sh took the whole installedargs line from profile.yaml, key
included, so every Ubuntu install from these profiles got a literal
'installedargs:' in GRUB_CMDLINE_LINUX.
The installed system kept the initramfs of the image. Ubuntu 26.04
builds it with dracut in host-only mode, so a captured image's initramfs
waits for the source's volume group and filesystem UUIDs and the
installed node ends in emergency mode. Rebuild it in post.sh, as EL does
through kernel-install.
Dropping every NetworkManager profile and ifcfg file also removed
settings that confignet does not recreate from confluent attributes,
such as bonds, DHCP profiles, MTU or the InfiniBand transport mode. Only
drop the profiles that carry static addresses of the source.
dhcpcd, present on Ubuntu 24.04 and later and on EL10, keeps its leases,
DUID and secret in /var/lib/dhcpcd, and NetworkManager keeps a secret in
secret_key. Copied into an image, every clone would use the same DHCPv6
DUID and derive the same stable privacy IPv6 addresses. Both regenerate
them when missing.
dhclient keeps its leases in /var/lib/dhcp there, and on EL also as
*.leases next to the *.lease files already excluded. Keep dhcpd.leases
of a DHCP server, which lives in the same directory.
The image kept /etc/hostname of the source host, so a diskless node ran
under that name until onboot set its own, and early journal and syslog
entries carried the wrong name. With an empty /etc/hostname systemd
keeps the node name the deployment initramfs already set. Install to
disk writes /etc/hostname itself.
Captured images kept the NetworkManager connection profiles and ifcfg
files of the source host. A diskless boot kept them too, and confignet
only rewrites the profiles named like the interfaces it configures, so
every other interface came up with the source host's settings, such as
its static addresses. Install to disk already removes them, and capture
already masks netplan files for the same reason. Drop them from the
image, except ifcfg-lo.