Skip to content

Latest commit

 

History

History
832 lines (547 loc) · 32.3 KB

README.md

File metadata and controls

832 lines (547 loc) · 32.3 KB

Node.js in the Cloud

Welcome 👋 to the Node.js in the Cloud workshop!

The workshop provides an introduction to cloud-native development with Node.js by walking you through how to extend an Express.js-based application to leverage cloud capabilities.

Target Audience: This workshop is aimed at developers who are familiar with Node.js but want to gain a base understanding of some of the key concepts of cloud-native development with Node.js.

Extending an application to leverage cloud capabilities

Building a Cloud-Ready Express.js Application

This will show you how to take a Node.js application and make it "cloud-ready" by adding support for Cloud Native Computing Foundation (CNCF) technologies.

In this self-paced tutorial you will:

  • Create an Express.js application
  • Add logging, metrics, and health checks
  • Build your application with Podman
  • Package your application with Helm
  • Deploy your application to Kubernetes
  • Monitor your application using Prometheus

The application you'll use is a simple Express.js application. You'll learn about Health Checks, Metrics, Podman, Kubernetes, Prometheus, and Grafana. In the end, you'll have a fully functioning application running as a cluster in Kubernetes, with production monitoring.

The content of this tutorial is based on recommendations from the NodeShift Reference Architecture for Node.js.

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, macOS 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 a 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 start 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.g. 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 the below command in PowerShell (with administration privileges).

    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. Download 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 minikube-darwin-* /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 the following 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 your operating system.

  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 macOS, a pop-up indicating that the application could not be verified may appear. 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

1. Create your Express.js Application

The following steps cover creating a base Express.js application. Express.js is a popular web server framework for Node.js.

  1. Create a directory to host your project:

    mkdir nodeserver
    cd nodeserver
  2. Initialize your project with npm and install the Express.js module:

    npm init --yes
    npm install express
  3. We'll also install the Helmet module. Helmet is a middleware that we can use to set some sensible default headers on our HTTP requests.

    npm install helmet
  4. It is important to add effective logging to your Node.js applications to facilitate observability, that is to help you understand what is happening in your application. The NodeShift Reference Architecture for Node.js recommends using Pino, a JSON-based logger.

    Install Pino:

    npm install pino
  5. Now, let's start creating our server. Create a file named server.js

  6. Add the following to server.js to produce an Express.js server that responds on the / route with 'Hello, World!'.

    const express = require('express');
    const helmet = require('helmet');
    const pino = require('pino')();
    const PORT = process.env.PORT || 3000;
    
    const app = express();
    
    app.use(helmet());
    
    app.get('/', (req, res) => {
       res.send('Hello, World!');
    });
    
    app.listen(PORT, () => {
       pino.info(`Server listening on port ${PORT}`);
    });
  7. Start your application:

    npm start
    
    > nodeserver@1.0.0 start /private/tmp/nodeserver
    > node server.js
    
    {"level":30,"time":1622040801251,"pid":21934,"hostname":"bgriggs-mac","msg":"Server listening on port 3000"}

Navigate to http://localhost:3000 and you should see the server respond with 'Hello, World!'. You can stop your server by entering CTRL + C in your terminal window.

2. Add Health Checks to your Application

Kubernetes, and a number of other cloud deployment technologies, provide "Health Checking" as a system that allows the cloud deployment technology to monitor the deployed application and to take action should the application fail or report itself as "unhealthy".

The simplest form of Health Check is process-level health checking, where Kubernetes checks to see if the application process still exists and restarts the container (and therefore the application process) if it is not. This provides a basic restart capability but does not handle scenarios where the application exists but is unresponsive, or where it would be desirable to restart the application for other reasons.a

The next level of Health Check is HTTP-based, where the application exposes a "livenessProbe" URL endpoint that Kubernetes can make requests to determine whether the application is running and responsive. Additionally, the request can be used to drive self-checking capabilities in the application.

Add a Health Check endpoint to your Express.js application using the following steps:

  1. Register a Liveness endpoint in server.js:

    app.get('/live', (req, res) => res.status(200).json({ status: 'ok' }));

Add this line after the app.use(helmet()); line. This adds a /live endpoint to your application. As no liveness checks are registered, it will return a status code of 200 OK and a JSON payload of {"status":"ok"}.

  1. Restart your application:

    npm start
  2. Check that your livenessProbe Health Check endpoint is running. Visit the live endpoint http://localhost:3000/live.

