One Chart to rule them all
Tags: helm, kubernetes, devops, gitops
Published: 2024-01-30 09:59:30

_Describing Kubernetes deployments having best practices applied is quite verbose and maintenance intensive. Doing that for multiple applications in various stages by different teams results in lots of yam(l)mer. Helm charts provide a convenient solution. For the best deployment experience, we're maintaining and using a generic application chart._

## Resources over Resources

Operating applications on Kubernetes beyond basic tutorials requires a few resources:

* the _Deployment_ itself
* a _ServiceAccount_ most of the time
* one to many _ConfigMaps_ as well as _Secrets_
* _Service_ and _Ingress_ if someone shall interact with your deployment
* often a _[Horizontal&vert;Vertical]PodAutoscaler_ referring the deployment

As we're adopting GitOps practices for quite some time with great success - you may have heard that 😇 - the manifests of all those resources have to be literally written into a Git repository.

Mentioning GitOps... for automating rollouts there are even more (custom) resources required in order to instruct our GitOps controllers (Flux & Friends):

* the _ImageRepository_ targeting the container registry
* a _ImagePolicy_ declaring version patterns to automate
* and eventually a _Canary_ as no one wants to observe rollouts manually

Observation - another great topic: To announce applications to the Prometheus monitoring stack, a _ServiceMonitor_ resource comes on top.

Well... over time you'll probably add a _PodDisruptionBudget_ or want to have a service mesh in place, adding furthermore resources, for instance _DestinationRules_ and _VirtualServices_ in case of Istio.
Wait - with Istio in place, the _Ingress_ resource gets replaced by a _VirtualService_.

As we're constantly evolving, our added service mesh may get superseded by the Kubernetes Gateway API, introducing _Gateway_ or _HttpRoutes_ in favor of the Istio CRDs.

Guess you got the point. Writing all those manifest isn't even the hardest part. They need to be maintained constantly. Kubernetes APIs come and go, with some version lifecycle in between.

## Scaling pains

Of course, some team has written all those resource manifests for the first application that was deployed. And they quickly put them into a Helm chart when it was about replication to multiple stages. The Helm chart could also be reused for further applications of that team, because - honestly - all applications ("microservices") are looking very similar.

To ensure, that changes on templated manifests don't break everything immediately, the team linted their Helm chart using different tools, hinting on upcoming Kubernetes API changes, or recommending best practices.

That's all nice and works out well. If there weren't other teams doing exactly the same but different. After a while, there were countless charts around, that all served the same purpose, but varied only slightly.

Like everywhere else, reuse is practiced by duplication, but we weren't able to copy solutions, because of the minor differences in the description of our application declarations.

## Combine and generalize

