Skip to content

Latest commit

 

History

History
774 lines (574 loc) · 24 KB

File metadata and controls

774 lines (574 loc) · 24 KB

Deploying your Node.js App via Helm

Packaging up your Node.js application into a Helm Chart

This will show you how to create your own Helm Chart for your Node.js Application and how to tweak it to best fit your applications needs.

In this self-paced tutorial you will:

  • Create a Helm Chart
  • Add a dependency chart
  • Create liveness probes

The application you will use is the one created from - https://github.com/nodeshift/mern-workshop

Prerequisites

Before getting started, make sure you have the following prerequisites installed on your system.

  1. Install Node.js 16 (or use nvm for linux, mac or nvm-windows for windows)
  2. Podman v4 (and above)
    • On Mac: Podman
    • On Windows: Skip this step, as for installing Podman you will get prompt during Podman Desktop installation.
    • On Linux: Podman
  3. Podman Desktop
  4. Kubernetes
  5. Helm v3 - Installation
    • Note: This workshop tested with Helm v3.7

Setting up

Starting Podman Machine

Linux

Nothing to do, no Podman machine is required on Linux

On Mac

After installing podman, open a terminal and run the below commands to initialize and run the podman machine:

NOTE: *On Apple M1 Pro chip, the system version has to be 12.4 and above.

podman machine init --cpus 2 --memory 8096 --disk-size 20
podman machine start
podman system connection default podman-machine-default-root

On Windows

  1. Launch Podman Desktop and on the home tab click on install podman. In case of any missing parts for podman installation (e.x. wsl, hyper-v, etc.) follow the instructions indicated by Podman Desktop on the home page. In that case you might need to reboot your system several times.

  2. After installing podman, set WSL2 as your default WSL by entering below command in PowerShell (with administration priviledges).

    wsl --set-default-version 2
    
  3. On Podman Desktop Home tab -> click on initialize Podman -> wait till the initialization is finished

  4. On Podman Desktop Home tab -> click on Run Podman to run podman.

On Windows Home

  1. Downlodad podman from https://github.com/containers/podman/releases the Windows installer file is named podman-v.#.#.#.msi
  2. Run the MSI file
  3. Launch as Administrator a new Command Prompt
  4. On the Command Prompt run:
    podman machine init
    podman machine set --rootful
    podman machine start
    
  5. Launch Podman Desktop to see and manage your containers, images, etc.

Starting Kubernetes

On Mac:

  1. Install Minikube

    Download binary file (click to expand)

    x86-64

    curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-amd64
    

    ARM64

    curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-arm64
    

    Add minikube binary file to your PATH system variable

    chmod +x minikube-darwin-*
    mv minkube-linux-* /usr/local/bin/minikube
    
  2. start minikube

    minikube start --driver=podman --container-runtime=cri-o
    

On Windows:

  1. Download minikube

    https://github.com/kubernetes/minikube/releases/latest/download/minikube-windows-amd64.exe
    
  2. Rename minikube-windows-amd64.exe to minikube.exe

  3. Move minikube under C:\Program Files\minikube directory

  4. Add minikube.exe binary to your PATH system variable

    1. Right-click on the Start Button -> Select System from the context menu -> click on Advanced system settings
    2. Go to the Advanced tab -> click on Environment Variables -> click the variable called Path -> Edit
    3. Click New -> Enter the path to the folder containing the binary e.x. C:\Program Files\minikube -> click OK to save the changes to your variables
    4. Start Podman Desktop and click on run podman
  5. Start minikube:

    • For windows Start minikube by opening Powershell or Command Prompt as administrator and enter below command.
    minikube start
    
    • For windows Home Start minikube by opening Powershell or Command Prompt as administrator and enter below command.
    minikube start --driver=podman --container-runtime=containerd
    

