Πώς να διαβάσεις αυτή τη σελίδα. Δέκα βήματα, ένα ερώτημα τη φορά. Για καθένα: το ερώτημα να πληκτρολογήσεις, η ακριβής απόκριση του εργαστηρίου (καρτέλα Table), και τι πρέπει να κοιτάξεις μέσα της. Πληκτρολόγησε εσύ ο ίδιος κάθε ερώτημα (χωρίς αντιγραφή-επικόλληση): γράφοντας τα άγκιστρα, τα εισαγωγικά και τις αγκύλες μπαίνει η γραμματική στο μυαλό. Οι αριθμοί θα είναι διαφορετικοί σε σένα· οι μορφές (αριθμός γραμμών, labels, τάξη μεγέθους) πρέπει να είναι οι ίδιες. Τα μπλοκ «Για να καταλάβεις καλά» είναι προαιρετικά. Αν το εργαστήριο δεν έχει ξεκινήσει, γύρνα στην καθοδηγούμενη πρακτική: η ενότητα Εν συντομία δίνει τις εντολές, μαζί με το kit (
https://github.com/hrhouma2/aiopsatlas-observabilite-labo-fr). Τίποτα δεν δημιουργείται ούτε τροποποιείται σε αυτό το εργαστήριο: η PromQL μόνο διαβάζει.
Η καθοδηγούμενη πρακτική σε έκανε να πληκτρολογήσεις δώδεκα ερωτήματα ήδη γραμμένα. Είδες τα αποτελέσματα, αλλά αν σου πάρουν το φύλλο, ξέρεις να γράψεις sum by (code) (rate(http_requetes_total{route="/cours"}[1m])) χωρίς να κάνεις λάθος παρένθεση; Εδώ, ξεκινάς από το πιο σύντομο δυνατό ερώτημα, ένα όνομα μετρικής, και προσθέτεις ένα μόνο κομμάτι σε κάθε βήμα: ένα label, έναν τελεστή, μια συνάρτηση, ένα χρονικό εύρος, μια ομαδοποίηση. Δύο βήματα είναι εκούσιες παγίδες: θα προκαλέσεις τα δύο μηνύματα σφάλματος που κάθε αρχάριος συναντά, για να τα αναγνωρίζεις την επόμενη φορά. Στο τέλος, ξέρεις τι είναι μια μετρική, ένα label και μια συνάρτηση γιατί συναρμολόγησες τα τρία κομμάτια εσύ ο ίδιος.
Ο Prometheus είναι ένα σημειωματάριο μετρήσεων. Κάθε 15 δευτερόλεπτα, περνά μπροστά από κάθε στόχο, διαβάζει τη σελίδα /metrics του και σημειώνει κάθε τιμή με την ώρα. Μια μετρική είναι το όνομα μιας στήλης του σημειωματάριου (up, http_requetes_total). Ένα label είναι μια ετικέτα κολλημένη στη γραμμή για να πει για τι μιλάμε (job="api", code="200")· το ίδιο όνομα μετρικής με διαφορετικές ετικέτες είναι διαφορετικές σειρές. Μια συνάρτηση είναι μια πράξη πάνω σε αυτό που διαβάσαμε: να μετρήσουμε τις γραμμές, να υπολογίσουμε μια κλίση, να αθροίσουμε.
| PromQL | Κλασική βάση SQL | Σε αυτό το εργαστήριο |
|---|---|---|
| μετρική | πίνακας | up, api_info, http_requetes_total |
| σειρά | γραμμή του πίνακα | up{instance="api:8000", job="api", service="api"} |
| label | στήλη | job, instance, route, code |
επιλογέας {job="api"} | WHERE job = 'api' | βήμα 3 |
=~ | WHERE job LIKE 'a%' (σε κανονική έκφραση) | βήμα 5 |
count(…), sum(…) | COUNT(*), SUM(…) | βήματα 6 και 10 |
by (code) | GROUP BY code | βήμα 10 |
εύρος [1m] | «οι γραμμές του τελευταίου λεπτού» | βήμα 7 |
rate(…[1m]) | κανένα απλό ισοδύναμο: μια κλίση ανά δευτερόλεπτο | βήμα 8 |
| στιγμιαίο διάνυσμα | μια τιμή ανά σειρά, τώρα | αυτό που επιστρέφει το up |
| διάνυσμα εύρους | πολλές χρονολογημένες τιμές ανά σειρά | αυτό που επιστρέφει το up[1m] |
Άνοιξε το http://localhost:9090. Βρίσκεσαι στη σελίδα Query. Το πεδίο εισαγωγής δέχεται ένα ερώτημα· το Execute (ή Enter) το στέλνει. Το αποτέλεσμα εμφανίζεται κάτω από το πεδίο, στην καρτέλα Table. Μείνε στο Table σε όλη τη διάρκεια του εργαστηρίου: εκεί βλέπεις τα labels γραμμένα ξεκάθαρα. Η καρτέλα Graph σχεδιάζει το ίδιο πράγμα στον χρόνο· η καρτέλα Explain αναλύει το ερώτημα.
Μια γραμμή αποτελέσματος έχει πάντα την ίδια μορφή: το όνομα της μετρικής, μετά μέσα σε άγκιστρα τα labels ταξινομημένα αλφαβητικά, μετά η τιμή δεξιά:
up{instance="api:8000", job="api", service="api"} 1Όταν το ερώτημα έχει λιώσει το όνομα (μια συνάρτηση, μια συνάθροιση), τα άγκιστρα μένουν, μερικές φορές κενά: {} 8. Κάτω από τις καρτέλες, το Result series: N σου λέει πόσες γραμμές έχεις. Ένα κενό αποτέλεσμα γράφεται Empty query result· ένα λάθος γραμμένο ερώτημα εμφανίζει ένα κόκκινο κουτί Error executing query ακολουθούμενο από το μήνυμα.
api_infoΤι ζητά το ερώτημα: την τελευταία τιμή της μετρικής api_info, για όλες τις σειρές της.
api_info{instance="api:8000", job="api", service="api", version="1.0.0"} 1Τι να κοιτάξεις: μία μόνο γραμμή, Result series: 1. Η τιμή είναι 1 και δεν θα αλλάξει ποτέ: η api_info είναι μια μετρική πληροφορίας, όλα όσα έχει να πει βρίσκονται στο label της version="1.0.0". Τρία άλλα labels που το API δεν έγραψε: instance, job και service προστέθηκαν από τον Prometheus τη στιγμή της ανάγνωσης. Στη σελίδα http://localhost:8000/metrics, η ίδια γραμμή γράφεται api_info{version="1.0.0"} 1.0.
api_info και όχι με το up; Γιατί έχει μία σειρά. Βλέπεις την πλήρη μορφή μιας γραμμής αποτελέσματος (όνομα, labels, τιμή) χωρίς να σε αποσπούν επτά άλλες γραμμές._ και :. Όχι παύλα, όχι κενό, όχι τελεία. Το api-info θα διαβαζόταν ως api μείον info.1 εδώ· ο Prometheus δεν αποθηκεύει κείμενο, γι' αυτό η έκδοση βρίσκεται σε ένα label.upΤι ζητά το ερώτημα: την τελευταία τιμή του up, για όλες τις σειρές του.
up{instance="localhost:9090", job="prometheus"} 1
up{instance="alloy:12345", job="alloy"} 1
up{instance="api:8000", job="api", service="api"} 1
up{instance="cadvisor:8080", job="cadvisor"} 1
up{instance="alertmanager:9093", job="alertmanager"} 1
up{instance="loki:3100", job="loki"} 1
up{instance="node-exporter:9100", job="node-exporter"} 1
up{instance="grafana:3000", job="grafana"} 1Τι να κοιτάξεις: Result series: 8, το ίδιο όνομα σε κάθε γραμμή, και τι αλλάζει από γραμμή σε γραμμή: οι τιμές των labels job και instance. Αυτό είναι μια σειρά: ένα όνομα πλέον ένα σύνολο labels. Οκτώ διαφορετικά σύνολα, οκτώ σειρές. Πρόσεξε ότι μόνο η τρίτη γραμμή φέρει service="api": αυτό το label προστέθηκε με το χέρι στο prometheus.yml, μόνο για το job api. Το up δεν υπάρχει σε καμία σελίδα /metrics: ο Prometheus το κατασκευάζει ο ίδιος, 1 αν η ανάγνωση πέτυχε, 0 αλλιώς.
up{job="api"}Τι ζητά το ερώτημα: τις σειρές του up των οποίων το label job ισούται ακριβώς με api.
up{instance="api:8000", job="api", service="api"} 1Τι να κοιτάξεις: μία μόνο γραμμή, η τρίτη του βήματος 2. Τα άγκιστρα μετά το όνομα είναι ένα φίλτρο: το ονομάζουμε επιλογέα. Το job είναι το όνομα του label, το "api" η τιμή του, μέσα σε διπλά εισαγωγικά, το = η ακριβής ισότητα. Είναι το WHERE job = 'api' της SQL, λέξη προς λέξη. Μπορείς να βάλεις πολλές συνθήκες χωρισμένες με κόμματα· όλες πρέπει να είναι αληθείς.
Δύο λάθος ερωτήματα, επίτηδες. Πρώτα, ξέχασε τα εισαγωγικά:
up{job=api}Error executing query
invalid parameter "query": 1:8: parse error: unexpected identifier "api" in label matching, expected stringΤι να κοιτάξεις: parse error, ο Prometheus δεν έψαξε καν: το ερώτημα είναι λάθος γραμμένο. Το 1:8 είναι η θέση (γραμμή 1, χαρακτήρας 8, αμέσως μετά το up{job=). expected string: περίμενε μια συμβολοσειρά μέσα σε εισαγωγικά. Μια τιμή label είναι πάντα συμβολοσειρά, ακόμη κι όταν μοιάζει με αριθμό: το {code=500} δίνει την ίδια οικογένεια σφάλματος, το {code="500"} είναι η σωστή μορφή.
Μετά, βάλε τα εισαγωγικά αλλά άλλαξε τα πεζά/κεφαλαία:
up{job="API"}Empty query resultΤι να κοιτάξεις: κανένα σφάλμα, καμία γραμμή. Είναι η πιο ύπουλη παγίδα: το ερώτημα είναι σωστό, απλώς ζητά μια σειρά που δεν υπάρχει. Οι τιμές των labels είναι ευαίσθητες στα πεζά/κεφαλαία και στην ορθογραφία (το "api " με ένα κενό δεν δουλεύει επίσης). Όταν παίρνεις Empty query result χωρίς λόγο, ξαναπληκτρολόγησε το βήμα 2 και ξαναδιάβασε τις ακριβείς τιμές.
Ένα μήνυμα σφάλματος του Prometheus έχει τρία μέρη: parse error (το ερώτημα είναι κακοσχηματισμένο) ή bad_data (το ερώτημα είναι σωστά σχηματισμένο αλλά αδύνατο να εκτελεστεί), μια θέση γραμμή:στήλη, και μια φράση που λέει τι περίμενε. Πήγαινε πάντα στην υποδεικνυόμενη θέση: το σφάλμα είναι εκεί ή αμέσως πριν. Τα τρία μηνύματα αυτού του εργαστηρίου καλύπτουν τη μεγάλη πλειονότητα των περιπτώσεων: expected string (εισαγωγικά), expected "(" (παρενθέσεις γύρω από το by), expected type range vector (αγκύλες, βήμα 9).
up{job=~"a.*"}Τι ζητά το ερώτημα: τις σειρές του up των οποίων το label job αντιστοιχεί στην κανονική έκφραση a.*: ένα a ακολουθούμενο από οτιδήποτε.
up{instance="alloy:12345", job="alloy"} 1
up{instance="api:8000", job="api", service="api"} 1
up{instance="alertmanager:9093", job="alertmanager"} 1Τι να κοιτάξεις: τρεις γραμμές, τα τρία jobs που αρχίζουν με a. Μία μόνο καινοτομία σε σχέση με το βήμα 3: το =~ στη θέση του =. Η κανονική έκφραση πρέπει να αντιστοιχεί σε ολόκληρη την τιμή: το "a" μόνο του δεν θα επέστρεφε τίποτα, χρειάζεται "a.*". Οι τέσσερις τελεστές επιλογής: = (ίσο), != (διαφορετικό: το up{job!="api"} επιστρέφει τις επτά άλλες), =~ (αντιστοιχεί), !~ (δεν αντιστοιχεί). Το =~ είναι αυτό που χρησιμοποιούσε η καθοδηγούμενη πρακτική στο {code=~"5.."} για να πιάσει όλα τα 5xx.
count(up)Τι ζητά το ερώτημα: τον αριθμό σειρών που επιστρέφει το up.
{} 8Τι να κοιτάξεις: μία μόνο γραμμή, και το όνομα εξαφανίστηκε: {} κενό, μετά 8. Είναι η πρώτη συνάρτηση του εργαστηρίου, και το αποτέλεσμα δεν είναι πλέον το up, είναι ένας αριθμός υπολογισμένος από το up. Το count είναι μια συνάθροιση: παίρνει πολλές σειρές και κάνει μία. Οι ξαδέρφες του: sum (το άθροισμα των τιμών: το sum(up) δίνει επίσης 8 όσο όλα είναι στο 1, και 7 μόλις πέσει ένας στόχος), min, max, avg. Ο πίνακας ελέγχου του kit και η εντολή etat μετρούν τους στόχους ακριβώς έτσι.
http_requetes_total{route="/cours", code="200"}[1m]Τι ζητά το ερώτημα: όλες τις τιμές αυτής της σειράς που καταγράφηκαν στο τελευταίο λεπτό, όχι μόνο την τελευταία.
http_requetes_total{code="200", instance="api:8000", job="api", methode="GET", route="/cours", service="api"}
2439 @1789505608.199
2499 @1789505623.199
2550 @1789505638.196
2609 @1789505653.197Τι να κοιτάξεις: μία σειρά, αλλά τέσσερις τιμές, καθεμία ακολουθούμενη από @ και μια ημερομηνία σε δευτερόλεπτα. Δεκαπέντε δευτερόλεπτα διαφορά μεταξύ δύο: είναι το scrape_interval. Ο μετρητής ανεβαίνει από 2439 σε 2609: 170 αιτήματα 200 στο /cours σε 45 δευτερόλεπτα. Μία μόνο καινοτομία: οι αγκύλες [1m] μετά τον επιλογέα. Μετατρέπουν ένα στιγμιαίο διάνυσμα (μια τιμή ανά σειρά) σε διάνυσμα εύρους (μια λίστα χρονολογημένων τιμών ανά σειρά). Κάνε κλικ στην καρτέλα Graph: αρνείται αυτό το ερώτημα (Error executing query και μετά invalid expression type "range vector" for range query, must be Scalar or instant Vector). Δεν σχεδιάζουμε ένα ακατέργαστο εύρος, το δίνουμε σε μια συνάρτηση. Αυτό είναι το βήμα 8. Γύρνα στο Table.
Ένα λεπτό περιέχει τέσσερα διαστήματα των 15 δευτερολέπτων, άρα τέσσερις ή πέντε καταγραφές ανάλογα με τη στιγμή που εκτελείς το ερώτημα σε σχέση με τον κύκλο scrape. Αν ξαναπληκτρολογήσεις το ερώτημα πολλές φορές, θα δεις μερικές φορές πέντε γραμμές. Οι ημερομηνίες @1789505608.199 είναι δευτερόλεπτα από την 1η Ιανουαρίου 1970 (η ώρα Unix)· η καρτέλα Graph τις μετατρέπει σε αναγνώσιμες ώρες.
rate(http_requetes_total{route="/cours", code="200"}[1m])Τι ζητά το ερώτημα: την ταχύτητα με την οποία αυξήθηκε αυτός ο μετρητής, σε μονάδες ανά δευτερόλεπτο, υπολογισμένη στο εύρος του τελευταίου λεπτού.
{code="200", instance="api:8000", job="api", methode="GET", route="/cours", service="api"} 3.7779456864749545Τι να κοιτάξεις: ξανά μία μόνο τιμή, και το όνομα http_requetes_total εξαφανίστηκε από τα άγκιστρα: δεν είναι πλέον μετρητής, είναι ταχύτητα. 3,78 αιτήματα ανά δευτερόλεπτο. Έλεγξε με το βήμα 7: 170 αιτήματα σε 45 δευτερόλεπτα κάνουν 3,78. Μία μόνο καινοτομία: η συνάρτηση rate(), που παίρνει ένα διάνυσμα εύρους και επιστρέφει ένα στιγμιαίο διάνυσμα. Είναι η πιο σημαντική συνάρτηση της PromQL: ένας ακατέργαστος μετρητής δεν διαβάζεται ποτέ, η κλίση του ναι.
rate χωρίς εύροςrate(http_requetes_total{route="/cours", code="200"})Error executing query
invalid parameter "query": 1:6: parse error: expected type range vector in call to function "rate", got instant vectorΤι να κοιτάξεις: expected type range vector … got instant vector. Έδωσες στο rate ένα στιγμιαίο διάνυσμα (μια τιμή), ήθελε ένα εύρος (πολλές χρονολογημένες τιμές): χωρίς δύο σημεία, δεν υπάρχει κλίση. Η σωστή κίνηση είναι το βήμα 8, με [1m]. Θα διαβάσεις αυτό το μήνυμα συχνά· σημαίνει πάντα «λείπει το […]».
Μια παραλλαγή που δεν δίνει σφάλμα αλλά δεν επιστρέφει τίποτα:
rate(http_requetes_total{route="/cours", code="200"}[10s])Empty query resultΤι να κοιτάξεις: ένα εύρος 10 δευτερολέπτων περιέχει στην καλύτερη περίπτωση μία μόνο καταγραφή (απέχουν 15 s), και το rate χρειάζεται τουλάχιστον δύο. Πρακτικός κανόνας: το εύρος πρέπει να είναι τουλάχιστον δύο φορές το scrape_interval, άρα [30s] ελάχιστο εδώ· [1m] ή [5m] στην πραγματική ζωή.
sum by (code) (rate(http_requetes_total{route="/cours"}[1m]))Τι ζητά το ερώτημα: την ταχύτητα όλων των σειρών του /cours (όλοι οι κωδικοί), αθροισμένη κρατώντας μόνο το label code.
{code="200"} 3.7779456864749545
{code="500"} 0Τι να κοιτάξεις: δύο γραμμές, και μένει μόνο ένα label μέσα στα άγκιστρα: το code. Όλα τα άλλα (instance, job, methode, route, service) έλιωσαν στο άθροισμα. Μία μόνο καινοτομία: το sum by (code) (…), η συνάθροιση του βήματος 6 με μια πρόταση by. Οι παρενθέσεις γύρω από το code είναι υποχρεωτικές (το sum by code (…) δίνει parse error: … expected "("). Η γραμμή {code="500"} 0 αξίζει μια ματιά: ισούται με μηδέν γιατί κανένα 500 δεν έπεσε στο /cours στο τελευταίο λεπτό (το API παράγει περίπου ένα κάθε δώδεκα δευτερόλεπτα, όλες οι διαδρομές μαζί). Το μηδέν δεν είναι απουσία: η σειρά υπάρχει, απλώς έχει μηδενική κλίση. Αν εκτελέσεις .\labo.ps1 casser erreurs (ή ./labo.sh casser erreurs) και ξαναπληκτρολογήσεις αυτό το ερώτημα ένα λεπτό αργότερα, η δεύτερη γραμμή ανεβαίνει· το reparer την κατεβάζει ξανά.
Αυτό το αποτέλεσμα είναι το παραδοτέο σου: οι δύο γραμμές, και μια φράση για καθεμία («το /cours σερβίρει 3,78 αποκρίσεις 200 ανά δευτερόλεπτο»· «καμία απόκριση 500 στο /cours στο τελευταίο λεπτό»).
Ξαναπιάσε τα δέκα ερωτήματα από μνήμης, με τη σειρά, και τσέκαρε:
api_info επιστρέφει 1 σειρά, τιμή 1, με version="1.0.0" στα labels.up επιστρέφει 8 σειρές, όλες στο 1 (αλλιώς, ένας στόχος έπεσε: το etat θα σου πει ποιος).up{job="api"} επιστρέφει 1 σειρά.up{job=api} επιστρέφει parse error … expected string· το up{job="API"} επιστρέφει Empty query result.up{job=~"a.*"} επιστρέφει 3 σειρές: alloy, api, alertmanager.count(up) επιστρέφει {} 8.http_requetes_total{route="/cours", code="200"}[1m] επιστρέφει 1 σειρά με 4 ή 5 χρονολογημένες τιμές @….rate(…[1m]) επιστρέφει 1 τιμή, μεταξύ 3 και 4 αιτημάτων ανά δευτερόλεπτο στο εργαστήριο του μαθήματος.rate(…) χωρίς αγκύλες επιστρέφει expected type range vector … got instant vector.sum by (code) (rate(http_requetes_total{route="/cours"}[1m])) επιστρέφει 2 σειρές, {code="200"} και {code="500"}.Τίποτα να καθαρίσεις: δεν δημιούργησες τίποτα. Το etat εμφανίζει πάντα 8/8 cibles up και τον ίδιο αριθμό σειρών στη μνήμη, με διαφορά μερικών δεκάδων (ο Prometheus συνεχίζει να συλλέγει).
Empty query result στο api_info ή στο http_requetes_total. Το API δεν έχει διαβαστεί ακόμη, ή είναι σταματημένο. etat: αν το labo-api είναι Exited, reparer· αν όλα είναι healthy, περίμενε 15 δευτερόλεπτα (ένα scrape) και ξαναεκτέλεσε.
Το up επιστρέφει 7 σειρές αντί για 8, όλες στο 1. Ένα job εξαφανίστηκε από τη ρύθμιση, όχι ένας στόχος που έπεσε (θα ήταν στο 0). Πήγαινε στο Status → Target health και σύγκρινε με τα οκτώ jobs του βήματος 2. Αν τροποποίησες το prometheus/prometheus.yml, επανάφερε το αρχικό αρχείο (git checkout prometheus/prometheus.yml) και docker compose restart prometheus.
Το βήμα 7 επιστρέφει Empty query result. Οι 4 τιμές του εύρους πρέπει να υπάρχουν: αμέσως μετά το demarrer, πρέπει να περιμένεις ένα λεπτό. Αν το API μόλις επανεκκίνησε (reparer), το ίδιο.
Το βήμα 8 επιστρέφει αρνητική ή τεράστια τιμή. Αδύνατο κατ' αρχήν: το rate διαχειρίζεται τους μηδενισμούς του μετρητή. Αν το δεις, έλεγξε ότι δεν πληκτρολόγησες rate σε ένα gauge (requetes_en_cours): δεν προκαλεί σφάλμα αλλά δεν έχει κανένα νόημα.
Η καρτέλα Graph μένει κενή. Για ένα ερώτημα εύρους (βήμα 7), είναι φυσιολογικό: το Graph εμφανίζει invalid expression type "range vector". Για τα άλλα, διεύρυνε την περίοδο (κουμπί -/+ πάνω από το γράφημα): αμέσως μετά την εκκίνηση, υπάρχουν μόνο λίγα λεπτά δεδομένων.
parse error που δεν αναγνωρίζεις. Πήγαινε στη θέση γραμμή:στήλη του μηνύματος. Μέτρησε τις παρενθέσεις σου: το sum by (code) (rate(x[1m])) έχει τρία ζεύγη. Έλεγξε κάθε εισαγωγικό: πηγαίνουν ανά δύο, ίσια ("), ποτέ τυπογραφικά (“ ”)· μια αντιγραφή-επικόλληση από επεξεργαστή κειμένου τα αντικαθιστά μερικές φορές.