Repository: firecracker-microvm/firecracker-containerd
Pull request #192 (2019-05-29 00:52:54)

Update quickstart
Update our quickstart guide.  Includes some other changes to make the guide work, like:

* Updating the Makefile to build firecracker-containerd
* Restoring some lost support for configuration values in `/etc/containerd/firecracker-runtime.json`
* Removing the "single container rootfs" from the image builder

This is ready for review now.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.



Patch:
diff --git a/Makefile b/Makefile
index 2948e9c..7dd011b 100644
--- a/Makefile
+++ b/Makefile
@@ -11,7 +11,7 @@
 # express or implied. See the License for the specific language governing
 # permissions and limitations under the License.
 
-SUBDIRS:=agent runtime snapshotter examples
+SUBDIRS:=agent runtime snapshotter examples firecracker-control/cmd/containerd
 export INSTALLROOT?=/usr/local
 export STATIC_AGENT
 
diff --git a/docs/getting-started.md b/docs/getting-started.md
index c00abc0..9f11a9f 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -40,15 +40,10 @@ You need to have the following things in order to use firecracker-containerd:
 * The Firecracker binary with the optional `vsock` feature enabled.  This
   feature requires building from source; instructions for doing so are in the
   [Firecracker getting started guide](https://github.com/firecracker-microvm/firecracker/blob/master/docs/getting-started.md#building-from-source)
-* An uncompressed ELF-format kernel image (you can use the one
-  [described here](https://github.com/firecracker-microvm/firecracker/blob/master/docs/getting-started.md#running-firecracker)
-  as `hello-vmlinux.bin`).
 * A root filesystem image (you can use the one
   [described here](https://github.com/firecracker-microvm/firecracker/blob/master/docs/getting-started.md#running-firecracker)
   as `hello-rootfs.ext4`). 
-* A recent installation of
-  [containerd](https://github.com/containerd/containerd/releases).  We suggest
-  [containerd v1.2.1](https://github.com/containerd/containerd/releases/tag/v1.2.1).
+* A recent installation of [Docker CE](https://docker.com).
 * Go 1.11 or later, which you can download from [here](https://golang.org/dl/).
 * Rust (and Cargo), which you can download from [here](https://rustup.rs/).
 
@@ -75,42 +70,18 @@ git checkout v0.12.0 # latest released tag
 cargo build --release --features vsock # --target x86_64-unknown-linux-gnu
 ```
 
-### Download appropriate filesystem images
+### Download appropriate kernel
 
-You can use the following images for the kernel and a default user-space:
+You can use the following kernel:
 
 ```bash
 curl -fsSL -o hello-vmlinux.bin https://s3.amazonaws.com/spec.ccfc.min/img/hello/kernel/hello-vmlinux.bin
-curl -fsSL -o hello-rootfs.ext4 https://s3.amazonaws.com/spec.ccfc.min/img/hello/fsfiles/hello-rootfs.ext4
 ```
 
-We will be modifying the `hello-rootfs.ext4` file in a subsequent step.
-
-### Install containerd
-
-You can install containerd from the
-[upstream binaries](https://github.com/containerd/containerd/releases/tag/v1.2.1),
-[build it yourself](https://github.com/containerd/containerd/blob/master/BUILDING.md),
-or use the containerd binary included with recent releases of Docker (like
-18.09).  We do not recommend using older versions of containerd.
-
-The [quickstart guide](quickstart.md) has example commands for building and
-installing containerd.
-
-### Install runc
-
-You can install runc from the
-[upstream binaries](https://github.com/opencontainers/runc/releases/tag/v1.0.0-rc6),
-[build it yourself](https://github.com/containerd/containerd/blob/v1.2.1/RUNC.md),
-or use the runc binary included with recent releases of Docker (like 18.06).  We
-do not recommend using older versions of containerd.
-
-Depending on whether or not the root filesystem of the microVM contains an
-installation of glibc, you may need to have a statically-linked build of runc.
-
-The [quickstart guide](quickstart.md) has example commands for building and
-installing runc, with static linking.
+### Install Docker
 
+You can install Docker CE from the [upstream
+binaries](https://docs.docker.com/install/) or from your Linux distribution.
 
 ### Clone this repository and build the components
 
@@ -119,85 +90,66 @@ We recommend choosing a directory outside of `$GOPATH` or `~/go`.
 
 ```bash
 git clone https://github.com/firecracker-microvm/firecracker-containerd
-make STATIC_AGENT=true
+make all
 ```
 
 If you clone repository inside the `$GOPATH` and golang version is 1.11.x, should explicitly set `GO111MODULE=on` before build.
 
 ```bash
-GO111MODULE=on make STATIC_AGENT=true
+GO111MODULE=on make all
+```
+
+Once you have built the runtime, be sure to place the following binaries on your
+`$PATH`:
+* `runtime/containerd-shim-aws-firecracker`
+* `snapshotter/cmd/devmapper/devmapper_snapshotter`
+* `snapshotter/cmd/naive/naive_snapshotter`
+* `firecracker-control/cmd/containerd/firecracker-containerd`
+
+You can use the `make install` target to install the files to `/usr/local/bin`,
+or specify a different `INSTALLROOT` if you prefer another location.
+
+### Build a root filesystem
+
+The firecracker-containerd repository includes an image builder component that
+constructs a Debian-based root filesystem for the VM.  This root filesystem
+bundles the necessary firecracker-containerd components and is configured to be
+safely shared among multiple VMs by overlaying a read-write filesystem layer on
+top of a read-only image.
+
+The image builder uses Docker, and expects you to be a member of the `docker`
+group (or otherwise have access to the Docker API socket).
+
+You can build an image like this:
+
+```bash
+make image
+sudo mkdir -p /var/lib/firecracker-containerd/runtime
+sudo cp tools/image-builder/rootfs.img /var/lib/firecracker-containerd/runtime/default-rootfs.img
 ```
 
-Once you have built the runtime, be sure to place the
-`containerd-shim-aws-firecracker` binary on your `$PATH`.
-
-### Inject the agent, runc shim, and runc into the rootfs image
-
-firecracker-containerd needs two components to be embedded in the root
-filesystem image.  They are:
-
-* `runc` - used to run containers
-* firecracker-containerd's `agent` - this is a process that communicates with
-  the outer firecracker-containerd `runtime` process over a `vsock` and proxies
-  commands to `containerd-shim-runc-v1`
-
-In order to inject these components in the root filesystem image above, we'll
-need to do a few steps.  These instructions assume that you're using the image
-described above; they are specific to the Alpine Linux user user-space and its
-init system.
-
-1. Increase the size of the root filesystem image.  The image that is supplied
-   by Firecracker (`hello-rootfs.ext4`) is an ext4 filesystem that is sized
-   exactly to the files already in it; no free space.  We can resize this
-   filesystem (while unmounted) by running
-   `e2fsck -f hello-rootfs.ext4 && resize2fs hello-rootfs.ext4`.
-2. Construct a `fc-agent.start` file so that the agent starts on boot:
-   <details>
-   <summary>fc-agent.start</summary>
-   
-   ```bash
-   #!/bin/sh
-   mkdir -p /container/rootfs
-   exec > /container/agent-debug.log # Debug logs from the agent
-   exec 2>&1
-   touch /container/runtime
-   mount -t auto -o rw /dev/vdb /container/rootfs
-   cd /container
-   /usr/local/bin/agent -id 1 -debug &
-   ```
-   </details>
-3. Mount the filesystem to a location like `/tmp/mnt`
-   <details>
-   <summary>Mounting at /tmp/mnt</summary>
-   
-   ```bash
-   sudo mkdir /tmp/mnt
-   sudo mount hello-rootfs.ext4 /tmp/mnt
-   ```
-   </details>
-4. Copy in the binaries, copy the `fc-agent.start` file, and set up OpenRC to
-   launch the necessary components
-   <details>
-   <summary>Copy in the binaries to /tmp/mnt</summary>
-   
-   ```bash
-   sudo cp $(which runc) firecracker-containerd/agent/agent /tmp/mnt/usr/local/bin
-   sudo cp fc-agent.start /tmp/mnt/etc/local.d
-   sudo chmod +x /tmp/mnt/etc/local.d/fc-agent.start
-   sudo ln -s /etc/init.d/local /tmp/mnt/etc/runlevels/default/local
-   sudo ln -s /etc/init.d/cgroups /tmp/mnt/etc/runlevels/default/cgroups
-   sudo umount /tmp/mnt
-   ```
-   </details>
-
-### Configure containerd snapshotter
-
-Add the snapshotter plugin to your `/etc/containerd/config.toml`
+### Configure `firecracker-containerd` binary
+
+The `firecracker-containerd` binary is a `containerd` binary that includes an
+additional plugin.  Configure it with a separate config file and have it use a
+separate location for on-disk state.  Make sure to include configuration for the
+snapshotter you intend to use.
+
+We recommend a configuration like the following:
+
 ```toml
+disabled_plugins = ["cri"]
+root = "/var/lib/firecracker-containerd/containerd"
+state = "/run/firecracker-containerd"
+[grpc]
+  address = "/run/firecracker-containerd/containerd.sock"
 [proxy_plugins]
   [proxy_plugins.firecracker-naive]
     type = "snapshot"
     address = "/var/run/firecracker-containerd/naive-snapshotter.sock"
+
+[debug]
+  level = "debug"
 ```
 
 ### Configure containerd runtime plugin
@@ -241,13 +193,13 @@ configuration file has the following fields:
 {
   "firecracker_binary_path": "/usr/local/bin/firecracker",
   "kernel_image_path": "/var/lib/firecracker-containerd/runtime/hello-vmlinux.bin",
-  "kernel_args": "console=ttyS0 noapic reboot=k panic=1 pci=off nomodules rw",
-  "root_drive": "/var/lib/firecracker-containerd/runtime/hello-rootfs.ext4",
-  "cpu_count": 1,
+  "kernel_args": "console=ttyS0 noapic reboot=k panic=1 pci=off nomodules ro systemd.journald.forward_to_console systemd.unit=firecracker.target init=/sbin/overlay-init",
+  "root_drive": "/var/lib/firecracker-containerd/runtime/default-rootfs.img",
   "cpu_template": "T2",
-  "log_fifo": "/tmp/fc-logs.fifo",
+  "log_fifo": "fc-logs.fifo",
   "log_level": "Debug",
-  "metrics_fifo": "/tmp/fc-metrics.fifo"
+  "metrics_fifo": "fc-metrics.fifo",
+
 }
 ```
 </details>
@@ -257,23 +209,30 @@ configuration file has the following fields:
 Start the containerd snapshotter
 
 ```bash
-$ ./naive_snapshotter -address /var/run/firecracker-containerd/naive-snapshotter.sock -path /tmp/fc-snapshot
+$ ./naive_snapshotter \
+  -address /var/run/firecracker-containerd/naive-snapshotter.sock \
+  -path /tmp/fc-snapshot
 ```
 
 In another terminal, start containerd
 
 ```bash
-sudo PATH=$PATH /usr/local/bin/containerd
+$ sudo PATH=$PATH /usr/local/bin/firecracker-containerd \
+  --config /etc/firecracker-containerd/config.toml
 ```
 
 Pull an image
 
 ```bash
-$ sudo ctr images pull --snapshotter firecracker-naive docker.io/library/busybox:latest
+$ sudo ctr --address /run/firecracker-containerd/containerd.sock images \
+  pull --snapshotter firecracker-naive \
+  docker.io/library/busybox:latest
 ```
 
 And start a container!
 
 ```bash
-$ sudo ctr run --snapshotter firecracker-naive --runtime aws.firecracker --tty docker.io/library/busybox:latest busybox-test
+$ sudo ctr --address /run/firecracker-containerd/containerd.sock \
+  run --snapshotter firecracker-naive --runtime aws.firecracker --tty \
+  docker.io/library/busybox:latest busybox-test
 ```
diff --git a/docs/quickstart.md b/docs/quickstart.md
index 5853b3b..bc4884d 100644
--- a/docs/quickstart.md
+++ b/docs/quickstart.md
@@ -55,94 +55,80 @@ sudo cp target/x86_64-unknown-linux-musl/release/{firecracker,jailer} /usr/local
 
 cd ~
 
-# Check out containerd and build it from the v1.2.4 tag
-mkdir -p ~/go/src/github.com/containerd/containerd
-git clone https://github.com/containerd/containerd.git ~/go/src/github.com/containerd/containerd
-cd ~/go/src/github.com/containerd/containerd
-git checkout v1.2.4
-sudo DEBIAN_FRONTEND=noninteractive apt-get install -y libseccomp-dev btrfs-progs
-make
-sudo cp bin/* /usr/local/bin
-
-cd ~
-
-# Check out runc and build it from the 6635b4f0c6af3810594d2770f662f34ddc15b40d
-# commit.  Note that this is the version described in
-# https://github.com/containerd/containerd/blob/v1.2.4/RUNC.md and
-# https://github.com/containerd/containerd/blob/v1.2.4/vendor.conf#L23
-mkdir -p ~/go/src/github.com/opencontainers/runc
-git clone https://github.com/opencontainers/runc ~/go/src/github.com/opencontainers/runc
-cd ~/go/src/github.com/opencontainers/runc
-git checkout 6635b4f0c6af3810594d2770f662f34ddc15b40d
-make static BUILDTAGS='seccomp'
-sudo make BINDIR='/usr/local/bin' install
+# Install Docker CE
+# Docker CE includes containerd, but we need a separate containerd binary, built
+# in a later step
+curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
+apt-key finger docker@docker.com | grep '9DC8 5822 9FC7 DD38 854A  E2D8 8D81 803C 0EBF CD88' || echo '**Cannot find Docker key**'
+echo "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | \
+     sudo tee /etc/apt/sources.list.d/docker.list
+sudo DEBIAN_FRONTEND=noninteractive apt-get update
+sudo DEBIAN_FRONTEND=noninteractive apt-get \
+     install --yes \
+     docker-ce aufs-tools-
+sudo usermod -aG docker $(whoami)
+exec newgrp docker
 
 cd ~
 
-# Check out firecracker-containerd and build it
+# Check out firecracker-containerd and build it.  This includes:
+# * block-device snapshotter gRPC proxy plugins
+# * firecracker-containerd runtime, a containerd v2 runtime
+# * firecracker-containerd agent, an inside-VM component
+# * runc, to run containers inside the VM
+# * a Debian-based root filesystem configured as read-only with a read-write
+#   overlay
+# * firecracker-containerd, an alternative containerd binary that includes the
+#   firecracker VM lifecycle plugin and API
 git clone https://github.com/firecracker-microvm/firecracker-containerd.git
 cd firecracker-containerd
 sudo DEBIAN_FRONTEND=noninteractive apt-get install -y dmsetup
-make STATIC_AGENT='true'
-sudo cp runtime/containerd-shim-aws-firecracker snapshotter/cmd/{devmapper/devmapper_snapshotter,naive/naive_snapshotter} /usr/local/bin
+make all image
+sudo make install
 
 cd ~
 
-# Download kernel and generic VM image
+# Download kernel
 curl -fsSL -o hello-vmlinux.bin https://s3.amazonaws.com/spec.ccfc.min/img/hello/kernel/hello-vmlinux.bin
-curl -fsSL -o hello-rootfs.ext4 https://s3.amazonaws.com/spec.ccfc.min/img/hello/fsfiles/hello-rootfs.ext4
-
-# Inject the agent, runc, and a startup script into the VM image
-mkdir /tmp/mnt
-# Construct fc-agent.start
-cat >fc-agent.start <<EOF
-#!/bin/sh
-mkdir -p /container
-exec > /container/agent-debug.log # Debug logs from the agent
-exec 2>&1
-touch /container/runtime
-mkdir /container/rootfs
-mount -t auto -o rw /dev/vdb /container/rootfs
-cd /container
-/usr/local/bin/agent -id 1 -debug &
-EOF
-chmod +x fc-agent.start
-truncate --size=+50M hello-rootfs.ext4
-/sbin/e2fsck -f hello-rootfs.ext4
-/sbin/resize2fs hello-rootfs.ext4
-sudo mount hello-rootfs.ext4 /tmp/mnt
-sudo cp $(which runc) firecracker-containerd/agent/agent /tmp/mnt/usr/local/bin
-sudo cp fc-agent.start /tmp/mnt/etc/local.d
-sudo ln -s /etc/init.d/local /tmp/mnt/etc/runlevels/default/local
-sudo ln -s /etc/init.d/cgroups /tmp/mnt/etc/runlevels/default/cgroups
-sudo umount /tmp/mnt
-rmdir /tmp/mnt
 
-cd ~
-
-# Configure containerd to use our new snapshotter
-sudo mkdir -p /etc/containerd
-sudo tee -a /etc/containerd/config.toml <<EOF
+# Configure our firecracker-containerd binary to use our new snapshotter and
+# separate storage from the default containerd binary
+sudo mkdir -p /etc/firecracker-containerd
+sudo mkdir -p /var/lib/firecracker-containerd/containerd
+sudo mkdir -p /run/firecracker-containerd
+sudo tee /etc/firecracker-containerd/config.toml <<EOF
+disabled_plugins = ["cri"]
+root = "/var/lib/firecracker-containerd/containerd"
+state = "/run/firecracker-containerd"
+[grpc]
+  address = "/run/firecracker-containerd/containerd.sock"
 [proxy_plugins]
   [proxy_plugins.firecracker-naive]
     type = "snapshot"
     address = "/var/run/firecracker-containerd/naive-snapshotter.sock"
+
+[debug]
+  level = "debug"
 EOF
 
 cd ~
 
 # Configure the aws.firecracker runtime
+# The long kernel command-line configures systemd inside the Debian-based image
+# and uses a special init process to create a read-write overlay on top of the
+# read-only image.
 sudo mkdir -p /var/lib/firecracker-containerd/runtime
-sudo cp hello-rootfs.ext4 /var/lib/firecracker-containerd/runtime/default-rootfs.img
-sudo cp hello-vmlinux.bin /var/lib/firecracker-containerd/runtime/default-vmlinux.bin
+sudo cp ~/firecracker-containerd/tools/image-builder/rootfs.img /var/lib/firecracker-containerd/runtime/default-rootfs.img
+sudo cp ~/hello-vmlinux.bin /var/lib/firecracker-containerd/runtime/default-vmlinux.bin
 sudo mkdir -p /etc/containerd
-sudo tee -a /etc/containerd/firecracker-runtime.json <<EOF
+sudo tee /etc/containerd/firecracker-runtime.json <<EOF
 {
   "firecracker_binary_path": "/usr/local/bin/firecracker",
   "cpu_template": "T2",
-  "log_fifo": "/tmp/fc-logs.fifo",
+  "log_fifo": "fc-logs.fifo",
   "log_level": "Debug",
-  "metrics_fifo": "/tmp/fc-metrics.fifo"
+  "metrics_fifo": "fc-metrics.fifo",
+  "kernel_args": "console=ttyS0 noapic reboot=k panic=1 pci=off nomodules ro systemd.journald.forward_to_console systemd.unit=firecracker.target init=/sbin/overlay-init"
 }
 EOF
 
@@ -161,19 +147,21 @@ sudo naive_snapshotter \
      -debug
 ```
 
-5. Open a new terminal and start `containerd` in the foreground
+5. Open a new terminal and start `firecracker-containerd` in the foreground
 
 ```bash
-sudo containerd
+sudo firecracker-containerd --config /etc/firecracker-containerd/config.toml
 ```
 
 6. Open a new terminal, pull an image, and run a container!
 
 ```bash
-sudo ctr image pull \
+sudo ctr --address /run/firecracker-containerd/containerd.sock \
+     image pull \
      --snapshotter firecracker-naive \
      docker.io/library/debian:latest
-sudo ctr run \
+sudo ctr --address /run/firecracker-containerd/containerd.sock \
+     run \
      --snapshotter firecracker-naive \
      --runtime aws.firecracker \
      --tty \
@@ -181,5 +169,10 @@ sudo ctr run \
      test
 ```
 
+In the commands above, note the `--address` argument targeting the
+`firecracker-containerd` binary instead of the normal `containerd` binary, the
+`--snapshotter` argument targeting the block-device snapshotter, and the
+`--runtime` argument targeting the firecracker-containerd runtime.
+
 When you're done, you can stop or terminate your i3.metal EC2 instance to avoid
 incurring additional charges from EC2.
diff --git a/firecracker-control/cmd/containerd/Makefile b/firecracker-control/cmd/containerd/Makefile
index a112bc8..d26230d 100644
--- a/firecracker-control/cmd/containerd/Makefile
+++ b/firecracker-control/cmd/containerd/Makefile
@@ -24,8 +24,8 @@ build: firecracker-containerd
 firecracker-containerd: $(SRC) $(GOMOD) $(GOSUM)
 	go build -v -o firecracker-containerd
 
-install: containerd
-	cp firecracker-containerd /usr/local/bin/
+install: firecracker-containerd
+	install -D -o root -g root -m755 -t $(INSTALLROOT)/bin firecracker-containerd
 
 test: $(SOURCES)
 	go test ./... $(EXTRAGOARGS)
diff --git a/runtime/config.go b/runtime/config.go
index 4a4912c..4410cf7 100644
--- a/runtime/config.go
+++ b/runtime/config.go
@@ -68,6 +68,7 @@ func LoadConfig(path string) (*Config, error) {
 		KernelImagePath: defaultKernelPath,
 		RootDrive:       defaultRootfsPath,
 		CPUCount:        defaultCPUCount,
+		CPUTemplate:     string(defaultCPUTemplate),
 	}
 	if err := json.Unmarshal(data, cfg); err != nil {
 		return nil, errors.Wrapf(err, "failed to unmarshal config from %q", path)
diff --git a/runtime/helpers.go b/runtime/helpers.go
index bba42fc..75a8a5c 100644
--- a/runtime/helpers.go
+++ b/runtime/helpers.go
@@ -22,10 +22,10 @@ import (
 	"github.com/firecracker-microvm/firecracker-containerd/proto"
 )
 
-func machineConfigurationFromProto(req *proto.FirecrackerMachineConfiguration) models.MachineConfiguration {
+func machineConfigurationFromProto(cfg *Config, req *proto.FirecrackerMachineConfiguration) models.MachineConfiguration {
 	config := models.MachineConfiguration{
-		CPUTemplate: defaultCPUTemplate,
-		VcpuCount:   defaultCPUCount,
+		CPUTemplate: models.CPUTemplate(cfg.CPUTemplate),
+		VcpuCount:   int64(cfg.CPUCount),
 		MemSizeMib:  defaultMemSizeMb,
 	}
 
diff --git a/runtime/service.go b/runtime/service.go
index ef1902e..98b2165 100644
--- a/runtime/service.go
+++ b/runtime/service.go
@@ -584,7 +584,7 @@ func (s *service) buildVMConfiguration(req *proto.CreateVMRequest) (*firecracker
 		VsockDevices: []firecracker.VsockDevice{{Path: "root", CID: s.machineCID}},
 		LogFifo:      s.shimDir().FirecrackerLogFifoPath(),
 		MetricsFifo:  s.shimDir().FirecrackerMetricsFifoPath(),
-		MachineCfg:   machineConfigurationFromProto(req.MachineCfg),
+		MachineCfg:   machineConfigurationFromProto(s.config, req.MachineCfg),
 	}
 
 	logger.Debugf("using socket path: %s", cfg.SocketPath)
@@ -594,13 +594,13 @@ func (s *service) buildVMConfiguration(req *proto.CreateVMRequest) (*firecracker
 	if val := req.KernelArgs; val != "" {
 		cfg.KernelArgs = val
 	} else {
-		cfg.KernelArgs = defaultKernelArgs
+		cfg.KernelArgs = s.config.KernelArgs
 	}
 
 	if val := req.KernelImagePath; val != "" {
 		cfg.KernelImagePath = val
 	} else {
-		cfg.KernelImagePath = defaultKernelPath
+		cfg.KernelImagePath = s.config.KernelImagePath
 	}
 
 	// Drives configuration
@@ -609,7 +609,7 @@ func (s *service) buildVMConfiguration(req *proto.CreateVMRequest) (*firecracker
 	if root := req.RootDrive; root != nil {
 		driveBuilder = firecracker.NewDrivesBuilder(root.PathOnHost)
 	} else {
-		driveBuilder = firecracker.NewDrivesBuilder(defaultRootfsPath)
+		driveBuilder = firecracker.NewDrivesBuilder(s.config.RootDrive)
 	}
 
 	// TODO: Reserve fake drives here (https://github.com/firecracker-microvm/firecracker-containerd/pull/154)
diff --git a/tools/docker/firecracker-runtime.json b/tools/docker/firecracker-runtime.json
index 0b2a664..5133ea1 100644
--- a/tools/docker/firecracker-runtime.json
+++ b/tools/docker/firecracker-runtime.json
@@ -1,8 +1,8 @@
 {
   "firecracker_binary_path": "/usr/local/bin/firecracker",
-  "kernel_image_path": "/var/lib/firecracker-containerd/runtime/hello-vmlinux.bin",
+  "kernel_image_path": "/var/lib/firecracker-containerd/runtime/default-vmlinux.bin",
   "kernel_args": "console=ttyS0 noapic reboot=k panic=1 pci=off nomodules rw",
-  "root_drive": "/var/lib/firecracker-containerd/runtime/hello-rootfs.ext4",
+  "root_drive": "/var/lib/firecracker-containerd/runtime/default-rootfs.img",
   "cpu_count": 1,
   "cpu_template": "T2",
   "log_fifo": "/tmp/fc-logs.fifo",
diff --git a/tools/image-builder/README.md b/tools/image-builder/README.md
index eb1a7dc..8008cf9 100644
--- a/tools/image-builder/README.md
+++ b/tools/image-builder/README.md
@@ -44,7 +44,7 @@ build-time dependency is that you can launch Docker container directly
 Alternatively, to build outside a container, you'll need:
 
 * To run the build process as root.
-* `[debootstrap](https://salsa.debian.org/installer-team/debootstrap)`
+* [`debootstrap`](https://salsa.debian.org/installer-team/debootstrap)
   (Install via the package of the same name on Debian and Ubuntu)
 * `mksquashfs`, available in the
    `[squashfs-tools](https://packages.debian.org/stretch/squashfs-tools)
diff --git a/tools/image-builder/files_debootstrap/etc/systemd/system/container-rootfs.mount b/tools/image-builder/files_debootstrap/etc/systemd/system/container-rootfs.mount
index fedf6ca..4814e91 100644
--- a/tools/image-builder/files_debootstrap/etc/systemd/system/container-rootfs.mount
+++ b/tools/image-builder/files_debootstrap/etc/systemd/system/container-rootfs.mount
@@ -1,8 +1,19 @@
+# Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"). You may
+# not use this file except in compliance with the License. A copy of the
+# License is located at
+#
+#       http://aws.amazon.com/apache2.0/
+#
+# or in the "license" file accompanying this file. This file is distributed
+# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+# express or implied. See the License for the specific language governing
+# permissions and limitations under the License.
 [Unit]
 Description=Single container root filesystem
 ConditionPathIsDirectory=/container/rootfs
 DefaultDependencies=no
-Before=local-fs.target
 
 [Mount]
 What=/dev/vdb
diff --git a/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker-agent.service b/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker-agent.service
index 2c8e239..64aaab5 100644
--- a/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker-agent.service
+++ b/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker-agent.service
@@ -1,7 +1,18 @@
+# Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"). You may
+# not use this file except in compliance with the License. A copy of the
+# License is located at
+#
+#       http://aws.amazon.com/apache2.0/
+#
+# or in the "license" file accompanying this file. This file is distributed
+# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+# express or implied. See the License for the specific language governing
+# permissions and limitations under the License.
 [Unit]
 Description=Firecracker VM agent
 StartLimitIntervalSec=2
-ConditionPathIsMountPoint=/container/rootfs
 After=local-fs.target
 Requires=local-fs.target
 
diff --git a/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker.target b/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker.target
index f82ccc2..19e6409 100644
--- a/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker.target
+++ b/tools/image-builder/files_debootstrap/etc/systemd/system/firecracker.target
@@ -1,3 +1,15 @@
+# Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"). You may
+# not use this file except in compliance with the License. A copy of the
+# License is located at
+#
+#       http://aws.amazon.com/apache2.0/
+#
+# or in the "license" file accompanying this file. This file is distributed
+# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+# express or implied. See the License for the specific language governing
+# permissions and limitations under the License.
 [Unit]
 Description=Firecracker containerd VM
 Requires=basic.target


Test patch:
diff --git a/runtime/config_test.go b/runtime/config_test.go
index 756133d..a6d8209 100644
--- a/runtime/config_test.go
+++ b/runtime/config_test.go
@@ -33,6 +33,7 @@ func TestLoadConfigDefaults(t *testing.T) {
 	assert.Equal(t, defaultKernelPath, cfg.KernelImagePath, "expected default kernel path")
 	assert.Equal(t, defaultRootfsPath, cfg.RootDrive, "expected default rootfs path")
 	assert.Equal(t, defaultCPUCount, cfg.CPUCount, "expected default CPU count")
+	assert.Equal(t, string(defaultCPUTemplate), cfg.CPUTemplate, "expected default CPU template")
 }
 
 func TestLoadConfigOverrides(t *testing.T) {
@@ -40,13 +41,15 @@ func TestLoadConfigOverrides(t *testing.T) {
 	overrideKernelPath := "OVERRIDE KERNEL PATH"
 	overrideRootfsPath := "OVERRIDE ROOTFS PATH"
 	overrideCPUCount := 42
+	overrideCPUTemplate := "OVERRIDE CPU TEMPLATE"
 	configContent := fmt.Sprintf(
 		`{
 			"kernel_args":"%s",
 			"kernel_image_path":"%s",
 			"root_drive":"%s",
-			"cpu_count": %d
-		}`, overrideKernelArgs, overrideKernelPath, overrideRootfsPath, overrideCPUCount)
+			"cpu_count": %d,
+            "cpu_template": "%s"
+		}`, overrideKernelArgs, overrideKernelPath, overrideRootfsPath, overrideCPUCount, overrideCPUTemplate)
 	configFile, cleanup := createTempConfig(t, configContent)
 	defer cleanup()
 	cfg, err := LoadConfig(configFile)
@@ -55,7 +58,8 @@ func TestLoadConfigOverrides(t *testing.T) {
 	assert.Equal(t, overrideKernelArgs, cfg.KernelArgs, "expected overridden kernel args")
 	assert.Equal(t, overrideKernelPath, cfg.KernelImagePath, "expected overridden kernel path")
 	assert.Equal(t, overrideRootfsPath, cfg.RootDrive, "expected overridden rootfs path")
-	assert.Equal(t, overrideCPUCount, cfg.CPUCount, "expected overridden cpu count")
+	assert.Equal(t, overrideCPUCount, cfg.CPUCount, "expected overridden CPU count")
+	assert.Equal(t, overrideCPUTemplate, cfg.CPUTemplate, "expected overridden CPU template")
 }
 
 func createTempConfig(t *testing.T, contents string) (string, func()) {
diff --git a/runtime/helpers_test.go b/runtime/helpers_test.go
index fab379b..f93926e 100644
--- a/runtime/helpers_test.go
+++ b/runtime/helpers_test.go
@@ -32,32 +32,81 @@ const (
 )
 
 func TestMachineConfigurationFromProto(t *testing.T) {
-	config := machineConfigurationFromProto(&proto.FirecrackerMachineConfiguration{
-		CPUTemplate: string(models.CPUTemplateC3),
-		VcpuCount:   vcpuCount,
-		MemSizeMib:  memSize,
-		HtEnabled:   true,
-	})
-
-	assert.EqualValues(t, models.CPUTemplateC3, config.CPUTemplate)
-	assert.EqualValues(t, vcpuCount, config.VcpuCount)
-	assert.EqualValues(t, memSize, config.MemSizeMib)
-	assert.True(t, config.HtEnabled)
-}
-
-func TestDefaultMachineConfigurationFromProto(t *testing.T) {
-	configs := map[string]models.MachineConfiguration{
-		"Nil":          machineConfigurationFromProto(nil),
-		"Empty struct": machineConfigurationFromProto(&proto.FirecrackerMachineConfiguration{}),
+	testcases := []struct {
+		name                  string
+		config                *Config
+		proto                 *proto.FirecrackerMachineConfiguration
+		expectedMachineConfig models.MachineConfiguration
+	}{
+		{
+			name:   "ProtoOnly",
+			config: &Config{},
+			proto: &proto.FirecrackerMachineConfiguration{
+				CPUTemplate: string(models.CPUTemplateC3),
+				VcpuCount:   vcpuCount,
+				MemSizeMib:  memSize,
+				HtEnabled:   true,
+			},
+			expectedMachineConfig: models.MachineConfiguration{
+				CPUTemplate: models.CPUTemplateC3,
+				VcpuCount:   vcpuCount,
+				MemSizeMib:  memSize,
+				HtEnabled:   true,
+			},
+		},
+		{
+			name: "ConfigOnly",
+			config: &Config{
+				CPUTemplate: "C3",
+				CPUCount:    vcpuCount,
+			},
+			proto: &proto.FirecrackerMachineConfiguration{},
+			expectedMachineConfig: models.MachineConfiguration{
+				CPUTemplate: models.CPUTemplateC3,
+				VcpuCount:   vcpuCount,
+				MemSizeMib:  defaultMemSizeMb,
+				HtEnabled:   false,
+			},
+		},
+		{
+			name: "NilProto",
+			config: &Config{
+				CPUTemplate: "C3",
+				CPUCount:    vcpuCount,
+			},
+			expectedMachineConfig: models.MachineConfiguration{
+				CPUTemplate: models.CPUTemplateC3,
+				VcpuCount:   vcpuCount,
+				MemSizeMib:  defaultMemSizeMb,
+				HtEnabled:   false,
+			},
+		},
+		{
+			name: "Overrides",
+			config: &Config{
+				CPUTemplate: "T2",
+				CPUCount:    vcpuCount + 1,
+			},
+			proto: &proto.FirecrackerMachineConfiguration{
+				CPUTemplate: string(models.CPUTemplateC3),
+				VcpuCount:   vcpuCount,
+				MemSizeMib:  memSize,
+				HtEnabled:   true,
+			},
+			expectedMachineConfig: models.MachineConfiguration{
+				CPUTemplate: models.CPUTemplateC3,
+				VcpuCount:   vcpuCount,
+				MemSizeMib:  memSize,
+				HtEnabled:   true,
+			},
+		},
 	}
 
-	for name, config := range configs {
-		cfg := config
-		t.Run(name, func(t *testing.T) {
-			assert.EqualValues(t, defaultCPUTemplate, cfg.CPUTemplate)
-			assert.EqualValues(t, defaultCPUCount, cfg.VcpuCount)
-			assert.EqualValues(t, defaultMemSizeMb, cfg.MemSizeMib)
-			assert.False(t, cfg.HtEnabled)
+	for _, tc := range testcases {
+		tc := tc // see https://github.com/kyoh86/scopelint/issues/4
+		t.Run(tc.name, func(t *testing.T) {
+			machineConfig := machineConfigurationFromProto(tc.config, tc.proto)
+			assert.Equal(t, tc.expectedMachineConfig, machineConfig)
 		})
 	}
 }
diff --git a/runtime/service_test.go b/runtime/service_test.go
index c07b3fd..aa275f3 100644
--- a/runtime/service_test.go
+++ b/runtime/service_test.go
@@ -18,6 +18,11 @@ import (
 	"syscall"
 	"testing"
 
+	"github.com/firecracker-microvm/firecracker-containerd/proto"
+	"github.com/firecracker-microvm/firecracker-go-sdk"
+	models "github.com/firecracker-microvm/firecracker-go-sdk/client/models"
+	"github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -47,3 +52,139 @@ func TestFindNextAvailableVsockCID(t *testing.T) {
 	_, _, err = findNextAvailableVsockCID(ctx)
 	require.Equal(t, context.Canceled, err)
 }
+
+func TestBuildVMConfiguration(t *testing.T) {
+	namespace := "TestBuildVMConfiguration"
+	rootfsDrive := models.Drive{
+		DriveID:      firecracker.String("containerRootfs"), // TODO: See https://github.com/firecracker-microvm/firecracker-containerd/pull/154
+		PathOnHost:   firecracker.String("/dev/null"),
+		IsReadOnly:   firecracker.Bool(false),
+		IsRootDevice: firecracker.Bool(false),
+	}
+	testcases := []struct {
+		name        string
+		request     *proto.CreateVMRequest
+		config      *Config
+		expectedCfg *firecracker.Config
+	}{
+		{
+			name:    "ConfigFile",
+			request: &proto.CreateVMRequest{},
+			config: &Config{
+				KernelArgs:      "KERNEL ARGS",
+				KernelImagePath: "KERNEL IMAGE",
+				RootDrive:       "ROOT DRIVE",
+				CPUTemplate:     "C3",
+				CPUCount:        2,
+			},
+			expectedCfg: &firecracker.Config{
+				KernelArgs:      "KERNEL ARGS",
+				KernelImagePath: "KERNEL IMAGE",
+				Drives: []models.Drive{
+					rootfsDrive,
+					{
+						DriveID:      firecracker.String("root_drive"),
+						PathOnHost:   firecracker.String("ROOT DRIVE"),
+						IsReadOnly:   firecracker.Bool(false),
+						IsRootDevice: firecracker.Bool(true),
+					},
+				},
+				MachineCfg: models.MachineConfiguration{
+					CPUTemplate: models.CPUTemplateC3,
+					VcpuCount:   2,
+					MemSizeMib:  defaultMemSizeMb,
+				},
+			},
+		},
+		{
+			name: "Input",
+			request: &proto.CreateVMRequest{
+				KernelArgs:      "REQUEST KERNEL ARGS",
+				KernelImagePath: "REQUEST KERNEL IMAGE",
+				RootDrive: &proto.FirecrackerDrive{
+					PathOnHost: "REQUEST ROOT DRIVE",
+				},
+				MachineCfg: &proto.FirecrackerMachineConfiguration{
+					CPUTemplate: "C3",
+					VcpuCount:   2,
+				},
+			},
+			config: &Config{},
+			expectedCfg: &firecracker.Config{
+				KernelArgs:      "REQUEST KERNEL ARGS",
+				KernelImagePath: "REQUEST KERNEL IMAGE",
+				Drives: []models.Drive{
+					rootfsDrive,
+					{
+						DriveID:      firecracker.String("root_drive"),
+						PathOnHost:   firecracker.String("REQUEST ROOT DRIVE"),
+						IsReadOnly:   firecracker.Bool(false),
+						IsRootDevice: firecracker.Bool(true),
+					},
+				},
+				MachineCfg: models.MachineConfiguration{
+					CPUTemplate: models.CPUTemplateC3,
+					VcpuCount:   2,
+					MemSizeMib:  defaultMemSizeMb,
+				},
+			},
+		},
+		{
+			name: "Priority",
+			request: &proto.CreateVMRequest{
+				KernelArgs: "REQUEST KERNEL ARGS",
+				RootDrive: &proto.FirecrackerDrive{
+					PathOnHost: "REQUEST ROOT DRIVE",
+				},
+				MachineCfg: &proto.FirecrackerMachineConfiguration{
+					CPUTemplate: "T2",
+					VcpuCount:   3,
+				},
+			},
+			config: &Config{
+				KernelArgs:      "KERNEL ARGS",
+				KernelImagePath: "KERNEL IMAGE",
+				CPUTemplate:     "C3",
+				CPUCount:        2,
+			},
+			expectedCfg: &firecracker.Config{
+				KernelArgs:      "REQUEST KERNEL ARGS",
+				KernelImagePath: "KERNEL IMAGE",
+				Drives: []models.Drive{
+					rootfsDrive,
+					{
+						DriveID:      firecracker.String("root_drive"),
+						PathOnHost:   firecracker.String("REQUEST ROOT DRIVE"),
+						IsReadOnly:   firecracker.Bool(false),
+						IsRootDevice: firecracker.Bool(true),
+					},
+				},
+				MachineCfg: models.MachineConfiguration{
+					CPUTemplate: models.CPUTemplateT2,
+					VcpuCount:   3,
+					MemSizeMib:  defaultMemSizeMb,
+				},
+			},
+		},
+	}
+
+	for _, tc := range testcases {
+		tc := tc // see https://github.com/kyoh86/scopelint/issues/4
+		t.Run(tc.name, func(t *testing.T) {
+			svc := &service{
+				namespace: namespace,
+				logger:    logrus.WithField("test", namespace+"/"+tc.name),
+				config:    tc.config,
+			}
+			// For values that remain constant between tests, they are written here
+			tc.expectedCfg.SocketPath = svc.shimDir().FirecrackerSockPath()
+			tc.expectedCfg.VsockDevices = []firecracker.VsockDevice{{Path: "root", CID: svc.machineCID}}
+			tc.expectedCfg.LogFifo = svc.shimDir().FirecrackerLogFifoPath()
+			tc.expectedCfg.MetricsFifo = svc.shimDir().FirecrackerMetricsFifoPath()
+
+			actualCfg, err := svc.buildVMConfiguration(tc.request)
+			assert.NoError(t, err)
+			require.Equal(t, tc.expectedCfg, actualCfg)
+		})
+	}
+}