Wednesday, December 30, 2015

My Adventure With Docker on an Early Version Raspberry Pi: Part Two

Meet the Pi

This Pi I use for development and testing may be on the short list of systems to replace with one of the new Pi 2's; it has 512MB of RAM and an ARM 6 compatible processor running 700 MHz (according to cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq )

I also attached a 500GB USB drive because an SD card was just too small for testing applications and storing Git repositories.

This is my lunchbox development system. It literally lives in a little lunchbox; a take-home container for leftover food. I cut a small hole in it through which power, video and network cables are passed. Outside the container is a powered USB hub for the external mouse and keyboard. Most of the system is safely contained in the portable container. At one point I thought it would be nifty to attach a battery and mini-LCD to the box to create a really portable developer system. But that's a different topic.

The Pi is upgraded to the latest Raspbian from Wheezy to Jessie.

uname -a
Linux nyhq-bms-pi 4.1.13+ #826 PREEMPT Fri Nov 13 20:13:22 GMT 2015 armv6l GNU/Linux

I should note that the Pi has quite a bit of information gleaned from a Pi-specific command...

vcgencmd commands
commands="vcos, ap_output_control, ap_output_post_processing, vchi_test_init, vchi_test_exit, pm_set_policy, pm_get_status, pm_show_stats, pm_start_logging, pm_stop_logging, version, commands, set_vll_dir, led_control, set_backlight, set_logging, get_lcd_info, set_bus_arbiter_mode, cache_flush, otp_dump, test_result, codec_enabled, get_camera, get_mem, measure_clock, measure_volts, scaling_kernel, scaling_sharpness, get_hvs_asserts, measure_temp, get_config, hdmi_ntsc_freqs, hdmi_adjust_clock, hdmi_status_show, hvs_update_fields, pwm_speedup, force_audio, hdmi_stream_channels, hdmi_channel_map, display_power, read_ring_osc, memtest, dispmanx_list, get_rsts, schmoo, render_bar, disk_notify, inuse_notify, sus_suspend, sus_status, sus_is_enabled, sus_stop_test_thread, egl_platform_switch, mem_validate, mem_oom, mem_reloc_stats, file, vctest_memmap, vctest_start, vctest_stop, vctest_set, vctest_get"

That command, vcgencmd,  can give you information about your Raspberry Pi hardware speed, memory, temperature, etc. (handy tip!)

Let's Install Docker!

Most installations of Docker are running on Intel/Intel-compatible processors; Raspberries are running on ARM processors. Therefore Docker and Docker images on the Pi must be compiled for ARM processors. Therefore we need a Pi-specific Docker install. I adapted the steps from https://github.com/umiddelb/armhf/wiki/Get-Docker-up-and-running-on-the-RaspberryPi-(ARMv6)-in-four-steps-(Wheezy)

curl -sSL http://downloads.hypriot.com/docker-hypriot_1.9.1-1_armhf.deb >/tmp/docker-hypriot_1.9.1-1_armhf.deb
sudo dpkg -i /tmp/docker-hypriot_1.9.1-1_armhf.deb
rm -f /tmp/docker-hypriot_1.9.1-1_armhf.deb
sudo sh -c 'usermod -aG docker $SUDO_USER'
sudo systemctl enable docker.service

At that point I rebooted. Restarting is most likely not necessary but it helps put my mind at rest to test if the service comes back up at reboot.

After logging back in, I tested with a generic Pi image.

docker run -i -t resin/rpi-raspbian

That gave the following output:

Unable to find image 'resin/rpi-raspbian:latest' locally
latest: Pulling from resin/rpi-raspbian
26ee9e029db4: Pull complete
7fd99c655462: Pull complete
74aa4ff44e2e: Pull complete
703e52d2c09e: Pull complete
1994fd62cbe1: Pull complete
acbda1fdb360: Pull complete
e97a8531a526: Pull complete
Digest: sha256:ba2f4ba0272e3de62954456792f65fd7329d8ac306477667afbf04cd7360bc61
Status: Downloaded newer image for resin/rpi-raspbian:latest
root@cbe3d48cb02a:/#

It works! The -i sets the instance to interactive, and the -t connects it to a pseudo-tty. resin/rpi-raspbian is the rpi-raspbian image from the resin repository. Basically -i -t is why I was dropped into a command line inside the Docker container of resin/rpi-raspbian when it was done building.

What About Networking?

At this point it seemed the container mostly works. Networking was acting up. But how to see network functions? Docker images are very stripped down, running the minimal software for running your application. That helps trim the resource footprint of the running instances on your host. Unfortunately this also means that tools like Ping were missing from the image. Fortunately theres a way to copy files between the host and the containers.

docker ps

...lists the image names of running Docker instances. You'll need the container ID. Also,

docker network ls

...confirmed that Docker saw a bridge, null, and host network available to containers. Next I created a "staging ground" for files that would be used to batch into a Docker build.

mkdir einal_docker
cd einal_docker

From a session on the host (outside the container) I copied over files I wanted to insert into the running Docker instance. If I re-ran the docker run command, all my changes and alterations go away. I copied ifconfig to my staging directory, then while in the host I ran

docker cp ./ifconfig <containerID>:/home

