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 }}