Come usare Vite.js con Helm
Tags: kubernetes, docker, helm
Published: 2023-10-03 14:18:33

---
title: Come usare Vite.js con Helm
published: true
date: 2023-09-14 00:00:00 UTC
tags:
  - "kubernetes"
  - "docker"
  - "helm"
canonical_url: 
---

<ins>Hai bisogno di sapere come portare la tua applicazione su Kubernetes in Vite.js da zero?</ins>

_Sei nel posto giusto._

Unico **requisito** per seguire questa guida: avere un’applicazione Vite.js. Nel caso in cui tu non ne abbia una pronta,[qui](https://github.com/serenasensini/Vite-with-Docker) trovi un esempio di un **counter** scritto in Vite.js.

Di base, questo esempio non fa altro che esporre una pagina dove è presente un pulsante che riporta il numero di click eseguiti:

![](https://theredcode.it/img/vite-home.png)

Il progetto -al momento- ha questa struttura:

![](https://theredcode.it/img/vite-chart-tree.png)

Ma andiamo avanti, e vediamo step-by-step come arrivare ad avere un’istanza su Kubernetes:

# Dockerfile

Per far sì che funzioni su Kubernetes, è necessario avere un’immagine: per avere un’immagine, serve un **Dockerfile** che ci permetta di configurare l’applicazione attraverso dei semplici passaggi:

- installazione di Node.js 18, dal momento che stiamo utilizzando la versione Vite.js 4.4.5+ (istruzione _FROM)_
- specificare la cartella di lavoro dove i file del codice sorgente verranno copiati (istruzione _WORKDIR_)
- copiare tutti i file necessari all’installazione delle dipendenze (istruzione _COPY_)
- installare _npm_ e le relative dipendenze (istruzione _RUN_)
- copiare i restanti file riguardanti l’applicazione (istruzione _RUN_)
- specificare la porta 5173, ossia la porta di default utilizzata da Vite.js per esporre l’applicazione (istruzione_EXPOSE_)
- definire qual è il comando con cui l’applicazione viene avviata (istruzione _CMD_).

```
FROM node:18
LABEL authors="TheRedCode.it"

WORKDIR /app

COPY package*.json ./

RUN npm i && npm install -g npm@9.8.1

COPY . .

RUN mkdir /.npm && \
 chgrp -R 0 /app && \
 chmod -R ug+rwX /app && \
 chown -R 1001:0 /app && \
 chgrp -R 0 /.npm && \
 chmod -R ug+rwX /.npm && \
 chown -R 1001:0 /.npm;

EXPOSE 5173

USER 1001

CMD ["npm", "run", "dev"]

```

Le restanti righe, come l’istruzione _RUN_ che contiene i comandi _mkdir_ e _chgrp_ o quella contenente l’istruzione USER servono per assegnare un utente che non abbia i permessi di amministratore al container e per far sì che possa eseguire i file necessari all’avvio dell’applicazione: questo perché vogliamo che l’immagine sia **sicura** e segua la **best practices** in materia di container.

> Ognuna delle righe precedenti può e deve essere adattata all’applicazione utilizzata: ad esempio, se la porta utilizzata è diversa da quella riportata, è sufficiente sostituirla; stesso vale per la versione di Node.js utilizzata o il comando che viene specificato per avviare l’applicazione in locale.

A questo punto, testiamo l’immagine con il comando seguente:

```
docker build -t vite-app .
docker run -d vite-app -p 5173:5173

```

Anche qui, utilizziamo il parametro _-p_ per specificare la porta dell’applicazione (parametro a destra) da esporre localmente (parametro a sinistra).

Se tutto ha funzionato correttamente, dovremmo ottenere lo stesso risultato visto in precedenza:

![](https://theredcode.it/img/vite-home.png)

Ora che l’immagine è pronta, dobbiamo renderla accessibile tramite un **registry** : esistono diverse opzioni, come[DockerHub](https://hub.docker.com/) o [Quay.io](https://quay.io/), che richiedono solamente un account gratuito.

Nel caso di [DockerHub](https://hub.docker.com/), è sufficiente creare un account, creare un repository e poi eseguire il login da riga di comando per poter eseguire il tag e il push dell’immagine.

```
docker login -u IL_MIO_UTENTE

docker tag vite-app IL_MIO_UTENTE/vite-app:0.0.1

docker push IL_MIO_UTENTE/vite-app:0.0.1

```

Nel caso di [Quay.io](https://quay.io/), è sufficiente creare un account, creare un repository e poi eseguire il login da riga di comando per poter eseguire il push dell’immagine.

```
docker login quay.io -u IL_MIO_UTENTE

docker tag vite-app IL_MIO_UTENTE/vite-app:0.0.1

docker push IL_MIO_UTENTE/vite-app:0.0.1

```

# Creare il Chart

Abbiamo già visto [cos’è Helm e come funziona](https://dev.to/serenasensini/helm-in-5-minuti-4epd-temp-slug-6124770): vediamo come contestualizzarlo su Vite.js.

Per iniziare a **costruire il Chart** , utilizziamo la riga di comando ed eseguiamo il comando:

```
helm create vite-chart
>>>
Creating vite-chart

```

Questo andrà a creare una **cartella** con i diversi file necessari alla sua esecuzione:

![](https://theredcode.it/img/vite-chart-tree.png)

Ora dobbiamo toglierci il cappello da _#dev_ e passare a quello di **#architect** per poter analizzare dall’alto l’applicazione e scegliere le giuste risorse Kubernetes necessarie al suo deploy.

## Stateless o no?

Partiamo dall’inizio: l’applicazione è _stateless_? Ossia, <ins>è in grado di girare senza la necessità di salvare
alcun dato riguardo la sessione? </ins> Nel caso di esempio, **sì** : possiamo quindi utilizzare un oggetto come il Deployment per eseguire il container, che è pensato nativamente per applicazioni stateless.

Apriamo dunque il file _deployment.yaml_ nella cartella _templates_ e iniziamo ad analizzarlo -e modificarlo.

Già dalle prime righe, notiamo che ci sono alcune istruzioni che, per chi non avesse familiarità con i Chart, potrebbero risultare complicate: l’istruzione _include_, ad esempio, fa riferimento a un file chiamato _helpers.tpl_ che rappresenta un **template** all’interno del quale è possibile includere diverse porzioni di YAML che rappresentano i valori con cui quel campo (nel caso di esempio seguente, _name_), dovrà essere valorizzato.

```
apiVersion: apps/v1
kind: Deployment
metadata:
 name: {{ include "vite-chart.fullname" . }}
...

```

Giusto per avere un riferimento, nel file **\_helpers.tpl** avremo una definizione di questo tipo, dove il campo_fullname_ viene valorizzato troncando a 63 il numero massimo di caratteri per evitare problemi di sintassi con alcune risorse Kubernetes che hanno questa limitazione; il tutto viene fatto tramite alcune istruzioni if/else che controllano diversi aspetti del campo per adattarlo al caso d’uso.

```
{{- define "vite-chart.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

```

Dal momento che vogliamo **semplificarci la vita** , faremo uso del solo file _values.yaml_, mentre il file _\_helpers. tpl_ verrà scartato, per cui possiamo rimuovere l’istruzione _include_ dallo YAML che definisce il Deployment e inserire un campo nel file _values.yaml_ per definire il _fullname_, così da avere un’istruzione che da riutilizzare, in questo modo: aggiungo il campo _fullname_ al file _values.yaml_, e poi inserisco l’istruzione che lo sostituisce nel file _deployment.yaml_:

```
# Default values for vite-chart.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

replicaCount: 1

image:
 repository: "ssensini/vite-app"
 pullPolicy: IfNotPresent
 # Overrides the image tag whose default is the chart appVersion.
 tag: "0.0.1"

imagePullSecrets: []
nameOverride: ""
fullname: "vite-chart"
...


apiVersion: apps/v1
kind: Deployment
metadata:
 name: {{ .Values.fullname }}
...

```

Lo stesso principio può essere applicato per i diversi campi, come _replicas_ e via dicendo, se vogliamo rendere parametrizzabile ogni aspetto del Deployment. In questo caso, proseguiamo con la lettura del file:

```
apiVersion: apps/v1
kind: Deployment
metadata:
 name: {{ .Values.fullname }}
 labels:
 app: vite-chart
spec:
 {{- if not .Values.autoscaling.enabled }}
 replicas: {{ .Values.replicaCount }}
 {{- end }}
 selector:
 matchLabels:
 app: vite-chart
...

```

Nel caso di _labels_ e _matchLabels_, inseriamo una label di default chiamata _app_:

```
apiVersion: apps/v1
kind: Deployment
metadata:
 name: {{ .Values.fullname }}
 labels:
 app: vite-chart
...

```

Andando avanti nella lettura del file, vediamo che viene aggiunto il campo _imagePullSecret_, _serviceAccountName_ e_securityContext_: questi sono valorizzati come vuoti nel file _values.yaml_ e in effetti _al momento non sono necessari_, quindi proseguiamo ignorando o rimuovendo questa sezione.

```
...
 spec:
 {{- with .Values.imagePullSecrets }}
 imagePullSecrets:
 {{- toYaml . | nindent 8 }}
 {{- end }}
 serviceAccountName: {{ include "vite-chart.serviceAccountName" . }}
 securityContext:
 {{- toYaml .Values.podSecurityContext | nindent 8 }}
...

```

Nella sezione relativa al **container** che verrà creato, troviamo invece le informazioni sull’immagine da utilizzare: questa fa sempre riferimento al file _values.yaml_ che, di default, viene valorizzata con l’immagine di Nginx in una specifica versione. Sostituiamo con quella presente nel repository creato in precedenza direttamente nel file_values.yaml_:

```
...
# Default values for vite-chart.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

replicaCount: 1

image:
 repository: "IL_MIO_UTENTE/vite-app"
 pullPolicy: IfNotPresent
 # Overrides the image tag whose default is the chart appVersion.
 tag: "0.0.1"
...

```

Continuando, la **porta** di default è impostata a 80, mentre dovrebbe essere **5173** : sempre nel file nel file_values.yaml_ modifichiamo questo valore:

```
...
service:
 type: ClusterIP
 port: 5173
...

```

Al momento, rimuoviamo le sezioni _livenessProbe_ e _readinessProbe_, dal momento che non sono presenti questa tipologia di controlli nella nostra applicazione e ignoriamo la sezione relativa alle risorse (chiamata _resources)_, che definiscono la CPU e la memoria necessarie all’applicazione per poter funzionare.

Rimuovendo anche gli ultimi campi, il Deployment dovrebbe risultare in questo modo:

```
apiVersion: apps/v1
kind: Deployment
metadata:
 name: {{ .Values.fullname }}
 labels:
 app: vite-chart
spec:
 {{- if not .Values.autoscaling.enabled }}
 replicas: {{ .Values.replicaCount }}
 {{- end }}
 selector:
 matchLabels:
 app: vite-chart
 template:
 metadata:
 {{- with .Values.podAnnotations }}
 annotations:
 {{- toYaml . | nindent 8 }}
 {{- end }}
 labels:
 {{- include "vite-chart.selectorLabels" . | nindent 8 }}
 spec:
 {{- with .Values.imagePullSecrets }}
 imagePullSecrets:
 {{- toYaml . | nindent 8 }}
 {{- end }}
 serviceAccountName: {{ include "vite-chart.serviceAccountName" . }}
 securityContext:
 {{- toYaml .Values.podSecurityContext | nindent 8 }}
 containers:
 - name: {{ .Chart.Name }}
 securityContext:
 {{- toYaml .Values.securityContext | nindent 12 }}
 image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
 imagePullPolicy: {{ .Values.image.pullPolicy }}
 ports:
 - name: http
 containerPort: {{ .Values.service.port }}
 protocol: TCP
 resources:
 {{- toYaml .Values.resources | nindent 12 }}

```

## Comunicare tramite Service

Per poter permettere l’accesso alla rete del cluster, è necessario utilizzare una risorsa di tipo **Service** : durante la creazione del Chart, è stato infatti creato un file _service.yaml_ che ne definisce uno standard. Analizziamolo:

```
apiVersion: v1
kind: Service
metadata:
 name: {{ include "vite-chart.fullname" . }}
 labels:
 {{- include "vite-chart.labels" . | nindent 4 }}
spec:
 type: {{ .Values.service.type }}
 ports:
 - port: {{ .Values.service.port }}
 targetPort: http
 protocol: TCP
 name: http
 selector:
 {{- include "vite-chart.selectorLabels" . | nindent 4 }}

```

Come fatto in precedenza, sostituiamo il valore del campo _name_ con quello recuperato dal file _values.yaml_:

```
apiVersion: v1
kind: Service
metadata:
 name: {{ .Values.fullname }}
...

```

Per il resto, il file riporta i valori del file _values.yaml_ correttamente definendo un Service di tipo ClusterIP esponendo la porta 5173 tramite protocollo TCP e chiama la porta del Service _http_.

```
apiVersion: v1
kind: Service
metadata:
 name: {{ .Values.fullname }}
 labels:
 app: vite-chart
spec:
 type: {{ .Values.service.type }}
 ports:
 - port: {{ .Values.service.port }}
 targetPort: {{ .Values.service.port }}
 name: http
 protocol: TCP

```

In questo modo, <ins>l’applicazione sarà in grado di comunicare con altri Pod all’<strong>interno</strong> del cluster.</ins>

Ultimo step: il file _Chart.yaml_: verifichiamo che le informazioni riguardo i metadati del Chart che andremo a creare siano corrette, rimuoviamo eventuali commenti e proseguiamo.

```
apiVersion: v2
name: vite-chart
description: A Helm chart for Kubernetes

type: application
version: 0.1.0

appVersion: "1.16.0"

```

# Ultimi ritocchi

Rimuoviamo una serie di file che non ci servono: in particolare, rimuoviamo _hpa.yaml_ e _serviceaccount.yaml_ e **controlliamo che la sintassi** di quanto fatto finora **sia corretta** tramite il comando _helm lint_: questo andrà a verificare se i file finora modificati contengono errori o meno.

```
helm lint .
==> Linting .
[INFO] Chart.yaml: icon is recommended

1 chart(s) linted, 0 chart(s) failed

```

Perfetto, **tutto funziona** perfettamente!

# Installazione

A questo punto, possiamo testare il Chart all’interno di un cluster Kubernetes. Per farlo, dopo esserci collegati, possiamo eseguire il comando _helm install_ sfruttando il file _values.yaml_ e assegnando _vite-app_ come nome alla release, in questo modo:

```
helm install -f values.yaml vite-app ./
>>>
NAME: vite-app
LAST DEPLOYED: ...
NAMESPACE: kube-public
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
 export POD_NAME=$(kubectl get pods --namespace kube-public -l "app.kubernetes.io/name=vite-chart,app.kubernetes.io/instance=vite-app" -o jsonpath=
"{.items[0].metadata.name}")
 export CONTAINER_PORT=$(kubectl get pod --namespace clinalytix $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
 echo "Visit http://127.0.0.1:8080 to use your application"
 kubectl --namespace clinalytix port-forward $POD_NAME 8080:$CONTAINER_PORT

```

# Esporre l’applicazione

Ok, sembra che il pod stia funzionando: e ora come vi accedo? Per fare un _test al volo_, possiamo sfruttare i comandi riportati nell’output e modificarli per nostra comodità.

In primis, eseguiamo il comando di _export_ che ci permette di recuperare il nome del pod dell’applicazione utilizzando la label _app_ e fornendo come output il solo campo _name_:

```
export POD_NAME=$(kubectl get pods --namespace kube-public -l "app" -o name)

```

Poi, recuperiamo la porta del container esposta dal pod con il secondo comando, sostituendo il nome del Pod in questo modo:

```
export CONTAINER_PORT=$(kubectl get $POD_NAME --namespace kube-public -o jsonpath="{.spec.containers[0].ports[0].containerPort}")

```

Infine, eseguiamo il _ **port-forward** _ della porta esposta dal container (ricordiamo, la 5173) verso la porta 8080 locale (o un’altra qualsiasi porta):

```
kubectl --namespace kube-public port-forward $POD_NAME 8080:$CONTAINER_PORT

```

Fatto questo, ci basterà aprire il browser e digitare _http://localhost:8080_ per visualizzare la pagina principale dell’applicazione:

![](https://theredcode.it/img/vite-home.png)

In realtà, per _esporre_ l’applicazione ci sono diversi altri modi, a seconda del tipo di configurazione di rete che è supportata: è possibile creare un **LoadBalancer** , oppure sfruttare un **Ingress** …

_Ma questo lo vedremo in un’altra puntata!_

# Risorse utili

- [Repository di esempio per Docker](https://github.com/serenasensini/Vite-with-Docker)
- [Repository di esempio per Kubernetes](https://github.com/serenasensini/Vite-with-Kubernetes)