# 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
config
for 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: config
in thehelmfile.yaml
- install will ask helmfile to deploy all releases labelled as
action: install
in 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.yaml
is where we put a bunch of infrastructure scope variables, we want them to be available in Helm and Helmfile templates.secret.yaml
is 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 thevalues
block earlier. - where to lookup release values. From our
global.yaml
file and from a file namedvalues.yaml.gotmpl
located in a subfolder named as the release in theconfigs
folder. - it also defines a special value named
global.secret
we use in our kargo (opens new window) charts. - it defines the release label(s). Here it's
action: install
which means it's a release that'll get deployed as part ofkaptain install
- a direct dependency on the
provision-configs
release.
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.yaml
file, 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-config
chart).
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.recurse
option 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