Simple, reconciliation-based runtime templating
The Template Operator is for platform engineers needing an easy and reliable way to create, copy and update kubernetes resources.
- 100% YAML –
Templatesare valid YAML and IDE validation and autocomplete of k8s resources works as normal. - Simple – Easy to use and quick to get started.
- Reconciliation based – Changes are applied quickly and resiliently (unlike webhooks) at runtime.
This README replicates much of the content from Simple, reconciliation-based runtime templating.
For further examples, see part 2 in the series: Powering up with Custom Resource Definitions (CRDs).
There are alternative templating systems in use by the k8s community – each has valid use cases and noting the downsides for runtime templating is not intended as an indictment – all are excellent choices under the right conditions.
| Alternative | Downside for templating |
|---|---|
| crossplane | Complex due to design for infrastructure composition |
| kyverno | Webhook based Designed as a policy engine |
| helm | Not 100% YAML Not reconciliation based (build time) |
API documentation available here.
This guide assumes you have either a kind cluster or minikube cluster running, or have some other way of interacting with a cluster via kubectl.
export VERSION=0.4.0 # For the latest release version: https://github.com/flanksource/template-operator/releases # Apply the operator kubectl apply -f https://github.com/flanksource/template-operator/releases/download/v${VERSION}/operator.ymlRun kubectl get pods -A and you should see something similar to the following in your terminal output:
NAMESPACE NAME READY template-operator template-operator-controller-manager-6bd8c5ff58-sz8q6 2/2To follow the manager logs, open a new terminal and, changing what needs to be changed, run :
kubectl logs -f --since 10m -n template-operator deploy/template-operator-controller-manager -c managerThese logs are where reconciliation successes and errors show up – and the best place to look when debugging.
As a platform engineer, I need to quickly provision Namespaces for application teams so that they are able to spin up environments quickly.
As organisations grow, platform teams are often tasked with creating Namespaces for continuous integration or for development.
To configure a Namespace, platform teams may need to commit or apply many boilerplate objects.
For this example, suppose you need a set of Roles and RoleBindings to automatically deploy for a Namespace .
Add a Namespace. You might add this after applying the Template, but it's helpful to see that the Template Operator doesn't care when objects are applied – a feature of the reconciliation-based approach. Note the label – this tags the Namespace as one that should produce RoleBindings.
cat <<EOF | kubectl apply -f - kind: Namespace apiVersion: v1 metadata: name: store-5678 labels: # This will be used to select on later type: application EOFWith the Namespace configured, you can apply the Template (see inline notes).
cat <<EOF | kubectl apply -f - apiVersion: templating.flanksource.com/v1 kind: Template metadata: name: namespace-rolebinder-developer namespace: template-operator spec: # The "source" field selects for the objects to monitor. # API docs here: https://pkg.go.dev/github.com/flanksource/template-operator/api/v1#ResourceSelector source: # Selects for the apiVersion apiVersion: v1 # Selects for the kind kind: Namespace # Selects for the label labelSelector: matchLabels: type: application # For every matched object, Template Operator will generate the listed resources. resources: - kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: developer # {{.metadata.name}} comes from the source object ("."). # Syntax is based on go text templates with gomplate functions (https://docs.gomplate.ca). namespace: "{{.metadata.name}}" rules: - apiGroups: [""] resources: ["secrets", "pods", "pods/log", "configmaps"] verbs: ["get", "watch", "list"] - kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: developer namespace: "{{.metadata.name}}" subjects: - kind: Group name: developer apiGroup: rbac.authorization.k8s.io roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: developer EOFBecause of reconciliation, though the Namespace "store-5678" was applied before the Template namespace-rolebinder-developer, the operator will still produce/update the required objects.
Once the Template Operator has reconciled (you can see this if you're tailing the logs), run kubectl get roles.rbac.authorization.k8s.io -A to see the newly created Role:
NAMESPACE NAME CREATED AT store-5678 developer 2021-07-16T06:30:27ZRun kubectl get rolebindings.rbac.authorization.k8s.io -A, for the RoleBinding:
NAMESPACE NAME ROLE AGE store-5678 developer Role/developer 10sNow you can apply a second Namespace:
cat <<EOF | kubectl apply -f - kind: Namespace apiVersion: v1 metadata: name: store-7674 labels: type: application EOFThe Template Operator will create/update the resources in its next cycle. Once the Template Operator reconciles, run kubectl get rolebindings.rbac.authorization.k8s.io -A and you should see something like:
NAMESPACE NAME ROLE AGE store-7674 developer Role/developer 2m store-5678 developer Role/developer 8sAnd for kubectl get roles.rbac.authorization.k8s.io -A:
NAMESPACE NAME CREATED AT store-5678 developer 2021-07-16T06:30:27Z store-7674 developer 2021-07-16T06:33:14ZAnd you're done! In the next example, you'll learn how to add a Template to copy Secrets across Namespaces.
As a platform engineer, I need to automatically copy appropriate Secrets to newly created Namespaces so that application teams have access to the Secrets they need by default.
Suppose you have a Namespace containing Secrets you want to copy to every development Namespace.
Apply the following manifests to set up the Namespace with the Secrets.
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Namespace metadata: name: development-secrets labels: environment: development --- apiVersion: v1 kind: Secret metadata: name: development-secrets-username namespace: development-secrets labels: secrets.flanksource.com/label: development stringData: username: rvvq6c8p272! type: Opaque --- apiVersion: v1 kind: Secret metadata: name: development-secrets-api namespace: development-secrets labels: secrets.flanksource.com/label: development stringData: apikey: 7jmpsscrd272jlh type: Opaque EOFThen add a Template with the copyToNamespaces field.
cat <<EOF | kubectl apply -f - kind: Template apiVersion: templating.flanksource.com/v1 metadata: name: copy-development-secrets spec: source: apiVersion: v1 kind: Secret # selects on the Namespace label namespaceSelector: matchLabels: environment: development # selects on the Secret label labelSelector: matchLabels: secrets.flanksource.com/label: development copyToNamespaces: # selects on the Namespace label namespaceSelector: matchLabels: type: application EOFOnce the Template Operator has reconciled, run kubectl get secrets -A to see the copied secrets:
NAMESPACE NAME TYPE DATA AGE store-5678 development-secrets-api Opaque 1 3s store-5678 development-secrets-username Opaque 1 3s store-7674 development-secrets-api Opaque 1 5s store-7674 development-secrets-username Opaque 1 5s