diff --git a/projects/compose/.gitignore b/projects/compose/.gitignore new file mode 100644 index 000000000..f2a767b3f --- /dev/null +++ b/projects/compose/.gitignore @@ -0,0 +1,2 @@ +image-cache/ +dist/ diff --git a/projects/compose/Makefile b/projects/compose/Makefile new file mode 100644 index 000000000..43efc9c6f --- /dev/null +++ b/projects/compose/Makefile @@ -0,0 +1,49 @@ +.PHONY: tag push hash dynamic static cache-images run-static run-dynamic + +ORG?=linuxkitprojects +IMAGE=compose +DEPS=image/Dockerfile docker-compose.yml image/waitfordocker.sh image/load-images-and-compose.sh +COMMON_IMAGES := \ + nginx:alpine \ + traefik + + +HASH?=$(shell git ls-tree HEAD -- ./image | awk '{print $$3}') + +hash: + @echo ${HASH} + +tag: $(DEPS) + docker build --squash --no-cache -t $(ORG)/$(IMAGE):$(HASH) image/ + +push: tag + DOCKER_CONTENT_TRUST=1 docker pull $(ORG)/$(IMAGE):$(HASH) || \ + DOCKER_CONTENT_TRUST=1 docker push $(ORG)/$(IMAGE):$(HASH) + +dynamic: + mkdir -p dist + moby build -name compose-dynamic -dir dist/ compose-dynamic.yml + +static: cache-images + mkdir -p dist + moby build -name compose-static -dir dist/ compose-static.yml + +run-dynamic: + linuxkit run dist/compose-dynamic + +run-static: + linuxkit run dist/compose-static + +clean: + rm -rf image-cache + +image-cache/%.tar: + mkdir -p $(dir $@) + DOCKER_CONTENT_TRUST=1 docker image pull $(shell basename $@ .tar) + docker image save -o $@ $(shell basename $@ .tar) + +# use make here for each image rather than a single for loop so we can cache dependencies +cache-images: + for image in $(COMMON_IMAGES) ; do \ + make "image-cache/$${image}.tar" ; \ + done diff --git a/projects/compose/README.md b/projects/compose/README.md new file mode 100644 index 000000000..0049e0864 --- /dev/null +++ b/projects/compose/README.md @@ -0,0 +1,57 @@ +# Compose Project + +The purpose of this project is to show how moby and linuxkit can be used to build a runnable linuxkit image with compose-style apps ready-to-run. + +The apps are simple: + +* nginx serving app A +* nginx serving app B +* traefik routing on the basis of hostname between apps A and B + + +## Compose Methods +We provide samples of two methods for using compose: dynamic and static. + +Both methods use the image `linuxkit/compose`. The image does the following: + +1. Wait for compose to be ready. +2. If there are any tar files available in `/images/*.tar`, treat them as tarred up docker images and load them into docker via `docker load ...` +3. Run `docker compose ...` + +The only difference between dynamic and static is whether or not container images are pre-loaded in the linuxkit image. + +* Compose: the `linuxkit/compose` image looks for a compose file at `/compose/docker-compose.yml` +* Images: the `linuxkit/compose` image looks for tarred container images at `/compose/images/*.tar` + +### Dynamic +Dynamic loads the _compose_ config into the linuxkit image at build time. Container images are not pre-loaded, and thus docker loads the container images from the registry **at run-time**. This is no different than doing the following: + +1. Using the docker linuxkit image +2. Connecting remotely via the docker API +3. Running the compose file remotely + +Except that the compose is run at launch time, and there is no need for a remote connection to the docker API. + +It works by loading the `docker-compose.yml` file onto the linuxkit image, and making it available to the `compose` container image via a bind-mount. + +To build a dynamic image, do `make dynamic`. To run it, do `make run-dynamic`. + +### Static +Static loads the _compose_ config **and** the container _images_ into the linuxkit image at build time. When run, docker loads the images from its local cache and does not depend on access to a registry. + +It works by loading the `docker-compose.yml` file onto the linuxkit image and tarred up container image files. It then makes them available to the `compose` container image via bind-mounts. + + +To build a static image, do `make static`. To run it, do `make run-static`. + +Static images pre-load them by doing: + +1. Download the image locally `docker image pull ` +2. Save the image to a local tar file `docker image save -o .tar` +3. copy the tar file to the container image +4. When starting the container from the image with the files, load the images into docker before running compose: `docker image load -i && rm -f .tar` + +### Conversion +A final option would be converting all of the containers defined in a `docker-compose.yml` into linuxkit `services`. It also would require setting up appropriate networks and other services provided by docker when running compose. + +An example may be added in the future. diff --git a/projects/compose/compose-dynamic.yml b/projects/compose/compose-dynamic.yml new file mode 100644 index 000000000..2ec0ce2bb --- /dev/null +++ b/projects/compose/compose-dynamic.yml @@ -0,0 +1,56 @@ +kernel: + image: "linuxkit/kernel:4.9.x" + cmdline: "console=ttyS0 page_poison=1" +init: + - linuxkit/init:1b8a7e394d2ec2f1fdb4d67645829d1b5bdca037 + - linuxkit/runc:3a4e6cbf15470f62501b019b55e1caac5ee7689f + - linuxkit/containerd:b1766e4c4c09f63ac4925a6e4612852a93f7e73b + - linuxkit/ca-certificates:75cf419fb58770884c3464eb687ec8dfc704169d +onboot: + - name: sysctl + image: "linuxkit/sysctl:3aa6bc663c2849ef239be7d941d3eaf3e6fcc018" + - name: sysfs + image: linuxkit/sysfs:1244c5a86dfa2318c4e304af68d37e12367e1b7f + - name: dhcpcd + image: "linuxkit/dhcpcd:7d2b8aaaf20c24ad7d11a5ea2ea5b4a80dc966f1" + command: ["/sbin/dhcpcd", "--nobackground", "-f", "/dhcpcd.conf", "-1"] + - name: binfmt + image: "linuxkit/binfmt:8ac5535f57f0c6f5fe88317b9d22a7677093c765" + - name: format + image: "linuxkit/format:180cb2dc1de5e60373385080f8148abf10a3afac" + - name: mount + image: "linuxkit/mount:ff5338822f20375b8913f5a80f9ed4f6ea9a592b" + command: ["/mount.sh", "/var/lib/docker"] +services: + - name: rngd + image: "linuxkit/rngd:1fa4de44c961bb5075647181891a3e7e7ba51c31" + - name: ntpd + image: "linuxkit/openntpd:45deeb05f736162d941c9bf494983f655ab80aa5" + - name: docker + image: "linuxkit/docker-ce:668d62da6e3da081a8f8aca7db3e2a98adf5da59" + capabilities: + - all + net: host + mounts: + - type: cgroup + options: ["rw","nosuid","noexec","nodev","relatime"] + binds: + - /var/lib/docker:/var/lib/docker + - /lib/modules:/lib/modules + - /var/run:/var/run + - /var/html:/var/html + - name: compose + image: "linuxkitprojects/compose:0535e78608f57702745dfd56fbe78d28d237e469" + binds: + - /var/run:/var/run + - /var/compose:/compose +files: + - path: var/html/a/index.html + source: html-a.html + - path: var/html/b/index.html + source: html-b.html + - path: var/compose/docker-compose.yml + source: docker-compose.yml +trust: + org: + - linuxkit diff --git a/projects/compose/compose-static.yml b/projects/compose/compose-static.yml new file mode 100644 index 000000000..8d818ba73 --- /dev/null +++ b/projects/compose/compose-static.yml @@ -0,0 +1,60 @@ +kernel: + image: "linuxkit/kernel:4.9.x" + cmdline: "console=ttyS0 page_poison=1" +init: + - linuxkit/init:1b8a7e394d2ec2f1fdb4d67645829d1b5bdca037 + - linuxkit/runc:3a4e6cbf15470f62501b019b55e1caac5ee7689f + - linuxkit/containerd:b1766e4c4c09f63ac4925a6e4612852a93f7e73b + - linuxkit/ca-certificates:75cf419fb58770884c3464eb687ec8dfc704169d +onboot: + - name: sysctl + image: "linuxkit/sysctl:3aa6bc663c2849ef239be7d941d3eaf3e6fcc018" + - name: sysfs + image: linuxkit/sysfs:1244c5a86dfa2318c4e304af68d37e12367e1b7f + - name: dhcpcd + image: "linuxkit/dhcpcd:7d2b8aaaf20c24ad7d11a5ea2ea5b4a80dc966f1" + command: ["/sbin/dhcpcd", "--nobackground", "-f", "/dhcpcd.conf", "-1"] + - name: binfmt + image: "linuxkit/binfmt:8ac5535f57f0c6f5fe88317b9d22a7677093c765" + - name: format + image: "linuxkit/format:180cb2dc1de5e60373385080f8148abf10a3afac" + - name: mount + image: "linuxkit/mount:ff5338822f20375b8913f5a80f9ed4f6ea9a592b" + command: ["/mount.sh", "/var/lib/docker"] +services: + - name: rngd + image: "linuxkit/rngd:1fa4de44c961bb5075647181891a3e7e7ba51c31" + - name: ntpd + image: "linuxkit/openntpd:45deeb05f736162d941c9bf494983f655ab80aa5" + - name: docker + image: "linuxkit/docker-ce:668d62da6e3da081a8f8aca7db3e2a98adf5da59" + capabilities: + - all + net: host + mounts: + - type: cgroup + options: ["rw","nosuid","noexec","nodev","relatime"] + binds: + - /var/lib/docker:/var/lib/docker + - /lib/modules:/lib/modules + - /var/run:/var/run + - /var/html:/var/html + - name: compose + image: "linuxkitprojects/compose:0535e78608f57702745dfd56fbe78d28d237e469" + binds: + - /var/run:/var/run + - /var/compose:/compose +files: + - path: var/html/a/index.html + source: html-a.html + - path: var/html/b/index.html + source: html-b.html + - path: var/compose/docker-compose.yml + source: docker-compose.yml + - path: var/compose/images/nginx:alpine.tar + source: image-cache/nginx:alpine.tar + - path: var/compose/images/traefik.tar + source: image-cache/traefik.tar +trust: + org: + - linuxkit diff --git a/projects/compose/docker-compose.yml b/projects/compose/docker-compose.yml new file mode 100644 index 000000000..f8d96e22a --- /dev/null +++ b/projects/compose/docker-compose.yml @@ -0,0 +1,25 @@ +version: '2.1' +services: + a: + image: nginx:alpine + volumes: + - /var/html/a:/usr/share/nginx/html + labels: + - "traefik.backend=a" + - "traefik.frontend.rule=Host:a.docker.localhost" + b: + image: nginx:alpine + volumes: + - /var/html/b:/usr/share/nginx/html + labels: + - "traefik.backend=b" + - "traefik.frontend.rule=Host:b.docker.localhost" + - "traefik.port=80" + proxy: + image: traefik + command: --web --docker --docker.domain=docker.localhost --logLevel=DEBUG + ports: + - "80:80" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /dev/null:/traefik.toml diff --git a/projects/compose/html-a.html b/projects/compose/html-a.html new file mode 100644 index 000000000..c279a82ef --- /dev/null +++ b/projects/compose/html-a.html @@ -0,0 +1,8 @@ + + + A index + + + This is the index for A + + diff --git a/projects/compose/html-b.html b/projects/compose/html-b.html new file mode 100644 index 000000000..250a357c7 --- /dev/null +++ b/projects/compose/html-b.html @@ -0,0 +1,8 @@ + + + B index + + + This is the index for B + + diff --git a/projects/compose/image/Dockerfile b/projects/compose/image/Dockerfile new file mode 100644 index 000000000..80dfddbc6 --- /dev/null +++ b/projects/compose/image/Dockerfile @@ -0,0 +1,27 @@ +FROM docker/compose:1.13.0 +# because compose requires all sorts of dynamic libs, including glibc, it is much easier to +# add docker client to compose than the reverse + +ENV DOCKER_BUCKET get.docker.com +ENV DOCKER_VERSION 17.05.0-ce +ENV DOCKER_SHA256 340e0b5a009ba70e1b644136b94d13824db0aeb52e09071410f35a95d94316d9 + +# we need docker compose and docker load +# also need curl to test availability of docker API +RUN apk add --update curl + +# we only need the client +RUN set -x \ + && curl -fSL "https://${DOCKER_BUCKET}/builds/Linux/x86_64/docker-${DOCKER_VERSION}.tgz" -o docker.tgz \ + && echo "${DOCKER_SHA256} *docker.tgz" | sha256sum -c - \ + && tar -xzvf docker.tgz \ + && mv docker/docker /usr/bin/ \ + && rm -rf docker docker.tgz \ + && docker -v + + +RUN mkdir -p /compose /app +WORKDIR /app +COPY . /app +ENTRYPOINT ["/app/waitfordocker.sh"] +CMD ["/app/load-images-and-compose.sh"] diff --git a/projects/compose/image/load-images-and-compose.sh b/projects/compose/image/load-images-and-compose.sh new file mode 100755 index 000000000..d8a3e9456 --- /dev/null +++ b/projects/compose/image/load-images-and-compose.sh @@ -0,0 +1,17 @@ +#!/bin/sh +set -e + +######### +# +# load any cached mounted images, and run compose +# +######## + +[ -n "$DEBUG" ] && set -x + +for image in /compose/images/*.tar ; do + docker image load -i $image && rm -f $image +done + + +docker-compose -f /compose/docker-compose.yml up -d diff --git a/projects/compose/image/waitfordocker.sh b/projects/compose/image/waitfordocker.sh new file mode 100755 index 000000000..d053f3d72 --- /dev/null +++ b/projects/compose/image/waitfordocker.sh @@ -0,0 +1,47 @@ +#!/bin/sh +set -e + +######### +# +# wait for docker socket to be ready, then run the rest of the command +# +######## +RETRIES=${RETRIES:-"-1"} +WAIT=${WAIT:=10} +[ -n "$DEBUG" ] && set -x + +# keep retrying until docker is ready or we hit our limut +retry_or_fail() { + local retry_count=0 + local success=1 + local cmd=$1 + local retryMax=$2 + local retrySleep=$3 + local message=$4 + until [[ $retry_count -ge $retryMax && $retryMax -ne -1 ]]; do + echo "trying to $message" + set +e + $cmd + success=$? + set -e + [[ $success == 0 ]] && break + retry_count=$(( $retry_count+1 )) || true + echo "attempt number $retry_count failed to $message, sleeping $retrySleep seconds..." + sleep $retrySleep + done + # did we succeed? + if [[ $success != 0 ]]; then + echo "failed to $message after $retryMax tries. Exiting..." >&2 + exit 1 + fi +} + +connect_to_docker() { + [ -S /var/run/docker.sock ] || return 1 + curl --unix-socket /var/run/docker.sock http://localhost/containers/json >/dev/null 2>&1 || return 1 +} +# try to connect to docker +retry_or_fail connect_to_docker $RETRIES $WAIT "connect to docker" + +# if we got here, we succeeded +$@