For information more information on health/liveness checks, refer to the following:

3. Add Metrics to your Application

For any application deployed to a cloud, it is important that the application is "observable": that you have sufficient information about an application and its dependencies such that it is possible to discover, understand and diagnose the state of the application. One important aspect of application observability is metrics-based monitoring data for the application.

One of the CNCF recommended metrics systems is Prometheus, which works by collecting metrics data by making requests of a URL endpoint provided by the application. Prometheus is widely supported inside Kubernetes, meaning that Prometheus also collects data from Kubernetes itself, and application data provided to Prometheus can also be used to automatically scale your application.

The prom-client package provides a library that auto-instruments your application to collect metrics. It is then possible to expose the metrics on an endpoint for consumption by Prometheus.

Add a /metrics Prometheus endpoint to your Express.js application using the following steps:

  1. Add the prom-client dependency to your project:

    npm install prom-client
  2. Require prom-client in server.js and choose to configure default metrics:

    // Prometheus client setup
    const Prometheus = require('prom-client');
    Prometheus.collectDefaultMetrics();

    It is recommended to add these lines around Line 3 below the pino logger import.

  3. Register a /metrics route to serve the data on:

    app.get('/metrics', async (req, res, next) => {
       try {
          res.set('Content-Type', Prometheus.register.contentType);
          const metrics = await Prometheus.register.metrics();
          res.end(metrics);
       } catch {
          res.end('');
       }
    });

Register the app.get('/metrics')... route after your /live route handler. This adds a /metrics endpoint to your application. This automatically starts collecting data from your application and exposes it in a format that Prometheus understands.

Check that your metrics endpoint is running:

  1. Start your application:

    npm start
  2. Visit the metrics endpoint http://localhost:3000/metrics.

For information on how to configure the prom-client library see the prom-client documentation.

You can install a local Prometheus server to graph and visualize the data, and additionally to set up alerts. For this workshop, you'll use Prometheus once you've deployed your application to Kubernetes.

4. Building your Application with Podman

Before you can deploy your application to Kubernetes, you first need to build your application into a container and produce a container image. This packages your application along with all of its dependencies in a ready-to-run format.

NodeShift provides a "Docker" project that provides several Dockerfile templates that can be used to build your container and produce your image. The same file format can be used with Podman.

For this workshop, you'll use the Dockerfile-run template, which builds a production-ready Docker image for your application.

Build a production Docker image for your Express.js application using the following steps:

On Mac/Linux

  1. Copy the Dockerfile-run template into the root of your project:

    curl -fsSL -o Dockerfile-run https://raw.githubusercontent.com/NodeShift/docker/master/Dockerfile-run
  2. Also, copy the .dockerignore file into the root of your project:

    curl -fsSL -o .dockerignore https://raw.githubusercontent.com/NodeShift/docker/master/.dockerignore
  3. Build the Docker run image for your application:

    podman build --tag nodeserver:1.0.0 --file Dockerfile-run .

You have now built a container image for your application called nodeserver with a version of 1.0.0. Use the following to run your application inside the container:

podman run --interactive --publish 3000:3000 --tty nodeserver:1.0.0

This runs your container image in a Podman container, mapping port 3000 from the container to port 3000 on your laptop so that you can access the application.

On Windows

We will use Podman Desktop to build and run our image.

  1. Create a file called Dockerfile-run and paste the content from the below URL: https://raw.githubusercontent.com/NodeShift/docker/master/Dockerfile-run

  2. Create another file called .dockerignore and paste the content from the below URL: https://raw.githubusercontent.com/NodeShift/docker/master/.dockerignore

  3. Run Podman Desktop

  4. On Podman Desktop Click on Images (tab - left sidebar) -> Build Image

    Available images (click to expand)

    Available images

  5. Set the containerfile path which in our case points to the Dockerfile-run and the image name nodeserver -> Build

    Start building image (click to expand)

    Build Image

    Image build process (click to expand)

    build process

  6. After the build process, on the Images tab the nodeserver Image should be visible.

    Final build image (click to expand)

    nodeserver Image

  7. Hover over the container image -> click on the play button to run the image -> set ports 3000 and 8080 -> start container

    Run image on port 3000 (click to expand)

    nodeserver container

Visit your application's endpoints to check that it is running successfully:

5. Packaging your Application with Helm

To deploy your container image to Kubernetes you need to supply Kubernetes with configuration on how you need your application to be run, including which container image to use, how many replicas (instances) to deploy, and how much memory and CPU to provide to each.

