DEV Community

Cover image for Master Helm, Chart the Kubernetes Seas ๐ŸŒŠ๐Ÿงญ๐Ÿดโ€โ˜ ๏ธ
Deon Pillsbury
Deon Pillsbury

Posted on

Master Helm, Chart the Kubernetes Seas ๐ŸŒŠ๐Ÿงญ๐Ÿดโ€โ˜ ๏ธ

Kubernetes is an amazing technology for managing large scale distributed applications. Its YAML configuration files make it easy to tune an application to fit your needs but Kubernetes has a lot of different objects and therefor a lot of different configuration files to maintain. For example, a production application could have a Deployment, Service, Secret, Config Map and Persistent Volume Claim for each of its components such as a Web App, API, Database, bot, etc. This is a lot of configuration files to maintain and they would be difficult to package if you are building an application which users need to download and run in their own environment. This is where Helm Charts come in to the picture, they group all of the configuration files needed to run an application into a single package called a Chart. Helm Charts templatize the configuration files, allow you to manage them with a global configuration and make it easy to publish your entire infrastructure as a single package. Lets take a look at some examples of using and building helm charts.

๐Ÿ’ก Refer to Kubernetes Quick Start Guide โ˜๏ธโšก๏ธ๐Ÿš€ for an in-depth Kubernetes tutorial

Using Helm Charts

Make sure you have Minikube setup and running and install Helm. After helm is installed we need to add a helm chart repository. Artifact Hub can be referenced to find helm repositories. A popular repository which offers production grade version of most of the popular open source applications is Bitnami.

Add the Bitnami repository and run the helm repo update command to make sure we have the latest versions.

$ helm repo add bitnami https://charts.bitnami.com/bitnami
"bitnami" has been added to your repositories

$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "bitnami" chart repository
Update Complete. โŽˆHappy Helming!โŽˆ
Enter fullscreen mode Exit fullscreen mode

Search the configured helm repositories to look for applications we may want to use, in this case search for a Redis chart.

$ helm search repo redis
NAME                    CHART VERSION   APP VERSION DESCRIPTION
bitnami/redis           18.2.0          7.2.2       Redis(R) is an open source, advanced key-value ...
bitnami/redis-cluster   9.0.13          7.2.2       Redis(R) is an open source, scalable, distribut...
Enter fullscreen mode Exit fullscreen mode

๐Ÿ’กย  The full details of helm charts can be referenced in their associated GitHub Repository.

Install the standard Redis chart.

$ helm install mydb bitnami/redis
NAME: mydb
LAST DEPLOYED: Mon Oct 30 07:43:01 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: redis
CHART VERSION: 18.2.0
APP VERSION: 7.2.2

** Please be patient while the chart is being deployed **

Redis® can be accessed on the following DNS names from within your cluster:

    mydb-redis-master.default.svc.cluster.local for read/write operations (port 6379)
    mydb-redis-replicas.default.svc.cluster.local for read-only operations (port 6379)

To get your password run:

    export REDIS_PASSWORD=$(kubectl get secret --namespace default mydb-redis -o jsonpath="{.data.redis-password}" | base64 -d)

To connect to your Redis® server:

1. Run a Redis® pod that you can use as a client:

   kubectl run --namespace default redis-client --restart='Never'  --env REDIS_PASSWORD=$REDIS_PASSWORD  --image docker.io/bitnami/redis:7.2.2-debian-11-r0 --command -- sleep infinity

   Use the following command to attach to the pod:

   kubectl exec --tty -i redis-client \
   --namespace default -- bash

2. Connect using the Redis® CLI:
   REDISCLI_AUTH="$REDIS_PASSWORD" redis-cli -h mydb-redis-master
   REDISCLI_AUTH="$REDIS_PASSWORD" redis-cli -h mydb-redis-replicas

To connect to your database from outside the cluster execute the following commands:

    kubectl port-forward --namespace default svc/mydb-redis-master 6379:6379 &
    REDISCLI_AUTH="$REDIS_PASSWORD" redis-cli -h 127.0.0.1 -p 6379
