GitLab: моніторинг – Prometheus, метрики, та Grafana dashboard

Автор |  11/03/2023

Отже, продовжуємо нашу подорож з міграцією GitLab до себе в Kubernetes. Див. попередні частини:

В цілому – все працює, і вже готуємося переносити репозиторії, останнє ( 🙂 ) що залишилось зробити – це моніторинг.

GitLab та Prometheus

Документація по моніторингу GitLab:

У нас в Kubernetes кластері розгорнутий свій Prometehus за допомогою Kube Prometheus Stack (далі – KPS) та його Prometheus Operator.

GitLab вміє запускати власний Prometheus, якому відразу налаштовує збір метрик з усіх подів та сервісів, які мають аннотацію gitlab.com/prometheus_scrape=true.

Крім того, всі поди та сервіси мають аннотацію prometheus.io/scrape=true, але KPS не вміє працювати з аннотаціями, див. документацію:

The prometheus operator does not support annotation-based discovery of services

Тож маємо два варіанти збору метрик:

  • вимкнути Promethus самого GitLab, та через ServiceMonitor-и збирати метрики з компонентів відразу в KPS Prometheus – але тоді всім компонентам доведеться включати ServiceMonitor (і не всі їх мають, тож деякі доведеться додавати вручну через окремі маніфести)
  • або ми можемо лишити “вбудований” Prometehus, в якому вже все налаштовано, і через Prometheus federation просто збирати потрібні нам метрики до KPS Prometheus

В другому випадку ми будемо витрачати зайві ресурси на роботу додаткового Prometheus, але знімаємо з себе необхідність в додатковій конфігурації чартів самого GitLab та Prometheus з KPS.

Налаштування Prometheus federation

Документація – Federation.

Спочатку, перевіримо налаштування Prometheus самого GitLab – чи є метрики і які є джоби.

Знаходимо Prometheus Service:

[simterm]

$ kk -n gitlab-cluster-prod get svc gitlab-cluster-prod-prometheus-server
NAME                                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
gitlab-cluster-prod-prometheus-server   ClusterIP   172.20.194.14   <none>        80/TCP    27d

[/simterm]

Відкриваємо до нього доступ:

[simterm]

$ kk -n gitlab-cluster-prod port-forward svc/gitlab-cluster-prod-prometheus-server 9090:80

[/simterm]

Заходимо в браузері на http://localhost:9090, переходимо в Status > Configuration, та дивимось які там є джоби:

Далі ще є job_name: kubernetes-service-endpoints та job_name: kubernetes-services, але ніяких метрик по ним зараз нема:

Джоби prometheus та kubernetes-apiservers нам не потрібні, бо це лише ганяти зайві метрики в KPS Prometheus: в job=prometheus метрики по самому GitLab Prometheus, в job=kubernetes-apiservers – дані по Kubernetes API, які Prometheus KPS збирає і так.

Перевіримо, що метрики в GitLab Prometheus взагалі є. Візьмемо, наприклад, метрику sidekiq_concurrency, див. GitLab Prometheus metrics:

Далі налаштовуємо федерацію – в values Kube Prometheus Stack в блоці prometheus додаємо additionalScrapeConfigs, де вказуємо ім’я джоби, шлях для federation, в params – задаємо match, за яким з GitLab Prometheus вибираємо тільки потрібні нам метрики, а в static_configs задаємо таргет – GitLab Prometheus Service URL:

...
      additionalScrapeConfigs:
        - job_name: 'gitlab_federation'
          honor_labels: true
          metrics_path: '/federate'
          params:
            'match[]':
              - '{job="kubernetes-pods"}'
              - '{job="kubernetes-service-endpoints"}'
              - '{job="kubernetes-services"}'
          static_configs:
          - targets: ["gitlab-cluster-prod-prometheus-server.gitlab-cluster-prod:80"]
...

Деплоїмо, та перевіряємо Targets в KPS Prometheus:

І за хвилину-дві перевіряємо чи пішли метрики до Prometheus KPS:

Метрики GitLab

Тепер, як маємо метрики в нашому Prometheus, давайте поглянемо що взагалі можно і треба моніторити в GitLab.

По-перше – це ресурси Kubernetes, але про них поговоримо, коли будемо створювати власний Grafana dashboard.