Helm charts provide an easy way to package your application with this information.

NodeShift provides a "Helm" project that provides a template Helm chart template that can be used to package your application for Kubernetes.

Add a Helm chart for your Express.js application using the following steps:

  1. Download the template Helm chart:

    On Linux and macOS:

    curl -fsSL -o main.tar.gz https://github.com/NodeShift/helm/archive/main.tar.gz

    On Windows:

    Download: https://github.com/NodeShift/helm/archive/main.zip

  2. Untar the downloaded template chart:

    On Linux and macOS:

    tar xfz main.tar.gz

    On Windows:

    • Right Click on helm-main.zip -> Extract All... -> Select the nodeserver directory -> Extract
  3. Move the chart to your projects root directory:

    On Linux and macOS:

    mv helm-main/chart chart
    rm -rf helm-main main.tar.gz

    On Windows Command Prompt:

    move helm-main\chart chart
    rmdir /s /q helm-main
    

The provided Helm chart provides a number of configuration files, with the configurable values extracted into chart/nodeserver/values.yaml. In this file you provide the name of the Docker image to use, the number of replicas (instances) to deploy, etc.

Go ahead and modify the chart/nodeserver/values.yaml file to use your image, and to deploy 3 replicas:

  1. Open the chart/nodeserver/values.yaml file
  2. Ensure that the pullPolicy is set to IfNotPresent
  3. Change the replicaCount value to 3 (Line 3)

The repository field gives the name of the Docker image to use. The pullPolicy change tells Kubernetes to use a local container image if there is one available rather than always pulling the container image from a remote repository. Finally, the replicaCount states how many instances to deploy.

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 IP by first running minikube ip to get the IP of the registry and then exporting the variable:

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:

minikube image build -t $MINIKUBE_REGISTRY/nodeserver:1.0.0 --file Dockerfile-run .

On Windows:

minikube image build -t %MINIKUBE_REGISTRY%/nodeserver:1.0.0 --file Dockerfile-run .

And we can list the images in minikube:

minikube image ls

Console output

192.168.58.2:42631/nodeserver:1.0.0

Next, we push the image into the registry using:

On Linux and macOS:

minikube image push $MINIKUBE_REGISTRY/nodeserver

On Windows:

minikube image push %MINIKUBE_REGISTRY%/nodeserver

Finally, we can install the Helm chart using:

On Linux and macOS:

helm install nodeserver \
  --set image.repository=$MINIKUBE_REGISTRY/nodeserver  chart/nodeserver

On Windows:

helm install nodeserver --set image.repository=%MINIKUBE_REGISTRY%/nodeserver  chart/nodeserver

_Note(Mac): If you cannot open the Helm CLI due Apple malicious software checks, control-click the Helm application icon in the Finder and select Open. (Instructions Reference)

  1. Check that all the "pods" associated with your application are running:

    minikube kubectl -- get pods

In earlier steps, we set the replicaCount to 3, so you should expect to see three nodeserver-deployment-* pods running.

Now everything is up and running in Kubernetes. It is not possible to navigate to localhost:3000 as usual because your cluster isn't part of the localhost network, and because there are several instances to choose from.

Kubernetes has a concept of a 'Service', which is an abstract way to expose an application running on a set of Pods as a network service. To access our service, we need to forward the port of the nodeserver-service to our local device:

  1. You can forward the nodeserver-service to your device by:
minikube kubectl -- port-forward service/nodeserver-service 3000

You can now access the application endpoints from your browser.

7. Monitoring your Application with Prometheus

Installing Prometheus into Kubernetes can be done using its provided Helm chart. This step needs to be done in a new Terminal window as you'll need to keep the application port-forwarded to localhost:3000.

minikube kubectl -- create namespace prometheus
minikube kubectl -- config set-context --current --namespace=prometheus
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install prometheus prometheus-community/prometheus --namespace=prometheus

You can then run the following two commands to be able to connect to Prometheus from your browser:

On Linux and macOS:

export POD_NAME=$(minikube kubectl -- get pods --namespace prometheus -l "app=prometheus,component=server" -o jsonpath="{.items[0].metadata.name}")
minikube kubectl -- --namespace prometheus port-forward $POD_NAME 9090

On Windows Command Prompt:

