# Infrastructure management
In this guide, basic knowledge of Helm (opens new window) releases and Kubernetes (opens new window) concepts are assumed.
Infrastructures are managed using the kaptain script. This script is a bash script wrapping calls to Helmfile (opens new window) adding policies we found interesting.
Helmfile is a tool that sits on top of Helm, it allows to manage a set of Helm releases through it's helmfile.yaml configuration file. With this set of tools (Helmfile, Helm and Kubernetes), it becomes possible to setup a source repository where configuration for a whole infrastructure is checked under revision. Managing what's deployed on the cluster, configuration options and every other infrastructure aspect now goes through editing of the proper yaml file (or template).
# The kaptain script
The kaptain script provides the following commands:
- config to install only the Helm releases that are labelled as
configfor the infrastructure - install to install all the releases required by the infrastructure.
- uninstall to undo what install did (minus the config releases).
- provision to setup and trigger the job that will provision data on the infrastructure (this is optional)
- diff to diff what's about to be installed through the install command with what's currently deployed on the infrastructure
Internally, kaptain calls helmfile with the proper set of options.
- config will ask helmfile to deploy all releases labelled as
action: configin thehelmfile.yaml - install will ask helmfile to deploy all releases labelled as
action: installin thehelmfile.yaml. Usually there's a direct dependency between releases labelled for install and those labelled for config, meaning it will first deploy configs, and then proceed to install. - provision will ask helmfile to deploy all releases labelles as
action: provision.
Theses labels (opens new window) are the only requirements of the helmfile.yaml to be usable with kaptain.
# Sample infrastructure definition
Let's start with a folder where we'll define our infrastructure. The first file to add is helmfile.yaml.
# Declare required helm repositories here
repositories:
# Bitnami
- name: bitnami
url: https://charts.bitnami.com/bitnami
# Others ...
- name: foo
url: https://charts.foo.bar/blah
We often have two additional yaml files along the helmfile :
global.yamlis where we put a bunch of infrastructure scope variables, we want them to be available in Helm and Helmfile templates.secret.yamlis where we put a bunch of infrastructure scope secrets, we usually deploy a secret named as the infrastructure where we pack all those values.
In order to use values from global.yaml in our helmfile templates, we usually add the following to the helmfile.yaml:
# Values listed here will be available in _helmfile_ templates
values:
- global.yaml
# This one is defined by kaptain, as it is required, helmfile will stop if it's undefined
- namespace: {{ requiredEnv "KAPTAIN_NAMESPACE" }}
And to use those values from helm templates, we usually factorize configuration using the following :
# This factorize reusable definitions
templates:
install: &install
# namespace where to deploy
namespace: {{ .Values.namespace }}
values:
# where to lookup release values.yaml (.gotmpl mean that helmfile must run it throught template evaluation)
- configs/{{`{{ .Release.Name }}`}}/values.yaml.gotmpl
# also add values from global.yaml in Helm values
- global.yaml
set:
- name: global.secret
value: {{ .Values.namespace }}
# selector label
labels:
action: install
# install _after_ provision-configs
needs:
- provision-configs
This block defines a set of default configuration options for releases we'll label using action: install :
- the namespace the release will be deployed into:
namespace: { { .Values.namespace } }. This is a Helmfile template, and values are looked up from what we defined with thevaluesblock earlier. - where to lookup release values. From our
global.yamlfile and from a file namedvalues.yaml.gotmpllocated in a subfolder named as the release in theconfigsfolder. - it also defines a special value named
global.secretwe use in our kargo (opens new window) charts. - it defines the release label(s). Here it's
action: installwhich means it's a release that'll get deployed as part ofkaptain install - a direct dependency on the
provision-configsrelease.
With all this in place, we can start adding releases to deploy in our helmfile.yaml :
releases:
# This chart is _always_ installed first
- name: provision-configs
chart: provision-configs
namespace: {{ .Values.namespace }}
values:
- global.yaml
- secret.yaml
- configs/{{`{{ .Release.Name }}`}}/values.yaml.gotmpl
labels:
action: config
- name: redis
chart: bitnami/redis
version: ~15.7.0
<<: *install
- name: redis-commander
chart: kalisio/redis-commander
<<: *install
redis and redis-commander are releases sharing the install definitions we added earlier.
provision-configs is a local chart labelled using action: config. It'll be installed first (because install labelled releases depend on it). We usually use this chart to deploy a bunch of infrastructure scope kubernetes manifests such as the rclone config secret, config maps for charts that don't generate their own and instead rely on user generated objects, the infrastructure secret built from the secret.yamlfile, various cronjobs ...
# Sample provision-configs chart
This chart can be used to deploy infrastructure config objects before other releases are installed. This is where you can add all the custom kubernetes manifests you may need. In the previous sample, the provision-configs release was defined as :
releases:
# This chart is _always_ installed first
- name: provision-configs
chart: provision-configs
namespace: {{ .Values.namespace }}
values:
- global.yaml
- secret.yaml
- configs/{{`{{ .Release.Name }}`}}/values.yaml.gotmpl
labels:
action: config
This states that we'll deploy the chart in the same namespace as other charts, that content from global.yaml and secret.yaml will be available to Helm templates, along with provision-config's values.yaml. It also defines the action: config label which mean this will be deployed as part of kaptain config.
We ususally find at least the following manifests in it's template folder :
# namespace-secret.yaml
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: {{ .Release.Namespace }}
stringData:
{{- toYaml .Values.secret | nindent 2 }}
This will contain all the secrets from the secret.yaml file.
# namespace-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Namespace }}
data:
{{ .Values.configKeys | toYaml | indent 2 }}
This will embed everything defined under configKeys in the release values.yaml
# rclone-config-secret.yaml
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: rclone-config
stringData:
rclone.conf: |
{{ (.Files.Get ".kaptain/rclone.conf") | indent 4 }}
This will create a secret from the rclone.conf file.
# Kaptain's hooks
Before calling helmfile with the proper set of options, the kaptain script runs user defined hooks. These are defined in the hooks.sh file. This file is sourced from the main kaptain script with the hook name as first parameter. The following environment variables are available in the hooks:
- KAPTAIN_NAMESPACE: the namespace we're deploying releases into
- KAPTAIN_NAMESPACE_DIRECTORY: the directory where the helmfile.yaml is located
- KAPTAIN_WORKING_DIRECTORY: the directory from where we started the kaptain script
Hooks are defined as pre-$ACTION and post-$ACTION where $ACTION is one of the kaptain's command (install, config, ...). There's an additional hook, named make-config which is called prior to install, provision, config and diff commands. This hook is commonly used to setup local charts, such as provision-configs. It is used for example to copy config files that must be turned into configmaps into the provision-configs hierarchy. It is required because Helm won't allow files from outside the chart to be read during the template evaluation.
This make-config hook is what makes possible to put the content of the rclone.conf file in the rclone config secret sample. The template uses (.Files.Get ".kaptain/rclone.conf") to read the file's content, but Helm will only agree to read files included in the chart it is currently processing (here, the provision-configchart).
Here's an example of hooks.sh:
# The name of the hook being run
HOOK=$1
if [ "$HOOK" = "make-config" ]; then
# These are used in the namespace-configmap
export REPO_REVISION=$(git -C $KAPTAIN_WORKING_DIRECTORY describe --tags --always --dirty=+)
export REPO_BRANCH=$(git -C $KAPTAIN_WORKING_DIRECTORY rev-parse --abbrev-ref HEAD)
embed_config express-gateway
rclone_config tileservergl
embed_config mapserver
embed_config mapcache
embed_config weacast
embed_config kano
# add gdal-wmts sources to both mapserver & mapcache
rclone copy "$KAPTAIN_WORKING_DIRECTORY/configs/gdal-wmts/" "$KAPTAIN_NAMESPACE_DIRECTORY/provision-configs/.kaptain/mapserver"
rclone copy "$KAPTAIN_WORKING_DIRECTORY/configs/gdal-wmts/" "$KAPTAIN_NAMESPACE_DIRECTORY/provision-configs/.kaptain/mapcache"
fi
embed_config, rclone_config are helper functions defined in the kaptain script.
Those hooks could probably be replaced by helmfile hooks (opens new window) but this is still work in progress ...
# Embedding the kaptain script
Kaptain is often embedded in infrastructure projects using a git submodule, here's a quick HOWTO cheatsheet:
Consider the following project's structure :
+ infrastructure-xyz
- global.yaml
- secret.yaml
- helmfile.yaml
+ scripts
- install.sh # convenience script calling kaptain
- diff.sh # convenience script calling kaptain
- ...
+ kargo # git submodule
+ scripts
- kaptain.sh
# Cloning the infrastructure-xyz project
Just clone like you're used to adding the --recurse-submodules flag.
git clone --recurse-submodules
If you cloned the project and forgot the --recurse-submodules flag, you can use git submodule update --init to fetch submodules.
# Pulling changes in the infrastructure-xyz project
Since the kargo folder is a git submodule, pull using --recurse-submodules flag. This will ensure you also pull the kargo's version that's associated with the infrastructure-xyz project.
git pull --recurse-submodules
You can make this the default by defining the submodule.recurseoption to true in your repository.
git config submodule.recurse true
# Updating the kargo submodule
If you want to change the version the kargo submodule points to then cd into the kargo folder and pull the desired commit, or tag, or branch. Once done, cd back to the root of your project and commit the kargo folder. This will record the kargo version your project depends on.
cd kargo
git pull v1.9
cd ..
git add kargo
git commit -m "Bumped kargo submodule to v1.9"
git push