<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>helm &#8211; Blog of Kliment Andreev &#8211; A place so I won&#039;t forget things</title>
	<atom:link href="https://blog.andreev.it/tag/helm/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.andreev.it</link>
	<description></description>
	<lastBuildDate>Sat, 21 Oct 2023 23:12:36 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	
	<item>
		<title>Kubernetes: Monitoring Node.js application with Prometheus and Grafana, Helm charts in AWS EKS</title>
		<link>https://blog.andreev.it/2023/01/kubernetes-monitoring-node-js-application-with-prometheus-and-grafana-helm-charts/</link>
					<comments>https://blog.andreev.it/2023/01/kubernetes-monitoring-node-js-application-with-prometheus-and-grafana-helm-charts/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Tue, 24 Jan 2023 16:01:05 +0000</pubDate>
				<category><![CDATA[Containers]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[EKS]]></category>
		<category><![CDATA[grafana]]></category>
		<category><![CDATA[helm]]></category>
		<category><![CDATA[nodejs]]></category>
		<category><![CDATA[prom-client]]></category>
		<category><![CDATA[prometheus]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=9312</guid>

					<description><![CDATA[Recently, we were migrating some Node.js applications to a different AWS account and we&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>Recently, we were migrating some Node.js applications to a different AWS account and we had a need to monitor the application with Prometheus and Grafana. In this post, I&#8217;ll explain how to install both Prometheus and Grafana using Helm charts, then we&#8217;ll create a simple Express.js web app, create a Helm chart for it and deploy it on an EKS cluster in AWS. I am using an EKS cluster, but you can use any Kubernetes cluster that you can manage it with kubectl. You might have to make some changes though in how you access the Load Balancer for Grafana.<br />
<strong>Requirements</strong>:</p>
<ul>
&#8211; eksctl<br />
&#8211; kubectl<br />
&#8211; helm</ul>
<h1>Create EKS cluster</h1>
<p>This is optional, if you already have a running Kubernetes cluster, you can skip this step.</p>
<pre class="brush: bash; title: ; notranslate">
eksctl create cluster --name eksTest \
--region us-east-2 \
--instance-types t3.medium \
--nodes 2 \
--managed \
--version 1.22
</pre>
<p>This command will create a 2 node cluster, with t3.medium instances in Ohio region using Amazon Linux 2 image. It will take about 15 minutes.<br />
Verify that everything looks OK.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get nodes
NAME                                           STATUS   ROLES    AGE     VERSION
ip-192-168-10-4.us-east-2.compute.internal     Ready    &lt;none&gt;   8m54s   v1.22.15-eks-fb459a0
ip-192-168-74-160.us-east-2.compute.internal   Ready    &lt;none&gt;   8m51s   v1.22.15-eks-fb459a0
</pre>
<h1>Install Prometheus and Grafana</h1>
<p>Add the Helm repos for Prometheus, Grafana and update any changes.</p>
<pre class="brush: bash; title: ; notranslate">
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
</pre>
<p>We&#8217;ll create a separate namespace for Prometheus and install the chart there.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create namespace prometheus
helm install prometheus prometheus-community/prometheus \
    --namespace prometheus \
    --set alertmanager.persistentVolume.storageClass=&quot;gp2&quot; \
    --set server.persistentVolume.storageClass=&quot;gp2&quot;
</pre>
<p>Create this file. It tells Grafana what to use as a source (url). It&#8217;s the Prometheus endpoint service that we just installed.</p>
<pre class="brush: plain; title: ; notranslate">
cat &lt;&lt; EOF &gt; grafana.yaml
datasources:
  datasources.yaml:
    apiVersion: 1
    datasources:
    - name: Prometheus
      type: prometheus
      url: http://prometheus-server.prometheus.svc.cluster.local
      access: proxy
      isDefault: true
EOF
</pre>
<p>Now, let&#8217;s create a namespace for Grafana and then install Grafana in that namespace. Change the admin password. In my case it&#8217;s Password1$.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create namespace grafana
helm install grafana grafana/grafana \
    --namespace grafana \
    --set persistence.storageClassName=&quot;gp2&quot; \
    --set persistence.enabled=true \
    --set adminPassword=&#039;Password1$&#039; \
    --values grafana.yaml \
    --set service.type=LoadBalancer
</pre>
<p>You&#8217;ll get something like this on the screen when the install ends.</p>
<pre class="brush: bash; title: ; notranslate">
export SERVICE_IP=$(kubectl get svc --namespace grafana grafana -o jsonpath=&#039;{.status.loadBalancer.ingress&#x5B;0].ip}&#039;)
</pre>
<p>That&#8217;s your load balancer IP so you can access Grafana. If you use EKS, your IP is different.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl get services -n grafana
NAME      TYPE           CLUSTER-IP     EXTERNAL-IP                                                               PORT(S)        AGE
grafana   LoadBalancer   10.100.20.53   a074996f790b74793a74bec584c04460-2034633908.us-east-2.elb.amazonaws.com   80:31916/TCP   3m21s
</pre>
<p>The IP is the gibberish URL that ends with amazonaws.com. If you go to that URL, you can log as <strong>admin </strong>and your password.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/01/P161-01.png"><img fetchpriority="high" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/01/P161-01-1024x929.png" alt="" width="1024" height="929" class="aligncenter size-large wp-image-9316" srcset="https://blog.andreev.it/wp-content/uploads/2023/01/P161-01-1024x929.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-01-300x272.png 300w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-01-768x696.png 768w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-01-585x531.png 585w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-01.png 1149w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Go to <strong>Dashboards</strong>, click on the <strong>New </strong>button on the right and click <strong>Import</strong>.<br />
Add the following dashboard IDs (one by one then click <strong>Load</strong>) and choose Prometheus as a source. The IDs are 3119 (Kubernetes cluster), 6417 (pods) and 11159 (Node.js). For the first dashboard, you&#8217;ll have some metrics. The Node.js dashboard is empty and our goal is to get the metrics from the application.</p>
<h1>Node.js application</h1>
<p>Create an empty folder and add this file.</p>
<pre class="brush: bash; title: ; notranslate">
mkdir mynodeapp
cd mynodeapp
cat &lt;&lt; EOF &gt; package.json
{
  &quot;name&quot;: &quot;mynodeapp&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;Node.js on Docker&quot;,
  &quot;author&quot;: &quot;Kliment Andreev &lt;kliment@andreev.it&gt;&quot;,
  &quot;main&quot;: &quot;server.js&quot;,
  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;node server.js&quot;
  },
  &quot;dependencies&quot;: {
    &quot;express&quot;: &quot;^4.16.1&quot;,
    &quot;prom-client&quot;: &quot;^14.1.1&quot;
  }
}
EOF
</pre>
<p>I use Node v18.12. Install the dependencies.</p>
<pre class="brush: bash; title: ; notranslate">
npm install
</pre>
<p>This is the simple web application. It listens on port 3000.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt; &quot;EOF&quot; &gt; server.js
&#039;use strict&#039;
const express = require(&#039;express&#039;)
const prom = require(&#039;prom-client&#039;)

const collectDefaultMetrics = prom.collectDefaultMetrics();

const app = express()
const port = process.env.PORT || 3000

app.get(&#039;/&#039;, (req, res, next) =&gt; {
  res.send(&#039;Hello World!&#039;);
});

app.get(&#039;/metrics&#039;, async (req, res) =&gt; {
  try {
	  res.set(&#039;Content-Type&#039;, prom.register.contentType);
	  res.end(await prom.register.metrics());
  } 
  catch (ex) {
	  res.status(500).end(ex);
  }
});

const server = app.listen(port, () =&gt; {
  console.log(`mynodeapp listening on port ${port}!`)
})
EOF
</pre>
<p>The app is a super simple web app that prints Hello World! The important part is the <strong>/metrics</strong> router. That&#8217;s how you get the metrics from the app. When we created the package.json file, we specify the <strong>prom-client</strong> as dependency. That&#8217;s the part that collects the metrics. Go to this <a href="https://github.com/siimon/prom-client" rel="noopener" target="_blank">link </a>for more info.<br />
Start the app with <strong>node server.js</strong>.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
node server.js
mynodeapp listening on port 3000!
</pre>
<p>Go to the localhost or the server IP where this app is running and you&#8217;ll see <em>Hello World!</em> printed out.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/01/P161-02.png"><img decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/01/P161-02.png" alt="" width="634" height="177" class="aligncenter size-full wp-image-9321" srcset="https://blog.andreev.it/wp-content/uploads/2023/01/P161-02.png 634w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-02-300x84.png 300w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-02-585x163.png 585w" sizes="(max-width: 634px) 100vw, 634px" /></a><br />
And if you go to the /metrics URL, you&#8217;ll see this text. That&#8217;s what Prometheus expects.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/01/P161-03.png"><img decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/01/P161-03.png" alt="" width="874" height="325" class="aligncenter size-full wp-image-9322" srcset="https://blog.andreev.it/wp-content/uploads/2023/01/P161-03.png 874w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-03-300x112.png 300w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-03-768x286.png 768w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-03-585x218.png 585w" sizes="(max-width: 874px) 100vw, 874px" /></a><br />
Now that we know the app is working, let&#8217;s create a Docker image.</p>
<h1>Docker image and container</h1>
<p>Create this Dockerfile.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt; EOF &gt; Dockerfile
FROM node:18-alpine

# Create app directory
WORKDIR /app

# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./

RUN npm install
# If you are building your code for production
# RUN npm install --only=production

# Bundle app source
COPY server.js /app

EXPOSE 3000
CMD &#x5B; &quot;npm&quot;, &quot;start&quot;, &quot;server.js&quot; ]
EOF
</pre>
<p>Create a Docker image based on the Dockerfile above. Replace the username with your Docker Hub username.</p>
<pre class="brush: bash; title: ; notranslate">
docker build -t &lt;username&gt;/mynodeapp:latest .
</pre>
<p>Run the container from the image.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
docker run -p 3000:3000 &lt;username&gt;/mynodeapp:latest

&gt; mynodeapp@1.0.0 start
&gt; node server.js server.js

mynodeapp listening on port 3000!
</pre>
<p>Use the same test as before by going to the localhost or IP URL on port 3000.<br />
If everything is OK, publish the image on Dockerhub.</p>
<pre class="brush: bash; title: ; notranslate">
docker login
docker push &lt;username&gt;/mynodeapp
</pre>
<h1>Helm chart</h1>
<p>Now that we have the Docker image, we&#8217;ll create a Helm chart so we can deploy the web app to the Kubernetes cluster.<br />
Create the necessary structure.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
helm create mynodeapp
Creating mynodeapp
</pre>
<p>Go to the sub-directory <strong>mynodeapp </strong>and edit the <strong>values.yaml</strong> file first. Look for the image key first and replace it so it looks like this. Make sure you replace the <strong><username></strong> with your Docker Hub username.</p>
<pre class="brush: bash; title: ; notranslate">
image:
  repository: &lt;username&gt;/mynodeapp
  pullPolicy: IfNotPresent
  # Overrides the image tag whose default is the chart appVersion.
  tag: &quot;latest&quot;
</pre>
<p>Then look for <strong>podAnnotations: {}</strong> line and replace it so it looks like this.</p>
<pre class="brush: bash; title: ; notranslate">
podAnnotations:
  prometheus.io/scrape: &quot;true&quot;
  prometheus.io/path: &quot;/metrics&quot;
  prometheus.io/port: &quot;3000&quot;
</pre>
<p>Look for the <strong>service </strong>parameter and change it so it looks like this.</p>
<pre class="brush: bash; title: ; notranslate">
service:
  type: LoadBalancer
  port: 80
  targetPort: 3000
  name: mynodeapp-service
</pre>
<p>Look for the resources key and uncomment the defaults.</p>
<pre class="brush: bash; title: ; notranslate">
resources: {}
  # We usually recommend not to specify default resources and to leave this as a conscious
  # choice for the user. This also increases chances charts run on environments with little
  # resources, such as Minikube. If you do want to specify resources, uncomment the following
  # lines, adjust them as necessary, and remove the curly braces after &#039;resources:&#039;.
limits:
  cpu: 100m
  memory: 128Mi
requests:
  cpu: 100m
  memory: 128Mi
</pre>
<p>Edit the <strong>templates/deployment.yaml</strong> file and find these lines.</p>
<pre class="brush: bash; title: ; notranslate">
ports:
  - name: http
    containerPort: {{ .Values.service.port }}
</pre>
<p>Replace the containerPort to look like this.</p>
<pre class="brush: bash; title: ; notranslate">
containerPort: 3000
</pre>
<p>Go back to the root of the <strong>mynodeapp </strong>folder and check the chart.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
helm install mynodeapp --generate-name
NAME: mynodeapp-1674575241
LAST DEPLOYED: Tue Jan 24 10:47:22 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
           You can watch the status of by running &#039;kubectl get --namespace default svc -w mynodeapp-1674575241&#039;
  export SERVICE_IP=$(kubectl get svc --namespace default mynodeapp-1674575241 --template &quot;{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}&quot;)
  echo http://$SERVICE_IP:80
</pre>
<p>Execute the <strong>export SERVICE_IP</strong> command above and echo the IP. That&#8217;s your URL. If you go to that URL you&#8217;ll see the <em>Hello World!</em> greetings.<br />
What we care is the pod. Get the pod running. It&#8217;s in the default namespace.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get pods
NAME                                    READY   STATUS    RESTARTS   AGE
mynodeapp-1674575241-5ff9d7469d-pddcz   1/1     Running   0          3m
</pre>
<p>Get the annotations. Replace with your pod name.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get pod mynodeapp-1674575241-5ff9d7469d-pddcz -o jsonpath=&#039;{.metadata.annotations}&#039;
{&quot;kubernetes.io/psp&quot;:&quot;eks.privileged&quot;,&quot;prometheus.io/path&quot;:&quot;/metrics&quot;,&quot;prometheus.io/port&quot;:&quot;3000&quot;,&quot;prometheus.io/scrape&quot;:&quot;true&quot;}
</pre>
<p>You can see the prometheus annotations.<br />
And if you go to Grafana and open up the NodeJS dash you&#8217;ll see the metrics.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/01/P161-04.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/01/P161-04-1024x740.png" alt="" width="1024" height="740" class="aligncenter size-large wp-image-9327" srcset="https://blog.andreev.it/wp-content/uploads/2023/01/P161-04-1024x740.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-04-300x217.png 300w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-04-768x555.png 768w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-04-1170x845.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-04-585x423.png 585w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-04.png 1251w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2023/01/kubernetes-monitoring-node-js-application-with-prometheus-and-grafana-helm-charts/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
