Home

Awesome

Smith Hands On Exercise

Introduction

Smith is a command line tool for building microcontainers.

The aims of this exercise are to walk you through:

  1. Installing Smith
  2. Building a "hello world" microcontainer image from scratch
  3. Shrinking an existing container

Pre - requisites

Installing Smith

A. As a Docker image

The easiest way to install Smith is as a Docker image. Smith can then be run in a container.

  1. Navigate to https://github.com/oracle/smith
  2. Clone the repo to your machine
  3. Build the Docker image using the Dockerfile provided, adding your own docker-repo-id to the tag:

sudo docker build -t <docker-repo-id>/smith .

  1. Create an alias (or script) to run Smith from the command line:
smith(){
    sudo docker run -it --rm \
    --privileged -v $PWD:/write \
    -v cache:/var/cache \
    -v /tmp:/tmp \
    -v mock:/var/lib/mock <docker-repo-id>/smith $@
}
  1. Run smith --version

If the response is something like smith version 1.1.2.22.bc4d01a (built from sha bc4d01a) then you're in good shape.

B. As a binary

  1. Navigate to https://github.com/oracle/smith
  2. Clone the repo to your machine
  3. Install build dependencies (as described in the Smith Readme).
  4. If you're using Debian (or a Debian derived distro), make sure you follow the additional steps for installing mock (or bad things will happen)
  5. Run sudo make install
  6. Run smith --version

If the response is something like smith version 1.1.2.22.bc4d01a (built from sha bc4d01a) then you're in good shape.

Building a "hello world" microcontainer from scratch

  1. Create a new directory to build your container in.
  2. Set environment variables for your DOCKER_ID and DOCKER_PWD (as used in the examples below).
  3. Create a smith.yaml file:
package: coreutils
paths:
- /usr/bin/cat
cmd:
- /usr/bin/cat
- /read/data

This is to build a microcontainer image from scratch.

Stepping through the file:

package: coreutils defines as the source.

paths:
- /usr/bin/cat

Adds /usr/bin/cat to the microcontainer image.

cmd:
- /usr/bin/cat
- /read/data

Is a command to cat the file /read/data.

Create a subdirectory rootfs from your working directory. Any files or directories placed in here will be appear under root in the image /.

So to add '/read/data' to the image, create a subdirectory read of rootfs. Then under read, create a file data and put the string you'd like to display (e.g. 'Hello World!') in it.

So you should have a structure like this:

[ewan@starbug cat]$ ls -R
.:
rootfs  smith.yaml

./rootfs:
read

./rootfs/read:
data
[ewan@starbug cat]$ cat rootfs/read/data
Hello World!

Now switch back to your working directory and run smith -i hello.tgz.

Once that has finished, you should have a hello.tgz file in your working directory.

To get Docker to run this, we need to upload it to a Docker repo (e.g. dockerhub) to turn it into Docker format (standards are wonderful when they're implemented):

smith upload -r https://$DOCKER_ID:$DOCKER_PWD@registry-1.docker.io/$DOCKER_ID/hello-smith -i hello.tgz

Then you should be able to run it:

docker run --rm $DOCKER_ID/hello-smith

For example:

ewan@starbug:~/projects/smith-examples/cat$ docker run --rm $DOCKER_ID/hello-smith
Unable to find image 'crush157/hello-smith:latest' locally
latest: Pulling from crush157/hello-smith
ca43e03ec88e: Pull complete
Digest: sha256:eb06aa9738691a7b53f2ee420600be18f473c8fccc40a53a13e683fd40c7f2eb
Status: Downloaded newer image for crush157/hello-smith:latest
Hello World!

Coffee time :-)

Shrinking an existing image

The image we're going to shrink contains a rudimentary "ticketing system" called Dogsbody. It exists only for the purpose of exercises and labs like this one.

If you're interested the source code is on GitHub.

There is already a container image on Docker Hub crush157/dogsbody.

If you prefer to shrink a different image, then you can just use this as an example. If you want another example you can also look at How To Build a Tiny Httpd Container.

The Dockerfile for the base Dogsbody image is as follows:

FROM ruby

RUN apt-get update

# Install app
RUN mkdir /app
WORKDIR /app
ADD ./dogsbody.tgz .
RUN bundle install
CMD ./run.sh

If you pull this down to your machine you'll see that it has a size of 934MB.

