Переезд: лимиты потребления на Beget после разгрузки — cgroup, своп, prune, мониторинг #3030

Open
opened 2026-08-21 12:55:38 +00:00 by lekss361 · 4 comments
Owner

Эпик: #2989

Решение владельца

Тариф Beget (6 ядер / 12 ГБ / 150 ГБ, 3 650 ₽) не понижать, вместо этого ограничить потребление. Замедление деплоя и скрапинга приемлемо: после разделения ни то, ни другое не смотрит в лицо пользователю.

Почему не понижать: ступень ниже — 4 ядра / 6 ГБ / 80 ГБ за ~2 190 ₽, экономия 1 560 ₽/мес, но в 6 ГБ роль не влезает (один дев-контур в простое это уже ~4 ГБ до скрапперов и сборок). Главное — вверх тариф поднять можно всегда, вниз обычно нет.

Целевая раскладка (11,7 ГиБ)

Потребитель RAM CPU
Forgejo 1,5 ГБ
GlitchTip (web + worker + Postgres + Redis) 2,5 ГБ
CouchDB 0,7 ГБ
CI-раннеры, конкурентность 1 3 ГБ 2 ядра
Скрапперы 3 ГБ 2–3 ядра
ОС и запас ~1 ГБ

Что сделать

  • Проставить mem_limit / cpus по таблице выше.
  • Своп 4 ГБ, vm.swappiness=10. Без свопа при жёстких лимитах всплеск упирается в OOM-killer, который прибьёт что попало. Со свопом всплеск деградирует в замедление вместо падения.
  • Диск: еженедельный docker image prune -af --filter until=168h, docker builder prune --keep-storage 20GB, дампов держать 4 штуки.
  • Включить бесплатный мониторинг Beget с Telegram-уведомлениями (входит в тариф, не используется), порог по диску 80 %.

Триггер расширения

Ферма устойчиво держит >4 параллельных браузеров или RAM стабильно >85 % → тариф 8/16/220 (~5 640 ₽/мес). Не раньше.

Приёмка

  • Лимиты выставлены и переживают рестарт
  • Своп 4 ГБ активен, swappiness=10 в sysctl
  • Prune в cron, проверен один прогон
  • Мониторинг Beget включён, тестовое уведомление дошло в Telegram
Эпик: #2989 ## Решение владельца Тариф Beget (6 ядер / 12 ГБ / 150 ГБ, 3 650 ₽) **не понижать**, вместо этого ограничить потребление. Замедление деплоя и скрапинга приемлемо: после разделения ни то, ни другое не смотрит в лицо пользователю. Почему не понижать: ступень ниже — 4 ядра / 6 ГБ / 80 ГБ за ~2 190 ₽, экономия 1 560 ₽/мес, но в 6 ГБ роль не влезает (один дев-контур в простое это уже ~4 ГБ до скрапперов и сборок). Главное — **вверх тариф поднять можно всегда, вниз обычно нет**. ## Целевая раскладка (11,7 ГиБ) | Потребитель | RAM | CPU | |---|---|---| | Forgejo | 1,5 ГБ | — | | GlitchTip (web + worker + Postgres + Redis) | 2,5 ГБ | — | | CouchDB | 0,7 ГБ | — | | CI-раннеры, **конкурентность 1** | 3 ГБ | 2 ядра | | Скрапперы | 3 ГБ | 2–3 ядра | | ОС и запас | ~1 ГБ | — | ## Что сделать - Проставить `mem_limit` / `cpus` по таблице выше. - **Своп 4 ГБ, `vm.swappiness=10`.** Без свопа при жёстких лимитах всплеск упирается в OOM-killer, который прибьёт что попало. Со свопом всплеск деградирует в замедление вместо падения. - Диск: еженедельный `docker image prune -af --filter until=168h`, `docker builder prune --keep-storage 20GB`, дампов держать 4 штуки. - Включить **бесплатный мониторинг Beget** с Telegram-уведомлениями (входит в тариф, не используется), порог по диску 80 %. ## Триггер расширения Ферма устойчиво держит >4 параллельных браузеров **или** RAM стабильно >85 % → тариф 8/16/220 (~5 640 ₽/мес). Не раньше. ## Приёмка - [ ] Лимиты выставлены и переживают рестарт - [ ] Своп 4 ГБ активен, `swappiness=10` в sysctl - [ ] Prune в cron, проверен один прогон - [ ] Мониторинг Beget включён, тестовое уведомление дошло в Telegram
lekss361 added the
chore
scope/devops
tradein
labels 2026-08-21 12:57:09 +00:00
Author
Owner