On Linux

  1. Install Minikube

    Download binary file (click to expand)

    x86-64

    curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
    

    ARM64

    curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-arm64
    

    ARMv7

    curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-arm
    

    ppc64

    curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-ppc64le
    

    S390x

    curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-s390x
    

    Add minikube binary file to your PATH system variable

    chmod +x minikube-linux-*
    mv minkube-linux-* /usr/local/bin/minikube
    
  2. Change minikube for starting podman rootless https://minikube.sigs.k8s.io/docs/drivers/podman/#rootless-podman

    minikube config set rootless true
    
  3. start minikube

    minikube start  --driver=podman --container-runtime=containerd
    
  4. Possible additional steps needed

Installing Helm v3.7

Helm is a package manager for Kubernetes. By installing a Helm "chart" into your Kubernetes cluster you can quickly run all kinds of different applications. You can install Helm by downloading the binary file and adding it to your PATH:

  1. Download the binary file from the section Installation and Upgrading for Operating system accordingly.

  2. Extract it:

    • On Linux:
      tar -zxvf helm-v3.7.2-*
      
    • On Windows: Right Click on helm-v3.7.2-windows-amd64 zipped file -> Extract All -> Extract
    • On Mac:
      tar -zxvf helm-v3.7.2-*
      
  3. Add helm binary file to your PATH system variable

    On Linux and Mac (sudo required for cp step on linux):

    cp `./<your-linux-distro>/helm` /usr/local/bin/helm
    rm -rf ./<your-linux-distro>
    

    If running on Mac results in a pop up indicating that the app could not be verified, you will need to go to Apple menu > System Preferences, click Security & Privacy and allow helm to run.

    On Windows:

    1. Move helm binary file to C:\Program Files\helm
    2. Right-click on the Start Button -> Select System from the context menu -> click on Advanced system settings
    3. Go to the Advanced tab -> click on Environment Variables -> click the variable called Path -> Edit
    4. Click New -> Enter the path to the folder containing the binary e.x. C:\Program Files\helm -> click OK to save the changes to your variables

Downloading the application

Make sure you clone down the application repo and you are in the correct directory.

$ git clone https://github.com/nodeshift/mern-workshop.git
$ cd mern-workshop

1. Create your Helm Chart files

In your terminal inside the folder holding your application run:

$ mkdir chart
$ cd chart
$ helm create myapp

This will create the following file structure:

myapp/
├── .helmignore   # Contains patterns of which files to ignore when packaging your Helm Chart.
├── Chart.yaml    # Information about your chart
├── values.yaml   # The default values for your templates
├── charts/       # Charts that this chart depends on
└── templates/    # The template files
    └── tests/    # The test files

These files will form the basis of your Helm Chart, lets explore the pre-created files and make some necessary changes.

2. Editing the .helmignore file

This file works the same as any .ignore file, you just fill in the patterns you don't want to be packaged up into the helm chart. For example, if you have some secrets saved as a JSON file that you do not want to be inside the helm chart. For our application we do not have any files we need to protect so let's move on to the next step.

3. The Chart.yaml file

This file contains the base information about your Helm Chart, your premade one should look similar to this (with extra comments):

apiVersion: v2
name: myapp
description: A Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: 1.0.0

apiVersion: v2 signals that this chart is designed for Helm3 support only.

version is the charts version, increment this under semver every time you update the chart

appVersion is the version of the app you are deploying, this is to be increased every time you increase the version of your app but does not impact the charts version

The rest of the fields are self explanatory but lets add some more information to describe our chart. We are going to set a Kubernetes minimum version, add some descriptive keywords and add our name as Maintainers. So go ahead and add the following to your Chart.yaml whilst subsituting your name and email in:

kubeVersion: ">= 1.21.0-0"
keywords:
  - nodejs
  - express
  - mern
maintainers:
  - name: Firstname Lastname
    email: FirstnameLastname@company.com

The final key thing we are going to add is a dependency, our application needs mongoDB to run so we are going to call an existing mongo chart to install mongo as we install our chart. Firstly we need to add to our Chart.yaml:

dependencies:
  - name: mongodb
    version: ">= 10.26.3"
    repository: https://charts.bitnami.com/bitnami

