Problems with scientific software
Singularity addresses a number of problems encountered with scientific software:
- Often, scientific software has a longer lifetime than the libraries it depends on.
- Scientific software may depend on a large number of libraries which are regularly updated.
- Scientific results may need to be reproduced years later.
For these reasons, it is desirable to be able to freeze an application together with its entire environment in time, so that its functionality is preserved. However, often scientific software is run on supercomputers, where users typically do not have the root rights required to install software (e.g., one can not use `apt-get' in order to install a working set of libraries).
Singularity containers
Singularity allows one to create a virtual Linux operating system (OS) inside of a so-called container (in the form of a file or folder). Inside of the virtual OS we install the necessary libraries, compile our scientific software, and then ship (=copy) the container to a different host system. The only software required on the other machine is Singularity, and perhaps an MPI library in the important case one wants to run parallel applications.
In effect, Singularity swaps the environment of the host system against that of the virtual OS. As a result, applications compiled on the virtual OS are independent of the host system and do not refer to any libraries or applications installed on it, except for Singularity itself (and MPI if applicable).
Basic commands
Only four basic commands are required to use Singularity:
sudo singularity build [container] [recipe/container] singularity shell [container] singularity exec [container] [command] singularity run [container]
Using `build' we create a container based on a recipe (see below) or on an existing container. The `shell' command opens the shell within the virtual OS. Using `exec' we can execute applications installed on the virtual OS. The `run' command allows to use a container as an executable, provided that it defines a runscript telling the container what to do when the `run' command is called on it.
Importantly, a user has the same rights inside of the virtual OS as on the host system. For this reason, creating a container using the `build' command requires root rights (and this is typically done on one's own machine). In contrast, running applications does not usually require root rights and hence the `shell', `exec', and `run' commands can be used without `sudo'.
Recipe files
A recipe file includes the instructions needed by Singularity to build a container. Let us look at the following recipe file `ubuntu_18_04_python.recipe':
BootStrap: docker From: ubuntu:18.04 %post apt-get -y update apt-get -y install wget apt-get -y install python python-dev wget https://bootstrap.pypa.io/pip/2.7/get-pip.py python get-pip.py python -m pip install numpy scipy tk rm get-pip.py
This recipe builds upon an Ubuntu 18.04 OS from the docker repository. It contains only a `%post' section with commands to be posted into the shell of the virtual Ubuntu 18.04 OS. These update the OS and install wget, in order to download files via weblinks. Then Python 2.7, pip, and a couple of Python modules are installed. Notice the use of `apt-get', which requires root rights. It is desirable to use apt-get where possible in order to avoid building from source. The `-y' flag is useful for automatization, so that all prompts are answered with `yes' immediately.
In general, recipe files contain also other sections where, for example, files are copied into the container (%files) or environment variables are defined (%environment). We encounter some cases below.
Building a container
Using the above recipe file, we build a container as follows:
sudo singularity build --sandbox container ubuntu_18_04_python.recipe
We do this as sudo because root rights are required to create the container and for using `apt-get' during the %post section of the recipe file. Due to the `--sandbox' flag Singularity will build the container as a simple folder, which we can open like any other and copy files into it or from it. Use this flag in particular for testing.
After the container has been built, a folder `container/' has appeared, which contains the modified Ubuntu 18.04 OS.
Running applications installed on the virtual OS
We open the shell within this OS and call Python:
singularity shell container/ Singularity> python Python 2.7.17 (default, Nov 28 2022, 18:51:39) [GCC 7.5.0] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>>
As expected, Python 2.7 is available inside the container. Let us exit using `ctrl+D' and invoke Python another time, but from the host system:
singularity exec container/ python Python 2.7.17 (default, Nov 28 2022, 18:51:39) [GCC 7.5.0] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>>
This way, we are able to run applications, installed on the virtual OS, on the host system. Notice that for this the host system only requires the Singularity application. It neither needs to have a Python installation or any of the libraries that Python depends on.
Binding folders of the host system
Let us now use the Python installation on the virtual OS to run a Python script on the host system. Consider the following file `hello_world.py', existing in the folder $PWD of the host system:
cat $PWD/hello_world.py print "Hello world!" singularity exec container/ python hello_world.py Hello world!
This was successful, however, this is surprising. How does the virtual OS in the container know about files inside $PWD of the host system? The answer ist that, by default, singularity binds $PWD to the home folder of the container. We can prevent this as follows:
singularity exec --no-home container/ python hello_world.py /usr/bin/python: can't open file 'hello_world.py': [Errno 2] No such file or directory
Indeed, the virtual OS can not access the file `hello_world.py', because the folder $PWD of the host system that contains it is not known from inside the container. In order to make the binding of folders by Singularity even more explicit, consider the following command:
singularity exec --no-home --bind $PWD:/mnt container/ python /mnt/hello_world.py Hello world!
Again, we prevented Singularity from binding the host folder $PWD to the home folder of the container. Instead, we bind $PWD by hand to the /mnt folder of the container. Hence, inside the container, the file is now known as `/mnt/hello_world.py'.
Developing a sandbox
Keep in mind that from the outset a Singularity container only provides a bare OS. Even the following command fails because so far the `vi' application is not installed inside of the container:
singularity exec container/ vi hello_world.py
Let us install `vi' by hand, which requires root rights:
sudo singularity shell --writable container/ Singularity> apt-get install vim
The flag `--writable' makes changes to the container permanent. In this way, we can test various software installations inside of sandbox containers and converge to the setup we want. However, it would not be good practice to change a sandbox container by hand and then port it to other host systems for practical applications, because the steps we undertook may be hardly reproducible.
Let us therefore document further steps in another recipe file `ubuntu_18_04_python_vi.recipe':
BootStrap: localimage From: container %files hello_world.py /mnt/ %post apt-get -y install vim %runscript python /mnt/hello_world.py
This time, we build from the pre-existing Singularity image called `container/'. In order to make `hello_world.py' permanently known inside of the container, we copy it to its `/mnt' folder. Finally, we define a runscript, which allows us to use our Singularity container like an executable.
Let us build this container, this time as a Singularity image file:
sudo singularity build container.sif ubuntu_18_04_python_vi.recipe
Let us now invoke the runscript of the container, which calls Python on its internal copy of the `hello_world.py' file.
singularity run container.sif Hello world!
WIP: MPI applications
Table of contents