Снял фактическое потребление на Beget. Два пункта приёмки из четырёх уже закрыты, а целевая таблица расходится с замером — причём в обе стороны, так что применять её как есть нельзя.

Уже сделано (не надо делать заново)

Своп и swappiness — есть:

Swap:  4.0Gi всего / 2.2Gi занято / 1.8Gi свободно
/swapfile  file  4G  2.2G  prio -2
vm.swappiness = 10

Ровно то, что требует приёмка. Причём своп реально работает — 2.2 ГБ занято, то есть он уже не раз спас от OOM.

Prune в cron — есть:

0 4 * * 0 bash /opt/gendesign/ops/docker-prune.sh >> /tmp/gendesign-docker-prune.log 2>&1

Еженедельно, воскресенье 04:00. Скрипт на месте (10.9 КБ).

Фактическое потребление, 24.08 ~02:5x

Хост: 11.68 ГиБ, занято ~100 ГБ из 145 ГБ диска (70 %).

Контейнер RAM Текущий лимит
forgejo 2.235 ГиБ нет
tradein-browser 1021 МиБ 2.5 ГиБ
tradein-postgres 855 МиБ 3 ГиБ
gendesign-worker-1 730 МиБ нет
gendesign-postgres-1 272 МиБ нет
gendesign-backend-1 156 МиБ нет
gendesign-beat-1 153 МиБ нет
tradein-backend 143 МиБ 768 МиБ
gendesign-site-finder-1 116 МиБ нет
tradein-scraper 85 МиБ 640 МиБ
tradein-tgbot 80 МиБ 256 МиБ
gendesign-couchdb 52 МиБ нет
glitchtip-web 36.6 МиБ 512 МиБ
glitchtip-worker 35.7 МиБ 384 МиБ
forgejo-runner ×3 ~30 МиБ каждый нет

Лимиты уже стоят на всём стеке tradein и на glitchtip. Не стоят — на gendesign-* (5 сервисов с mem_limit в compose, остальные без), на forgejo и на раннерах.

Целевая таблица разошлась с замером

Потребитель План Факт Вывод
Forgejo 1,5 ГБ 2,235 ГиБ план ниже факта в простое — выставить 1,5 ГБ значит гарантированно словить OOM
GlitchTip (web+worker+PG+Redis) 2,5 ГБ ~0,1 ГиБ на web+worker план завышен на порядок
CouchDB 0,7 ГБ 52 МиБ завышен ~13×
Скрапперы 3 ГБ ~1,2 ГиБ (browser+scraper+backend) план с запасом, разумно
CI-раннеры 3 ГБ ~90 МиБ в простое в простое; под сборкой будет много больше — по простою судить нельзя

Два числа надо пересмотреть до выставления лимитов:

  1. Forgejo 1,5 ГБ — опасно. Он и сейчас ест 2,2 ГиБ, ничего не делая (Go-рантайм с большим кэшем). Лимит ниже текущего потребления убьёт git в момент, когда он нужнее всего — во время переезда. Минимум 3 ГБ, и лучше сначала посмотреть на пик под нагрузкой CI.
  2. GlitchTip 2,5 ГБ и CouchDB 0,7 ГБ — сильно завышены. Освободившиеся ~3 ГБ разумно отдать раннерам и скрапперам, где план как раз впритык.

