kube-compose creates and destroys environments in Kubernetes based on docker compose files with an emphasis on CI use cases.
Add the tap:
brew tap kube-compose/homebrew-kube-composeInstall kube-compose:
brew install kube-composeTo upgrade kube-compose to the latest stable version:
brew upgrade kube-composeDownload the binary from https://github.com/kube-compose/kube-compose/releases, ensure it has execute permissions and place it on your PATH.
kube-compose targets a Kubernetes namespace, and will need a running Kubernetes cluster and a kube config file. If you do not have a running Kubernetes cluster, consider running one locally using:
kube-compose loads Kubernetes configuration the same way kubectl does.
To run kube-compose with the test docker-compose.yml:
kube-compose -f'test/docker-compose.yml' -e'myuniquelabel' upThe -e flag sets a unique identifier that is used to isolate labels and selectors and ensure names are unique when deploying to shared namespaces. This is ideal for CI where many jobs and test environments run simultaneously. The command will wait for pods and stream their logs to stdout. When run on a terminal, use ctrl + c to return control to the terminal.
Similar to docker-compose, an environment can be stopped and destroyed using the down command:
kube-compose -f'test/docker-compose.yml' -e'myuniquelabel' downThe CLI of kube-compose mirrors docker-compose as much as possible, but has some differences.
To avoid repeating the -e flag you can use the environment variable KUBECOMPOSE_ENVID. The above two commands can also be written as:
cd test
export KUBECOMPOSE_ENVID='myuniquelabel'
kube-compose -f'test/docker-compose.yml' upand
kube-compose -f'test/docker-compose.yml' downFor a full list of options and commands, run the help command:
kube-compose --helpThis section shows several examples of how kube-compose supports common CI use cases, in particular the following common system testing steps:
- Start environment
- Wait until the environment has fully started
- Run tests
- Stop environmnent
When performing system testing in CI, waiting until an application and any stubs/dependencies are ready is common task. kube-compose supports ordered startup and readiness waiting through depends_on with condition: service_healthy and healthchecks. This approach is powerful, because it does not require writing complicated startup scripts. NOTE: version 3 docker compose files do not support depends_on conditions anymore (see https://docs.docker.com/compose/startup-order/).
For example, if docker-compose.yml is...
version: '2.4'
services:
web:
image: web:latest
depends_on:
db:
condition: service_healthy
db:
image: db:latest
helper:
image: ubuntu:latest
depends_on:
web:
condition: service_healthy...then...
kube-compose up -d 'helper'...will create the environment and wait for the environment to be fully started. The service helper is used to to make sure that web is healthy as soon as kube-compose returns, so that the environment can be immediately used after the up command returns (e.g. to run system testing).
NOTE: in the background kube-compose converts Docker healthchecks to readiness probes and will only start service web when the pod of db is ready, and will only start helper when the pod of web is ready. The pod of helper exits immediately, but this pattern is simple and useful.
kube-compose currently supports basic simulation of docker's bind mounted volumes. This supports the use case of mounting configuration files into containers, which is a common way of parameterising containers.
For example, consider the following docker compose file:
version: '2.4'
services:
volumedemo:
image: 'ubuntu:latest'
entrypoint:
- /bin/bash
- -c
- |
echo 'Inception...'
cat /mnt/inception
volume:
- './docker-compose.yml:/mnt/inception:ro'
x-kube-compose:
cluster_image_storage:
type: 'docker'
volume_init_base_image: 'ubuntu:latest'It describes a service that prints the contents of the host file ./docker-compose.yml, using a bind mounted volume. When kube-compose's up command is run against the above file, kube-compose simulates the bind mounted volume by:
- Building a helper image with the relevant host files (in this case only
./docker-compose.yaml); - Running the helper image as an initContainer that initialises an emptyDir volume; -and
- Mounting the emptyDir volume into the main container at the configured mount path.
The additional x-kube-compose configuration is required so that:
kube-composeknows where to store docker images so that the Kubernetes cluster can run them.kube-composeknows which base image to use for helper images.
NOTE1: the x- is the prefix for extensions, standardized in docker-compose.
NOTE2: a cluster_image_storage with type: docker typically only works with Docker Desktop's Kubernetes cluster. See this section on how to configure other clusters.
- Volumes that are not bind mounted volumes are ignored.
- If a docker compose service makes changes in a mount of a bind mounted volume then those changes will not be reflected in the host file system, and vice versa.
- If docker compose services
s1ands2have mountsm1andm2, respectively, andm1andm2mount overlapping portions of the host file system, then changes inm1will not be reflected inm2(ifc1=c2then this can be implemented easily by mounting the same volume multiple times).
The third limitation implies that sharing volumes between two docker compose services is not supported, even though this could be implemented through persistent volumes.
Docker images and stubs run in CI often cannot be easily modified because they are provided by a third party, and the cluster's pod security policy can deny images from being run with the correct user. For this reason, kube-compose allows you to use the --run-as-user flag:
kube-compose up --run-as-userThis will set each pod's runAsUser (and runAsGroup) based on the user property of the docker-compose service and the USER configuration of the docker image. Consequently, additional privileges are required by the deployer when this flag is set, but it is an easy way of making CI "just work".
NOTE1: if a Dockerfile does not have a USER instruction, then the user is inherited from the base image. This makes it very easy to run images as root.
NOTE2: at first glance this is a useless feature, because if the deployer has permissions to create pods running as any user then the user of the image is respected already. But the user property of a docker-compose service can only be properly implemented by setting runAsUser (and runAsGroup), and the --run-as-user flag will enable early errors when the deployer has insufficient permissions.
When running tests against a dynamic environment that runs in a shared namespace, the test configuration will need to be generated. kube-compose has a get command that prints the .svc hostnames of services.
Suppose for example that a docker-compose service named my-service has been deployed to a Kubernetes namespace named mynamespace, and the environment id was set to myenv. Then the command...
kube-compose -e'myenv' get 'my-service' -o'{{.Hostname}}'...will output...
my-service-myenv.mynamespace.svc.cluster.localNOTE: a Kubernetes service will only be created for docker-compose services that have ports.
- The
upsubcommand does not build images ofdocker-composeservices if they are not present locally (#188). - Volumes: see this section.
x-kube-compose is an additional configuration section in docker compose files. It is required by kube-compose's simulation of bind mounted volumes (see Volumes), and it can also be set to make kube-compose push images to a different docker registry as part of deployments. For example, consider the following docker compose file:
version: '3'
services:
service1:
image: 'docker-registry.example.com/ubuntu:latest'
x-kube-compose:
cluster_image_storage:
type: 'docker_registry'
host: 'docker-registry.openshift-cluster.example.com'
volume_init_base_image: 'docker-registry.example.com/ubuntu:latest'The volume_init_base_image configuration item specifies the base image of helper images built to implement bind mounted volumes. This option is useful for corporate networks that do not have a proxy or docker registry mirror available. The base image must have bash and cp installed.
The cluster_image_storage configuration item includes the field type which must be either docker or docker_registry, denoting a docker daemon or a docker registry. The former can be used when deploying to Docker Desktop's cluster. The latter also implies that a field host (the host of the docker registry) must be included.
Currently kube-compose can only push to docker registries that are configured like OpenShift's default docker registry. In particular, kube-compose makes the following assumptions when the image storage location is a docker registry:
- Within the cluster the hostname of the docker registry is assumed to be
docker-registry.default.svc:5000. - The kube configuration is assumed to have bearer token credentials, that are supplied as the password to the docker registry (the username will be
unused). If the docker registry is unauthenticated then this authentication should be ignored. - References to pushed images have the form
<registry>/<project>/<imagestream>:latest, as required by OpenShift.
When specifying multiple files on the command line, the x-kube-compose section will also be merged.
go build .Install the linter if you do not have it already:
brew install golangci-lintRun the linter:
golangci-lint run --deadline=30mTo run unit tests:
go test ./...To run unit tests with code coverage:
go test -coverpkg=./... -coverprofile=coverage.out ./...
go tool cover -html=coverage.outUse kubectl to set the target Kubernetes namespace and the service account of kube-compose.
Run kube-compose with the test docker-compose.yml:
kube-compose -f test/docker-compose.yml --env-id test123 upTo clean up after the test:
kube-compose down