Enter fullscreen mode Exit fullscreen mode

Verify the helm chart is deployed and Redis is running on the Kubernetes cluster.

$ helm ls
NAME    NAMESPACE   REVISION    UPDATED                                 STATUS      CHART           APP VERSION
mydb    default     1           2023-10-30 07:43:01.730917 -0400 EDT    deployed    redis-18.2.0    7.2.2

$ kubectl get all
NAME                        READY   STATUS    RESTARTS   AGE
pod/mydb-redis-master-0     1/1     Running   0          4m4s
pod/mydb-redis-replicas-0   1/1     Running   0          4m4s
pod/mydb-redis-replicas-1   1/1     Running   0          3m23s
pod/mydb-redis-replicas-2   1/1     Running   0          3m1s

NAME                          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/kubernetes            ClusterIP   10.96.0.1       <none>        443/TCP    2d22h
service/mydb-redis-headless   ClusterIP   None            <none>        6379/TCP   4m4s
service/mydb-redis-master     ClusterIP   10.106.79.115   <none>        6379/TCP   4m4s
service/mydb-redis-replicas   ClusterIP   10.106.46.152   <none>        6379/TCP   4m4s

NAME                                   READY   AGE
statefulset.apps/mydb-redis-master     1/1     4m4s
statefulset.apps/mydb-redis-replicas   3/3     4m4s
Enter fullscreen mode Exit fullscreen mode

Follow the post installation prompt and verify that Redis is working correctly.

$ export REDIS_PASSWORD=$(kubectl get secret --namespace default mydb-redis -o jsonpath="{.data.redis-password}" | base64 -d)
$ kubectl run --namespace default redis-client --restart='Never'  --env REDIS_PASSWORD=$REDIS_PASSWORD  --image docker.io/bitnami/redis:7.2.2-debian-11-r0 --command -- sleep infinity
pod/redis-client created

$ kubectl exec --tty -i redis-client --namespace default -- bash
I have no name!@redis-client:/$ REDISCLI_AUTH="$REDIS_PASSWORD" redis-cli -h mydb-redis-master

mydb-redis-master:6379> SET key value
OK
mydb-redis-master:6379> GET key
"value"
Enter fullscreen mode Exit fullscreen mode

Helm charts are a great way to run popular applications like Databases since the helm chart implements many of the best practices for you and allows you to just provide higher level tuning.

Delete the Redis helm chart.

$ helm delete mydb
release "mydb" uninstalled

$ helm ls
NAME    NAMESPACE   REVISION    UPDATED STATUS  CHART   APP VERSION
Enter fullscreen mode Exit fullscreen mode

Helm utilizes a values.yaml file for its configuration, here is the Bitnami Redis values.yaml as a reference and a custom values.yaml file can be passed to the helm install command to override any of the values.

Create a custom values.yaml file to override the password and replica count.

๐Ÿ“ย values.yaml

auth:
  password: myDbSecret123

replica:
  replicaCount: 4
Enter fullscreen mode Exit fullscreen mode

Install Redis again but now pass in the updated configuration.

$ helm install mydb bitnami/redis -f values.yaml
NAME: mydb
LAST DEPLOYED: Tue Oct 31 06:57:24 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: redis
CHART VERSION: 18.2.0
APP VERSION: 7.2.2

** Please be patient while the chart is being deployed **

Redis&reg; can be accessed on the following DNS names from within your cluster:

    mydb-redis-master.default.svc.cluster.local for read/write operations (port 6379)
    mydb-redis-replicas.default.svc.cluster.local for read-only operations (port 6379)

To get your password run:

    export REDIS_PASSWORD=$(kubectl get secret --namespace default mydb-redis -o jsonpath="{.data.redis-password}" | base64 -d)

To connect to your Redis&reg; server:

1. Run a Redis&reg; pod that you can use as a client:

   kubectl run --namespace default redis-client --restart='Never'  --env REDIS_PASSWORD=$REDIS_PASSWORD  --image docker.io/bitnami/redis:7.2.2-debian-11-r0 --command -- sleep infinity

   Use the following command to attach to the pod:

   kubectl exec --tty -i redis-client \
   --namespace default -- bash

