Apptainer (a.k.a. Singularity)
Note
This documentation is a work in progress. Any comment or suggestion is welcome in sinfin@iac.es.
To stay informed about updates to "Apptainer", tips, etc., or to ask any questions regarding its use, please use the #computing/hpc channel in IAC-zulip.
From Introduction to Apptainer:
Apptainer is a container platform. It allows you to create and run containers that package up pieces of software in a way that is portable and reproducible. You can build a container using Apptainer on your laptop, and then run it on many of the largest HPC clusters in the world, local university or company clusters, a single server, in the cloud, or on a workstation down the hall. Your container is a single file, and you don’t have to worry about how to install all the software you need on each different operating system.
Here we only present a very brief introduction and some minimal examples to get you started with Apptainer at the IAC. For more detailed information, please visit the Apptainer webpage and refer to the official documentation.
Note
One important feature of Apptainer is that all our actions (from creating to running a container) can be performed with an unprivileged account, so you can run all the examples below with your regular IAC account.
Quick start
Attention
If you are using a "burro" with Slurm (all the public "burros" and some of the project ones), this will only work from within a Slurm session (either batch or interactive). More information about the use of the "burros" can be found here.
Note
This is an abridged version of the Quick Start page, shortened and modified to work with the IAC "burros".
At the IAC computers, the latest Apptainer version available at the time of writing is 1.4.0 and can be used via Environment Modules.
$ module load apptainer
Loaded environment for Apptainer version 1.4.0.
$ apptainer help
Linux container platform optimized for High Performance Computing (HPC) and
Enterprise Performance Computing (EPC)
[...]
Let's motivate the remaining of this section by assuming that, for some reason, we need to run version 0.5 of the Gnuastro package (https://www.gnu.org/software/gnuastro/). If we look at the Gnuastro versions page, we can see that this version was available in Ubuntu 18.04, so below we will show how to build and run a container with these versions.
There are different ways to create a container image, but often the most convenient way is to start with a basic image and then customize it with the software we need. So, let's start by downloading a pre-built Ubuntu 18.04 image. For this we can use Docker Hub.
Note
When downloading pre-built images proceed with caution and only do so from trusted providers. For example, below we use an image tagged as "Docker Official Image", which is built by the Docker Hub team.
In Docker Hub, we can find many pre-built Official Ubuntu images. Apptainer can download and convert these images to the required format with a single command:
$ apptainer pull docker://ubuntu:18.04
INFO: Converting OCI blobs to SIF format
INFO: Starting build...
[...]
INFO: Creating SIF file...
In the directory where you run the previous command you should now have a
ubuntu_18.04.sif
file, which is the created container. We can verify that it
indeed contains a Ubuntu 18.04 distribution with the apptainer exec
command:
$ apptainer exec ubuntu_18.04.sif cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.6 LTS (Bionic Beaver)"
ID=ubuntu
[...]
In the example above we just run a command within the container, but we can also run a shell within the container and interact with it as if it were a virtual machine.
$ apptainer shell ubuntu_18.04.sif
Apptainer> cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.6 LTS (Bionic Beaver)"
[...]
Apptainer> which astfits
Apptainer> exit
$
Creating a customized container
As you can see in the example above, the pre-built image does not come with
Gnuastro installed, so the astfits
command is not found in the
container. Thus, we will have to somehow modify the previous container or create
a new one with the required packages installed. There are various ways to do
this, but perhaps the simplest one is to follow these steps.
Create a sandbox container based on the pre-built image
A sandbox container is created as a directory instead of as a .sif
file, and
we just create it with the following command (after running it you will see that
a custom_container
directory has been created. You can obviously choose any
name you want).
$ apptainer build --sandbox custom_container docker://ubuntu:18.04
Modify the container sandbox interactively
Then we can run a shell within the container, but this time we will do it with
the option -f --writable
, which will allow us to modify the container sandbox
as if we were the root
user (so that we can use administrative commands like
apt-get
to install software). As we can see below, we have no problem with
installing and running Gnuastro this time.
$ apptainer shell -f --writable custom_container/
INFO: User not listed in /etc/subuid, trying root-mapped namespace
INFO: Using fakeroot command combined with root-mapped namespace
WARNING: Skipping mount /etc/localtime [binds]: /etc/localtime doesn't exist in container
Apptainer> apt-get update
[...]
Apptainer> apt-get install gnuastro
[...]
Apptainer> astfits --version
Fits (GNU Astronomy Utilities) 0.5
At this point we already have a customized container with Gnuastro installed, but not in a very convenient format. We could use Apptainer to convert the sandbox container to a ".sif" one, but this is not ideal either since if we did that, we would have a working container, but the information on how we created it would get lost. For reproducibility we show below how to rebuild this container via a "Definition File".
Rebuild the customized container with a Definition File
Being able to interactively modify the container as we did above is very useful when creating a new container to quickly find missing dependencies, check things are working correctly, etc., but once we have verified that the container is working as expected, it is extremely useful to write the necessary steps as a Definition File.
Tip
For anything not trivial, don't wait until you have a working container to write the Definition File. Instead incrementally write the file as you interact with the sandbox container, so that you don't forget any required step.
Definition files have a number of sections that are useful for more complex scenarios (see the official documentation), but in this case the definition file can be as simple as:
bootstrap: docker
from: ubuntu:18.04
%post
:
: ============================ :
: CREATING Gnuastro CONTAINER :
: ============================ :
apt-get update
apt-get -y install gnuastro
# To avoid the WARNING message about /etc/localtime
touch /etc/localtime
Assuming we named the above file as custom.def
, we can now rebuild and test our
customized container with:
$ apptainer build custom.sif custom.def
[...]
INFO: Creating SIF file...
INFO: Build complete: custom.sif
$ apptainer exec custom.sif astfits --version
Fits (GNU Astronomy Utilities) 0.5
Copyright (C) 2015-2017, Free Software Foundation, Inc.
[...]
After we verify that the rebuilt container (named here custom.sif
) does indeed
work as expected, we can safely remove the whole sandbox directory created
earlier. If we want, we can also safely rename the SIF file and/or move it to
any other location.
If you want to rebuild the container in the future (or use it to build the container by somebody else or in some other machine), make sure to safely keep the ".def" file.
Using the applications inside the container
Other container technologies focus on isolation, but Apptainer favours
integration over isolation, which means that a user is allowed to easily use
host resources. For our example this means that the directory where we run the
apptainer
command is automatically shared with the container (if we need to
mount other directories we can use the -B
option of the apptainer
exec command).
Thus, we can easily use the application inside the container, but using input
and output files outside it. In the example below we use the astcrop
command
from the container (note that the SIF file does not need to be in the directory
where we are calling the apptainer
command), taking input data from the
download
directory and writing output data to the flat-ir
directory (both
relative to the directory where we are running the command).
$ apptainer exec ../custom.sif astcrop --mode=wcs -h0 \
--output=flat-ir/xdf-f105w.fits \
--polygon="53.187414,-27.779152 : 53.159507,-27.759633 : \
53.134517,-27.787144 : 53.161906,-27.807208" \
download/hlsp_xdf_hst_wfc3ir-60mas_hudf_f105w_v1_sci.fits
Crop started on Sat Dec 23 17:20:42 2023
- Read metadata of 1 dataset. 0.021913 seconds
---- flat-ir/xdf-f105w.fits created: 1 input.
Crop finished in: 1.021145 seconds
Use cases
Running Python Code Across Different Systems Using Apptainer
Python virtual environments are widely used to manage dependencies for projects and they work great in your laptop or workstation. However, if you need to run the same project code in another machine (for example in one of the IAC "burros" or in the LaPalma supercomputer; or even submit hundreds of runs with HTCondor), these environments can be difficult to replicate due to: compatibility issues, dependency conflicts and lack of administrative privileges to install software.
In these cases Apptainer provides a simple and efficient solution to ensure the
reproducibility of your code across different machines. As a basic example,
let's imagine that we want to explore the famous Iris dataset for evaluating
classification methods, and that we have developed our code using Python 3.8
and the packages requirements are given in the following requirements.txt
file:
numpy==1.24.2
pandas==1.5.3
scikit-learn==1.2.2
Then, we can create the following iris.def
file that will be used to create a
container with the given Python version and requirement packages:
Bootstrap: docker
From: python:3.8-slim
%files
$PWD/requirements.txt requirements.txt
%post
pip install --no-cache-dir --root-user-action=ignore -r requirements.txt
# Clean up
pip cache purge
apt-get clean
rm -rf /var/lib/apt/lists/*
rm -rf /usr/share/doc /usr/share/man /usr/share/info
Creating the container is as simple as:
$ apptainer build iris.sif iris.def
and we have the full container with all the dependencies we need to run our code in a single file of just 130MB (obviously the container size will be different depending on the packages that you need to install).
This .sif
file is all you need to copy to another computer in order to have
your full environment, which you can then use to run your project code. So, for
example, if your code is written in file iris.py
, you could run this script
(but using the environment in the iris.sif
container, not the one in the
host), with the apptainer run
command. We illustrate this below for three
different systems (a laptop with Manjaro Linux; an IAC "burro" with Ubuntu
22.04; and LaPalma Supercomputer with SLES12.2), emphasizing again that the
iris.sif
file is the same file:
angelv@siehp:$ head -n 1 /etc/os-release
NAME="Manjaro Linux"
angelv@siehp:$ apptainer run iris.sif python iris.py
Dataset Overview:
Total samples: 150
Features: sepal length (cm), sepal width (cm), petal length (cm), petal width (cm)
Target classes: setosa, versicolor, virginica
[...]
(sinter) [angelv@deimos iris]$ head -n 1 /etc/os-release
PRETTY_NAME="Ubuntu 22.04.4 LTS"
(sinter) [angelv@deimos iris]$ apptainer run iris.sif python iris.py
Dataset Overview:
Total samples: 150
Features: sepal length (cm), sepal width (cm), petal length (cm), petal width (cm)
Target classes: setosa, versicolor, virginica
[...]
can01003@login1:/storage/scratch/can01/can01003/iris> head -n 1 /etc/os-release
NAME="SLES"
can01003@login1:/storage/scratch/can01/can01003/iris> apptainer run iris.sif python iris.py
[...]
Dataset Overview:
Total samples: 150
Features: sepal length (cm), sepal width (cm), petal length (cm), petal width (cm)
Target classes: setosa, versicolor, virginica
[...]