Але ще у нас є компоненти самого GitLab, які мають власні метрики:

  • PostgreSQL: моніториться власним експортером
  • KeyDB/Redis: моніториться власним експортером
  • Gitaly: віддає метрики сам, включені по дефолту, див. values
  • Runner: віддає метрики сам, виключені по дефолту, див. values
  • Shell: віддає метрики сам, виключені по дефолту, див. values
  • Registry: віддає метрики сам, виключені по дефолту, див. values
  • Sidekiq: віддає метрики сам, включені по дефолту, див. values
  • Toolbox && backups: нічого по метрикам, див. values
  • Webservice: віддає метрики сам, включені по дефолту, див. values
    • додатково метрики від workhorse, виключені по дефолту, див. values

Також є GitLab Exporter з власними метриками – values.

На сторінці GitLab Prometheus metrics є багато метрик, але не всі, тож має сенс пройтись руками по подах, та переглянути метрики прямо з сервісів.

Наприклад, у Gitaly є метрика gitaly_authentications_total, якої нема в документації.

Відкриваємо доступ до порту з метриками (є у його values):

[simterm]

$ kk -n gitlab-cluster-prod port-forward gitlab-cluster-prod-gitaly-0 9236:9236

[/simterm]

І перевіряємо їх:

[simterm]

$ curl localhost:9236/metrics
# HELP gitaly_authentications_total Counts of of Gitaly request authentication attempts
# TYPE gitaly_authentications_total counter
gitaly_authentications_total{enforced="true",status="ok"} 5511
...

[/simterm]

Далі – список цікавих (на мій власний погляд) метрик з компонентів, які можна буде потім використати для побудови Grafana dashboards per GitLab service та алертів.

Gitaly

Тут метрики:

  • gitaly_authentications_total: Counts of of Gitaly request authentication attempts
  • gitaly_command_signals_received_total: Sum of signals received while shelling out
  • gitaly_connections_total: Total number of connections to Gitaly
  • gitaly_git_protocol_requests_total: Counter of Git protocol requests
  • gitaly_gitlab_api_latency_seconds_bucket: Latency between posting to GitLab’s `/internal/` APIs and receiving a response
  • gitaly_service_client_requests_total: Counter of client requests received by client, call_site, auth version, response code and deadline_type
  • gitaly_supervisor_health_checks_total: Count of Gitaly supervisor health checks
  • grpc_server_handled_total: Total number of RPCs completed on the server, regardless of success or failure
  • grpc_server_handling_seconds_bucket: Histogram of response latency (seconds) of gRPC that had been application-level handled by the server

Runner

Тут метрики:

  • gitlab_runner_api_request_statuses_total: The total number of api requests, partitioned by runner, endpoint and status
  • gitlab_runner_concurrent: The current value of concurrent setting
  • gitlab_runner_errors_total: The number of caught errors
  • gitlab_runner_jobs: The current number of running builds
  • gitlab_runner_limit: The current value of concurrent setting
  • gitlab_runner_request_concurrency: The current number of concurrent requests for a new job
  • gitlab_runner_request_concurrency_exceeded_total: Count of excess requests above the configured request_concurrency limit

Shell

Тут чомусь не працює ендпоінт метрик, не став копатись:

[simterm]

$ kk -n gitlab-cluster-prod port-forward gitlab-cluster-prod-gitlab-shell-744675c985-5t8wn 9122:9122
Forwarding from 127.0.0.1:9122 -> 9122
Forwarding from [::1]:9122 -> 9122
Handling connection for 9122
E0311 09:36:35.695971 3842548 portforward.go:407] an error occurred forwarding 9122 -> 9122: error forwarding port 9122 to pod 51856f9224907d4c1380783e46b13069ef5322ae1f286d4301f90a2ed60483c0, uid : exit status 1: 2023/03/11 07:36:35 socat[10867] E connect(5, AF=2 127.0.0.1:9122, 16): Connection refused

[/simterm]

Registry

Тут метрики:

  • registry_http_in_flight_requests: A gauge of requests currently being served by the http server
  • registry_http_request_duration_seconds_bucket: A histogram of latencies for requests to the http server
  • registry_http_requests_total: A counter for requests to the http server
  • registry_storage_action_seconds_bucket: The number of seconds that the storage action takes
  • registry_storage_rate_limit_total: A counter of requests to the storage driver that hit a rate limit

