March 17

Просто алерт. Просто Арго.

Прилетает алёрт: HPA maxed out.

HPA: keda-hpa-vmagent-scaler
Cluster: stg-**-uswest1
Current value: 10 (max)

Сперва я вообще задумался - а нахрена этот алерт? Что он мне дает? Ну уперлось в максимум, и что? Все остальное работает ок, никаких других алертов.

Спросил у умных людей, умные люди дали умные советы, что может быть неверные триггеры трешхолда, может быть сервис в максимуме и скоро будут ошибки. Ладно, аргумент.

Ну ок, пошёл смотреть.


Проблема первая:

  • vmagent жрёт памяти больше, чем ему отведено

Первым делом смотрю что там с HPA:

NAME REFERENCE TARGETS MIN MAX REPLICAS
keda-hpa-vmagent-scaler VMAgent/... 5694m/40 (avg), memory: 50%/40% 2 10 10

Два триггера.

  • Один prometheus-based - 5.7/40, всё хорошо.
  • Второй - memory: 50%/40%.
    Вот он виновник.

Смотрю дальше:

  • Лимит на каждый pod: 256Mi
  • Фактическое потребление: ~130 MiB на pod

130/256 = 50%. Цель триггера - 40%, то есть 102 MiB.

Это физически недостижимо - vmagent столько и держит в памяти просто чтобы работать, независимо от нагрузки. Горизонтальный скейлинг тут не поможет: добавишь реплик, каждая всё равно будет жрать те же 130 MiB.

Решение простое - поднять лимит. 384Mi > утилизация падает до 34%, HPA успокоится.

Правлю values в репозитории с ArgoCD-приложениями для кластера stg-*-uswest1:

resources:
  limits:
    memory: 384Mi
  requests:
    memory: 384Mi

Коммичу, засинкал ArgoCD.

И вот тут началось.


Проблема вторая:

  • ArgoCD и KEDA устроили драку

После синка:

Operation cannot be fulfilled on scaledobjects.keda.sh "vmagent-scaler":
the object has been modified; please apply your changes to the latest version
and try again. Retrying attempt #1

Классический 409. ArgoCD читает объект, хочет запатчить - а за это время KEDA уже успел его обновить.
Проверяю как часто это происходит:

kubectl -n vm get scaledobject vmagent-scaler -o jsonpath='{.metadata.resourceVersion}'
sleep 5
kubectl -n vm get scaledobject vmagent-scaler -o jsonpath='{.metadata.resourceVersion}'

# 157227839 → 157227898 за 5 секунд

KEDA пишет в ScaledObject каждые 1-2 секунды. Статусы, условия, метрики - всё туда.
Ретрай через 10, 20, 40 секунд не помогал - KEDA всегда успевал раньше.


Попытка 1:

  • ignoreDifferences

В конфиге ApplicationSet уже был блок:

ignoreDifferences:
  - group: keda.sh
    kind: ScaledObject
    managedFieldsManagers:
      - keda-operator
      - keda-metrics-adapter

Но не работает. Пошёл смотреть кто реально владеет полями в объекте:

kubectl -n vm get scaledobject vmagent-scaler --show-managed-fields -o json

manager: keda               | op: Update
manager: argocd-controller  | op: Apply

А я что написал? keda-operator. А реальное имя - просто keda.

Добавил keda в список.

Не помогло.

Дело в том, что ignoreDifferences влияет только на то, что ArgoCD показывает в дифе.
На сам apply - никак не влияет. ArgoCD всё равно патчит объект при синке.
Это я понял позже.

Изрядно помучавшись с другими подобными попытками я снова пришел к умным людям, которые снова дали умные советы, в том числе сервер сайд апплай.

Почитал документацию, вроде красиво, ок.


Попытка 2:

  • включить ServerSideApply

Логика была такая: с SSA ArgoCD перестаёт посылать resourceVersion в патче, значит 409 уйдёт.

Добавил ServerSideApply=true в syncOptions для vm-приложений.

Тут выяснился интересный нюанс. В ApplicationSet у нас базовый шаблон уже имел ServerSideApply=true:

syncPolicy:
  syncOptions:
    - ServerSideApply=true

Но в templatePatch для vm-секции был такой кусок:

syncPolicy:
  syncOptions:
    - RespectIgnoreDifferences=true

И он молча перезаписывал базовый список вместо того чтобы добавить к нему.
Итого vm-приложения жили без SSA всё это время, хотя казалось что SSA включён. 🤡

Добавил ServerSideApply=true явно в vm-секцию. Применил. Синканул.

Ошибка изменилась:

Please review the fields above--they currently have other managers.
Please re-run the apply command with the --force-conflicts flag.

