The Kubernetes generator allows to quickly generate Kubernetes manifests.
git clone [email protected]:kapicorp/kapitan-reference.git kapitan-templates
cd kapitan-templates
Create a new kapitan target file in any subdirectory of the inventory/targets
folder.
For this tutorial, we will assume the target file to be inventory/targets/demo.yml
The target name is the name of the file without the extentions (e.g
demo
).
classes:
# boilerplate class to get you started
- common
EVERY time you make a change, you will want to tell kapitan
to compile your targets.
kapitan
will create a folder for each target under the compiled
folder
./kapitan compile -t demo
./kapitan compile
Let's start by creating a simple component, a deployment
to be more precise.
Note: Also see the StatefulSet and Jobs sections!
We will use the jmalloc/echo-server
for this demo.
The generator is expecting components to be defined under the parameters.components
path of the inventory.
For instance, create a component echo-server
, simply create the following section:
classes:
# boilerplate class to get you started
- common
parameters:
components:
echo-server:
image: jmalloc/echo-server
Run kapitan compile
and check the output in the compiled/demo/manifests
folder.
You can define env variables by nesting them under the env
directive:
parameters:
components:
echo-server:
<other config>
env:
KAPITAN_ROCKS: 'YES!
You can also use secretKeyRef
and configMapKeyRef
provided you have defined your secrets/configmaps below.
parameters:
components:
echo-server:
<other config>
env:
KAPITAN_SECRET:
secretKeyRef:
name: a_secret *OPTIONAL*
key: 'kapitan_secret'
NOTE that you do not need to specify the
name
directive, as the generator will attempt to work out where to get it from.
Also fieldRef
works as expected
parameters:
components:
echo-server:
<other config>
env:
NODE_NAME:
fieldRef:
fieldPath: spec.nodeName
You can define the ports your component uses by adding them under the ports
directive:
parameters:
components:
echo-server:
<other config>
ports:
http:
container_port: 8080
The above will produce the following effect:
--- a/compiled/demo/manifests/echo-server-bundle.yml
+++ b/compiled/demo/manifests/echo-server-bundle.yml
@@ -38,6 +38,10 @@ spec:
- image: jmalloc/echo-server
imagePullPolicy: IfNotPresent
name: echo-server
+ ports:
+ - containerPort: 8080
+ name: http
+ protocol: TCP
You can also quickly add a readiness
/liveness
check:
parameters:
components:
echo-server:
<other config>
healthcheck:
readiness:
type: http
port: http
path: /health/readiness
timeout_seconds: 3
liveness:
type: http
port: http
path: /health/liveness
timeout_seconds: 3
which produces:
--- a/compiled/demo/manifests/echo-server-bundle.yml
+++ b/compiled/demo/manifests/echo-server-bundle.yml
@@ -42,6 +42,15 @@ spec:
- containerPort: 8080
name: http
protocol: TCP
+ readinessProbe:
+ failureThreshold: 3
+ httpGet:
+ path: /health/readiness
+ port: http
+ scheme: HTTP
+ periodSeconds: 10
+ successThreshold: 1
+ timeoutSeconds: 3
+ livenessProbe:
+ failureThreshold: 3
+ httpGet:
+ path: /health/liveness
+ port: http
+ scheme: HTTP
+ periodSeconds: 10
+ successThreshold: 1
+ timeoutSeconds: 3
Types
tcp
andcommand
are also supported.
If you want to expose the service, add the service
directive with the desired service type
, and define the service_port
:
parameters:
components:
echo-server:
<other config>
service:
type: ClusterIP
ports:
http:
service_port: 80
container_port: 8080
Note: if you want to prevent a port from being added to the service, omit the
<service_port>
directive
Which will create a service manifest with the same name as the component, and will produce the following effect:
--- a/compiled/demo/manifests/echo-server-bundle.yml
+++ b/compiled/demo/manifests/echo-server-bundle.yml
@@ -52,3 +52,21 @@ metadata:
name: echo-server
name: echo-server
namespace: demo
+---
+apiVersion: v1
+kind: Service
+metadata:
+ labels:
+ app: echo-server
+ name: echo-server
+ namespace: demo
+spec:
+ ports:
+ - name: http
+ port: 80
+ protocol: TCP
+ targetPort: http
+ selector:
+ app: echo-server
+ sessionAffinity: None
+ type: LoadBalancer
Creating both secrets
and config maps
is very simple with Kapitan Generators, and the interface is very similar with minor differences between them.
config_maps:
config:
data:
echo-service.conf:
value: |-
# A configuration file
example: true
A ConfigMap manifest was created. The name is taken from the component.
cat compiled/demo/manifests/echo-server-config.yml
apiVersion: v1
data:
echo-service.conf: '# A configuration file
example: true'
kind: ConfigMap
metadata:
labels:
name: echo-server
name: echo-server
namespace: demo
Note that in the previous example the config map is not mounted, because the mount
directive is missing.
config_maps:
config:
mount: /opt/echo-service
data:
echo-service.conf:
value: |-
# A configuration file
example: true
Simply adding the above configuration, will immediately configure the component to mount the config map we have just defined:
+ volumeMounts:
+ - mountPath: /opt/echo-service
+ name: config
+ readOnly: true
restartPolicy: Always
terminationGracePeriodSeconds: 30
+ volumes:
+ - configMap:
+ defaultMode: 420
+ name: echo-server
+ name: config
A more advanced way to create the configuration file, is to use an external jinja
file as source:
config_maps:
config:
mount: /opt/echo-service
data:
echo-service.conf:
template: 'components/echo-server/echo-server.conf.j2'
values:
example: true
with the file echo-server.conf.j2 being a jinja template file.
As expected, we can inject any value from the inventory into the the file.
You can also use the file
and the directory
directives to copy a single file or a full directory to your ConfigMaps or Secrets.
config_maps:
config:
mount: /opt/echo-service
data:
example.txt:
file: 'components/echo-server/example.txt'
We do not always expect to mount all files available in a config map. Sometimes in the config map we have a mix of files and other values destined to be consumed by environment variables instead.
For instance, given the following setup, we can restrict the mount only to files defined in the items
directive:
config_maps:
config:
mount: /opt/echo-service
items:
- echo-service.conf
data:
echo-service.conf:
template: 'components/echo-server/echo-server.conf.j2'
values:
example: true
simple_config:
value: "not mounted"
the diff shows that the generator makes use of the items directive in the manifest:
--- a/compiled/demo/manifests/echo-server-config.yml
+++ b/compiled/demo/manifests/echo-server-bundle.yml
@@ -60,6 +60,9 @@ spec:
volumes:
- configMap:
defaultMode: 420
+ items:
+ - key: echo-service.conf
+ path: echo-service.conf
name: echo-server
Secrets use the same configuations as config maps, but are nested under the secrets
key.
In addition, secrets support automatic base64 encoding with the b64_encode
directive:
secrets:
secret:
data:
encoded_secret:
value: my_secret
b64_encode: true
cat compiled/demo/manifests/echo-server-secret.yml
apiVersion: v1
data:
encoded_secret: bXlfc2VjcmV0 # ENCODED my_secret
kind: Secret
metadata:
labels:
name: echo-server
name: echo-server
namespace: demo
type: Opaque
Note that, because in this example the
mount
directive is missing, the secret will not be mounted automatically.
Please review the generic way of Kapitan to manage secrets at https://kapitan.dev/secrets/ and Secrets management with Kapitan
In summary, remember that you can summon the power of Google KMS (once setup) and use kapitan secrets like this:
secrets:
secret:
data:
encoded_secret:
value: my_secret
b64_encode: true
better_secret:
value: ?{gkms:targets/${target_name}/password||randomstr|base64}
which will generate an truly encrypted secret using Google KMS (other backends also available)
The generator can automatically "version" you ConfigMap or Secrets so that the associated workload can automatically detect the change and handle it appropriately (rollout restart)
In both secrets and config_maps, just define versioned: true
(default: false
)
config_maps:
config:
versioned: true
mount: /opt/echo-service
data:
example.txt:
file: 'components/echo-server/example.txt'
The generator will hash the content of the resource, and add it to the name of the rendered object:
kind: ConfigMap
metadata:
labels:
name: echo-server
name: echo-server-01f3716a
namespace: echo-server
[cut]
when the content of the object changes, the hash will be updated accordingly.
PLEASE NOTE: kapitan
is not responsible for garbage collecting unused secrets of config maps.
You can define a StatefulSet by using the type
directive to statefulset
(that normally defaults to deployment
)
The statefulset uses all (applicable) configurations available to the deployment
type, but also includes.
volume_mounts:
datadir:
mountPath: /var/lib/mysql
volume_claims:
datadir:
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "standard"
resources:
requests:
storage: 10Gi
You can define a Job by using the type
directive to job
(that normally defaults to deployment
)
You can define a CronJob by setting the schedule
type to a valid value.
parameters:
components:
postgres-backup:
type: job
schedule: "0 */6 * * *"
image: moep1990/pgbackup:lates
env:
PGDATABASE: postgres
PGHOST: postgres
PGPASSWORD: postgres
PGPORT: 5432
PGUSER: postgres
Which will automatically generate the CronJob resource
apiVersion: batch/v1beta1
kind: CronJob
metadata:
labels:
name: postgres-backup
name: postgres-backup
spec:
jobTemplate:
spec:
backoffLimit: 1
completions: 1
parallelism: 1
template:
metadata:
labels:
app.kubernetes.io/managed-by: kapitan
app.kubernetes.io/part-of: gitea
name: postgres-backup
spec:
containers:
- env:
- name: PGDATABASE
value: postgres
- name: PGHOST
value: postgres
- name: PGPASSWORD
value: postgres
- name: PGPORT
value: '5432'
- name: PGUSER
value: postgres
image: moep1990/pgbackup:latest
imagePullPolicy: Always
name: postgres-backup
restartPolicy: Never
terminationGracePeriodSeconds: 30
schedule: 0 */6 * * *
You can instruct the generator to add one or more additional containers to your definition:
parameters:
components:
echo-server:
<other config>
# Additional containers
additional_containers:
nginx:
image: nginx
ports:
nginx:
service_port: 80
You can access the same config_maps and secrets as the main container, but you can override mountpoints and subPaths
For instance while this is defined in the outer "main" container scope, we can still mount the nginx config file:
parameters:
components:
echo-server:
<other config>
# Additional containers
additional_containers:
nginx:
image: nginx
ports:
nginx:
service_port: 80
config_maps:
config:
mount: /etc/nginx/conf.d/nginx.conf
subPath: nginx.conf
<other config>
config_maps:
config:
mount: /opt/echo-service/echo-service.conf
subPath: echo-service.conf
data:
echo-service.conf:
template: "components/echo-server/echo-server.conf.j2"
values:
example: true
nginx.conf:
value: |
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://localhost:8080/;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
You can instruct the generator to create initContainers by using the init_containers
directive:
parameters:
components:
echo-server:
<other config>
# Init containers
init_containers:
busybox:
image: busybox
commands:
- echo test
Just like the additional_containers
, tou can access the same config_maps and secrets as the main container, but you can override mountpoints and subPaths.
You can also generate Network Policies by simply adding them under the network_policies
structure.
# One or many network policies
network_policies:
default:
pod_selector:
name: echo-server
ingress:
- from:
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 6379
Which will automatically generate the NetworkPolicy resource
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
labels:
name: echo-server
name: echo-server
spec:
ingress:
- from:
- podSelector:
matchLabels:
role: frontend
ports:
- port: 6379
protocol: TCP
podSelector:
name: echo-server
policyTypes:
- Ingress
- Egress
Define PrometheusRules and ServiceMonitor alongside your application definitions.
For a working example, have a look at tesoro_monitoring.yaml
Simply add your definitions:
parameters:
components:
tesoro:
prometheus_rules:
rules:
- alert: TesoroFailedRequests
annotations:
message: 'tesoro_requests_failed_total has increased above 0'
expr: sum by (job, namespace, service, env) (increase(tesoro_requests_failed_total[5m])) > 0
for: 1m
labels:
severity: warning
- alert: KapitanRevealRequestFailures
annotations:
message: 'kapitan_reveal_requests_failed_total has increased above 0'
expr: sum by (job, namespace, service, env) (increase(kapitan_reveal_requests_failed_total[5m])) > 0
for: 1m
labels:
severity: warning
to produce:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
labels:
name: tesoro.rules
name: tesoro.rules
namespace: tesoro
spec:
groups:
- name: tesoro.rules
rules:
- alert: TesoroFailedRequests
annotations:
message: tesoro_requests_failed_total has increased above 0
expr: sum by (job, namespace, service, env) (increase(tesoro_requests_failed_total[5m]))
> 0
for: 1m
labels:
severity: warning
- alert: KapitanRevealRequestFailures
annotations:
message: kapitan_reveal_requests_failed_total has increased above 0
expr: sum by (job, namespace, service, env) (increase(kapitan_reveal_requests_failed_total[5m]))
> 0
for: 1m
labels:
severity: warning
parameters:
components:
tesoro:
service_monitors:
endpoints:
- interval: 15s
path: /
targetPort: 9095
produces the following resource
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
labels:
name: tesoro-metrics
name: tesoro-metrics
namespace: tesoro
spec:
endpoints:
- interval: 15s
path: /
targetPort: 9095
jobLabel: tesoro-metrics
namespaceSelector:
matchNames:
- tesoro
selector:
matchLabels:
name: tesoro
Sometimes, when defining many components, you and up repeating many repeating configurations. With this generator, you can define defaults in 2 ways:
The global defaults can be used to set defaults for every component being generated.
As you can see, some defaults are already set:
generators:
manifest:
default_config:
type: deployment
annotations:
"manifests.kapicorp.com/generated": true
You do not have to change that class directly, as long as you add to the same inventory structure for another class.
For instance, when we enable the features.tesoro
class, we can see that we are adding the following yaml fragment:
generators:
manifest:
default_config:
globals:
secrets:
labels:
tesoro.kapicorp.com: enabled
Which has the effect to add the tesoro.kapicorp.com: enabled
label to every generated configMap resource.
You can also create application defaults, where an application is a class/profile that can be associated to multiple components.
For instance, let's assume you have the following definition for an application class called microservices
parameters:
applications:
microservices:
component_defaults:
replicas: 3
env:
KAPITAN_APPLICATION: microservices
Every component that belongs to that application class will receive the defaults for the application.
To associate a component to an application, use the application
directive.
parameters:
components:
echo-server:
application: microservices
image: jmalloc/echo-server
Compiling, kapitan will generate a deployment with image jmalloc/echo-server
, 3 replicas, an annotation and an env variable.
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
manifests.kapicorp.com/generated: 'true'
labels:
app: echo-server
name: echo-server
namespace: tutorial
spec:
replicas: 3
selector:
matchLabels:
app: echo-server
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
labels:
app: echo-server
spec:
containers:
- env:
- name: KAPITAN_APPLICATION
value: microservices
image: jmalloc/echo-server
imagePullPolicy: IfNotPresent
name: echo-server
restartPolicy: Always
terminationGracePeriodSeconds: 30