Date Category Blog

For a little while now I've been using the excellent shairport-sync package by Mike Brady on a Raspberry Pi 2B+ to stream music in my kitchen. I'd originally tried this out on FreeBSD around a year ago, but hit a wall due to only ALSA support for the package at the time, and a lack of ALSA support on FreeBSD for ARM-based architectures.

At the time, I threw in the towel, loaded up Raspbian Jessie, and it mostly just worked. There were occasional issues over the year where it seemed like connection sessions with the Avahi agent would get stuck and the Shairport daemon would fail to kick off defunct streaming clients, but I paid it little mind.

Now I haven't been working on QBSD much over the last few weeks, mostly due to family trips (it's that time of the year, after all). To get back on the wagon I decided to take a stab at replacing the Shairport-Sync's manually built system with a Docker container, because a little bit of experience with how Docker actually works in practice will help me plan out my project (codename: Condominium) for FreeBSD. There's little good coding or configuration I can do without being led by good design, so it would be pretty dumb to plow ahead without at least checking out how the issues of packaged service deployment were solved by others first.

First thing I noticed returning to Linux is the immense sea of somewhat conflicting or competing guides and advice for running Docker in the first place. I'd heard that Debian now offered a package, so of course I first try installing that. Seems to work; installs with no complaints. However, the only version available for Jessie in the standard repository is 1.3.3, which was so out-of-date that I couldn't pull any images to run. Instead I get this maddening nonsense:

me@kitchen:~ $ sudo docker pull orbsmiv/shairport-sync-rpi
Pulling repository orbsmiv/shairport-sync-rpi
2018/08/19 08:49:06 Could not reach any registry endpoint

So the hunt began.

The first guide that actually worked on the Pi was this random github 4-step guide. The Hypriot guys seem ok, and to be perfectly fair I didn't follow their own guid, but umiddelb's, in order to run the Hypriot install script as listed ... and this fetched version 1.10.3-1. A little poking and prodding revealed the consistent early problem that for some reason, systemd refused to start the service due to "masking", whatever that means. Apparently all I had to do was this:

me@kitchen:~ $ sudo systemctl unmask docker.service
me@kitchen:~ $ sudo systemctl unmask docker.socket
me@kitchen:~ $ sudo systemctl start docker

... and those magic words finally started my dockerd-hypriot .deb package. After that, it was just a matter of pulling the same image, and telling it to run with the default config. Nice.

But wait; what if I want to reboot the Pi in case that stuck session issue crops up again? After a quick reboot test I realize I have to run the docker run command again on the same image, which seems a bit... nonsense. After poking around online, it seems like the issue was resolved in version 1.11 where the --restart flag was introduced.

Ok, so this seems easy enough: go to the Hypriot website and note that there's a single minor-version upgrade available to 1.11.1 as a debian package (Hypriot, I note, has a fully-loaded image with Docker v1.18 supported, but that would require I pull the damn thing down off my cabinet, reflash the system, and plug it into my currently-occupied TV to set up the networking again correctly). So I try to install the newer version.

me@kitchen:~ $ sudo apt-get install docker-hypriot=1.11.1-1
Reading package lists... Done
Building dependency tree       
Reading state information... Done
docker-hypriot is already the newest version.
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
1 not fully installed or removed.
After this operation, 0 B of additional disk space will be used.
Do you want to continue? [Y/n] 
Setting up docker-hypriot (1.11.1-1) ...
Job for docker.service failed. See 'systemctl status docker.service' and 'journalctl -xn' for details.
invoke-rc.d: initscript docker, action "start" failed.
dpkg: error processing package docker-hypriot (--configure):
 subprocess installed post-installation script returned error exit status 1
Errors were encountered while processing:
 docker-hypriot
E: Sub-process /usr/bin/dpkg returned an error code (1)

... is this the same error? Let's try that magic unmasking again.

me@kitchen:~ $ sudo systemctl unmask docker.service
me@kitchen:~ $ sudo systemctl unmask docker.socket
me@kitchen:~ $ sudo systemctl start docker
Job for docker.service failed. See 'systemctl status docker.service' and 'journalctl -xn' for details.
me@kitchen:~ $ journalctl -xn
No journal files were found.
me@kitchen:~ $ systemctl status docker.servicedocker.service - Docker Application Container Engine
   Loaded: loaded (/lib/systemd/system/docker.service; enabled)
   Active: failed (Result: exit-code) since Sun 2018-08-19 11:55:51 UTC; 16s ago
     Docs: https://docs.docker.com
  Process: 3002 ExecStart=/usr/bin/docker daemon -H fd:// (code=exited, status=1/FAILURE)
 Main PID: 3002 (code=exited, status=1/FAILURE)

So what the hell is going on now? First stop was looking at alternative installation methods, because if 1.11 is the latest Raspbian package available for Docker something must be seriously wrong. This leads me back to Alex Ellis's blog on running v1.12, and a short instruction set on how to do this using a more standard supply chain. But it's another script to enable fetching from a different repository. So first I purge the special docker-hypriot package; second I run Ellis's recommendation.