for /f "tokens=*" %i in ('"minikube kubectl -- get pods --namespace prometheus -l app=prometheus,component=server -o jsonpath={.items[0].metadata.name}"') do set POD_NAME=%i
minikube kubectl -- --namespace prometheus port-forward %POD_NAME% 9090

This may fail with a warning about the status being "Pending" until Prometheus has started, retry once the status is "Running" for all pods:

minikube kubectl -- -n prometheus get pods --watch

You can now connect to Prometheus at http://localhost:9090.

This should show the following screen:

Prometheus dashboard

Prometheus will be automatically collecting data from your Express.js application, allowing you to create graphs of your data.

To build your first graph, type nodejs_heap_size_used_bytes into the Expression box and click on the Graph tab.

This will show a graph mapping the nodejs_heap_size_used_bytes across each of the three pods. You'll probably want to reduce the graph interval to 1m (1 minute) so that you can start seeing the changes in the graph. You can also try sending some requests to your server (http://localhost:3000) in another browser window to add load to the server, which you should then see reflected in the graph.

Prometheus Graph

Whilst Prometheus provides the ability to build simple graphs and alerts, Grafana is commonly used to build more sophisticated dashboards.

Installing Grafana into Kubernetes

Installing Grafana into Kubernetes can be done using its provided Helm chart.

In a third Terminal window:

minikube kubectl -- create namespace grafana
minikube kubectl -- config set-context --current --namespace=grafana
helm repo add grafana https://grafana.github.io/helm-charts
helm install grafana grafana/grafana --set adminPassword=PASSWORD --namespace=grafana

You can then run the following two commands to be able to connect to Grafana from your browser:

On Linux and macOS:

export POD_NAME=$(minikube kubectl -- get pods --namespace grafana -o jsonpath="{.items[0].metadata.name}")
minikube kubectl -- --namespace grafana port-forward $POD_NAME 3001:3000

On Windows Command Prompt:

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

You can now connect to Grafana at the following address, using admin and PASSWORD to login:

This should show the following screen:

Grafana home screen

To connect Grafana to the Prometheus service, go to http://localhost:3001/datasources and click Add Data Source. Select Prometheus.

This opens a panel that should be filled out with the following entries:

  • Name: Prometheus
  • URL: http://prometheus-server.prometheus.svc.cluster.local

Prometheus dashboard

Now click on Save & Test to check the connection and save the Data Source configuration.

Grafana now has access to the data from Prometheus.

Installing a Kubernetes Dashboard into Grafana

The Grafana community provides a large number of pre-created dashboards which are available for download, including some which are designed to display Kubernetes data.

To install one of those dashboards, expand the Dashboards menu on the left sidebar and click on the + Import.

In the provided panel, enter 1621 into the Import via Grafana.com field to import dashboard number 1621, press Load and the Import.

Note: If 1621 is not recognized, it may be necessary to download the JSON for 1621 (select Download JSON), and use Upload JSON in the Grafana UI.

This then loads the information on dashboard 1621 from Grafana.com.

Grafana import dashboard screen

Change the Prometheus field so that it is set to Prometheus and click Import.

This will then open the dashboard, which will automatically start populating with data about your Kubernetes cluster.

Grafana Kubernetes cluster monitoring dashboard

Adding Custom Graphs

To extend the dashboard with your own graphs, click the Add panel icon on the top toolbar

Grafana dashboard for nodejs_heap_size_used_bytes metric

and select Add new Panel

Note: On some Grafana versions, after you click Add panel in the toolbar, it is necessary to select Choose Visualization before selecting Graph.

This opens an editor panel where you can select data that you'd like to graph.

In the bottom half of the page, click on the Query tab, and validate that Data Source is set to Prometheus.

Type nodejs_heap_size_used_bytes into the Metrics box, click on the Run Queries button and a graph of your application's process heap size used from Node.js will be shown. You may need to click the Query icon on the left to access the Metrics box. Grafana dashboard for nodejs_heap_size_used_bytes metric

You now have integrated monitoring for both your Kubernetes cluster and your deployed Express.js application.

Congratulations! 🎉

You now have an Express.js application deployed with scaling using Docker and Kubernetes, with automatic restart, and full metrics-based monitoring enabled!

Once you are finished, you should exit all the running terminal processes with CTRL + C, and then use the following commands to delete the helm releases and remove your Kubernetes pods:

helm delete nodeserver -n default
helm delete prometheus -n prometheus
helm delete grafana -n grafana

To change your Kubernetes context back to default use:

minikube kubectl -- config set-context --current --namespace=default