Now want to shrink this.

  1. Create another working directory (it's just tidier that way), and cd into it.
  2. Download the image in OCI format:

smith download -r https://$DOCKER_ID:$DOCKER_PWD@registry-1.docker.io/crush157/dogsbody -i dogsbody-image.tgz 3. Create a bare bones smith.yaml file:

type: oci
package: dogsbody-image.tgz
paths:
- /app/
  1. Run smith smith -i dogsbody.tar.gz. This will create an image with not much in it, but it will also unpack the OCI image for us to have a look around.
  2. If you look under /tmp you will see a directory with a name something like smith-unpack-1000 (the last part will be your UID so may be different). In there are the contents of the OCI container. Let's have a look around for ruby and sinatra:
ewan@starbug:/tmp/smith-unpack-1000$ find ./ -name ruby
./usr/local/bin/ruby
./usr/local/lib/ruby
./usr/local/include/ruby-2.4.0/x86_64-linux/ruby
./usr/local/include/ruby-2.4.0/ruby
ewan@starbug:/tmp/smith-unpack-1000$ find ./ -name sinatra
./usr/local/bundle/gems/sinatra-2.0.0/lib/sinatra
./usr/local/bundle/gems/mustermann-1.0.1/lib/mustermann/sinatra
./root/.bundle/cache/compact_index/rubygems.org.443.29b0360b937aa4d161703e6160654e47/info/sinatra
  1. So lets add some likely looking paths to our smith.yaml, and the command to run the app:
type: oci
package: dogsbody-image.tgz
paths:
- /app/
- /usr/local/bin/
- /usr/local/lib/
- /usr/local/include/
- /usr/local/bundle/
cmd:
- ruby
- app.rb
- '-e production'
  1. Then run smith -i dogsbody.tar.gz again to create a new image.
  2. Let's upload it to docker hub, to get an image in Docker format:

smith upload -r https://$DOCKER_ID:$DOCKER_PWD@registry-1.docker.io/$DOCKER_ID/smith-dogsbody -i dogsbody.tar.gz

  1. Now let's try and run it. The first thing we need is a MySQL instance. If you haven't got one already, then run a MySQL container (change the password / ip / port if you want to):
docker run -d --ip 172.17.0.2 -e MYSQL_ROOT_PASSWORD=Welcome_1 --publish 3306:3306/tcp --name dogsbody_db mysql --default-authentication-plugin=mysql_native_password
  1. Then we need to create a database "dogsbody". If you're running MySQL in a container, use docker exec to access it and create the database:
$ docker exec -it dogsbody_db bash

You should now have a root bash prompt in the container. Log into the MySQL database.

# mysql -u root -p 
Password: <type MYSQL_ROOT_PASSWORD>

Now you should have a mysql> prompt. Create the dogsbody database, and then exit the mysql client and the bash shell:

mysql> create database dogsbody;
Query OK, 1 row affected (0.00 sec)
mysql> exit
# exit
  1. The next thing we do is run rake db:migrate which checks the db schema and updates it if necessary. Replace the MYSQLCS_* environment variables in the example command below with the appropriate values for your database and then run it:
ewan@starbug:~/projects/smith-examples/dogsbody$ docker run -it --rm \
>   --name dogsbody \
>   --read-only \
>   --env MYSQLCS_CONNECT_STRING="172.17.0.1:3306/dogsbody" \
>   --env MYSQLCS_USER_PASSWORD="Welcome_1" \
>   --env MYSQLCS_USER_NAME="root" \
>   $DOCKER_ID/smith-dogsbody rake db:migrate
Migrating to latest
  1. If it returned "Migrating to latest" you should be good to run the service. Replace the MYSQLCS_* environment variables in the example command below with the appropriate values for your database and then run it:
ewan@starbug:~/projects/smith-examples/dogsbody$ docker run -d --rm \
>   --name dogsbody \
>   --read-only \
>   --env MYSQLCS_CONNECT_STRING="172.17.0.1:3306/dogsbody" \
>   --env MYSQLCS_USER_PASSWORD="Welcome_1" \
>   --env MYSQLCS_USER_NAME="root" \
>   --env PATH="/usr/local/bin" \
>   --publish 22222:4567/tcp \
>   $DOCKER_ID/smith-dogsbody
5c7931542a7c5b41ab517b451dac37c8224bcca1fb8d1fbd91989c9a887727dc
  1. Check that it's running:
ewan@starbug:~/projects/smith-examples/dogsbody$ docker ps
CONTAINER ID        IMAGE                       COMMAND                  CREATED             STATUS                   PORTS                               NAMES
5c7931542a7c        crush157/smith-dogsbody     "ruby app.rb '-e p..."   36 seconds ago      Up 35 seconds            0.0.0.0:22222->4567/tcp             dogsbody
640f14c778ad        mysql/mysql-server:latest   "/entrypoint.sh my..."   12 days ago         Up 5 minutes (healthy)   0.0.0.0:3306->3306/tcp, 33060/tcp   mysql
ewan@starbug:~/projects/smith-examples/dogsbody$ curl -v localhost:22222/users
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 22222 (#0)
> GET /users HTTP/1.1
> Host: localhost:22222
> User-Agent: curl/7.52.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Content-Length: 2
< X-Content-Type-Options: nosniff
< Connection: keep-alive
< Server: thin
<
* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact
[]

200 is what we want.

  1. Let's check the image size:
ewan@starbug:~/projects/smith-examples/dogsbody$ docker images
REPOSITORY                 TAG                 IMAGE ID            CREATED             SIZE
crush157/smith-dogsbody    latest              5bbfc75d826b        12 minutes ago      83.4MB
crush157/hello-smith       latest              a430ff69520c        2 hours ago         2.3MB
ruby                       2.4.2-alpine3.6     8647c16e6bb0        8 days ago          72MB
crush157/smith-httpd       latest              9d1ba46c4fb0        11 days ago         4.76MB
crush157/squash-dogsbody   latest              a8e0ddf918b5        12 days ago         934MB
crush157/dogsbody          latest              18e67fa6c47e        12 days ago         934MB
jruby                      latest              300c281a1aca        13 days ago         594MB
ruby                       latest              c7715c1eb8fe        2 weeks ago         687MB
httpd                      latest              74ad7f48867f        2 weeks ago         177MB
debian                     latest              6d83de432e98        2 weeks ago         100MB
alpine                     latest              053cde6e8953        2 weeks ago         3.97MB
mysql/mysql-server         latest              a3ee341faefb        5 weeks ago         246MB

And we see that we've gone from 934MB down to 83.4MB!

With a little extra work, you can even knock of another 2MB! The smith.yaml for that is:

type: oci
package: dogsbody-image.tgz
paths:
- /app/
- /usr/local/bin/ruby
- /usr/local/bin/rake
- /usr/local/lib/libruby.so
- /usr/local/lib/ruby
- /usr/local/bundle
cmd:
- ruby
- app.rb
- '-e production'
  1. Now you've worked through the example, why don't you try to shrink one of your own images?