2. Connect using the Redis&reg; CLI:
   REDISCLI_AUTH="$REDIS_PASSWORD" redis-cli -h mydb-redis-master
   REDISCLI_AUTH="$REDIS_PASSWORD" redis-cli -h mydb-redis-replicas

To connect to your database from outside the cluster execute the following commands:

    kubectl port-forward --namespace default svc/mydb-redis-master 6379:6379 &
    REDISCLI_AUTH="$REDIS_PASSWORD" redis-cli -h 127.0.0.1 -p 6379
Enter fullscreen mode Exit fullscreen mode

Validate that the new configuration has been applied correctly, you should see the new password set in the secret and 4 replicas running.

$ helm ls
NAME    NAMESPACE   REVISION    UPDATED                                 STATUS      CHART           APP VERSION
mydb    default     1           2023-10-31 06:57:24.661346 -0400 EDT    deployed    redis-18.2.0    7.2.2

$ kubectl get all
NAME                        READY   STATUS    RESTARTS   AGE
pod/mydb-redis-master-0     1/1     Running   0          3m51s
pod/mydb-redis-replicas-0   1/1     Running   0          3m51s
pod/mydb-redis-replicas-1   1/1     Running   0          3m16s
pod/mydb-redis-replicas-2   1/1     Running   0          2m51s
pod/mydb-redis-replicas-3   1/1     Running   0          2m30s

NAME                          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/kubernetes            ClusterIP   10.96.0.1       <none>        443/TCP    3d21h
service/mydb-redis-headless   ClusterIP   None            <none>        6379/TCP   3m51s
service/mydb-redis-master     ClusterIP   10.102.17.165   <none>        6379/TCP   3m51s
service/mydb-redis-replicas   ClusterIP   10.111.189.75   <none>        6379/TCP   3m51s

NAME                                   READY   AGE
statefulset.apps/mydb-redis-master     1/1     3m51s
statefulset.apps/mydb-redis-replicas   4/4     3m51s

$ echo $(kubectl get secret --namespace default mydb-redis -o jsonpath="{.data.redis-password}" | base64 -d)
myDbSecret123

$ helm delete mydb
release "mydb" uninstalled
Enter fullscreen mode Exit fullscreen mode

Helm also allows you to override values by passing them in with the --set flag which can be used to pass in sensitive values such as secrets. In production this method is used to set the secrets of a helm chart using environment variables within the CI/CD Pipeline.

๐Ÿ’ก Refer to DevOps CI/CD Quick Start Guide with GitHub Actions ๐Ÿ› ๏ธ๐Ÿ™โšก๏ธ for a Guide on creating CI/CD Pipelines

Remove the password from the values.yaml file.

๐Ÿ“ย values.yaml

replica:
  replicaCount: 4
Enter fullscreen mode Exit fullscreen mode

Now pass in the password from the command line with the --set flag.

$ helm install mydb bitnami/redis --set auth.password=myDbSecret123 -f values.yaml
NAME: mydb
LAST DEPLOYED: Tue Oct 31 07:10:39 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: redis
CHART VERSION: 18.2.0
APP VERSION: 7.2.2

** Please be patient while the chart is being deployed **

Redis&reg; can be accessed on the following DNS names from within your cluster:

    mydb-redis-master.default.svc.cluster.local for read/write operations (port 6379)
    mydb-redis-replicas.default.svc.cluster.local for read-only operations (port 6379)

To get your password run:

    export REDIS_PASSWORD=$(kubectl get secret --namespace default mydb-redis -o jsonpath="{.data.redis-password}" | base64 -d)

To connect to your Redis&reg; server:

1. Run a Redis&reg; pod that you can use as a client:

   kubectl run --namespace default redis-client --restart='Never'  --env REDIS_PASSWORD=$REDIS_PASSWORD  --image docker.io/bitnami/redis:7.2.2-debian-11-r0 --command -- sleep infinity

   Use the following command to attach to the pod:

   kubectl exec --tty -i redis-client \
   --namespace default -- bash

