AWS: Instance Metadata Service v1 vs IMDS v2 та робота з Kubernetes Pod і Docker контейнерів
0 (0)

11 Квітня 2023

Instance metadata (IMDS – Instance Metadata Service) – дані про EC2 інстанс, такі як інформація про AMI, IP, ім’я хосту, і т.д.

Також до Instance Metadata можна додати User Data для зберігання якихось параметрів, які потім можна буде отримати всередині інстансу.

Див. Instance metadata and user data та Instance metadata categories.

Від початку, в AWS була реалізована request/response модель доступу до IMDS, тобто для отримання доступу достатньо було зробити HTTP-запит з хосту. Пізніше, була реалізована система session-oriented, коли для доступу вже треба було отримати токен, і ця система отримала індекс v2. Див. Add defense in depth against open firewalls, reverse proxies, and SSRF vulnerabilities with enhancements to the EC2 Instance Metadata Service та Use IMDSv2.

Приклад роботи з IMDSv1

Наприклад, запустимо EC2 та в User Data додамо якесь значення:

Та пробуємо curl на адресу 169.254.169.254:

[simterm]

$ curl http://169.254.169.254/latest/user-data
somedata: somevalue

[/simterm]

Або отримаємо саме метадату інстансу:

[simterm]

$ curl http://169.254.169.254/latest/meta-data
ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/
events/
hostname
identity-credentials/
instance-action
instance-id
instance-life-cycle
instance-type
local-hostname
local-ipv4
mac
metrics/
network/
placement/
profile
public-hostname
public-ipv4
public-keys/
reservation-id
security-groups
services/

[/simterm]

Або теж саме з Docker-контейнеру:

[simterm]

root@ip-172-31-30-97:/home/ubuntu# docker run -ti alpine/curl curl http://169.254.169.254/latest/meta-data/
Unable to find image 'alpine/curl:latest' locally
latest: Pulling from alpine/curl
59bf1c3509f3: Pull complete 
da353f38084f: Pull complete 
05df90dbd213: Pull complete 
Digest: sha256:81372de8c566f2d731bde924bed45230018e6d7c21d051c15e283eb8e06dfa2d
Status: Downloaded newer image for alpine/curl:latest
ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/
events/
hostname
...

[/simterm]

Доступ до IMDS із Kubernetes Pod

Тобто, і у випадку Kubernetes будь-який под має змогу отримати ці дані.

Для перевірки створимо под:

apiVersion: v1
kind: Pod
metadata:
  name: test-imds
  namespace: default
spec:
  containers:
  - name: test-imds
    image: alpine/curl:latest
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
  restartPolicy: Always

Запускаємо його:

[simterm]

$ kk apply -f pod-imds.yaml

[/simterm]

І з нього виконуємо той самий запит з curl:

[simterm]

$ kk exec -ti test-imds -- curl http://169.254.169.254/latest/meta-data/
ami-id
ami-launch-index
ami-manifest-path
autoscaling/
block-device-mapping/
events/
hostname
iam/
...

[/simterm]

IMDS Security Credentials

Крім інших даних, IMDS може повернути Access/Secret ключі та токен, які використовуються для отримання доступу до Instance IAM Role.

Отримаємо security credentials:

[simterm]

$ curl http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance
{
  "Code" : "Success",
  "LastUpdated" : "2023-03-28T12:48:33Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "ASI***BND",
  "SecretAccessKey" : "4Yz***8Bx",
  "Token" : "IQo***GaQ=",
  "Expiration" : "2023-03-28T18:51:13Z"
}

[/simterm]

А використовуючи ці Access/Secret ключі, ми можемо робити все, що дозволено інстансу, і, наприклад, якщо до інстансу підключено ІAМ роль з AdminAccess – ми зможемо отримати ці права.

Перевіримо – додамо до інстансу роль з доступом до S3-бакетів:

Перевіряємо роль в метаданих:

[simterm]

root@ip-172-31-30-97:/home/ubuntu# curl http://169.254.169.254/latest/meta-data/iam/info
{
  "Code" : "Success",
  "LastUpdated" : "2023-03-28T13:34:51Z",
  "InstanceProfileArn" : "arn:aws:iam::514***799:instance-profile/IMDSTestS3ReadOnlyAccess",
  "InstanceProfileId" : "AIPAXPNJUS3H7XEB7UT24"
}

[/simterm]

Та отримаємо ключі та токен:

[simterm]

root@ip-172-31-30-97:/home/ubuntu# curl http://169.254.169.254/latest/meta-data/iam/security-credentials/IMDSTestS3ReadOnlyAccess
{
  "Code" : "Success",
  "LastUpdated" : "2023-03-28T13:35:16Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "ASI***3PJ",
  "SecretAccessKey" : "IfC***t2n",
  "Token" : "IQo***o4a",
  "Expiration" : "2023-03-28T20:09:51Z"
}

[/simterm]

Додаємо їх собі на робочу машину в ~/.aws/credentials:

[testiam]
aws_access_key_id = ASI***3PJ
aws_secret_access_key = IfC***t2n
aws_session_token = IQo***o4a

Та створимо профіль у ~/.aws/config:

[profile testiam]
region = eu-central-1
output = json

І отримаємо доступ:

[simterm]

$ aws --profile testiam s3 ls
2022-12-19 16:43:06 cronjob-test
2023-02-14 15:02:28 gitlab-s3-cache-test
...

[/simterm]

Жах! :scream:

Права доступу до IMDS

Щоб запобігти такому, можна просто відключити IMDS взагалі, або використовувати IMDS v2.

Відключення IMDS

Для ЕС2 робиться через AWS CLI:

[simterm]

$ aws --profile internal ec2 modify-instance-metadata-options --instance-id i-0b0c0e351255ba78c --http-endpoint disabled
{
    "InstanceId": "i-0b0c0e351255ba78c",
    "InstanceMetadataOptions": {
        "State": "pending",
        "HttpTokens": "optional",
        "HttpPutResponseHopLimit": 1,
        "HttpEndpoint": "disabled",
        "HttpProtocolIpv6": "disabled",
        "InstanceMetadataTags": "disabled"
    }
}

[/simterm]

Або через AWS Console – EC2 > Instance Settings > Modify instance metadata options:

І тепер при запиті до meta-data маємо помилку 403 – Forbidden:

[simterm]

root@ip-172-31-30-97:/home/ubuntu# curl http://169.254.169.254/latest/meta-data/iam/info
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
  <title>403 - Forbidden</title>
...

[/simterm]

Переключення на IMDS v2

Якщо доступ все ж треба, то можемо відключити IMDS v1 та використовувати тільки IMDS v2 додавши обов’язкове використання токена за допомогою параметру --http-tokens:

[simterm]

$ aws --profile internal ec2 modify-instance-metadata-options --instance-id i-0b0c0e351255ba78c --http-endpoint enabled --http-tokens required
{
    "InstanceId": "i-0b0c0e351255ba78c",
    "InstanceMetadataOptions": {
        "State": "pending",
        "HttpTokens": "required",
        "HttpPutResponseHopLimit": 1,
        "HttpEndpoint": "enabled",
        "HttpProtocolIpv6": "disabled",
        "InstanceMetadataTags": "disabled"
    }
}

[/simterm]

Тепер при запиті маємо 401 – Unauthorized:

[simterm]

root@ip-172-31-30-97:/home/ubuntu# curl http://169.254.169.254/latest/meta-data/iam/info
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
  <title>401 - Unauthorized</title>
...

[/simterm]

Але якщо додамо токен – то все працюватиме.

Отримаємо токен:

[simterm]

root@ip-172-31-30-97:/home/ubuntu# TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`

[/simterm]

Глянемо його:

[simterm]

root@ip-172-31-30-97:/home/ubuntu# echo $TOKEN
AQA***Vyg==

[/simterm]

І тепер знову curl з хедером X-aws-ec2-metadata-token:

[simterm]

root@ip-172-31-30-97:/home/ubuntu# curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/info
{
  "Code" : "Success",
  "LastUpdated" : "2023-03-28T13:34:51Z",
  "InstanceProfileArn" : "arn:aws:iam::514***799:instance-profile/IMDSTestS3ReadOnlyAccess",
  "InstanceProfileId" : "AIPAXPNJUS3H7XEB7UT24"
}

[/simterm]

При використані Terraform модулів для створення Node Groups, звертайте увагу на опції. Наприклад, у cloudposse/terraform-aws-eks-node-group по дефолту включена IMDSv2, див. Behavior changes.

IMDS v2 та Docker

У випадку, коли використовуються контейнери, з включеним IMDSv2 можуть бути проблеми при отримані токену, наприклад:

[simterm]

root@64cbbd918977:/# curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" 
root@64cbbd918977:/# echo $?
56

[/simterm]

Щоб запобігти цьому, додаємо параметр http-put-response-hop-limit зі значенням більше 1, так як виклик з контейнеру додає ще один хоп при проходженні запросу від клієнта до IMDS: перший, це запит з самого хосту, а другий – із контейнера на цьому хості:

[simterm]