me@kitchen:~ $ curl -sSL https://get.docker.com | sh
# Executing docker install script, commit: 36b78b2
+ sudo -E sh -c apt-get update -qq >/dev/null
+ sudo -E sh -c apt-get install -y -qq apt-transport-https ca-certificates curl >/dev/null
+ sudo -E sh -c curl -fsSL "https://download.docker.com/linux/raspbian/gpg" | apt-key add -qq - >/dev/null
+ sudo -E sh -c echo "deb [arch=armhf] https://download.docker.com/linux/raspbian jessie edge" > /etc/apt/sources.list.d/docker.list
+ [ raspbian = debian ]
+ sudo -E sh -c apt-get update -qq >/dev/null
+ sudo -E sh -c apt-get install -y -qq --no-install-recommends docker-ce >/dev/null
The mail frontend needs a installed 'sendmail', using pager
E: Sub-process /usr/bin/dpkg returned an error code (1)

Now what? Like a dummy, I say ok, let's install sendmail. This ends similarly:

Processing triggers for systemd (215-17+deb8u7) ...
Errors were encountered while processing:
 docker-ce
E: Sub-process /usr/bin/dpkg returned an error code (1)

Feeling a little defeated, I start looking around for hits regarding the actual error message returned by the systemctl status docker.service as listed. Turns out there's an unlisted action that the purge script failed to invoke: removing /var/lib/docker.

So I do that, remove sendmail, purge docker-ce, apt-get autoremove and apt-get clean the remaining packages just for good measure, and try the install script again.

me@kitchen:~ $ curl -sSL https://get.docker.com | sh
# Executing docker install script, commit: 36b78b2
+ sudo -E sh -c apt-get update -qq >/dev/null
+ sudo -E sh -c apt-get install -y -qq apt-transport-https ca-certificates curl >/dev/null
+ sudo -E sh -c curl -fsSL "https://download.docker.com/linux/raspbian/gpg" | apt-key add -qq - >/dev/null
+ sudo -E sh -c echo "deb [arch=armhf] https://download.docker.com/linux/raspbian jessie edge" > /etc/apt/sources.list.d/docker.list
+ [ raspbian = debian ]
+ sudo -E sh -c apt-get update -qq >/dev/null
+ sudo -E sh -c apt-get install -y -qq --no-install-recommends docker-ce >/dev/null
+ sudo -E sh -c docker version
Client:
 Version:           18.06.0-ce
 API version:       1.38
 Go version:        go1.10.3
 Git commit:        0ffa825
 Built:             Wed Jul 18 19:24:36 2018
 OS/Arch:           linux/arm
 Experimental:      false

Server:
 Engine:
  Version:          18.06.0-ce
  API version:      1.38 (minimum version 1.12)
  Go version:       go1.10.3
  Git commit:       0ffa825
  Built:            Wed Jul 18 19:20:24 2018
  OS/Arch:          linux/arm
  Experimental:     false
If you would like to use Docker as a non-root user, you should now consider
adding your user to the "docker" group with something like:

  sudo usermod -aG docker me

Remember that you will have to log out and back in for this to take effect!

WARNING: Adding a user to the "docker" group will grant the ability to run
         containers which can be used to obtain root privileges on the
         docker host.
         Refer to https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface
         for more information.

Success! And a much more recent version of Docker now running on my previously configured rpi.

Let's restart that docker pull-and-run procedure, now with --restart=always enabled.

me@kitchen:~ $ sudo docker pull orbsmiv/shairport-sync-rpi
Using default tag: latest
latest: Pulling from orbsmiv/shairport-sync-rpi
95d54dd4bdad: Pull complete 
  ## [snipped content]
Digest: sha256:50e00fca643ec68cf1e648ba9fc08c77485438fb77ea7bd6026d7ff8fde7d5cb
Status: Downloaded newer image for orbsmiv/shairport-sync-rpi:latest
rdain@kitchen:~ $ docker run -d --net host --restart=always --device /dev/snd -e AIRPLAY_NAME=ktchen_ap orbsmiv/shairport-sync-rpi

I note there's no "masking" at issue any more. Waiting a bit now for the service to come up...

And there it is, after I finally get tired of waiting and try to run systemctl status docker.service again. Time to reboot and see how long it takes to come up.

Cool, it's back. And in no time at all, really. Looks like the former AirPlay connection left in my iPhone's list isn't stale, either.


So my experience so far is that Docker is extremely easy to use ... once it's running. Updating the daemon itself, or updating the running software service shairport-sync isn't clear or straightforward; I'll need to unpack how to get the version information of the installed shairport-sync software out of the container, and then either rebuild it myself the next time Mike Brady updates it, or I can just wait for orbsmiv to update the thing. That may be where I'll find room for improvement.