...then from inside the Docker container I ran:

cd /home
./ifconfig

...and that gave these results:

root@<containerID>:/home# ./ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:ac:11:00:02
          inet addr:172.17.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:acff:fe11:2/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:8 errors:0 dropped:0 overruns:0 frame:0
          TX packets:10 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:648 (648.0 B)  TX bytes:828 (828.0 B)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

That IP address isn't within our networks, so Docker must be running with some kind of NAT. From what I could tell it wasn't hitting our DNS servers because it couldn't use apt-get to update from repos, instead throwing lookup errors.

To test this more, I copied Ping over. From the host:

cp /bin/ping .
docker cp ./ping <containerID>:/home

All attempts to ping returned a host unreachable error. ARGH! I closed out of the container using exit.

Let's Try Host Networking

Host networking can be explicitly set from the command line.

docker run --net="host" -i -t resin/rpi-raspbian

From the host, I ran "docker ps" and re-copied the ifconfig and ping executables from my staging directory into the new container. This showed more promising results!

docker0   Link encap:Ethernet  HWaddr 02:42:07:34:b7:bb
          inet6 addr: <clip> Scope:Link
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:39 errors:0 dropped:0 overruns:0 frame:0
          TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:2208 (2.1 KiB)  TX bytes:648 (648.0 B)

eth0      Link encap:Ethernet  HWaddr b8:27:eb:60:50:ae
          inet addr:<clip>  Bcast:<clip> Mask:255.255.255.0
          inet6 addr: <clip>/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:160416 errors:0 dropped:0 overruns:0 frame:0
          TX packets:5059 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:67957668 (64.8 MiB)  TX bytes:684671 (668.6 KiB)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

Now the Docker container was sharing my Pi's network address, pinging addresses worked, and apt-get was able to run updates!

EINAL Likes Logs, and so do I: Persistent Data Storage

I like to log what my applications do. EINAL keeps track of files it deleted and activity it experiences, but Docker instances are ephemeral. If it's not logging to an outside database or data store, data will be lost.

The Docker answer to this is a Volume, where data is stored on the host in a persistent location. I exited the running container and tried launching a new one with a Volume flag specified.

docker run --net="host" -v /var/logs -i -t resin/rpi-raspbian

After that successfully ran, I ran the docker ps command to get the new container ID, then ran:

docker inspect <containerID>

That dumped quite a bit of information, but here was the important bit:

   "Mounts": [
        {
            "Name": "fde7c2e91bfacc32e0c41817a89a1f386a746cb203a8641940b7a7b9c3289847",
            "Source": "/var/lib/docker/volumes/fde7c2e91bfacc32e0c41817a89a1f386a746cb203a8641940b7a7b9c3289847/_data",
            "Destination": "/var/logs",
            "Driver": "local",
            "Mode": "",
            "RW": true
        }
    ],

This says that the directory /var/logs (which I specified in the docker run command) inside the Docker image is linked to the directory on the host starting with /var/lib/docker/volumes (next to the "source" label.) Source is on the host, destination is in the container. When the container stops running, the data in the source directory stays intact while everything else disappears. And yes, logs are actually in /var/log, not /var/logs. The directory was created automatically in the container so both appeared.

Because of ownership permissions, I had to use "sudo ls" to view the contents of the directory; it was empty. From inside the container, I ran:

cd /var/logs
echo “hello” > test.txt

Then on the host, I re-checked the directory; test.txt was found. I exited the running container ("docker ps" showed no running instances) and re-checked the host directory. Test.txt was still there.

Can I specify the destination on the host where volume data is stored? Turns out I can.

docker run --net="host" -v /mnt/mydrive/projects_docker/einal_docker/einal_docker_logs:/var/logs -i -t 

I created another test file inside the /var/logs directory inside the container, and this time it appeared in the /mnt/mydrive... directory! The specification of which directory is in the container and which is on the host is a little backwards to my way of thinking, but this works.

Let's Change Where Docker Stores Stuff

The SD card in the Pi isn't huge, and Docker is storing much of its data in /var/lib/docker. I have a larger drive mounted by default in /mnt/mydrive. I wanted to change Docker to store stuff on the big drive.

I removed the temporary test stuff I'd made so far and created a new directory on the large drive.

sudo mkdir /mnt/mydrive/docker_rootdir
sudo chown pi:pi /mnt/mydrive/docker_rootdir
sudo chmod 777 /mnt/mydrive/docker_rootdir

Docker keeps a list of configuration options in /etc/default/docker. I opened it up and looked for the line beginning with "DOCKER_OPTS=". In that list I added "--graph=/mnt/mydrive/docker_rootdir" and restarted the machine. The options in that file are passed to the Docker daemon and while a service restart should force it to re-read the options, I still like the restart to make sure everything comes up properly if the system shuts down.

I created another container instance with "docker run --net="host" -v /var/logs -i -t resin/rpi-raspbian" and re-checked, using "docker ps" and "docker inspect <containerID>", where it was storing files. Docker was now using my external drive!

After confirming Docker was switched over I removed the old storage files using

sudo rm -fr /var/lib/docker

In the next post I "Dockerize" EINAL!

No comments:

Post a Comment