2. Connect using the Redis&reg; CLI:
   REDISCLI_AUTH="$REDIS_PASSWORD" redis-cli -h mydb-redis-master
   REDISCLI_AUTH="$REDIS_PASSWORD" redis-cli -h mydb-redis-replicas

To connect to your database from outside the cluster execute the following commands:

    kubectl port-forward --namespace default svc/mydb-redis-master 6379:6379 &
    REDISCLI_AUTH="$REDIS_PASSWORD" redis-cli -h 127.0.0.1 -p 6379
Enter fullscreen mode Exit fullscreen mode

Verify the password has been set correctly then delete the chart.

$ echo $(kubectl get secret --namespace default mydb-redis -o jsonpath="{.data.redis-password}" | base64 -d)
myDbSecret123

$ helm delete mydb
release "mydb" uninstalled
Enter fullscreen mode Exit fullscreen mode

Nice! This is the basics for using helm charts and there are charts available for nearly every popular application, this gives us a simple way to get production grade instances of them running. Now lets take a look at creating a Kubernetes application.

App with K8s configuration files

โญ๏ธย The complete source code referenced in this guide is available on GitHub
https://github.com/dpills/helm-quick-start

Application setup

Create a small FastAPI app which will use a secret static token to authenticate the endpoint.

๐Ÿ’กย Refer to FastAPI Production Setup Guide ๐Ÿโšก๏ธ๐Ÿš€ย for a full Python FastAPI Guide

Install the necessary dependencies with Poetry.

๐Ÿ“ย pyproject.toml

