We use Kubernetes to run our applications. Moreover, we make use of Flux v2, which allows us to manage kubernetes resources as code that is checked into this repository, following the concept of GitOps, but more on that below.
This repository contains a couple of so-called Kustomizations (basically kubernetes yaml files on steroids), specifying which kubernetes resources should exist on our cluster.
These resource definitions are picked up by Flux v2, which creates, updates and deletes corresponding objects on our cluster as needed. The Flux application itself is not deployed on an external server, but also running directly within our cluster, with its own resources also being managed through this repository.
Flux performs one more crucial task to make this whole setup work: Let's suppose you're working on an app that is deployed here, and you create new releases of your app from time to time. Ideally, you don't want to manually update your app's version tag everytime you build and push a new container image. This is why flux can be told to watch specific images in a docker registry, or specific charts in a helm registry, for updates, and automatically update and commit the latest tag to this repository, even following sophisticated versioning schemes, if you want.
Refer to the Flux v2 docs for a more detailed overview regarding flux's features.
This guide assumes that you want to deploy a simple app, consisting of one docker container that you want to expose at a (sub-)domain under your control. If you need anything else for your app (e.g. multiple containers, a persisted disk, etc.), or this guide is too hard to follow, feel free to contact @juliuste directly, or open an issue on the issues page. We're more than happy to help, and don't be discouraged even if you don't understand anything, that happens to a lot of people working with the kubernetes ecosystem for the first time (or even the 100th time 😅), so don't worry.
Ok, now back to the instructions. To deploy your app, you need to do a couple of things in this repository and one thing in your app's repo.
In your app's repository, you should set up a CI task to automatically build and publish a docker image containing the latest code to a docker registry of your choice. Before publishing, the built image needs to be tagged as v1_<first-seven-characters-of-git-commit-hash>_<date-as-YYYY-MM-DDTHH.mm.ssZ>, so e.g. v1_abcdef0_2021-10-08T12.44.03Z. Instead of implementing this yourself, though, you can just refer to our example-deployment and copy .github/workflows/ci.yaml from there, which publishes to GitHub's container registry out of the box, without the need for any configuration. 😎
- Pick an identifier for your app. Just some string containing only lowercase characters and hyphens (
-), that makes it clear to other people reading the code what app they're looking at. You will need that identifier in the following steps, we'll just usesome-appas an example. - Go to the
kubernetes/appsdirectory and clone theexample-appfolder to a new directory calledsome-app(your identifier). - Open the file
release.yamlin thesome-appdirectory you just created. There are a handful of comments beginning with the term[MODIFY]in there, indicating fields that you need to modify and explaining what you need to fill them with, go ahead with that. - Open the file
kubernetes/apps/kustomization.yaml, which contains a list of all deployed apps. Add your app's identifier there (e.g.some-app) below theexample-app. - If your app needs to use some secret environment variables, … docs still TBD (for now, contact @juliuste to help you in this case, we already have a system set up allowing you to upload secrets to git in an encrypted manner, docs will be added here later)
- Create a PR with your changes. Your app should be deployed once that PR is merged.
- Create a
CNAMErecord for your domain totilia.cluster.infra.public-transport.earth. If you can't use aCNAMErecord, create anArecord to142.132.243.125and anAAAArecord to2a01:4f8:c01e:71c::1instead.
As mentioned before, your app will be updated automatically as long as you publish docker images tagged in the format described above. However, there might be times when you want to change some configuration here first instead of updating your app automatically. For these situations, our docker tags include versioning information, by default v1_…. If you want to introduce a breaking change to your app, you can do so as follows:
- Update your app's repository to tag docker images with
v2_…instead ofv1_… - Make any required changes to the infrastructure defined in this repo
- In your app's
release.yaml, under theimagesection, add the optiontagUpdatePattern: '^v2_[a-fA-F0-9]{7}_(?P<datetime>.*)Z$'. This replaces the regex determinining which docker tags your app can be upgraded to, flux will then automatically install the latest image tagged withv2_…
Some of the most common reasons for using Kubernetes are the fault-tolerance and scalability you can achieve for your deployments. One disclaimer in that regard: By default, apps you add here don't scale automatically. The reason for this is that our infrastructure (meaning the number of worker machines) also doesn't scale up automatically for now, so having apps "autoscale" by default might cause misunderstandings and a false sense of security. However, enabling autostaling can still make sense, even on a finite pool of machines, and is very easy with our setup, only requiring a bit of additional configuration in your app's release.yaml (tbd).
You only need to read this if you're interested in doing anything beyond deploying simple apps.
The basic structure of the kubernetes directory is as follows:
.chartscontains our own custom helm charts, which help us to reduce code and complexity overall. These charts are automatically packaged and published by our CI using GitHub releases and GitHub pagesappscontains all app-specific resources (except secrets)secretscontains secrets encrypted with the public key insecrets/.sops.pub.ascinfrastructurecontains underlying components required by most/all apps, such as tls certificate issuing or log collectionclusters/tiliais the "entrypoint" for our cluster, all required resources are imported/referenced here from the other directories listed above. Think of everything inclusters/tiliaas the cluster'spackage.json, just split up into different files 😄 We could - in theory - also define other clusters besidestiliain theclustersdirectory, however we currently only need this for short-term maintenance/migration purposes.
- Logging (docs missing)
- Metrics