-
Notifications
You must be signed in to change notification settings - Fork 11
Creating a new package
- ... what Docker is
- ... what Kubernetes objects (such as Deployments and Ingresses) are
- ... the basic structure of Kubernetes YAML files
A package is a collection of files describing how to construct an application. These files describe how the different parts of the application should be created (for instance which webserver to use) and how it is allowed to interact with other applications (ex. if the application should be available to the outside world, which ports it should use etc.).
Technically packages uses an extension of the Helm chart packaging format, which provides a generic way of describing Kubernetes objects.
The different elements of a package is best shown through an example. The following section will describe how to create a package capable of creating a Jupyter notebook.
Usually packages have the structure seen below
jupyter/
Chart.yaml # metadata describing the package/chart
README.md # (Optional) providees general information about the chart
resources.yaml # (Optional) describes which third-party resources this package supports
values.yaml # default configuration values
templates/ # (will be described below)
templates/_helpers.tpl
If you are following this example from home, you are should create a similar file tree now.
Some metadata is required for a package to function correctly.
A file called Chart.yaml
is used to store this metadata. TheChart.yaml
that will be used in the Jupyter package is shown below.
# filename: Chart.yaml
apiVersion: v1 # chart API version, always "v1"
name: jupyter # package name
description: Jupyter # a short sentence describing the package.
version: 1.0.0 # package version (in SemVer 2 format)
maintainers:
- name: My Name
email: me@domain.mail
home: https://package.com # package homepage
icon: https://package.com/pic.png # package icon
In order to make the package more user-friendly, a README.md
is
recommended. This file provides general information about the package, such
as what application the package creates and how to use it.
The README.md
for our Jupyter notebook may look like the following file:
# Jupyter notebook
[Jupyter Notebook](http://jupyter.org/) is an open-source web application that
allows you to create and share documents that contain live code, equations,
visualizations and narrative text. Uses include: data cleaning and transformation,
numerical simulation, statistical modeling, data visualization, machine learning, and much more.
We want the user to be able to specify certain parts of the application
configuration. A package contains a values.yaml
file that contains default
values that can be overridden by the user.
When creating the Jupyter notebook, we want the user to be able to allocate a custom amount of resources, as well as specifiying which host the application will be using.
The following values.yaml
file contains values that does this
# filename: values.yaml
ingress:
host: "local-chart.example.com"
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
cpu: 300m
memory: 1Gi
dockerImage: quay.io/uninett/jupyterlab:20180501-6469a2f
So, if specifies that the host should be foo.bar.interwebz.cat
when installing the application,
the value of ingress.host: "local-chart.example.com"
will be replaced with the user input. So, all the values in this file is optional, and a user does not specify a value, the value present in values.yaml
will be used as defaults.
when later creating the Kubernetes object templates,
these values can be referenced like this {{ .Values.ingress.host }}
.
As we want make it possible for the user to specify how certain aspects of a
application should be created, we need generic /templates/ that can be used to
create Kubernetes objects. These templates are stored in the templates/
directory.
Templates are written in Go template syntax with some additional functions added by Helm. See the Helm template guide for more information.
A template is mostly just a definition of a Kubernetes object, but with the ability to insert variables into parts of the template. Below an example of a template is shown
apiVersion: v1
kind: Secret
metadata:
name: secret
type: Opaque
data:
psst: {{ .Values.very_secret | quote }} # <-- this uses the Go templating engine to insert some secret data
Given the preceeding values.yaml
file
very_secret: "Pink is pretty cool"
the Kubernetes secret above will be rendered as
apiVersion: v1
kind: Secret
metadata:
name: secret
type: Opaque
data:
psst: "Pink is pretty cool"
In order to make the package easier to maintain, it is useful to define functions or commonly used variables.
Below the _helpers.tpl
file we will use for the Jupyter notebook is shown.
{{/* filename: templates/_helpers.tpl */}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes
name fields are limited to this (by the DNS naming spec).
*/}}
{{- define "fullname" -}}
{{- $name := default .Chart.Name -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
The template above defines a variable called fullname
which ensures that the application name is valid in Kubernetes. Another variable called "oidcconfig" is also defined, which contains the JSON config required to use a component we will create soon.
To use this variable, the following syntax is used {{ template "fullname" . }}
.
Knowing that we can use functions and variables from templates/_helpers.tpl
, we can continue creating our regular templates.
Our Jupyter notebook will consist of the following components:
- The Jupyter notebook
- An ingress which exposes the application to the outside world
- A proxy which provides Dataporten authentication
We will begin by creating the Jupyter notebook. In order to make the application easy to manage and scale, we will use a Kubernetes Deployment object as shown below.
# filename: templates/deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ template "fullname" . }}
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
spec:
replicas: 1
template:
metadata:
labels:
app: {{ template "fullname" . }}
spec:
containers:
- name: jupyter
image: {{ .Values.dockerImage }}
resources:
{{ toYaml .Values.resources | indent 10 }}
ports:
- containerPort: 8888
this file creates a Kubernetes deployment using an image provided by the user (through the dockerImage value in values.yaml
) and exposes it on port 8888.
Next, we want to create the ingress exposing the application to the outside world. To do so, we first need a service which exposes the deployment to the cluster
# filename: templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ template "fullname" . }}
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
spec:
ports:
- port: 8888
targetPort: 8888
protocol: TCP
name: {{ template "fullname" . }}-service
selector:
app: {{ template "fullname" . }}
then, we can create the ingress as follows
# filename: templates/ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ template "fullname" . }}
labels:
app: {{ template "fullname" . }}
chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
release: "{{ .Release.Name }}"
heritage: "{{ .Release.Service }}"
annotations:
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme-staging: "true"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
spec:
tls:
- secretName: {{ template "fullname" . }}-tls
hosts:
- {{ .Values.ingress.host }}
rules:
- host: {{ .Values.ingress.host }}
http:
paths:
- path: /
backend:
serviceName: {{ template "fullname" . }}
servicePort: 8888
As the application by default is isolated from all the others in the clusters, we now need to create a NetworkPolicy allowing traffic between the notebook and the ingress.
# filename: templates/networkpolicy.yaml
apiVersion: extensions/v1beta1
kind: NetworkPolicy
metadata:
name: {{ template "fullname" . }}
spec:
podSelector:
matchLabels:
app: {{ template "fullname" . }}
ingress:
- from:
- namespaceSelector:
matchLabels:
name: kube-ingress
ports:
- protocol: TCP
port: 8888
To make it easier for third-parties to provide custom resources, it is possible to specify which third-party resources a package supports. A third-party plugin can for instance be used to get information from a API and then insert the value into the templates.
In order to see whether the packages is valid, you can use
helm lint --strict <chart directory>
which will notify you of any errors.
This repository (that is, uninett/helm-charts) also contains a script called lint-chart.sh
which uses kubeval and kubetest to determine whether the package is valid. This script can be run in the following way:
./lint-chart.sh <chart directory>
.
In order to see what the Kubernetes objects will look like after having been passed through the template engine, you can use
helm template <chart directory>
which will output all the generated files to stdout.