4
0
Fork
You've already forked c4k-common
0
No description
Clojure 98.4%
Python 1.6%
2025年12月19日 17:06:07 +01:00
doc remove cljs support 2025年07月23日 13:59:51 +02:00
src fix pg namesapce 2025年07月28日 08:21:22 +02:00
.gitignore remove cljs support 2025年07月23日 13:59:51 +02:00
.gitlab-ci.yml remove cljs support 2025年07月23日 13:59:51 +02:00
build.py remove cljs support 2025年07月23日 13:59:51 +02:00
LICENSE changed license 2021年06月18日 15:36:16 +02:00
project.clj bump version to: 12.0.2-SNAPSHOT 2025年07月28日 08:27:04 +02:00
README.md update refactorings 2025年12月19日 17:06:07 +01:00

convention 4 kubernetes: c4k-common

Clojars Project pipeline status

DeltaChat chat over e-mail | M meissa@social.meissa-gmbh.de | Blog | Website

Rationale

There are many comparable solutions for creating c4k deployments like helm or kustomize. kustomize is great to manage your k8s manifests by splitting huge files into handy parts. helm is great because of its large community.

Why do we need another one? Why do you continue the reading here?

We combine the simplicity of kustomize with the ability to do real programming like software developers would do.

Following the principle

"Use programming language for programming" 

we clearly enjoy writing Kubernetes manifests with Clojure. In comparison to helms templating, things such as business logic, conventions, input validation, versions, dependencies and reuse are much easier and much more reliable to implement with c4k.

By the way, c4k means "convention for kubernetes".

Features

c4k-common supports the following use cases:

Target CLI and Web Frontend

To create your own c4k module set up your cli analogous to the following:

(defn -main [& cmd-args]
 (uberjar/main-common 
 "c4k-forgejo" ;; name of your app
 core/config? ;; schema for config validation
 core/auth? ;; schema for credential validation
 core/config-defaults ;; want to set default values?
 core/k8s-objects ;; the function generate the k8s manifest
 cmd-args ;; command line arguments given
 )) 

The full example can be found here: https://repo.prod.meissa.de/meissa/c4k-forgejo/src/branch/main/src/main/clj/dda/c4k_forgejo/uberjar.clj

Separate Configuration From Credentials

We think it is a good idea to have credentials separated from configuration. All our functions, cli and frontend are following this principle. Thus, for executing separated config and authentication infos have to be provided.

The following command line yields the resulting k8s manifests in k8s-manifest.yaml:

java -jar c4k-common.jar config.edn auth.edn > k8s-manifest.yaml

Using the tool jarwrapper the command line can even be shortened to:

c4k-common config.edn auth.edn > k8s-manifest.yaml

Input as EDN or Yaml

c4k-common supports yaml and edn as format for all of its resources (input and output). Hence, the following command line will also work:

c4k-common config.yaml auth.yaml > k8s-manifest.yaml

Inline k8s Resources for Versioning & Dependencies

We inline all resources used in our libraries & applications. You can generate k8s manifests everywhere without additional external dependencies.

In case of

Work on Structured Data Instead of Flat Templating

To keep things simple, we also do templating. But we convert given k8s resources to structured data. This allows us to have more control and do unit tests:

k8s-resource:

apiVersion:traefik.containo.us/v1alpha1kind:Middlewaremetadata:name:ratelimitspec:rateLimit:average:AVGburst:BRS

Replace values:

(defn-spec generate-rate-limit-middleware pred/map-or-seq?
 [config rate-limit-config?]
 (let [{:keys [max-rate max-concurrent-requests]} config]
 (->
 (yaml/load-as-edn "forgejo/middleware-ratelimit.yaml")
 (cm/replace-key-value :average max-rate)
 (cm/replace-key-value :burst max-concurrent-requests)))) 

Have a unit-test:

(deftest should-generate-middleware-ratelimit
 (is (= {:apiVersion "traefik.containo.us/v1alpha1",
 :kind "Middleware",
 :metadata {:name "ratelimit"},
 :spec {:rateLimit {:average 10, :burst 5}}}
 (cut/generate-rate-limit-middleware {:max-rate 10, :max-concurrent-requests 5}))))

Validate Your Inputs

Have you recognized the defn-spec macro above? We use allover validation, e.g.

(def rate-limit-config? (s/keys :req-un [::max-rate
 ::max-concurrent-requests]))
(defn-spec generate-rate-limit-middleware pred/map-or-seq?
 [config rate-limit-config?]
 ...)

Namespaces

