Slingring is a tool to automate the creation and usage of software development containers. Its main objective is to provide stable and reproducible environments for developers with a minimum amount of effort. It relies on existing tools like debootstrap, schroot, and Ansible to offer a rock solid foundation while minimizing the learning curve.
Develop your software inside a flexible linux container.
-
Use
seed create foo
to bootstrap an instruction set for a project called "foo" in an accordingly named folder -
Use
universe install foo/
to install the container defined in the instruction set (seed) in the folder "foo" -
Use
slingring foo
to enter your container
Like Slingring itself, this documentation is still under heavy development. If you don’t find some information you need, it is most likely a bug. You can help the development of Slingring by opening a new ticket on GitHub.
Development containers are isolated environments for software development projects. Some development eco systems provide something similar, e.g. virtualenv for Python or RVM for Ruby. But instead of those virtual environments, development containers are more like Docker containers. They contain a small but complete Linux user land and share basic resources (like the kernel) with the host system.
Developing software within containers comes with some decent benefits:
-
The tool chain is easily reproducible
-
No more package conflicts
-
No more multilib systems
-
Minimized setup time for new team members
So why not use Docker? While application containers share a lot of characteristics with development containers, they also have some fundamental differences. Application containers are designed to run idealy stateless applications in immutable containers. Restarting a container usually means resetting their state.
There is no need for this behaviour in development containers. Writing software is a continual process of change. Imagine your development machine being reset back to yesterdays contents every morning.
Aside from that, application containers are a lot more isolated than development containers. Application containers are designed for horizontal scalability. A single host may run a multitude of the same container’s instances at the same time. Application containers must therefore not expose their ports unless specifically configured to do so. The same development container is usually running only once at a time, so there is rarely the need to prevent port collissions in development containers. On the contrary, IDEs, editors, and alike should run on the host systems X-server. And if we actually run two development containers at the same time, it is likely that we want them to communicate.
Development containers are designed to suit these requirements.
You can install Slingring using the provided packages or build your own packages using fpm.
slingring 0.8.3 RPM (tested on Fedora 26)
schroot-process-check 1.1 RPM (64 bit) (tested on Fedora 26)
Install using $ sudo dnf install slingring-0.8.3-1.noarch.rpm schroot-process-check-1.1-1.x86_64.rpm
slingring 0.8.3 Deb (tested on Ubuntu 16.04 Xenial)
schroot-process-check 1.1 Deb (64 bit) (tested on Ubuntu 16.04 Xenial)
Install using $ sudo apt install slingring_0.8.3_all.deb schroot-process-check_1.1_amd64.deb
slingring 0.8.3 Pacman Package (tested on Arch Linux)
schroot-process-check 1.1 Pacman Package (64 bit) (tested on Arch Linux)
Install using $ sudo pacman -U slingring-0.8.3-1-any.pkg.tar.xz schroot-process-check-1.1-1-x86_64.pkg.tar.xz
You must have fpm installed on your system. Make sure you have the packages necessary for creating rpm/deb/pacman packages installed. That might include rpm build tools and bsdtar.
It is then sufficient to run ./package.sh
in the repository’s root directory.
You might have to do the same for schroot-process-check, which will also need the golang compiler installed on the build system. This might also be your best bet if you want a 32-bit binary.
The following packages have to be present on the target system:
-
Python >= 3.5
-
PyYAML (Python 3)
-
Jinja2 (Python 3)
-
Debootstrap
-
Schroot
-
Ansible
-
GnuPG
-
Figlet
Also, you need to have schroot-process-check installed. Since this is not present in any of the official repositories, you can use the packages provided above, or create your own package using fpm.
Slingring defines three simple terms to illustrate its components:
-
Universe - the development container (like a docker container)
-
Seed - the instruction set needed to create a development container (like a dockerfile)
-
Portal - a terminal connection inside the container (like an ssh connection into the container).
A seed contains all instructions needed to create the development container. This might include the packages of your tool chain, the location of the repositories, or even database configurations. Most of these instructions are defined using Ansible. Ansible is an IT automation tool which runs so-called playbooks on machines to ensure a given state. It is assumed that you have basic knowledge of Ansible. If you don’t, it is strongly recommended that you head over there first and familiarize yourself with Ansible playbooks.
To create a new seed run seed create seed-name
.
This will create a new folder called seed-name, containing an already bootstrapable seed.
Let’s take a look at the contents of this folder.
The most important part is a file called universe.yml
.
It contains the following information:
-
The universe name
-
The universe version
-
The universe architecture
-
The universe distribution
-
A list of the information needed for the Ansible playbook
-
Further small configuration details.
If you open the file, you’ll find a detailed description of every configuration parameter above it. It is pre-filled with sensible defaults, but you are free to adjust the values to your liking.
The sub-directory initializer
contains shell scripts which are used to prepare the container for the Ansible playbook.
Those scripts are run inside the container in alphabetical order and perform basic setup tasks which cannot be done by the Ansible playbook.
The scripts run with administrative rights.
For example, Ansible requires Python 2.7 to be present in the container.
In the default template, there is an initializer script in place which takes care of that.
There are some environment variables in place, which might be of help:
Variable | Content |
---|---|
SLINGRING_USER_NAME |
The name of the user executing the universe command. |
SLINGRING_GROUP_NAME |
The default group of the user executing the universe command. |
In most cases, you should not have to adjust anything in the initializer directory.
The sub-directory ansible
contains an Ansible playbook and an arbitrary number of Ansible roles.
You can configure the playbook in this directory to your liking.
A typical playbook will ensure that the needed packages (e.g. IDEs, editors, compilers, databases etc.) are installed in their desired version and all needed configurations are in place. You could, for example, desire a specific version of the JDK, while you always want the latest version of the IDE. If the playbook is re-run on the container at a later point in time, the IDE might then be updated to a newer version, while the JDK is left as it is.
In addition to the usual Ansible facts, it is possible to gather information from the user which might be needed for the playbook. You might, for example, check out a git repository which requires username/password credentials.
You can define those variables within the universe file like that:
variables:
- name: git_username
description: The git-username for the foo repository
- name: git_password
description: The git-password for the foo repository
secret: yes
While the universe is being bootstrapped, the user will be queried for the information using the given description.
The secret flag indicates that the entered information should not be echoed. It is also handed over to Ansible in an encrypted container (vault).
Inside the Ansible playbook, you can reference this information using {{ user_vars.variable_name }}
and {{ user_secrets.secret_name }}
.
In the above example this would be {{ user_vars.git_username }}
and {{ user_secrets.git_password }}
.
The universe command will ensure that these variables are defined when the universe is bootstrapped.
Seeds are created from templates.
Slingring comes with a basic default template, which describes a very basic empty Ubuntu LTS container.
It is stored in /usr/share/slingring/templates/default
.
You can use the default template as a starting point for your own templates.
The default place to put custom templates is ~/.slingring/templates/template-name
.
There are a number of variables available in templates:
Variable | Content |
---|---|
bootstrap.universe_name |
The universe name specified by the user when creating the seed. |
bootstrap.universe_version |
An auto-generated version in the scheme |
bootstrap.ascii_art.universe_name |
A nice ascii art version of the universe name (created by figlet using the "slant" font). |
Bootstrap variables have to be specified using arrow brackets (e.g. << bootstrap.universe_name >>
).
Since the seed might contain any kind of file (also blobs), not all files are searched for replaceable expressions by default.
Instead, there is a file called template.yml
in the root directory of the template.
In this file, you can define a template filter and a template blacklist.
The template filter is a list of files which will be processed while bootstrapping the universe description. The usual glob wildcards are supported. Double asterisks (**) can be used for recursive matching. Be careful when adding wildcards like *. Processing included binary files may take a long time even though they contain no variables to substitute.
Example:
template_filter: - '**/*.yml' - '**/*.j2'
Using the blacklist, you can define files which will not be processed, even though they match one or more of the above defined filters. The same glob wildcards are supported.
Example:
template_blacklist: - 'ansible/example.yml' - '**/templates/*.yml'
If no template.yml file can be found, expression substitution will be disabled while creating a seed.
There is still little to no support for checking the validity of templates, so double check your templates before publishing them.
You can use the seed list
command to see a list of templates available on your system.
If you want to create a seed from a specific template, you can do it like seed -t template-name seed-name
.
You can get a list of all installed universes using universe list
.
The verbose flag (‘-v’) will also show the corresponding location of each universe.
A universe is a locally installed instance of a development container. Universes are bootstrapped from seeds.
To bootstrap a universe run universe create /path/to/seed_folder
.
The universe command mostly wraps other tools like debootstrap and ansible. If one of those tools fails, the universe command will print the wrapped command’s stdout and stderr.
If you want to see more details about what is happening, use the -v
flag like universe -v create /path/to/seed_folder
.
This will print all the wrapped commands' output to stdout.
This is what the universe command does while creating a new container:
-
Copy the seed to the local multiverse (
~/.slingring/multiverse/universe-name
) -
Create a chroot in the library (default:
/var/lib/slingring/universe-name
) -
Create a schroot configuration for the chroot
-
Initialize nssdatabases like passwd/shadow etc. based on the host
-
Copy the initializers to the universe and run them one by one using schroot
-
Mount the virtual filesystems into the chroot (e.g. /dev, /proc, /sys etc.)
-
Run the Ansible playbook in the
ansible
sub-directory of the seed on the chroot -
Unmount the virtual filesystems
After the container is bootstrapped, the command you can use to enter your container is printed on the screen.
Since the seed has been copied to the local multiverse, it is no longer needed.
If your seed specifies any packages in their latest version, you might want to update your universe from time to time. A playbook which, for example, contains a role to install a proprietary IDE like IntelliJ IDEA might update this package, even if it is not part of any repositories available in your container. This will essentially re-run the local copy of the Ansible playbook on your universe.
Doing so might also fix problems like accidentally removed packages or repositories. It is therefore advisable to update your universe from time to time.
The default template therefore includes the timestamp of the last update in the welcome header when opening a portal.
To update a universe, simply run universe update universe-name
.
Adding the -v
flag to the universe command will print all wrapped commands' output to stdout.
If you create or receive a newer version of a seed you used to bootstrap a local universe, you can upgrade your universe to the new seed. This will remove the seed used to bootstrap your universe from the local multiverse in favor of the new version and then run the update routine. Keep in mind that this behaviour is destined to fail if the new seed contains fundamental changes outside of the playbook. If, for example, the underlying base image has been changed to a newer Ubuntu version, there is no guarantee that an upgrade will work. It is therefore recommended to upgrade a universe only if the author of the seed explicitly lists your seed version as compatible.
To upgrade a universe to a new seed version run universe upgrade universe-name seed-location/
.
Adding the -v
flag to the universe command will print all wrapped commands' output to stdout.
You can get rid of any universe by simply entering universe remove universe-name
.
Removing a universe will delete
-
the local copy of the seed in
~/.slingring/multiverse
-
the schroot configuration in
/etc/schroot/chroot.d
-
the chroot of the universe (usually in
/var/lib/slingring/
)
This also works with incomplete universes which may be a result of a failed bootstrap attempt.
The slingring command is used to enter a universe: slingring universe-name
.
You can also run a command directly inside the universe by appending it to the slingring command (e.g. slingring universe-name ls
).
The slingring command is a thin wrapper around the schroot command. It mostly manages the schroot session and passes some selected environment variables into the container.
Entering a universe is also called "opening a portal". The terminal can be seen as a portal inside the universe.
When the first portal is opened, a new schroot session is created. This session contains mounts of the virtual file systems (/dev, /proc, /sys etc). When the last portal is closed, slingring will try to end the session.
If a daemon has been started inside the universe, slingring will not be able to end the session.
In that case a corresponding warning is shown.
You can use the schroot-process-check command to show the PIDs of the processes running inside the universe.
The session name is UNIVERSE-NAME-seu-session
.
The command schroot-process-check -v foo-seu-session
will show all PIDs of processes inside the "foo" universe.
It is possible to open a portal, start a daemon and close the portal. In order to correctly end the session, open the portal again, stop the daemon and close the portal.
Not ending a session before shutting down the system will usually not really break something. On the other hand, there is no guarantee that the virtual filesystems might not postpone the shutdown or all processes will end properly. It is therefore recommended to stop all daemons and close all open portals afterwards to end the session.
One of the main benefits of using chroots for development containers is that the universes reside directly within the host file system. This makes it incredibly easy to move/copy files in and out of the universe: just open the universe directory on your host system and copy the files in and out.
Since the universes passwd/shadow nssdatabase are initialized based on your host system, all files in the user home of your universe have the same owner as the files in your host’s user home.
If you want to access the host system from the guest, you can use a bind mount. On your host system, run
mount -o bind /path/on/your/host/system /path/to/the/mount/point/inside/the/universe
Keep in mind that manual mounts are not managed by slingring and must be manually unmounted.
When a universe is created, its user database is copied from the host system. From that point on, the user databases are strictly separated. That means that adding a user or changing a password outside/inside the universe will not affect the other side.
The reason for this behaviour is that you might want to install services like mysql or apache inside your container which will have their own service user. Those users should not be present outside your universe.
As a consequence, you can now add arbitrary users in your playbooks without the risk of doing harm to the user’s host system.
On the other hand, password changes outside the universe will not affect the users inside the universe. So if you change your user’s password, make sure you do the same inside your universes.
There are plans to add a convenience function to the universe command to update the password of one or more users inside all containers to the host’s password for these users.
Contribution to Slingring is more than welcome:
-
Please report any bugs or incompatible software as a GitHub ticket.
-
You can also open a new ticket if you have wishes or feature suggestions.
-
If you want to contribute code, don’t hesitate to create a pull request. Please do so from a dedicated branch.