Changes

Openshift - HAproxy metrics EN

62,584 bytes added, 12:57, 19 November 2019
Created page with "Openshift - HAproxy metrics HU :File:ClipCapIt-190807-102633.PNG =Preliminary= ===Overview=== In OpenShift 3.11, the default router is t..."
[[Openshift - HAproxy metrics|Openshift - HAproxy metrics HU]]
:[[File:ClipCapIt-190807-102633.PNG]]



=Preliminary=
===Overview===
In OpenShift 3.11, the default router is the HAProxy template router. This is based on the of the 'openshift3 / ose-haproxy-router' image. The image runs two components inside a container, one is HAproxy itself and the other is the router controller, the tempalte-router-plugin, which maintains the HAproxy configuration. The router POD listens on the host machine's network interface and directs external requests to the appropriate pod within the OpenShfit cluster. Unlike Kubernetes Ingress, OS routers do not have to run on all nodes, they are installed just on dedicated nodes, so external traffic must be directed to the public IP address of these nodes.


HAProxy provides standard prometheus metrics through the router's Kubernetes service. The problem is that only a very small part of the metrics provided by HAproxy can be used meaningfully. Unfortunately, the http response-time metric is calculated from the average of the last 1024 requests, making it completely unsuitable for real-time monitoring purposes.

Real requests and responses information is only provided in the HAProxy acces-log, and only in debug mode, but it is really detailed, it contains all parameters of the requests / responses. These logs can be used to generate prometheus metrics using multiple tools (e.g. grok-exporter, Fluentd, Logstash).
<br>
<br>

===Haproxy main configuration===
The HAproxy configuration file is located in '/var/lib/haproxy/conf/haproxy.config'. It contains all the services that are configured in the router.

/var/lib/haproxy/conf/haproxy.config
<pre>
global
...
log 127.0.0.1 local1 debug

backend config:
##-------------- app level backends ----------------
....
backend be_edge_http:mynamespace:test-app-service
mode http
...
server pod:test-app-57574c8466-qbtg8:test-app-service:172.17.0.12:8080 172.17.0.12:8080 cookie babb614345c3670472f80ce0105a11b0 weight 256
</pre>
The backends that belong to the route are listed in the '''app level backends''' section. You can see in the example that the backend called test-app-service is available at 172.17.0.12:8080.

<br>

===Git repository===
:[[File:ClipCapIt-190810-104337.PNG|300px]]

All the required resources used in the following implementation are available in the following git repository: https://github.com/berkiadam/haproxy-metrics


<br>
<br>
===Http test alkalmazás===
A http forgalom generálásához készítettem egy teszt applikációt, amivel tetszőleges válaszidejű és http válasz kódú kéréseket lehet generálni. A forrás elérhető itt: https://github.com/berkiadam/haproxy-metrics/tree/master/test-app

A Kubernetes fájlok pedig megtalálhatók a git repository gyökerében.

Telepítés után itt érhető el az alkalmazás:
* http://test-app-service-mynamespace.192.168.42.185.nip.io/test/slowresponse/< késleltetés millisecundumban >
* http://test-app-service-mynamespace.192.168.42.185.nip.io/test/slowresponse/< késleltetés millisecundumban >/< http response kód>





<br>
<br>
<br>
<br>

=HAproxy metrika végpont használata=
A HAproxy-nak van beépített metrika végpontja, ahol alapértelmezetten Prometheus szabványú metrikákat szolgáltat (tud még CSV-tis). Az így nyert metrikák nagy része nem igazán értelmes metrikák. Két olyan metrikát tudunk innen kinyerni, ami mindenképpen figyelendő prometheus-ban, ezek a hibás és 200-as válaszok számlálói backend-ekre lebontva.

A metrikákat lekérdező végpont (metrics) alapból be van kapcsolva. Ezt ki lehet kapcsolni, de attól még gyűjteni fogja a metrikákat a HAProxy. A HAproxy pod két komponensből épül fel. Az egyik maga a HAproxy a másik a router-controller ami a HAproxy konfigurációját kezeli. A metrikákat mind a két komponensről 5 másodpercenként gyűjti be a metrika kezelő. A metrikák között vannak frontend és backend metrikák is külön szolgáltatásonként összegyűjtve.

:[[File:ClipCapIt-190808-094455.PNG|600px]]


<br>
==Metrikák lekérdezése==
A metrikák lekérdezésére két lehetőség van.
# felhasználó név + jelszó használata: Basic authentikácóval a /metrics végpontot meghívva le lehet kérdezni a metrikákat.
# RBAC szabályok definiálása a megfelelő serviceAccount -hoz: Gépi feldolgozásra (Prometheus) lehetőség van rá, hogy RBAC szabályokkal engedélyezzük egy adott service-account-nak hogy lekérdezze a metrikákat.

<br>
===User + jelszó alapú lekérdezés ===

Felhasználó név alapú lekérdezés esetén az alapértelmezett metrika URL az alábbi:
<pre>
http://<user>:<password>@<router_IP>:<STATS_PORT>/metrics
</pre>


A metrika user, jelszó és port a router-hez tartozó service definíciójában van. Ehhez elsőként meg kell keresni a router-hez tartozó service-t:
<pre>
# kubectl get svc -n default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
router ClusterIP 172.30.130.191 <none> 80/TCP,443/TCP,1936/TCP 4d
</pre>
Láthatjuk, hogy az '''1936'''-os porton is hallgatózik, ez a metrika végpontjának a portja.


Most nézzünk bele a service definíciójába, hogy megkapjuk a user és pass-t is:
<source lang="C++">
# kubectl get svc router -n default -o yaml
apiVersion: v1
kind: Service
metadata:
annotations:
prometheus.openshift.io/password: 4v9a7ucfMi
prometheus.openshift.io/username: admin
...
</source>


Ennek függvényében, a node IP címét felhasználva, (minishfit IP) a metrika URL az alábbi: http://admin:4v9a7ucfMi@192.168.42.64:1936/metrics (ezt böngészőben nem lehet meghívni, mert nem ismerik ezt a formátumot)
<pre>
# curl admin:4v9a7ucfMi@192.168.42.64:1936/metrics


# HELP apiserver_audit_event_total Counter of audit events generated and sent to the audit backend.
# TYPE apiserver_audit_event_total counter
apiserver_audit_event_total 0
# HELP apiserver_client_certificate_expiration_seconds Distribution of the remaining lifetime on the certificate used to authenticate a request.
# TYPE apiserver_client_certificate_expiration_seconds histogram
apiserver_client_certificate_expiration_seconds_bucket{le="0"} 0
apiserver_client_certificate_expiration_seconds_bucket{le="21600"} 0
...
</pre>

<br>
<br>


===ServiceAccount alapú lekérdezés===
Lehetőség van rá, hogy ne csak basic authentication-el kérdezzük le a HAproxy metrikákat, hanem RBAC szabályokkal.


Készíteni kell egy olyan '''ClusterRole'''-t ami megengedi hogy lekérdezést kezdeményezzünk a '''routers/metrics''' végponton. Ezt később össze fogjuk rendelni a prometheus-t futtató serviceAccount-al.
<br>
'''cr-prometheus-server-route.yaml'''
<source lang="C++">
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app: prometheus
component: server
release: prometheus
name: prometheus-server-route
rules:
- apiGroups:
- route.openshift.io
resources:
- routers/metrics
verbs:
- get
</source>
<br>


Második lépésben készíteni kell egy '''ClusterRoleBinding'''-ot, ami összerendeli a prometheus-hoz tartozó serviceAccount-ot a fenit role-al.
<br>
'''crb-prometheus-server-route.yaml'''
<source lang="C++">
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
app: prometheus
chart: prometheus-8.14.0
component: server
release: prometheus
name: prometheus-server-route
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: prometheus-server-route
subjects:
- kind: ServiceAccount
name: prometheus-server
namespace: mynamespace
</source>
Hozzuk létre a két feni objektumot:
<pre>
# kubectl apply -f cr-prometheus-server-route.yaml
clusterrole.rbac.authorization.k8s.io/prometheus-server-route created

# kubectl apply -f crb-prometheus-server-route.yaml
clusterrolebinding.rbac.authorization.k8s.io/prometheus-server-route created
</pre>


<br>
<br>

==Prometheus integráció==