Sidekiq

Тут метрики:

  • Jobs:
    • sidekiq_jobs_cpu_seconds: Seconds of CPU time to run Sidekiq job
    • sidekiq_jobs_db_seconds: Seconds of DB time to run Sidekiq job
    • sidekiq_jobs_gitaly_seconds: Seconds of Gitaly time to run Sidekiq job
    • sidekiq_jobs_queue_duration_seconds: Duration in seconds that a Sidekiq job was queued before being executed
    • sidekiq_jobs_failed_total: Sidekiq jobs failed
    • sidekiq_jobs_retried_total: Sidekiq jobs retried
    • sidekiq_jobs_interrupted_total: Sidekiq jobs interrupted
    • sidekiq_jobs_dead_total: Sidekiq dead jobs (jobs that have run out of retries)
    • sidekiq_running_jobs: Number of Sidekiq jobs running
    • sidekiq_jobs_processed_total: (from gitlab-exporter)
  • Redis:
    • sidekiq_redis_requests_total: Redis requests during a Sidekiq job execution
    • gitlab_redis_client_exceptions_total: Number of Redis client exceptions, broken down by exception class
  • Queue (from gitlab-exporter):
    • sidekiq_queue_size
    • sidekiq_queue_latency_seconds
  • Misc:
    • sidekiq_concurrency: Maximum number of Sidekiq jobs

Webservice

Трохи про сервіси:

  • Action Cable: is a Rails engine that handles websocket connections – див. Action Cable
  • Puma: is a simple, fast, multi-threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications – див. GitLab Puma

Тут метрики:

  • Database:
    • gitlab_database_transaction_seconds: Time spent in database transactions, in seconds
    • gitlab_sql_duration_seconds: SQL execution time, excluding SCHEMA operations and BEGIN / COMMIT
    • gitlab_transaction_db_count_total: Counter for total number of SQL calls
    • gitlab_database_connection_pool_size: Total connection pool capacity
    • gitlab_database_connection_pool_connections: Current connections in the pool
    • gitlab_database_connection_pool_waiting: Threads currently waiting on this queue
  • HTTP:
    • http_requests_total: Rack request count
    • http_request_duration_seconds: HTTP response time from rack middleware for successful requests
    • gitlab_external_http_total: Total number of HTTP calls to external systems
    • gitlab_external_http_duration_seconds: Duration in seconds spent on each HTTP call to external systems
  • ActionCable:
    • action_cable_pool_current_size: Current number of worker threads in ActionCable thread pool
    • action_cable_pool_max_size: Maximum number of worker threads in ActionCable thread pool
    • action_cable_pool_pending_tasks: Number of tasks waiting to be executed in ActionCable thread pool
    • action_cable_pool_tasks_total: Total number of tasks executed in ActionCable thread pool
  • Puma:
    • puma_workers: Total number of workers
    • puma_running_workers: Number of booted workers
    • puma_running: Number of running threads
    • puma_queued_connections: Number of connections in that worker’s “to do” set waiting for a worker thread
    • puma_active_connections: Number of threads processing a request
    • puma_pool_capacity: Number of requests the worker is capable of taking right now
    • puma_max_threads: Maximum number of worker threads
  • Redis:
    • gitlab_redis_client_requests_total: Number of Redis client requests
    • gitlab_redis_client_requests_duration_seconds: Redis request latency, excluding blocking commands
  • Cache:
    • gitlab_cache_misses_total: Cache read miss
    • gitlab_cache_operations_total: Cache operations by controller or action
  • Misc:
    • user_session_logins_total: Counter of how many users have logged in since GitLab was started or restarted

Workhorse

Про сервіс: GitLab Workhorse is a smart reverse proxy for GitLab, див. GitLab Workhorse.

