Premetto che ho scoperto da poco questa cosa. Naturalmente sto parlando di Kubernetes che qui nel mio blog ha quasi il 100% dei post in quest'ultimo periodo. In questo caso farò la combinata Kubernetes con Net 6. La scoperta recente riguarda la possiblità di collegare direttamente i miei Pod, in cui girano container scritti in C#, con Prometheus che permette il monitoraggio delle risorse di un cluster, e di conseguenza con Grafana che permette di creare report con grafici dettagliati delle informazioni raccolte.
Inizio lanciando l'esecuzione di questi due software all'interno del mio cluster. Utilizzerò il servizio di Kubernetes all'interno dell'installazione standard di Docker (anche se ho fatto prove anche con Minikube e su servizi online reali). Il metodo più veloce per l'installazione di questi due pacchetti è grazie a Helm. Helm è un gestore di pacchetti per Kubernetes che permette l'installazione di applicazioni anche molto complesse con un solo comando.
In questo post scavalco completamente l'installazione di Helm visto che in rete si trovano moltissimi esempi in merito e risulta semplice su tutte le piattaforme (su Windows 10 è sufficiente scaricare lo zip da questa pagina e estrarre l'eseguibile).
Storicamente per l'installazione di Prometheus si usava il repository stable, ma da più di un anno lui e molti altri risultano Deprecated. Il motivo principale si trova qui. Da questo motore di ricerca per Helm si possono cercare alternative:
E nel caso si fa una ricerca per Prometheus si trova l'ufficiale:
https://artifacthub.io/packages/helm/prometheus-community/prometheus
Sinceramente in passato l'ho usato senza problemi perché molto comodo: con un solo comando installa Prometheus, Grafana e tutto ciò che serve - Pod, service... - ma sono mesi che l'utilizzo di questo package mi dà problemi che necessitano di workaround per fare funzionare il tutto. Ora preferisco utilizzare altri pacchetti per Prometheus, come quello di Bitnami:
https://bitnami.com/stack/prometheus-operator/helm
Per Grafana:
https://bitnami.com/stack/grafana/helm
E' ora di fare sul serio installando tutto. Avendo Helm installato e un cluster di Kubernetes, inizio creando il Namespace dove inserirò tutto ciò che riguarda questi due software:
kubectl create namespace monitors
Quindi aggiungo il repository a Helm:
helm repo add bitnami https://charts.bitnami.com/bitnami helm repo update
... e installo Prometheus:
>helm install my-prometheus --namespace monitors bitnami/kube-prometheus W0109 10:04:14.516714 16608 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+ W0109 10:04:14.521874 16608 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+ W0109 10:04:14.526425 16608 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+ W0109 10:04:14.530130 16608 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+ W0109 10:04:14.532808 16608 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+ W0109 10:04:15.369263 16608 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+ W0109 10:04:15.376872 16608 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+ W0109 10:04:15.376872 16608 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+ W0109 10:04:15.378700 16608 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+ W0109 10:04:15.380284 16608 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+ NAME: my-prometheus LAST DEPLOYED: Sun Jan 9 10:04:13 2022 NAMESPACE: monitors STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: CHART NAME: kube-prometheus CHART VERSION: 6.6.0 APP VERSION: 0.53.1 ** Please be patient while the chart is being deployed ** Watch the Prometheus Operator Deployment status using the command: kubectl get deploy -w --namespace monitors -l app.kubernetes.io/name=kube-prometheus-operator,app.kubernetes.io/instance=my-prometheus Watch the Prometheus StatefulSet status using the command: kubectl get sts -w --namespace monitors -l app.kubernetes.io/name=kube-prometheus-prometheus,app.kubernetes.io/instance=my-prometheus Prometheus can be accessed via port "9090" on the following DNS name from within your cluster: my-prometheus-kube-prometh-prometheus.monitors.svc.cluster.local To access Prometheus from outside the cluster execute the following commands: echo "Prometheus URL: http://127.0.0.1:9090/" kubectl port-forward --namespace monitors svc/my-prometheus-kube-prometh-prometheus 9090:9090 Watch the Alertmanager StatefulSet status using the command: kubectl get sts -w --namespace monitors -l app.kubernetes.io/name=kube-prometheus-alertmanager,app.kubernetes.io/instance=my-prometheus Alertmanager can be accessed via port "9093" on the following DNS name from within your cluster: my-prometheus-kube-prometh-alertmanager.monitors.svc.cluster.local To access Alertmanager from outside the cluster execute the following commands: echo "Alertmanager URL: http://127.0.0.1:9093/" kubectl port-forward --namespace monitors svc/my-prometheus-kube-prometh-alertmanager 9093:9093
Vengono visualizzate informazioni utili che potrei usare più tardi. Ora tocca a Grafana:
>helm install my-grafana --namespace monitors --set admin.password=password bitnami/grafana NAME: my-grafana LAST DEPLOYED: Sun Jan 9 10:07:17 2022 NAMESPACE: monitors STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: CHART NAME: grafana CHART VERSION: 7.6.0 APP VERSION: 8.3.3 ** Please be patient while the chart is being deployed ** 1. Get the application URL by running these commands: echo "Browse to http://127.0.0.1:8080" kubectl port-forward svc/my-grafana 8080:3000 & 2. Get the admin credentials: echo "User: admin" echo "Password: $(kubectl get secret my-grafana-admin --namespace monitors -o jsonpath="{.data.GF_SECURITY_ADMIN_PASSWORD}" | base64 --decode)"
Nei due link sopra si possono trovare anche tutti i parametri che si possono aggiungere ai due pacchetti durante l'installazione. Nel mio caso mi sono limitato a specificare in quale Namespace installare il tutto e la password per l'utente principale in Grafana (altrimenti sarà creata una casuale).
Innanzitutto verifico che Prometheus sia avviato correttamente. Cerco il Service a lui collegato:
>kubectl -n monitors get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE alertmanager-operated ClusterIP None <none> 9093/TCP,9094/TCP,9094/UDP 4m20s my-grafana ClusterIP 10.111.50.218 <none> 3000/TCP 80s my-prometheus-kube-prometh-alertmanager ClusterIP 10.103.68.153 <none> 9093/TCP 4m23s my-prometheus-kube-prometh-operator ClusterIP 10.98.63.55 <none> 8080/TCP 4m23s my-prometheus-kube-prometh-prometheus ClusterIP 10.99.91.123 <none> 9090/TCP 4m23s my-prometheus-kube-state-metrics ClusterIP 10.107.81.39 <none> 8080/TCP 4m23s my-prometheus-node-exporter ClusterIP 10.106.217.82 <none> 9100/TCP 4m23s prometheus-operated ClusterIP None <none> 9090/TCP 4m20s
Eccolo è my-prometheus-kube-prometh-prometheus. Ma è di tipo cluster, quindi accessibile solo internamente. Per poterlo richiamare anche dall'esterno (da browser) uso il comando port-forward come è stato mostrato anche nella risposta di Helm:
kubectl port-forward --namespace monitors svc/my-release1-kube-prometheu-prometheus 9090:9090
Ora posso utilizzare il browser e richiamare la pagina http://localhost:9090 (anche se il cluster di Kubernetes fosse avviato in un servizio in cloud):
Funziona. Per ora non mi interessa altro. E' il momento di Grafana:
kubectl port-forward svc/my-grafana 8080:3000
Ora aprendo il browser alla pagina http://localhost:8080 saranno richieste le credenziali: admin e password:
Il passaggio successivo è collegare Grafana con Prometheus. Vado in Configuration nella barra a sinistra, Data sources. Quindi click su Add Data Source. Ora aggiungo come fonte Prometheus e nella pagina successiva, nella Textbox dell'url, inserisco il nome del suo service visto prima (le informazioni riportate da Helm non sono complete):
http://my-prometheus-kube-prometh-prometheus.monitors.svc.cluster.local:9090
Quindi, sul fondo della pagina, clicco su Save and Test:
Se è tutto verde e funziona, posso inserire una Dashboard. L'installazione dai pacchetti di Bitnami non installa nessuna Dashboard di default, cosa che invece fa la versione Community. Non è un problema. Nella barra a destra click su + (create), quindi Import. Nella Textbox "Import via grafana.com" inserisco il valore numerico 10000 e come Data source seleziono Prometheus dal dropdownlist. Ed ecco una Dashboard con le informazioni di base del cluster:
Se si vogliono avere dati sui Pod, consiglio questa Dashboard: 15398:
Oppure per la visione dei Pod: 15336, 8860 e 10518. A parte i gusti personali, è presente un ricco database con molte dashboard già pronte: https://grafana.com/grafana/dashboards/
Grafana, come già detto, lavora in copia con Prometheus visto che richiede i dati a quest'ultimo per popolare i suoi grafici. Personalmente preferisco partire proprio da lui per eseguire i miei test prima di portarli in Grafana. Nella home page di Prometheus è possibile inserire le query nel suo formato interno e vedere subito il risultato in formato testuale o chart. L'intellisense della Textbox permette di vedere subito i datapoint disponibili, anche quelli custom che definirò dopo:
I datapoint principali e più utilizzati in Prometheus sono due:
- counter: è un valore numerico a incremento, come potrebbe essere il numero di richieste ad un container.
- gauge: è un valore numerico assoluto, come il consumo di memoria di un container.
In Prometheus il più semplice è il gauge, per esempio, per vedere il consumo di memoria dei container che girano nel cluster:
In un cluster con pochi Pod i risultati sono gestibili, ma è sempre meglio poterli filtrare. Avendo come output una riga come la seguente:
container_memory_usage_bytes{container="run", endpoint="https-metrics", ... namespace="testmetrics", node="docker-desktop", ...}
Posso filtrare per il Namespace interessato:
E qui il risultato filtrato con solo tre linee che sono i container che girano in quel container. Se volessi aggregare questi dati:
sum(container_memory_usage_bytes{namespace="testmetrics"})
Se volessi filtrare anche per Node:
sum(container_memory_usage_bytes{namespace="testmetrics",node="docker-desktop"})
Per il tipo counter la visualizzazione in chart sarebbe poco interessante perché, essendo un valore a solo incremento, si avrebbe una linea che sale in modo più o meno accentuato. Prometheus permette di aggregare questi dati in periodi di tempo, rendendo la visualizzazione di questi dati molto più utile. Prendo il counter container_cpu_user_seconds_total che mostra l'utilizzo di cpu per ogni container per secondo. Ora aggregando i dati a 5 minuti:
rate(container_cpu_user_seconds_total [5m])
Nella documentazione di Prometheus è possibile trovare funzioni e altre info utili per la creazione di query - io qui ho solo mostrato le basi e non mi posso definire di certo un maestro nel suo uso, quindi mi fermo qui. E' giunto il momento di Net 6. Anche un Pod in cui gira un mio codice in Net Core visualizzerebbe le informazioni come sopra - un Pod è sempre un Pod. Ma se volessi inserire informazioni mirate ci si scontra con le limitazioni di informazioni disponibili - se volessi sapere quante richieste http ha avuto una determinata pagina, come potrei avere questa informazione?
La principale domanda è un'altra: come fa Prometheus a prendere queste informazioni dal cluster di Kubernetes? Dall'interfaccia di Prometheus visualizzo la pagina Service Discovery:
http://localhost:9090/service-discovery
Questi service sono letti dalla lista dei ServiceMonitor che è una CustomResource creata da Prometheus:
>kubectl get servicemonitor -A NAMESPACE NAME AGE monitors my-prometheus-kube-prometh-alertmanager 61m monitors my-prometheus-kube-prometh-apiserver 61m monitors my-prometheus-kube-prometh-coredns 61m monitors my-prometheus-kube-prometh-kube-controller-manager 61m monitors my-prometheus-kube-prometh-kube-proxy 61m monitors my-prometheus-kube-prometh-kube-scheduler 61m monitors my-prometheus-kube-prometh-kubelet 61m monitors my-prometheus-kube-prometh-operator 61m monitors my-prometheus-kube-prometh-prometheus 61m monitors my-prometheus-kube-state-metrics 61m monitors my-prometheus-node-exporter 61m
Ergo, per permettere la lettura da Prometheus dei miei dati custom devo creare un ServiceMonitor. Ma è solo il primo passo, perché queste Resource servono solo a Prometheus a indicare dove prendere le informazioni che io devo esporre in qualche modo. Ultimo tassello è creare un Exporter che, nel mio caso, è una pagina nella mia webapplication che mette a disposizione queste informazioni in un formato ben preciso. La community ha creato numerosi exporter per i software più utilizzati, qui la lista:
https://prometheus.io/docs/instrumenting/exporters/
Per esempio, se nel mio cluster avessi il database MySql potrei collegare l'exporter:
https://github.com/prometheus/mysqld_exporter
E avrei dettagli completi del Pod e del database (sono presenti anche le immagini Docker già pronte per essere usate in Kubernetes).
Per la mia semplice web application in Net 6 l'exporter lo creerò direttamente nell'applicazione. In nuget sono presenti alcuni Package che semplificano il tutto, nel mio caso ho usato questo:
https://www.nuget.org/packages/prometheus-net.AspNetCore/
Ed ecco la mia minimal app in C# e Net 6, il file program.cs:
using System.Net; using Prometheus; using WebApiMetrics; string pathMetrics = Environment.GetEnvironmentVariable("METRICS_PATH") ?? "/metrics"; using MetricsApp metrics = new(pathMetrics); var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.UseRouting(); app.UseHttpMetrics(); app.MapMetrics(pattern: pathMetrics); app.Use(metrics.Use); app.MapGet("/", () => "Hello, World! v1.0"); app.MapGet("/host", () => $"Host: {Dns.GetHostName()}"); app.MapGet("/health", () => "health check"); app.Run();
Per la creazione automatica dell'exporter bastano queste due righe:
app.UseHttpMetrics(); app.MapMetrics(pattern: pathMetrics);
In pattern ho inserito come Path /metrics. Questo in una pagina pubblica potrebbe essere facilmente scoperto da chicchessia, ma in questo caso è compito nostro bloccare tali richieste oppure crearne una difficile da scoprire a caso (sfido a trovare a caso il Path /metrics_fjoewfu23jfewojeczcewmjhf_fwef8fetreoiuoiw).
Nella classe MetricsApp ho inserito la configurazione custom per aggiungere mie informazioni:
using System.Diagnostics; using Prometheus; namespace WebApiMetrics; public sealed class MetricsApp : IDisposable { private readonly Counter _counter1, _counter2, _counter3, _counter4; private readonly string _pathMetrics; private readonly Process _proc; public MetricsApp(string pathMetrics) { _pathMetrics = pathMetrics ?? throw new ArgumentException(nameof(pathMetrics)); _proc = Process.GetCurrentProcess(); _counter1 = Metrics.CreateCounter( "webapimethods_path_counter", "requests to webapimethods", new CounterConfiguration { LabelNames = new[] { "method", "endpoint" } }); _counter2 = Metrics.CreateCounter( "private_memory_size_64", "PrivateMemorySize64"); _counter3 = Metrics.CreateCounter( "gc_get_total_memory", "GC.GetTotalMemory"); _counter4 = Metrics.CreateCounter( "system_environment_workingset", "System.Environment.WorkingSet"); } public void Dispose() { _proc.Dispose(); } public Task Use(HttpContext context, Func<Task> next) { var path = context.Request.Path.Value; if (path is not null && path != _pathMetrics && path != "/health" && path != "/favicon.ico") { _counter1.WithLabels(context.Request.Method, path).Inc(); _counter2.IncTo(_proc.PrivateMemorySize64); _counter3.IncTo(GC.GetTotalMemory(true)); _counter4.IncTo(Environment.WorkingSet); } return next.Invoke(); } }
Nel costruttore della classe mi sono creato un custom counter che inserirà questi due dati: Method e Endpoint, quindi altri tre Counter che aggiungeranno altre informazioni sul consumo di memoria della mia applicazione. Quindi il mio middleware (metodo Use) leggerà ogni Path e inserirà nel Counter le informazioni.
In questo esempio ho usato entrambi i tipo di Counter di cui ho scritto prima. Il primo di tipo counter per incrementare il numero di pagine visitate:
_counter1 = Metrics.CreateCounter( "webapimethods_path_counter", "requests to webapimethods", new CounterConfiguration { LabelNames = new[] { "method", "endpoint" } });
Alla creazione del Counter ho inserito il nome, il testo descrittivo che sarà usato per l'help, e due Label che differenzieranno il record salvato con il path e il method (GET, POST...) usato per la richiesta. E' possibile usare più Label per specificare nel dettaglio altre informazioni e per ottenere report più dettagliati. Essendo di tipo Counter, per aumentarne il contatore, scriverò poi:
_counter1.WithLabels(context.Request.Method, path).Inc();
Gli altri Counter sono di tipo gauge (con valore assoluto):
_counter2 = Metrics.CreateCounter( "private_memory_size_64", "PrivateMemorySize64"); ... _counter2.IncTo(_proc.PrivateMemorySize64);
Senza parametri aggiuntivi, definito il Counter, uso il metodo IncTo per inserire il valoro assoluto.
Una volta lanciato ho il risultato voluto richiamando nell'url la pagina metrics:
# HELP process_start_time_seconds Start time of the process since unix epoch in seconds. # TYPE process_start_time_seconds gauge process_start_time_seconds 1641932683.7917504 # HELP process_private_memory_bytes Process private memory size # TYPE process_private_memory_bytes gauge process_private_memory_bytes 38047744 # HELP process_working_set_bytes Process working set # TYPE process_working_set_bytes gauge process_working_set_bytes 48214016 # HELP process_virtual_memory_bytes Virtual memory size in bytes. # TYPE process_virtual_memory_bytes gauge process_virtual_memory_bytes 2213288710144 # HELP process_cpu_seconds_total Total user and system CPU time spent in seconds. # TYPE process_cpu_seconds_total counter process_cpu_seconds_total 1.234375 # HELP process_num_threads Total number of threads # TYPE process_num_threads gauge process_num_threads 23 # HELP process_open_handles Number of open handles # TYPE process_open_handles gauge process_open_handles 432 # HELP system_environment_workingset System.Environment.WorkingSet # TYPE system_environment_workingset counter system_environment_workingset 48316416 # HELP http_request_duration_seconds The duration of HTTP requests processed by an ASP.NET Core application. # TYPE http_request_duration_seconds histogram http_request_duration_seconds_sum{code="200",method="GET",controller="",action=""} 0.09010299999999999 http_request_duration_seconds_count{code="200",method="GET",controller="",action=""} 12 http_request_duration_seconds_bucket{code="200",method="GET",controller="",action="",le="0.001"} 3 http_request_duration_seconds_bucket{code="200",method="GET",controller="",action="",le="0.002"} 8 http_request_duration_seconds_bucket{code="200",method="GET",controller="",action="",le="0.004"} 10 http_request_duration_seconds_bucket{code="200",method="GET",controller="",action="",le="0.008"} 10 http_request_duration_seconds_bucket{code="200",method="GET",controller="",action="",le="0.016"} 11 http_request_duration_seconds_bucket{code="200",method="GET",controller="",action="",le="0.032"} 11 http_request_duration_seconds_bucket{code="200",method="GET",controller="",action="",le="0.064"} 12 http_request_duration_seconds_bucket{code="200",method="GET",controller="",action="",le="0.128"} 12 http_request_duration_seconds_bucket{code="200",method="GET",controller="",action="",le="0.256"} 12 http_request_duration_seconds_bucket{code="200",method="GET",controller="",action="",le="0.512"} 12 http_request_duration_seconds_bucket{code="200",method="GET",controller="",action="",le="1.024"} 12 http_request_duration_seconds_bucket{code="200",method="GET",controller="",action="",le="2.048"} 12 http_request_duration_seconds_bucket{code="200",method="GET",controller="",action="",le="4.096"} 12 http_request_duration_seconds_bucket{code="200",method="GET",controller="",action="",le="8.192"} 12 http_request_duration_seconds_bucket{code="200",method="GET",controller="",action="",le="16.384"} 12 http_request_duration_seconds_bucket{code="200",method="GET",controller="",action="",le="32.768"} 12 http_request_duration_seconds_bucket{code="200",method="GET",controller="",action="",le="+Inf"} 12 http_request_duration_seconds_sum{code="404",method="GET",controller="",action=""} 0.0005065 http_request_duration_seconds_count{code="404",method="GET",controller="",action=""} 1 http_request_duration_seconds_bucket{code="404",method="GET",controller="",action="",le="0.001"} 1 http_request_duration_seconds_bucket{code="404",method="GET",controller="",action="",le="0.002"} 1 http_request_duration_seconds_bucket{code="404",method="GET",controller="",action="",le="0.004"} 1 http_request_duration_seconds_bucket{code="404",method="GET",controller="",action="",le="0.008"} 1 http_request_duration_seconds_bucket{code="404",method="GET",controller="",action="",le="0.016"} 1 http_request_duration_seconds_bucket{code="404",method="GET",controller="",action="",le="0.032"} 1 http_request_duration_seconds_bucket{code="404",method="GET",controller="",action="",le="0.064"} 1 http_request_duration_seconds_bucket{code="404",method="GET",controller="",action="",le="0.128"} 1 http_request_duration_seconds_bucket{code="404",method="GET",controller="",action="",le="0.256"} 1 http_request_duration_seconds_bucket{code="404",method="GET",controller="",action="",le="0.512"} 1 http_request_duration_seconds_bucket{code="404",method="GET",controller="",action="",le="1.024"} 1 http_request_duration_seconds_bucket{code="404",method="GET",controller="",action="",le="2.048"} 1 http_request_duration_seconds_bucket{code="404",method="GET",controller="",action="",le="4.096"} 1 http_request_duration_seconds_bucket{code="404",method="GET",controller="",action="",le="8.192"} 1 http_request_duration_seconds_bucket{code="404",method="GET",controller="",action="",le="16.384"} 1 http_request_duration_seconds_bucket{code="404",method="GET",controller="",action="",le="32.768"} 1 http_request_duration_seconds_bucket{code="404",method="GET",controller="",action="",le="+Inf"} 1 # HELP dotnet_collection_count_total GC collection count # TYPE dotnet_collection_count_total counter dotnet_collection_count_total{generation="1"} 21 dotnet_collection_count_total{generation="0"} 21 dotnet_collection_count_total{generation="2"} 21 # HELP dotnet_total_memory_bytes Total known allocated memory # TYPE dotnet_total_memory_bytes gauge dotnet_total_memory_bytes 1600320 # HELP http_requests_received_total Provides the count of HTTP requests that have been processed by the ASP.NET Core pipeline. # TYPE http_requests_received_total counter http_requests_received_total{code="200",method="GET",controller="",action=""} 12 http_requests_received_total{code="404",method="GET",controller="",action=""} 1 # HELP http_requests_in_progress The number of requests currently in progress in the ASP.NET Core pipeline. One series without controller/action label values counts all in-progress requests, with separate series existing for each controller-action pair. # TYPE http_requests_in_progress gauge http_requests_in_progress{method="GET",controller="",action=""} 0 # HELP webapimethods_path_counter requests to webapimethods # TYPE webapimethods_path_counter counter webapimethods_path_counter{method="GET",endpoint="/host"} 2 webapimethods_path_counter{method="GET",endpoint="/"} 10 # HELP gc_get_total_memory GC.GetTotalMemory # TYPE gc_get_total_memory counter gc_get_total_memory 1477464 # HELP private_memory_size_64 PrivateMemorySize64 # TYPE private_memory_size_64 counter private_memory_size_64 36917248
E infatti sono presenti le due voci per le due pagine inserite, la home e la host:
webapimethods_path_counter{method="GET",endpoint="/host"} 1 webapimethods_path_counter{method="GET",endpoint="/"} 3
Inoltre sono presenti i tre Counter custom del consumo di memoria con tanto di help:
# HELP system_environment_workingset System.Environment.WorkingSet # TYPE system_environment_workingset counter system_environment_workingset 48316416 ... # HELP gc_get_total_memory GC.GetTotalMemory # TYPE gc_get_total_memory counter gc_get_total_memory 1477464 # HELP private_memory_size_64 PrivateMemorySize64 # TYPE private_memory_size_64 counter private_memory_size_64 36917248
Ma se si osserva l'output sono presenti molte altre voci, alcune molto utili, come il numero Thread e Handle in esecuzione... E tutte queste informazioni saranno esposte e visibili Prometheus con le query viste prima.
E' giunto il momento di creare un ServiceMonitor dedicato alla mia applicazione. Innanzitutto è necessario creare una immagine Docker e caricarla su un public registry (sempre se non si ha uno privato anche installato in locale). Nel codice, il cui link inserirò alla fine di questo post, ho inserito il Dockerfile per la sua creazione nel formato compatto, e anche il file di Deploy per Kubernetes che qui inserirò spezzato in due parti. La prima parte è per il Deploy:
apiVersion: v1 kind: Namespace metadata: name: testmetrics --- apiVersion: v1 kind: Service metadata: labels: app: minimalapiwebservice name: minimalapiwebservice namespace: testmetrics spec: selector: app: minimalapiweb ports: - name: metrics protocol: TCP port: 8081 targetPort: metrics type: LoadBalancer # externalIPs: # <- per minikube # - 192.168.49.2 # <- per minikune. Ip = minikube ip dal terminale --- apiVersion: apps/v1 kind: Deployment metadata: name: minimalapiweb labels: app: minimalapiweb namespace: testmetrics spec: replicas: 1 selector: matchLabels: app: minimalapiweb template: metadata: labels: app: minimalapiweb spec: containers: - name: run image: sbraer/webapimetrics:v1.0 imagePullPolicy: IfNotPresent ports: - name: metrics containerPort: 3000 securityContext: runAsUser: 1000 runAsGroup: 100 #command: ["./app/WebApiMetrics"] readinessProbe: httpGet: path: /health port: 3000 periodSeconds: 10 initialDelaySeconds: 3 livenessProbe: httpGet: path: /health port: 3000 periodSeconds: 10 initialDelaySeconds: 10
Innanzitutto creo un Namespace dedicato - testmetrics - quindi un Service e un Deploy sempre in questo Namespace. Ho inserito la definizione delle porte sotto il nome metrics, perché sembra che sia necessario inserirne il nome perché possa essere viste dal ServiceMonitor, così come ho inserito la label nel service definito in questo modo:
labels: app: minimalapiwebservice
Tutto è necessario per il prossimo ServiceMonitor:
apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: minimalapiweb-sm spec: endpoints: - interval: 5s port: metrics namespaceSelector: matchNames: - testmetrics selector: matchLabels: app: minimalapiwebservice
In endpoint c'è il nome della porta definita nel Pod, in namespaceselector il Namespace dove è inerita l'applicazione e in selector il nome del Service. Infine in interval definisco quante volte Prometheus dovrà venire a prendere questi dati (in questo caso ogni 5 secondi).
Inserisco il Deploy nel mio cluster di Kubernetes:
kubectl apply -f deploy-webapp.yaml
E nel browser richiamo le due pagine di esempio:
http://localhost:8081 mostra una stringa di esempio.
http://localhost:8081/host mostra anche il nome del container dove gira la mia applicazione.
E infine controllo che la pagina che sarà utilizzata da Prometheus risponda:
Se tutto funziona, ritorna la pagina vista in precedenza. Ora tornando in Prometheus, dopo qualche istante, apparirà dopo il refresh della pagina anche il mio servicemonitor in prima posizione:
Ora andando nella home page di Prometheus posso eseguire le query su di esso. Nel mio codice avevo inserito come custom Counter la voce webapimethods_path_counter:
Ora in Grafana provo a importare quei miei dati. Nella home page seleziono Create, Dashboard. Nella nuova schermata inserisco un nuovo panel.
Dopo averne inseriti alcuni si potrebbe avere un output come il seguente dove visualizzo oltre alle varie voci sul consumo di memoria, il numero di richieste che hanno restituito un valore 200 in rapporto con le 404 (pie), numero di Thread usati, numero di pagine richieste per Path (mio custom Counter):
Come sempre parto con l'idea di scrivere un post breve e mi esce un post di lunghezza allucinante. Va be', non ho il dono della sintesi, anche se qui avrei molto altro da scrivere... e non ho toccato l'argomento Alert... Ci rinuncio.
Per spegnere tutto:
kubectl delete -f deploy-webapp.yaml helm uninstall my-grafana -n monitors helm uninstall my-prometheus -n monitors
Qui il codice usato.
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
- Snaturare Kubernetes evitando i custom container Docker, il 6 gennaio 2022 alle 19:40
- Provando Kaniko in Kubernetes come alternativa a Docker per la creazione di immagini, il 18 dicembre 2021 alle 20:11
- Divertissement con l'OpenID e Access Token, il 6 dicembre 2021 alle 20:05
- Operator per Kubernetes in C# e Net Core 6., il 28 novembre 2021 alle 19:44
- RBAC in Kubernetes verso gli operator, il 21 novembre 2021 alle 20:52