Сумма плана — 11,7 ГиБ, то есть весь хост без запаса. При этом ОС + своп 2,2 ГБ уже заняты. Если выставить план буквально, суммарные лимиты превысят физическую память, и смысл лимитов теряется — они перестают быть гарантией и становятся просто потолками.

Диск

Images          19    16.48 GB   reclaimable 2.34 GB (14 %)
Local Volumes   10    40.63 GB   reclaimable 0
Containers      25     0.44 GB

Тома — 40,6 ГБ и из них не освобождается ничего: это данные (Postgres, Forgejo, CouchDB), а не мусор. Prune может забрать только 2,3 ГБ образов. То есть порог 80 % по диску сработает не от мусора, а от роста данных — prune тут не поможет, поможет только переезд (по замеру из #2203 та же база после dump/restore занимает 2,3 ГБ вместо 21 ГБ, то есть ~19 ГБ вернётся само).

Что могу сделать без тебя

Выставить mem_limit/cpus на gendesign-* и forgejo параметризованно — тем же приёмом, что в #3058 (${VAR:-дефолт}), чтобы на Beget сейчас ничего не изменилось, а после разгрузки значения включались одной переменной. Скажи только числа для Forgejo и раннеров — или разреши взять мои (Forgejo 3 ГБ, GlitchTip 0,7 ГБ, CouchDB 0,3 ГБ, раннеры 3 ГБ, скрапперы 3,5 ГБ), и я оформлю PR.

Не делаю без тебя: сами лимиты вживую на работающем проде (ограничить сейчас — значит ограничить то, что ещё не разгружено), и мониторинг Beget — это панель.

Статус приёмки

  • Лимиты выставлены и переживают рестарт — частично: tradein и glitchtip да, gendesign-*/forgejo/раннеры нет
  • Своп 4 ГБ активен, swappiness=10 — уже так
  • Prune в cron — еженедельно, скрипт на месте
  • Мониторинг Beget включён — панель, за тобой
Снял фактическое потребление на Beget. **Два пункта приёмки из четырёх уже закрыты**, а целевая таблица расходится с замером — причём в обе стороны, так что применять её как есть нельзя. ## Уже сделано (не надо делать заново) **Своп и swappiness — есть:** ``` Swap: 4.0Gi всего / 2.2Gi занято / 1.8Gi свободно /swapfile file 4G 2.2G prio -2 vm.swappiness = 10 ``` Ровно то, что требует приёмка. Причём своп **реально работает** — 2.2 ГБ занято, то есть он уже не раз спас от OOM. **Prune в cron — есть:** ``` 0 4 * * 0 bash /opt/gendesign/ops/docker-prune.sh >> /tmp/gendesign-docker-prune.log 2>&1 ``` Еженедельно, воскресенье 04:00. Скрипт на месте (10.9 КБ). ## Фактическое потребление, 24.08 ~02:5x Хост: **11.68 ГиБ**, занято ~100 ГБ из 145 ГБ диска (70 %). | Контейнер | RAM | Текущий лимит | |---|---|---| | **forgejo** | **2.235 ГиБ** | **нет** | | tradein-browser | 1021 МиБ | 2.5 ГиБ | | tradein-postgres | 855 МиБ | 3 ГиБ | | gendesign-worker-1 | 730 МиБ | нет | | gendesign-postgres-1 | 272 МиБ | нет | | gendesign-backend-1 | 156 МиБ | нет | | gendesign-beat-1 | 153 МиБ | нет | | tradein-backend | 143 МиБ | 768 МиБ | | gendesign-site-finder-1 | 116 МиБ | нет | | tradein-scraper | 85 МиБ | 640 МиБ | | tradein-tgbot | 80 МиБ | 256 МиБ | | gendesign-couchdb | 52 МиБ | нет | | **glitchtip-web** | **36.6 МиБ** | 512 МиБ | | **glitchtip-worker** | **35.7 МиБ** | 384 МиБ | | forgejo-runner ×3 | ~30 МиБ каждый | нет | Лимиты **уже стоят** на всём стеке tradein и на glitchtip. Не стоят — на `gendesign-*` (5 сервисов с `mem_limit` в compose, остальные без), на `forgejo` и на раннерах. ## Целевая таблица разошлась с замером | Потребитель | План | Факт | Вывод | |---|---|---|---| | Forgejo | 1,5 ГБ | **2,235 ГиБ** | план **ниже факта в простое** — выставить 1,5 ГБ значит гарантированно словить OOM | | GlitchTip (web+worker+PG+Redis) | 2,5 ГБ | **~0,1 ГиБ** на web+worker | план завышен на порядок | | CouchDB | 0,7 ГБ | 52 МиБ | завышен ~13× | | Скрапперы | 3 ГБ | ~1,2 ГиБ (browser+scraper+backend) | план с запасом, разумно | | CI-раннеры | 3 ГБ | ~90 МиБ в простое | в простое; под сборкой будет много больше — по простою судить нельзя | Два числа надо пересмотреть **до** выставления лимитов: 1. **Forgejo 1,5 ГБ — опасно.** Он и сейчас ест 2,2 ГиБ, ничего не делая (Go-рантайм с большим кэшем). Лимит ниже текущего потребления убьёт git в момент, когда он нужнее всего — во время переезда. Минимум 3 ГБ, и лучше сначала посмотреть на пик под нагрузкой CI. 2. **GlitchTip 2,5 ГБ и CouchDB 0,7 ГБ — сильно завышены.** Освободившиеся ~3 ГБ разумно отдать раннерам и скрапперам, где план как раз впритык. Сумма плана — 11,7 ГиБ, то есть **весь хост без запаса**. При этом ОС + своп 2,2 ГБ уже заняты. Если выставить план буквально, суммарные лимиты превысят физическую память, и смысл лимитов теряется — они перестают быть гарантией и становятся просто потолками. ## Диск ``` Images 19 16.48 GB reclaimable 2.34 GB (14 %) Local Volumes 10 40.63 GB reclaimable 0 Containers 25 0.44 GB ``` Тома — 40,6 ГБ и из них не освобождается ничего: это данные (Postgres, Forgejo, CouchDB), а не мусор. Prune может забрать только 2,3 ГБ образов. То есть **порог 80 % по диску сработает не от мусора, а от роста данных** — prune тут не поможет, поможет только переезд (по замеру из #2203 та же база после dump/restore занимает 2,3 ГБ вместо 21 ГБ, то есть ~19 ГБ вернётся само). ## Что могу сделать без тебя Выставить `mem_limit`/`cpus` на `gendesign-*` и forgejo **параметризованно** — тем же приёмом, что в #3058 (`${VAR:-дефолт}`), чтобы на Beget сейчас ничего не изменилось, а после разгрузки значения включались одной переменной. Скажи только числа для Forgejo и раннеров — или разреши взять мои (Forgejo 3 ГБ, GlitchTip 0,7 ГБ, CouchDB 0,3 ГБ, раннеры 3 ГБ, скрапперы 3,5 ГБ), и я оформлю PR. **Не делаю без тебя:** сами лимиты вживую на работающем проде (ограничить сейчас — значит ограничить то, что ещё не разгружено), и мониторинг Beget — это панель. ## Статус приёмки - [ ] Лимиты выставлены и переживают рестарт — частично: tradein и glitchtip да, `gendesign-*`/forgejo/раннеры нет - [x] **Своп 4 ГБ активен, `swappiness=10`** — уже так - [x] **Prune в cron** — еженедельно, скрипт на месте - [ ] Мониторинг Beget включён — панель, за тобой
Author
Owner

Замер после разгрузки: половина задачи уже сделана, а целевая таблица устарела

Продукты уехали на Poincare, поэтому исходную раскладку имеет смысл сверять не с планом, а с фактом. Замер на Beget 26.08.

Что уже стоит и работает

Пункт приёмки Состояние
Своп 4 ГБ 4.0Gi, занято 754 МБ
vm.swappiness=10 на обоих хостах
Weekly prune в кроне 0 4 * * 0 docker-prune.sh
Лимиты выставлены частично — на 13 контейнерах из 20

Фактическое потребление — запас большой

Mem: 11 Gi всего · 3.5 Gi занято · 6.3 Gi кэш · 8.1 Gi доступно
Диск: 65 из 145 ГБ (45 %)

Суммарно объявлено 9.0 ГиБ лимитов при реальном потреблении 3.5 ГиБ.

Кто остался без лимитов — и это ровно те, кто рискует

Контейнер Лимит Потребление сейчас
forgejo 456 МБ
forgejo-runner 173 МБ
forgejo-runner-2 52 МБ
forgejo-runner-3 52 МБ
gendesign-couchdb 61 МБ
gendesign-caddy-1, redis, garmin-mcp, auth-forwarder < 60 МБ

Раннеры — единственные, кто в принципе способен на всплеск (сборка образа), и именно они не ограничены. CPU-лимитов нет ни на одном контейнере, конкурентность раннеров не задана.

Почему я это не выставил сам: forgejo и раннеры подняты из /home/gendesign/forgejo/{docker-compose,runner-compose}.yml — файлов вне репозитория. Правка не оформляется в PR, а применение лимита требует пересоздания контейнера, то есть остановки Forgejo и CI посреди недели переезда. Это шаг для спокойного окна, а не для сегодня.

Целевая таблица требует пересмотра

В плане была строка «Скрапперы — 3 ГБ, 2–3 ядра». Скрапперов на Beget больше нет — они уехали вместе с продуктами. Освободившийся бюджет разумнее отдать раннерам, а не резервировать под то, чего здесь не будет. Ограничение конкурентности раннеров до 1 при этом теряет часть смысла: оно вводилось, чтобы CI не конкурировал со скрапингом за память.

Отдельная находка: на Beget остался замороженный tradein-postgres

listings = 109 308,  последний scraped_at = 2026-08-25 17:23 UTC
активных сессий: 0

На Poincare тот же кластер живёт и пишется прямо сейчас: 109 773 листинга, scraped_at = 2026-08-26 16:32. То есть копия на Beget замерла сутки назад, к ней никто не подключается, и она не входит ни в один бэкап (крон tradein уехал на Poincare).

При этом она держит самый большой лимит на машине — 3 ГиБ — и 593 МБ резидентной памяти. Остановить её — самый дешёвый способ освободить бюджет, и он же убирает риск, что кто-то в окне подключится не к тому кластеру. Но это остановка боевого по виду контейнера с данными, поэтому предлагаю, а не делаю: сначала свежий дамп, потом стоп, потом наблюдение, и только затем удаление.

Остаток задачи

  • Своп 4 ГБ активен, swappiness=10
  • Prune в кроне
  • Лимиты на forgejo и трёх раннерах — правка host-local compose + рестарт CI, нужно окно
  • CPU-лимиты и конкурентность раннеров — с учётом пересмотра таблицы (скрапперов здесь нет)
  • Мониторинг Beget с уведомлениями — панель провайдера, за владельцем
  • Решение по замороженному tradein-postgres на Beget

Мелочь: docker-prune.sh на Beget пишет лог в /tmp — теряется при перезагрузке. На Poincare тот же крон пишет в /opt/gendesign/logs/. Стоит выровнять.

## Замер после разгрузки: половина задачи уже сделана, а целевая таблица устарела Продукты уехали на Poincare, поэтому исходную раскладку имеет смысл сверять не с планом, а с фактом. Замер на Beget 26.08. ### Что уже стоит и работает | Пункт приёмки | Состояние | |---|---| | Своп 4 ГБ | ✅ `4.0Gi`, занято 754 МБ | | `vm.swappiness=10` | ✅ на **обоих** хостах | | Weekly prune в кроне | ✅ `0 4 * * 0 docker-prune.sh` | | Лимиты выставлены | **частично** — на 13 контейнерах из 20 | ### Фактическое потребление — запас большой ``` Mem: 11 Gi всего · 3.5 Gi занято · 6.3 Gi кэш · 8.1 Gi доступно Диск: 65 из 145 ГБ (45 %) ``` Суммарно объявлено **9.0 ГиБ** лимитов при реальном потреблении **3.5 ГиБ**. ### Кто остался без лимитов — и это ровно те, кто рискует | Контейнер | Лимит | Потребление сейчас | |---|---|---| | `forgejo` | **—** | 456 МБ | | `forgejo-runner` | **—** | 173 МБ | | `forgejo-runner-2` | **—** | 52 МБ | | `forgejo-runner-3` | **—** | 52 МБ | | `gendesign-couchdb` | — | 61 МБ | | `gendesign-caddy-1`, `redis`, `garmin-mcp`, `auth-forwarder` | — | < 60 МБ | Раннеры — единственные, кто в принципе способен на всплеск (сборка образа), и именно они не ограничены. CPU-лимитов нет **ни на одном** контейнере, конкурентность раннеров не задана. **Почему я это не выставил сам:** `forgejo` и раннеры подняты из `/home/gendesign/forgejo/{docker-compose,runner-compose}.yml` — файлов **вне репозитория**. Правка не оформляется в PR, а применение лимита требует пересоздания контейнера, то есть остановки Forgejo и CI посреди недели переезда. Это шаг для спокойного окна, а не для сегодня. ### Целевая таблица требует пересмотра В плане была строка «Скрапперы — 3 ГБ, 2–3 ядра». **Скрапперов на Beget больше нет** — они уехали вместе с продуктами. Освободившийся бюджет разумнее отдать раннерам, а не резервировать под то, чего здесь не будет. Ограничение конкурентности раннеров до 1 при этом теряет часть смысла: оно вводилось, чтобы CI не конкурировал со скрапингом за память. ### Отдельная находка: на Beget остался замороженный `tradein-postgres` ``` listings = 109 308, последний scraped_at = 2026-08-25 17:23 UTC активных сессий: 0 ``` На Poincare тот же кластер живёт и пишется прямо сейчас: **109 773** листинга, `scraped_at = 2026-08-26 16:32`. То есть копия на Beget замерла сутки назад, к ней никто не подключается, и она не входит ни в один бэкап (крон tradein уехал на Poincare). При этом она держит **самый большой лимит на машине — 3 ГиБ** — и 593 МБ резидентной памяти. Остановить её — самый дешёвый способ освободить бюджет, и он же убирает риск, что кто-то в окне подключится не к тому кластеру. Но это остановка боевого по виду контейнера с данными, поэтому предлагаю, а не делаю: сначала свежий дамп, потом стоп, потом наблюдение, и только затем удаление. ### Остаток задачи - [x] Своп 4 ГБ активен, `swappiness=10` - [x] Prune в кроне - [ ] Лимиты на `forgejo` и трёх раннерах — правка host-local compose + рестарт CI, нужно окно - [ ] CPU-лимиты и конкурентность раннеров — с учётом пересмотра таблицы (скрапперов здесь нет) - [ ] Мониторинг Beget с уведомлениями — панель провайдера, **за владельцем** - [ ] Решение по замороженному `tradein-postgres` на Beget Мелочь: `docker-prune.sh` на Beget пишет лог в `/tmp` — теряется при перезагрузке. На Poincare тот же крон пишет в `/opt/gendesign/logs/`. Стоит выровнять.
Author
Owner

Замерил пики по метрикам — и наткнулся на то, что оставшийся пункт приёмки бьёт мимо цели

Пункт «лимиты не выставлены на 7 контейнерах, из них раннеры — единственные, кто рискует» я собирался закрыть напрямую. Проверил, куда именно уходит память при сборке, и оказалось, что лимит на раннере её не ограничит вообще.

Почему

runner-compose.yml монтирует раннерам /var/run/docker.sock, а runner/data/config.yml держит docker_host: unix:///var/run/docker.sock. То есть контейнеры задач раннер не запускает внутри себя — он просит хостовый демон поднять их рядом, как соседей. Их память живёт в своём cgroup, а не в cgroup раннера.

Отсюда и цифры. Пик за 7 суток:

контейнер пик
forgejo 1306 МБ
forgejo-runner 524 МБ
forgejo-runner-2 207 МБ
forgejo-runner-3 157 МБ

Раннер на пике — полгигабайта, и это его собственная бухгалтерия, а не сборка. Ограничить его — значит получить ложное чувство защищённости: настоящий расход идёт мимо этого cgroup.

Где расход на самом деле

Пики контейнеров задач за те же 7 суток:

контейнер задачи пик
…WORKFLOW-CI_JOB-openapi-codegen-check 1491 МБ
…WORKFLOW-CI_JOB-openapi-codegen-check 1418 МБ
…WORKFLOW-CI_JOB-openapi-codegen-check 1046 МБ
…WORKFLOW-CI_JOB-backend-tests 940 МБ
…WORKFLOW-CI_JOB-backend-tests 891 МБ

Максимум — 1.5 ГБ на openapi-codegen-check. Именно эти контейнеры и способны съесть хост, и именно они сейчас ничем не ограничены.

Рычаг — одна строка, но не в compose

runner/data/config.yml, секция container, сейчас:

container:
  options: ""

Ограничение задаётся здесь и применяется к каждому контейнеру задачи:

container:
  options: "--memory=3g --memory-swap=3g"

3 ГБ — вдвое больше наблюдённого максимума (1.5 ГБ). При трёх раннерах худший случай 9 ГБ на хосте с 11 ГБ, но это потолок, а не резерв: одновременный пик всех трёх на практике не наблюдался, а смысл потолка ровно в том, чтобы одна сбежавшая задача не утащила Forgejo вместе с исходным кодом.

Почему не применил сам

Три причины, и первая — главная:

  1. Это развилка, а не исполнение записанного. В приёмке стоит «лимиты на контейнерах»; замер говорит, что ставить их надо в другом месте и другим механизмом. Смена способа — за владельцем.
  2. Правка требует перезапуска демонов раннеров, а он отменяет задачи на лету. У отменённой задачи test есть известное последствие (см. .claude/rules/tradein.md, «rapid-merge trap»): build пропускается, и «деплой прошёл» оказывается деплоем старого образа. Делать это за три дня до окна переезда без нужды не стоит.
  3. runner-compose.yml и config.yml лежат в /home/gendesign/forgejo/вне git. Любая правка там не отражается ни в одном diff. Отдельно стоит решить, заносить ли эти два файла в репозиторий: сейчас конфигурация CI-хоста нигде не версионируется.

Что по forgejo (1306 МБ пика, лимита нет)

Тоже не трогал намеренно. Если поставить потолок и промахнуться, OOM-kill придётся на git-сервер, то есть разом на исходный код, CI и деплой. Пик 1.3 ГБ при 8 ГБ свободных на хосте — запас есть; после разгрузки (ради которой issue и заведён) станет ещё больше. Ставить сюда потолок безопаснее уже после переезда, когда видна новая база потребления.

## Замерил пики по метрикам — и наткнулся на то, что оставшийся пункт приёмки бьёт мимо цели Пункт «лимиты не выставлены на 7 контейнерах, из них раннеры — единственные, кто рискует» я собирался закрыть напрямую. Проверил, куда именно уходит память при сборке, и оказалось, что лимит на раннере её не ограничит **вообще**. ### Почему `runner-compose.yml` монтирует раннерам `/var/run/docker.sock`, а `runner/data/config.yml` держит `docker_host: unix:///var/run/docker.sock`. То есть контейнеры задач раннер не запускает внутри себя — он просит **хостовый демон** поднять их рядом, как соседей. Их память живёт в своём cgroup, а не в cgroup раннера. Отсюда и цифры. Пик за 7 суток: | контейнер | пик | |---|---:| | `forgejo` | 1306 МБ | | `forgejo-runner` | 524 МБ | | `forgejo-runner-2` | 207 МБ | | `forgejo-runner-3` | 157 МБ | Раннер на пике — полгигабайта, и это его собственная бухгалтерия, а не сборка. Ограничить его — значит получить ложное чувство защищённости: настоящий расход идёт мимо этого cgroup. ### Где расход на самом деле Пики контейнеров задач за те же 7 суток: | контейнер задачи | пик | |---|---:| | `…WORKFLOW-CI_JOB-openapi-codegen-check` | **1491 МБ** | | `…WORKFLOW-CI_JOB-openapi-codegen-check` | 1418 МБ | | `…WORKFLOW-CI_JOB-openapi-codegen-check` | 1046 МБ | | `…WORKFLOW-CI_JOB-backend-tests` | 940 МБ | | `…WORKFLOW-CI_JOB-backend-tests` | 891 МБ | Максимум — 1.5 ГБ на `openapi-codegen-check`. Именно эти контейнеры и способны съесть хост, и именно они сейчас ничем не ограничены. ### Рычаг — одна строка, но не в compose `runner/data/config.yml`, секция `container`, сейчас: ```yaml container: options: "" ``` Ограничение задаётся здесь и применяется к **каждому** контейнеру задачи: ```yaml container: options: "--memory=3g --memory-swap=3g" ``` 3 ГБ — вдвое больше наблюдённого максимума (1.5 ГБ). При трёх раннерах худший случай 9 ГБ на хосте с 11 ГБ, но это потолок, а не резерв: одновременный пик всех трёх на практике не наблюдался, а смысл потолка ровно в том, чтобы одна сбежавшая задача не утащила Forgejo вместе с исходным кодом. ### Почему не применил сам Три причины, и первая — главная: 1. **Это развилка, а не исполнение записанного.** В приёмке стоит «лимиты на контейнерах»; замер говорит, что ставить их надо в другом месте и другим механизмом. Смена способа — за владельцем. 2. Правка требует перезапуска демонов раннеров, а он отменяет задачи на лету. У отменённой задачи `test` есть известное последствие (см. `.claude/rules/tradein.md`, «rapid-merge trap»): `build` пропускается, и «деплой прошёл» оказывается деплоем старого образа. Делать это за три дня до окна переезда без нужды не стоит. 3. `runner-compose.yml` и `config.yml` лежат в `/home/gendesign/forgejo/` — **вне git**. Любая правка там не отражается ни в одном diff. Отдельно стоит решить, заносить ли эти два файла в репозиторий: сейчас конфигурация CI-хоста нигде не версионируется. ### Что по `forgejo` (1306 МБ пика, лимита нет) Тоже не трогал намеренно. Если поставить потолок и промахнуться, OOM-kill придётся на git-сервер, то есть разом на исходный код, CI и деплой. Пик 1.3 ГБ при 8 ГБ свободных на хосте — запас есть; после разгрузки (ради которой issue и заведён) станет ещё больше. Ставить сюда потолок безопаснее уже **после** переезда, когда видна новая база потребления.
Collaborator

Перепроверка 27.08.2026 (после пересборки базы 25-26.08 и переезда продукта на Poincare). Тело тикета писалось 20-24.08 по старой инфраструктуре.

Давления ресурсов на Beget больше нет: available 8,6 Gi из 11, диск 46 %, своп 4G при swappiness=10, prune в cron.

Строка «Скрапперы 3 ГБ / 2-3 ядра» мертва — скрапперы уехали на Selectel.

Что остаётся: без cgroup-лимитов работают forgejo, couchdb и раннеры; раннеров запущено 3.

**Перепроверка 27.08.2026** (после пересборки базы 25-26.08 и переезда продукта на Poincare). Тело тикета писалось 20-24.08 по старой инфраструктуре. Давления ресурсов на Beget больше нет: available **8,6 Gi** из 11, диск **46 %**, своп 4G при `swappiness=10`, `prune` в cron. Строка «Скрапперы 3 ГБ / 2-3 ядра» мертва — скрапперы уехали на Selectel. **Что остаётся:** без cgroup-лимитов работают forgejo, couchdb и раннеры; раннеров запущено **3**.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3030
No description provided.