- Lint your changes:
docker run --rm -it -v ${PWD}:/charts -w /charts quay.io/helmpack/chart-testing:v2.4.0 ct lint --chart-yaml-schema scripts/schema.yaml --chart-dirs incubator --chart-dirs stable
- End-to-end test your changes:
- Install kind 0.7.0+
- Install chart-testing (ct)
scripts/e2e-test.sh setup
to set up Kubernetes in Dockerscripts/e2e-test.sh test
to test your local changes until they passscripts/e2e-test.sh teardown
when you are done testing to delete your cluster
- Submit a PR
- Follow the [Chart Guidelines](#Chart Guidelines)
- Make sure your chart passes a
helm lint
There is a .pre-commit-config.yaml
in this repo. If you use pre-commit, then you can just run pre-commit install
to automatically run the linter on modified charts.
Chart releases must be immutable. Any change to a chart warrants a chart version bump even if it is only changes to the documentation.
The Chart.yaml
should be as complete as possible. The following fields are mandatory:
- name (same as chart's directory)
- home
- version
- appVersion
- description
- maintainers (name should be Github username)
Stable charts should not depend on charts in incubator.
Resources and labels should follow some conventions. The standard resource metadata (metadata.labels
and spec.template.metadata.labels
) should be this:
name: {{ include "myapp.fullname" . }}
labels:
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ include "myapp.chart" . }}
If a chart has multiple components, a app.kubernetes.io/component
label should be added (e. g. app.kubernetes.io/component: server
). The resource name should get the component as suffix (e. g. name: {{ include "myapp.fullname" . }}-server
).
Note that templates have to be namespaced. With Helm 2.7+, helm create
does this out-of-the-box. The app.kubernetes.io/name
label should use the name
template, not fullname
as is still the case with older charts.
spec.selector.matchLabels
must be specified should follow some conventions. The standard selector should be this:
selector:
matchLabels:
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
If a chart has multiple components, a component
label should be added to the selector (see above).
spec.selector.matchLabels
defined in Deployments
/StatefulSets
/DaemonSets
>=v1/beta2
must not contain helm.sh/chart
label or any label containing a version of the chart, because the selector is immutable.
The chart label string contains the version, so if it is specified, whenever the the Chart.yaml version changes, Helm's attempt to change this immutable field would cause the upgrade to fail.
- If it does not specify
spec.selector.matchLabels
, set it - Remove
helm.sh/chart
label inspec.selector.matchLabels
if it exists - Bump patch version of the Chart
- Remove
helm.sh/chart
label inspec.selector.matchLabels
if it exists - Bump major version of the Chart as it is a breaking change
Label selectors for services must have both app.kubernetes.io/name
and app.kubernetes.io/instance
labels.
selector:
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
If a chart has multiple components, a app.kubernetes.io/component
label should be added to the selector (see above).
In case of a Statefulset
, spec.volumeClaimTemplates.metadata.labels
must have both app.kubernetes.io/name
and app.kubernetes.io/instance
labels, and must not contain helm.sh/chart
label or any label containing a version of the chart, because spec.volumeClaimTemplates
is immutable.
labels:
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
If a chart has multiple components, a app.kubernetes.io/component
label should be added to the selector (see above).
In case of a PersistentVolumeClaim
, unless special needs, matchLabels
should not be specified
because it would prevent automatic PersistentVolume
provisioning.
- Yaml file should be indented with two spaces.
- List indentation style should be consistent.
- There should be a single space after
{{
and before}}
.
- Docker images should be configurable. Image tags should use stable versions.
image:
repository: myapp
tag: 1.2.3
pullPolicy: IfNotPresent
- The use of the
default
function should be avoided if possible in favor of defaults invalues.yaml
. - It is usually best to not specify defaults for resources and to just provide sensible values that are commented out as a recommendation, especially when resources are rather high. This makes it easier to test charts on small clusters or Minikube. Setting resources should generally be a conscious choice of the user.
- Persistence should be enabled by default
- PVCs should support specifying an existing claim
- Storage class should be empty by default so that the default storage class is used
- All options should be shown in README.md
- Example persistence section in values.yaml:
persistence:
enabled: true
## If defined, storageClassName: <storageClass>
## If set to "-", storageClassName: "", which disables dynamic provisioning
## If undefined (the default) or set to null, no storageClassName spec is
## set, choosing the default provisioner. (gp2 on AWS, standard on
## GKE, AWS & OpenStack)
##
storageClass: ""
accessMode: ReadWriteOnce
size: 10Gi
# existingClaim: ""
- Example pod spec within a deployment:
volumes:
- name: data
{{- if .Values.persistence.enabled }}
persistentVolumeClaim:
claimName: {{ .Values.persistence.existingClaim | default (include "myapp.fullname" .) }}
{{- else }}
emptyDir: {}
{{- end -}}
- Example pvc.yaml:
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }}
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: {{ include "myapp.fullname" . }}
labels:
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ include "myapp.chart" . }}
spec:
accessModes:
- {{ .Values.persistence.accessMode | quote }}
resources:
requests:
storage: {{ .Values.persistence.size | quote }}
{{- if .Values.persistence.storageClass }}
{{- if (eq "-" .Values.persistence.storageClass) }}
storageClassName: ""
{{- else }}
storageClassName: "{{ .Values.persistence.storageClass }}"
{{- end }}
{{- end }}
{{- end }}
-
Autoscaling should be disabled by default
-
All options should be shown in README.md
-
Example autoscaling section in values.yaml:
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 5
targetCPUUtilizationPercentage: 50
targetMemoryUtilizationPercentage: 50
- Example hpa.yaml:
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "myapp.fullname" . }}
labels:
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ include "myapp.chart" . }}
app.kubernetes.io/component: "{{ .Values.name }}"
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "myapp.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
- type: Resource
resource:
name: cpu
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: memory
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
- See the Ingress resource documentation for a broader concept overview
- Ingress should be disabled by default
- Example ingress section in values.yaml:
ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
path: /
hosts:
- chart-example.test
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.test
- Example ingress.yaml:
{{- if .Values.ingress.enabled -}}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ include "myapp.fullname" }}
labels:
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ include "myapp.chart" . }}
{{- with .Values.ingress.annotations }}
annotations:
{{ toYaml . | indent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ . | quote }}
http:
paths:
- path: {{ .Values.ingress.path }}
backend:
serviceName: {{ include "myapp.fullname" }}
servicePort: http
{{- end }}
{{- end }}
- Example prepend logic for getting an application URL in NOTES.txt:
{{- if .Values.ingress.enabled }}
{{- range .Values.ingress.hosts }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ . }}{{ $.Values.ingress.path }}
{{- end }}
README.md
and NOTES.txt
are mandatory. README.md
should contain a table listing all configuration options. NOTES.txt
should provide accurate and useful information how the chart can be used/accessed.
If you would like to use helm-docs, it is included in the pre-commit file. Just make sure your chart is not in the .helmdocsignore
file.
Please create a github-style CODEOWNERS file in your chart folder and add your name to it. This will ensure that you are asked to review PRs that involve your chart.