Then run the following command in the terminal to download the chart:

cd myapp
helm dependency update
cd ..

4. Template files

Inside the templates folder you will find that Helm has created some files for us already:

$ ls myapp/templates

NOTES.txt
_helpers.tpl
deployment.yaml
hpa.yaml
ingress.yaml
service.yaml
serviceaccount.yaml
tests

These files represent the kubernetes objects that our Helm Chart will deploy - you can set up the deployment and service, and you can also create a serviceaccount for your app to use. The NOTES.txt file is just the notes that will be displayed to the user when they deploy your Helm Chart, you can use this to provide further instruction to them to get your application working. _helpers.tpl is where template helpers that you can re-use throughout the chart go. However for this tutorial we are going to use our own files so you can go ahead and remove the generated files:

$ rm -rf chart/myapp/templates/*

Now let's move on to making our own template files!

4.1 Frontend Service

The first template we will create will deploy the service for our frontend. Let's create a file called chart/myapp/templates/frontend-service.yaml and paste in the following:

apiVersion: v1
kind: Service
metadata:
  annotations:
    prometheus.io/scrape: 'true'
  name: "frontend-service"
spec:
  ports:
  - name: http
    port: {{ .Values.frontend.service.servicePort }}
    nodePort: 30444
  type: {{ .Values.frontend.service.type }}
  selector:
    app: "frontend-selector"

This will create the service that serves our frontend and allows for it to be connected to the outside world. nodePort is the port we want our service to be accessible through so localhost:30444 will take us to our frontend once deployed

4.2 Frontend Deployment

The next template we create will be the deployment for the frontend. Create a file called chart/myapp/templates/frontend-deployment.yaml and paste in the following:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-deployment
  labels:
    chart: frontend
spec:
  selector:
    matchLabels:
      app: "frontend-selector"
  template:
    metadata:
      labels:
        app: "frontend-selector"
    spec:
      containers:
      - name: frontend
        image: "{{ .Values.frontend.image.repository }}/{{ .Values.frontend.image.tag }}"
        imagePullPolicy: {{ .Values.frontend.image.pullPolicy }}
        livenessProbe:
          httpGet:
            path: /
            port: {{ .Values.frontend.service.servicePort }}
          initialDelaySeconds: {{ .Values.frontend.livenessProbe.initialDelaySeconds}}
          periodSeconds: {{ .Values.frontend.livenessProbe.periodSeconds}}
        ports:
        - containerPort: {{ .Values.frontend.service.servicePort}}

What this creates is our frontend deployment, inside that is the pod that is running our frontend image plus its replicaset.

image is the image we want the container to deploy using variables pulled from our values.yaml file.

We also add the frontend-selector label to our pod which allows for our frontend service to connect with it.

4.3 Backend Service

Our third file will spawn the service for the backend part of our application. Create a file called chart/myapp/templates/backend-service.yaml and paste in the following:

apiVersion: v1
kind: Service
metadata:
  annotations:
    prometheus.io/scrape: 'true'
  name: "backend-service"
spec:
  ports:
  - name: http
    port: {{ .Values.backend.service.servicePort }}
    nodePort: 30555
  type: NodePort
  selector:
    app: "backend-selector"

This file does the same thing as frontend-service.yaml but for the backend and under a different port.

4.4 Backend Deployment

Our final template is the deployment for the backend. Create a file called chart/myapp/templates/backend-deployment.yaml and paste in the following:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend-deployment
  labels:
    chart: backend
spec:
  selector:
    matchLabels:
      app: "backend-selector"
  template:
    metadata:
      labels:
        app: "backend-selector"
    spec:
      containers:
      - name: backend
        image: "{{ .Values.backend.image.repository }}/{{ .Values.backend.image.tag }}"
        imagePullPolicy: {{ .Values.backend.image.pullPolicy }}
        livenessProbe:
          httpGet:
            path: /health
            port: {{ .Values.backend.service.servicePort }}
          initialDelaySeconds: {{ .Values.backend.livenessProbe.initialDelaySeconds}}
          periodSeconds: {{ .Values.backend.livenessProbe.periodSeconds}}
        ports:
        - containerPort: {{ .Values.backend.service.servicePort}}
        env:
          - name: PORT
            value : "{{ .Values.backend.service.servicePort }}"
          - name: APPLICATION_NAME
            value: "{{ .Release.Name }}"
          - name: MONGO_URL
            value: {{ .Values.backend.services.mongo.url }}
          - name: MONGO_DB_NAME
            value: {{ .Values.backend.services.mongo.name }}

Similar to our frontend deployment file, this file creates our backend deployment, with a key difference being the mongoDB information that is passed through to allow for communication with the mongo instance.

5. Values file

For the chart/myapp/values.yaml file we are going to split it into 3 sections, have a read of each section and then add them all to your values.yaml file

# Frontend
frontend:
  replicaCount: 1
  revisionHistoryLimit: 1
  image:
    repository: frontend 
    tag: frontend:v1.0.0
    pullPolicy: IfNotPresent
    resources:
      requests:
        cpu: 200m
        memory: 300Mi
  livenessProbe:
    initialDelaySeconds: 30
    periodSeconds: 10
  service:
    name: frontend
    type: NodePort
    servicePort: 80 # the port where nginx serves its traffic

These values are for the frontend section. Here we pass through the image name, tag and the values for the frontend service.

# backend
backend:
  replicaCount: 1
  revisionHistoryLimit: 1
  image:
    repository: backend
    tag: backend:v1.0.0
    pullPolicy: IfNotPresent
    resources:
      requests:
        cpu: 200m
        memory: 300Mi
  livenessProbe:
    initialDelaySeconds: 30
    periodSeconds: 10
  service:
    name: backend
    type: NodePort
    servicePort: 30555
  services:
    mongo:
      url: myapp-mongodb
      name: todos
      env: production

These values are for the backend section. Here we pass through the image, tag, service information, and some mongoDB information to locate the instance.

# mongo
mongodb:
  auth:
    enabled: false
  replicaSet:
    enabled: true
    replicas:
      secondary: 3
  service:
    type: LoadBalancer

Finally these values are passed through to the mongoDB chart we downloaded earlier.

6. Deploying your Application to Kubernetes

Now that you have built a Helm chart for your application, the process for deploying your application has been greatly simplified.

Deploy your Express.js application into Kubernetes using the following steps:

  1. Create a local image registry
    You will need to push the image into the kubernetes container registry so that minikube can access it.

First we enable the image registry addon for minikube:

minikube addons enable registry

console output:

$ minikube addons enable registry
╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮
│                                                                                                      │
│    Registry addon with podman driver uses port 42795 please use that instead of default port 5000    │
│                                                                                                      │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯
📘  For more information see: https://minikube.sigs.k8s.io/docs/drivers/podman
    ▪ Using image registry:2.7.1
    ▪ Using image gcr.io/google_containers/kube-registry-proxy:0.4
🔎  Verifying registry addon...
🌟  The 'registry' addon is enabled

Note: As the message indicates, be sure you use the correct port instead of 5000. If you don't see the warning then just use 5000 for the port in the instructions below.

On Linux and macOS export a variable with the registry with:

export MINIKUBE_REGISTRY=$(minikube ip):<port>

replacing with the port listed when you ran minikube addons enable registry.

On Windows export a variable with the registry with by first running

minikube ip

to get the ip of the registry and then exporting

set MINIKUBE_REGISTRY=<ip from minikube ip command above>:<port>

We can now build the image directly using minikube image build: On Linux and macOS:

cd backend
minikube image build  -t $MINIKUBE_REGISTRY/backend:v1.0.0 --file Dockerfile  .
cd ../frontend
minikube image build  -t $MINIKUBE_REGISTRY/frontend:v1.0.0 --file Dockerfile  .

On Windows:

cd ../backend
minikube image build -t %MINIKUBE_REGISTRY%/backend:v1.0.0 --file Dockerfile  .
cd ../frontend
minikube image build -t %MINIKUBE_REGISTRY%/frontend:v1.0.0 --file Dockerfile  .

And we can list the images in minikube:

minikube image ls

Console output

<minikube-ip>:<minikube-registry-port>/frontend:v1.0.0
<minikube-ip>:<minikube-registry-port>/backend:v1.0.0

Next, we push the image into the registry using:

On Linux and macOS:

minikube image push $MINIKUBE_REGISTRY/backend
minikube image push $MINIKUBE_REGISTRY/frontend

On Windows:

minikube image push %MINIKUBE_REGISTRY%/backend
minikube image push %MINIKUBE_REGISTRY%/frontend

6. Deploy your Helm Chart

Once the images are built you can now deploy your helm chart

Validate you are in the mern-workshop directory

On Linux and macOS:

$ helm install myapp  --set backend.image.repository=$MINIKUBE_REGISTRY --set frontend.image.repository=$MINIKUBE_REGISTRY chart/myapp

On Windows:

$ helm install myapp  --set backend.image.repository=%MINIKUBE_REGISTRY% --set frontend.image.repository=%MINIKUBE_REGISTRY% chart/myapp

Check your pods are running by running:

$ kubectl get pods

You should get a similar output to:

NAME                                   READY   STATUS    RESTARTS   AGE
backend-deployment-5d6bb8c5f8-xm4bv    1/1     Running   0          67s
frontend-deployment-6c7779ff9d-xjdrv   1/1     Running   0          67s
myapp-mongodb-64df664c5b-st8fb         1/1     Running   0          66s

Wait for some time until mongo status is running

Also, by viewing the logs of the backend service with below command

kubectl logs backend-deployment-xxxxxxxx-xxxxx -f

you should get below message

Connection is established with mongodb, details: mongodb://myapp-mongodb:27017

You can then run the following two commands in order to forward 30555 port to your backend pod:

On Linux and macOS:

export BACKEND_POD_NAME=$(minikube kubectl -- get pods --namespace default -o jsonpath="{.items[0].metadata.name}")
minikube kubectl -- --namespace default port-forward $BACKEND_POD_NAME 30555:30555

On Windows Command Prompt:

for /f "tokens=*" %i in ('"minikube kubectl -- get pods --namespace default -o jsonpath={.items[0].metadata.name}"') do set BACKEND_POD_NAME=%i
minikube kubectl -- --namespace default port-forward %BACKEND_POD_NAME% 30555:30555

And by running the following two commands you are able to port forward your app on port 30444 on localhost:

On Linux and macOS:

export FRONTEND_POD_NAME=$(minikube kubectl -- get pods --namespace default -o jsonpath="{.items[1].metadata.name}")
minikube kubectl -- --namespace default port-forward $FRONTEND_POD_NAME 30444:80

On Windows Command Prompt:

for /f "tokens=*" %i in ('"minikube kubectl -- get pods --namespace default -o jsonpath={.items[1].metadata.name}"') do set FRONTEND_POD_NAME=%i
minikube kubectl -- --namespace default port-forward %FRONTEND_POD_NAME% 30444:80

Now you can access your application at http://localhost:30444/

Congratulations! 🎉

You have now created a Helm Chart which deploys a frontend and a backend whilst also calling and installing a dependency chart from the internet.

Interacting with the application

By visiting http://localhost:30444/ you should be able to see the UI as shown on the image below

Application ui

Lets add an item by filling the textbox and clicking on the Add new toDo button

Adding item

By clicking the Add new ToDo button, the item should be added on the list as shown on image below.

You should also be able to see below message on the backend service

Creating Todo for NodeConfEU Clean the bicycle

by viewing the logs with below command

kubectl logs backend-deployment-xxxxxxxx-xxxxx -f

Added item

Click the x button next to the todo item and it should be removed

Uninstalling the App

Once you are finished you can uninstall the chart by running:

$ helm uninstall myapp