У нас в Kubernetes запускаються GitHub Runner для білда і деплоя нашого Backend API, див. GitHub Actions: запуск Actions Runner Controller в Kubernetes.
Але з часом ми звернули увагу, що на NAT Gateway бігає якось забагато трафіку – див. VictoriaLogs: дашборда в Grafana з AWS VPC Flow Logs – мігруємо з Grafana Loki.
Зміст
Проблема: трафік на AWS NAT Gateway
Коли почали перевіряти, то виявили цікаву деталь:
Тут через NAT GW пройшло 40.8 гігабайт даних за годину, з них 40.7 – Ingress.
З цих 40 GB в топі три Remote IP, кожен з яких передав нам майже по 10 GB трафіку (табличка зліва внизу на скріні вище).
В топі Remote IP у нас:
Remote IP Value Percent ------------------------------ 20.60.6.4 10.6 GB 28% 20.150.90.164 9.79 GB 26% 20.60.6.100 8.30 GB 22% 185.199.111.133 2.06 GB 5% 185.199.108.133 1.89 GB 5% 185.199.110.133 1.78 GB 5% 185.199.109.133 1.40 GB 4% 140.82.114.4 805 MB 2% 146.75.28.223 705 MB 2% 54.84.248.61 267 MB 1%
А в топі по трафіку в Kubernetes – у нас чотири Kubernetes Pods IP:
Source IP Pod IP Value Percent ----------------------------------------------- 20.60.6.4 => 10.0.43.98 1.54 GB 14% 20.60.6.100 => 10.0.43.98 1.49 GB 14% 20.60.6.100 => 10.0.42.194 1.09 GB 10% 20.150.90.164 => 10.0.44.162 1.08 GB 10% 20.60.6.4 => 10.0.44.208 1.03 GB 9%
І всі ці IP належать до подів з GitHub Runners, а “kraken” в імені – це як раз ті раннери для білдів і деплоїв нашого проекту “kraken“, бекенду:
Далі – цікавіше: якщо перевірити IP https://20.60.6.4 – то побачимо цікавий hostname:
*.blob.core.windows.net???
Шта? Дуже здивувався, бо у нас білдиться Python, і ніяких бібліотек від Mifcrosoft нема. Але потім з’явилась ідея: через те, що ми використовуємо кешування PiP і Docker в GitHub Actions для білдів Backend API, то скоріш за все це саме GitHub storage і є, і саме з нього ми ці кеши тягнемо в Kubernetes.
Аналогічна перевірка 185.199.111.133 та 140.82.114.4 нам показує *.github.io, а 54.84.248.61 – це вже athena.us-east-1.amazonaws.com.
Отже, що вирішили зробити – це запустити в Kubernetes локальне кешування з Sonatype Nexus, і його використовувати як проксі для PyPi.org і для Docker Hub images.
Про Docker caching поговоримо наступного разу, а сьогодні:
- протестуємо Nexus локально з Docker на робочій машині
- запустимо Nexus в Kubernetes з Helm-чарту
- налаштуємо і перевіримо роботу PyPi cache для білдів
- і подивимось на результати
Nexus: тестування локально з Docker
Запускаємо Nexus:
$ docker run -ti --rm --name nexus -p 8081:8081 sonatype/nexus3
Чекаємо кілька хвилин, бо Nexus на Java, тому стартує довго.
Отримуємо пароль адміна:
$ docker exec -ti nexus cat /nexus-data/admin.password 6221ad20-0196-4771-b1c7-43df355c2245
В браузері переходимо на http://localhost:8081, логінимось:
Якщо не зробили в Setup wizard, то заходимо в Security > Anonymous access, дозволяємо підключатись без аутентифікації:
Додавання репозиторію pypi (proxy)
Переходимо в Settings > Repositories, клікаємо Create repository:
Вибираємо тип pypi (proxy)
:
Створюємо репозиторій:
- Name:
pypi-proxy
- Remote storage:
https://pypi.org
- Blob store:
default
Знизу клікаємо Create repository.
Перевіримо які дані у нас зараз в default
Blob storage – заходимо в контейнер Nexus:
$ docker exec -ti nexus bash bash-4.4$
І дивимось каталог /nexus-data/blobs/default/content/
– зараз тут пусто:
bash-4.4$ ls -l /nexus-data/blobs/default/content/ total 8 drwxr-xr-x 3 nexus nexus 4096 Nov 27 11:02 directpath drwxr-xr-x 2 nexus nexus 4096 Nov 27 11:02 tmp
Перевірка Nexus PyPi cache
Тепер перевіримо чи наш проксі-кеш працює.
Знаходимо IP контейнера з Nexus:
$ docker inspect nexus | jq '.[].NetworkSettings.IPAddress' "172.17.0.2"
Запускаємо ще один контейнер з Python:
$ docker run -ti --rm python bash root@addeba5d307c:/#
І виконуємо pip install --index-url http://172.17.0.2:8081/repository/pypi-proxy/simple setuptools --trusted-host 172.17.0.2
root@addeba5d307c:/# time pip install --index-url http://172.17.0.2:8081/repository/pypi-proxy/simple setuptools --trusted-host 172.17.0.2 Looking in indexes: http://172.17.0.2:8081/repository/pypi-proxy/simple Collecting setuptools Downloading http://172.17.0.2:8081/repository/pypi-proxy/packages/setuptools/75.6.0/setuptools-75.6.0-py3-none-any.whl (1.2 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.2/1.2 MB 81.7 MB/s eta 0:00:00 Installing collected packages: setuptools Successfully installed setuptools-75.6.0 ... real 0m2.595s ...
Бачимо, що виконався Downloading, і це зайняло 2.59 секунди.
Глянемо, що у нас тепер в default
Blob storage в Nexus:
bash-4.4$ ls -l /nexus-data/blobs/default/content/ total 20 drwxr-xr-x 3 nexus nexus 4096 Nov 27 11:02 directpath drwxr-xr-x 2 nexus nexus 4096 Nov 27 11:21 tmp drwxr-xr-x 3 nexus nexus 4096 Nov 27 11:21 vol-05 drwxr-xr-x 3 nexus nexus 4096 Nov 27 11:21 vol-19 drwxr-xr-x 3 nexus nexus 4096 Nov 27 11:21 vol-33
Вже якісь дані з’явились, ок.
Тестуємо pip
ще раз – спочатку видалимо встановлений пакет:
root@addeba5d307c:/# pip uninstall setuptools
І встановлюємо його ще раз, але тепер додаємо --no-cache-dir
, аби не використовувати локальний кеш в контейнері:
root@5dc925fe254f:/# time pip install --no-cache-dir --index-url http://172.17.0.2:8081/repository/pypi-proxy/simple setuptools --trusted-host 172.17.0.2 Looking in indexes: http://172.17.0.2:8081/repository/pypi-proxy/simple Collecting setuptools Downloading http://172.17.0.2:8081/repository/pypi-proxy/packages/setuptools/75.6.0/setuptools-75.6.0-py3-none-any.whl (1.2 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.2/1.2 MB 942.9 MB/s eta 0:00:00 Installing collected packages: setuptools Successfully installed setuptools-75.6.0 ... real 0m1.589s
Тепер часу зайняло 1.52 секунди замість 2.59.
Окей – наче все працює?
Давайте запустимо Nexus в Kubernetes.
Запуск Nexus в Kubernetes
Є такий чарт – stevehipwell/nexus3.
Можна написати маніфести самому, можна спробувати цей чарт.
Що нам може бути цікаво з вальюсів чарту:
config.anonymous.enabled
: працювати Nexus буде локально в Kubernetes з доступом тільки по ClusterIP, тому поки це в PoC і чисто для кешу PiP – можна без аутентифікаціїconfig.blobStores
: поки можна залишити як є, але пізніше, можливо, підключити окремий EBS або AWS Elastic File System, див. такожpersistence.enabled
config.job.tolerations
таnodeSelector
: якщо треба ранити на окремій ноді, див. Kubernetes: Pods та WorkerNodes – контроль розміщення подів на нодахconfig.repos
: відразу через values створити репозиторіїingress.enabled
: не наш кейс, але можливість єmetrics.enabled
: потім можна буде подивитись на моніторинг
Спочатку давайте встановимо з дефолтними параметрами, потім накидаємо власні values.
Додаємо репозиторій:
$ helm repo add stevehipwell https://stevehipwell.github.io/helm-charts/ "stevehipwell" has been added to your repositories
Створюємо окремий неймспейс ops-nexus-ns
:
$ kk create ns ops-nexus-ns namespace/ops-nexus-ns created
Встановлюємо чарт:
$ helm -n ops-nexus-ns upgrade --install nexus3 stevehipwell/nexus3
Запускався він хвилин 5 – я вже думав дропати чарт, і писати самому, але врешті-решт таки стартанув – Java, шо поробиш.
Перевіряємо що у нас тут є:
$ kk -n ops-nexus-ns get all NAME READY STATUS RESTARTS AGE pod/nexus3-0 4/4 Running 0 6m5s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/nexus3 ClusterIP 172.20.160.147 <none> 8081/TCP 6m5s service/nexus3-hl ClusterIP None <none> 8081/TCP 6m5s NAME READY AGE statefulset.apps/nexus3 1/1 6m6s
Додавання Admin user password
Створимо Kubernetes Secret з паролем:
$ kk -n ops-nexus-ns create secret generic nexus-root-pass --from-literal=password=p@ssw0rd secret/nexus-root-pass created
Пишемо файл nexus-values.yaml
, в якому задаємо ім’я Kubernetes Secret і ключ з паролем, заодно включаємо Anonymous Access:
rootPassword: secret: nexus-root-password key: password config: enabled: true anonymous: enabled: true
Додавання репозиторію в Nexus через Helm chart values
Тут трохи довелось робити “методом тика”, але завелось.
Отже, в values.yaml чарту сказано: “Repository configuration; based on the REST API (API reference docs require an existing Nexus installation and can be found at **Administration** under _System_ → _API_) but with `format` & `type` defined in the object.”
Подивимось специфікацію Nexus API – які поля передаються в API request:
А що по формату?
Поля Format і Type можемо глянути в якомусь існуючому репозиторії:
Описуємо репозиторій і інші потрібні параметри – в мене все раз наразі виглядає так:
rootPassword: secret: nexus-root-password key: password persistence: enabled: true storageClass: gp2-retain resources: requests: cpu: 1000m memory: 1500Mi config: enabled: true anonymous: enabled: true repos: - name: pip-cache format: pypi type: proxy online: true negativeCache: enabled: true timeToLive: 1440 proxy: remoteUrl: https://pypi.org metadataMaxAge: 1440 contentMaxAge: 1440 httpClient: blocked: false autoBlock: true connection: retries: 0 useTrustStore: false storage: blobStoreName: default strictContentTypeValidation: false
Тут досить простий сетап, якийсь тюнінг якщо треба то буду робити потім. Але з ним вже їздить, стріляє працює, кешує.
Деплоїмо:
$ helm -n ops-nexus-ns upgrade --install nexus3 stevehipwell/nexus3 -f nexus-values.yml
У випадку помилок типу “Could not create repository“:
$ kk -n ops-nexus-ns logs -f nexus3-config-9-2cssf Configuring Nexus3... Configuring anonymous access... Anonymous access configured. Configuring blob stores... Configuring scripts... Script 'cleanup' updated. Script 'task' updated. Configuring cleanup policies... Configuring repositories... ERROR: Could not create repository 'pip-cache'.
Перевіряємо логи – Nexus хоче передачу майже всіх полів, в данному випадку не вистачало config.repos.httpClient.contentMaxAge
:
nexus3-0:nexus3 2024-11-27 12:34:16,818+0000 WARN [qtp554755438-84] admin org.sonatype.nexus.siesta.internal.resteasy.ResteasyViolationExceptionMapper - (ID af473d22-3eca-49ea-adb9-c7985add27e7) Response: [400] '[ValidationErrorXO{id='PARAMETER strictContentTypeValidation', message='must not be null'}, ValidationErrorXO{id='PARAMETER negativeCache', message='must not be null'}, ValidationErrorXO{id='PARAMETER metadataMaxAge', message='must not be null'}, ValidationErrorXO{id='PARAMETER contentMaxAge'[]ust not be null]arg0.httpClient]ntMaxAge]]TypeValidation]TER httpClient', message='must not be null'}]'; mapped from: [PARAMETER]
Під часу деплою, коли ми задаємо параметр config.enabled=true
, чарт запускає ще один Kubernetes Pod, який власне виконує конфігурацію Nexus.
Перевіримо доступ і репозиторій – відкриваємо собі доступ:
$ kk -n ops-nexus-ns port-forward pod/nexus3-0 8082:8081 Forwarding from 127.0.0.1:8082 -> 8081 Forwarding from [::1]:8082 -> 8081
Заходимо на http://localhost:8082/#admin/repository/repositories:
Ресурсів, особливо Memory, Nexus хоче багато, бо знов-таки – Java:
Тому є сенс в values відразу виставити requests
.
Перевірка Nexus в Kubernetes
Запускаємо Pod з Python:
$ kk run pod --rm -i --tty --image python bash If you don't see a command prompt, try pressing enter. root@pod:/#
Знаходимо Kubernetes Service для Nexus:
$ kk -n ops-nexus-ns get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nexus3 ClusterIP 172.20.160.147 <none> 8081/TCP 78m nexus3-hl ClusterIP None <none> 8081/TCP 78m
Знов запускаємо pip install
:
root@pod:/# time pip install --index-url http://nexus3.ops-nexus-ns.svc:8081/repository/pip-cache/simple setuptools --trusted-host nexus3.ops-nexus-ns.svc Looking in indexes: http://nexus3.ops-nexus-ns.svc:8081/repository/pip-cache/simple Collecting setuptools Downloading http://nexus3.ops-nexus-ns.svc:8081/repository/pip-cache/packages/setuptools/75.6.0/setuptools-75.6.0-py3-none-any.whl (1.2 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.2/1.2 MB 86.3 MB/s eta 0:00:00 Installing collected packages: setuptools Successfully installed setuptools-75.6.0 ... real 0m3.958s
Встановило setuptools-75.6.0
за 3.95 секунди.
Перевіримо в http://localhost:8082/#browse/browse:pip-cache:
Видаляємо setuptools
з нашого поду:
root@pod:/# pip uninstall setuptools
І встановлюємо ще раз, знов з --no-cache-dir
:
root@pod:/# time pip install --no-cache-dir --index-url http://nexus3.ops-nexus-ns.svc:8081/repository/pip-cache/simple setuptools --trusted-host nexus3.ops-nexus-ns.svc Looking in indexes: http://nexus3.ops-nexus-ns.svc:8081/repository/pip-cache/simple Collecting setuptools Downloading http://nexus3.ops-nexus-ns.svc:8081/repository/pip-cache/packages/setuptools/75.6.0/setuptools-75.6.0-py3-none-any.whl (1.2 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.2/1.2 MB 875.9 MB/s eta 0:00:00 Installing collected packages: setuptools Successfully installed setuptools-75.6.0 .. real 0m2.364s
Тепер це зайняло 2.364s.
Залишилось оновити GitHub Workflows – відключити там всякі кеші, і додати використання Nexus.
GitHub та результати по AWS NAT Gateway трафіку
На Workflow детально зупинятись не буду, бо це у кожного своє, але якщо кратко, то відключаємо кешування PiP:
... - name: "Setup: Python 3.10" uses: actions/setup-python@v5 with: python-version: "3.10" # cache: 'pip' check-latest: "false" # cache-dependency-path: "**/*requirements.txt" ...
Це збереже близько 540 мегабайт на завантаженні архіву з кешем.
Далі у нас є step, який виконує pip install
через виклик make
:
... - name: "Setup: Dev Dependencies" id: setup_dev_dependencies #run: make dev-python-requirements run: make dev-python-requirements-nexus shell: bash ...
А в Makefile я зробив нову таску, аби можна було швидко повернути на старий конфіг:
... dev-python-requirements: python3 -m pip install --no-compile -r dev-requirements.txt dev-python-requirements-nexus: python3 -m pip install --index-url http://nexus3.ops-nexus-ns.svc:8081/repository/pip-cache/simple --no-compile -r dev-requirements.txt --trusted-host nexus3.ops-nexus-ns.svc ...
У Workflow відключаємо всякі кеши типу actions/cache
:
.. # - name: "Setup: Get cached api-generator images" # id: api-generator-cache # uses: actions/cache@v4 # with: # path: ~/_work/api-generator-cache # key: api-generator-cache ...
Ну і порівняємо результати.
Білд зі старим конфігом, без Nexus і з кешами GitHub – трафік Kubernetes Pod раннера, який цей білд виконував:
3.55 гігабайт трафіку, білд-деплой зайняли 4 хвилини 11 секунд часу.
І ця сама GitHub Actions джоба, але вже зі змерженими змінами і використанням Nexus і без GitHub caching.
В логах бачимо, що пакети дійсно беруться з Nexus:
Трафік:
329 мегабайт, білд-деплой зайняли 4 хвилини 20 секунд часу.
Ну і на цьому поки все.
Що буде зробити далі – це подивитись як Nexus можна моніторити, які в нього є метрики і які з них можна зробити алерти, і далі додати ще Docker кеш, бо доволі часто стикаємось з лімітами Docker Hub – “429 Too Many Requests – Server message: toomanyrequests: You have reached your pull rate limit. You may increase the limit by authenticating and upgrading“.