Building Your Own Helm Chart
In this documentation we will go over configuration of HELM we require to have a better experience and a seamless experience.
Each hyperlink will redirect you to official documentation which should provide more dept to help you better undestand the changes we are about to do.
Initializing Your First Chart.
-
We will now create a Helm Chart
-
browse to a folder where you would like to create the helm chart
cd ~/git_repo/helm/
helm create hello_thales
- You should now have these files and folders.
.helmignore
Chart.yaml
charts
templates
values.yaml
- Let's modify the file Chart.yaml
- You should read each comment and understand what each does
- We want to change the
name
,description
,version
andappVersion
e.g.apiVersion: v2
name: hello_thales
description: An Example Helm Chart to help Thales Devs
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.0.0"
Editing Your Templates
The following changes are to be put in place to ensure a seamless experience on our platform.
Liveness Readiness
- Ensure readiness probe is configured
- Ensure liveness probe is configured
- Configure Liveness, Readiness and Startup Probes
-
These configuration help your application know if it has started, if it's live and if it can start accepting traffic.
-
The configuration will be found in templates/deployment.yaml
- Under
spec.template.spec.containers
- you should see this block of code
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http- We will change this to leverage the usage of the
values.yaml
file and add more option to control both probes
ports:
- name: http
containerPort: {{ .Values.application.port }}
protocol: TCP
{{- if .Values.livenessProbe.enabled }}
livenessProbe:
httpGet:
path: /
port: {{ .Values.application.port }}
initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
successThreshold: {{ .Values.livenessProbe.successThreshold }}
failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
{{- end }}
{{- if .Values.readinessProbe.enabled }}
readinessProbe:
httpGet:
path: /
port: {{ .Values.application.port }}
initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
successThreshold: {{ .Values.readinessProbe.successThreshold }}
failureThreshold: {{ .Values.readinessProbe.failureThreshold }}- Now let's configure the probe in the
values.yaml
- To make use of the
httpGet
probe we need to define the container port- in
image:
repository: nginx
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
application:
port: 80 - Between the
ingress:
andresources:
add the following, these number can be adjusted if needed by your application.
livenessProbe:
enabled: true
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
successThreshold: 1
readinessProbe:
enabled: true
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
successThreshold: 1 - Under
-
Note: Readiness probes runs on the container during its whole lifecycle.
Caution: Liveness probes do not wait for readiness probes to succeed. If you want to wait before executing a liveness probe you should use initialDelaySeconds or a startupProbe.
Pod Disruption Budget
- Disruptions
- Pod Disruption Budget
- As an application owner, you can create a PodDisruptionBudget (PDB) for each application. A PDB limits the number of Pods of a replicated application that are down simultaneously from voluntary disruptions. For example, a quorum-based application would like to ensure that the number of replicas running is never brought below the number needed for a quorum. A web front end might want to ensure that the number of replicas serving load never falls below a certain percentage of the total.
Note, you need to take into account your
affinity
andanti-affinity
, number ofnodes
andreplica
to build properpdb
as this can result in a deadlock preventing us to properly upgrade the cluster.
- The first thing to do if you want to use pdb will be to have more than
1
replica of your application- In values.yaml, you should find this line
replicaCount:
depending on your need bump this to a higher number than 1.
- In values.yaml, you should find this line
# Default values for template.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 2
image:
repository: nginx
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: "1.20.1"
-
Then we need to create a new file, call it pdb.yaml, then add these lines
{{- if and .Values.podDisruptionBudget.enabled}}
---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: {{ template "deployment.fullname" . }}-pdb
namespace: {{ .Release.Namespace }}
labels:
spec:
{{- if .Values.podDisruptionBudget.minAvailable }}
minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
{{- end }}
{{- if .Values.podDisruptionBudget.maxUnavailable }}
maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }}
{{- end }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "deployment.name" . }}
{{- end }}- In values.yaml add these lines and specify disruption budget depending on your need following the documentation recommendation. In our case we will allow all disruption but 1 pod, hence specifying 1 at minAvailable.
- You can specify only one of
maxUnavailable
andminAvailable
in a singlePodDisruptionBudget
.maxUnavailable
can only be used to control the eviction of pods that have an associated controller managing them. In the examples below, "desired replicas" is the scale of the controller managing the pods being selected by thePodDisruptionBudget
.
- You can specify only one of
podDisruptionBudget:
enabled: true
minAvailable: 1
# maxUnavailable:- With a replicaCount of
2
this is what the status of yourpdb
should look like.
status:
currentHealthy: 2
desiredHealthy: 1
disruptionsAllowed: 1
expectedPods: 2
observedGeneration: 1 - In values.yaml add these lines and specify disruption budget depending on your need following the documentation recommendation. In our case we will allow all disruption but 1 pod, hence specifying 1 at minAvailable.
WhiteList and Network Security Groups
- We don't recommand the usage of nginx/whitelist-source-range as it doesn't block other ip to access the cluster it will only block the access at the nginx level.
- e.g.
nginx.ingress.kubernetes.io/whitelist-source-range: 10.201.248.4,10.201.248.5,10.201.198.128/25
- We would recommend openning a ticket with our team so we can add these addresse in the Network Security Groups instead. This way only allowed Ip would be able to have access to the cluster.
Assign Pods to Nodes using Node Affinity
- Node Affinity
- Pod and Node Affinity is a great tool to ensure a proper distribution of your apps. In this scenario we ask the deployment to spread as much as possible the deployement across differente node.
- In values.yaml you should see the following section.
affinity: {}
- Change it to :
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
topologyKey: "app.kubernetes.io/instance"
labelSelector:
matchLabels:
app.kubernetes.io/name: hello_thales # This would be the name of your application, in this exemple we use hello_thales.
- This will tell your deployment to use the Label
app.kubernetes.io/name: hello_thales
during the scheduling to not schedule any pod on the same node as another one, as we use thepodAntiAffinity
with the topologyKeyapp.kubernetes.io/instance
- This will force pod to be schedule on different node instead of all being stacked on the same and lower the risk of the application going down if a node fail.
CPU, Memory Request & Limits
- Ensure CPU request is set
- Ensure CPU limits are set
- Ensure memory requests are set
- Ensure memory limits are set
- Configuring proper
CPU
,Memory
limits/request is essential for the scaling/scheduling of your pods/container.- You will need to understand the usage of your application to decide the resources needed.
- You have access to
Grafana
to monitor the metrics of your application and make a responsible choice on the resource your application need to run - In
values.yaml
, you should find the resources: bloc- Uncomment the necessary lines,
don't forget to delete the {} after resources:
- Uncomment the necessary lines,
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
- Configuring proper
Images Tags and Digest
- Ensure image tag is set to Fixed - not Latest or Blank
- Ensure images are selected using a digest
- We recommend you avoid using the
:latest
and:blank
tags when deploying containers in production as it is harder to track which version of the image is running, and more difficult to roll back properly. - Pulling using a digest allows you to “pin” an image to that version, and guarantee that the image you’re using is always the same. Digests also prevent race-conditions; if a new image is pushed while a deploy is in progress, different nodes may be pulling the images at different times, so some nodes have the new image, and some have the old one. Services automatically resolve tags to digests, so you don't need to manually specify a digest.
image:
repository: nginx
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: "1.20.1" - We recommend you avoid using the
Managing Multiple Env with secrets
- If you need to manage multiple env for your container during your deployment and use secrets here's a simple way to do it instead of adding tons of line in your deployment template.
- At the top of your deployment.yml add
{{- $deploymentName := include "deployment.fullname" . -}}
- Then under spec.template.spec.container add these line.
env:
{{- range $key, $value := .Values.extraEnv }}
- name: {{ $key }}
valueFrom:
secretKeyRef:
name: {{ $deploymentName }}
key: {{ $value }}
{{- end }} - In values.yaml add the lines
extraEnv:
and for each of key value pair you need underextraEnv:
key1: value1
key2: value2
Security, Filesystem & Users
- Ensure containers do not run with AllowPrivilegeEscalation
- Use Read-Only filesystem for containers where possible
- Minimize admission of root containers
- Ensure securityContext is applied to pods and containers
- Ensure admission of containers with capabilities assigned is limited
- Ensure containers run with a high UID to avoid host conflict
- We recommend using these configuration to limit the privilege of your containers.
securityContext:
# capabilities:
# drop:
# - ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 10000
Enabling or Disabling the deployment of an helm resource
- In a scenario where you would apply a configmap for your dev environment but not in prod and you would like to leverage the same helm chart. You can easily do so by adding condition to the resources. Here's a quick example.
- At the top of your resources file, add the following line
{{- if .Values.ConfigMap.enabled -}}
- In your values.yaml file add these lines to control the deployment of the configmap
ConfigMap:
enabled: <bool>
Other Use Cases
- Ensure seccomp is set to Docker/Default or Runtime/Default
- If needed you can also add this annotation to disables non-essential system calls.
annotations:
seccomp.security.alpha.kubernetes.io/pod: "docker/default"
or
seccomp.security.alpha.kubernetes.io/pod: "runtime/default"
- If needed you can also add this annotation to disables non-essential system calls.