Хм. Это уже не 409 от гонки - это SSA ownership conflict.
Стало хуже. 🙃

  • До SSA: транзиентная 409, иногда сама проходила с третьей попытки.
  • После SSA: постоянный конфликт владения полями, не проходит никогда.


Попытка 3:

  • найти конкретные поля-конфликтеры

Смотрю что именно KEDA записывает в managedFields:

kubectl get <resource> --show-managed-fields -o json \
  | jq -r '.metadata.managedFields[].manager'

KEDA через CSA (client-side apply) владеет spec.advanced.scalingModifiers.
ArgoCD через SSA тоже хочет применить spec.advanced (оно есть в Helm-темплейте).


Конфликт.

Добавил jsonPointers для этого поля:

jsonPointers:
  - /metadata/resourceVersion
  - /metadata/finalizers
  - /spec/advanced/scalingModifiers
  - /status

Не помогло.

Потому что ignoreDifferences + RespectIgnoreDifferences=true - это "не синкай ресурс если разница ТОЛЬКО в этих полях". Но если ресурс синкается по другой причине (а он синкался, потому что менялись лимиты памяти в VMAgent) - ArgoCD применяет объект целиком.

Включая все поля.

Включая конфликтные. 🤡


Попытка 4:

  • Force=true аннотация

Думаю, ну раз нужен --force-conflicts, может есть способ сказать ArgoCD "применяй этот ресурс с --force-conflicts"?

Добавил в Helm-темплейт ScaledObject:

annotations:
  argocd.argoproj.io/sync-options: Force=true

Тут я вовремя остановился и проверил что это вообще делает.
Force=true в ArgoCD - это delete + recreate ресурса при каждом синке.
Не --force-conflicts для SSA. Это вообще другое и довольно опасное.

Убрал, не применял.


Что реально сработало

Пока я всё это ковырял, читал документацию, смотрел менеджед поля и логи, понял в чём корень.

Проблема - смешанный ownership: KEDA пишет в ScaledObject через CSA (старый client-side apply), ArgoCD пытается применить через SSA. Когда два разных механизма клеймят одно поле - кубернетис говорит "разберитесь между собой".

И ignoreDifferences тут не поможет никак - он только про вычисление диффа, не про применение.

Самое простое решение - удалить ScaledObject и дать ArgoCD пересоздать его с нуля через SSA.

После пересоздания:

  • ArgoCD - единственный SSA-owner всех полей в объекте
  • KEDA потом пишет scalingModifiers через CSA - это его поле, арго его не трогает
  • Поля не пересекаются > конфликта нет

Похер, это стейдж.

kubectl -n vm delete scaledobject vmagent-scaler

ArgoCD пересоздал. Синк прошёл с первого раза. Всё зелёное. 🎉

Следом HPA отскейлился обратно - vmagent с новым лимитом 384Mi держит ~34% утилизации, ниже порога 40%.

Через 10 минут (stabilizationWindowSeconds: 600 на scaleDown) реплики упали с 10 до 2.


Что поправили в итоге

В Terraform-модулях - ApplicationSet для всех mt-кластеров:

  • Добавили ServerSideApply=true явно в vm-секцию templatePatch (чтобы он не терялся при override)
  • Добавили keda в managedFieldsManagers (было только keda-operator и keda-metrics-adapter, реального имени менеджера не было)
  • Добавили /spec/advanced/scalingModifiers и /metadata/finalizers в jsonPointers - теперь ArgoCD корректно игнорирует эти поля при диффе и не показывает ложные OutOfSync

В репозитории с ArgoCD-приложениями - values для stg-mt-uswest1:

  • Лимит vmagent: 256Mi → 384Mi


Итоги

  • ignoreDifferences - только про отображение диффа. Не про apply. Всегда.
  • CSA + SSA на одном объекте = смешанный ownership = проблемы. Лечится пересозданием.
  • Имена менеджеров надо проверять в реальном объекте, не угадывать:
kubectl get <resource> --show-managed-fields -o json \
  | jq -r '.metadata.managedFields[].manager'
  • Force=true в ArgoCD != --force-conflicts в kubectl SSA.
    • Первое - delete+recreate
    • второе - принудительное взятие ownership поля.

Разные вещи.

  • templatePatch в ApplicationSet переписывает поля целиком, а не мержит. Если в базовом шаблоне есть syncOptions: [ServerSideApply=true], а в templatePatch ты пишешь syncOptions: [RespectIgnoreDifferences=true] - SSA пропадает молча.
  • Иногда самый быстрый путь - удалить объект и дать системе пересоздать его в правильном состоянии.

Несколько часов потратил на то, что решилось одной командой kubectl delete.

Просто алерт. Просто Арго. Просто пять минут.

Классика.