Keressük meg router-ekhez tartozó '''Endpoint''' definíciót. Ezt fogjuk hozzáadni a prometheus konfigurációhoz, amivel az összes router pod-ot meg tudja majd keresni. Olyan endpoint-t fogunk keresni, aminek a neve '''router''' és van '''1936-tcp''' nevű portja, amin keresztül le fogjuk kérdezni az alapértelmezett metrika végponton (/metrics) keresztül a HAproxy metrikákat.
<pre>
# kubectl get Endpoints router -n default -o yaml
apiVersion: v1
kind: Endpoints
metadata:
creationTimestamp: "2019-07-09T20:26:25Z"
labels:
router: router
name: router
subsets:
ports:
- name: 1936-tcp
</pre>

<br>
<br>
A Promethues konfigurációba hozzá kell adni egy új '''target'''-et, amiben '''kubernetes_sd_configs'''-al keressük meg azt az Endpoint-ot, aminek a neve '''router''' és van '''1936-tcp''' nevű portja.
<pre>
- job_name: 'openshift-router'
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt
server_name: router.default.svc
bearer_token_file: /var/run/secrets/kubernetes.io/scraper/token
kubernetes_sd_configs:
- role: endpoints
namespaces:
names:
- default
relabel_configs:
- source_labels: [__meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
action: keep
regex: router;1936-tcp
</pre>


Frissítsük a Prometheus konfigurációját tartozó '''ConfigMap'''-et.
<pre>
# kubectl apply -f cm-prometheus-server-haproxy.yaml
configmap/prometheus-server created
</pre>


Nézzük meg a promethues pod-ban, hogy a konfiguráció újra lett e már töltve:
<pre>
# kubectl describe pod prometheus-server-75c9d576c9-gjlcr -n mynamespace
Containers:
prometheus-server-configmap-reload:
...
prometheus-server:
</pre>


A Promethues pod-ban futó side card konténerbe nézzük meg a logokat (ez felelős a konfiguráció újra töltéséért). Látnunk kell, hogy újra töltötte a konfigurációt.
<pre>
# kubectl logs -c prometheus-server-configmap-reload prometheus-server-75c9d576c9-gjlcr -n mynamespace
2019/07/22 19:49:40 Watching directory: "/etc/config"
2019/07/22 20:25:36 config map updated
2019/07/22 20:25:36 successfully triggered reload
</pre>


Ugyan ezt látni kell, ha a promethues server konténerének a logját nézzük:
<pre>
# kubectl logs -c prometheus-server prometheus-server-75c9d576c9-gjlcr -n mynamespace
...
level=info ts=2019-07-22T20:25:36.016Z caller=main.go:730 msg="Loading configuration file" filename=/etc/config/prometheus.yml
</pre>


Ezek után, ha megnyitjuk a Promethues target oldalt a konzolon: http://mon.192.168.42.185.nip.io/targets
:[[File:ClipCapIt-190722-233253.PNG]]
Ha több router lenne a klaszterben, akkor azok mind feltűnnének itt mint külön végpontok.
<br>

<br>

<br>
==Metrika fajták==
http://people.redhat.com/jrivera/openshift-docs_preview/openshift-origin/glusterfs-review/architecture/networking/haproxy-router.html<br>

Első ránézésre két értelmes metrikát találhatunk a HAproxy repertoárjában. Ezek az alábbiak:

<br>
===haproxy_server_http_responses_total===
Megmutatja backend-enként hogy hángy 200-as és hány 500-as http státuszú választ adott egy adott service. Itt nincs pod alapú bontás. Sajnos a http 300 és 400-as hibákról nem kapunk információt. Ezeket is majd az access log-ból tudjuk kinyerni
<br>
<br>
Generáljuk a teszt alkalmazással egy 200-as választ. Látunk kell, hogy a counter egyel megnő: http://test-app-service-mynamespace.192.168.42.185.nip.io/test/slowresponse/1/200
<pre>
haproxy_server_http_responses_total{code="2xx",job="openshift-router",namespace="mynamespace",pod="test-app",route="test-app-service",service="test-app-service"} 1
</pre>

<br>
Generáljuk a teszt alkalmazással egy 500-as választ. Látunk kell, hogy a counter egyel megnő: http://test-app-service-mynamespace.192.168.42.185.nip.io/test/slowresponse/1/500
<pre>
haproxy_server_http_responses_total{code="5xx",job="openshift-router",namespace="mynamespace",pod="test-app",route="test-app-service",service="test-app-service"} 1
</pre>

<br>
<br>

===haproxy_server_response_errors_total===

<pre>
haproxy_server_response_errors_total{instance="192.168.122.223:1936",job="openshift-router",namespace="mynamespace",pod="test-app-57574c8466-pvcsg",route="test-app-service",server="172.17.0.17:8080",service="test-app-service"}
</pre>

<br>
<br>

<br>
<br>

<br>
<br>

=Metrikák gyűjtése logokból=
==Bevezető==
Az a feladat, hogy a HAproxy access logját egy log értelmezővel feldolgozzuk, és abból Prometheus metrikákat képezzünk, amit egy végponton elérhetővé kell tenni a Prometheus számára. Erre a '''grok-exporter''' eszközt fogjuk használni, ami ezt egy személyben meg tudja valósítani. A logokat fájlból vagy stdin-ről képes felolvasni, és abból képes metrikákat képezni. A grok-exporter a logokat egy mellé csomagolt rsyslog szerveren keresztül fogja megkapni a HAproxy-tól. Az rsyslog lerakja fájlba a logokat, ahonnan a grok-exporter fel fogja tudni olvasni. A grok-exporter promethues metrikákká alakítja a logokat.


:[[File:ClipCapIt-190812-132113.PNG]]



* Létre kell hozni egy olyan docker image-t a grok-exporter-ből, amiben van rsyslog is. A konténernek tudnia kell root-ként futtatni az rsyslogot, ez extra openShfit konfigurációt igényel.
* A grok-exporter image-t úgy kell futtatni OpenShfit-en, hogy egyrészt a grok-exporter konfigurációja egy ConfigMap legyen, másrészt az rsyslog munkaterülete egy OpenSfhit volume legyen.
* A grok-exporter deployment-hez létre kell hozni egy ClasterIP típusú service-t, ami képes load-balancing-ot végezni a grok-exporter pod-ok között.
* A routerekben (HAproxy) be kell állítani, hogy debug módban logoljanak, és az így előálló access log-ot küldje el a grok-exporter service 514-es portjára.
* A grok-exporter pod-ban futó rsyslog szerver a megkapott HAproxy access logokat egyrészt lerakja a '''/var/log/messages''' fájlba (emptyDir típusú volume) másrészt elküldi az '''stdout'''-ra is.
* Az stdout-ra írt logokat a dokcer-log-driver fel fogja szedni, és továbbítani fogja a centralizált log architektúra felé.
* A grok-exporter program olvassa a '''/var/log/messages''' fájlt, a benne lévő HAproxy access-log-okból prometheus metrikákat képez.
* A promethues konfigurációját úgy kell kialakítani, hogy '''kubernetes_sd_configs''' segítségével közvetlen a grok-proxy pod-okat szólítsa meg a metrika begyűjtéséhez, ne a service-en keresztül menjen, hogy kikerülje a load-balancing-ot, mivel minden pod-ot le kell kérdezni.

<br>

==HAproxy log struktúra==
https://www.haproxy.com/blog/introduction-to-haproxy-logging/
<br>
A HAproxy az alábbi log struktúrát produkálja minden egyes request-response párhoz:
<pre>
Aug 6 20:53:30 192.168.122.223 haproxy[39]: 192.168.42.1:50708 [06/Aug/2019:20:53:30.267] public be_edge_http:mynamespace:test-app-service/pod:test-app-57574c8466-qbtg8:test-app-service:172.17.0.12:8080 1/0/0/321/321 200 135 - - --NI 2/2/0/1/0 0/0 "GET /test/slowresponse/1 HTTP/1.1"
</pre>

<pre>
Field Format Extract from the example above
1 Log writing date: Aug 6 20:53:30
2 HAproxy instant name: 192.168.122.223
3 process_name '[' pid ']:' haproxy[39]:
4 client_ip ':' client_port 192.168.42.1:50708
5 '[' request_date ']' [06/Aug/2019:20:53:30.267]
6 frontend_name public
7 backend_name '/' server_name be_edge_http:mynamespace:test-app-service....
8 TR '/' Tw '/' Tc '/' Tr '/' Ta* 1/0/0/321/321
9 status_code 200
10 bytes_read* 135
11 captured_request_cookie -
12 captured_response_cookie -
13 termination_state --NI
14 actconn '/' feconn '/' beconn '/' srv_conn '/' retries* 1/1/1/1/0
15 srv_queue '/' backend_queue 0/0
16 '"' http_request '"' "GET /test/slowresponse/1 HTTP/1.1"
</pre>

* '''Tq''': total time in milliseconds spent waiting for the client to send a full HTTP request, not counting data
* '''Tw''': total time in milliseconds spent waiting in the various queues
* '''Tc''': total time in milliseconds spent waiting for the connection to establish to the final server, including retries
* '''Tr''': total time in milliseconds spent waiting for the server to send a full HTTP response, not counting data
* '''Tt''': total time in milliseconds elapsed between the accept and the last close. It covers all possible processings

:[[File:ClipCapIt-190810-110305.PNG|600px]]


<br>
* '''actconn''': total number of concurrent connections on the process when the session was logged
* '''feconn''': total number of concurrent connections on the frontend when the session was logged
* '''beconn''': total number of concurrent connections handled by the backend when the session was logged
* '''srv conn''': total number of concurrent connections still active on the server when the session was logged
* '''retries''': number of connection retries experienced by this session when trying to connect to the server
<br>

Teljes specifikáció: https://github.com/berkiadam/haproxy-metrics/blob/master/ha-proxy-log-structure.pdf

<br>
<br>

==grok-exporter bemutatása==
A grok-exporter egy olyan eszköz, ami logokat képes reguláris kifejezések alapján feldolgozni, amiből elő tudja állítani a 4 alapvető prometheus metrika típust:
* gauge
* counter
* histogram
* kvantilis

A metrikához tetszőleges számú címkét tudunk beállítani a parszolt logsor elemeit felhasználva. A grok-exporter a '''logstash-grok''' implementációján alapul, a logstash-ben definiált patterneket és függvényeket használja.

Részletes dokumentáció itt: <br>
https://github.com/fstab/grok_exporter/blob/master/CONFIG.md<br>

<br>
A grok-exporter háromféle inputot tud kezelni:
* '''file''': mi ezt fogjuk használni, az rsyslog által írt log-ot fogja feldolgozni.
* '''webhook''': Ez a megoldás is használható lenne, ha rsyslog szervernek a logstash-t használnánk fel, majd a logstash '''http-output''' plugin-el webhook-on tovább küldenénk a grok-exporter-nek.
* '''stdin''': Az rsyslog-al az stdin is használható lenne. Ehhez az ''' omprog''' programot kell használni. Az omprog képes stdin-en átadni egy programnak azt amit rsyslog socket-ről olvas. A programot az omprog újra fogja indítani ha már nem fut. https://www.rsyslog.com/doc/v8-stable/configuration/modules/omprog.html



<br>
===Alternatív megoldások===
'''Fluentd''':<br>
A '''fluentd'''-vel is megoldható a feladat. Ehhez három fluentd plugin-t kell használni (ezt nem probáltam ki):
* fluent-plugin-rewrite-tag-filter
* fluent-plugin-prometheus
* fluent-plugin-record-modifier.
https://medium.com/@tom.fawcett/extracting-useful-duration-metrics-from-haproxy-prometheus-fluentd-2be9832ff702
<br>

'''mtail''':<br>
A másik alternatíva a google '''mtail''' projektje lenne, ami állítólag erőforrás hatékonyabban lenne képes feldolgozni a logsorokat mint a grok motor. <br>
https://github.com/google/mtail


<br>

===Config fájl===
A grok-exporter konfigurációja a '''/etc/grok_exporter/config.yml''' fájlban van. 5 részre osztható

* global:
* input: Megmondja, hogy honnan és hogyan olvassa be a logokat. Lehet stdin, file és webhook. Mi a file inputot fogjuk használni.
* grok: A grok patternek helye. A Docker image-ben ez a /grok/patterns mappa lesz.
* metrics: Ez a legfontosabb rész. Itt kell egyenként definiálni a metrikákat és a hozzá tartozó reguláris kifejezést (grok patternek formájában)
* server: Milyen porton hallgatózzon a szerver.

<br>
====Metrics====
A metrikákat metrika típusonként kell definiálni. A négy alapvető prometheus metrika típus támogatott: '''Gauge, Counter, Histogram, Summary''' (kvantilis)
A típus alatt meg kell adni:
* name: ezen a néven fog szerepelni a metrika
* help: Ez lesz a metrika help szövege.
* match: Itt kell leírni reguláris kifejezés szerűen a logsor szerkezetét amire illeszkedni kell a metrikáknak. Itt grok patterneket lehet használni, amik előre definiáltak:
** '''ALAP grok patternek''': https://github.com/logstash-plugins/logstash-patterns-core/blob/master/patterns/grok-patterns
** '''HAROXY patterns''': https://github.com/logstash-plugins/logstash-patterns-core/blob/master/patterns/haproxy

* label: A találati csoportoknak lehet nevet adni. A névre lehet hivatkozni a label szekcióban, amiből létre fog hozni egy olyan címkét, aminek az értéke a parsolt adat lesz.

<br>
====match====
A match-ben fel kell írni egy reguláris kifejezést grok építő kockákból. Azt feltételezzük, hogy az egyes elemeket a log-ban egy szünet választja el. Minden egyes építő kocka '''%{PATTERN-NÉV}''' alakú, ahol a PATTERN-NÉV-nek léteznie kell valamelyik pattern gyűjteményben. A legáltalánosabb típus a '''%{DATA}''', ami egy tetszőleges adatstruktúrára vonatkozik, ami nem tartalmaz szünetet. Több olyan pattern is van, ami több elemi pattern-ből van kombinálva. Ha azt akarjuk hogy a pattern-el leírt reguláris kifejezésből találati csoport is képződjön, nevet kell adni a patternek, pl:
<pre>
%{DATA:this_is_the_name}
</pre>
Ekkor pattern-el megtalált mező értéke bele fog kerülni a '''this_is_the_name''' változóba, amire lehet hivatkozni a metrika értékének a meghatározásánál illetve a címke legyártásnál.

<br>
====labels====
A labels szekcióban nevesített pattern-ekre lehet hivatkozni. Ekkor az adott logsorból parszolt mező értékét fogja adni a definiált címkének. Pl. a '''%{DATA:this_is_the_name}''' pattern használata esetén felírhatjuk a következő címkét: <br>
<pre>
mylabel: '{{.this_is_the_name}}'
</pre>
Ekkor, ha a %{DATA} pattern által leírt mező értéke 'myvalue' volt, akkor a metrikára rá fog kerülni egy ilyen címke: '''{mylabel="myvalue"}'''<br>
Nézzünk egy példát: <br>
Adott a következő log sor:
<pre>
30.07.2016 14:37:03 adam 1.5
</pre>
És a következő metrika szabály a grok config-ban:
<source lang="C++">
metrics:
- type: counter
name: grok_example_lines_total
help: Example counter metric with labels.
match: '%{DATE} %{TIME} %{USER:user} %{NUMBER}'
labels:
user: '{{.user}}'
</source>
A metrika neve '''grok_example_lines_total''' lesz. A metrikára az alábbi lesz:
<pre>
# HELP Example counter metric with labels.
# TYPE grok_example_lines_total counter
grok_example_lines_total{user="adam"} 1
</pre>

<br>
====Metrika értékének meghatározása====
A counter típusú metrikánál nincs szükség a metrika értékét meghatározni, mert ott azt fogja számolni, hogy hány illeszkedő logsort talált. Ezzel ellentétben az összes többi típusnál meg kell adni, hogy mit tekintünk az értéknek. Ezt a '''value''' szekcióban kell megadni, ahol be kell hivatkozni egy nevesített grok pattern-t a match szekcióból ugyan úgy Go templét formában, ahogy a címkéket is definiáltuk. Pl adott a következő két logsor:
<pre>
30.07.2016 14:37:03 adam 1
30.07.2016 14:37:03 adam 5
</pre>
És erre az alábbi histogram-ot definiáljuk, ami két vödörből áll, az 1-es és 2-es vödörből:
<source lang="C++">
metrics:
- type: histogram
name: grok_example_lines
help: Example counter metric with labels.
match: '%{DATE} %{TIME} %{USER:user} %{NUMBER:val}'
buckets: [1,2]
value: '{{.val}}'
labels:
user: '{{.user}}'
</source>
Ekkor az alábbi metrika fog ebből keletkezni:
<pre>
# HELP Example counter metric with labels.
# TYPE grok_example_lines histogram
grok_example_lines_bucket{user="adam", le="1"} 1
grok_example_lines_bucket{user="adam", le="2"} 1
grok_example_lines_bucket{user="adam", le="+Inf"} 2
grok_example_lines_count{user="adam"} 2
grok_example_lines_sum
</pre>

<br>
====Függvények====
A metrika értékekre (value) és a címkékre is lehet függvényeket alkalmazni. A függvények a '''0.2.7'''-es grok-exporter verzió kell vagy újabb. Lehet használni string manipulációs függvényeket és aritmetikai függvényeket is. A következő két argumentumú aritmetikai függvények támogatottak:
* add
* subtract
* multiply
* divide
A függvény szintaxisa az alábbi: <pre>{{FÜGGVÉNY_NEVE ATTR1 ATTR2}}</pre> ahol az ATTR1 és az ATTR2 is lehet egy patternből nyert érték vagy egy természetes szám. A patternből nyert értékeket ugyan úgy .-al kell kiírni. Pl ha a multiply függvényt használjuk a fenti példában:
<source lang="C++">
value: "{{multiply .val 1000}}"
</source>
Akkor a metrika az alábbira módosul:
<pre>
# HELP Example counter metric with labels.
# TYPE grok_example_lines histogram
grok_example_lines_bucket{user="adam", le="1"} 0
grok_example_lines_bucket{user="adam", le="2"} 0
grok_example_lines_bucket{user="adam", le="+Inf"} 2
...
</pre>
Mivel a két érték 1000 ill 5000-re fog módosulni, ezért mindkettő az infinite kategóriába fog esni.

<br>
<br>

==grok config fájl elkészítése==
Össze kell állítani egy olyan grok pattern-t ami illeszkedik a HAproxy access-log sorokra, és képes kigyűjteni az összes számunkra fontos attribútumot:

* válasz kiszolgálásának szumma ideje
* haproxy instance id
* openshfit service névtér
* pod név

<br>
Példa haproxy access-log:
<pre>
Aug 6 20:53:30 192.168.122.223 haproxy[39]: 192.168.42.1:50708 [06/Aug/2019:20:53:30.267] public be_edge_http:mynamespace:test-app-service/pod:test-app-57574c8466-qbtg8:test-app-service:172.17.0.12:8080 1/0/0/321/321 200 135 - - --NI 2/2/0/1/0 0/0 "GET /test/slowresponse/1 HTTP/1.1"
</pre>

A config.yml fájlban egy olyan hisztogramot fogunk definiálni, ami a requestek teljes kiszolgálásának a válaszidejét tartalmazza. Ez egy klasszikus histogram, általában a következő vödröket szokta tartalmazni (másodpercben):
<pre>
[0.1, 0.2, 0.4, 1, 3, 8, 20, 60, 120]
</pre>
A válaszidőket tartalmazó metrikáknak konvenció szerint a következő a neve: '''<prefix>_http_request_duration_seconds'''


'''config.yml'''
<source lang="C++">
global:
config_version: 2
input:
type: file
path: /var/log/messages
readall: true
grok:
patterns_dir: ./patterns
metrics:
- type: histogram
name: haproxy_http_request_duration_seconds
help: The request durations of the applications running in openshift that have route defined.
match: '%{SYSLOGTIMESTAMP:timestamp} %{DATA:Aloha_name} %{DATA:haproxy_process}: %{DATA:client_ip}:%{INT:client_port} \[%{HAPROXYDATE:accept_date}\] %{DATA:frontend_name} %{DATA}:%{DATA:namespace}:%{DATA:service}/pod:%{DATA:pod_name}:%{DATA} %{INT:Tq}/%{INT:Tw}/%{INT:Tc}/%{INT:Tr}/%{INT:Tt} %{INT:http_status_code} %{NOTSPACE:bytes_read} %{DATA} %{DATA} %{DATA} %{INT:actconn}/%{INT:feconn}/%{INT:beconn}/%{INT:srvconn}/%{NOTSPACE:retries} %{INT:srv_queue}/%{INT:backend_queue} "%{WORD:Method} %{URIPATHPARAM:request} HTTP/%{NUMBER:http_version}"'
value: "{{divide .Tr 1000}}"
buckets: [0.1, 0.2, 0.4, 1, 3, 8, 20, 60, 120]
labels:
haproxy: '{{.haproxy_process}}'
namespace: '{{.namespace}}'
service: '{{.service}}'
pod_name: '{{.pod_name}}'
server:
port: 9144
</source>


* '''type: file''' -> fájlból olvassuk a logokat
* '''path: /var/log/messages''' -> Az rsyslog szerver a /var/log/messages mappába írja a logokat alapértelmezetten
* '''readall: true''' -> mindig az egész log fájlt beolvassa. Ezt csak tesztelésre szabad így használni, éles környezetben, ezt mindig false-ra kell állítani.
* '''patterns_dir: ./patterns''' -> A docker image-ben itt találhatók a pattern definíciók
* <pre>value: "{{divide .Tt 1000}}"</pre> A kiszolgálási idő a HAproxy log-ban miliszekundumban van, ezt konvertálni kell szekundumra.
* '''port: 9144''' -> Ezen a porton lesz elérhető a /metrics végpont.
<br>
{{warning|nem szabad éles környezetben elfelejteni a '''readall''' értékét '''false'''-ra állítani, mert nagyon lerontja a hatásfokot}}
<br>
<br>
===Online grok teszter===
Több online grok tesztelő eszköz is létezik. Ezekkel nagyon hatékonyan össze lehet állatni a szükséges grok pattern-t: https://grokdebug.herokuapp.com/

:[[File:ClipCapIt-190808-170333.PNG]]

<br>
<br>

==docker image elkészítése==
A grok-exporter docker image elérhető a docker hub-on több változatban is. A gond velük csak az, hogy nem tartalmazzák az rsyslog szervert, amire szükségünk van, hogy a HAproxy közvetlen el tudja küldeni a logokat a grok-exporter podokank. <br>
docker-hub link: https://hub.docker.com/r/palobo/grok_exporter <br>

<br>
A második gond az, hogy ubuntu base image-en alapulnak, ahol nagyon nehéz megoldani, hogy az rsyslog az stdout-ra is logoljon, ami ahhoz kell, hogy a Kubernetets centralizált log gyűjtője is megkapja a HAproxy logokat, így mind a monitoring mind centralizált logging-ot ki tudjuk szolgálni. Ezrét az eredeti Dockerfile-t portolni fogjuk '''centos 7'''-re, valamint ki fogjuk egészíteni az rsyslog szerver telepítésével is.
<br>
Az összes szükséges fájl elérhető a git-hub-on: https://github.com/berkiadam/haproxy-metrics/tree/master/grok-exporter-centos<br>
Készítettem egy ubuntu alapú megoldást is, ami az eredeti docker-hub-os megoldás kiegészítése, ez szintén megtalálható a git-hub-on a '''grok-exporter-ubuntu mappában'''. A howot további részében mi mindig a centos verziót fogjuk használni.
<br>
<br>
===Dockerfile===
A '''palobo/grok_exporter''' Dockerfile-ból fogunk kiindulni, de ki fogjuk azt egészíteni az rsyslog installációval és portoljuk centos-re: https://github.com/berkiadam/haproxy-metrics/tree/master/grok-exporter-centos
<br>

➲[[File:Grok-exporter-docker-build.zip|Dokcer image buld-hez szükséges összes fájl letöltése]]

<br>
Dockerfile
<source lang="C++">
FROM centos:7
LABEL Maintainer="Adam Berki <https://github.com/berkiadam/>"
LABEL Name="grok_exporter"
LABEL Version="0.2.8"

ENV PID_DIR /tmp/pidDir
ENV GROK_ARCH="grok_exporter-0.2.8.linux-amd64"
ENV GROK_VERSION="v0.2.8"

USER root

RUN yum -y install rsyslog wget unzip && \
yum clean all && \
echo "" > /etc/rsyslog.d/listen.conf && \
mkdir -p ${PID_DIR} && \
chmod 777 ${PID_DIR} \
&& wget https://github.com/fstab/grok_exporter/releases/download/$GROK_VERSION/$GROK_ARCH.zip \
&& unzip $GROK_ARCH.zip \
&& mv $GROK_ARCH /grok \
&& rm $GROK_ARCH.zip \
&& yum -y remove wget unzip \
&& rm -fr /var/lib/apt/lists/*

RUN mkdir -p /etc/grok_exporter && ln -sf /etc/grok_exporter/config.yml /grok/

COPY rsyslog.conf /etc/rsyslog.conf

EXPOSE 514/tcp 514/udp 9144/tcp
WORKDIR /grok

CMD sh -c "nohup /usr/sbin/rsyslogd -i ${PID_DIR}/pid -n &" && ./grok_exporter -config /grok/config.yml
</source>
{{note|Fontos, hogy a grok-exporter-ből legalább a 0.2.7-es verziót használjuk, abban jelent meg először a függvények kezelése}}

<br>
<br>
Az '''rsyslog.conf''' fájlt az alábbiakkal kell kiegészíteni, ami lehetővé teszi, hogy az 514-es porton UDP-n és TCP-n is fogadni tudjon logot (részletesen lásd a fenti ZIP-ben), valamint, hogy minden log-t írjon ki az stdout-ra és a /var/log/messages fájlba is.
<pre>
$ModLoad omstdout.so

# provides UDP syslog reception
module(load="imudp")
input(type="imudp" port="514")

# provides TCP syslog reception
module(load="imtcp")
input(type="imtcp" port="514")
...
*.* :omstdout: # send everything to stdout
*.*;mail.none;authpriv.none;cron.none /var/log/messages
</pre>

<br>

===Lokális build és lokális teszt===
Első körben a lokális docker démonnal fogjuk build-elni a docker image-t, hogy tudjuk lokálisan futtatni tesztelés céljából. Később majd ezt a minishfit VM-en fogjuk build-elni, mert csak onnan fogjuk tudni feltölteni a minishfit docker registry-be. Mivel majd egy távoli (nem a lokális) docker repository-ba akarjuk majd az image-t felölteni, fontos, hogy betartsuk az elnevezési konvenciókat:
<pre>
<repo URL>:<repo port>/<névtér>/<image-név>:<tag>
</pre>

Mi a minishift-en futó docker-registry-be fogjuk feltölteni majd az image-t, ezért fontos, hogy a minishfit-docker-registry címét és portját adjuk meg és azt az OpenShift névteret, ahova az image kerül majd.
<pre>
# docker build -t 172.30.1.1:5000/default/grok_exporter:1.1.0 .
</pre>


Az elkészült image-t natív, lokális docker futtatással tesztelhetjük. Készítsünk egy haproxy teszt log fájlt ('''haproxy.log''') amibe helyezzük el az alábbi tartalmat. Ezt fogjuk feldogoztatni a grok-exporter-el, mintha a haproxy-tól kapta volna.
<pre>
Aug 6 20:53:30 192.168.122.223 haproxy[39]: 192.168.42.1:50708 [06/Aug/2019:20:53:30.267] public be_edge_http:mynamespace:test-app-service/pod:test-app-57574c8466-qbtg8:test-app-service:172.17.0.12:8080 1/0/0/321/321 200 135 - - --NI 2/2/0/1/0 0/0 "GET /test/slowresponse/1 HTTP/1.1"
Aug 6 20:53:30 192.168.122.223 haproxy[39]: 192.168.42.1:50708 [06/Aug/2019:20:53:30.588] public be_edge_http:mynamespace:test-app-service/pod:test-app-57574c8466-qbtg8:test-app-service:172.17.0.12:8080 53/0/0/11/63 404 539 - - --VN 2/2/0/1/0 0/0 "GET /favicon.ico HTTP/1.1"
</pre>

<br>
Helyezzük el ugyan ebbe a mappába a fent elkészített grok '''config.yml''' fájlt is. A config.yml fájlban állítsuk át az input.path értékét '''/grok/haproxy.log-ra''', hogy a grok-exporter a teszt logfájlunkat dolgozza fel. Majd egy '''docker run''' paranccsal indítsuk el az alábbi módon:
<pre>
# docker run -d -p 9144:9144 -p 514:514 -v $(pwd)/config.yml:/etc/grok_exporter/config.yml -v $(pwd)/haproxy.log:/grok/haproxy.log --name grok 172.30.1.1:5000/default/grok_exporter:1.1.0
</pre>

<br>
Az indulás után ellenőrizzük a log-ban hogy a grok és az rsyslog valóban elindultak e:
<pre>
# docker logs grok
* Starting enhanced syslogd rsyslogd
...done.
Starting server on http://7854f3a9fe76:9144/metrics
</pre>

<br>
Ekkor a böngészőben a http://localhost:9144/metrics címen elérhetőek a metrikák:
<pre>
...
# HELP haproxy_http_request_duration_seconds_bucket The request duration of the applications running in openshift that have route defined.
# TYPE haproxy_http_request_duration_seconds_bucket histogram
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[39]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="0.1"} 1
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[39]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="0.2"} 1
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[39]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="0.4"} 2
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[39]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="1"} 2
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[39]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="3"} 2
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[39]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="8"} 2
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[39]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="20"} 2
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[39]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="60"} 2
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[39]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="120"} 2
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[39]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="+Inf"} 2
haproxy_http_request_duration_seconds_bucket_sum{haproxy="haproxy[39]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service"} 0.384
haproxy_http_request_duration_seconds_bucket_count{haproxy="haproxy[39]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service"} 2
</pre>

<br>
<br>
Második lépésként ellenőrizzük le, hogy a docker konténerben futó '''rsyslog''' képes e távoli log üzeneteket fogadni. Ehhez elsőként lépjünk be a konténerbe és figyeljük a /var/log/messages fájlt:
<pre>
# docker exec -it grok /bin/bash
root@a27e5b5f2de7:/grok# tail -f /var/log/messages
Aug 8 14:44:37 a27e5b5f2de7 rsyslogd: [origin software="rsyslogd" swVersion="8.16.0" x-pid="21" x-info="http://www.rsyslog.com"] start
Aug 8 14:44:37 a27e5b5f2de7 rsyslogd-2039: Could not open output pipe '/dev/xconsole':: Permission denied [v8.16.0 try http://www.rsyslog.com/e/2039 ]
Aug 8 14:44:37 a27e5b5f2de7 rsyslogd: rsyslogd's groupid changed to 107
Aug 8 14:44:37 a27e5b5f2de7 rsyslogd: rsyslogd's userid changed to 105
Aug 8 14:44:38 a27e5b5f2de7 rsyslogd-2007: action 'action 9' suspended, next retry is Thu Aug 8 14:45:08 2019 [v8.16.0 try http://www.rsyslog.com/e/2007 ]
</pre>

<br>
Most az anya gépről a '''logger''' paranccsal küldjünk egy log üzenetet a konténerben futó rsyslog szervernek az 514-es porton:
<pre>
# logger -n localhost -P 514 -T "this is the message"
</pre>
(T=TCP)

Ekkor a log meg kell jelenjen a '''syslog''' fájlban:
<pre>
Aug 8 16:54:25 dell adam this is the message
</pre>

Törölhetjük a lokális docker konténert.

<br>
<br>
===Távoli build===
Fel szeretnénk tölteni az elkészült docker image-t a minishfit saját registry-ébe. Ehhez az image-t a minishfit VM lokális docker démonjával kell build-elni, mert csak onnan ehet hozzáférni a minishfit registry-hez. <br>
Részletek itt: [[Openshift_basics#Minishfit_docker_registry|➲Image push a minishift docker registriy-be]]


Ahhoz hogy az '''admin''' user-nek legyen joga feltölteni az image-t a minisfhit registry-be a '''default''' névtérbe, ahol a router is fut, szüksége hogy megkapja a '''cluster-admin''' jogot. Fontos, hogy '''-u system:admin''' -al lépjünk be ne csupán ''oc login'''-al, mert akkor nem lesz jogunk az '''oc adm''' parancsot kiadni. Ugyan így fogunk hivatkozni a user-re is az '''--as''' paraméterben.
<pre>
# oc login -u system:admin
# oc adm policy add-cluster-role-to-user cluster-admin admin --as=system:admin
cluster role "cluster-admin" added: "admin"
</pre>
{{note|Ha ezt a hibát kapjuk '''Error from server (NotFound): the server could not find the requested resource''', ez azt jelenti, hogy az '''oc''' kliens programunk régebbi mint a OpenShift verzió}}


Irányítsuk át a lokális docker kliensünket a minisfhit VM-en futó docker démonra, majd jelentkezzünk be a minishift docker registry-be:
<pre>
# minishift docker-env
# eval $(minishift docker-env)

# oc login
Username: admin
Password: <admin>

# docker login -u admin -p $(oc whoami -t) $(minishift openshift registry)
Login Succeeded
</pre>


Build-eljük le a minishfit VM-en is:
<pre>
# docker build -t 172.30.1.1:5000/default/grok_exporter:1.1.0 .
</pre>

Lépjünk be a minisfhit docker registry-be majd adjuk ki a '''push''' parancsot.
<pre>
# docker push 172.30.1.1:5000/default/grok_exporter:1.1.0
</pre>


<br>
<br>

==Kubernetes objektumok==

A grok-exporter-hez létre fogunk hozni egy serviceAccount-ot, egy deployment-et, egy service-t és egy comifMap-et ahol a grok-exporter konfigurációját fogjuk tárolni. Ezen felül módosítani fogjuk az anyuid nevű '''SecurityContextConstraints''' objektumot, mivel az rsyslog szerver miatt a grok-exporter konténernek privilegizált módban kell futnia.

* haproxy-exporter service account
* cm-haproxy-exporter.yaml
* deployment-haproxy-exporter.yaml
* svc-haproxy-exporter-service.yaml
* scc-anyuid.yaml

A teljes konfigurációt itt tölthetjük le: [[File:Haproxy-kubernetes-objects.zip]], vagy megtalálható az alábbi git repository-ban: https://github.com/berkiadam/haproxy-metrics

<br>
<br>
===ServiceAccount létrehozása===
A haproxy-exporter-nek szüksége van egy saját serviceAccount-ra, amire engedélyezni fogjuk a privilegizált (root) konténer futtatást. Erre az rsyslog szervernek van szüksége.

<pre>
# kubectl create serviceaccount haproxy-exporter -n default
serviceaccount/haproxy-exporter created
</pre>

Ennek a hatáséra a következő serviceAccount definíció jött létre:
<source lang="C++">
apiVersion: v1
imagePullSecrets:
- name: haproxy-exporter-dockercfg-67x4j
kind: ServiceAccount
metadata:
creationTimestamp: "2019-08-10T12:27:52Z"
name: haproxy-exporter
namespace: default
resourceVersion: "837500"
selfLink: /api/v1/namespaces/default/serviceaccounts/haproxy-exporter
uid: 45a82935-bb6a-11e9-9175-525400efb4ec
secrets:
- name: haproxy-exporter-token-8svkx
- name: haproxy-exporter-dockercfg-67x4j
</source>


===Objektumok definiálása===

<br>
'''cm-haproxy-exporter.yaml'''
<source lang="C++">
apiVersion: v1
data:
config.yml: |
...grok-exporter config.yml...
kind: ConfigMap
metadata:
name: haproxy-exporter
namespace: default
</source>

<br>
'''deployment-haproxy-exporter.yaml'''
<source lang="C++">
apiVersion: apps/v1
kind: Deployment
metadata:
...
name: haproxy-exporter
namespace: default
spec:
...
template:
...
spec:
containers:
- image: '172.30.1.1:5000/default/grok_exporter:1.1.0'
imagePullPolicy: IfNotPresent
name: grok-exporter
ports:
- containerPort: 9144
protocol: TCP
- containerPort: 514
protocol: TCP
volumeMounts:
- mountPath: /etc/grok_exporter/
name: config-volume
- mountPath: /var/log
name: log-dir
...
volumes:
- name: config-volume
configMap:
defaultMode: 420
name: haproxy-exporter
- name: log-dir
emptyDir: {}
</source>

<br>
'''svc-haproxy-exporter-service.yaml'''
<source lang="C++">
apiVersion: v1
kind: Service
metadata:
labels:
run: haproxy-exporter
name: haproxy-exporter-service
namespace: default
spec:
ports:
- name: port-1
port: 9144
protocol: TCP
targetPort: 9144
- name: port-2
port: 514
protocol: TCP
targetPort: 514
- name: port-3
port: 514
protocol: UDP
targetPort: 514
selector:
run: haproxy-exporter
sessionAffinity: None
type: ClusterIP
</source>


===SecurityContextConstraints===

<br>
A grok-exporter-ben lévő rsyslog szerver miatt fontos, hogy a konténer privilegizált üzemmódban fusson. Ehhez az '''anyuid''' nevű SCC-be fel kell venni a haproxy-exporter-hez tartozó serviceAcccount-t, hogy engedélyezzük a root nevében futtatást. Tehát nincs szükség a privileged SCC-re, mert a konténer elve root-ként szeretne indulni. Más különben az rsyslog nem lesz képes létrehozni a socket-eket.
{{warning|Az SCC-k kezeléshez nem elég a developer user mynamespace-re kapott admin rolebindg-ja. Ehhez admin-ként kell bejelentkezni: oc login -u system:admin}}

<br><br>

Listázzuk ki a SCC-ket:
<pre>
# kubectl get SecurityContextConstraints
NAME PRIV CAPS SELINUX RUNASUSER FSGROUP SUPGROUP PRIORITY READONLYROOTFS VOLUMES
anyuid false [] MustRunAs RunAsAny RunAsAny RunAsAny 10 false [configMap downwardAPI emptyDir persistentVolumeClaim
...
privileged true [*] RunAsAny RunAsAny RunAsAny RunAsAny <none> false [*]
...
</pre>
<br>
<br>


Az '''anyuid''' SCC-hez a users szekcióban kell hozzáadni a '''serviceAccount'''-ot az alábbi formában:
- system:serviceaccount:<névtér>:<serviceAccount>
<br>
'''scc-anyuid.yaml'''
<source lang="C++">
kind: SecurityContextConstraints
metadata:
name: anyuid
...
users:
- system:serviceaccount:default:haproxy-exporter
...
</source>


Mivel ez már egy létező '''scc''' és csak egy apró módosítást akarunk rajta eszközölni, ezért helyben is szerkeszthetjük:
<pre>
# oc edit scc anyuid
securitycontextconstraints.security.openshift.io/anyuid edited
</pre>


<br>

===objektumok létrehozása===

<pre>
# kubectl apply -f cm-haproxy-exporter.yaml
configmap/haproxy-exporter created
</pre>


<pre>
# kubectl apply -f deployment-haproxy-exporter.yaml
deployment.apps/haproxy-exporter created

# kubectl rollout status deployment haproxy-exporter -n default
deployment "haproxy-exporter" successfully rolled out
</pre>



<pre>
# kubectl apply -f svc-haproxy-exporter-service.yaml
</pre>

<br>

===Tesztelés===

Keressük meg a haproxy-exporter pod-ot majd nézzük meg a pod logját:
<pre>
# kubectl logs haproxy-exporter-744d84f5df-9fj9m -n default
* Starting enhanced syslogd rsyslogd
...done.
Starting server on http://haproxy-exporter-744d84f5df-9fj9m:9144/metrics
</pre>


Majd lépjünk be a konténerbe és teszteljük re az rsyslog működését:
<pre>
# kubectl exec -it haproxy-exporter-647d7dfcdf-gbgrg /bin/bash -n default
</pre>

Majd a '''logger''' paranccsal küldjünk egy log üzenetet az rsyslog-nak.
<pre>
logger -n localhost -P 514 -T "this is the message"
</pre>

Most listázzuk ki a /var/log/messages mappa tartalmát:
<pre>
# cat messages
Aug 28 19:16:09 localhost root: this is the message
</pre>

Lépjünk ki a konténerből, és kérjük le megint a pod logjait, hogy megnézzük, hogy az stdout-ra is kirakta e a logot:
<pre>
# kubectl logs haproxy-exporter-647d7dfcdf-gbgrg -n default
Starting server on http://haproxy-exporter-647d7dfcdf-gbgrg:9144/metrics
2019-08-28T19:16:09+00:00 localhost root: this is the message
</pre>

<br>
<br>

==HAproxy konfiguráció==


===Környezeti változók beállítása===
A HAproxy-nak be fogjuk állítani környezeti változónk keresztül a haporxy-exporter pod-ban futó rsyslog szerver címét. Ehhez első lépésben listázzuk a haproxy-exporter service-t.
<pre>
# kubectl get svc -n default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
haproxy-exporter-service ClusterIP 172.30.213.183 <none> 9144/TCP,514/TCP,514/UDP 15s
..
</pre>


A HAproxy az rsyslog szerver címét a '''ROUTER_SYSLOG_ADDRESS''' nevű környezeti változóban tárolja (Deployment része). Ezt futásidőben át tudjuk írni az '''oc set env''' paranccsal. A változó átírása után a pod magától újra fog indulni.
<pre>
# oc set env dc/myrouter ROUTER_SYSLOG_ADDRESS=172.30.213.183 -n default
deploymentconfig.apps.openshift.io/myrouter updated
</pre>
{{note|Minishift-en a router konténerben nem működik a service-ek nevére a névfeloldás, mivel nem a Kubernetes klaszter DNS szerver címe van beállítva, hanem a minishfit VM. Ezért nem tehetünk mást, mint hogy a service IP címét adjuk meg a neve helyett. OpenShift környezetben a service nevét adjuk meg}}

Majd második lépésben állítsuk át debug-ra a logszintet a HAproxy-ban, mert csak debug szinten van access-log.
<pre>
# oc set env dc/myrouter ROUTER_LOG_LEVEL=debug -n default
deploymentconfig.apps.openshift.io/myrouter updated
</pre>
{{warning|Teljesítmény tesztel meg kell vizsgálni hogy mekkora plusz terhelést jelent a haproxy-nak ha debug módban fut}}


<br>
A fenti két környezeti változó módosításának a hatására a router konténerben a '''/var/lib/haproxy/conf/haproxy.config''' fájlban a HAproxy konfigurációja az alábbira változott:

<pre>
# kubectl exec -it myrouter-5-hf5cs /bin/bash -n default
$ cat /var/lib/haproxy/conf/haproxy.config
global
..
log 172.30.82.232 local1 debug
</pre>
A lényeg, hogy megjelent a log paraméternél a haproxy-exporter service címe és a '''debug''' log szint.
<br>
<br>
<br>

===rsyslog szerver tesztelése===
Generáljunk egy kis forgalmat a haproxy-n keresztül, majd lépjünk vissza a haproxy-exporter konténerbe, és listázzuk a messages fájl tartalmát.
<pre>
# kubectl exec -it haproxy-exporter-744d84f5df-9fj9m /bin/bash -n default
#
# tail -f /var/log/messages

Aug 9 12:52:17 192.168.122.223 haproxy[24]: Proxy fe_sni stopped (FE: 0 conns, BE: 0 conns).
Aug 9 12:52:17 192.168.122.223 haproxy[24]: Proxy be_no_sni stopped (FE: 0 conns, BE: 0 conns).
Aug 9 12:52:17 192.168.122.223 haproxy[24]: Proxy fe_no_sni stopped (FE: 0 conns, BE: 0 conns).
Aug 9 12:52:17 192.168.122.223 haproxy[24]: Proxy openshift_default stopped (FE: 0 conns, BE: 1 conns).
Aug 9 12:52:17 192.168.122.223 haproxy[24]: Proxy be_edge_http:dsp:nginx-route stopped (FE: 0 conns, BE: 0 conns).
Aug 9 12:52:17 192.168.122.223 haproxy[24]: Proxy be_http:mynamespace:prometheus-alertmanager-jv69s stopped (FE: 0 conns, BE: 0 conns).
Aug 9 12:52:17 192.168.122.223 haproxy[24]: Proxy be_http:mynamespace:prometheus-server-2z6zc stopped (FE: 0 conns, BE: 0 conns).
Aug 9 12:52:17 192.168.122.223 haproxy[24]: Proxy be_edge_http:mynamespace:test-app-service stopped (FE: 0 conns, BE: 0 conns).
Aug 9 12:52:17 192.168.122.223 haproxy[24]: Proxy be_edge_http:myproject:nginx-route stopped (FE: 0 conns, BE: 0 conns).
Aug 9 12:52:17 192.168.122.223 haproxy[32]: 127.0.0.1:43720 [09/Aug/2019:12:52:17.361] public openshift_default/<NOSRV> 1/-1/-1/-1/0 503 3278 - - SC-- 1/1/0/0/0 0/0 "HEAD / HTTP/1.1"
</pre>
Ha a logjait megnézzük a haproxy-exporter pod-nak, ugyan ezt kell ássuk.



<br>
<br>
http://test-app-service-mynamespace.192.168.42.185.nip.io/test/slowresponse/3000


<pre>
...
Aug 9 12:57:21 192.168.122.223 haproxy[32]: 192.168.42.1:48266 [09/Aug/2019:12:57:20.636] public be_edge_http:mynamespace:test-app-service/pod:test-app-57574c8466-qbtg8:test-app-service:172.17.0.17:8080 1/0/12/428/440 200 135 - - --II 2/2/0/1/0 0/0 "GET /test/slowresponse/1 HTTP/1.1"
Aug 9 12:57:28 192.168.122.223 haproxy[32]: 192.168.42.1:48266 [09/Aug/2019:12:57:21.075] public be_edge_http:mynamespace:test-app-service/pod:test-app-57574c8466-qbtg8:test-app-service:172.17.0.17:8080 4334/0/0/3021/7354 200 135 - - --VN 2/2/0/1/0 0/0 "GET /test/slowresponse/3000 HTTP/1.1"
Aug 9 12:57:28 192.168.122.223 haproxy[32]: 192.168.42.1:48266 [09/Aug/2019:12:57:28.430] public be_edge_http:mynamespace:test-app-service/pod:test-app-57574c8466-qbtg8:test-app-service:172.17.0.17:8080 90/0/0/100/189 404 539 - - --VN 2/2/0/1/0 0/0 "GET /favicon.ico HTTP/1.1"
Aug 9 12:57:35 192.168.122.223 haproxy[32]: 192.168.42.1:48268 [09/Aug/2019:12:57:20.648] public public/<NOSRV> -1/-1/-1/-1/15002 408 212 - - cR-- 2/2/0/0/0 0/0 "<BADREQ>"
</pre>

===grok-exporter tesztelése===
Kérjük le a grok-exporter metrikákat a http://<pod IP>:9144/metrics címen. Vagy a haproxy-exporter pod-ban localhost hívással, vagy bármelyik másik pod-ban a haporxy-exporter pod IP címét felhasználva. Az alábbi példában a test-app-ba lépek be. Látnunk kell a metrikák között a '''haproxy_http_request_duration_seconds_bucket''' histogramot.
<pre>
# kubectl exec -it test-app-57574c8466-qbtg8 /bin/bash -n mynamespace
$
$ curl http://172.30.213.183:9144/metrics

...
# HELP haproxy_http_request_duration_seconds The request durations of the applications running in openshift that have route defined.
# TYPE haproxy_http_request_duration_seconds histogram
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[32]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="0.1"} 0
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[32]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="0.2"} 1
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[32]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="0.4"} 1
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[32]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="1"} 2
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[32]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="3"} 2
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[32]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="8"} 3
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[32]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="20"} 3
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[32]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="60"} 3
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[32]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="120"} 3
haproxy_http_request_duration_seconds_bucket{haproxy="haproxy[32]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service",le="+Inf"} 3
haproxy_http_request_duration_seconds_sum{haproxy="haproxy[32]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service"} 7.9830000000000005
haproxy_http_request_duration_seconds_count{haproxy="haproxy[32]",namespace="mynamespace",pod_name="test-app-57574c8466-qbtg8",service="test-app-service"} 3
</pre>


<br>
<br>

==Prometheus beállítások==

===Statikus konfiguráció===
<source lang="C++">
- job_name: grok-exporter
scrape_interval: 5s
metrics_path: /metrics
static_configs:
- targets: ['grok-exporter-service.default:9144']
</source>


===Pod szintű adatgyűjtés===

Azt szeretnénk, hogy a haproxy-exporter podok skálázhatóak legyenek. Ehhez az kell, hogy a prometheus ne a service-en keresztül kérje le a metrikát (mert akkor a service loadbalancing-ot csinál) hanem közvetlenül a pod-okat szólítsa meg. Ehhez az kell, hogy a prometheus a Kubernetes API-n keresztül kérje le a haproxy-epxporter-hez tartozó '''Endpoint'''-ot, ami tartalmazza a service-hez tartozó podok ip címének a listáját. Ehhez a prometheus '''kubernetes_sd_configs''' elemét fogjuk használni. (Ennek előfeltétele, hogy a Prometheus képes legyen kommunikálni a Kubernetes API-val. Részleteket lásd itt: [[Prometheus_on_Kubernetes]])


A '''kubernetes_sd_configs''' használatakor mindig egy adott Kubernetes objektum listát kérünk le a szerverről (node, service, endpoints, pod), majd a kapott listából megkeressük azt az erőforrást, amiből be akarjuk gyűjteni a metrikákat. Ezt úgy tesszük meg hogy a '''relabel_configs''' szekcióban majd szűrőfeltételeket írunk föl az adott Kubernetes resource címkéire. Jelen esetben a haproxy-exporter-hez tartozó Endpoint-ot akarjuk megtalálni, mert az alapján a Prometheus meg tudja találni az összes a service-hez tartozó pod-ot. Tehát a címék alapján meg akarjuk majd találni egyrészt azt az endpoint-ot, amit '''haproxy-exporter-service'''-nak hívnak, ezen felül van egy '''metrics''' portja, amin keresztül a Prometheus képes lekérni a metrikákat. Az alapértelmezett URL a '''/metrics''', tehát ezt külön nem kell definiálni, a grok-exporter is ezt használja.
<pre>
# kubectl get Endpoints haproxy-exporter-service -n default -o yaml
apiVersion: v1
kind: Endpoints
metadata:
name: haproxy-exporter-service
...
ports:
- name: log-udp
port: 514
protocol: UDP
- name: metrics
port: 9144
protocol: TCP
- name: log-tcp
port: 514
protocol: TCP
</pre>


Két címkét keresünk az Endpoints listában:
* __meta_kubernetes_endpoint_port_name: metrics -> 9144
* __meta_kubernetes_service_name: haproxy-exporter-service
<br>
A proetheus.yaml-t, vagyis a prometheus.yaml-t leíró config-map-et az alábbiakkal kell kiegészíteni:
<source lang="C++">
- job_name: haproxy-exporter
scheme: http
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
server_name: router.default.svc
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- role: endpoints
namespaces:
names:
- default
relabel_configs:
- source_labels: [__meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
action: keep
regex: haproxy-exporter-service;metrics
</source>

Töltsük újra a configMap-et:
<pre>
# kubectl apply -f cm-prometheus-server-haproxy-full.yaml
</pre>

Majd várjuk meg, hogy a Prometheus újra olvassa a konfigurációs fájlt:
<pre>
# kubectl logs -f -c prometheus-server prometheus-server-75c9d576c9-gjlcr -n mynamespace
...
level=info ts=2019-07-22T20:25:36.016Z caller=main.go:730 msg="Loading configuration file" filename=/etc/config/prometheus.yml
</pre>

<br>
Majd a http://mon.192.168.42.185.nip.io/targets képernyőn ellenőrizzük, hogy eléri e a Prometheus a haproxy-exporter target-et:
:[[File:ClipCapIt-190809-164445.PNG]]



<br>
<br>

===haproxy-exporter skálázása===

<pre>
# kubectl scale deployment haproxy-exporter --replicas=2 -n default
deployment.extensions/haproxy-exporter scaled
</pre>


<pre>
# kubectl get deployment haproxy-exporter -n default
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
haproxy-exporter 2 2 2 2 3h
</pre>


<br>
:[[File:ClipCapIt-190809-174825.PNG]]


<br>
<br>

==Metrika fajták==


===haproxy_http_request_duration_seconds_bucket===
type: histogram
<br>
===haproxy_http_request_duration_seconds_bucket_count===
type: counter<br>
Az összes darabszáma az adott histogramba eső request-ek számának

<pre>
haproxy_http_request_duration_seconds_count{haproxy="haproxy[39]",job="haproxy-exporter",namespace="mynamespace",pod_name="test-app",service="test-app-service"} 5
</pre>
<br>
<br>
===haproxy_http_request_duration_seconds_sum===
type: counter<br>
A válaszidők idejének összege az adott hisztogramban. Az előző példa alapján összesen 5 kérés jött, és a kiszolgálási idő összeadva 13 s volt.

<pre>
haproxy_http_request_duration_seconds_sum{haproxy="haproxy[39]",job="haproxy-exporter",namespace="mynamespace",pod_name="test-app",service="test-app-service"} 13.663
</pre>

<br>
<br>


<br>
<br>

=OpenShift router + rsyslog=

OpenShift 3.11-től kezdődően lehet olyan router-t definiálni, hogy az OpenShfit automatikusan elindít egy side car rsyslog konténert a router pod-ban és be is állítja, hogy a HAproxy egy socket-en keresztül (emptyDir volume) elküldje a logokat az rsyslog szervernek, ami az stdout-ra írja azokat alapértelmezetten. Az rsyslog konfigurációja egy configMap-ban van.

:[[File:ClipCapIt-190810-164907.PNG]]

<br>
A router-t syslogserverrel a '''--extended-logging''' kapcsolóval hozhatjuk létre az '''oc adm router''' paranccsal.
<pre>
# oc adm router myrouter --extended-logging -n default
info: password for stats user admin has been set to O6S6Ao3wTX
--> Creating router myrouter ...
configmap "rsyslog-config" created
warning: serviceaccounts "router" already exists
clusterrolebinding.authorization.openshift.io "router-myrouter-role" created
deploymentconfig.apps.openshift.io "myrouter" created
service "myrouter" created
--> Success
</pre>


<br>
Kapcsoljuk be a debug szintet a HAproxy-ban:
<pre>
# oc set env dc/myrouter ROUTER_LOG_LEVEL=debug -n default
deploymentconfig.apps.openshift.io/myrouter updated
</pre>



<br>
Két konténer van az új router pod-ban:
<pre>
# kubectl describe pod/myrouter-2-bps5v -n default
..
Containers:
router:
Image: openshift/origin-haproxy-router:v3.11.0
Mounts:
/var/lib/rsyslog from rsyslog-socket (rw)
...
syslog:
Image: openshift/origin-haproxy-router:v3.11.0
Mounts:
/etc/rsyslog from rsyslog-config (rw)
/var/lib/rsyslog from rsyslog-socket (rw)
...
rsyslog-config:
Type: ConfigMap (a volume populated by a ConfigMap)
Name: rsyslog-config
Optional: false
rsyslog-socket:
Type: EmptyDir (a temporary directory that shares a pod's lifetime)
Medium:
SizeLimit: <unset>
</pre>


Láthatjuk, hogy mind két konténerbe mount-olva van '''/var/lib/rsyslog/''' mappa. A HAproxy konfigurációs fájljában ide fogja létrehozni az rsyslog.sock fájlt.

<br>
<br>
===router konténer===
Ha belépünk a router konténerbe, láthatjuk, hogy már fel is nyalta a konfiguráció:
<pre>
# kubectl exec -it myrouter-2-bps5v /bin/bash -n default -c router
bash-4.2$ cat /var/lib/haproxy/conf/haproxy.config
global
...
log /var/lib/rsyslog/rsyslog.sock local1 debug
...
defaults
...
option httplog --> Enable logging of HTTP request, session state and timers

...
backend be_edge_http:mynamespace:test-app-service
</pre>

<br>
<br>
===rsyslog konténer===

<pre>
# kubectl exec -it myrouter-2-bps5v /bin/bash -n default -c syslog

$ cat /etc/rsyslog/rsyslog.conf
$ModLoad imuxsock
$SystemLogSocketName /var/lib/rsyslog/rsyslog.sock
$ModLoad omstdout.so
*.* :omstdout:
</pre>

<br>
Ha át akarjuk konfigurálni az rsyslog-ot hogy küldje el a logokat pl a logstash-nek, akkor csak a configMap-et kell átírni. Alapértelmezetten csak az stdout-ra írja amit kap.
<pre>
# kubectl get cm rsyslog-config -n default -o yaml
apiVersion: v1
data:
rsyslog.conf: |
$ModLoad imuxsock
$SystemLogSocketName /var/lib/rsyslog/rsyslog.sock
$ModLoad omstdout.so
*.* :omstdout:
kind: ConfigMap
metadata:
name: rsyslog-config
namespace: default
</pre>


<br>
<br>
===HAproxy logok nézegetése===
<pre>
# kubectl logs -f myrouter-2-bps5v -c syslog
</pre>