We support namespaces for ingress & postgres (monitoring lives in it's own namespace monitoring).

(dda.c4k-common.namespace/generate {:namespace "myapp"})

yields:

[{:apiVersion "v1"
 :kind "Namespace"
 :metadata {:name "myapp"}}]

which renders to:

apiVersion:v1kind:Namespacemetadata:name:myapp

Ingress

In most cases we use generate-ingress-and-cert which generates an ingress in combination with letsencrypt cert for a named service.

(dda.c4k-common.ingress/generate-ingress-and-cert 
 {:fqdns ["test.jit.si"]
 :service-name "web"
 :service-port 80})

yields:

[{:apiVersion "cert-manager.io/v1",
 :kind "Certificate",
 ...
 :spec
 {:secretName "web",
 :commonName "test.jit.si",
 :duration "2160h",
 :renewBefore "720h",
 :dnsNames ["test.jit.si"],
 :issuerRef {:name "staging", :kind "ClusterIssuer"}}}
 {:apiVersion "networking.k8s.io/v1",
 :kind "Ingress",
 ...
 :spec
 {:tls [{:hosts ["test.jit.si"], :secretName "web"}],
 :rules
 [{:host "test.jit.si",
 :http {:paths [{:path "/",
 :pathType "Prefix",
 :backend
 {:service {:name "web",
 :port {:number 80}}}}]}}]}}]

which renders to:

apiVersion:cert-manager.io/v1kind:Certificate...spec:secretName:webcommonName:test.jit.siduration:2160hrenewBefore:720hdnsNames:- test.jit.siissuerRef:name:stagingkind:ClusterIssuer---apiVersion:networking.k8s.io/v1kind:Ingress...spec:tls:- hosts:- test.jit.sisecretName:webrules:- host:test.jit.sihttp:paths:- pathType:Prefixpath:/backend:service:name:webport:number:80

Postgres Database

If your application needs a database, we often use postgres:

(cut/generate-deployment {:postgres-image "postgres:16"})

yields:

{:apiVersion "apps/v1",
 :kind "Deployment",
 ...
 :spec
 {:selector {:matchLabels {:app "postgresql"}},
 :strategy {:type "Recreate"},
 :template
 {:metadata {:labels {:app "postgresql"}},
 :spec
 {:containers
 [{:image "postgres:16",
 :name "postgresql",
 :env
 [{:name "POSTGRES_USER", :valueFrom {:secretKeyRef {:name "postgres-secret", :key "postgres-user"}}}
 {:name "POSTGRES_PASSWORD", :valueFrom {:secretKeyRef {:name "postgres-secret", :key "postgres-password"}}}
 {:name "POSTGRES_DB", :valueFrom {:configMapKeyRef {:name "postgres-config", :key "postgres-db"}}}],
 :ports [{:containerPort 5432, :name "postgresql"}],
 :volumeMounts
 [...],
 :volumes
 [...]}}}}

which renders to:

apiVersion:apps/v1kind:Deployment...spec:selector:matchLabels:app:postgresqlstrategy:type:Recreatetemplate:metadata:labels:app:postgresqlspec:containers:- image:postgres:16name:postgresqlenv:- name:POSTGRES_USERvalueFrom:secretKeyRef:name:postgres-secretkey:postgres-user- name:POSTGRES_PASSWORDvalueFrom:secretKeyRef:name:postgres-secretkey:postgres-password- name:POSTGRES_DBvalueFrom:configMapKeyRef:name:postgres-configkey:postgres-dbports:- containerPort:5432name:postgresqlvolumeMounts:...volumes:...

We optimized our db installation to run between 2Gb anf 16Gb Ram usage.

Monitoring With Grafana Cloud

With minimal config of

(def conf 
 {:k3s-cluster-name "your-cluster-name"
 :k3s-cluster-stage :prod
 :grafana-cloud-url "your-url"})
(def auth 
 {:grafana-cloud-user "user"
 :grafana-cloud-password "password"})
 (monitoring/generate conf auth)

You can get a cluster local node-exporter, kube-state-metrics, pushgateway & prometheus writing important metrics to grafana cloud.

Refactoring & Module Overview

Module Version commons backup
c4k-keycloak 2.0
c4k-taiga 2.0
c4k-nextcloud 11.0
c4k-jitsi 4.0 -
c4k-forgejo 7.0 x
c4k-stats 2.0 x
c4k-website 3.0 -
c4k-monitoring 0.1 x

Development & Mirrors

Development happens at: https://repo.prod.meissa.de/meissa/c4k-common

Mirrors are:

For more details about our repository model see: https://repo.prod.meissa.de/meissa/federate-your-repos

License

Copyright © 2022 - 2025 meissa GmbH Licensed under the Apache License, Version 2.0 (the "License") Pls. find licenses of our subcomponents here