This is an example of simple container orchestration with singularity-compose, It is based on django-nginx-upload.
important if you use Docker on your machine, your iptables are likely edited so you will have an issue running this with the latest version of Singularity. You can either run the simple-example, or install a (not yet released) fixed Singularity version from this branch to have a fully working example. If you don't use Docker, then you are a clean machine and no further action is required.
For a singularity-compose project, it's expected to have a singularity-compose.yml
in the present working directory. You can look at the example
paired with the specification
to understand the fields provided.
Generally, each section in the yaml file corresponds with a container instance to be run, and each container instance is matched to a folder in the present working directory. For example, if I give instruction to build an nginx instance from a Singularity.nginx file, I should have the following in my singularity-compose:
nginx:
build:
context: ./nginx
recipe: Singularity.nginx
...
paired with the following directory structure:
singularity-compose-example
├── nginx
...
│ ├── Singularity.nginx
│ └── uwsgi_params.par
└── singularity-compose.yml
Notice how I also have other dependency files for the nginx container in that folder. As another option, you can just define a container to pull, and it will be pulled to the same folder that is created if it doesn't exist.
nginx:
image: docker://nginx
...
singularity-compose-example
├── nginx (- created if it doesn't exist
│ └── nginx.sif (- named according to the instance
└── singularity-compose.yml
If you don't have it installed, install the latest singularity-compose
$ pip install singularity-composeThe quickest way to start is to first build your containers (you will be asked for sudo):
$ singularity-compose buildand then bring it up!
$ singularity-compose upVerify it's running:
$ singularity-compose ps
INSTANCES NAME PID IMAGE
1 app 10238 app.sif
2 nginx 10432 nginx.sifAnd then look at logs,
$ singularity-compose logs app
$ singularity-compose logs app --tail 30
$ singularity-compose logs nginxshell inside,
$ singularity-compose shell app
$ singularity-compose shell nginxor execute a command!
$ singularity-compose exec app uname -a
$ singularity-compose exec nginx echo "Hello!"When you open your browser to http://127.0.0.1 you should see the upload interface.
If you drop a file in the box (or click to select) we will use the nginx-upload module to send it directly to the server. Cool!
This is just a simple Django application, the database is sqlite3, in the app folder:
$ ls app/
app.sif db.sqlite3 manage.py nginx requirements.txt run_uwsgi.sh Singularity upload uwsgi.iniThe images are stored in images:
$ ls images/
2018-02-20-172617.jpg 40-acos.png _upload And static files are in static.
$ ls static/
admin css jsIf you look at the singularity-compose.yml, we bind these folders to locations in the container where the web server needs write. This is likely a prime different between Singularity and Docker compose - Docker doesn't need binds for write, but rather to reduce isolation. Continue below to read about networking, and see these commands in detail.
When you bring the container up, you'll see generation of an etc.hosts file,
and if you guessed it, this is indeed bound to /etc/hosts in the container.
Let's take a look:
10.22.0.3 app
10.22.0.2 nginx
127.0.0.1 localhost
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allroutersThis file will give each container that you create (in our case, nginx and app)
a name on its local network. Singularity by default creates a bridge for
instance containers, which you can conceptually think of as a router,
This means that, if I were to reference the hostname "app" in a second container,
it would resolve to 10.22.0.3. Singularity compose does this by generating
these addresses before creating the instances, and then assigning them to it.
If you would like to see the full commands that are generated, run the up
with --debug (binds and full paths have been removed to make this easier to read).
$ singularity instance start \
--bind /home/vanessa/Documents/Dropbox/Code/singularity/singularity-compose-simple/etc.hosts:/etc/hosts \
--net --network-args "portmap=80:80/tcp" --network-args "IP=10.22.0.2" \
--hostname app \
--writable-tmpfs app.sif appThe following commands are currently supported.
Build will either build a container recipe, or pull a container to the instance folder. In both cases, it's named after the instance so we can easily tell if we've already built or pulled it. This is typically the first step that you are required to do in order to build or pull your recipes. It ensures reproducibility because we ensure the container binary exists first.
$ singularity-compose buildThe working directory is the parent folder of the singularity-compose.yml file.
Given that you have built your containers with singularity-compose build,
you can create your instances as follows:
$ singularity-compose createIf you want to both build and bring them up, you can use "up." Note that for builds that require sudo, this will still stop and ask you to build with sudo.
$ singularity-compose upYou can list running instances with "ps":
$ singularity-compose ps
INSTANCES NAME PID IMAGE
1 app 6659 app.sif
2 db 6788 db.sif
3 nginx 6543 nginx.sifYou can easily shell inside of a running instance:
$ singularity-compose shell app
Singularity app.sif:~/Documents/Dropbox/Code/singularity/singularity-compose-example> You can easily execute a command to a running instance:
$ singularity-compose exec app ls /
bin
boot
code
dev
environment
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
singularity
srv
sys
tmp
usr
varYou can bring one or more instances down (meaning, stopping them) by doing:
$ singularity-compose down
Stopping (instance:nginx)
Stopping (instance:db)
Stopping (instance:app)To stop a custom set, just specify them:
$ singularity-compose down nginxYou can of course view logs for all instances, or just specific named ones:
$ singularity-compose logs
Fri Jun 21 10:24:40 2019 - WSGI app 0 (mountpoint='') ready in 1 seconds on interpreter 0x55daf463f920 pid: 27 (default app)
Fri Jun 21 10:24:40 2019 - uWSGI running as root, you can use --uid/--gid/--chroot options
Fri Jun 21 10:24:40 2019 - *** WARNING: you are running uWSGI as root !!! (use the --uid flag) ***
Fri Jun 21 10:24:40 2019 - *** uWSGI is running in multiple interpreter mode ***
Fri Jun 21 10:24:40 2019 - spawned uWSGI master process (pid: 27)
Fri Jun 21 10:24:40 2019 - spawned uWSGI worker 1 (pid: 29, cores: 1)
``
### Config
You can load and validate the configuration file (singularity-compose.yml) and
print it to the screen as follows:
```bash
$ singularity-compose config .
{
"version": "1.0",
"instances": {
"nginx": {
"build": {
"context": "./nginx",
"recipe": "Singularity.nginx"
},
"volumes": [
"./nginx.conf:/etc/nginx/conf.d/default.conf:ro",
"./uwsgi_params.par:/etc/nginx/uwsgi_params.par:ro",
".:/code",
"./static:/var/www/static",
"./images:/var/www/images"
],
"volumes_from": [
"app"
],
"ports": [
"80"
]
},
"db": {
"image": "docker://postgres:9.4",
"volumes": [
"db-data:/var/lib/postgresql/data"
]
},
"app": {
"build": {
"context": "./app"
},
"volumes": [
".:/code",
"./static:/var/www/static",
"./images:/var/www/images"
],
"ports": [
"5000:80"
],
"depends_on": [
"nginx"
]
}
}
}