Тут метрики:

  • gitlab_workhorse_gitaly_connections_total: Number of Gitaly connections that have been established
  • gitlab_workhorse_http_in_flight_requests: A gauge of requests currently being served by the http server
  • gitlab_workhorse_http_request_duration_seconds_bucket: A histogram of latencies for requests to the http server
  • gitlab_workhorse_http_requests_total: A counter for requests to the http server
  • gitlab_workhorse_internal_api_failure_response_bytes: How many bytes have been returned by upstream GitLab in API failure/rejection response bodies
  • gitlab_workhorse_internal_api_requests: How many internal API requests have been completed by gitlab-workhorse, partitioned by status code and HTTP method
  • gitlab_workhorse_object_storage_upload_requests: How many object storage requests have been processed
  • gitlab_workhorse_object_storage_upload_time_bucket: How long it took to upload objects
  • gitlab_workhorse_send_url_requests: How many send URL requests have been processed

Ух… Багацько.

Але було цікаво і корисно, щоб більш-менш поринути в те, що взагалі відбувається всередині GitLab кластеру.

Grafana GitLab Overview dashboard

Ну і останнім – побудуємо власну дашборду для GitLab, хоча є багато готових ось тут>>>, можна з них брати приклади запитів та панелей.

Для самих компонентів GitLab мабуть потім можна буде створити окрему, а поки що хочеться на одному екрані бачити що відбувається з подами, воркер-нодами Kubernetes, та загальную інформацю про сервіси GitLab і їхній статус.

Що нам цікаво?

З ресурсів Kubernetes:

  • pods: рестарти, pendings
  • PVC: зайнято/вільно місця на дисках, IOPS
  • CPU/Memory по подах та CPU throttling (якби у подів були ліміти, по-дефолту нема)
  • network: in/out bandwich, errors rate

Крім того – хотілося бі мати перед очима статус компонентів GitLab, дані по базі даних, Redis та якусь статистику по HTTP/Git/SSH.

Я вже трохи маю досвід з побудування подібних “обзорних” дашборд, тож в принципі маю уяву як воно буде виглядати, але інколи має сенс набросати схему розміщення блоків олівцем на папері, а потім вже будувати саму борду.

Чисто для мене – бажано, щоб всі дані були на одному екрані/моніторі – потім зручно відразу бачити все, що треба.

Колись давно, коли ще ходив до офісу, це виглядало так – load testing нашого першого Kubernetes-кластеру на колишній роботі:

Поїхали.

Variables

Щоб мати змогу вивести інформацю по конкретному компоненту кластера – додамо змінну component.

Значення формуємо за запитом до kube_pod_info:

label_values(kube_pod_info{namespace="gitlab-cluster-prod", pod!~".*backup.*"}, pod)

З якої отримаємо лейблу pod, і потім регуляркою /^([^\d]+)-/ вирізаємо все до цифр:

А далі можемо використовувати $component, щоб отримати тільки потрібні поди.

Статус компонентів GitLab

Тут досить просто: знаємо кількість подів кожного сервісу. Рахуємо їх, та виводимо UP/DEGRADED/DOWN.

На прикладі Webservice – використовуємо такий запит:

sum(kube_pod_info{namespace="gitlab-cluster-prod", pod=~"gitlab-cluster-prod-webservice-.+"})

Створюємо панель з типом Stat, отримуємо кількість подів:

Задаємо Text mode = Value:

Unit = number:

Створюємо Value mapping:

У нас наразі 2 поди в Deployment, тож якщо буде нуль – то пишему DOWN, якщо тільки один – то DEGRADED, ну а 2 і більше – то ОК, UP.

Повторюємо для всіх сервісів:

Pods status та кількість WorkerNodes

Друге, за чим важливо стежити – це статуси подів та кількість EC2 у AWS EC2 AutoScale групі, бо маємо виділенний node pool під GitLab кластер.

Pod restarts table

Для Pod restarts використаємо тип Table:

Запит:

sum(delta(kube_pod_container_status_restarts_total{namespace="gitlab-cluster-prod", pod=~"$component.*"}[5m])) by (pod)

І ставимо тип Table:

Додаємо Value mappins – в залежності від значення в колонці рестартів ячейка буде змінювати колір:

В Override ховаємо колонку Time, поле Value називаємо Restarts, змінюємо колір колонки Pod та її ім’я:

Результат:

Pods status graph

Далі, виведомо графік статусів подів – рестарти, Pending, etc.

Для статусу використаємо:

sum(avg(kube_pod_status_phase{namespace="gitlab-cluster-prod", phase!="Succeeded", pod=~"$component.*"}) by(namespace, pod, phase)) by(phase)

Для відображення рестартів:

sum(delta(kube_pod_container_status_restarts_total{namespace="gitlab-cluster-prod", pod=~"$component.*"}[5m]))

Результат:

Cluster Autoscaler Worker Nodes

Тут трохи цікавіше: треба порахувати всі Kubernetes Worker Nodes, на яких є поди GitLab, але метрики від самого Cluster AutoScaler на мають лейбли типу “namespace”, тож використаємо метрику kube_pod_info, яка має лейбли namespace та node, і по сумі node дізнаємось кількість EC2 інстансів:

count(count(kube_pod_info{namespace="gitlab-cluster-prod", pod!~"logical-backup.+"}) by (node))

Для Max nodes довелося значення задавати вручну, але навряд чи воно буде часто змінюватись.

В Thresholds задаємо значення, коли треба напрягатись, нехай буде 10, і включаємо Show thresholds = As filled regions and lines, щоб бачити його на графіку:

Результат:

І все разом виглядає так:

CPU та Memory by Pod

CPU by Pod

Рахуємо % від доступного CPU по кількості ядер. Тут цю кількість теж задав руками, знаючи тип ЕС2, але можна пошукати метрики типу “cores allocatable”:

sum(rate(container_cpu_usage_seconds_total{namespace="gitlab-cluster-prod", container!="POD",pod!="", image=~"", pod=~"$component.*"}[5m]) / 2 * 100) by (pod)

Не пам’ятаю вже, звідки сам запит – але результат у kubectl top pod підтверджує дані – перевіримо на поді з Sidekiq:

І top:

121 millicpu з 2000 доступних (2 ядра) це:

[simterm]

>>> 121/2000*100
6.05

[/simterm]

На графіку 5,43 – виглядає ок.

В Legend переносимо список вправо, та включаємо Values = Last, щоб сортувати по значеннях:

Результат:

Memory by Pod

Тут рахуємо по container_memory_working_set_bytes, налаштування таблиці аналогічні:

Доречі, можна було вивести % від доступної пам’яті на ноді, але нехай краще буде в “чистих” байтах.

Або можна додати Threshold с максимум 17179869184 байт – але тоді не так добре буде видно графікі з подів.

І разом маємо таке:

Статистка по дисках

Gitaly PVC used space

Що хотілося б по-перше – бачити вільне місце на диску Gitaly, де будуть всі репозиторії, та загальну статистку по записам-читанню на дисках.

Запити брав з якоїсь дефолтної дашборди з комплекту Kube Prometheus Stack.

Для отримання % зайнятого місця на Gitaly використовуємо запит:

100 - ( 
    kubelet_volume_stats_available_bytes{namespace="gitlab-cluster-prod", persistentvolumeclaim="repo-data-gitlab-cluster-prod-gitaly-0"} / 
    kubelet_volume_stats_capacity_bytes{namespace="gitlab-cluster-prod", persistentvolumeclaim="repo-data-gitlab-cluster-prod-gitaly-0"}
    * 100
)

Та тип Gauge, Unit – Percent 0-100, і додаємо Thresholds:

Disc IOPS

Додамо operations per second на дисках, запит теж десь з готових борд брав:

ceil(sum by(pod) (rate(container_fs_reads_total{job="kubelet", metrics_path="/metrics/cadvisor", container!="", device=~"(/dev/)?(mmcblk.p.+|nvme.+|rbd.+|sd.+|vd.+|xvd.+|dm-.+|dasd.+)", namespace="gitlab-cluster-prod", pod=~"$component.*"}[$__rate_interval]) + rate(container_fs_writes_total{job="kubelet", metrics_path="/metrics/cadvisor", container!="", namespace="gitlab-cluster-prod", pod=~"$component.*"}[$__rate_interval])))

Сумуємо по подах, у Legend знову додаємо Values = Last, щоб мати змогу сортування:

Disc Throughput

Тут все в принципі аналогічно, тільки інший запит:

sum by(pod) (rate(container_fs_reads_bytes_total{job="kubelet", metrics_path="/metrics/cadvisor", container!="", device=~"(/dev/)?(mmcblk.p.+|nvme.+|rbd.+|sd.+|vd.+|xvd.+|dm-.+|dasd.+)", namespace="gitlab-cluster-prod", pod=~"$component.*"}[$__rate_interval]) + rate(container_fs_writes_bytes_total{job="kubelet", metrics_path="/metrics/cadvisor", container!="", namespace="gitlab-cluster-prod", pod=~"$component.*"}[$__rate_interval]))