[tool.poetry]
name = "helm-api"
version = "0.1.0"
description = ""
authors = ["dpills"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.11"
fastapi = "^0.104.1"
uvicorn = { extras = ["standard"], version = "^0.23.2" }
pydantic-settings = "^2.0.3"

[tool.poetry.group.dev.dependencies]
ruff = "^0.1.3"
black = "^23.10.1"
mypy = "^1.6.1"

[tool.black]
line-length = 88

[tool.ruff]
select = ["E", "F", "I"]
fixable = ["ALL"]
exclude = [".git", ".mypy_cache", ".ruff_cache"]
line-length = 88

[tool.mypy]
disallow_any_generics = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_return_any = true
strict_equality = true
disallow_untyped_decorators = false
ignore_missing_imports = true
implicit_reexport = true
plugins = "pydantic.mypy"

[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true
warn_required_dynamic_aliases = true
warn_untyped_fields = true

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Enter fullscreen mode Exit fullscreen mode
$ poetry install
Installing dependencies from lock file

No dependencies to install or update
Enter fullscreen mode Exit fullscreen mode

Create the API code.

๐Ÿ“ย main.py

import uvicorn
from fastapi import FastAPI, HTTPException, Security
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from pydantic import BaseModel
from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    static_token: str
    model_config = SettingsConfigDict(env_file=".env", extra="ignore")

settings = Settings()
app = FastAPI()
security = HTTPBearer()

class Data(BaseModel):
    item: str

@app.get("/data", response_model=Data)
async def get_data(
    access_token: HTTPAuthorizationCredentials = Security(security),
) -> Data:
    """
    Get Data
    """
    if access_token.credentials != settings.static_token:
        raise HTTPException(status_code=401, detail="Unauthorized")

    return Data(item="helm")

if __name__ == "__main__":
    uvicorn.run(
        "main:app",
        host="0.0.0.0",
        port=8000,
        log_level="debug",
        reload=True,
    )
Enter fullscreen mode Exit fullscreen mode

Add a .env file for local testing.

๐Ÿ“ย .env

STATIC_TOKEN=MY_SECRET_TOKEN_123
Enter fullscreen mode Exit fullscreen mode

Make sure the API is working as expected locally, it should only allow the API call to be made when the secret static token is sent in the Authorization Bearer token header.

$ poetry run python3 main.py
INFO:     Will watch for changes in these directories: ['/Users/dpills/articles/helm-quick-start/api']
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [13765] using WatchFiles
INFO:     Started server process [13771]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     127.0.0.1:51135 - "GET /data HTTP/1.1" 403 Forbidden
INFO:     127.0.0.1:51136 - "GET /data HTTP/1.1" 200 OK
Enter fullscreen mode Exit fullscreen mode
$ curl -v http://localhost:8000/data
< HTTP/1.1 403 Forbidden
{"detail":"Not authenticated"}

$ curl -v http://localhost:8000/data -H 'Authorization: Bearer MY_SECRET_TOKEN_123'
< HTTP/1.1 200 OK
{"item":"helm"}
Enter fullscreen mode Exit fullscreen mode

Now that we know it is working as expected, containerize it with a Dockerfile.

๐Ÿ“ย Dockerfile

FROM python:3.11-slim-bookworm as requirements-stage

RUN pip install poetry
COPY ./pyproject.toml ./poetry.lock /
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes --without=dev

FROM python:3.11-slim-bookworm

COPY --from=requirements-stage /requirements.txt /requirements.txt
COPY ./app /app

RUN python3 -m pip install --no-cache-dir --upgrade -r requirements.txt
EXPOSE 8000

ENTRYPOINT ["uvicorn", "--host", "0.0.0.0", "--port", "8000", "main:app"]
Enter fullscreen mode Exit fullscreen mode

๐Ÿ’กย Refer to Containers Demystified ๐Ÿณ๐Ÿค” for a Docker container guide

Build and push the container image to Docker Hub.

$ docker build . -t dpills/helm-api:v1.0.0 --push
[+] Building 40.7s (13/13) FINISHED                                               docker:desktop-linux
...
 => [requirements-stage 2/4] RUN pip install poetry                                              19.6s
 => [requirements-stage 3/4] COPY ./pyproject.toml ./poetry.lock /                                0.0s 
 => [requirements-stage 4/4] RUN poetry export -f requirements.txt --output requirements.txt --w  0.5s 
 => [stage-1 2/4] COPY --from=requirements-stage /requirements.txt /requirements.txt              0.0s 
 => [stage-1 3/4] COPY ./main.py /main.py                                                         0.0s 
 => [stage-1 4/4] RUN python3 -m pip install --no-cache-dir --upgrade -r requirements.txt         6.4s 
...
 => => naming to docker.io/dpills/helm-api:v1.0.0                                                 0.0s 
 => pushing dpills/helm-api:v1.0.0 with docker                                                   11.2s 
Enter fullscreen mode Exit fullscreen mode

Standard Kubernetes Configuration

Now that we have our application container image built, we need to create the Kubernetes configuration files to deploy it.

Create a secret for our STATIC_TOKEN environment variable.

๐Ÿ“ย k8s/secret.yaml

apiVersion: v1
kind: Secret
metadata:
  name: helm-guide-api-secret
data:
  STATIC_TOKEN: TVlfU0VDUkVUX1RPS0VOXzEyMw==
Enter fullscreen mode Exit fullscreen mode

๐Ÿ’กย This can be generated from the .env file with kubectl . Secret files are just base64 encodings of the values.

$ kubectl create secret generic helm-guide-api-secret --from-env-file=.env --dry-run=client -o yaml
apiVersion: v1
data:
  STATIC_TOKEN: TVlfU0VDUkVUX1RPS0VOXzEyMw==
kind: Secret
metadata:
  creationTimestamp: null
  name: helm-guide-api-secret

$ echo TVlfU0VDUkVUX1RPS0VOXzEyMw== | base64 -d
MY_SECRET_TOKEN_123

Create a deployment with the new API container image and map the secret to the environment variables.

๐Ÿ“ย k8s/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: helm-guide-api
  name: helm-guide-api
spec:
  replicas: 1 # One Instance
  selector:
    matchLabels:
      app: helm-guide-api
  template:
    metadata:
      labels:
        app: helm-guide-api
    spec:
      containers:
        - name: helm-guide-api
          image: dpills/helm-api:v1.0.0 # API Image
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8000
              protocol: TCP
          envFrom:
            - secretRef:
                name: helm-guide-api-secret # Secret Mapping
          resources:
            limits:
              cpu: "1"
              memory: 1Gi
            requests:
              cpu: "1"
              memory: 1Gi
Enter fullscreen mode Exit fullscreen mode

Finally, create a service to expose the API.

๐Ÿ“ย k8s/service.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    app: helm-guide-api
  name: helm-guide-api-svc
spec:
  ports:
    - name: http
      port: 8000
      protocol: TCP
      targetPort: 8000
  selector:
    app: helm-guide-api
Enter fullscreen mode Exit fullscreen mode

Apply the Kubernetes configuration.

$ kubectl apply -f k8s
deployment.apps/helm-guide-api created
secret/helm-guide-api-secret created
service/helm-guide-api-svc created
Enter fullscreen mode Exit fullscreen mode

Connect to the service with Minikube.

$ minikube service helm-guide-api-svc --url
๐Ÿ˜ฟ  service default/helm-guide-api-svc has no node port
http://127.0.0.1:51476
โ—  Because you are using a Docker driver on darwin, the terminal needs to be open to run it.
Enter fullscreen mode Exit fullscreen mode

Validate it is working as expected with the exposed Minikube port from the last command.

$ curl -v http://localhost:51476/data
< HTTP/1.1 403 Forbidden
{"detail":"Not authenticated"}

$ curl -v http://localhost:51476/data -H 'Authorization: Bearer MY_SECRET_TOKEN_123'
< HTTP/1.1 200 OK
{"item":"helm"}
Enter fullscreen mode Exit fullscreen mode

Awesome! this is great but if we look at the Kubernetes configuration files we can see that the root application name helm-guide-api needs to be constantly referenced throughout the configuration and there is a lot of boilerplate which will never change. The main items we will update often will be the image version and potentially replica count. Also with following Infrastructure as Code (IaC) best practices and committing all of our configuration files, we are exposing our application secret. Being able to template these files and create variables for the portions that we need to change or keep secret is what Helm allows us to do.

App with a Helm Chart

Create the new helm chart with the helm create command. You can take a look at the default bootstrapped helm chart example created by this command to see a lot of the functionality which we will not fully cover in this article.

$ helm create infra
Creating infra
Enter fullscreen mode Exit fullscreen mode

Update the Chart.yaml with a description and version.

๐Ÿ“ย infra/Chart.yaml

apiVersion: v2
name: helm-guide-api
description: Helm API
type: application
version: 1.0.0
appVersion: "1.0.0"
Enter fullscreen mode Exit fullscreen mode

Now delete all of the bootstrapped files in the templates folder and copy the deployment.yaml , service.yaml and secret.yaml configuration files into the templates folder.

The file structure should look like this.

Helm Chart

Update the service to use the helm release name for the application name using the double curly brace helm templating syntax.

๐Ÿ“ย infra/templates/service.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    app: {{ .Release.Name }}
  name: {{ .Release.Name }}-svc
spec:
  ports:
    - name: http
      port: 8000
      protocol: TCP
      targetPort: 8000
  selector:
    app: {{ .Release.Name }}
Enter fullscreen mode Exit fullscreen mode

For the deployment file we need to pass in the replica count and container image and as we saw in the start of this article the values.yaml file is used to pass in values. Update the values.yaml file with an API configuration defining these two values.

๐Ÿ“ย infra/values.yaml

api:
  image: dpills/helm-api:v1.0.0
  replicas: 1
Enter fullscreen mode Exit fullscreen mode

Update the deployment template to use the release name, api image and replica count. For the replica count, cast the type to make sure it is an integer.

๐Ÿ“ย infra/templates/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: {{ .Release.Name }}
  name: {{ .Release.Name }}
spec:
  replicas: {{ int .Values.api.replicas }}
  selector:
    matchLabels:
      app: {{ .Release.Name }}
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}
    spec:
      containers:
        - name: {{ .Release.Name }}
          image: {{ .Values.api.image }}
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8000
              protocol: TCP
          envFrom:
            - secretRef:
                name: {{ .Release.Name }}-secret
          resources:
            limits:
              cpu: "1"
              memory: 1Gi
            requests:
              cpu: "1"
              memory: 1Gi
Enter fullscreen mode Exit fullscreen mode

For the secret we need to pass in the static token and base64 encode it but we should not commit the secret to our source code. Add an entry will a null value to the values.yaml file which will be passed in with the helm install --set flag.

๐Ÿ“ย infra/values.yaml

api:
  image: dpills/helm-api:v1.0.0
  replicas: 1
  static_token: null # Pass this in with --set static_token=XXX
Enter fullscreen mode Exit fullscreen mode

Update the secret to to use the release name, validate that a static token value has been passed in with the required keyword and base64 encode it by piping to the b64enc function.

๐Ÿ“ย infra/templates/secret.yaml

apiVersion: v1
kind: Secret
metadata:
  name: {{ .Release.Name }}-secret
data:
  STATIC_TOKEN: {{ required "A static token is required" .Values.api.static_token | b64enc }}
Enter fullscreen mode Exit fullscreen mode

The NOTES.txt is what is printed out after installing the chart, update this to some simple text for this example.

๐Ÿ“ย infra/templates/NOTES.txt

Helm API Example!
Enter fullscreen mode Exit fullscreen mode

This should now be fully setup, delete the standard Kubernetes configuration deployment if it is still running and run the application with the helm chart instead.

$ kubectl delete -f k8s
deployment.apps "helm-guide-api" deleted
secret "helm-guide-api-secret" deleted
service "helm-guide-api-svc" deleted

$ helm install helm-api ./infra --set api.static_token=MY_SECRET_TOKEN_123
NAME: helm-api
LAST DEPLOYED: Wed Nov  1 08:44:37 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Helm API Example!
Enter fullscreen mode Exit fullscreen mode

Validate the application is working as expected.

$ helm ls
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
helm-api        default         1               2023-11-01 08:44:37.222599 -0400 EDT    deployed        helm-guide-api-1.0.0    1.0.0

$ kubectl get all
NAME                            READY   STATUS    RESTARTS   AGE
pod/helm-api-79877dcfc6-8rv47   1/1     Running   0          61s

NAME                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/helm-api-svc   ClusterIP   10.109.109.197   <none>        8000/TCP   61s
service/kubernetes     ClusterIP   10.96.0.1        <none>        443/TCP    4d23h

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/helm-api   1/1     1            1           61s

NAME                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/helm-api-79877dcfc6   1         1         1       61s
Enter fullscreen mode Exit fullscreen mode
$ minikube service helm-api-svc --url
๐Ÿ˜ฟ  service default/helm-api-svc has no node port
http://127.0.0.1:51778
โ—  Because you are using a Docker driver on darwin, the terminal needs to be open to run it.

$ curl -v http://localhost:51778/data
< HTTP/1.1 403 Forbidden
{"detail":"Not authenticated"}

$ curl -v http://localhost:51778/data -H 'Authorization: Bearer MY_SECRET_TOKEN_123'
< HTTP/1.1 200 OK
{"item":"helm"}
Enter fullscreen mode Exit fullscreen mode

Congrats you just created a helm chart! ๐ŸŽ‰ย  We can see that the release name that we passed in to the helm install command has been used for all of the object names. The primary configuration file is greatly simplified with the values.yaml file and our secret configuration is committed to our source code but the sensitive value is no longer exposed. The Helm Chart Documentation can be referenced for a full overview of the syntax and features. Utilizing helm charts to manage Kubernetes applications has been very useful for me and I hope this guide helps you build your next amazing application! ๐Ÿ˜Š

Top comments (1)

Collapse
 
kingaj12 profile image
Alan King

Very nice! I did a couple of things differently.
1) in the Docker file used the line COPY main.py /
2) used port forwarding to get to the service: kubectl port-forward service/helm-api-svc 8001:8000