Over the last weekend I had occasion to unshelve a Ruby-on-Rails project that I had worked on in 2015. It was a great glimpse into the state of the art of the time. I had chosen some forward leaning but hopefully future-proof tech for the infra side of things. We’d been deploying to Heroku for test and prod environments but leveraged Docker for our development environments.
One of the last few commits to the repo added support for Docker Compose which allowed us to stand up the full stack with a simple command:
- Ruby-on-Rails single container web app
- Postgres database
- Redis for caching
The configuration file (docker-compose.yml) was only about 25 lines that looked like this
web: build: . command: ./bin/rails server -p 3000 -b 0.0.0.0 environment: REDISCLOUD_URL: redis://redis DATABASE_URL: postgres://postgres@db/development volumes: - .:/myapp links: - db - redis ports: - "3000:3000" db: image: postgres:9.4.1 ports: - "5432:5432" redis: image: redis ports: - "6379:6379"
By running the
docker-compose up command you could get a reproducible version of the app on your local laptop. I was wary that after 6 years, none of this would work. We all know how much software rots when it isn’t looked after. Much to my surprise the whole stack came up in short order because I had done some work to pin versions of my Ruby environment, Postgresql version and dependencies (Gemfile).
Docker has been a huge leap forward for the ability to nail down a point in time version of a stack like this.
Transition to Continuous Development with Skaffold
With this setup, we had setup Rails to do hot code reloading so that when we changed the business logic on our dev branches it would automatically update in the running app. This was awesome for quickly iterating and testing any changes you were making but when you wanted to update the image or add a dependency you had to stop the running app rebuild the Docker image.
Once I had gotten myself back to the best-of-breed dev setup of 2015, I decided to see what it would take to get a representative environment on the latest and greatest dev tools of today. Obviously I am bit biased, so I turned my attention to figuring out how to port the Docker Compose setup to Skaffold and Minikube.
Many folks are familiar with Minikube, which lets you quickly and efficiently stand up a Kubernetes cluster as either a container running in Docker or a VM running on your machine.
Skaffold is a tool that lets you get a hot-code reloading feel for your apps running on Kubernetes. Skaffold watches your filesystem and as things change it does the right thing to make sure your app is updated as needed. For example, changes to your Dockerfile or any of your app code will cause a rebuild of your container image and it will be redeployed to your minikube cluster.
Below is a diagram showing the before (Docker Compose) and after (Minikube and Skaffold):
The discerning eye will look at these two diagrams and notice that the Kubernetes YAML files are new and that we have a new config file to tell Skaffold how to build and deploy our app.
To create my initial Skaffold configuration all I needed to do was run the
skaffold init command which tries to detect the Dockerfiles and Kubernetes YAMLs in my app folder and then lets me pair them up to create my Skaffold YAML file. Since I didn’t yet have Kubernetes YAML, I passed in my Docker Compose file so that Skaffold would also provide me an initial set of Kubernetes manifests.
skaffold init --compose-file docker-compose.yml
In this section I’ll walk you through the same process with a readily available application. In this case, we’ll be using Taiga which is an open source “project management tool for multi-functional agile teams”. I found Taiga by searching on GitHub for docker-compose.yml files to test my procedure with.
If you have a Google account and want to run the tutorial interactively in a free sandbox environment click here:
- Install Skaffold, Minikube and Kompose. Kompose is used by Skaffold to convert the docker-compose.yml into Kubernetes manifests.
- Start minikube.
3. Clone the docker-taiga repository which contains the code necessary to get Taiga up and running with Docker Compose.
git clone https://github.com/docker-taiga/taiga cd taiga
4. The Docker Compose file in
docker-taiga doesn’t set ports for all the services which is required for proper discoverability when things are converted to Kubernetes. Apply the following patch to ensure each service exposes its ports properly.
cat > compose.diff <<EOF diff --git a/docker-compose.yml b/docker-compose.yml index e09d717..94920c8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,0 +13,2 @@ services: + ports: + - 80:8000 @@ -22,0 +25,2 @@ services: + ports: + - 80:80 @@ -33,0 +38,2 @@ services: + ports: + - 80:80 @@ -46,0 +53,2 @@ services: + ports: + - 5432:5432 @@ -55,0 +64,2 @@ services: + ports: + - 5672 diff --git a/variables.env b/variables.env index 4e48c17..8de5b09 100644 --- a/variables.env +++ b/variables.env @@ -1 +1 @@ -TAIGA_HOST=taiga.lan +TAIGA_HOST=localhost @@ -3 +3 @@ TAIGA_SCHEME=http -TAIGA_PORT=80 +TAIGA_PORT=4506 EOF patch -p1 < compose.diff
5. Next, we’ll bring in the source code for one of Taigas components that we want to develop on, in this case the backend. We’ll also set our development branch to the
6.0.1 tag that the Compose File was written for.
git clone https://github.com/taigaio/taiga-back -b 6.0.1
6. Now that we have our source code, Dockerfile, and Compose File, we’ll run
skaffold init to generate the
skaffold.yaml and Kubernetes manifests.
skaffold init --compose-file docker-compose.yml
Skaffold will ask which Dockerfiles map to the images in the Kubernetes manifests.
The default answers are correct for all questions except the last. Make sure to answer yes (y) when it asks if you want to write out the file.
? Choose the builder to build image dockertaiga/back Docker (taiga-back/docker/Dockerfile) ? Choose the builder to build image dockertaiga/events None (image not built from these sources) ? Choose the builder to build image dockertaiga/front None (image not built from these sources) ? Choose the builder to build image dockertaiga/proxy None (image not built from these sources) ? Choose the builder to build image dockertaiga/rabbit None (image not built from these sources) ? Choose the builder to build image postgres None (image not built from these sources) apiVersion: skaffold/v2beta12 kind: Config metadata: name: taiga build: artifacts: - image: dockertaiga/back context: taiga-back/docker docker: dockerfile: Dockerfile deploy: kubectl: manifests: - kubernetes/back-claim0-persistentvolumeclaim.yaml - kubernetes/back-claim1-persistentvolumeclaim.yaml - kubernetes/back-deployment.yaml - kubernetes/db-claim0-persistentvolumeclaim.yaml - kubernetes/db-deployment.yaml - kubernetes/default-networkpolicy.yaml - kubernetes/events-deployment.yaml - kubernetes/front-claim0-persistentvolumeclaim.yaml - kubernetes/front-deployment.yaml - kubernetes/proxy-claim0-persistentvolumeclaim.yaml - kubernetes/proxy-deployment.yaml - kubernetes/proxy-service.yaml - kubernetes/rabbit-deployment.yaml - kubernetes/variables-env-configmap.yaml ? Do you want to write this configuration to skaffold.yaml? Yes
We’ll also fix an issue with the Docker context configuration that Skaffold interpreted from the Compose File. The
taiga-back repo keeps its Dockerfile in a sub-folder rather than the top-level and uses the context from the root. Also
sed -i 's/context:.*/context: taiga-back/' skaffold.yaml sed -i 's/dockerfile:.*/dockerfile: docker\/Dockerfile/' skaffold.yaml sed -i 's/name: TAIGA_SECRET/name: TAIGA_SECRET_KEY/' back-deployment.yaml
7. Now we’re ready to run Skaffold’s dev loop to continuously re-build and re-deploy our app as we make changes to the source code. Skaffold will also display the logs of the app and even port-forward important ports to your machine.
You should start to see the backend running the database migrations necessary to start the app and be able to get to the web app via http://localhost:4056
skaffold dev --port-forward
8. To initialize the first user, run the following command in a new terminal and then log in to Taiga with the username admin and password 123123.
kubectl exec deployment/back python manage.py loaddata initial_user
Congrats! You’ve now transitioned your development tooling from Docker Compose to Skaffold and Minikube.