І все разом:

Networking

Ще мабуть буде корисно бачити що з мережею – помилки, та рейти In/Out.

Received/Transmitted Errors

Додамо Gauge, де будемо виводити % помилок – container_network_receive_errors_total, який рахуємо за запитом:

sum(rate(container_network_receive_errors_total{namespace="gitlab-cluster-prod"}[5m]))
/
sum(rate(container_network_receive_packets_total{namespace="gitlab-cluster-prod"}[5m]))
* 100

Та аналогічно – для Transmitted:

sum(rate(container_network_transmit_errors_total{namespace="gitlab-cluster-prod"}[5m]))
/
sum(rate(container_network_transmit_packets_total{namespace="gitlab-cluster-prod"}[5m]))
* 100

Network Bandwidth Bytes/second

Тут рахуємо кількість байт в секунду на кожному поді – container_network_receive_bytes_total та container_network_transmit_bytes_total:

Network Packets/second

Аналогічно, тільки з метриками container_network_receive_packets_total/container_network_transmit_packets_total:

Не впевнений, що воно буде корисно, але поки що нехай буде.

Webservice HTTP statistic

Для загальної картини – додамо трохи даних по HTTP-запитам на Webservice.

HTTP requests/second

Використаємо метрику http_requests_total:

Додамо Override, щоб змінити колір для даних по 4хх та 5хх кодам:

Webservice HTTP request duration

Тут можна було б побудувати Heatmap використовуєчи http_request_duration_seconds_bucket, але як на мене – то звичайний графік по типам запитів буде кращий:

sum(increase(http_request_duration_seconds_sum{kubernetes_namespace="gitlab-cluster-prod"}[5m])) by (method) 
/ 
sum(increase(http_request_duration_seconds_count{kubernetes_namespace="gitlab-cluster-prod"}[5m])) by (method)

Але можна й Heatmap:

sum(increase(http_request_duration_seconds_bucket[10m])) by (le)

Взагалі, по метрикам з типом Histogram можна робити досить багато цікавого, хоча я якось не користувався ними.

Див:

Статистика сервісів GitLab

Ну й на останнє – трохи даних по компонентам самого GitLab. В процессі роботи вже щось точно буду міняти, бо поки він не сильно використовується – то й не дуже зрозуміло, що саме заслуговує уваги.

Але з того, що поки приходить в голову – це Sidekiq та його джоби, Redis, PostgreSQL, GitLab Runner.

Sidekiq Jobs Errors rate

Запит:

sum(sidekiq_jobs_failed_total) / sum(sidekiq_jobs_processed_total) * 100

GitLab Runner Errors rate

Запит:

sum(gitlab_runner_errors_total) / sum(gitlab_runner_api_request_statuses_total) * 100

GitLab Redis Errors rate

Запит:

sum(gitlab_redis_client_exceptions_total) / sum(gitlab_redis_client_requests_total) * 100

Gitaly Supervisor errors rate

Запит:

sum(gitaly_supervisor_health_checks_total{status="bad"}) / sum(gitaly_supervisor_health_checks_total{status="ok"}) * 100

Database transactions latency

Запит:

sum(rate(gitlab_database_transaction_seconds_sum[5m])) by (kubernetes_pod_name) / sum(rate(gitlab_database_transaction_seconds_count[5m])) by (kubernetes_pod_name)

User Sessions

Запит:

sum(user_session_logins_total)

Git/SSH failed connections/second – by Grafana Loki

А тут використаємо значення, отримані з Loki – рейт помилок “kex_exchange_identification: Connection closed by remote host“, в Loki це виглядає так:

Див. Grafana Loki: можливості LogQL для роботи з логами та створення метрик для алертів.

В панелі вказуємо Data source = Loki, та використовуємо sum() і rate() для отримання значень:

Налаштовуємо Thresholds та Overrides, і маємо такий графік:

І взагалі вся борда тепер виглядає так:

Побачимо, як воно буде далі, і що стане у нагоді, а що буде видалитись, та що можна буде додати ще.

Ну і самі алерти теж треба буде зробити.