Categories
Posts in this category
- Automating Deployments: A New Year and a Plan
- Automating Deployments: Why bother?
- Automating Deployments: Simplistic Deployment with Git and Bash
- Automating Deployments: Building Debian Packages
- Automating Deployments: Debian Packaging for an Example Project
- Automating Deployments: Distributing Debian Packages with Aptly
- Automating Deployments: Installing Packages
- Automating Deployments: 3+ Environments
- Architecture of a Deployment System
- Introducing Go Continuous Delivery
- Technology for automating deployments: the agony of choice
- Automating Deployments: New Website, Community
- Continuous Delivery for Libraries?
- Managing State in a Continuous Delivery Pipeline
- Automating Deployments: Building in the Pipeline
- Automating Deployments: Version Recycling Considered Harmful
- Automating Deployments: Stage 2: Uploading
- Automating Deployments: Installation in the Pipeline
- Automating Deployments: Pipeline Templates in GoCD
- Automatically Deploying Specific Versions
- Story Time: Rollbacks Saved the Day
- Automated Deployments: Unit Testing
- Automating Deployments: Smoke Testing and Rolling Upgrades
- Automating Deployments and Configuration Management
- Ansible: A Primer
- Continuous Delivery and Security
- Continuous Delivery on your Laptop
- Moritz on Continuous Discussions (#c9d9)
- Git Flow vs. Continuous Delivery
Sat, 09 Jan 2016
Automating Deployments: Debian Packaging for an Example Project
Permanent link
After general notes on Debian packaging, I want to introduce an example project, and how it's packaged.
The Project
package-info is a
minimalistic web project, written solely for demonstrating packaging and
deployment. When called in the browser, it produces a text document containing
the output of dpkg -l
, which gives an overview of installed (and
potentially previously installed) packages, their version, installation state
and a one-line description.
It is written in Perl using the Mojolicious web framework.
The actual code resides in the file
usr/lib/package-info/package-info
and is delightfully short:
#!/usr/bin/perl use Mojolicious::Lite; plugin 'Config'; get '/' => sub { my $c = shift; $c->render(text => scalar qx/dpkg -l/, format => 'text'); }; app->start;
It loads the "Lite" version of the framework, registers a route for the URL
/
, which renders as plain text the output of the system command
dpkg -l
, and finally starts the application.
It also loads the Config-Plugin, which is used to specify the PID file for the server process.
The corresponding config file in etc/package-info.conf
looks
like this:
#!/usr/bin/perl { hypnotoad => { pid_file => '/var/run/package-info/package-info.pid', }, }
which again is perl code, and specifies the location of the PID file when run under hypnotoad, the application server recommended for use with Mojolicious.
To test it, you can install the libmojolicious-perl
package,
and run MOJO_CONFIG=$PWD/etc/package-info.conf morbo
usr/lib/package-info/package-info
. This starts a development server on
port 3000. Pointing your browser at http://127.0.0.1:3000/, you should see a
list like this:
Desired=Unknown/Install/Remove/Purge/Hold | Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend |/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad) ||/ Name Version Architecture Description +++-=====================================-====================================-============-=============================================================================== ii ack-grep 2.14-4 all grep-like program specifically for large source trees ii acl 2.2.52-2 amd64 Access control list utilities rc acroread-debian-files 0.2.5 amd64 Debian specific parts of Adobe Acrobat Reader ii adduser 3.113+nmu3 all add and remove users and groups ii adwaita-icon-theme 3.14.0-2 all default icon theme of GNOME
though much longer.
Initial Packaging
Installing dh-make
and running dh_make --createorig -p
package-info_0.1
gives us a debian
directory along
with several files.
I started by editing debian/control
to look like this:
Source: package-info Section: main Priority: optional Maintainer: Moritz LenzBuild-Depends: debhelper (>= 9) Standards-Version: 3.9.5 Package: package-info Architecture: all Depends: ${misc:Depends}, libmojolicious-perl Description: Web service for getting a list of installed packages
Debian packages support the notion of source package, which a
maintainer uploads to the Debian build servers, and from which one or more
binary package are built. The control
reflects this
structure, with the first half being about the source package and its build
dependencies, and the second half being about the binary package.
Next I deleted the file debian/source/format
, which by default
indicates the use of the quilt patch
management system, which isn't typically used in git based workflows.
I leave debian/rules
, debian/compat
and
debian/changelog
untouched, and create a file
debian/install
with two lines:
etc/package-info.conf usr/lib/package-info/package-info
In lieu of a proper build system, this tells dh_install
which
files to copy into the debian package.
This is a enough for a building a Debian package. To trigger the build, this command suffices:
debuild -b -us -uc
The -b
instructs debuild to only create a binary package, and
the two -u*
options skips the steps where debuild
cryptographically signs the generated files.
This command creates three files in the directory above the source tree:
package-info_0.1-1_all.deb
,
package-info_0.1-1_amd64.changes
and
package-info_0.1-1_amd64.build
. The .deb file contains the actual
program code and meta data, the .changes file meta data about the package as
well as the last changelog entry, and the .build file a transcript of the
build process.
A Little Daemonology
Installing the .deb file from the previous step would install a working software, but you'd have to start it manually.
Instead, it is useful to provide means to automatically start the server process at system boot time. Traditionally, this has been done by shipping init scripts. Since Debian transitioned to systemd as its init system with the "Jessie" / 8 version, systemd service files are the new way to go, and luckily much shorter than a robust init script.
The service file goes into debian/package-info.service
:
[Unit] Description=Package installation information via http Requires=network.target After=network.target [Service] Type=simple RemainAfterExit=yes SyslogIdentifier=package-info PIDFile=/var/run/package-info/package-info.pid Environment=MOJO_CONFIG=/etc/package-info.conf ExecStart=/usr/bin/hypnotoad /usr/lib/package-info/package-info -f ExecStop=/usr/bin/hypnotoad -s /usr/lib/package-info/package-info ExecReload=/usr/bin/hypnotoad /usr/lib/package-info/package-info
The [Unit]
section contains the service description, as well
as the specification when it starts. The [Service]
section
describes the service type, where simple
means that systemd
expects the start command to not terminate as long as the process is running.
With Environment
, environment variables can be set for all three
of the ExecStart, ExecStop and ExecReload commands.
Another debhelper, dh-systemd
takes care of installing the
service file, as well as making sure the service file is read and the service
started or restarted after a package installation. To enable it,
dh-systemd
must be added to the Build-Depends
line
in file debian/control
, and the catch-all build rule in
debian/rules
be changed to:
%: dh $@ --with systemd
To enable hypnotoad to write the PID file, the containing directory must
exists. Writing /var/run/package-info/
into a new
debian/dirs
file ensures this directory is created at package
installation.
To test the changes, again invoke debuild -b -us -uc
and
install the resulting .deb file with sudo dpkg -i
../package-info_0.1-1_all.deb
.
The server process should now listen on port 8080, so you can test it with
curl http://127.0.0.1:8080/ | head
.
A Bit More Security
As it is now, the application server and the application run as the root
user, which violates the Principle of
least privilege. Instead it should run as a separate user,
package-info
that isn't allowed to do much else.
To make the installation as smooth as possible, the package should create
the user itself if it doesn't exist. The debian/postinst
script
is run at package installation time, and is well suited for such tasks:
#!/bin/sh set -e test $DEBIAN_SCRIPT_DEBUG && set -v -x export PATH=$PATH:/sbin:/usr/sbin:/bin:/usr/bin USER="package-info" case "$1" in configure) if ! getent passwd $USER >/dev/null ; then adduser --system $USER fi chown -R $USER /var/run/package-info/ ;; esac #DEBHELPER# exit 0
There are several actions that a postinst script can execute, and
configure
is the right one for creating users. At this time, the
files are already installed.
Note that it also changes the permissions for the directory in which the
PID file is created, so that when hypnotoad
is invoked as the
package-info
user, it can still create the PID file.
Please note the presence of the #DEBHELPER#
tag, which the
build system replaces with extra actions. Some of these come from
dh-systemd
, and take care of restarting the service after
installation, and enabling it for starting after a reboot on first
installation.
To set the user under which the service runs, adding the line
User=package-info
to the [UNIT]
section of
debian/package-info.service
.
Linux offers more security features that can be enabled in a declarative
way in the systemd service file in the [Unit]
section. Here are a few
that protect the rest of the system from the server process, should it be
exploited:
PrivateTmp=yes InaccessibleDirectories=/home ReadOnlyDirectories=/bin /sbin /usr /lib /etc
Additional precautions can be taken by limiting the number of processes
that can be spawned and the available memory through the
LimitNPROC
and MemoryLimit
options.
The importance of good packaging
If you tune your packages so that they do as much configuration and environment setup themselves, you benefit two-fold. It makes it easy to the package in any context, regardless of whether it is embedded in a deployment system. But even if it is part of a deployment system, putting the package specific bits into the package itself helps you keep the deployment system generic, and thus easy to extend to other packages.
For example configuration management systems such as Ansible, Chef and Puppet allow you to create users and to restart services when a new package version is available, but if you rely on that, you have to treat each package separately in the configuration management system.
I'm writing a book on automating deployments. If this topic interests you, please sign up for the Automating Deployments newsletter. It will keep you informed about automating and continuous deployments. It also helps me to gauge interest in this project, and your feedback can shape the course it takes.