$ aws --profile internal ec2 modify-instance-metadata-options --http-endpoint enabled --http-tokens required --http-put-response-hop-limit 2 --instance-id i-0b0c0e351255ba78c
{
    "InstanceId": "i-0b0c0e351255ba78c",
    "InstanceMetadataOptions": {
        "State": "pending",
        "HttpTokens": "required",
        "HttpPutResponseHopLimit": 2,
        "HttpEndpoint": "enabled",
...

[/simterm]

І пробуємо зараз:

[simterm]

root@64cbbd918977:/# TOKEN=`curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`
root@64cbbd918977:/# curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/info
{
  "Code" : "Success",
  "LastUpdated" : "2023-04-10T10:46:45Z",
  "InstanceProfileArn" : "arn:aws:iam::514***799:instance-profile/IMDSTestS3ReadOnlyAccess",
  "InstanceProfileId" : "AIP***T24"
}

[/simterm]

Готово.

Loading

Knative: Serverless для Kubernetes – огляд можливостей та запуск у Minikube
0 (0)

6 Квітня 2023

Knative – система, яка дозволяє використовувати Serverless модель розробки у Kubernetes. По суті, Knative можна уявляти собі як ще один рівень абстракції, який дозволяє девелоперам не поринати в деталі деплойменту, скелінгу та нетворкінгу у “vanilla” Kubernetes.

Розробка самого Knative була розпочта у Google за співучастю таких компаній, як IBM, Pivotal, Red Hat, та загалом має близько 50 компаній-контрібьюторів.

What is: Serverless computing

Але спочатку, давайте розглянемо що таке Serverless взагалі.

Отже, Server та Less, це модель розробки, коли вам не потрібно перейматись менеджментом серверів – все це бере на себе cloud-провайдер, який надає вам послугу Serverless computing.

Тобто, зазвичай маємо в клауді:

  • bare-metal сервери десь в дата-центрі
  • на яких запускаються віртуальні машини
  • на яких ми запускаємо контейнери

Serverless computing поверх цих шарів додає ще один, де ви можете запускати вашу функцію, тобто мінімальний deployable-юніт, не займаючись ані металом, ані віртуалками, ані контейнерами. Ви просто маєте код, який в пару кліків можна запустити в клауді, а всі задачі по менеджменту інфрастуктури бере на себе клауд-провайдер. Ви не маєте турбуватись ані про хай авайлабіліті, ні про failt-tolerance, ні про бекапи, ні про секьюріті-патчі, ні про бекапи, ні про моніторинг і логування того, що відбувається на рівні інфрастуктури. Ба більше – на рівні мережі вам не треба думати про лоад-балансінг та те, як розподіляти навантаження по вашому сервісу – ви просто приймаєте запити на API Gateway або налаштовуєте триггер на event, який триггерить вашу Функцію. Тобто, cloud provider предоставляє вам послугу Function-as-a-Service, FaaS.

Все це чудово підходить у випадках, коли проект тільки-но стартує, і у девелоперів нема достатнього досвіду та/або часу, щоб піднімати сервери та кластери, тобто – скорочується Time-to-Market, або коли проект тільки тестує свою модель роботи системи взагалі, щоб швидко та безболісно реалізувати свою архітектуру.

“Запусти, та радуйся!”

Крім того, в FaaS інша модель оплати за сервіс – замість того, щоб платити за запущені сервери не зважаючи на те, чи виконують вони якусь роботу, чи просто знаходяться у idle стейті, при використанні FaaS ви платите тільки за той час, коли ваша функція виконується – Pay-for-Use Services.

Отже, ви:

  • створюєте функцію
  • налаштовуєте івенти, за якими ця функця буде запускатися (все ще нічого не платите)
  • при виникненні івента – він тригерить запуск функції (тут вже платите, допоки вона виконується)
  • після завершення роботи функції – вона “схлопується”, і біллінг за неї зупиняється до наступного запуску

Серед провайдерів FaaS – Amazon Web Services з його AWS Lambda, Microsoft Azure та Azure Functions, Google Cloud та Cloud Functions, і IBM Cloud з його IBM Cloud Functions.

Детальніше про Serverless – у CNCF Serverless Whitepaper.

Serverless use cases

Serverless модель буде ідеальною для рішень, які можуть працювати асінхронно та не потребують збереження стану, тобто являються Stateless системами.

Наприклад, це може бути функція в AWS, котора при створенні нового ЕС2 в аккаунті буде автоматично додавати теги до неї, накшталт AWS: Lambda – копирование тегов EC2 на EBS, часть 2 – создание Lambda-функции, коли ми використовуємо CloudWatch, який при створенні ЕС2 створює event, який триггерить функцю, передаючи їй аргументом ID EC2-інстансу, для дисків якого треба додати теги: функція запускається по цьому триггеру, додає теги, та зупинятється до наступного виклику.

Self-hosted serverless

Для використання Serverless моделі, необов’язково прив’язуватись до FaaS-провайдеру, натомість можна запустити self-hosted сервіс наприклад в своєму Kubernetes-кластері, і таким чином уникнути vendor lock.

Серед таких рішень:

  • KubelessIron FunctionsSpace CloudOpenLambdaFunktion, OpenWhisk та Fn Project: наразі, більше мертві, ніж живі, хоча деякі досі мають оновлення
  • Fission: має релізи, тобто розвивається, але займає тільки 2%
  • OpenFaaS:  використовується у 10% випадків
  • Knative: наразі являється найбільш популярним (27%) та найбільше активно розвиваючимся проектом

Knative vs AWS Lambda

Але навіщо взагалі морочитись із запуском власного Serverless?

  • маєте можливість обійти ліміти, задані провайдером – див. AWS Lambda quotas
  • уникаєте vendor lock, тобто маєте можливість швидко переїхати до іншого провайдера або використовувати multi-cloud архітектуру для більшої надійності
  • маєте більше можливостей для моніторингу, трейсінгу та роботи з логами, бо в Kubernetes ми можемо все, на відміну від AWS Lambda та її прив’язки до CloudWatch

Доречі, Knative має змогу запускати функції, розроблені для AWS Labmda завдяки Knative Lambda Runtimes.

Компоненти та архітектура Knative

Knative має два основні компоненти – Knative Serving та Knative Eventing.

Для контролю мережи, роутів та ревізій Knative використовує Istio (хоча може бути і інший, див. Configuring the ingress gateway).

Knative Serving

Knative Serving відповідає за деплоймент контейнеру, оновлення, мережу та автоскейлінг.

Роботу Knative Serving можна відобразити наступним чином:

Трафик від клієнта приходить на Ingress, та в залежності від запиту відправляється на конкретний Knative Service, який являє собою Kubernetes Service та Kubernetes Deployment.

В залежності від конфігурації конкретного Knative Service, трафік може бути розподіленний між різними ревізіями, тобто версіями самого application.

KPA – Knative Pod Autoscaler, перевіряє кількість запитів та при необхідності додає нові поди в Deployment. Якщо трафіка нема – то KPA скейлить поди в нуль, а коли з’являються нові запити від клієнтів – запускає поди та скейлить їх в залежності від кількості запитів, які треба обробити.

Основні типи ресурсів:

  • Service: service.serving.knative.dev відповідає за весь цикл життя вашого workload (тобто, деплойменту та зв’язанних з ним ресурсів) – контролює створення залежних ресурсів, таких як роути, конфігурації, ревізії
  • Routes: route.serving.knative.dev відповідає за зв’язок між ендпоінтом та ревізіями
  • Configurations: configuration.serving.knative.dev відповідає за необхідний стан (desired state) деплойменту та створює revisions, на які буде потрапляти трафік
  • Revisions: revision.serving.knative.dev являє собою point-in-time snapshot коду та конфігурацї для кожної зміни у workload

Див. Resource Types.

Knative Eventing

Knative Eventing відповідає за асинхронний зв’язок розподіленних частин вашої системи. Замість того, щоб робити їх залежними друг від друга, вони можуть створювати events (event producers), які будуть отримані іншим компонентом системи (event consumers або subcsribers, або в термінології Knative – sinks), тобто реалізувати event-driven architecture.

Knative Eventing використовує стандартні запроси HTTP POST для відправлення та отримання таких івентів, які мають відповідати специфікації CloudEvents.

Knative Eventing дозволяє створити:

  • Source to Sink: Source (event producers) відправляє івент (чи таки евент?) до Sink, який його обробляє. В ролі Source може бути PingSource, APIServerSource (Kubernetes API івенти), Apache Kafka, GitHub, тощо
  • Channel and Subscription: створює event pipe, коли івент потрапивши до Channel відразу відправляється до subscriber
  • Broker and Trigger: являє собою event mesh – івент, потрапляє до Broker, який має одного чи більше Trigger, котрі має фільтри, в залежності від яких отримється від Брокера ці events

Install Knative in Minikube

Компоненти Knative можна запустити за допомогою YAML-маніфестів (див. Install Knative Serving by using YAML та Install Knative Eventing by using YAML), використовуючи Knative Operator, або ж за допомогою Knative Quickstart plugin до Knative CLI.

Для знайомства нам підійде і quickstart плагін, тож використаємо його, а запускати будемо у Minikube.

Install Knative CLI

Див. Installing kn.

Для Arch-based дистрибутивів – за допомогою pacman із репозиторію:

[simterm]

$ sudo pacman -S knative-client

[/simterm]

Перевіряємо:

[simterm]

$ kn version
Version:      
Build Date:   
Git Revision: 
Supported APIs:
* Serving
  - serving.knative.dev/v1 (knative-serving v0.34.0)
* Eventing
  - sources.knative.dev/v1 (knative-eventing v0.34.1)
  - eventing.knative.dev/v1 (knative-eventing v0.34.1)

[/simterm]

Запуск Knative з Quickstart plugin

Див. kn-plugin-quickstart.

Знаходимо останню версію на сторінці релізів, вибираємо потрібну версію, в моєму випадку це буде kn-quickstart-linux-amd64, та завантажуємо у каталог, який є в $PATH:

[simterm]

$ wget -O /usr/local/bin/kn-quickstart https://github.com/knative-sandbox/kn-plugin-quickstart/releases/download/knative-v1.8.2/kn-quickstart-linux-amd64
$ chmod +x /usr/local/bin/kn-quickstart

[/simterm]

Перевіряємо (у CLI навіть є автокомпліт):

[simterm]

$ kn plugin list
- kn-quickstart : /usr/local/bin/kn-quickstart

[/simterm]

Перевіряємо:

[simterm]

$ kn quickstart --help
Get up and running with a local Knative environment

Usage:
  kn-quickstart [command]
  ...

[/simterm]

Quickstart плагін замість Istio встановить Kourier, а також створить Minikube кластер та встановить Knative Serving з sslip.io в ролі DNS і Knative Eventing.

Запускаємо:

[simterm]

$ kn quickstart minikube
Running Knative Quickstart using Minikube
Minikube version is: v1.29.0
...
🏄  Done! kubectl is now configured to use "knative" cluster and "default" namespace by default


To finish setting up networking for minikube, run the following command in a separate terminal window:
    minikube tunnel --profile knative
The tunnel command must be running in a terminal window any time when using the knative quickstart environment.

Press the Enter key to continue
...

[/simterm]

В іншому терміналі створюємо minikube-тунель для доступу з хост-машини:

[simterm]

$ minikube tunnel --profile knative &

[/simterm]

Повертаємось до попереднього вікна:

[simterm]

...
🍿 Installing Knative Serving v1.8.5 ...
...
🕸 Installing Kourier networking layer v1.8.3 ...
...
🕸 Configuring Kourier for Minikube...
...
🔥 Installing Knative Eventing v1.8.8 ...
...
🚀 Knative install took: 3m23s 
🎉 Now have some fun with Serverless and Event Driven Apps!

[/simterm]

Перевіряємо кластера Minikube:

[simterm]

$ minikube profile list
|---------|-----------|---------|--------------|------|---------|---------|-------|--------|
| Profile | VM Driver | Runtime |      IP      | Port | Version | Status  | Nodes | Active |
|---------|-----------|---------|--------------|------|---------|---------|-------|--------|
| knative | docker    | docker  | 192.168.49.2 | 8443 | v1.24.3 | Running |     1 |        |
|---------|-----------|---------|--------------|------|---------|---------|-------|--------|

[/simterm]

Неймспейси в Kubernetes:

[simterm]

$ kubectl get ns
NAME               STATUS   AGE
default            Active   7m30s
knative-eventing   Active   5m54s
knative-serving    Active   6m43s
kourier-system     Active   6m23s
...

[/simterm]

Поди в kourier-system:

[simterm]

$ kk -n kourier-system get pod
NAME                                      READY   STATUS    RESTARTS   AGE
3scale-kourier-gateway-7b89ff5c79-hwmpr   1/1     Running   0          7m26s

[/simterm]

Та в knative-eventing:

[simterm]

$ kk -n knative-eventing get pod
NAME                                    READY   STATUS    RESTARTS   AGE
eventing-controller-5c94cfc645-8pxgk    1/1     Running   0          7m10s
eventing-webhook-5554dc76bc-656p4       1/1     Running   0          7m10s
imc-controller-86dbd688db-px5z4         1/1     Running   0          6m39s
imc-dispatcher-5bfbdfcd85-wvpc7         1/1     Running   0          6m39s
mt-broker-controller-78dcfd9768-ttn7t   1/1     Running   0          6m28s
mt-broker-filter-687c575bd4-rpzk7       1/1     Running   0          6m28s
mt-broker-ingress-59d566b54-8t4x7       1/1     Running   0          6m28s

[/simterm]

І в knative-serving:

[simterm]

$ kk -n knative-serving get pod
NAME                                      READY   STATUS      RESTARTS   AGE
activator-66b65c899d-5kqsm                1/1     Running     0          8m17s
autoscaler-54cb7cd8c6-xltb9               1/1     Running     0          8m17s
controller-8686fd49f-976dk                1/1     Running     0          8m17s
default-domain-mk8tf                      0/1     Completed   0          7m34s
domain-mapping-559c8cdcbb-fs7q4           1/1     Running     0          8m17s
domainmapping-webhook-cbfc99f99-kqptw     1/1     Running     0          8m17s
net-kourier-controller-54f9c959c6-xsgp8   1/1     Running     0          7m57s
webhook-5c5c86fb8b-l24sh                  1/1     Running     0          8m17s

[/simterm]

Тепер давайте подвимось, що Knative вміє.

Knative Functions

Functions дозволяє швидко розробляти функції без необхідності писати Dockerfile та взагалі знати Knative та Kubernetes.

Див. About Knative Functions.

Додаємо ще один плагін – kn-func:

[simterm]

$ wget -O /usr/local/bin/kn-func https://github.com/knative/func/releases/download/knative-v1.9.3/func_linux_amd64
$ chmod +x /usr/local/bin/kn-func

[/simterm]

Перевіряємо:

[simterm]

$ kn plugin list
- kn-func : /usr/local/bin/kn-func
- kn-quickstart : /usr/local/bin/kn-quickstart

[/simterm]

Логінимось до Docker Registry (хоча якщо не зробити зараз – то Knative запросить логін під час деплою):

[simterm]

$ docker login

[/simterm]

func create

За допомогою func create створимо функцію на Python, див. Creating a function:

[simterm]

$ kn func create -l python hello-world
Created python function in /home/setevoy/Scripts/Knative/hello-world

[/simterm]

На робочій машині буде створено директорію з її кодом:

[simterm]

$ ll hello-world/
total 28
-rw-r--r-- 1 setevoy setevoy   28 Apr  5 12:36 Procfile
-rw-r--r-- 1 setevoy setevoy  862 Apr  5 12:36 README.md
-rwxr-xr-x 1 setevoy setevoy   55 Apr  5 12:36 app.sh
-rw-r--r-- 1 setevoy setevoy 1763 Apr  5 12:36 func.py
-rw-r--r-- 1 setevoy setevoy  390 Apr  5 12:36 func.yaml
-rw-r--r-- 1 setevoy setevoy   28 Apr  5 12:36 requirements.txt
-rw-r--r-- 1 setevoy setevoy  258 Apr  5 12:36 test_func.py

[/simterm]

У файлі func.yaml описується її конфіг:

[simterm]

$ cat hello-world/func.yaml 
specVersion: 0.35.0
name: hello-world
runtime: python
registry: ""
image: ""
imageDigest: ""
created: 2023-04-05T12:36:46.022291382+03:00
build:
  buildpacks: []
  builder: ""
  buildEnvs: []
run:
  volumes: []
  envs: []
deploy:
  namespace: ""
  remote: false
  annotations: {}
  options: {}
  labels: []
  healthEndpoints:
    liveness: /health/liveness
    readiness: /health/readiness

[/simterm]

А в func.py – сам код, який буде запускатись:

[simterm]

$ cat hello-world/func.py   
from parliament import Context
from flask import Request
import json


# parse request body, json data or URL query parameters
def payload_print(req: Request) -> str:
...

[/simterm]

func run

func run дозволяє перевірити, як функція буде працювати без необхідності її деплою до Knative. Для цього Knative CLI створить образ за допомогою вашого container runtime, та запустить його. Див. Running a function.

Запускаємо, вказуючи шлях до коду:

[simterm]

$ kn func run --path hello-world/
   🙌 Function image built: docker.io/setevoy/hello-world:latest
Function already built.  Use --build to force a rebuild.
Function started on port 8080

[/simterm]

Перевіряємо Docker-контейнери:

[simterm]

$ docker ps
CONTAINER ID   IMAGE                      COMMAND            CREATED          STATUS          PORTS                     NAMES
3869fa00ac51   setevoy/hello-world:latest "/cnb/process/web" 52 seconds ago   Up 50 seconds   127.0.0.1:8080->8080/tcp  sweet_vaughan

[/simterm]

Та пробуємо curl:

[simterm]

$ curl localhost:8080
{}

[/simterm]

В аутпуті func run:

[simterm]

Received request
GET http://localhost:8080/ localhost:8080
  Host: localhost:8080
  User-Agent: curl/8.0.1
  Accept: */*
URL Query String:
  {}

[/simterm]

Або можна перевірити за допомогою func invoke:

[simterm]

$ kn func invoke --path hello-world/
Received response
{"message": "Hello World"}

[/simterm]

І в самій функцї:

[simterm]

Received request
POST http://localhost:8080/ localhost:8080
  Host: localhost:8080
  User-Agent: Go-http-client/1.1
  Content-Length: 25
  Content-Type: application/json
  Accept-Encoding: gzip
Request body:
  {"message": "Hello World"}

[/simterm]

func deploy

func deploy створить образ, запушить його до registry, та задеплоїть у Kubernetes як Knative Service.

Якшо в коді зробити зміни, або просто виконати func build – то в Knative для функції буде створено нову ревізію, на яку буде переключений відповідний route:

[simterm]

$ kn func deploy --build --path hello-world/ --registry docker.io/setevoy
   🙌 Function image built: docker.io/setevoy/hello-world:latest
   ✅ Function updated in namespace "default" and exposed at URL: 
   http://hello-world.default.10.106.17.160.sslip.io

[/simterm]

Перевіряємо под в default namespace:

[simterm]

$ kk get pod 
NAME                                           READY   STATUS    RESTARTS   AGE
hello-world-00003-deployment-74dc7fcdd-7p6ql   2/2     Running   0          9s

[/simterm]

Kubernetes Service:

[simterm]

$ kk get svc 
NAME                                    TYPE           CLUSTER-IP       EXTERNAL-IP                                         PORT(S)                                              AGE
example-broker-kne-trigger-kn-channel   ExternalName   <none>           imc-dispatcher.knative-eventing.svc.cluster.local   80/TCP                                               33m
hello-world                             ExternalName   <none>           kourier-internal.kourier-system.svc.cluster.local   80/TCP                                               3m50s
hello-world-00001                       ClusterIP      10.109.117.53    <none>                                              80/TCP,443/TCP                                       4m1s
hello-world-00001-private               ClusterIP      10.105.142.206   <none>                                              80/TCP,443/TCP,9090/TCP,9091/TCP,8022/TCP,8012/TCP   4m1s
hello-world-00002                       ClusterIP      10.107.71.130    <none>                                              80/TCP,443/TCP                                       2m58s
hello-world-00002-private               ClusterIP      10.108.40.73     <none>                                              80/TCP,443/TCP,9090/TCP,9091/TCP,8022/TCP,8012/TCP   2m58s
hello-world-00003                       ClusterIP      10.103.142.53    <none>                                              80/TCP,443/TCP                                       2m37s
hello-world-00003-private               ClusterIP      10.110.95.58     <none>                                              80/TCP,443/TCP,9090/TCP,9091/TCP,8022/TCP,8012/TCP   2m37s

[/simterm]

Або за допомогою Knative CLI:

[simterm]

$ kn service list
NAME          URL                                                 LATEST              AGE     CONDITIONS   READY   REASON
hello-world   http://hello-world.default.10.106.17.160.sslip.io   hello-world-00003   4m41s   3 OK / 3     True    

[/simterm]

Або можна за kubectl отримати ресурс kvcs:

[simterm]

$ kubectl get ksvc
NAME          URL                                                 LATESTCREATED       LATESTREADY         READY   REASON
hello         http://hello.default.10.106.17.160.sslip.io         hello-00001         hello-00001         True    
hello-world   http://hello-world.default.10.106.17.160.sslip.io   hello-world-00003   hello-world-00003   True

[/simterm]

Визиваємо нашу функцію:

[simterm]

$ kn func invoke --path hello-world/
Received response
{"message": "Hello World"}

[/simterm]

І через хвилину відповідний Pod буде вбито, тобто Deployment заскейлиться в нуль:

[simterm]

$ kk get pod 
NAME                                           READY   STATUS        RESTARTS   AGE
hello-world-00003-deployment-74dc7fcdd-7p6ql   2/2     Terminating   0          79s

[/simterm]

Knative Serving

Functions – чудово, але ж ми начебто інженери, і нам цікаво щось більш наближене до Kubernetes, тож давайте глянемо на Knative Service.

Як вже говорилось, Knative Service – це “повний workload”, включаючи Kubernetes Service, Kubernetes Deployment, Knative Pod Autoscaler та відповідні роути і конфіги. Див. Deploying a Knative Service.

Тут використаємо YAML маніфест (хоча можно і kn service create), в якому описано образ, з якого буде створено под:

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: hello
spec:
  template:
    spec:
      containers:
        - image: gcr.io/knative-samples/helloworld-go
          ports:
            - containerPort: 8080
          env:
            - name: TARGET
              value: "World"

Деплоїмо до Kubernetes:

[simterm]

$ kubectl apply -f knative-hello-go-svc.yaml 
service.serving.knative.dev/hello created

[/simterm]

Перевіряємо Knative Service:

[simterm]

$ kn service list
NAME          URL                                                 LATEST              AGE     CONDITIONS   READY   REASON
hello         http://hello.default.10.106.17.160.sslip.io         hello-00001         70s     3 OK / 3     True    

[/simterm]

Пробуємо з curl:

[simterm]

$ curl http://hello.default.10.106.17.160.sslip.io
Hello World!

[/simterm]

Глянемо поди:

[simterm]

$ kk get pod
NAME                                      READY   STATUS    RESTARTS   AGE
hello-00001-deployment-5897f54974-wdhq8   2/2     Running   0          10s

[/simterm]

Та Kubernetes Deployments:

[simterm]

$ kk get deploy
NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
hello-00001-deployment         1/1     1            1           19m
hello-world-00001-deployment   0/0     0            0           26m
hello-world-00002-deployment   0/0     0            0           25m
hello-world-00003-deployment   0/0     0            0           24m

[/simterm]

Autoscaling

Knative Serving використовує власний Knative Pod Autoscaler (KPA), хоча можна замість нього створити “класичний” Horizontal Pod Autoscaler (HPA).

KPA підтримує scale to zero, але не вміє в CPU-based autoscaling. Крім того, у KPA можемо використовувати concurrency або requests per second метрики для скейлінгу.

HPA ж навпаки – вміє CPU-based autoscaling (і багато іншого), але не знає як скейлити в нуль, а для concurrency або RPS йому потрібні додаткові налаштування (див. Kubernetes: HorizontalPodAutoscaler – обзор и примеры).

Ми вже маємо KPA, які були створені при func deploy та під час створення нашого Knative Service:

[simterm]

$ kk get kpa
NAME                DESIREDSCALE   ACTUALSCALE   READY   REASON
hello-00001         0              0             False   NoTraffic
hello-world-00001   0              0             False   NoTraffic
hello-world-00002   0              0             False   NoTraffic
hello-world-00003   0              0             False   NoTraffic

[/simterm]

Запустимо curl в 10 потоків:

[simterm]

$ seq 1 10 | xargs -n1 -P 10 curl http://hello.default.10.106.17.160.sslip.io

[/simterm]

А в іншому вікні термінала глянемо на kubectl get pod --watch:

[simterm]

$ kk get pod -w
NAME                                      READY   STATUS              RESTARTS   AGE
hello-00001-deployment-5897f54974-9lxwl   0/2     ContainerCreating   0          1s
hello-00001-deployment-5897f54974-9lxwl   1/2     Running             0          2s
hello-00001-deployment-5897f54974-9lxwl   2/2     Running             0          2s

[/simterm]

“Воу, воно скейлиться!” 🙂

Knative Revisions

Knative завдяки Istio (або Kourier чи Kong) вміє розподіляти трафік між різними версіями (revisions) системи, що дозволяє виконувати blue/green або canary deployments.

Див. Traffic splitting.

В нашому Knative Service ми задавали змінну оточення $TARGET зі значенням World – давайте замінимо його на Knative:

[simterm]

$ kn service update hello --env TARGET=Knative
Updating Service 'hello' in namespace 'default':

  0.025s The Configuration is still working to reflect the latest desired specification.
  2.397s Traffic is not yet migrated to the latest revision.
  2.441s Ingress has not yet been reconciled.
  2.456s Waiting for load balancer to be ready
  2.631s Ready to serve.

Service 'hello' updated to latest revision 'hello-00002' is available at URL:
http://hello.default.10.106.17.160.sslip.io

[/simterm]

Перевіряємо ревізії:

[simterm]

$ kn revision list
NAME                SERVICE       TRAFFIC   TAGS   GENERATION   AGE   CONDITIONS   READY   REASON
hello-00002         hello         100%             2            20s   4 OK / 4     True    
hello-00001         hello                          1            24m   3 OK / 4     True    

[/simterm]

Та пробуємо звернутись до ендпоінту:

[simterm]

$ curl http://hello.default.10.106.17.160.sslip.io
Hello Knative!

[/simterm]

Тепер спробуємо поділити трафік – 50 відсотків відправити на попередню ревізію, а 50% – на нову:

[simterm]

$ kn service update hello --traffic hello-00001=50 --traffic @latest=50

[/simterm]

Глянемо revisions зараз:

[simterm]

$ kn revision list
NAME                SERVICE       TRAFFIC   TAGS   GENERATION   AGE    CONDITIONS   READY   REASON
hello-00002         hello         50%              2            115s   4 OK / 4     True    
hello-00001         hello         50%              1            26m    4 OK / 4     True    

[/simterm]

Та знов пробуємо curl, повторюємо кілька раз:

[simterm]

$ curl http://hello.default.10.106.17.160.sslip.io
Hello Knative!
$ curl http://hello.default.10.106.17.160.sslip.io
Hello World!
$ curl http://hello.default.10.106.17.160.sslip.io
Hello Knative!
$ curl http://hello.default.10.106.17.160.sslip.io
Hello World!

[/simterm]

Nice!

Knative Eventing

Knative Eventing – це набор сервісів та ресурсів, які дозволяють нам побудувати event-driven applications, коли Функції викликаються якимось евентом. Для цього можемо підключити sources, які ці події будуть генерувати, та sinks, тобто “споживачі”, які на ці події реагують.

У подальшій документації по Quickstart є приклад роботи з Broker та Trigger за допомогою cloudevents-player, але як на мене, там не дуже-то демонструються можливості Knative Eventing, тож виберемо трошки the hard way.

Як вже писалось, Knative підтримує три типи реалізації event-driven системи – Source to Sink, Channel and Subscription та Broker and Trigger.

Source to Sink

Source to Sink – сама базова модель, створюється за допомогою ресурсів Source та Sink, де Source – це Event Producer, а Sink – ресурс, який можна визвати або адресувати йому повідомлення.

Серед прикладів Sources – APIServerSource (Kubernetes API server), GitHub та GitLab, RabbitMQ та інші, див. Event sources.

В ролі Sink може бути Knative Service, Channel або Broker (тобто, “раковина”, куди ми “зливаємо” наші івенти). Хоча при побудові саме Source to Sink моделі, в ролі sink має бути саме Knative Service – про Channel та Broker поговоримо далі.

Channel and Subscription

Channel та Subscription являє собой even pipe (як pipe в bash, коли ми через | перенаправляємо stdout одніє програми у stdin іншої).

Тут Channel – це інтерфейс між event source та subscriber цього каналу. При цьому channel являє собою тип sink, так як він може зберігати івенти, щоб потім віддати їх своїм subscribers.

Knative підтримує три типи каналів:

  • In-memory Channel
  • Apache Kafka Channel
  • Google Cloud Platform Pub-sub Channel

In-memory Channel – тип за-замовченням, і не має змоги відновити івенти або зберігати їх постійно – для цього використовуйте типи накшталт Apache Kafka Channel.

Далі, Subscription – відповідає за з’єднання Channel з відповідним Knative Service – Сервіси підписуються на Канал через Subscription, та починають отримувати з Каналу повідомлення.

Broker and Trigger

Broker та Trigger являють собою event mesh модель, дозволяючи передавати події необхідним сервісам.

Тут Broker збирає події з різних sources предоставляючи вхідний шлюз дня них, а Trigger по суті є Subscription, але з можливістью фільтрування того, які саме івенти він буде отримувати.

Приклад створення Source to Sink

Створимо Knative Service, який буде нашим sink, тобто отримувачем:

[simterm]

$ kn service create knative-hello --concurrency-target=1 --image=quay.io/redhattraining/kbe-knative-hello:0.0.1
...
Service 'knative-hello' created to latest revision 'knative-hello-00001' is available at URL:
http://knative-hello.default.10.106.17.160.sslip.io

[/simterm]

concurrency-target тут вказує, що наш Service може обробляти тільки один запит одночасно. Якщо їх буде більше – то відповідний KPA створить додаткові поди.

Створюємо Event Source, наприклад – PingSource, який кожну хвилину буде слати повідомлення в вигляді JSON до нашого knative-hello Service:

[simterm]

$ kn source ping create knative-hello-ping-source --schedule "* * * * *" --data '{"message": "Hello from KBE!"}' --sink ksvc:knative-hello
Ping source 'knative-hello-ping-source' created in namespace 'default'.

[/simterm]

Перевіряємо:

[simterm]

$ kn source list
NAME                        TYPE         RESOURCE                          SINK                 READY
knative-hello-ping-source   PingSource   pingsources.sources.knative.dev   ksvc:knative-hello   True

[/simterm]

І глянемо логи поду з Service knative-hello:

[simterm]

$ kubectl logs -f knative-hello-00001-deployment-864756c67d-sk76m
...
2023-04-06 10:23:00,329 INFO  [eventing-hello] (executor-thread-1) ce-id=bd1093a9-9ab7-4b79-8aef-8f238c29c764
2023-04-06 10:23:00,331 INFO  [eventing-hello] (executor-thread-1) ce-source=/apis/v1/namespaces/default/pingsources/knative-hello-ping-source
2023-04-06 10:23:00,331 INFO  [eventing-hello] (executor-thread-1) ce-specversion=1.0
2023-04-06 10:23:00,331 INFO  [eventing-hello] (executor-thread-1) ce-time=2023-04-06T10:23:00.320053265Z
2023-04-06 10:23:00,332 INFO  [eventing-hello] (executor-thread-1) ce-type=dev.knative.sources.ping
2023-04-06 10:23:00,332 INFO  [eventing-hello] (executor-thread-1) content-type=null
2023-04-06 10:23:00,332 INFO  [eventing-hello] (executor-thread-1) content-length=30
2023-04-06 10:23:00,333 INFO  [eventing-hello] (executor-thread-1) POST:{"message": "Hello from KBE!"}

[/simterm]

Наразі це все, що хотілося дізнатись про Knative.

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

Посилання по темі

Loading

GitLab: gitlab-shell timeouts та /metrics Connection refused
0 (0)

21 Березня 2023

Запустились ми в production, і вилізла дуже неприємна бага – при git операціях clone/pull/push запит іноді зависав на 1-2 хвилини.

Виглядало це як якась “плавуюча” бага, тобто 5 раз могло склонити нормально, а потім раз зависає.

Проблеми

gitlab-shell timeouts

Наприклад – раз нормально:

[simterm]

$ time git clone [email protected]:example/platform/tables-api.git
Cloning into 'tables-api'...
...
real    0m1.380s

[/simterm]

А потім clone того ж самого репозиторію – 2 хвилини:

[simterm]

$ time git clone [email protected]:example/platform/tables-api.git
Cloning into 'tables-api'...
...
real    2m10.497s

[/simterm]

І це не виглядає, як якась мережева проблема, а скоріш щось з SSH на етапі встановлення сесії та обміну ключами.

На щастя, не став глибокого копатись, бо спершу вирішив зафіксити проблему з метриками, щоб мати змогу в моніторингу побачити що взагалі коїться з GitLab Shell.

gitlab-shell /metrics Connection refused

Про проблему з метриками вже говорив, коли описував налаштування моніторингу – GitLab: моніторинг – Prometheus, метрики, та Grafana dashboard, і там була проблема з метриками Git/SSH з поду gitlab-shell.

Це виглядало так: відкриваємо порт 9122 (див. values):

[simterm]

$ kk -n gitlab-cluster-prod port-forward gitlab-cluster-prod-gitlab-shell-744675c985-5t8wn 9122

[/simterm]

Пробуємо curl:

[simterm]

$ curl localhost:9122/metrics
curl: (52) Empty reply from server

[/simterm]

І под нам каже, що “Connection refused”:

[simterm]

...
Handling connection for 9122
E0315 12:40:43.712508  826225 portforward.go:407] an error occurred forwarding 9122 -> 9122: error forwarding port 9122 to pod 51856f9224907d4c1380783e46b13069ef5322ae1f286d4301f90a2ed60483c0, uid : exit status 1: 2023/03/15 10:40:43 socat[28712] E connect(5, AF=2 127.0.0.1:9122, 16): Connection refused
E0315 12:40:43.713039  826225 portforward.go:233] lost connection to pod

[/simterm]

Рішення

Як виявилось, GitLab Shell підтримує два SSH-демони – openssh та gitlab-sshd, при чьому саме openssh являється дефолтним значенням, див. values:

...## Allow to select ssh daemon that would be executed inside container
## Possible values: openssh, gitlab-sshd
sshDaemon: openssh
...

Тож, оновлюємо свій values:

...
    gitlab-shell:
      enabled: true
      metrics:
        enabled: true
      sshDaemon: gitlab-sshd
...

Деплоїмо, та перевіряємо метрики:

[simterm]

$ curl localhost:9122/metrics
# HELP gitlab_build_info Current build info for this GitLab Service
# TYPE gitlab_build_info gauge
gitlab_build_info{built="20230309.174623",version="v14.17.0"} 1
# HELP gitlab_shell_gitaly_connections_total Number of Gitaly connections that have been established
# TYPE gitlab_shell_gitaly_connections_total counter
gitlab_shell_gitaly_connections_total{status="ok"} 2
...

[/simterm]

Проблема з таймаутами теж вирішилась – тепер результат не більше 1 секунди – real    0m0.846s.

Loading

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

11 Березня 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, і маємо такий графік:

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

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

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

Loading

GitLab: міграція даних з GitLab cloud та процес backup-restore у self-hosted версії в Kubernetes
0 (0)

1 Березня 2023

Продовжуємо рухатись до запуску нашого self-hosted GitLab в production.

Див. попередні частини – GitLab: компоненти, архітектура, інфраструктура та запуск з Helm-чарту в Minikube та GitLab: Helm-чарт values, залежності та деплой у Kubernetes з AWS S3.

Міграція даних

Ще дуже хотілося написати пост про міграцію даних з GitLab Cloud до self-hosted, але немає часу, та й пройшло це майже повз мене – автоматизували через GitLab API та скрипт силами девелоперів.

Проте, я таки трохи інвестигейтив це питання, і в двох словах – “дефолтна” міграція, тобто direct transfer (recommended) не підтягувала проекти, тож використали depricated, але робочий варіант з file export, тобто коли для групи або проекту у Cloud-версії робиться експорт у файл, а потім імпортується вже у self-hosted.

Ну і поки девелопери працюють над допилюванням міграції – я займуся тествування та налаштування backups/restore на впипадок, якщо (коли?) “щось піде не так” (с).

Отже – подивимось, як зробити бекап вручну, як налаштувати запуск створення бекапів за розкладом, та спробуємо відновити дані. Забігаючи наперед – відновити вдалось, але з PostgreSQL довелося трошки повозитися.

GitLab Toolbox

“Recommended way” – це використовувати Toolbox pod, в якому по cron можна створювати бекапи, та при потребі – відновити дані.

Под Toolbox має декілька корисних утіліт:

  • gitlab-rails: дозволяє виконувати з консолі всякі адміністративні задачі в GitLab, пов’язані з траблшутінгом або для отримання якихось даних
  • gitlab-rake: теж для консолі, теж для адміністративних задач, в тому числі по бекапу та відновленню
  • backup-utility: ну й та сама утіліта, яку ми будемо використовувати для створення повного бекапу GitLab та всіх його залежностей типу бази даних

GitLab backup

Документація – Backing up a GitLab installation та Backup and restore.

backup-utility

Створення резервної копії виконується за допомогою поду з GitLab Toolbox, в якому є утіліта backup-utility:

[simterm]

git@gitlab-cluster-prod-toolbox-778bb8dc9f-knsr8:/$ which backup-utility
/usr/local/bin/backup-utility

[/simterm]

Яка являє собою bash-скрипт:

[simterm]

git@gitlab-cluster-prod-toolbox-778bb8dc9f-knsr8:/$ cat /usr/local/bin/backup-utility
#!/bin/bash
set -e

ACTION=""
export BACKUP_BUCKET_NAME=${BACKUP_BUCKET_NAME-gitlab-backups}
export BACKUP_BACKEND=${BACKUP_BACKEND-s3}
export S3_TOOL=${S3_TOOL-s3cmd}
export AWS_KMS_SETTINGS=""
export AWS_S3_SETTINGS=""
AWS_KMS_SETTINGS_LIST=()
AWS_S3_SETTINGS_LIST=()
S3_CMD_BACKUP_OPTION=""

rails_dir=/srv/gitlab
backups_path=$rails_dir/tmp/backups
backup_tars_path=$rails_dir/tmp/backup_tars
object_storage_backends=( registry uploads artifacts lfs packages external_diffs terraform_state pages ci_secure_files )
...

[/simterm]

По дефолту бекап виконується за допомогую s3cmd:

...
--s3tool TOOL                          S3 CLI tool to use. Can be either 's3cmd' or 'awscli'
...

Всі опції можна побачити за допомогою backup-utility --help.

При створенні бекапу, backup-utility виконає:

  • бекап бази даних Постгрі
  • бекап репозиторієв
  • бекап сторейджів – Package registry, Container registry, etc

Див. Sequence of execution.

Так як у нас вже налаштовано ServiceAccount з AWS IAM роллю, яка дозволяє доступ до S3, то замість s3cmd, до якоїх потрібен окремий конфіг, спробуємо AWS CLI.

Поки відкрите питання по розміру диску для самого Toolbox, бо бекап у вигляді TAR-архіву спочатку створюється у поді, а потім вигружається до S3-бакету. Поки зилишимо, як є, але маємо на увазі на майбутнє – якщо що, то можна буде збільшити диск для Toolbox, там це не страшно робити, бо ніяких даних він не сберігає (тобто под з Toolbox є stateless-сервісом).

UPD – схоже, що можна додати окремий диск через values та gitlab.toolbox.backups.persistence.

UPD-2: А ось знайшлось про розмір диску у документації по restore:

GitLab repositories data

Найбільше цікавить, звісно, бекап репозиторіїв.

Самі репозиторії зберігаються у Kubernetes PersistenVolume, який підключено до поду з Gitaly:

[simterm]

$ kk -n gitlab-cluster-prod describe pod gitlab-cluster-prod-gitaly-0
...
Containers:
  gitaly:
  ...
    Mounts:
      ...
      /home/git/repositories from repo-data (rw)
...

[/simterm]

В якому і є весь наш код:

[simterm]

$ kk -n gitlab-cluster-prod exec -ti gitlab-cluster-prod-gitaly-0 -- ls -l /home/git/repositories
Defaulted container "gitaly" out of: gitaly, certificates (init), configure (init)
total 28
drwxrwsr-x  3 git  git  4096 Feb 28 17:10 +gitaly
drwxrws--- 19 git  git  4096 Feb 28 16:04 @hashed
drwxrwsr-x  4 git  git  4096 Feb 24 11:39 gitlab.com-import
drwxrws---  2 root git 16384 Feb 21 13:31 lost+found

[/simterm]

Я б ще окремо подумав би про бекап цього PV/EBS за допомогою External Snaphotter та/або чи Amazon Data Lifecycle Manager.

Ручний запуск бекапу

Спробуємо спочатку запустити вручну – потім глянемо, як це налаштувати по cron-джобі:

[simterm]

git@gitlab-cluster-prod-toolbox-778bb8dc9f-knsr8:/$ backup-utility --s3tool awscli
2023-03-01 11:59:41 UTC -- Dumping main_database ... 
Dumping PostgreSQL database gitlabhq_production ... pg_dump: warning: could not find where to insert IF EXISTS in statement "-- *not* dropping schema, since initdb creates it
"
[DONE]
2023-03-01 11:59:45 UTC -- Dumping main_database ... done
2023-03-01 11:59:45 UTC -- Dumping ci_database ... [DISABLED]
2023-03-01 11:59:45 +0000 -- Deleting backup and restore lock file
2023-03-01 11:59:55 UTC -- Dumping repositories ... 
{"command":"create","gl_project_path":"gitlab-root.wiki","level":"info","msg":"started create","relative_path":"@groups/d4/73/d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35.wiki.git","storage_name":"default","time":"2023-03-01T11:59:55.890Z"}
{"command":"create","error":"manager: repository empty: repository skipped","gl_project_path":"gitlab-root.wiki","level":"warning","msg":"skipped create","relative_path":"@groups/d4/73/d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35.wiki.git","storage_name":"default","time":"2023-03-01T11:59:55.898Z"}
...
{"command":"create","gl_project_path":"migration-testing/companyname/apps/apple","level":"info","msg":"started create","relative_path":"@hashed/f6/e0/f6e0a1e2ac41945a9aa7ff8a8aaa0cebc12a3bcc981a929ad5cf810a090e11ae.git","storage_name":"default","time":"2023-03-01T12:00:00.994Z"}
...
{"command":"create","gl_project_path":"migration-testing/companyname/apps/apple","level":"info","msg":"completed create","relative_path":"@hashed/f6/e0/f6e0a1e2ac41945a9aa7ff8a8aaa0cebc12a3bcc981a929ad5cf810a090e11ae.git","storage_name":"default","time":"2023-03-01T12:00:05.826Z"}
...
2023-03-01 12:00:05 UTC -- Dumping repositories ... done
2023-03-01 12:00:05 +0000 -- Deleting backup and restore lock file
Dumping registry ...
empty
Dumping uploads ...
done
Dumping artifacts ...
done
Bucket not found: or-git-lfs-prod. Skipping backup of lfs ...
Dumping packages ...
empty
Dumping external_diffs ...
empty
Dumping terraform_state ...
empty
Bucket not found: gitlab-pages. Skipping backup of pages ...
Dumping ci_secure_files ...
empty
Packing up backup tar
[DONE] Backup can be found at s3://or-gitlab-backups-prod/1677671971_2023_03_01_15.8.1-ee_gitlab_backup.tar

[/simterm]

Воу… Працює)

Перевіримо зміст S3:

[simterm]

$ aws --profile internal s3 ls or-gitlab-backups-prod
2023-03-01 14:01:31 1006673920 1677671971_2023_03_01_15.8.1-ee_gitlab_backup.tar

[/simterm]

Backup Cron

Теперь додамо запуск створення бекапу за роскладом, наприклад – о 6 ранку по Києву, що наче як виходить 23.00 у США, бо наша команда є і там, і там, і навряд чи хтось буде пушити в GitLab в ці години.

Див. Configuration та values самого чарту Toolbox.

Використаємо https://crontab.guru.

Для тесту в розкладі cronjob задамо якийсь наближчий час.

Перевіряємо час в поді, бо може бути інша таймзона:

[simterm]

git@gitlab-cluster-prod-toolbox-778bb8dc9f-knsr8:/$ date
Wed Mar  1 12:32:12 UTC 2023

[/simterm]

А в Києві зараз 14:32, тож поставимо запуск на 13:00, як перевіримо – змінимо на 6 ранку.

Оновлюємо конфіг:

...
    toolbox:
      backups:
        cron:
          enabled: true
          concurrencyPolicy: Replace
          failedJobsHistoryLimit: 1
          schedule: "0 13 * * *"
          successfulJobsHistoryLimit: 3
          suspend: false
          backoffLimit: 6
          restartPolicy: "OnFailure"
          extraArgs: "--s3tool awscli"
          resources:
            requests:
              cpu: 50m
              memory: 350M
          persistence:
            enabled: false
            accessMode: ReadWriteOnce
            size: 10Gi
        objectStorage:
          backend: s3
...

Я тут навмисно вписав дефолтні resources.requests та persistence, щоб потім легше було їх змінити.

У objectStorage.config.secret спробуємо той самий сікрет, який ми створювали для appConfig.object_store, бо Toolbox його вимагає.

І в extraArgs: "--s3tool awscli" задаємо використання AWS CLI.

Деплоїмо, переверіяємо чи додалась кронджоба:

[simterm]

$ kk -n gitlab-cluster-prod get cj     
NAME                                        SCHEDULE     SUSPEND   ACTIVE   LAST SCHEDULE   AGE
gitlab-cluster-prod-toolbox-backup          0 13 * * *   False     0        <none>          6s
logical-backup-devops-gitlab-cluster-psql   0 1 * * *    False     0        11h             19d

[/simterm]

І чекаємо на 13.00 – спрацює, чи ні?

І такі да – о 13.00 з’явився под gitlab-cluster-prod-toolbox-backup-27961260-x4n45, в якому почав виконуватись бекап:

Ну – виглядає, наче ОК?

Давайте спробує відновити наш GitLab.

GitLab restore

Документація – Restoring the backup file.

Що хочеться протестити:

  • відновлення репозиторія
  • відновлення бази Postgres

Що зробимо:

  • створимо табличку в базі
  • створимо тестовий репозиторій

Потім створимо бекап, потім видалимо тестові дані, і спробуємо запустити рестор.

Тестовий проект

Створюємо проект:

Побудемо трошки рубістами:

Тестова PostgreSQL

Далі, логінимось до PostgreSQL.

В нас, нагадаю, PostgreSQL Operator (давно в чернетках пост по ньому, якось закінчу).

Знаходимо пароль – беремо із сікрету postgres.devops-gitlab-cluster-psql.credentials.postgresql.acid.zalan.do, виводимо в json, з jq отримаємо значення поля .data.password, та декодимо з base64:

[simterm]

$ kk -n gitlab-cluster-prod get secret postgres.devops-gitlab-cluster-psql.credentials.postgresql.acid.zalan.do -o json | jq -r '.data.password' | base64 -d
IcL***dPP

[/simterm]

Знаходимо master-інстанс:

[simterm]

$ kk -n gitlab-cluster-prod get pod -l application=spilo -L spilo-role
NAME                           READY   STATUS    RESTARTS   AGE     SPILO-ROLE
devops-gitlab-cluster-psql-0   2/2     Running   0          21h     replica
devops-gitlab-cluster-psql-1   2/2     Running   0          6d23h   replica
devops-gitlab-cluster-psql-2   2/2     Running   0          11d     master

[/simterm]

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

[simterm]

$ kk -n gitlab-cluster-prod port-forward devops-gitlab-cluster-psql-2 5432:5432
Forwarding from 127.0.0.1:5432 -> 5432
Forwarding from [::1]:5432 -> 5432

[/simterm]

Логінимось в кластер з юзером postgres:

[simterm]

$ psql -h localhost -U postgres -d gitlabhq_production
Password for user postgres: 
psql (15.1, server 14.4 (Ubuntu 14.4-1.pgdg18.04+1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off)
Type "help" for help.

gitlabhq_production=# 

[/simterm]

Створюємо табличку в базі gitlabhq_production:

[simterm]

gitlabhq_production=# CREATE TABLE test_restore(key_id serial PRIMARY KEY, key_name VARCHAR (255) UNIQUE NOT NULL);
CREATE TABLE

[/simterm]

Перевіряємо, що вона є:

[simterm]

gitlabhq_production-# \dt test_restore
            List of relations
 Schema |     Name     | Type  |  Owner   
--------+--------------+-------+----------
 public | test_restore | table | postgres
(1 row)

[/simterm]

Переходимо до поду з Toolbox, та запускаємо бекап:

[simterm]

git@gitlab-cluster-prod-toolbox-6fc9c7fc89-8jws7:/$ backup-utility --s3tool awscli
2023-03-01 14:34:22 UTC -- Dumping main_database ... 
Dumping PostgreSQL database gitlabhq_production ... pg_dump: error: query failed: ERROR:  permission denied for table test_restore
pg_dump: detail: Query was: LOCK TABLE public.test_restore IN ACCESS SHARE MODE
[FAILED]
2023-03-01 14:34:22 UTC -- Dumping main_database failed: Failed to create compressed file '/srv/gitlab/tmp/backups/db/database.sql.gz' when trying to backup the main database:
 - host: 'devops-gitlab-cluster-psql'
 - port: '5432'
 - database: 'gitlabhq_production'
...

[/simterm]

Oops… “Щось пішло не так”.

Читаємо доку про GRANT, та задаємо права всім:

[simterm]

gitlabhq_production=# GRANT ALL ON test_restore TO PUBLIC;
GRANT

[/simterm]

Пробуємо ще раз, і:

[simterm]

git@gitlab-cluster-prod-toolbox-6fc9c7fc89-8jws7:/$ backup-utility --s3tool awscli
2023-03-01 14:37:39 UTC -- Dumping main_database ... 
Dumping PostgreSQL database gitlabhq_production ... pg_dump: error: query failed: ERROR:  permission denied for sequence test_restore_key_id_seq
pg_dump: detail: Query was: SELECT last_value, is_called FROM public.test_restore_key_id_seq
...

[/simterm]

Твою ж…

Додаємо ще прав на SEQUENCE:

[simterm]

gitlabhq_production=# GRANT USAGE, SELECT ON SEQUENCE test_restore_key_id_seq TO PUBLIC;
GRANT

[/simterm]

Тепер пішло:

[simterm]

git@gitlab-cluster-prod-toolbox-6fc9c7fc89-8jws7:/$ backup-utility --s3tool awscli
2023-03-01 14:40:38 UTC -- Dumping main_database ... 
Dumping PostgreSQL database gitlabhq_production ... pg_dump: warning: could not find where to insert IF EXISTS in statement "-- *not* dropping schema, since initdb creates it
"
[DONE]
2023-03-01 14:40:43 UTC -- Dumping main_database ... done
...
Packing up backup tar
[DONE] Backup can be found at s3://or-gitlab-backups-prod/1677681628_2023_03_01_15.8.1-ee_gitlab_backup.tar

[/simterm]

Добре.

Видалення даних

Тепер – видаляємо таблицю з бази:

[simterm]

gitlabhq_production=# DROP TABLE test_restore;
DROP TABLE
gitlabhq_production=# \dt test_restore
Did not find any relation named "test_restore".

[/simterm]

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

Відновлення даних з бекапу

Be sure to stop Puma, Sidekiq, and any other process

Ну і пробуємо – визиваємо той самий backup-utility з опцією --restore:

[simterm]

git@gitlab-cluster-prod-toolbox-6fc9c7fc89-8jws7:/$ backup-utility --s3tool awscli --restore -t 1677681628_2023_03_01_15.8.1-ee
Unpacking backup
2023-03-01 14:48:06 UTC -- Restoring main_database ... 
2023-03-01 14:48:06 UTC -- Be sure to stop Puma, Sidekiq, and any other process that
connects to the database before proceeding. For Omnibus
installs, see the following link for more information:
https://docs.gitlab.com/ee/raketasks/backup_restore.html#restore-for-omnibus-gitlab-installations

Before restoring the database, we will remove all existing
tables to avoid future upgrade problems. Be aware that if you have
custom tables in the GitLab database these tables and all data will be
removed.

[/simterm]

Ага… А в документації про це не говориться… Хоча в цілому варто переглянути Back up and restore GitLab – там ще багато чого цікавого.

Добре – згадаємо, що у нас використовує Postgres – див. Інфраструктура:

Ну, наче тільки Webservice та Sidekiq.

Стопаємо їх:

[simterm]

$ kk -n gitlab-cluster-prod scale deploy gitlab-cluster-prod-sidekiq-all-in-1-v2 --replicas=0
deployment.apps/gitlab-cluster-prod-sidekiq-all-in-1-v2 scaled

$ kk -n gitlab-cluster-prod scale deploy gitlab-cluster-prod-webservice-default --replicas=0
deployment.apps/gitlab-cluster-prod-webservice-default scaled

[/simterm]

Запускаємо рестор знову:

[simterm]

git@gitlab-cluster-prod-toolbox-6fc9c7fc89-8jws7:/$ backup-utility --s3tool awscli --restore -t 1677681628_2023_03_01_15.8.1-ee
Unpacking backup
2023-03-01 15:01:25 UTC -- Restoring main_database ... 
2023-03-01 15:01:25 UTC -- Be sure to stop Puma, Sidekiq, and any other process that
...

[/simterm]

Та ну твою ж дивізію…

Ну, ок… Може це через Postgres Operator? Бо він точно якісь конекти тримає. Чи через моніторинг – експортер?

Глянемо за таким нагугленим запитом:

select pid as process_id, 
       usename as username, 
       datname as database_name, 
       client_addr as client_address, 
       application_name,
       backend_start,
       state,
       state_change
from pg_stat_activity;

Виконуємо:

[simterm]

gitlabhq_production=# select pid as process_id, 
       usename as username, 
       datname as database_name, 
       client_addr as client_address, 
       application_name,
       backend_start,
       state,
       state_change
from pg_stat_activity;
 process_id |            username            |    database_name    | client_address |            application_name            |         backend_start         | state  |         state_change          
------------+--------------------------------+---------------------+----------------+----------------------------------------+-------------------------------+--------+-------------------------------
      25175 |                                |                     |                |                                        | 2023-02-21 10:48:54.37466+00  |        | 
        152 | postgres                       | postgres            |                | Patroni                                | 2023-02-17 14:51:34.098346+00 | idle   | 2023-03-01 15:04:27.471442+00
         77 | postgres                       | postgres            |                |                                        | 2023-02-17 14:51:22.942046+00 |        | 
      25177 | postgres                       | postgres            |                | pg_cron scheduler                      | 2023-02-21 10:48:54.376082+00 | idle   | 2023-02-21 10:48:56.389663+00
      25179 | postgres                       |                     |                |                                        | 2023-02-21 10:48:54.377106+00 |        | 
      25178 | postgres                       |                     |                | TimescaleDB Background Worker Launcher | 2023-02-21 10:48:54.376869+00 |        | 
       5744 | standby                        |                     | 10.0.101.37    | devops-gitlab-cluster-psql-0           | 2023-02-28 16:20:44.085971+00 | active | 2023-02-28 16:20:44.092053+00
      25292 | gitlabhq_production_owner_user | gitlabhq_production | 10.0.81.98     | /usr/bin/gitlab-exporter               | 2023-02-21 10:51:42.135462+00 | idle   | 2023-03-01 15:03:53.761035+00
      15286 | standby                        |                     | 10.0.41.195    | devops-gitlab-cluster-psql-1           | 2023-02-22 14:24:15.551548+00 | active | 2023-02-22 14:24:15.560445+00
      24578 | postgres                       | gitlabhq_production | 127.0.0.1      | psql                                   | 2023-03-01 15:04:24.063071+00 | active | 2023-03-01 15:04:36.803864+00
        147 |                                |                     |                |                                        | 2023-02-17 14:51:33.082497+00 |        | 
      25176 |                                |                     |                |                                        | 2023-02-21 10:48:54.376307+00 |        | 
        146 |                                |                     |                |                                        | 2023-02-17 14:51:33.081686+00 |        | 
      25174 |                                |                     |                |                                        | 2023-02-21 10:48:54.374117+00 |        | 

[/simterm]

Оу, май… Ну, так… Багацько…

ERROR: must be owner of view pg_stat_statements_info

Добре – думаю, можно з цими конектами запускати – ігноруємо помилку, відповідаємо yes на запит “Do you want to continue?“:

[simterm]

git@gitlab-cluster-prod-toolbox-6fc9c7fc89-8jws7:/$ backup-utility --s3tool awscli --restore -t 1677681628_2023_03_01_15.8.1-ee
Unpacking backup
2023-03-01 15:06:43 UTC -- Restoring main_database ... 
2023-03-01 15:06:43 UTC -- Be sure to stop Puma, Sidekiq, and any other process that
...
Do you want to continue (yes/no)? yes
Removing all tables. Press `Ctrl-C` within 5 seconds to abort
2023-03-01 15:07:02 UTC -- Cleaning the database ... 
2023-03-01 15:07:02 +0000 -- Deleting backup and restore lock file
rake aborted!
ActiveRecord::StatementInvalid: PG::InsufficientPrivilege: ERROR:  must be owner of view pg_stat_statements_info
...

[/simterm]

SUUUUUKA!)

Окей… Ми вперті.

Гуглимо ішью – Cannot restore to helm-installed version.

Пробуємо видалити Postgres Extention:

[simterm]

gitlabhq_production=# DROP EXTENSION pg_stat_statements;
ERROR:  cannot drop extension pg_stat_statements because other objects depend on it
DETAIL:  function metric_helpers.pg_stat_statements(boolean) depends on type pg_stat_statements
view metric_helpers.pg_stat_statements depends on function metric_helpers.pg_stat_statements(boolean)
extension pg_stat_kcache depends on extension pg_stat_statements
HINT:  Use DROP ... CASCADE to drop the dependent objects too.

[/simterm]

Ну, ок… залежності не виглядають як якісь супер-критичні, і можливо потім ми таки зможемо повернути цей pg_stat_statements.

Пробуємо з CASCADE:

[simterm]

gitlabhq_production=# DROP EXTENSION pg_stat_statements CASCADE;
NOTICE:  drop cascades to 3 other objects
DETAIL:  drop cascades to function metric_helpers.pg_stat_statements(boolean)
drop cascades to view metric_helpers.pg_stat_statements
drop cascades to extension pg_stat_kcache
DROP EXTENSION

[/simterm]

Запускаємо…

І – чудо! Воно пішло, хоча з купою помилок на права доступа.

Знов спитало, чи продовжувати – знов відповідаємо yes:

[simterm]

git@gitlab-cluster-prod-toolbox-6fc9c7fc89-8jws7:/$ backup-utility --s3tool awscli --restore -t 1677681628_2023_03_01_15.8.1-ee
Unpacking backup
2023-03-01 15:14:41 UTC -- Restoring main_database ... 
2023-03-01 15:14:41 UTC -- Be sure to stop Puma, Sidekiq, and any other process that
connects to the database before proceeding. For Omnibus
installs, see the following link for more information:
https://docs.gitlab.com/ee/raketasks/backup_restore.html#restore-for-omnibus-gitlab-installations

Before restoring the database, we will remove all existing
tables to avoid future upgrade problems. Be aware that if you have
custom tables in the GitLab database these tables and all data will be
removed.

Do you want to continue (yes/no)? yes
Removing all tables. Press `Ctrl-C` within 5 seconds to abort
2023-03-01 15:14:49 UTC -- Cleaning the database ... 
2023-03-01 15:14:53 UTC -- done
Restoring PostgreSQL database gitlabhq_production ... ERROR:  permission denied for schema metric_helpers
ERROR:  permission denied for schema metric_helpers
ERROR:  permission denied for schema metric_helpers
ERROR:  permission denied for schema user_management
ERROR:  permission denied for schema user_management
...
ERROR:  permission denied for schema metric_helpers
------ END ERRORS -------
[DONE]
2023-03-01 15:15:39 UTC -- Restoring main_database ... done
2023-03-01 15:15:39 UTC -- There were errors in restoring the schema. This may cause
issues if this results in missing indexes, constraints, or
columns. Please record the errors above and contact GitLab
Support if you have questions:
https://about.gitlab.com/support/

Do you want to continue (yes/no)? yes
...
2023-03-01 15:16:45 UTC -- Restoring repositories ... done
2023-03-01 15:16:45 +0000 -- Deleting backup and restore lock file
Restoring uploads ...
done
Restoring artifacts ...
done

[/simterm]

Перевіряємо нашу тестову таблицю:

[simterm]

gitlabhq_production=# \dt test_restore
                       List of relations
 Schema |     Name     | Type  |             Owner              
--------+--------------+-------+--------------------------------
 public | test_restore | table | gitlabhq_production_owner_user
(1 row)

[/simterm]

Запускаємо стопнуті сервіси:

[simterm]

$ kk -n gitlab-cluster-prod scale deploy gitlab-cluster-prod-webservice-default --replicas=2
deployment.apps/gitlab-cluster-prod-webservice-default scaled

$ kk -n gitlab-cluster-prod scale deploy gitlab-cluster-prod-sidekiq-all-in-1-v2 --replicas=1
deployment.apps/gitlab-cluster-prod-sidekiq-all-in-1-v2 scaled

[/simterm]

Перевіряємо наш репозиторій:

Все є…

Дивно…)

Але ок – виглядає так, наче воно працює.

Плюс, для бази PostgreSQL робиться окремий бекап самим Оператором, тож, думаю, можна йти далі.

Хоча дуже сподіваюсь, що використовувати restore ніколи не доведеться.

Loading

Kubernetes: збільшення Persistent Volume з PersistentVolumeClaim та volumeClaimTemplates
0 (0)

21 Лютого 2023

GitLab для зберігання репозиторіїв по дефолту створює диск розміром 50 GB, чого для Production явно буде мало.

Persistent Volume resize опція доступна як general availability з версії Kubernetes 1.24, див. Kubernetes 1.24: Volume Expansion Now A Stable Feature, а в Beta – з версії 1.11, див. Resizing Persistent Volumes using Kubernetes.

Доступно для дисків AWS EBS, GCE PD, Azure Disk, Azure File, Glusterfs, Cinder, Portworx та Ceph RBD.

Спочатку подивимось на приклад, коли у нас PVC створються зі звичайного маніфесту, а потім – як збільшити диск, котрий створювався через StatefulSet та його volumeClaimTemplates.

Збільшення диску через PersistentVolumeClaim

Перевіряємо чи можливо це з нашим “залізом”, тобто StorageClass, який використовується, в нашому випадку це AWS Elastic Block Store з типом gp3 – дивимось ресурс StorageClass, та його колонку ALLOWVOLUMEEXPANSION:

[simterm]

$ kk get StorageClass
NAME              PROVISIONER       RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
cold-storage      ebs.csi.aws.com   Delete          WaitForFirstConsumer   true                   362d
encrypted         ebs.csi.aws.com   Delete          WaitForFirstConsumer   true                   362d
gp2               ebs.csi.aws.com   Delete          WaitForFirstConsumer   true                   362d
gp3 (default)     ebs.csi.aws.com   Delete          WaitForFirstConsumer   true                   362d
high-iops-ssd     ebs.csi.aws.com   Delete          WaitForFirstConsumer   true                   362d
medium-iops-ssd   ebs.csi.aws.com   Delete          WaitForFirstConsumer   true                   362d
throughput        ebs.csi.aws.com   Delete          WaitForFirstConsumer   true                   362d

[/simterm]

Також памя’таємо, що після зміни розміру EBS потрібно змінити розмір файлової системи. У Kubernetes з версії 1.24 це робиться автоматично завдяки параметру ExpandInUsePersistentVolumesonline file system expansion. Між 1.11 та 1.24 потрібно перезапускати поди, які використовують цей PVC.

Ну і перевіримо.

Створимо Pod з PersistentVolumeClaim на 1 гігабайт:

---
apiVersion: v1
kind: Pod
metadata:
  name: test-resize-pv-pod
  labels:
    app: test-resize
spec:
  containers:
    - name: test-resize-pv-container
      image: nginxdemos/hello
      ports:
        - containerPort: 80
      volumeMounts:
        - mountPath: "/data"
          name: test-resize-pv-volume
  volumes:
  - name: test-resize-pv-volume
    persistentVolumeClaim:
      claimName: test-resize-pv-volume-pvc
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-resize-pv-volume-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: gp3

Деплоїмо:

[simterm]

$ kk -n tests apply -f test-pod-w-pvc.yaml 
pod/test-resize-pv-pod created
persistentvolumeclaim/test-resize-pv-volume-pvc created

[/simterm]

Перевіряємо PersistentVolumeClaim:

[simterm]

$ kk -n tests get pvc test-resize-pv-volume-pvc

[/simterm]

По ID подивимось на сам PersistentVolume (тобто AWS EBS):

[simterm]

$ kk -n tests get pv pvc-8e75b850-8ef2-431b-a525-2a3200d17634
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                             STORAGECLASS   REASON   AGE
pvc-8e75b850-8ef2-431b-a525-2a3200d17634   1Gi        RWO            Delete           Bound    tests/test-resize-pv-volume-pvc   gp3                     86s

[/simterm]

Редагуємо PersistentVolumeClaim – збільшимо в resources.requests.storage розмір до 2 ГБ:

---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-resize-pv-volume-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 2Gi
  storageClassName: gp3

Оновлюємо:

[simterm]

$ kk -n tests apply -f test-pod-w-pvc.yaml 
pod/test-resize-pv-pod configured
persistentvolumeclaim/test-resize-pv-volume-pvc configured

[/simterm]

Та перевіряємо ще раз:

[simterm]

$ kk -n tests get pvc test-resize-pv-volume-pvc
NAME                        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
test-resize-pv-volume-pvc   Bound    pvc-8e75b850-8ef2-431b-a525-2a3200d17634   2Gi        RWO            gp3            4m50s

[/simterm]

В самому поді:

[simterm]

$ kk -n tests exec -ti test-resize-pv-pod -- df -h /data
Filesystem                Size      Used Available Use% Mounted on
/dev/nvme4n1              1.9G     24.0K      1.9G   0% /data

[/simterm]

Гуд.

Збільшення диску з StatefulSet та volumeClaimTemplates

Створюємо маніфест з STS та volumeClaimTemplates – цей volumeClaimTemplates створить PersistentVolumeClaim:

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: registry.k8s.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

Деплоїмо, перевіряємо:

[simterm]

$ kk -n tests get pvc www-web-0
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-web-0   Bound    pvc-ca9d909f-1d34-40d9-acac-78ab778f500f   1Gi        RWO            gp3            2m7s

[/simterm]

The StatefulSet is invalid: spec: Forbidden

Пробуємо змінити volumeClaimTemplates.spec.resources.requests.storage на 2GB, аплаїмо:

[simterm]

$ kubectl -n tests apply -f test-sts-w-pvc.yaml 
The StatefulSet "web" is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'template', 'updateStrategy' and 'minReadySeconds' are forbidden

[/simterm]

Ага…

Тож лишаємо значення в volumeClaimTemplates, та редагуємо PVC вручну:

Або з kubectl patch:

[simterm]

$ kubectl -n tests patch pvc www-web-0 -p '{"spec":{"resources":{"requests":{"storage":"2Gi"}}}}'

[/simterm]

І за хвилину перевіряємо:

[simterm]

$ kk -n tests get pvc www-web-0
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-web-0   Bound    pvc-ca9d909f-1d34-40d9-acac-78ab778f500f   2Gi        RWO            gp3            3m43s

[/simterm]

Та сам Persistent Volume:

[simterm]

$ kk -n tests get pv pvc-ca9d909f-1d34-40d9-acac-78ab778f500f
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS   REASON   AGE
pvc-ca9d909f-1d34-40d9-acac-78ab778f500f   2Gi        RWO            Delete           Bound    tests/www-web-0   gp3                     4m14s

[/simterm]

Готово.

Loading

Prometheus: створення Custom Prometheus Exporter на Python
0 (0)

18 Лютого 2023

У Прометеуса є багато готових до використання експортерів, але інколи може з’явитися потреба у зборі своїх власних метрик.

Для цього Прометеус надає клієнтські бібліотеки, які можемо використати для генерації метрик з потрібними лейблами.

Експортер можна включити прямо в код вашого додатку, або можна запускати окремим сервісом, який буде звертатися до якогось вашого сервісу і отримувати від нього дані, які потім буде конвертувати в Prometheus-формат та віддавати серверу Prometheus.

Prometheus Metrics

Загальна схема роботи Prometheus-серверу та його експортерів у Kubernetes виглядає так:

Тут маємо:

  1. Prometheus Server, який в нашому випадку розгортається за допомогою Kube Promeheus Stack та Prometheus Operator
  2. за допомогою ServiceMonitor через Operator ми створюємо Scrape Job, яка має один чи декілька Targets, тобто сервісів, які буде опитувати Prometheus для отримання метрик, які він зберігає у своїй Time Series Database
  3. за URL, які вказані в Target, Prometheus звертається до ендпоінту Prometheus Exporter
  4. а Prometheus Exporter збирає метрики з вашого додатку, які потім віддає до Prometheus

Типи метрик Prometheus

Коли плануємо писати свій експортер, необхідно знати які типи метрик ми можемо в ньому використовувати. Основні типи:

  • Counter: може тільки збільшувати своє значення, наприклад для підрахунку кількості HTTP-запитів
  • Enum: має попередньо задані значення, використовується наприклад для моніторингу кількості подів у статус Running або Failed
  • Histograms: зберігає значення за проміжок часу, можна використовавти для, наприклад, отримання часу відповіді веб-северу за період часу – rate(metric_name{}[5m])
  • Gauges: може приймати будь-яке значення, можемо використовувати для, наприклад, зберігання значень нагрузки на CPU
  • Info: key-value storage, наприклад для Build information, Version information, або metadata

У кожного типу є свої методи, тож варто подивитись документацію, там ще й приклади є. Див. Prometheus Python Client, або в документації самої бібліотеки:

[simterm]

>>> import prometheus_client
>>> help(prometheus_client.Enum)

[/simterm]

Python Custom Prometheus Exporter

Python HTTPServer Exporter з Counter

Для початку, давайте подивимось як воно взагалі працює – напишимо скрипт на Python, в якому на порту 8080 буде звичайний HTTP-сервер, а на порту 9000 – експортер, який буде збирати статистику по запитах з кодами відповідей і створювати метрику http_requests з двома лейблами – в одній будемо зберігати код відповіді, а в іншій – ім’я хоста, з якого метрика була отримана.

Встановлюємо бібліотеку:

[simterm]

$ pip install prometheus_client

[/simterm]

Пишемо скрипт:

#!/usr/bin/env python

import os
import re
import platform
from time import sleep

from http.server import BaseHTTPRequestHandler, HTTPServer

from prometheus_client import start_http_server, Counter, REGISTRY

class HTTPRequestHandler(BaseHTTPRequestHandler):
    
    def do_GET(self):
        if self.path == '/':
            self.send_response(200)
            self.send_header('Content-type','text/html')
            self.end_headers()
            self.wfile.write(bytes("<b> Hello World !</b>", "utf-8"))
            request_counter.labels(status_code='200', instance=platform.node()).inc()
        else:
            self.send_error(404)
            request_counter.labels(status_code='404', instance=platform.node()).inc()
    
                
if __name__ == '__main__':
            
    start_http_server(9000)
    request_counter = Counter('http_requests', 'HTTP request', ["status_code", "instance"])

    webServer = HTTPServer(("localhost", 8080), HTTPRequestHandler).serve_forever()
    print("Server started")

Тут ми:

  • start_http_server() – запускаємо HTTP-сервер самого експортеру на порту 9000
  • request_counter – створюємо метрику з ім’ям http_requests, типом Counter, і додаємо їй дві labels – status_code та instance
  • webServer – запускаємо звичайний HTTP-сервер на Python на порту 8080

Далі, коли ми робитимо HTTP-запит на localhost:8080, він буде попадати до do_GET(), в якому буде перевірятися URI. Якщо йдемо на / – то отримаємо код 200, якщо будь-який інший – то 404.

І там же оновлюємо значення метрики http_requests – додаємо код відповіді та ім’я хоста, і викликаємо метод Counter.inc(), який інкрементить значення метрики на одиницю. Таким чином кожен запит, який буде оброблено веб-сервером webServer буде додавати +1 в нашу метрику, а в залежності від коду відповіді – ми отримаємо цю метрику з двома різними лейблами – 200 та 404.

Перевіряємо – запускаємо сам скрипт:

[simterm]

$ ./py_http_exporter.py

[/simterm]

Робимо декілька запитів з різними URI:

[simterm]

$ curl -I -X GET localhost:8080/
HTTP/1.0 200 OK
$ curl -I -X GET localhost:8080/
HTTP/1.0 200 OK
$ curl -I -X GET localhost:8080/
HTTP/1.0 200 OK

$ curl -I -X GET localhost:8080/blablabla
HTTP/1.0 404 Not Found
$ curl -I -X GET localhost:8080/blablabla
HTTP/1.0 404 Not Found
$ curl -I -X GET localhost:8080/blablabla
HTTP/1.0 404 Not Found

[/simterm]

А тепер перевіримо, що маємо на ендпоінті експортеру:

[simterm]

$ curl -X GET localhost:9000
...
# HELP http_requests_total HTTP request
# TYPE http_requests_total counter
http_requests_total{instance="setevoy-wrk-laptop",status_code="200"} 3.0
http_requests_total{instance="setevoy-wrk-laptop",status_code="404"} 3.0

[/simterm]

Чудово – маємо три запроси з кодом 200, та три – з кодом 404.

Jenkins Jobs Exporter з Gauage

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

Наприклад, ми можемо звертатись до якогось API, і від нього отримати дані, в цьому прикладі це буде Jenkins:

#!/usr/bin/env python

import time
import random
from prometheus_client import start_http_server, Gauge
from api4jenkins import Jenkins

jenkins_client = Jenkins('http://localhost:8080/', auth=('admin', 'admin'))

jenkins_jobs_counter = Gauge('jenkins_jobs_count', "Number of Jenkins jobs")

def get_metrics():
   jenkins_jobs_counter.set(len(list(jenkins_client.iter_jobs())))

if __name__ == '__main__':
    start_http_server(9000)
    while True:
        get_metrics()
        time.sleep(15)

Тут ми за допомогою бібліотеки api4jenkins створюємо об’єкт jenkins_client, який підключається до інстансу Jenkins та отримує кількість його jobs. Потім в функції get_metrics() ми рахуємо кількість об’єктів із jenkins_client.iter_jobs(), та вносимо їх до метрики jenkins_jobs_counter.

Запускаємо в Docker:

[simterm]

$ docker run -p 8080:8080 jenkins:2.60.3

[/simterm]

Створюємо тестову задачу:

В результаті отримуємо такий результат:

[simterm]

$ curl localhost:9000
...
# HELP jenkins_jobs_count Number of Jenkins jobs
# TYPE jenkins_jobs_count gauge
jenkins_jobs_count 1.0

[/simterm]

Prometheus Exporter та Kubernetes

І давайте протестимо якийсь більш реальний приклад.

У нас є API-сервіс, який використовує базу даних PostgreSQL. Для перевірки підключення девелопери створили ендпоінт, на який ми можемо звертатися для отримання поточного статусу – є чи нема підключення до серверу баз даних.

Зараз для його моніторингу ми використовуємо Blackbox Exporter, але згодом хочется трохи розширити можливості, тож спробуємо створити експортер, котрий поки що буде просто перевіряти код відповіді він цього ендпоінту.

Exporter з Enum та Histogram

#!/usr/bin/env python

import os
import requests
import time
from prometheus_client import start_http_server, Enum, Histogram

hitl_psql_health_status = Enum("hitl_psql_health_status", "PSQL connection health", states=["healthy", "unhealthy"])
hitl_psql_health_request_time = Histogram('hitl_psql_health_request_time', 'PSQL connection response time (seconds)')

def get_metrics():

    with hitl_psql_health_request_time.time():
        resp = requests.get(url=os.environ['HITL_URL'])
    
    print(resp.status_code)
            
    if not (resp.status_code == 200):
        hitl_psql_health_status.state("unhealthy")
            
if __name__ == '__main__':
    start_http_server(9000)
    while True:
        get_metrics()
        time.sleep(1)

Задаємо змінну оточення з URL:

[simterm]

$ export HITL_URL=https://hitl.qa.api.example.com/api/v1/postgres/health-check

[/simterm]

Та запускаємо скрипт:

[simterm]

$ ./py_hitl_exporter.py 
500
500
500

[/simterm]

Ммм… Чудово – ендпоінт лежить 🙂 То в понеділок нехай девелопери перевірять, нам підходить і це.

Docker-образ

Далі, збираємо Docker-образ – спочатку створимо requirements.txt з залежностями:

requests
prometheus_client

Теперь Dockerfile:

FROM python:3.8

COPY py_hitl_exporter.py /app/py_hitl_exporter.py
COPY requirements.txt /app/requirements.txt
WORKDIR /app
RUN pip install -r requirements.txt
ENV HITL_URL $HITL_URL
CMD ["python3", "/app/py_hitl_exporter.py"]

Збираємо образ:

[simterm]

$ docker build -t setevoy/test-exporter .

[/simterm]

Запускаємо локально для перевірки:

[simterm]

$ docker run -p 9000:9000 -e HITL_URL=https://hitl.qa.api.example.com/api/v1/postgres/health-check setevoy/test-exporter

[/simterm]

Перевіряємо метрики:

[simterm]

$ curl localhost:9000
...
# HELP hitl_psql_health_status PSQL connection health
# TYPE hitl_psql_health_status gauge
hitl_psql_health_status{hitl_psql_health_status="healthy"} 0.0
hitl_psql_health_status{hitl_psql_health_status="unhealthy"} 1.0
# HELP hitl_psql_health_request_time PSQL connection response time (seconds)
# TYPE hitl_psql_health_request_time histogram
hitl_psql_health_request_time_bucket{le="0.005"} 0.0
hitl_psql_health_request_time_bucket{le="0.01"} 0.0
hitl_psql_health_request_time_bucket{le="0.025"} 0.0
hitl_psql_health_request_time_bucket{le="0.05"} 0.0
hitl_psql_health_request_time_bucket{le="0.075"} 0.0
hitl_psql_health_request_time_bucket{le="0.1"} 0.0
hitl_psql_health_request_time_bucket{le="0.25"} 0.0
hitl_psql_health_request_time_bucket{le="0.5"} 0.0
hitl_psql_health_request_time_bucket{le="0.75"} 0.0
hitl_psql_health_request_time_bucket{le="1.0"} 0.0
hitl_psql_health_request_time_bucket{le="2.5"} 0.0
hitl_psql_health_request_time_bucket{le="5.0"} 0.0
hitl_psql_health_request_time_bucket{le="7.5"} 0.0
hitl_psql_health_request_time_bucket{le="10.0"} 0.0
hitl_psql_health_request_time_bucket{le="+Inf"} 9.0
hitl_psql_health_request_time_count 9.0
hitl_psql_health_request_time_sum 96.56228125099824

[/simterm]

Добре.

Пушимо його в Docker Hub:

[simterm]

$ docker login
$ docker push setevoy/test-exporter

[/simterm]

Kubernetes Pod, Service та ServiceMonitor для Prometheus

Далі – треба цей образ запустити в Kubernetes, та створити ServiceMonitor для Prometheus, який там вже запущено.

Створюємо маніфест з Pod, Service та ServiceMonitor:

apiVersion: v1
kind: Pod
metadata:
  name: hitl-exporter-pod
  labels:
    app: hitl-exporter
spec:
  containers:
    - name: hitl-exporter-container
      image: setevoy/test-exporter
      env:
      - name: HITL_URL
        value: https://hitl.qa.api.example.com/api/v1/postgres/health-check
      ports:
        - name: metrics
          containerPort: 9000
          protocol: TCP
---     
apiVersion: v1
kind: Service
metadata:
  name: hitl-exporter-service
  labels:
    app: hitl-exporter
spec:
  selector:
    app: hitl-exporter
  ports:
    - name: metrics
      port: 9000
      targetPort: metrics
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  labels:
    release: kps
    app: hitl-exporter
  name: hitl-exporter-monitor
spec:
  endpoints:
    - port: metrics
      interval: 15s
      scrapeTimeout: 10s
  namespaceSelector:
    matchNames:
    - monitoring
  selector:
    matchLabels:
      app: hitl-exporter

Запускаємо:

[simterm]

$ kubectl -n monitoring apply -f hitl_exporter.yaml 
pod/hitl-exporter-pod created
service/hitl-exporter-service created
servicemonitor.monitoring.coreos.com/hitl-exporter-monitor created

[/simterm]

І за хвилину-дві перевіряємо Service Discovery:

Targets:

Та Jobs:

Переходимо до графіків – і маємо наші метрики:

Готово.

Loading

GitLab: Helm-чарт values, залежності та деплой у Kubernetes з AWS S3
0 (0)

4 Лютого 2023

Продовжуємо зайомство з GitLab та його деплоєм у Kubernetes. Перша частина –  GitLab: компоненти, архітектура, інфраструктура та запуск із Helm-чарту в Minikube, тепер давайте готуватися деплоїти в AWS Elastic Kubernetes Service.

Що робитимемо і де:

  • деплоїмо в AWS з Helm-чарту, для початку якийсь “test env”
  • Kubernetes – AWS EKS
  • object store – AWS S3
  • PostgreSQL – з operator
  • Redis – поки використовуємо дефолтний, потім переїдемо на KeyDB, який теж розгортається оператором
  • Gitaly – спробуємо в кластері, можливо, на окремій ноді – у нас всього 150-200 користувачів, навантаження не повинно бути великим, скейлінг і тим більше Praefik не потрібні

GitLab Operator виглядає в цілому цікаво, але в документації через слово “Experimental” і “Beta”, тому поки не чіпаємо – використовуємо чарт.

Helm chart prerequisites

По чарту: для початку треба пройтися по доступним параметрам чарту, і подивитися, що нам взагалі треба буде із зовнішніх ресурсів (лоад-балансери, корзини S3, PostgreSQL), і що ще можна налаштувати через чарт.

Не забуваємо подивитися GitLab chart prerequisites , з якого нам поки що цікаві:

  • PostgreSQL: ми будемо використовувати оператор, та розгорнемо свій кластер з блекджеком і базами
  • Redis: у нас є KeyDB-оператор, потім будемо використовувати його, поки дефолтний із чарту
  • Networking and DNS: використовуємо AWS ALB Contoller, створимо ACM сертифікат, DNS у Route53, записи створюються через External DNS
  • Persistence: цікавий нюанс, можливо, треба буде налаштувати reclaimPolicy to Retain, див. Configuring Cluster Storage
  • Prometheus: теж поінт на подумати – у нас є Kube Prometheus Stack зі своїм Prometheus та Alertmanager, треба буде подумати чи відключати вбудований у GitLab, чи залишати
  • Outgoing email: поки не налаштовуватимемо, але потім треба буде подумати. У принципі є підтримка AWS SES, десь у документації зустрічав, так що нормально
  • RBAC: у нас є, підтримується, тож залишаємо за замовчуванням, тобто включаємо

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

Структура Helm-чарту та його values.yaml

У GitLab Helm-чарту досить складна структура його values, так як чарт включає набір сабчартів і залежностей, див. GitLab Helm subcharts .

Щоб краще зрозуміти структуру його values – можна подивитися структуру каталогу charts з дочірніми чартами:

[simterm]

$ tree -d gitlab/charts/
gitlab/charts/
|-- certmanager-issuer
|   `-- templates
|-- gitlab
|   |-- charts
|   |   |-- geo-logcursor
|   |   |   `-- templates
|   |   |-- gitaly
|   |   |   `-- templates
|   |   |-- gitlab-exporter
|   |   |   `-- templates
|   |   |-- gitlab-grafana
|   |   |   `-- templates
|   |   |-- gitlab-pages
|   |   |   `-- templates
|   |   |-- gitlab-shell
|   |   |   `-- templates
|   |   |-- kas
|   |   |   `-- templates
|   |   |-- mailroom
|   |   |   `-- templates
|   |   |-- migrations
|   |   |   `-- templates
|   |   |-- praefect
|   |   |   `-- templates
|   |   |-- sidekiq
|   |   |   `-- templates
|   |   |-- spamcheck
|   |   |   `-- templates
|   |   |-- toolbox
|   |   |   `-- templates
|   |   `-- webservice
|   |       `-- templates
|   |           `-- tests
|   `-- templates
|-- minio
|   `-- templates
|-- nginx-ingress
|   `-- templates
|       `-- admission-webhooks
|           `-- job-patch
`-- registry
    `-- templates

[/simterm]

Крім того, є набір зовнішніх залежностей, див requirements.yaml.

Відповідно у values основного чарту параметри розбиті на global (див. Subcharts and Global Values), які використовуються дочірніми чартами, та параметри для конкретних чартів, див. Configure charts using globals, тобто наш values.yaml виглядатиме так:

global: 

  hosts:
    domain:
    hostSuffix: # використовується у ./charts/gitlab/charts/webservice, ./charts/gitlab/charts/toolbox/, ./charts/registry/, etc
  ...

gitlab:
  kas:
    enabled: # використовується у ./charts/gitlab/charts/kas/
...

nginx-ingress:
  enabled: # використовується для ./gitlab/charts/nginx-ingress
...

postgresql:
  install: # використовується при встановленні зовнішнього чарту postgresql

Тепер подивимося на самі values, і які нам можуть бути корисні.

Helm chart values

“Стартова точка” для роботи з чартом – Installing GitLab by using Helm .

Ще добре пройтися дефолтним values.yaml– там у коментарях є посилання на сервіси/параметри.

Власне, параметри, які нам можуть бути цікаві, див. у Configure charts using globals та GitLab Helm chart deployment options :

  • global.hosts:
    • domain: вказуємо домен, в якому будуть створюватися записи для GitLab
    • externalIP: він начебто обов’язковий, але оскільки у нас буде AWS ALB, то не використовуємо
    • hostSuffixtest – додамо суффікс до субдоменів, що створюються, отримаємо запис виду gitlab-test.example.com
    • httpstrue , буде ALB із AWS Certificate Manager
    • ssh: тут потрібно буде задати окремий субдомен для доступу до GitLab Shell
  • global.ingress: так як у нас AWS ALB, то див. alb-full.yaml
    • annotations.*annotation-key*: додамо аннотації для ALB
    • configureCertmanager: треба буде відключити, так як SSL термінейтимо на лоад-балансері
    • tls.secretName: нам не потрібен, так як SSL термінейтимо на лоад-балансері
    • path: для ALB буде потрібен /*
    • provider: для ALB == aws
  • global.gitlabVersion: думав тут задати версію Gitlab, яку будемо деплоїти, але виявилося, що ні – правильніше через версію чарту, див. GitLab Version
  • global.psql: параметри для підключення до PostgreSQL сервісами GitLab, див. Configure PostgreSQL settings
    • host: адреса PosgtreSQL Service
    • databaseusername: зрозуміло
    • password:
      • useSecret: пароль зберігатимемо в Secret
      • secret: ім’я секрету
      • key: ключ/поле у ​​Секреті, за яким отримуємо пароль
  • global.redis: взагалі буде зовнішній, KeyDB, і тут треба буде вказати параметри доступу до нього, але спочатку залишимо дефолтний, див. Configure Redis settings
  • global.grafana.enabled: включаємо – подивимося, які там є дашборди (таки треба буде налаштовувати окремо, див. Grafana JSON Dashboards )
  • global.registry: див. Using the Container Registry
    • bucket: треба буде створити S3 бакет, цей параметр використовується /charts/gitlab/charts/toolbox/ для бекапів, сам GitLab Registry налаштовується через окремі параметри, розглянемо нижче
  • global.gitaly: поки використовуємо з чарту, незважаючи на рекомендації – будемо деплоїти у вигляді Kubernetes Pod в кластер, залишаємо за замовчуванням, але маємо на увазі
  • global.minio: відключимо – використовуємо відразу AWS S3
  • global.appConfig: ось тут прям багато всього, див. Configure appConfig settings – містить загальні параметри для чартів WebserviceSidekiq та Gitaly
    • cdnHost: взагалі-то корисна начебто річ, але подивимося, як його використовувати, поки не чіпаємо
    • contentSecurityPolicy: теж корисна штука, але налаштуємо потім, див. Content Security Policy
    • enableUsagePing: “телеметрія” для самого GitLab Inc, не бачу сенсу, відключимо
    • enableSeatLink: не зрозумів, що це, посилання seat link support веде в “нікуди” – на сторінці інформації про seat не знайшов, але мабуть це щось пов’язане з кількістю користувачів в ліцензії, а оскільки ми її не купуємо – то можна вимкнути
    • object_store: загальні параметри для роботи з S3 типу ключів доступу та proxy, див. Consolidated object storage
      • connection: тут потрібно буде створити секрет, в якому описуються налаштування підключення до корзин, у тому числі ACCESS/SECRET ключі, але ми замість ключів використовуємо ServiceAccount та IAM role
    • Specify buckets : які корзини потрібні, є набір дефолтних імен типу gitlab-artifacts , але для dev- та test- має сенс перевизначити
      • storage_options: шифрування для бакетів, має сенс змінювати якщо використовується AWS KMS, але ми швидше за все залишимо дефолтне шифрування
    • LFS, Artifacts, Uploads, Packages, External MR diffs, and Dependency Proxy – налаштування корзин для різних сервісів, поки не чіпатимемо, подивимося по ходу використання
    • gitlab_kas: налаштування для GitLab Agent for Kubernetes, поки залишимо за замовчуванням, не впевнений, що він нам знадобиться
    • omniauth: налаштування для SSO, якось потім, взагалі будемо підключати аутентифікацію через Google, див. OmniAuth
  • global.serviceAccounts: замість ACCESS/SECRET будемо використовувати Kubernetes ServiceAccounts з IAM Role для подів:
    • create: включаємо створення SA для сервісів
    • annotations: а тут вкажемо ARN IAM-ролі
  • global.nodeSelector: корисно, потім будемо на виділених нодах
  • global.affinity&& global.antiAffinity: плюс можна налаштувати Affinity
  • global.common.labels: задамо тут всякі environment , team , etc
  • global.tracing: поки не чіпаємо, але маємо на увазі на майбутнє – потім додамо якийсь Jaeger/Grafana Tempo
  • global.priorityClassName: можливо корисно або навіть потрібно, потім потикаємо, див. Pod Priority and Preemption

З глобальних змінних це начебто все, крім них нам треба буде:

  • вимкнути certmanager – використуємо AWS Certificate Manager на AWS LoadBalncer
  • вимкнути postgresql – використуємо зовнішній
  • відключити gitlab-runner – у нас вже є запущені раннери, потім спробуємо прикрутити їх до цього інстансу GitLab
  • відключити nginx-ingress – у нас AWS ALB Controller та AWS ALB балансери
  • налаштувати Services для webservice та gitlab-shell
  • налаштувати registry

Тепер перед деплоєм чарту треба підготувати зовнішні сервіси:

  • кластер та база PostgreSQL
  • бакети AWS S3
  • сертифікат в AWS Certificate Manager для лоад-балансеру

Підготовка – створення зовнішніх ресурсів

PostgreSQL

У нас використовується PostgreSQL Operator – описуємо створення кластера.

Параметри по коннектам, пам’яті, диску та реквестам-лімітам поки що дефолтні – подивимося по ходу діла, як сильно навантажуватиметься база.

Описуємо створення бази gitlabhq_test та дефолтних юзерів – defaultUsers=true:

kind: postgresql
apiVersion: acid.zalan.do/v1
metadata:
  name: gitlab-cluster-test-psql
  namespace: gitlab-cluster-test
  labels:
    team: devops
    environment: test
spec:
  teamId: devops
  postgresql:
    version: "14"
    parameters:
      max_connections: '100'
      shared_buffers: 256MB
      work_mem: 32MB
  numberOfInstances: 3
  preparedDatabases:
    gitlabhq_test:
      defaultUsers: true
      schemas:
        public:
          defaultRoles: false
  enableMasterLoadBalancer: false
  enableReplicaLoadBalancer: false
  enableConnectionPooler: false
  enableReplicaConnectionPooler: false
  volume:
    size: 10Gi
    storageClass: encrypted
  resources:
    requests:
      cpu: "100m"
      memory: 100Mi
    limits:
      memory: 1024Mi
  enableLogicalBackup: true
  logicalBackupSchedule: "0 1 * * *"
  sidecars:
    - name: exporter
      image: quay.io/prometheuscommunity/postgres-exporter:v0.11.1
      ports:
        - name: exporter
          containerPort: 9187
          protocol: TCP
      resources:
        limits:
          memory: 50M
        requests:
          cpu: 50m
          memory: 50M
      env:
      - name: DATA_SOURCE_URI
        value: localhost/postgres?sslmode=disable
      - name: DATA_SOURCE_USER
        value: "$(POSTGRES_USER)"
      - name: DATA_SOURCE_PASS
        value: "$(POSTGRES_PASSWORD)"
      - name: PG_EXPORTER_AUTO_DISCOVER_DATABASES
        value: "true"

Створюємо неймспейс:

[simterm]

$ kk create ns gitlab-cluster-test

[/simterm]

Деплоїмо кластер:

[simterm]

$ kk apply -f postgresql.yaml 
postgresql.acid.zalan.do/gitlab-cluster-test-psql created

[/simterm]

Перевіряємо поди:

[simterm]

$ kk -n gitlab-cluster-test get pod
NAME                                READY   STATUS              RESTARTS   AGE
devops-gitlab-cluster-test-psql-0   2/2     Running             0          24s
devops-gitlab-cluster-test-psql-1   0/2     ContainerCreating   0          4s

[/simterm]

Окей, створюються.

Перевіряємо Секрети – потім використуємо секрет юзера postgres у конфігах GitLab:

[simterm]

$ kk -n gitlab-cluster-test get secret
NAME                                                                                             TYPE                                  DATA   AGE
default-token-2z2ct                                                                              kubernetes.io/service-account-token   3      4m12s
gitlabhq-test-owner-user.devops-gitlab-cluster-test-psql.credentials.postgresql.acid.zalan.do    Opaque                                2      65s
gitlabhq-test-reader-user.devops-gitlab-cluster-test-psql.credentials.postgresql.acid.zalan.do   Opaque                                2      65s
gitlabhq-test-writer-user.devops-gitlab-cluster-test-psql.credentials.postgresql.acid.zalan.do   Opaque                                2      66s
postgres-pod-token-p7b4g                                                                         kubernetes.io/service-account-token   3      66s
postgres.devops-gitlab-cluster-test-psql.credentials.postgresql.acid.zalan.do                    Opaque                                2      66s
standby.devops-gitlab-cluster-test-psql.credentials.postgresql.acid.zalan.do                     Opaque                                2      66s

[/simterm]

Наче ОК.

AWS S3

Тепер створимо корзини.

Нам будуть потрібні:

Щоб уникнути помилки BucketAlreadyExists, до імен корзин додаємо or як власний “ідентифікатор”, бо імена загальні, і ім’я оточення – test , тобто список виходить такий:

  • or-gitlab-registry-test
  • or-gitlab-artifacts-test
  • or-git-lfs-test
  • or-gitlab-packages-test
  • or-gitlab-uploads-test
  • or-gitlab-mr-diffs-test
  • or-gitlab-terraform-state-test
  • or-gitlab-ci-secure-files-test
  • or-gitlab-dependency-proxy-test
  • or-gitlab-pages-test
  • or-gitlab-backups-test
  • or-gitlab-tmp-test

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

Створюємо з AWS CLI:

[simterm]

$ aws --profile internal s3 mb s3://or-gitlab-registry-test
make_bucket: or-gitlab-registry-test

[/simterm]

Повторюємо для решти корзин.

AWS Certificate Manager

Для Ingress нам потрібен TLS-сертифікат, див. Requesting a public certificate, а для отримання сертифіката – вочевидь, що домен.

У нашому випадку використовуємо internal.example.com, для якого створимо wildcard-сертифікат в ACM:

У FQDN вказуємо *.internal.example.com, щоб включити всі субдомени. Потім для GitLab використуємо параметри global.hosts.domain=internal.example.com та hostSuffix=test, що в результаті створить кілька Ingress та Services, які через ExternalDNS створять необхідні записи у Route53.

У Validation Method вибираємо DNS – найпростіший, тим більш що доменна зона хоститься в Route53 – все створюється в пару кліків:

Переходимо до сертифікату – він зараз у Pending validation, клікаємо Create records in Route53 :

Тепер статус Issued, запам’ятовуємо його ARN – він нам знадобиться у values:

AWS IAM Policy та IAM Role для ServiceAccount

Для ServiceAccount, який повинен буде давати доступ до AWS S3, потрібно створити IAM Policy та IAM Role. Детально див. Kubernetes ServiceAccounts з IAM Role для подів, тут швиденько.

Створюємо поліси:

{
    "Statement": [
        {
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject",
                "s3:ListMultipartUploadParts",
                "s3:AbortMultipartUpload"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::or-gitlab-registry-test/*",
                "arn:aws:s3:::or-gitlab-artifacts-test/*",
                "arn:aws:s3:::or-git-lfs-test/*",
                "arn:aws:s3:::or-gitlab-packages-test/*",
                "arn:aws:s3:::or-gitlab-uploads-test/*",
                "arn:aws:s3:::or-gitlab-mr-diffs-test/*",
                "arn:aws:s3:::or-gitlab-terraform-state-test/*",
                "arn:aws:s3:::or-gitlab-ci-secure-files-test/*",
                "arn:aws:s3:::or-gitlab-dependency-proxy-test/*",
                "arn:aws:s3:::or-gitlab-backups-test/*",
                "arn:aws:s3:::or-gitlab-tmp-test/*"
            ]
        },
        {
            "Action": [
                "s3:ListBucket",
                "s3:ListAllMyBuckets",
                "s3:GetBucketLocation",
                "s3:ListBucketMultipartUploads"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::or-gitlab-registry-test",
                "arn:aws:s3:::or-gitlab-artifacts-test",
                "arn:aws:s3:::or-git-lfs-test",
                "arn:aws:s3:::or-gitlab-packages-test",
                "arn:aws:s3:::or-gitlab-uploads-test",
                "arn:aws:s3:::or-gitlab-mr-diffs-test",
                "arn:aws:s3:::or-gitlab-terraform-state-test",
                "arn:aws:s3:::or-gitlab-ci-secure-files-test",
                "arn:aws:s3:::or-gitlab-dependency-proxy-test",
                "arn:aws:s3:::or-gitlab-backups-test",
                "arn:aws:s3:::or-gitlab-tmp-test"
            ]
        }
    ],
    "Version": "2012-10-17"
}

Створюємо IAM Role – вибираємо Web identity:

Підключаємо створену вище Policy:

Наче все? Можна писати values.

Деплой Gitlab Helm

Створення values.yaml

Всі дефолтні values ​​тут>>>, можна використати як приклад, але не варто повністю копіювати – у свій values ​​пишемо тільки те, що у нас відрізняється від дефолтного.

global

hosts

Configure Host settings

Вказуємо домен, використовуючи який чарт пропише значення для Ingress та Services які буде створювати – отримаємо набір доменів виду gitlab.internal.example.comregistry.internal.example.com і т.д.

Для SSH у прикладі для Ingress вказаний окремий субдомен, оскільки під SSH буде створюватися окремий Service з Network Load Balancer для доступу до 22 TCP.

hostSuffix додасть суфікс до створюваних записів, тобто в результаті будуть субдомени виду gitlab-test.internal.example.com та registry-test.internal.example.com .

Але для SSH hostSuffix не застосовується, тому вказуємо відразу з суфіксом.

Виходить так:

global:
  hosts:
    domain: internal.example.com
    hostSuffix: test
    ssh: gitlab-shell-test.internal.example.com

ingress

Configure Ingress settings

Беремо для прикладу alb-full.yaml, від себе додамо тільки load_balancing.algorithm.type=least_outstanding_requests:

ingress:
  # Common annotations used by kas, registry, and webservice
  annotations:
    alb.ingress.kubernetes.io/backend-protocol: HTTP
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:eu-central-1:514***799:certificate/7227a8fa-1124-441c-81d7-ec168180190d
    alb.ingress.kubernetes.io/group.name: gitlab
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS": 443}]'
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-group-attributes: load_balancing.algorithm.type=least_outstanding_request
    alb.ingress.kubernetes.io/target-type: ip
    kubernetes.io/ingress.class: alb
    nginx.ingress.kubernetes.io/connection-proxy-header: "keep-alive"
  class: none
  configureCertmanager: false
  enabled: true
  path: /*
  pathType: ImplementationSpecific
  provider: aws
  tls:
    enabled: false

Якщо використовувати KAS, йому потрібен буде окремий LoadBalancer, налаштуємо його трохи пізніше в блоці gitlab.kas.

psql

Configure PostgreSQL settings

Знаходимо ім’я Service, який був створений під час деплою PostgreSQL кластеру:

[simterm]

$ kk -n gitlab-cluster-test get svc
NAME                                     TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
devops-gitlab-cluster-test-psql          ClusterIP   172.20.67.135    <none>        5432/TCP   63m
devops-gitlab-cluster-test-psql-config   ClusterIP   None             <none>        <none>     63m
devops-gitlab-cluster-test-psql-repl     ClusterIP   172.20.165.249   <none>        5432/TCP   63m

[/simterm]

Користувача поки що візьмемо дефолтного postgress, з готового секрету:

[simterm]

$ kk -n gitlab-cluster-test get secret postgres.devops-gitlab-cluster-test-psql.credentials.postgresql.acid.zalan.do        
NAME                                                                            TYPE     DATA   AGE
postgres.devops-gitlab-cluster-test-psql.credentials.postgresql.acid.zalan.do   Opaque   2      67m

[/simterm]

Для production таки треба буде робити окремого, бо postgress == root .

Готуємо конфіг:

psql:
  host: devops-gitlab-cluster-test-psql
  database: gitlabhq_test
  username: postgres
  password:
    useSecret: true    
    secret: postgres.devops-gitlab-cluster-test-psql.credentials.postgresql.acid.zalan.do
    key: password

redis

Configure Redis settings

Configure Redis chart-specific settings

Configure the GitLab chart with an external Redis

Поки залишимо все за замовчуванням – Redis використовується тільки для кешування, подивимося, як він працюватиме і що по ресурсам.

registry

Configure Registry settings

В globals для registry вказуємо тільки корзину:

registry:
  bucket: or-gitlab-registry-test

gitaly

Поки що залишимо, як є. Потім подумаємо, може винесемо на окрему ноду.

Using the GitLab-Gitaly chart

Треба буде моніторити як він ресурси використовує, але у випадку з нашими 150-200 користувачів навряд чи там буде необхідність сильно допилювати його або тим більше розгортати кластер Praefect .

minio

Відключаємо, ходитимемо відразу в AWS S3:

minio:
  enabled: false

grafana

Включимо, подивимося, що в ній є (нічого 🙂):

grafana:
  enabled: true

appConfig

Список корзин є у values.

object_store таки треба налаштовувати – додати секрет, в якому буде вказано провайдер і регіон бакетів. Ключі не використовуємо – буде ServiceAccount.

Приклад візьмемо з , rails.s3.yaml, див. connection:

provider: AWS
region: eu-central-1

Створюємо Secret:

[simterm]

$ kk -n gitlab-cluster-test create secret generic gitlab-rails-storage --from-file=connection=rails.s3.yaml

[/simterm]

І описуємо appConfig, виходить так:

appConfig:
  enableUsagePing: false
  enableSeatLink: true # disable?
  object_store:
    enabled: true
    proxy_download: true
    connection:  
      secret: gitlab-rails-storage
      key: connection
  artifacts:
    bucket: or-gitlab-artifacts-test
  lfs:
    bucket: or-git-lfs-test
  packages:
    bucket: or-gitlab-packages-test
  uploads:
    bucket: or-gitlab-uploads-test
  externalDiffs:
    bucket: or-gitlab-mr-diffs-test
  terraformState:
    bucket: or-gitlab-terraform-state-test
  ciSecureFiles:
    bucket: or-gitlab-ci-secure-files-test
  dependencyProxy:
    bucket: or-gitlab-dependency-proxy-test
  backups:
    bucket: or-gitlab-backups-test
    tmpBucket: or-gitlab-tmp-test

serviceAccount

Див. Kubernetes: ServiceAccount з AWS IAM Role для Kubernetes Pod.

Начебто можна без створення ServiceAccount, просто через анотації до сервісів – IAM roles for AWS when using the GitLab chart.

Але ми будемо використовувати ServiceAccount, роль із політикою вже робили – додаємо:

serviceAccount:
  enabled: true
  create: true
  annotations: 
    eks.amazonaws.com/role-arn: arn:aws:iam::514***799:role/S3GitlabClusterTest

Так, з globals начебто все.

registry

Defining the Registry Configuration

Тут треба налаштувати storage, який зберігається в Secret, і в якому теж необхідно вказати ім’я корзини: параметр globals.registry буде використовуватися для бекапів, а параметр тут – самим сервісом Registry, див Docker Registry images .

Для прикладу візьмемо файл registry.s3.yaml, але без ключів, тому що для Registry буде створено свій ServiceAccoumt з IAM Role:

s3:
  bucket: or-gitlab-registry-test
  region: eu-central-1
  v4auth: true

Створюємо секрет:

[simterm]

$ kk -n gitlab-cluster-test create secret generic registry-storage --from-file=config=registry.s3.yaml

[/simterm]

Описуємо конфіг:

registry:
  enabled: true
  service:
    type: NodePort
  storage:
    secret: registry-storage
    key: config

gitlab – Services

Окремо описуємо Services для kaswebservice та gitlab-shell, з того ж прикладу alb-full.yaml.

Для gitlab-shell Service в аннотації external-dns.alpha.kubernetes.io/hostname вказуємо ім’я хоста:

gitlab:
  kas:
    enabled: true
    ingress:
      # Specific annotations needed for kas service to support websockets
      annotations:
        alb.ingress.kubernetes.io/healthcheck-path: /liveness
        alb.ingress.kubernetes.io/healthcheck-port: "8151"
        alb.ingress.kubernetes.io/healthcheck-protocol: HTTP
        alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=4000,routing.http2.enabled=false
        alb.ingress.kubernetes.io/target-group-attributes: stickiness.enabled=true,stickiness.lb_cookie.duration_seconds=86400
        alb.ingress.kubernetes.io/target-type: ip
        kubernetes.io/tls-acme: "true"
        nginx.ingress.kubernetes.io/connection-proxy-header: "keep-alive"
        nginx.ingress.kubernetes.io/x-forwarded-prefix: "/path"
    # k8s services exposed via an ingress rule to an ELB need to be of type NodePort
    service:
      type: NodePort
  webservice:
    enabled: true
    service:
      type: NodePort
  # gitlab-shell (ssh) needs an NLB
  gitlab-shell:
    enabled: true
    service:
      annotations:
        external-dns.alpha.kubernetes.io/hostname: "gitlab-shell-test.internal.example.com"
        service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
        service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
        service.beta.kubernetes.io/aws-load-balancer-type: "external"
      type: LoadBalancer

Інше

Відключаємо непотрібні нам сервіси:

certmanager:
  install: false

postgresql:
  install: false

gitlab-runner:
  install: false

nginx-ingress:
  enabled: false

Повний values.yaml

В результаті отримуємо такий конфіг:

global:

  hosts:
    domain: internal.example.com
    hostSuffix: test
    ssh: gitlab-shell-test.internal.example.com

  ingress:
    # Common annotations used by kas, registry, and webservice
    annotations:
      alb.ingress.kubernetes.io/backend-protocol: HTTP
      alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:eu-central-1:514***799:certificate/7227a8fa-1124-441c-81d7-ec168180190d
      alb.ingress.kubernetes.io/group.name: gitlab
      alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS": 443}]'
      alb.ingress.kubernetes.io/scheme: internet-facing
      alb.ingress.kubernetes.io/target-group-attributes: load_balancing.algorithm.type=least_outstanding_requests
      alb.ingress.kubernetes.io/target-type: ip
      kubernetes.io/ingress.class: alb
      nginx.ingress.kubernetes.io/connection-proxy-header: "keep-alive"
    class: none
    configureCertmanager: false
    enabled: true
    path: /*
    pathType: ImplementationSpecific
    provider: aws
    tls:
      enabled: false

  psql:
    host: devops-gitlab-cluster-test-psql
    database: gitlabhq_test
    username: postgres
    password:
      useSecret: true
      secret: postgres.devops-gitlab-cluster-test-psql.credentials.postgresql.acid.zalan.do
      key: password

  registry:
    bucket: or-gitlab-registry-test

  minio:
    enabled: false

  grafana:
    enabled: true

  appConfig:
    enableUsagePing: false
    enableSeatLink: true # disable?
    object_store:
      enabled: true
      proxy_download: true
      connection:
        secret: gitlab-rails-storage
        key: connection
    artifacts:
      bucket: or-gitlab-artifacts-test
    lfs:
      bucket: or-git-lfs-test
    packages:
      bucket: or-gitlab-packages-test
    uploads:
      bucket: or-gitlab-uploads-test
    externalDiffs:
      bucket: or-gitlab-mr-diffs-test
    terraformState:
      bucket: or-gitlab-terraform-state-test
    ciSecureFiles:
      bucket: or-gitlab-ci-secure-files-test
    dependencyProxy:
      bucket: or-gitlab-dependency-proxy-test
    backups:
      bucket: or-gitlab-backups-test
      tmpBucket: or-gitlab-tmp-test

  serviceAccount:
    enabled: true
    create: true
    annotations:
      eks.amazonaws.com/role-arn: arn:aws:iam::514***799:role/S3GitlabClusterTest

  common:
    labels:
      environment: test

gitlab:
  kas:
    enabled: true
    ingress:
      # Specific annotations needed for kas service to support websockets
      annotations:
        alb.ingress.kubernetes.io/healthcheck-path: /liveness
        alb.ingress.kubernetes.io/healthcheck-port: "8151"
        alb.ingress.kubernetes.io/healthcheck-protocol: HTTP
        alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=4000,routing.http2.enabled=false
        alb.ingress.kubernetes.io/target-group-attributes: stickiness.enabled=true,stickiness.lb_cookie.duration_seconds=86400
        alb.ingress.kubernetes.io/target-type: ip
        kubernetes.io/tls-acme: "true"
        nginx.ingress.kubernetes.io/connection-proxy-header: "keep-alive"
        nginx.ingress.kubernetes.io/x-forwarded-prefix: "/path"
    # k8s services exposed via an ingress rule to an ELB need to be of type NodePort
    service:
      type: NodePort
  webservice:
    enabled: true
    service:
      type: NodePort
  # gitlab-shell (ssh) needs an NLB
  gitlab-shell:
    enabled: true
    service:
      annotations:
        external-dns.alpha.kubernetes.io/hostname: "gitlab-shell-test.internal.example.com"
        service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
        service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
        service.beta.kubernetes.io/aws-load-balancer-type: "external"
      type: LoadBalancer

registry:
  enabled: true
  service:
    type: NodePort
  storage:
    secret: registry-storage
    key: config

certmanager:
  install: false

postgresql:
  install: false

gitlab-runner:
  install: false

nginx-ingress:
  enabled: false

Gitlab deploy

Ну і начебто все? Давайте деплоїти:

[simterm]

$ helm repo add gitlab https://charts.gitlab.io/
$ helm repo update
$ helm upgrade --install --namespace gitlab-cluster-test gitlab gitlab/gitlab --timeout 600s -f gitlab-cluster-test-values.yaml

[/simterm]

Перевіряємо Ingresses:

[simterm]

$ kk -n gitlab-cluster-test get ingress
NAME                        CLASS    HOSTS                                ADDRESS                                                           PORTS   AGE
gitlab-grafana-app          <none>   gitlab-test.internal.example.com     k8s-***.eu-central-1.elb.amazonaws.com   80      117s
gitlab-kas                  <none>   kas-test.internal.example.com        k8s-***.eu-central-1.elb.amazonaws.com   80      117s
gitlab-registry             <none>   registry-test.internal.example.com   k8s-***.eu-central-1.elb.amazonaws.com   80      117s
gitlab-webservice-default   <none>   gitlab-test.internal.example.com     k8s-***.eu-central-1.elb.amazonaws.com   80      117s

[/simterm]

Відкриваємо URL https://gitlab-test.internal.example.com:

Отримуємо рутовий пароль:

[simterm]

$ kubectl -n gitlab-cluster-test get secret gitlab-gitlab-initial-root-password -ojsonpath='{.data.password}' | base64 --decode ; echo
cV6***y1t

[/simterm]

Логінимося під root:

Перевірка деплою

Не віриться мені, що з першого разу все завелося.

Repository

Перевіряємо роботу з репозиторієм, тобто роботу сервісів Gitaly та GitLab Shell.

Створюємо тестовий репозиторій:

Копіюємо адресу – з субдоменом gitlab-shell-test.internal.example.com, який вказували в конфігах:

Клонуємо:

[simterm]

$ git clone [email protected]:gitlab-instance-da4355a9/test-repo.git
Cloning into 'test-repo'...
The authenticity of host 'gitlab-shell-test.internal.example.com (3.***.***.79)' can't be established.
ED25519 key fingerprint is SHA256:xhC1Q/lduNbg49kGljYUb21YlBBsxrG89xE+iCHD+xc.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'gitlab-shell-test.internal.example.com' (ED25519) to the list of known hosts.
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.

[/simterm]

І зміст:

[simterm]

$ ll test-repo/
total 8
-rw-r--r-- 1 setevoy setevoy 6274 Feb  4 13:24 README.md

[/simterm]

Спробуємо пушнути якісь зміни назад:

[simterm]

$ cd test-repo/
$ echo test > test.txt
$ git add test.txt
$ git commit -m "test"
$ git push
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 16 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 285 bytes | 285.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To gitlab-shell-test.internal.example.com:gitlab-instance-da4355a9/test-repo.git
   7217947..4eb84db  main -> main

[/simterm]

І в WebUI:

Окей, працює.

Container Registry

Далі перевіримо Registry – приклади команд є у веб-інтерфейсі > Container Registry :

Логінимося з тим же логіном:паролем, які використовували для логіна в веб-інтерфейс GitLab (користувач root, пароль з секрету gitlab-gitlab-initial-root-password):

[simterm]

$ docker login registry-test.internal.example.com
Username: root
Password:
...
Login Succeeded

[/simterm]

Створюємо Dockerfile:

FROM busybox
RUN echo "hello world"

Збираємо образ, тегаємо з ім’ям нашого Registry:

[simterm]

$ docker build -t registry-test.internal.example.com/gitlab-instance-da4355a9/test-repo:1 .

[/simterm]

І пушимо:

[simterm]

$ docker push registry-test.internal.example.com/gitlab-instance-da4355a9/test-repo:1  
The push refers to repository [registry-test.internal.example.com/gitlab-instance-da4355a9/test-repo]
b64792c17e4a: Mounted from gitlab-instance-da4355a9/test 
1: digest: sha256:eb45a54c2c0e3edbd6732b454c8f8691ad412b56dd10d777142ca4624e223c69 size: 528

[/simterm]

Перевіряємо корзину or-gitlab-registry-test:

[simterm]

$ aws --profile internal s3 ls or-gitlab-registry-test/docker/registry/v2/repositories/gitlab-instance-da4355a9/test-repo/_manifests/tags/1/
                           PRE current/
                           PRE index/

[/simterm]

Окей – Registry працює, доступ до S3 є, з ServiceAccounts проблем начебто немає.

ServiceAccounts та S3 access

Але про всяк випадок перевіримо ServiceAccounts та доступ до S3 в інших сервісах, наприклад з поду Toolbox:

[simterm]

$ kk -n gitlab-cluster-test exec -ti gitlab-toolbox-565889b874-vgqcb -- bash
git@gitlab-toolbox-565889b874-vgqcb:/$ aws s3 ls or-gitlab-registry-test
                           PRE docker/

[/simterm]

Окей – тут також все працює.

Grafana

Перевіряємо Grafana – https://gitlab-test.internal.example.com/-/grafana/.

Логін root, пароль беремо із секрету:

[simterm]

$ kubectl -n gitlab-cluster-test get secret gitlab-grafana-initial-password -ojsonpath='{.data.password}' | base64 --decode ; echo
Cqc***wFS

[/simterm]

Працює. Дашборди треба підключати окремо, потім займемося, як дійдемо до моніторингу взагалі, поки див. Grafana JSON Dashboards .

Загалом, начебто все.

Подивимося, що далі – адміністрування, міграція проектів, моніторинг, секьюріті.

Loading

GitLab: компоненти, архітектура, інфраструктура та запуск з Helm-чарту в Minikube
0 (0)

2 Лютого 2023

Оскільки GitLab нещодавно змінив політику надання Free-доступу, і тепер по Free підписці буде доступно лише 5 користувачів, то вирішили ми переїжджати на self-hosted версію.

Взагалі з ліцензією у них цікаво: ціна залежить від кількості користувачів, купити можна щонайменше на рік, і після покупки зменшити кількість користувачів у ліцензії не можна (але можна збільшити).

GitLab буде жити в Kubernetes, і питань перед запуском багато, тим більше особисто я раніше GitLab взагалі не дуже користувався.

Деплоїти GitLab будемо через ArgoCD, запускати будемо в AWS Elastic Kubernetes Service, для object store використовуємо AWS S3. Але про це все потім, а для початку подивимося що GitLab є “зсередини” і як його взагалі деплоїти.

GitLab Operator vs GitLab Helm chart

Перше – GitLab Operator чи GitLab Helm chart?

Подивився на можливості – не побачив особливої різниці між Оператором та чартом, більше того, Оператор під капотом використовує той самий Helm-чарт.

За можливостями бекапа та відновлення – використовуються аналогічні утіліти та процес однаковий, див. General backup and restore guidance.

А ось в оновленні версії GitLab-інстансу документація Оператора виглядає простіше. Див. Upgrade the GitLab chart vs How the Operator handles GitLab upgrades.

І навіть сам Gitlab.com деплоїться їх чартом:

The largest known GitLab instance is on GitLab.com, which is deployed using our official GitLab Helm chart

Але в документації до Оператора прямо через слово зістрічаються “Experimental” та “Beta“, тому поки що напевно без нього.

Та й у будь-якому випадку все зав’язано на чарт, тож знайомитися будемо в основному з ним та його параметрами.

Архітектура та компоненти GitLab

Для ознайомлення:

Спрощена схема з документації:

Тут:

  • NGINX: приймаємо вхідні з’єднання
  • GitLab Workhorse: реверс-проксі для завантаження та вивантаження файлів, Git push/pull і т.д.
  • Puma: веб-сервер на Ruby, використовується GitLab для API та своїх веб-сторінок
  • Sidekiq: для створення та управління чергами завдань
    • використовує Redis для зберігання інформації про джоби
  • PostgreSQL: зберігання інформації про користувачів, права доступу, метаданих і т.д.
  • GitLab Shell: працює з репозиторіями по SSH та керує ключами доступів
    • звертається до Gitaly для обробки Git-об’єктів
    • надсилає інформацію в Redis для створення завдань у Sidekiq
  • Gitaly: Git RPC server, займається завданнями в репозиторіях, які отримує від GitLab Shell і GitLab веб-додатки

Інфраструктура

Для роботи GitLab потрібні PostgreSQL, Redis, бакети AWS S3 та поштовий сервіс для отримання та надсилання листів.

Helm-чарт за замовчуванням встановлює PostgreSQL та Redis, але для production PostgreSQL рекомендується встановлювати окремо, див. Configure the GitLab chart with an external database та Configure PostgreSQL, хоча ми будемо використовувати PostgreSQL Operator і крутити кластер PostgreSQL у Kubernetes.

Аналогічні вимоги Redis та Gitaly – їх теж бажано запускати не з чарту та не в Kubernetes-кластері. Див. Installing GitLab by using Helm. У нас замість Redis швидше за все буде KeyDB, теж через оператор, також у Kubernetes.

Документація з розгортання Gitaly говорить, що до 500 користувачів достатньо однієї окремої віртуальної машини з самим Gitaly. Якщо планується 1000 і більше користувачів, то рекомендується запускати Gitaly Cluster з Praefect. Див. Gitaly > High Availability. Враховуючи кількість користувачів у нас – не бачу сенсу виносити на окремий EC2, тому будемо деплоїти разом із чартом, потім подивимося на його роботу та ресурси, можливо винесемо на окрему ноду Kubernetes.

Для роботи з object store в чарті встановлюється Minio, для production рекомендується зовнішній сервіс, такий як AWS S3. Див. Configure the GitLab chart with an external object storage.

Оскільки ми в AWS і використовуємо AWS ALB Controller, тож є сенс вимкнути NGINX Controller, див. Customize the GitLab Ingress options.

Є гарний приклад інфраструктури в GitLab on AWS Partner Solution Deployment Guide, але це для прям серйозних масштабів, ми будемо робити простіше. Проте, сама схема цікава і корисна для розуміння загальної концепції при плануванні інфраструктури:

Крім того, є Reference architectures, в яких описані варіанти запуску GitLab під різні навантаження. З них нам можуть бути особливо цікаві Cloud native hybrid, які описують запуск у Kubernetes (hybrid – тому що частину сервісів таки рекомендується запускати не в кластері):

Для LoadBalancer рекомендується least outstanding requests замість стандартного round-robin – треба не забути.

Корисно пройтися по всіх доступних опціях, подивитися що і як можна налаштувати, див. GitLab Helm chart deployment options, займемося цим в наступному пості.

Моніторинг GitLab – окрема тема, дійдемо до неї пізніше, поки див. Monitoring GitLab.

Запуск GitLab в Minikube

Взагалі використовується для розробників, які пиляють фічі під Kubernetes, але ми використовуємо для знайомства з чартом GitLab. Див. Developing for Kubernetes with minikube.

Запускаємо Minikube:

[simterm]

$ minikube start --cpus 4 --memory 10240

[/simterm]

Включаємо Ingress плагін:

[simterm]

$ minikube addons enable ingress

[/simterm]

Клонуємо репозиторій та встановлюємо залежності – корисно на них подивитися, хоча вони описані в документації до чарту:

[simterm]

$ git clone https://gitlab.com/gitlab-org/charts/gitlab.git
$ cd gitlab
$ helm dependency update
...
Dependency gitlab did not declare a repository. Assuming it exists in the charts directory
Dependency certmanager-issuer did not declare a repository. Assuming it exists in the charts directory
Dependency minio did not declare a repository. Assuming it exists in the charts directory
Dependency registry did not declare a repository. Assuming it exists in the charts directory
Downloading cert-manager from repo https://charts.jetstack.io/
Downloading prometheus from repo https://prometheus-community.github.io/helm-charts
Downloading postgresql from repo https://raw.githubusercontent.com/bitnami/charts/eb5f9a9513d987b519f0ecd732e7031241c50328/bitnami
Downloading gitlab-runner from repo https://charts.gitlab.io/
Downloading grafana from repo https://grafana.github.io/helm-charts
Downloading redis from repo https://raw.githubusercontent.com/bitnami/charts/eb5f9a9513d987b519f0ecd732e7031241c50328/bitnami
Dependency nginx-ingress did not declare a repository. Assuming it exists in the charts directory
...

[/simterm]

Перевіряємо IP Мінікуба:

[simterm]

$ minikube ip
192.168.49.2

[/simterm]

І встановлюємо з values-minikube.yaml:

[simterm]

$ helm upgrade --install gitlab . --timeout 600s -f https://gitlab.com/gitlab-org/charts/gitlab/raw/master/examples/values-minikube.yaml --set global.hosts.domain=$(minikube ip).nip.io --set global.hosts.externalIP=$(minikube ip)

[/simterm]

Перевіряємо поди – тут прям багато всього:

[simterm]

$ kubectl get pod
NAME                                          READY   STATUS      RESTARTS      AGE
gitlab-gitaly-0                               1/1     Running     0             11m
gitlab-gitlab-exporter-5c8dbdc954-hr7jj       1/1     Running     0             11m
gitlab-gitlab-runner-7c4488ff58-bg8f5         0/1     Running     3 (39s ago)   8m30s
gitlab-gitlab-shell-7f9f5bb9ff-qlpxr          1/1     Running     0             11m
gitlab-gitlab-shell-7f9f5bb9ff-wc9rx          1/1     Running     0             11m
gitlab-kas-84cb5c548b-jbp69                   1/1     Running     0             11m
gitlab-kas-84cb5c548b-jxqqr                   1/1     Running     0             11m
gitlab-migrations-2-vw5jd                     0/1     Completed   0             8m30s
gitlab-minio-74467697bb-z8nms                 1/1     Running     0             11m
gitlab-minio-create-buckets-2-b6zl6           0/1     Completed   0             8m30s
gitlab-postgresql-0                           2/2     Running     0             11m
gitlab-prometheus-server-6bf4fffc55-6xpfm     2/2     Running     0             11m
gitlab-redis-master-0                         2/2     Running     0             11m
gitlab-registry-cd64f65dc-frsmt               1/1     Running     0             8m30s
gitlab-registry-cd64f65dc-nvfv2               1/1     Running     0             8m10s
gitlab-sidekiq-all-in-1-v2-59ccd7d6b9-9mpmz   1/1     Running     0             8m30s
gitlab-toolbox-6586c478f5-ktj5x               1/1     Running     0             7m58s
gitlab-webservice-default-6787f4b5db-kfp9h    2/2     Running     0             6m55s
gitlab-webservice-default-6787f4b5db-q62f9    2/2     Running     0             8m30s

[/simterm]

Сервіси:

[simterm]

$ kubectl get svc
NAME                         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                               AGE
gitlab-gitaly                ClusterIP   None             <none>        8075/TCP,9236/TCP                     11m
gitlab-gitlab-exporter       ClusterIP   10.110.108.219   <none>        9168/TCP                              11m
gitlab-gitlab-shell          NodePort    10.110.231.31    <none>        32022:32022/TCP                       11m
gitlab-kas                   ClusterIP   10.102.124.129   <none>        8150/TCP,8153/TCP,8154/TCP,8151/TCP   11m
gitlab-minio-svc             ClusterIP   10.99.213.94     <none>        9000/TCP                              11m
gitlab-postgresql            ClusterIP   10.102.38.18     <none>        5432/TCP                              11m
gitlab-postgresql-headless   ClusterIP   None             <none>        5432/TCP                              11m
gitlab-postgresql-metrics    ClusterIP   10.107.123.35    <none>        9187/TCP                              11m
gitlab-prometheus-server     ClusterIP   10.104.133.44    <none>        80/TCP                                11m
gitlab-redis-headless        ClusterIP   None             <none>        6379/TCP                              11m
gitlab-redis-master          ClusterIP   10.96.133.252    <none>        6379/TCP                              11m
gitlab-redis-metrics         ClusterIP   10.105.193.58    <none>        9121/TCP                              11m
gitlab-registry              ClusterIP   10.109.96.193    <none>        5000/TCP                              11m
gitlab-webservice-default    ClusterIP   10.108.231.229   <none>        8080/TCP,8181/TCP,8083/TCP            11m
kubernetes                   ClusterIP   10.96.0.1        <none>        443/TCP                               23m

[/simterm]

Заходимо на https://gitlab.192.168.49.2.nip.io:

Знаходимо пароль:

[simterm]

$ kubectl get secret gitlab-gitlab-initial-root-password -ojsonpath='{.data.password}' | base64 --decode ; echo
yNU***njU

[/simterm]

І логінімося під юзером root:

Давайте подивимося, що ми тут надеплоїли:

  • gitaly – знаємо
  • gitlab-exporter – збір метрик GitLab для його Prometheus
  • gitlab-runner – воркери для CI/CD
  • gitlab-shell – вже читали вище
  • kas – Kubernetes agent server для Gitlab Agent, “GitLab Agent for Kubernetes is an active in-cluster component for solving any GitLab<->Kubernetes integration tasks” – подивимось на нього уважніше іншим разом
  • migrations – джоби для роботи з міграціями бази даних
  • minioHigh Performance Object Storage, API compatible with Amazon S3 – використовується, коли немає S3
  • postgresql – база даних, читали
  • prometheus-server – моніторинг GitLab
  • redis-master – Редіс)
  • registry – Container Registry для зберігання образів
  • sidekiq – вже читали
  • toolbox – бекапи та інші утіліти
  • webservice-defaultGitLab Rails webserver

Подивимося, що є всередині – переходимо до Адмінки:

Ммм… Смачно) І багато. З адмініструванням GitLab треба буде розбиратися окремо.

Поки можна приступати до вивчення чарту, і пробувати його деплоїти в Kubernetes – див. GitLab: Helm-чарт values, залежності та деплой у Kubernetes з AWS S3.

Loading

Kubernetes: моніторинг вартості кластеру – Kubernetes Resource Report та Kubecost
0 (0)

23 Січня 2023

Дуже правильне діло – моніторити, наскільки ефективно використовується кластер, особливо, якщо ресурси деплояться розробниками, які не сильно вникають у requests, і встановлюють завищені значення “про запас”. Запас, звичайно, потрібен, але й просто так реквестити ресурси ідеї погана.

Наприклад, у вас є WorkerNode з 4 vCPU (4000 milicpu) та 16 ГБ оперативної пам’яті, і ви створюєте Kubernetes Deployment, у якому для подів задаєте CPU requests 2500m і 4 Гб пам’яті. Після запуску одного пода – він зареквестить більше половини доступного процесорного часу, і під час запуску другого поду Kubernetes повідомить про нестачу ресурсів на доступних нодах, що призведе до запуску ще одного WorkerNode, який, зрозуміло, відразиться на загальній вартості кластера.

Щоб уникнути цього, є кілька утиліт, таких як Kubernetes Resource Report та Kubecost.

Kube Resource Report

Kubernetes Resource Report – найпростіший у запуску та можливостях: просто виводить ресурси групуючи їх за типом, і відображає статистику – скільки CPU/MEM requested і скільки реально використовується.

Мені вона подобається саме простотою – просто запускаємо, раз в пару тижнів дивимося що відбувається в кластері, і за необхідності пінгуємо розробників із питанням “А вам насправді потрібно 100500 гіг пам’яті для цієї апки? »

Є Helm-чарт, але він оновлюється рідко, тому простіше встановити з маніфестів.

Створюємо Namespace:

[simterm]

$ kk create ns kube-resource-report
namespace/kube-resource-report created

[/simterm]

Завантажуємо репозиторій з kube-resource-report:

[simterm]

$ git clone https://codeberg.org/hjacobs/kube-resource-report
$ cd kube-resource-report/

[/simterm]

У каталозі  deploy вже є файл Kustomize – задамо в ньому деплой в наш неймспейс:

[simterm]

$ echo "namespace: kube-resource-report" >> deploy/kustomization.yaml

[/simterm]

Перевіряємо:

[simterm]

$ cat deploy/kustomization.yaml 
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - rbac.yaml
  - service.yaml
  - configmap.yaml
namespace: kube-resource-report

[/simterm]

Встановлюємо:

[simterm]

$ kubectl apply -k deploy/
serviceaccount/kube-resource-report created
clusterrole.rbac.authorization.k8s.io/kube-resource-report created
clusterrolebinding.rbac.authorization.k8s.io/kube-resource-report created
configmap/kube-resource-report created
service/kube-resource-report created
deployment.apps/kube-resource-report created

[/simterm]

Відкриваємо собі доступ до Service:

[simterm]

$ kubectl -n kube-resource-report port-forward svc/kube-resource-report 8080:80

[/simterm]

Відкриваємо репорт в браузері:

Далі переходимо, наприклад, до Namespaces, сортуємо колонки за CR (CPU Requested):

При наведенні курсора на повзунок Kube Resource Report підкаже оптимальне з його точки зору значення.

Далі думаємо самі – чи дійсно потрібно стільки requests, чи його можна зменшити.

У цьому випадку у нас Apache Druid з 16 подами, в кожній працює JVM, яка і процесор і пам’ять любить, і для Druid процесор бажано виділяти по одному ядру на кожен thread виконання Java, тому ОК – нехай буде 14,65 процесора.

Kubecost

Kubecost – це Kube Resource Report на стероїдах. Вміє рахувати трафік, відправляти алерти, генерує метрики для Prometheus, має свої дашборди Grafana, може підключатися до кількох кластерів Kubernetes та багато іншого.

Місцями не без багів, але в цілому штука приємна.

Правда, вартість бізнес-ліцензії в 499 доларів трохи завищена, як на мене. Втім для базових речей цілком доступна Free версія.

“Під капотом” використовує свій Prometheus для зберігання даних. Можна відключити і використовувати зовнішній, але не рекомендується .

Installation

Основні доступні параметри описані в документації на Github , також можна переглянути дефолтні values його Helm-чарту.

Потребує реєстрації для отримання ключа – заходимо на https://www.kubecost.com/install.html , вказуємо пошту – і відразу переадресує на інструкцію з вашим ключем:

Helm chart values

Поспішати з установкою не будемо – спочатку створимо свої values.

Якщо ви вже маєте Kube Prometheus Stack, є Grafana і NodeExporter – то в Kubecost їх має сенс відключити. Крім того, відключимо kube-state-metrics, що б не дублювали дані в моніторингу.

Що б Prometheus із нашого власного стека почав збирати метрики Kubecost – налаштуємо створення ServiceMonitor і додамо йому labels – тоді можна буде генерувати свої алерти та використовувати Grafana dashboard.

А ось якщо відключити запуск вбудованої Grafana – под с kubecost-cost-analyzer не стартує. Не знаю – бага чи фіча. Але в ній є свої дашборды які можуть бути корисними, тож можна залишити.

Ще можна включити networkCosts, але мені так і не вдалося побачити адекватних витрат на трафік – можливо, неправильно налаштував.

networkCosts може бути достатньо прожорливим по ресурсам – треба моніторити використання ЦПУ.

Власне, сам values.yaml:

kubecostToken: "c2V***f98"

kubecostProductConfigs:
  clusterName: development-qa-data-services

prometheus:
  kube-state-metrics:
    disabled: true
  nodeExporter:
    enabled: false
  serviceAccounts:
    nodeExporter:
      create: false

serviceMonitor:
  enabled: true
  additionalLabels:
    release: prometheus

networkCosts:
  enabled: true
  podMonitor:
    enabled: true
  config:
    services:
      amazon-web-services: true

Встановлюємо в Namespace kubecost:

[simterm]

$ helm repo add kubecost https://kubecost.github.io/cost-analyzer/
$ helm upgrade --install -n kubecost --create-namespace -f values.yaml kubecost kubecost/cost-analyzer

[/simterm]

Відкриваємо порт:

[simterm]

$ kubectl -n kubecost port-forward svc/kubecost-cost-analyzer 9090:9090

[/simterm]

Перевіряємо поды – чи запустився kubecost-cost-analyzer:

[simterm]

$ kubectl -n kubecost get pod
NAME                                         READY   STATUS    RESTARTS   AGE
kubecost-cost-analyzer-5f5b85bf59-f22ld      2/2     Running   0          59s
kubecost-grafana-6bd995d6f9-kslh2            2/2     Running   0          63s
kubecost-network-costs-22dps                 1/1     Running   0          64s
kubecost-network-costs-m7rf5                 1/1     Running   0          64s
kubecost-network-costs-tcdvn                 1/1     Running   0          64s
kubecost-network-costs-xzvsz                 1/1     Running   0          64s
kubecost-prometheus-server-ddb597d5c-dvrgc   2/2     Running   0          6m49s

[/simterm]

Відкриваємо доступ до kubecost-cost-analyzer Service:

[simterm]

$ kubectl -n kubecost port-forward svc/kubecost-cost-analyzer 9090:9090

[/simterm]

І переходимо на http://localhost:9090.

Тут скрин з Kubecost, який вже майже тиждень запущено на одному з наших кластерів:

Коротко оглянемо основні пункти меню.

Assets

Для розуміння витрат краще почати з пункту Assets , де виводиться вартість “заліза”:

Бачимо, що в день наш кластер коштує 43 долари.

Можна “провалитися” глибше в деталі кластера, і побачити розбивку по ресурсам – WorkerNodes, лоад-балансерам, дискам та вартість самого AWS Elastic Kubernetes Service:

Переходимо ще далі, в Nodes:

І дивимось деталі вартості по конкретній ноді:

Перевіряємо:

0.167 за годину, як Kubecost і репортить в Hourly Rate.

Для налаштування витрат на AWS Spot Instances – див. Spot Instance data feed та AWS Spot Instances.

Cost Allocation

Відображає куди в самому Кубері витрачаються ресурси:

Kubecost враховує вартість CPU та RAM на WorkerNodes і, відповідно, виводить вартість кожного неймспейса залежно від його requests і usage.

Див. Pod resource efficiency.

Тут наш Apache Druid має реквестів CPU на цілих 48 долларів за тиждень або 4.08 в день.

Переходимо далі, і маємо картину по конкретним контролерам – StatefulSet, Deployment:

Колонки тут:

  • CPU, RAM: вартість використовуваних ресурсів в залежності від вартості ресурсів WorkerNode
  • PV: вартість PersistentVolume у вибраному контролері, тобто для StaefulSet MiddleManager маємо PV, котрий являє собою AWS EBS, за який ми платимо гроші
  • Network: треба перевіряти, бо якось дивно рахує – дуже мало
  • LB: LoadBalancers по вартості в AWS
  • Shared: загальні ресурси, які не будут рахуватися окремо, наприклад неймспейс kube-system, налаштовується в http://localhost:9090/settings > Shared Cost
  • Efficiency: утилізація vs реквесты за формулою:
    ((CPU Usage / CPU Requested) * CPU Cost) + (RAM Usage / RAM Requested) * RAM Cost) / (RAM Cost + CPU Cost))
    основний показник ефективності ресурсу, див. Efficiency and Idle

Якщо перейти ще глибше – буде посилання на Grafana, де можна переглянути використання ресурсів конкретним подом:

Правда, “з коробки” не відображаються метрики в RAM Requested.

Для перевірки метрик можна зайти на локальний Pometheus:

[simterm]

$ kubectl -n kubecost port-forward svc/kubecost-prometheus-server 9091:80

[/simterm]

І так – kube_pod_container_resource_requests_memory_bytes пуста:

Тому що метрика теперь називаєтся kube_pod_container_resource_requests з resource="memory", треба оновити запрос в цій Графані:

avg(kube_pod_container_resource_requests{namespace=~"$namespace", pod="$pod", container!="POD", resource="memory"}) by (container)

__idle__

Витрати __idle__ – різниця між вартістю ресурсів виділених під існуючі об’єкти (поди, деплойменти) – їх реквести і реальний usage, та “заліза, що простоює”, на якому вони працюють, тобто не зайняті ЦПУ/пам’ять, які можна використовувати під запуск нових ресурсів.

Savings

Тут зібрані поради щодо оптимізації витрат:

Наприклад, у “Right-size your container requests” зібрані рекомендації щодо налаштувань реквестів для ресурсів – аналог репортів у Kubernetes Resource Report:

Глянемо той же Apache Druid:

Тут явний овер-реквест по CPU, і Kubecost рекомендує зменшити ці реквести:

Але про Druid вже писалося вище – JVM, на кожен под MiddleManager ми запускаємо один Supervisor із двома Tasks, а під кожну Task бажано виділяти по повному ядру. Тож залишаємо, як є.

Корисна штука “Delete unassigned resources” – у нас, наприклад, знайшлася пачка EBS, що не використовуються:

Health

Теж корисна штука, що відображає основні проблеми із кластером:

kubecost-network-costs активно використовує CPU, вище своїх реквестів, і Kubernetes його тротлить.

Alerts

Тут можемо налаштувати алерти, але мені вдалося налаштувати відправку тільки через Slack Webhook:

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

Prometehus Alertmanager можна налаштувати через values ​​, але використовується свій локальний, який запускається разом з Prometheus, а як йому налаштувати роути – не знайшов.

Приклад алерту, який можна налаштувати в Kubecost:

global:
  notifications:
    alertConfigs:
      alerts:
        - type: budget
          threshold: 1
          window: 1d
          aggregation: namespace
          filter: druid
    alertmanager:
      enabled: true
      fqdn: http://prometheus-kube-prometheus-alertmanager.monitoring.svc

Тут додаємо алерт з типом budget, в якому перевіряємо вартість неймспейсу druid за останній день, і алертимо, якщо він стає дорожчим за 1 долар.

Оновлюємо сетап:

[simterm]

$ helm upgrade --install -n kubecost -f values.yaml kubecost kubecost/cost-analyzer

[/simterm]

Алерт з’являється у списку:

Але на кнопку Test не реагує, і в локальному Alertmanager алерт не з’являється.

Slack webhook

Спробуємо через Slack webhook, документація тут>>>.

Створюємо Application:

Переходимо в Webhooks:

Активуємо та натискаємо Add New Webhook:

Вибираємо канал:

Додаємо URL у Kubecost, і тестуємо:

Final values.yaml

Зрештою для тесту зібрав такий values:

kubecostToken: "c2V***f98"
kubecostProductConfigs:
  clusterName: development-qa-data-services
global:
  notifications:
    alertConfigs:
      globalSlackWebhookUrl: https://hooks.slack.com/services/T03***c1f
      alerts:
        - type: assetBudget
          threshold: 30
          window: 1d
          aggregation: type
          filter: 'Node'

        - type: assetBudget
          threshold: 4
          window: 1d
          aggregation: type
          filter: 'LoadBalancer'

        - type: assetBudget
          threshold: 3
          window: 1d
          aggregation: type
          filter: 'Disk'

        - type: assetBudget
          threshold: 40
          window: 3d
          aggregation: cluster
          filter: 'development-qa-data-services'

        - type: spendChange
          relativeThreshold: 0.01  # change relative to baseline average cost. Must be greater than -1 (can be negative).
          window: 1d
          baselineWindow: 7d       # previous window, offset by window
          aggregation: namespace
          filter: default, druid
        
        - type: spendChange
          relativeThreshold: 0.01
          window: 1d
          baselineWindow: 7d
          aggregation: cluster
          filter: 'development-qa-data-services'
        
        - type: health              # Alerts when health score changes by a threshold
          window: 10m
          threshold: 1
prometheus:
  kube-state-metrics:
    disabled: true
  nodeExporter:
    enabled: false
  serviceAccounts:
    nodeExporter:
      create: false

#serviceMonitor:
#  enabled: true
#  additionalLabels:
#    release: prometheus

networkCosts:
  enabled: true
  podMonitor:
    enabled: true
  config:
    destinations:
      direct-classification:
      - region: "us-west-2"
        zone: "us-west-2c"
        ips:
          - "10.0.64.0/19"
          - "10.0.160.0/20"
          - "10.0.208.0/21"
      - region: "us-west-2"
        zone: "us-west-2d"
        ips:
          - "10.0.216.0/21"
          - "10.0.96.0/19"
          - "10.0.176.0/20"
    services:
      amazon-web-services: true

Тут тестові алерти, які можна буде в принципі тягнути у продакшен.

ServiceMonitor для отримання метрик у зовнішньому Prometheus відключив, бо сенсу поки що не бачу – алертити буде через Slack Webhook своїми алертами, а дашборда для Grafana у вбудованій Графані краща, і їх там кілька.

Додав direct-classification для networkCosts – подивимося, можливо покаже більш правильні дані щодо трафіку.

#TODO

З чим поки що не вдалося розібратися:

  • алерти через Alertmanager (можливо, має сенс таки спробувати відключити внутрішній Prometheus)
  • Kubecost не бачить Node Exporter (перевіряти на сторінці http://localhost:9090/diagnostics ), але це наче ні на що не впливає – основні метрики отримує від cAdvisor
  • витрати на нетворкінг занадто маленькі (але це не точно)

Не робив/не тестував:

  • не налаштовував Cost Usage Reports для AWS , див. AWS Cloud Integration
  • не налаштовував AWS Spot Instances прайсинг
  • не додав Ingress, тому що у нас AWS ALB Controller, і треба робити авторизацію, а SAML в Kubecost доступний тільки в Premium

В цілому, на цьому все.

Система цікава та корисна, але є баги та складності, з якими треба розбиратися.

Loading