Skip to content
Engineering

Palinurus – A Helm chart conversion tool

By Ovidiu Georgescu | 6 minute read

MailChannels relies heavily on two tools to orchestrate the services that run our email processing cloud:

  • Kubernetes – a portable, extensible, open-source platform for managing containerized workloads and services; and,
  • Helm – a “package manager” for Kubernetes, which allows us to define reliable and repeatable deployments of collections of services within Kubernetes.

Helm defines its “packages” using a concept called “charts,” which declaratively describe a collection of files that describe a related set of Kubernetes resources. Writing Helm charts is a tedious and error-prone manual process.

At least, it was tedious and error-prone until we fixed that with a new tool called Palinurus. Palinurus converts Kubernetes resource files to helm charts automatically, eliminating a manual, error-prone step on the road from service definition to reliable deployment.

What makes Helm so cool?

In much the same way as a RedHat RPM package allows a software developer to reliably incorporate third party libraries and components into their own software release, Helm charts let us quickly integrate third-party services with our own proprietary software into a single microservice deployment chain that can be reliably and repeatedly deployed and re-deployed. A versioned history of each deployment structure lets us reliably roll back to a prior version if we find a bug. In the middle of the night, when you’re struggling to understand why something is broken with the current deployment, the advantage of having the ability to reliably roll everything back to yesterday’s build cannot be understated.

What is Palinurus?

The main objective of Palinurus is to create a helm chart structure by scraping a directory containing  Kubernetes resources for a microservice. Palinurus  assembles a Helm chart based upon a template that is stored with the Palinurus script. The cool thing about these templates is that they can be modified to respect a format or standard that you might see fit.

Download Palinurus on GitHub

Examples

The following example create a chart from a Kubernetes deployment and a Kubernetes service yaml files:

deployment.yaml

apiVersion: apps/v1

kind: Deployment

metadata:

  name: nginx

  labels:

    app: nginx

spec:

  replicas: 1

  selector:

    matchLabels:

      app: nginx

  template:

    metadata:

      labels:

        app: nginx

    spec:

      containers:

      - name: nginx

        image: nginx:1.7.9

        ports:

        - containerPort: 80

service.yaml

apiVersion: v1

kind: Service

metadata:

  name: nginx-deployment

  labels:

    app: nginx

spec:

  type: NodePort

  ports:

    - port: 80

The chart that will be created is going to be a directory with the same name as the first Kubernetes “Deployment” object found in the directory.

nginx

├── Chart.yaml

├── templates

│   ├── _helpers.tpl

│   ├── nginx-deployment-service.yaml

│   └── nginx-deployment.yaml

└── values.yaml

Where nginx is a directory that holds the helm chart.

This chart was created from the two kubernetes resource files and the templates that are packaged with Palinurus. The templates are specific for each kubernetes resource. For example :

  • Kubernetes deployments have a template in the “deployment.yaml.template” and it looks like :

apiVersion: apps/v1

kind: Deployment

metadata:

 name: {{ template "$name.name" . }}

 labels:

   app: {{ template "$name.name" . }}

   chart: {{ template "$name.chart" . }}

   release: {{ .Release.Name }}

   heritage: {{ .Release.Service }}

   repo: {{ index .Values "global" "$name" "repo" }}

 namespace: {{ .Values.global.namespace }}

spec:

 replicas: {{ .Values.replicaCount }}

 selector:

   matchLabels:

     app: {{ template "$name.name" . }}

     release: {{ .Release.Name }}

 template:

   metadata:

     labels:

       app: {{ template "$name.name" . }}

       release: {{ .Release.Name }}

   spec:

     imagePullSecrets:

       - name: {{ index .Values "global" "$name" "images" "secret" }}

     containers:

       - name: {{ .Chart.Name }}

         image: {{ index .Values "global" "$name" "images" "repository" }}/{{ index .Values "global" "$name" "images" "$ccname" "image" }}:{{ index .Values "global" "$name" "images" "$ccname" "tag" }}

         imagePullPolicy: {{ index .Values "global" "$name" "images" "pullPolicy" }}

         ports:

         - containerPort: {{ index .Values "global" "$name" "service" "targetPort" }}

         livenessProbe:

           httpGet:

             path: /

             port: {{ index .Values "global" "$name" "service" "targetPort" }}

         readinessProbe:

           httpGet:

             path: /

             port: {{ index .Values "global" "$name" "service" "targetPort" }}

         env:

{{ toYaml (index .Values "global" "$name" "images" "$ccname" "env") | indent 12 }}

         resources:

{{ toYaml (index .Values "global" "$name" "images" "$ccname" "resources") | indent 12 }}

  • Kubernetes services have a template named  “service.yaml.template” :

apiVersion: v1

kind: Service

metadata:

 name: {{ template "$name.name" . }}

 labels:

   app: {{ template "$name.name" . }}

   chart: {{ template "$name.chart" . }}

   release: {{ .Release.Name }}

   heritage: {{ .Release.Service }}

   repo: {{ index  .Values "global" "$name" "repo" }}

 namespace: {{ .Values.global.namespace }}

spec:

 type: {{ index .Values "global" "$name" "service" "type" }}

 ports:

   - port: {{ index .Values "global" "$name" "service" "port" }}

     targetPort: {{ index .Values "global" "$name" "service" "targetPort" }}

 selector:

   app: {{ template "$name.name" . }}

   release: {{ .Release.Name }}

Cut your support tickets and make customers happier