So we did what was the obvious solution: We took the best from all those Helm charts and brewed a generic [_application_ chart](https://github.com/MediaMarktSaturn/helm-charts/tree/main/charts/application).

This chart was even our first open sourced component. It describes a typical application workload, incorporating the requirements of many, many product teams - and guess what: they're all nearly the same.

Our application chart is tested against the latest seven Kubernetes versions (1.23 to 1.29 at the time of writing) in lots of variations using the [Helm chart-testing project](https://github.com/helm/chart-testing). Each permutation is also validated using a specialized Kubernetes manifest linter for best practices and security hardening.

Every time, someone requires something not yet possible, the chart is improved for the benefit of all. In the meanwhile, there's quite an impressive feature list, which you can easily study by reading its [values.yaml](https://github.com/MediaMarktSaturn/helm-charts/blob/main/charts/application/values.yaml) - being also the documentation of the chart. Some highlights are:

* Init container and sidecar configuration without hassle
* Google Kubernetes Engine (GKE) support on workload identity and ingress config
* Automated canary deployment support using [Flagger](https://flagger.app/), implemented using:
* Service meshes (Istio or Linkerd) as well as the Gateway API
* Various configuration, secret and volume mount options
* Job execution before rollout for preparation tasks like database schema updates

All in all, the following [custom] resource manifests are managed by the chart (at the time of writing):

* _flagger.app_ **Canary**
* _image.toolkit.fluxcd.io_ **ImagePolicy**
* _image.toolkit.fluxcd.io_ **ImageRepository**
* _cloud.google.com_ **BackendConfig**
* _networking.istio.io_ **DestinationRule**
* _networking.istio.io_ **VirtualService**
* **ConfigMap**
* _apps_ **Deployment**
* _autoscaling_ **HorizontalPodAutoscaler**
* _gateway.networking.k8s.io_ **HTTPRoute**
* _networking.k8s.io_ **NetworkPolicy**
* _policy_ **PodDisruptionBudge**
* _batch_ **Job** (as `pre-install,pre-upgrade` Helm hook)
* **Secret**
* **ServiceAccount**
* **Service**
* _monitoring.coreos.com_ **ServiceMonitor**

Needless to mention, that the highest API version available is also respected, so full compatibility to all supported Kubernetes releases is ensured.

## Sample time

As stated earlier, this chart, which we're using for a wide spectrum of different types of application on different Kubernetes versions, is available for everyone.

The chart can be installed using the Helm cli

```shell
helm repo add mms-tech https://helm-charts.mms.tech
```

or Flux

```yaml
---
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
  name: mms-tech
  namespace: default
spec:
  interval: 120m
  url: https://helm-charts.mms.tech
```

To give you an impression on its usage, this is how our (well-known and beloved) GitHub app [Technolinator](https://github.com/MediaMarktSaturn/technolinator) is configured:

```yaml
apiVersion: helm.toolkit.fluxcd.io/v2beta2
kind: HelmRelease
metadata:
  name: technolinator
  namespace: app
spec:
  chart:
    spec:
      chart: application
      version: "~1"
      sourceRef:
        kind: HelmRepository
        name: chart-repo
        namespace: default
      interval: 60m
  interval: 10m
  values:
    image:
      repository: ghcr.io/mediamarktsaturn/technolinator
      tag: 1.48.11 # {"$imagepolicy": "app:technolinator:tag"}
      tagSemverRange: "~1"
    secretEnvFrom:
      - technolinator-config
    resources:
      requests:
        cpu: "1"
        memory: 10Gi
      limits:
        cpu: "6"
        memory: 35Gi
    container:
      port: 8080
    livenessProbe:
      path: /q/health/live
    readinessProbe:
      path: /q/health/ready
    monitoring:
      serviceMonitor: true
      metricsPath: /q/metrics
    podSecurityContext:
      runAsUser: 201
      runAsGroup: 101
      fsGroup: 101
      fsGroupChangePolicy: Always
    volumeMounts:
      - name: data
        pvcName: technolinator-data
        mountPath: /data
    configuration:
      APP_ANALYSIS_TIMEOUT: 120M
      APP_PROCESS_LOGLEVEL: DEBUG
      APP_PULL_REQUESTS_CONCURRENCY_LIMIT: 10
```

Again: All features are explorable in the [values.yaml](https://github.com/MediaMarktSaturn/helm-charts/blob/main/charts/application/values.yaml).
We will be happy, if this chart could also help others. And we're welcome contributions of any kinds for further improvements and use cases that we have not yet considered.

### The end

There's a very divisive discussion about such generic (monster/mega) charts. Sure, unique applications, provided by their projects have their own charts, and it doesn't make any sense to squeeze them into a generic one.

But in our opinion it's different for internal business applications of a company. Using our generic chart is a matter of hardening, reuse and easy knowledge sharing on application deployment configuration. The first one demanding a certain setup crafts it once, for everyone's benefit.

Especially when it comes to repetitive complex configurations like sidecars used for integration with databases or database schema update jobs, there's no need to reinvent the wheel over and over.

Reach out (for instance using GitHub [issues](https://github.com/MediaMarktSaturn/helm-charts/issues) or [discussions](https://github.com/MediaMarktSaturn/helm-charts/discussions)), if you'd like to discuss with us.

---

**_get to know us 👉 [https://mms.tech](https://mms.tech) 👈_**