<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>Alexandr Kruchkov</title><author><name>Alexandr Kruchkov</name></author><id>https://teletype.in/atom/kruchkov_alexandr</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/kruchkov_alexandr?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@kruchkov_alexandr?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kruchkov_alexandr"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/kruchkov_alexandr?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-04-28T22:56:44.158Z</updated><entry><id>kruchkov_alexandr:mwVCBuS1y6T</id><link rel="alternate" type="text/html" href="https://teletype.in/@kruchkov_alexandr/mwVCBuS1y6T?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kruchkov_alexandr"></link><title>Просто алерт. Просто Арго.</title><published>2026-03-17T09:27:02.097Z</published><updated>2026-03-17T09:27:02.097Z</updated><summary type="html">Прилетает алёрт: HPA maxed out.</summary><content type="html">
  &lt;p id=&quot;fgfn&quot;&gt;Прилетает алёрт: &lt;u&gt;HPA maxed out.&lt;/u&gt;&lt;/p&gt;
  &lt;pre id=&quot;qSY5&quot;&gt;HPA: keda-hpa-vmagent-scaler
Cluster: stg-**-uswest1
Current value: 10 (max)&lt;/pre&gt;
  &lt;p id=&quot;hsHT&quot;&gt;Сперва я вообще задумался - а нахрена этот алерт? Что он мне дает? Ну уперлось в максимум, и что? Все остальное работает ок, никаких других алертов.&lt;/p&gt;
  &lt;p id=&quot;tR0R&quot;&gt;Спросил у умных людей, умные люди дали умные советы, что может быть неверные триггеры трешхолда, может быть сервис в максимуме и скоро будут ошибки. Ладно, аргумент. &lt;/p&gt;
  &lt;p id=&quot;5S3a&quot;&gt;Ну ок, пошёл смотреть.&lt;/p&gt;
  &lt;p id=&quot;aNr1&quot;&gt;&lt;br /&gt;Проблема первая: &lt;/p&gt;
  &lt;ul id=&quot;SRvp&quot;&gt;
    &lt;li id=&quot;h9rO&quot;&gt;vmagent жрёт памяти больше, чем ему отведено&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;DlpZ&quot;&gt;Первым делом смотрю что там с HPA:&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;nABD&quot;&gt;NAME                      REFERENCE        TARGETS                           MIN   MAX   REPLICAS&lt;br /&gt;keda-hpa-vmagent-scaler   VMAgent/...      5694m/40 (avg), memory: 50%/40%   2     10    10&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;xnPr&quot;&gt;Два триггера. &lt;/p&gt;
  &lt;ul id=&quot;h8Wd&quot;&gt;
    &lt;li id=&quot;eHBO&quot;&gt;Один prometheus-based - 5.7/40, всё хорошо. &lt;/li&gt;
    &lt;li id=&quot;lhcD&quot;&gt;Второй - memory: 50%/40%.&lt;br /&gt;Вот он виновник.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;voM3&quot;&gt;Смотрю дальше:&lt;/p&gt;
  &lt;ul id=&quot;OUPk&quot;&gt;
    &lt;li id=&quot;mZLY&quot;&gt;Лимит на каждый pod: 256Mi&lt;/li&gt;
    &lt;li id=&quot;D7Pt&quot;&gt;Фактическое потребление: ~130 MiB на pod&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;ubbX&quot;&gt;130/256 = 50%. Цель триггера - 40%, то есть 102 MiB.&lt;/p&gt;
  &lt;p id=&quot;DQW1&quot;&gt;Это физически недостижимо - &lt;code&gt;vmagent&lt;/code&gt; столько и держит в памяти просто чтобы работать, независимо от нагрузки. Горизонтальный скейлинг тут не поможет: добавишь реплик, каждая всё равно будет жрать те же 130 MiB.&lt;/p&gt;
  &lt;p id=&quot;bHQH&quot;&gt;Решение простое - поднять лимит. 384Mi &amp;gt; утилизация падает до 34%, HPA успокоится.&lt;/p&gt;
  &lt;p id=&quot;r2Qi&quot;&gt;Правлю values в репозитории с ArgoCD-приложениями для кластера stg-*-uswest1:&lt;/p&gt;
  &lt;pre id=&quot;zLvy&quot;&gt;resources:
  limits:
    memory: 384Mi
  requests:
    memory: 384Mi&lt;/pre&gt;
  &lt;p id=&quot;dfub&quot;&gt;Коммичу, засинкал ArgoCD.&lt;/p&gt;
  &lt;p id=&quot;4wiN&quot;&gt;И вот тут началось.&lt;/p&gt;
  &lt;p id=&quot;ellz&quot;&gt;&lt;br /&gt;Проблема вторая: &lt;/p&gt;
  &lt;ul id=&quot;pv9t&quot;&gt;
    &lt;li id=&quot;tK1w&quot;&gt;ArgoCD и KEDA устроили драку&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;IMUo&quot;&gt;После синка:&lt;/p&gt;
  &lt;pre id=&quot;s79w&quot;&gt;Operation cannot be fulfilled on scaledobjects.keda.sh &amp;quot;vmagent-scaler&amp;quot;:
the object has been modified; please apply your changes to the latest version
and try again. Retrying attempt #1&lt;/pre&gt;
  &lt;p id=&quot;FYba&quot;&gt;Классический 409. ArgoCD читает объект, хочет запатчить - а за это время KEDA уже успел его обновить.&lt;br /&gt;Проверяю как часто это происходит:&lt;/p&gt;
  &lt;pre id=&quot;6NgM&quot;&gt;kubectl -n vm get scaledobject vmagent-scaler -o jsonpath=&amp;#x27;{.metadata.resourceVersion}&amp;#x27;
sleep 5
kubectl -n vm get scaledobject vmagent-scaler -o jsonpath=&amp;#x27;{.metadata.resourceVersion}&amp;#x27;

# 157227839 → 157227898 за 5 секунд&lt;/pre&gt;
  &lt;p id=&quot;UDql&quot;&gt;KEDA пишет в &lt;code&gt;ScaledObject&lt;/code&gt; каждые 1-2 секунды. Статусы, условия, метрики - всё туда.&lt;br /&gt;Ретрай через 10, 20, 40 секунд не помогал - KEDA всегда успевал раньше.&lt;/p&gt;
  &lt;p id=&quot;y1vk&quot;&gt;&lt;br /&gt;Попытка 1: &lt;/p&gt;
  &lt;ul id=&quot;S5eV&quot;&gt;
    &lt;li id=&quot;ZYFm&quot;&gt;&lt;code&gt;ignoreDifferences&lt;/code&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;Cwkj&quot;&gt;В конфиге ApplicationSet уже был блок:&lt;/p&gt;
  &lt;pre id=&quot;BgSk&quot;&gt;ignoreDifferences:
  - group: keda.sh
    kind: ScaledObject
    managedFieldsManagers:
      - keda-operator
      - keda-metrics-adapter&lt;/pre&gt;
  &lt;p id=&quot;FBER&quot;&gt;Но не работает. Пошёл смотреть кто реально владеет полями в объекте:&lt;/p&gt;
  &lt;pre id=&quot;cI4o&quot;&gt;kubectl -n vm get scaledobject vmagent-scaler --show-managed-fields -o json

manager: keda               | op: Update
manager: argocd-controller  | op: Apply&lt;/pre&gt;
  &lt;p id=&quot;6GTS&quot;&gt;А я что написал? keda-operator. А реальное имя - просто keda. &lt;/p&gt;
  &lt;p id=&quot;cwbo&quot;&gt;Добавил keda в список.&lt;/p&gt;
  &lt;p id=&quot;pYLR&quot;&gt;Не помогло.&lt;/p&gt;
  &lt;p id=&quot;NAUm&quot;&gt;Дело в том, что &lt;code&gt;ignoreDifferences&lt;/code&gt; влияет только на то, что ArgoCD показывает в дифе.&lt;br /&gt;На сам apply - никак не влияет. ArgoCD всё равно патчит объект при синке.&lt;br /&gt;Это я понял позже. &lt;/p&gt;
  &lt;p id=&quot;73Xh&quot;&gt;Изрядно помучавшись с другими подобными попытками я снова пришел к умным людям, которые снова дали умные советы, в том числе сервер сайд апплай.&lt;/p&gt;
  &lt;p id=&quot;KdZ7&quot;&gt;Почитал документацию, вроде красиво, ок.&lt;/p&gt;
  &lt;p id=&quot;c7Iz&quot;&gt;&lt;br /&gt;Попытка 2: &lt;/p&gt;
  &lt;ul id=&quot;SO1a&quot;&gt;
    &lt;li id=&quot;azG3&quot;&gt;включить &lt;code&gt;ServerSideApply&lt;/code&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;RSOL&quot;&gt;Логика была такая: с SSA ArgoCD перестаёт посылать resourceVersion в патче, значит 409 уйдёт.&lt;/p&gt;
  &lt;p id=&quot;kccP&quot;&gt;Добавил &lt;code&gt;ServerSideApply=true&lt;/code&gt; в syncOptions для vm-приложений.&lt;/p&gt;
  &lt;p id=&quot;pQxh&quot;&gt;Тут выяснился интересный нюанс. В ApplicationSet у нас базовый шаблон уже имел ServerSideApply=true:&lt;/p&gt;
  &lt;pre id=&quot;0g98&quot;&gt;syncPolicy:
  syncOptions:
    - ServerSideApply=true&lt;/pre&gt;
  &lt;p id=&quot;suii&quot;&gt;Но в templatePatch для vm-секции был такой кусок:&lt;/p&gt;
  &lt;pre id=&quot;QLUq&quot;&gt;syncPolicy:
  syncOptions:
    - RespectIgnoreDifferences=true&lt;/pre&gt;
  &lt;p id=&quot;vzRr&quot;&gt;И он молча перезаписывал базовый список вместо того чтобы добавить к нему.&lt;br /&gt;Итого vm-приложения жили без SSA всё это время, хотя казалось что SSA включён. 🤡&lt;/p&gt;
  &lt;p id=&quot;aeaw&quot;&gt;Добавил ServerSideApply=true явно в vm-секцию. Применил. Синканул.&lt;/p&gt;
  &lt;p id=&quot;sMed&quot;&gt;Ошибка изменилась:&lt;/p&gt;
  &lt;pre id=&quot;TtYh&quot;&gt;Please review the fields above--they currently have other managers.
Please re-run the apply command with the --force-conflicts flag.&lt;/pre&gt;
  &lt;p id=&quot;eBR5&quot;&gt;Хм. Это уже не 409 от гонки - это SSA ownership conflict.&lt;br /&gt;Стало хуже. 🙃&lt;/p&gt;
  &lt;ul id=&quot;FY24&quot;&gt;
    &lt;li id=&quot;eSsg&quot;&gt;До SSA: транзиентная 409, иногда сама проходила с третьей попытки.&lt;/li&gt;
    &lt;li id=&quot;FBJ4&quot;&gt;После SSA: постоянный конфликт владения полями, не проходит никогда.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;6JIr&quot;&gt;&lt;br /&gt;Попытка 3: &lt;/p&gt;
  &lt;ul id=&quot;iKjM&quot;&gt;
    &lt;li id=&quot;jGgy&quot;&gt;найти конкретные поля-конфликтеры&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;Ylgz&quot;&gt;Смотрю что именно KEDA записывает в managedFields:&lt;/p&gt;
  &lt;pre id=&quot;HG1Y&quot;&gt;kubectl get &amp;lt;resource&amp;gt; --show-managed-fields -o json \
  | jq -r &amp;#x27;.metadata.managedFields[].manager&amp;#x27;&lt;/pre&gt;
  &lt;p id=&quot;xvUE&quot;&gt;KEDA через CSA (client-side apply) владеет spec.advanced.scalingModifiers.&lt;br /&gt;ArgoCD через SSA тоже хочет применить spec.advanced (оно есть в Helm-темплейте).&lt;/p&gt;
  &lt;p id=&quot;XSCa&quot;&gt;&lt;br /&gt;Конфликт.&lt;/p&gt;
  &lt;p id=&quot;Qz86&quot;&gt;Добавил jsonPointers для этого поля:&lt;/p&gt;
  &lt;pre id=&quot;wkXV&quot;&gt;jsonPointers:
  - /metadata/resourceVersion
  - /metadata/finalizers
  - /spec/advanced/scalingModifiers
  - /status&lt;/pre&gt;
  &lt;p id=&quot;nRrx&quot;&gt;Не помогло.&lt;/p&gt;
  &lt;p id=&quot;l4SS&quot;&gt;Потому что ignoreDifferences + RespectIgnoreDifferences=true - это &amp;quot;не синкай ресурс если разница ТОЛЬКО в этих полях&amp;quot;. Но если ресурс синкается по другой причине (а он синкался, потому что менялись лимиты памяти в VMAgent) - ArgoCD применяет объект целиком. &lt;/p&gt;
  &lt;p id=&quot;FIZZ&quot;&gt;Включая все поля. &lt;/p&gt;
  &lt;p id=&quot;fGQH&quot;&gt;Включая конфликтные. 🤡&lt;/p&gt;
  &lt;p id=&quot;Gs6D&quot;&gt;&lt;br /&gt;Попытка 4: &lt;/p&gt;
  &lt;ul id=&quot;121n&quot;&gt;
    &lt;li id=&quot;D1i7&quot;&gt;&lt;code&gt;Force=true&lt;/code&gt; аннотация&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;5M1I&quot;&gt;Думаю, ну раз нужен &lt;code&gt;--force-conflicts&lt;/code&gt;, может есть способ сказать ArgoCD &amp;quot;применяй этот ресурс с --force-conflicts&amp;quot;?&lt;/p&gt;
  &lt;p id=&quot;hrd4&quot;&gt;Добавил в Helm-темплейт ScaledObject:&lt;/p&gt;
  &lt;pre id=&quot;aok3&quot;&gt;annotations:
  argocd.argoproj.io/sync-options: Force=true&lt;/pre&gt;
  &lt;p id=&quot;ERjU&quot;&gt;Тут я вовремя остановился и проверил что это вообще делает.&lt;br /&gt;Force=true в ArgoCD - это delete + recreate ресурса при каждом синке.&lt;br /&gt;Не --force-conflicts для SSA. Это вообще другое и довольно опасное.&lt;/p&gt;
  &lt;p id=&quot;1Psj&quot;&gt;Убрал, не применял.&lt;/p&gt;
  &lt;h2 id=&quot;UgF3&quot;&gt;&lt;br /&gt;Что реально сработало&lt;/h2&gt;
  &lt;p id=&quot;j3nk&quot;&gt;Пока я всё это ковырял, читал документацию, смотрел менеджед поля и логи, понял в чём корень.&lt;/p&gt;
  &lt;p id=&quot;CTs0&quot;&gt;Проблема - смешанный &lt;code&gt;ownership&lt;/code&gt;: KEDA пишет в ScaledObject через &lt;code&gt;&lt;u&gt;CSA&lt;/u&gt;&lt;/code&gt; (старый client-side apply), ArgoCD пытается применить через &lt;code&gt;&lt;u&gt;SSA&lt;/u&gt;&lt;/code&gt;. Когда два разных механизма клеймят одно поле - кубернетис говорит &amp;quot;разберитесь между собой&amp;quot;.&lt;/p&gt;
  &lt;p id=&quot;18Tw&quot;&gt;И ignoreDifferences тут не поможет никак - он только про вычисление диффа, не про применение.&lt;/p&gt;
  &lt;p id=&quot;iO4U&quot;&gt;Самое простое решение - удалить ScaledObject и дать ArgoCD пересоздать его с нуля через SSA.&lt;/p&gt;
  &lt;p id=&quot;8uQJ&quot;&gt;После пересоздания:&lt;/p&gt;
  &lt;ul id=&quot;CdLr&quot;&gt;
    &lt;li id=&quot;csSp&quot;&gt;ArgoCD - единственный SSA-owner всех полей в объекте&lt;/li&gt;
    &lt;li id=&quot;TXAr&quot;&gt;KEDA потом пишет scalingModifiers через CSA - это его поле, арго его не трогает&lt;/li&gt;
    &lt;li id=&quot;cxoa&quot;&gt;Поля не пересекаются &amp;gt; конфликта нет&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;JJnR&quot;&gt;Похер, это стейдж.&lt;/p&gt;
  &lt;pre id=&quot;YbW7&quot;&gt;kubectl -n vm delete scaledobject vmagent-scaler&lt;/pre&gt;
  &lt;p id=&quot;RDWS&quot;&gt;ArgoCD пересоздал. Синк прошёл с первого раза. Всё зелёное. 🎉&lt;/p&gt;
  &lt;p id=&quot;PS6y&quot;&gt;Следом HPA отскейлился обратно - vmagent с новым лимитом 384Mi держит ~34% утилизации, ниже порога 40%. &lt;/p&gt;
  &lt;p id=&quot;jI5N&quot;&gt;Через 10 минут (stabilizationWindowSeconds: 600 на scaleDown) реплики упали с 10 до 2.&lt;/p&gt;
  &lt;h2 id=&quot;8HWt&quot;&gt;&lt;br /&gt;Что поправили в итоге&lt;/h2&gt;
  &lt;p id=&quot;TtGN&quot;&gt;В Terraform-модулях - ApplicationSet для всех mt-кластеров:&lt;/p&gt;
  &lt;ul id=&quot;nANk&quot;&gt;
    &lt;li id=&quot;6n5b&quot;&gt;Добавили &lt;code&gt;ServerSideApply=true&lt;/code&gt; явно в vm-секцию &lt;code&gt;templatePatch&lt;/code&gt; (чтобы он не терялся при override)&lt;/li&gt;
    &lt;li id=&quot;jvAq&quot;&gt;Добавили keda в managedFieldsManagers (было только keda-operator и keda-metrics-adapter, реального имени менеджера не было)&lt;/li&gt;
    &lt;li id=&quot;2DS2&quot;&gt;Добавили &lt;code&gt;/spec/advanced/scalingModifiers&lt;/code&gt; и &lt;code&gt;/metadata/finalizers&lt;/code&gt; в jsonPointers - теперь ArgoCD корректно игнорирует эти поля при диффе и не показывает ложные OutOfSync&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;nrSC&quot;&gt;В репозитории с ArgoCD-приложениями - values для stg-mt-uswest1:&lt;/p&gt;
  &lt;ul id=&quot;O4v7&quot;&gt;
    &lt;li id=&quot;6lCq&quot;&gt;Лимит vmagent: 256Mi → 384Mi&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;ZuBr&quot;&gt;&lt;br /&gt;Итоги&lt;/h2&gt;
  &lt;ul id=&quot;ksQN&quot;&gt;
    &lt;li id=&quot;GObQ&quot;&gt;ignoreDifferences - только про отображение диффа. Не про apply. Всегда.&lt;/li&gt;
    &lt;li id=&quot;atUO&quot;&gt;CSA + SSA на одном объекте = смешанный ownership = проблемы. Лечится пересозданием.&lt;/li&gt;
    &lt;li id=&quot;0XyC&quot;&gt;Имена менеджеров надо проверять в реальном объекте, не угадывать:&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;yUzj&quot;&gt;kubectl get &amp;lt;resource&amp;gt; --show-managed-fields -o json \
  | jq -r &amp;#x27;.metadata.managedFields[].manager&amp;#x27;&lt;/pre&gt;
  &lt;ul id=&quot;e9IX&quot;&gt;
    &lt;li id=&quot;i7xM&quot;&gt;&lt;code&gt;Force=true&lt;/code&gt; в &lt;code&gt;ArgoCD != --force-conflicts&lt;/code&gt; в kubectl &lt;u&gt;SSA&lt;/u&gt;. &lt;/li&gt;
    &lt;ul id=&quot;FmNr&quot;&gt;
      &lt;li id=&quot;ybd3&quot;&gt;Первое - delete+recreate&lt;/li&gt;
      &lt;li id=&quot;L1dE&quot;&gt;второе - принудительное взятие ownership поля. &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;7zN6&quot;&gt;Разные вещи.&lt;/p&gt;
  &lt;ul id=&quot;Ck9T&quot;&gt;
    &lt;li id=&quot;AIGX&quot;&gt;templatePatch в ApplicationSet переписывает поля целиком, а не мержит. Если в базовом шаблоне есть syncOptions: [ServerSideApply=true], а в templatePatch ты пишешь syncOptions: [RespectIgnoreDifferences=true] - SSA пропадает молча.&lt;/li&gt;
    &lt;li id=&quot;rDgo&quot;&gt;Иногда самый быстрый путь - удалить объект и дать системе пересоздать его в правильном состоянии.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;hiLe&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;agFs&quot;&gt;Несколько часов потратил на то, что решилось одной командой &lt;code&gt;kubectl delete&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;EkGu&quot;&gt;Просто алерт. Просто Арго. Просто пять минут.&lt;/p&gt;
  &lt;p id=&quot;48sJ&quot;&gt;Классика.&lt;/p&gt;

</content></entry><entry><id>kruchkov_alexandr:--Mo_yGq58L</id><link rel="alternate" type="text/html" href="https://teletype.in/@kruchkov_alexandr/--Mo_yGq58L?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kruchkov_alexandr"></link><title>Нули</title><published>2026-03-05T17:34:50.170Z</published><updated>2026-03-05T17:36:19.230Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/a7/0d/a70d5f75-c614-4fc5-8081-7a4a693d2926.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/91/8b/918b5a3f-3c33-41ff-a8a8-67d15e7429a2.png&quot;&gt;#бытовое #troubleshooting #одинденьизжизни</summary><content type="html">
  &lt;figure id=&quot;FlZi&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/91/8b/918b5a3f-3c33-41ff-a8a8-67d15e7429a2.png&quot; width=&quot;768&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;zQMn&quot;&gt;На планшете для работы умер кулер. Приплыли.&lt;/p&gt;
  &lt;p id=&quot;kq50&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;jLmf&quot;&gt;Ну, не совсем всё умерло - он включается, нагревается и троттлит 100% времени.&lt;br /&gt;Windows-планшет, на котором жила вся моя рабочая среда: куб контексты, профили AWS и Azure, скрипты, IDEs. Да всё. Всё было там.&lt;/p&gt;
  &lt;p id=&quot;Ocmn&quot;&gt;Надо быстро восстановить рабочее место.&lt;/p&gt;
  &lt;p id=&quot;mKKz&quot;&gt;Причём в &lt;u&gt;изолированное&lt;/u&gt; окружение, чтобы ничего, связанного с работой не перемешалось с личным и не было лишних утечек.&lt;br /&gt;Поднял виртуальную машину с Windows на Mac (да, я привык к Windows-среде, не осуждайте 🙃), начал по памяти восстанавливать инструменты. &lt;/p&gt;
  &lt;p id=&quot;eu1J&quot;&gt;&lt;br /&gt;Благо есть &lt;code&gt;asdf&lt;/code&gt; с &lt;code&gt;.tool-versions&lt;/code&gt; - всё задокументировано, стоишь раз за разом одни и те же версии. Скопировал файл, запустил инсталляцию.&lt;/p&gt;
  &lt;p id=&quot;rhTm&quot;&gt;Поставилось автоматически:&lt;/p&gt;
  &lt;ul id=&quot;TrEq&quot;&gt;
    &lt;li id=&quot;aQkp&quot;&gt;kubectl, helm, helmfile, terragrunt, terraform&lt;/li&gt;
    &lt;li id=&quot;0sci&quot;&gt;awscli, azure-cli, kubelogin&lt;/li&gt;
    &lt;li id=&quot;O9a9&quot;&gt;jq, yq, k9s, kubectx&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;TwCp&quot;&gt;Дохера всего, около 45 утилит.&lt;/p&gt;
  &lt;p id=&quot;S6Qv&quot;&gt;Дальше надо запустить скрипты - клонировать весь GitLab компании разом, настроить контексты кластеров всех облаков и профили облаков. Всё это у меня было. Всё это я написал раньше. Надо просто запустить.&lt;/p&gt;
  &lt;p id=&quot;wfgF&quot;&gt;Запускаю скрипт клонирования GitLab.&lt;br /&gt;Завис. Минута. Две. Пять.&lt;br /&gt;Окей, Ctrl+C, думаю. Что-то с сетью? Или токен протух?&lt;/p&gt;
  &lt;p id=&quot;7SQJ&quot;&gt;&lt;br /&gt;Иду в GitLab UI - токен живой. Проверяю вручную:&lt;/p&gt;
  &lt;pre id=&quot;FiHp&quot;&gt;curl -s -H &amp;quot;PRIVATE-TOKEN: $TOKEN&amp;quot; &amp;quot;https://gl.company.com/api/v4/user&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;ftUw&quot;&gt;Всё нормально, вернул мой профиль, HTTP 200. Хорошо.&lt;br /&gt;Значит не токен.&lt;/p&gt;
  &lt;p id=&quot;tMt6&quot;&gt;Меняю токен на новый - на всякий случай. Запускаю скрипт. Снова завис.&lt;/p&gt;
  &lt;p id=&quot;KAEq&quot;&gt;Прошу нейронку помочь. &lt;br /&gt;Та начинает советовать добавить таймаут в curl, добавить проверки пагинации, переписать функции... &lt;br /&gt;Делаю всё это. Всё равно зависает или выдаёт нули:&lt;br /&gt;Хотя вручную curl отдаёт нормальные данные.&lt;br /&gt;Ни один совет нейронки не помог ни в чем.&lt;/p&gt;
  &lt;p id=&quot;YDkb&quot;&gt;Добавляю отладочный вывод. &lt;br /&gt;Оборачиваю запрос в файл, читаю из файла, убираю BOM, убираю нулевые байты, убираю CR... &lt;br /&gt;Нули.&lt;/p&gt;
  &lt;p id=&quot;nuiM&quot;&gt;Пишу отдельный дебаг-скрипт с пошаговыми запросами. &lt;br /&gt;Каждый шаг - отдельный curl, каждый ответ - в переменную, потом проверяем &lt;br /&gt;&lt;code&gt;jq -e &amp;#x27;type == &amp;quot;array&amp;quot;&amp;#x27;...&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;jdTh&quot;&gt;И тут вижу странное:&lt;br /&gt;API error на /groups page=1:&lt;/p&gt;
  &lt;pre id=&quot;mzJX&quot;&gt;[{&amp;quot;id&amp;quot;:10,&amp;quot;web_url&amp;quot;:&amp;quot;https://gl.company.com/groups/all&amp;quot;,&amp;quot;name&amp;quot;:&amp;quot;All&amp;quot;...&lt;/pre&gt;
  &lt;p id=&quot;W5fE&quot;&gt;&lt;strong&gt;Что.&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;aWgf&quot;&gt;Стоп. Там же чётко написано &lt;code&gt;[{&amp;quot;id&amp;quot;:10...&lt;/code&gt; - это массив. &lt;br /&gt;Это валидный, сука, JSON. Ты чо, пёс.&lt;br /&gt;Но проверка говорит &amp;quot;не массив&amp;quot;.&lt;br /&gt;Проверяю сам:&lt;/p&gt;
  &lt;pre id=&quot;fG33&quot;&gt;echo &amp;#x27;[]&amp;#x27; | jq -e &amp;#x27;type == &amp;quot;array&amp;quot;&amp;#x27;; echo &amp;quot;exit: $?&amp;quot;
exit: 1&lt;/pre&gt;
  &lt;p id=&quot;wAHb&quot;&gt;&lt;strong&gt;Чтоооо.&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;Wy6F&quot;&gt;echo &amp;#x27;[1,2,3]&amp;#x27; | jq -e &amp;#x27;type == &amp;quot;array&amp;quot;&amp;#x27;; echo &amp;quot;exit: $?&amp;quot;
exit: 1&lt;/pre&gt;
  &lt;p id=&quot;62HS&quot;&gt;&lt;strong&gt;ЧТО.&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;1Wen&quot;&gt;jq возвращает 1 для валидного массива. На вопрос &amp;quot;это массив?&amp;quot; jq отвечает &amp;quot;нет&amp;quot;.&lt;/p&gt;
  &lt;p id=&quot;sNUj&quot;&gt;Ладно, решаю - пофиг на GitLab, пофиг на jq, склоню вручную потом. &lt;br /&gt;Сначала сделаю рабочую среду.&lt;/p&gt;
  &lt;p id=&quot;p3mH&quot;&gt;Иду настраивать kubectl-контексты. Запускаю скрипт - не работает.&lt;br /&gt;Иду в AWS, пробую aws eks update-kubeconfig - зависает.&lt;br /&gt;Пробую вручную aws sts get-caller-identity - зависает на несколько секунд, потом отрабатывает.&lt;br /&gt;Что-то медленное и странное.&lt;/p&gt;
  &lt;p id=&quot;pGSA&quot;&gt;Пробую aws s3 ls - работает, но медленно.&lt;br /&gt;Иду в Azure - az account list отрабатывает, но вывод странный. Где-то что-то парсится не так.&lt;/p&gt;
  &lt;p id=&quot;9OFe&quot;&gt;Начинаю замечать паттерн: &lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(55,  86%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;9BCI&quot;&gt;всё, что связано с обработкой JSON в CLI-инструментах, либо зависает, либо даёт неверный результат. awscli внутри гоняет Python и boto3, там свой парсер. &lt;br /&gt;jq - отдельный бинарник.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;YZu8&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;gTVA&quot;&gt;Стоп. Какого лешего тут происходит.&lt;/p&gt;
  &lt;p id=&quot;Vp3E&quot;&gt;Как мне может ВСЁ поломать всего-лишь одна утилита?????&lt;/p&gt;
  &lt;p id=&quot;tRo1&quot;&gt;Пойдём смотреть тебя.&lt;/p&gt;
  &lt;pre id=&quot;XAIG&quot;&gt;which jq
/home/alexk/.asdf/shims/jq
file /home/alexk/.asdf/shims/jq
/home/alexk/.asdf/shims/jq: Bourne-Again shell script, 
ASCII text executable&lt;/pre&gt;
  &lt;p id=&quot;RaDS&quot;&gt;&lt;br /&gt;Шим asdf. Смотрю куда он ведёт:&lt;/p&gt;
  &lt;pre id=&quot;iRUE&quot;&gt;ls /home/alexk/.asdf/installs/jq/
1.8.1
file /home/alexk/.asdf/installs/jq/1.8.1/bin/jq
ELF 64-bit LSB executable, x86-64&lt;/pre&gt;
  &lt;p id=&quot;Vd01&quot;&gt;От сука.&lt;/p&gt;
  &lt;p id=&quot;Jtsc&quot;&gt;&lt;br /&gt;А теперь проверяем ещё раз для наглядности:&lt;/p&gt;
  &lt;pre id=&quot;zZEU&quot;&gt;uname -m
aarch64&lt;/pre&gt;
  &lt;p id=&quot;661N&quot;&gt;&lt;br /&gt;Вот оно.&lt;br /&gt;Система - ARM64. Бинарник jq - x86-64.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(55,  86%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;nZ6P&quot;&gt;WSL2 на Windows ARM (в моём случае - Windows виртуалка на Mac с чипом Apple Silicon, WSL2 внутри неё) умеет запускать x86-64 бинарники через Microsoft Prism - встроенный эмулятор. &lt;br /&gt;Запускает. Но нестабильно. Базовые операции типа &lt;code&gt;jq &amp;#x27;.field&amp;#x27; или jq &amp;#x27;.[]&amp;#x27;&lt;/code&gt; работают. &lt;br /&gt;А вот более сложные выражения, типа &lt;code&gt;jq -e &amp;#x27;type == &amp;quot;array&amp;quot;&amp;#x27;&lt;/code&gt; или даже &lt;code&gt;jq --version&lt;/code&gt; - падают или зависают.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;MZHq&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;0Dzx&quot;&gt;Именно поэтому:&lt;/p&gt;
  &lt;ul id=&quot;Mgsk&quot;&gt;
    &lt;li id=&quot;SjOK&quot;&gt;скрипт клонирования GitLab зависал на первой же функции, которая проверяла тип ответа через jq&lt;/li&gt;
    &lt;li id=&quot;PPk5&quot;&gt;echo &lt;code&gt;&amp;#x27;[]&amp;#x27; | jq -e &amp;#x27;type == &amp;quot;array&amp;quot;&amp;#x27;&lt;/code&gt; возвращал 1 вместо 0&lt;/li&gt;
    &lt;li id=&quot;jVuK&quot;&gt;счётчики были нулями - jq тихо ломался при подсчёте длины массива&lt;/li&gt;
    &lt;li id=&quot;UOiF&quot;&gt;всё, что просто парсило JSON вручную через awscli/azure-cli, работало через собственные парсеры&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;efoI&quot;&gt;&lt;br /&gt;Короче все скрипты подготовки среды на новом рабочем железе.&lt;/p&gt;
  &lt;p id=&quot;gesr&quot;&gt;Понятно что сломалось. &lt;br /&gt;Непонятно почему asdf поставил неправильный бинарник.&lt;/p&gt;
  &lt;p id=&quot;BJjO&quot;&gt;Лезу в плагин:&lt;/p&gt;
  &lt;pre id=&quot;tIF1&quot;&gt;cat ~/.asdf/plugins/jq/bin/download&lt;/pre&gt;
  &lt;p id=&quot;BUP9&quot;&gt;&lt;br /&gt;Нахожу функцию определения архитектуры:&lt;/p&gt;
  &lt;pre id=&quot;USrs&quot;&gt;get_arch(){
  declare arch=&amp;quot;$(uname -m)&amp;quot;
  if [ &amp;quot;$arch&amp;quot; == &amp;#x27;x86_64&amp;#x27; ]; then
    echo &amp;#x27;64&amp;#x27;
  elif [ &amp;quot;$arch&amp;quot; == &amp;#x27;aarch64&amp;#x27; ]; then
    echo &amp;#x27;64&amp;#x27;     # &amp;lt;--- вот оно
  elif [ &amp;quot;$arch&amp;quot; == &amp;#x27;arm64&amp;#x27; ]; then
    echo &amp;#x27;64&amp;#x27;     # &amp;lt;--- и вот
  elif [ &amp;quot;$arch&amp;quot; == &amp;#x27;i386&amp;#x27; ]; then
    echo &amp;#x27;32&amp;#x27;
  ...
}&lt;/pre&gt;
  &lt;p id=&quot;BwxY&quot;&gt;&lt;br /&gt;И чуть ниже - как формируется имя файла для скачивания:&lt;/p&gt;
  &lt;pre id=&quot;Ranb&quot;&gt;guessed_file=&amp;quot;jq-linux$arch&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;sNdj&quot;&gt;&lt;br /&gt;То есть: aarch64 -&amp;gt; get_arch() возвращает &amp;#x27;64&amp;#x27; -&amp;gt; скачивается jq-linux64 - это x86-64 бинарник.&lt;/p&gt;
  &lt;p id=&quot;ji1X&quot;&gt;Самое смешное - в том же файле есть функция guess_download_url(), которая это правильно обрабатывает:&lt;/p&gt;
  &lt;pre id=&quot;OGff&quot;&gt;guess_download_url() {
  ...
  if [ &amp;quot;$arch&amp;quot; == &amp;#x27;aarch64&amp;#x27; ]; then
    arch=&amp;quot;arm64&amp;quot;
  fi
  ...
}&lt;/pre&gt;
  &lt;p id=&quot;21E2&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Mr9c&quot;&gt;Правильная логика есть. Но эта функция - мёртвый код. &lt;br /&gt;Нигде не вызывается. Кто-то написал, не подключил и забыл. &lt;br /&gt;А в &lt;code&gt;download()&lt;/code&gt; используется старая &lt;code&gt;get_arch()&lt;/code&gt;, которая для любой 64-битной архитектуры, включая ARM, выдаёт одинаковое &amp;#x27;64&amp;#x27;.&lt;/p&gt;
  &lt;p id=&quot;ZpJI&quot;&gt;А на GitHub Releases у jq 1.7.1+ есть отдельный jq-linux-arm64. &lt;br /&gt;Он существует. &lt;br /&gt;Просто плагин про него не знает.&lt;/p&gt;
  &lt;p id=&quot;fPfd&quot;&gt;&lt;br /&gt;Правлю плагин (PR с фиксом позже сделаю в плагин):&lt;/p&gt;
  &lt;p id=&quot;j3Bb&quot;&gt;было:&lt;/p&gt;
  &lt;pre id=&quot;z7ge&quot;&gt;elif [ &amp;quot;$arch&amp;quot; == &amp;#x27;aarch64&amp;#x27; ]; then
    echo &amp;#x27;64&amp;#x27;
elif [ &amp;quot;$arch&amp;quot; == &amp;#x27;arm64&amp;#x27; ]; then
    echo &amp;#x27;64&amp;#x27;&lt;/pre&gt;
  &lt;p id=&quot;Hp39&quot;&gt;стало:&lt;/p&gt;
  &lt;pre id=&quot;FV23&quot;&gt;elif [ &amp;quot;$arch&amp;quot; == &amp;#x27;aarch64&amp;#x27; ]; then
    echo &amp;#x27;arm64&amp;#x27;
elif [ &amp;quot;$arch&amp;quot; == &amp;#x27;arm64&amp;#x27; ]; then
    echo &amp;#x27;arm64&amp;#x27;&lt;/pre&gt;
  &lt;p id=&quot;azFW&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;KFFT&quot;&gt;И имя файла:&lt;/p&gt;
  &lt;p id=&quot;pV5N&quot;&gt;было:&lt;/p&gt;
  &lt;pre id=&quot;oJfm&quot;&gt;guessed_file=&amp;quot;jq-linux$arch&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;2ecH&quot;&gt;стало:&lt;/p&gt;
  &lt;pre id=&quot;P29B&quot;&gt;case &amp;quot;$arch&amp;quot; in
  64)    guessed_file=&amp;quot;jq-linux64&amp;quot; ;;
  32)    guessed_file=&amp;quot;jq-linux32&amp;quot; ;;
  arm64) guessed_file=&amp;quot;jq-linux-arm64&amp;quot; ;;
  *)     guessed_file=&amp;quot;jq-linux-$arch&amp;quot; ;;
esac&lt;/pre&gt;
  &lt;p id=&quot;4jAd&quot;&gt;&lt;br /&gt;Переустанавливаю:&lt;/p&gt;
  &lt;pre id=&quot;GwPo&quot;&gt;asdf uninstall jq 1.8.1
asdf install jq 1.7.1

file ~/.asdf/installs/jq/1.7.1/bin/jq
ELF 64-bit LSB executable, ARM aarch64 ✅

echo &amp;#x27;[]&amp;#x27; | jq -e &amp;#x27;type == &amp;quot;array&amp;quot;&amp;#x27;; echo &amp;quot;exit: $?&amp;quot;
exit: 0 ✅&lt;/pre&gt;
  &lt;p id=&quot;lG0s&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;wn1g&quot;&gt;Запускаю скрипт клонирования и контексты всех куберентисов.&lt;br /&gt;Всё работает. Красота.&lt;/p&gt;
  &lt;p id=&quot;gWCV&quot;&gt;&lt;br /&gt;&lt;u&gt;Итоги&lt;/u&gt;&lt;br /&gt;- планшет умер, виртуалка поднялась, среда восстановлена. Полдня потрачено.&lt;br /&gt;- из них часа два - на отладку того, что &lt;u&gt;казалось&lt;/u&gt; сломанным скриптом или токеном.&lt;br /&gt;- причина: jq x86-64 на aarch64-системе работает через эмуляцию, частично. Простые операции - норм. Чуть сложнее - всё, привет нулям и зависаниям.&lt;br /&gt;- баг в asdf-плагине для jq: get_arch() возвращает 64 для любой 64-битной архитектуры, ARM в том числе. В итоге всегда скачивается jq-linux64 (x86-64).&lt;br /&gt;- правильная логика в том же файле есть - в мёртвой функции guess_download_url(), которая никогда не вызывается.&lt;/p&gt;
  &lt;p id=&quot;yKed&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;aseb&quot;&gt;Нули.&lt;/p&gt;

</content></entry><entry><id>kruchkov_alexandr:wWa-INvQvJH</id><link rel="alternate" type="text/html" href="https://teletype.in/@kruchkov_alexandr/wWa-INvQvJH?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kruchkov_alexandr"></link><title>Когда kubectl врёт</title><published>2026-02-11T18:07:58.287Z</published><updated>2026-02-11T18:12:32.010Z</updated><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/c4/34/c43441cf-cf71-463d-ac0d-d41be3a40d1d.png&quot;&gt;Сегодня мне для нашего кубернетис оператора надо было поменять Namespaced scope на Cluster scope у одного CRD.</summary><content type="html">
  &lt;p id=&quot;qToV&quot;&gt;&lt;br /&gt;Сегодня мне для нашего кубернетис оператора надо было поменять &lt;code&gt;Namespaced scope&lt;/code&gt; на &lt;code&gt;Cluster scope&lt;/code&gt; у одного CRD.&lt;/p&gt;
  &lt;p id=&quot;zxO3&quot;&gt;&lt;br /&gt;Казалось бы рутинная задача: &lt;/p&gt;
  &lt;ul id=&quot;XJgZ&quot;&gt;
    &lt;li id=&quot;XfFz&quot;&gt;удалить старый CRD&lt;/li&gt;
    &lt;li id=&quot;meDE&quot;&gt;накатить новый с scope: Cluster (через ArgoCD сменой версии)&lt;/li&gt;
    &lt;li id=&quot;cvqj&quot;&gt;обновить манифесты custom resources, чтобы не было namespace в метаданных.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;OUIE&quot;&gt;Поднял локальный kind-кластер, протестировал  всё летает. &lt;br /&gt;Накатил на stage-кластер  готово, CRD установился, статус &lt;code&gt;Established&lt;/code&gt;.&lt;br /&gt;Применяю тестовый манифест:&lt;/p&gt;
  &lt;pre id=&quot;thOb&quot;&gt;kubectl apply -f test.yaml&lt;/pre&gt;
  &lt;p id=&quot;wvNL&quot;&gt;И тут...&lt;/p&gt;
  &lt;pre id=&quot;Qk7y&quot;&gt;Error from server (NotFound): error when creating &amp;quot;test.yaml&amp;quot;: 
the server could not find the requested resource 
(post myresources.operator.example.com)&lt;/pre&gt;
  &lt;figure id=&quot;lHEI&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c4/34/c43441cf-cf71-463d-ac0d-d41be3a40d1d.png&quot; width=&quot;480&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;0k8U&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;e2ly&quot;&gt;Поехали дебажить&lt;/h3&gt;
  &lt;p id=&quot;Nt1U&quot;&gt;CRD в кластере есть:&lt;/p&gt;
  &lt;pre id=&quot;hrFv&quot;&gt;kubectl get crd myresources.operator.example.com
NAME                                 CREATED AT
myresources.operator.example.com     2026-02-11T15:23:50Z&lt;/pre&gt;
  &lt;p id=&quot;BX53&quot;&gt;Статус &lt;code&gt;Established&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;cXIP&quot;&gt;kubectl get crd myresources.operator.example.com -o 
jsonpath=&amp;#x27;{.status.conditions[?(@.type==&amp;quot;Established&amp;quot;)]}&amp;#x27;
{&amp;quot;lastTransitionTime&amp;quot;:&amp;quot;2026-02-11T15:23:50Z&amp;quot;,
&amp;quot;message&amp;quot;:&amp;quot;the initial names have been accepted&amp;quot;,
&amp;quot;reason&amp;quot;:&amp;quot;InitialNamesAccepted&amp;quot;,
&amp;quot;status&amp;quot;:&amp;quot;True&amp;quot;,
&amp;quot;type&amp;quot;:&amp;quot;Established&amp;quot;}&lt;/pre&gt;
  &lt;p id=&quot;HWtZ&quot;&gt;&lt;code&gt;Scope &lt;/code&gt;правильный:&lt;/p&gt;
  &lt;pre id=&quot;SoLQ&quot;&gt;kubectl get crd myresources.operator.example.com 
-o jsonpath=&amp;#x27;{.spec.scope}&amp;#x27;
Cluster&lt;/pre&gt;
  &lt;p id=&quot;aNbW&quot;&gt;List работает:&lt;/p&gt;
  &lt;pre id=&quot;rtYW&quot;&gt;kubectl get --raw /apis/operator.example.com/v1alpha1/myresources
{&amp;quot;kind&amp;quot;:&amp;quot;MyResourceList&amp;quot;,&amp;quot;apiVersion&amp;quot;:&amp;quot;operator.example.com/v1alpha1&amp;quot;,
&amp;quot;items&amp;quot;:[]}&lt;/pre&gt;
  &lt;p id=&quot;pvyS&quot;&gt;А apply нет. 404. Не найдено. Бред.&lt;/p&gt;
  &lt;p id=&quot;Mdqh&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;tuAS&quot;&gt;Начинаем копать&lt;/h3&gt;
  &lt;p id=&quot;Fy8B&quot;&gt;Первая мысль &amp;quot;&lt;em&gt;может API ещё не зарегистрировался до конца?&lt;/em&gt;&amp;quot;&lt;br /&gt;Жду минуту. Пять минут. Десять минут.&lt;br /&gt;Та же херня.&lt;/p&gt;
  &lt;p id=&quot;AtPt&quot;&gt;Вторая мысль &amp;quot;&lt;em&gt;может права? RBAC?&lt;/em&gt;&amp;quot;&lt;br /&gt;Проверяю:&lt;/p&gt;
  &lt;pre id=&quot;Q980&quot;&gt;kubectl auth can-i create myresources.operator.example.com
yes&lt;/pre&gt;
  &lt;p id=&quot;LVvT&quot;&gt;&lt;br /&gt;Права есть. API отвечает. CRD Established. &lt;/p&gt;
  &lt;p id=&quot;yK0j&quot;&gt;А apply возвращает 404. Бред.&lt;/p&gt;
  &lt;p id=&quot;pxfK&quot;&gt;Ладно, смотрим что kubectl вообще пытается сделать.&lt;br /&gt;Включаю verbose режим:&lt;/p&gt;
  &lt;pre id=&quot;LqnI&quot;&gt;kubectl create -f test.yaml -v=9 2&amp;gt;&amp;amp;1 | grep -E &amp;quot;POST |Request Body|namespaces/&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;mjJK&quot;&gt;И вот тут я вижу ЭТО:&lt;/p&gt;
  &lt;pre id=&quot;Xw3k&quot;&gt;Request Body: {&amp;quot;apiVersion&amp;quot;:&amp;quot;operator.example.com/v1alpha1&amp;quot;,&amp;quot;kind&amp;quot;:&amp;quot;MyResource&amp;quot;,&amp;quot;metadata&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;my-test-resource&amp;quot;,&amp;quot;namespace&amp;quot;:&amp;quot;my-namespace&amp;quot;}, ...}

curl -v -XPOST ... &amp;#x27;https://.../apis/operator.example.com/v1alpha1/namespaces/my-namespace/myresources?...&amp;#x27;

POST https://.../apis/operator.example.com/v1alpha1/namespaces/my-namespace/myresources 404 Not Found&lt;/pre&gt;
  &lt;p id=&quot;qfWv&quot;&gt;Стоп.&lt;/p&gt;
  &lt;p id=&quot;wVll&quot;&gt;&lt;code&gt;kubectl &lt;/code&gt;пытается создать ресурс по &lt;code&gt;namespaced &lt;/code&gt;URL: &lt;/p&gt;
  &lt;pre id=&quot;2Sq1&quot;&gt;.../namespaces/my-namespace/myresources.&lt;/pre&gt;
  &lt;section style=&quot;background-color:hsl(hsl(55,  86%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;GlHs&quot;&gt;Но ресурс-то у меня теперь &lt;code&gt;cluster-scoped&lt;/code&gt; !!!!!!!&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;0DKy&quot;&gt;Для него URL &lt;code&gt;должен &lt;/code&gt;быть &lt;/p&gt;
  &lt;pre id=&quot;1Bxs&quot;&gt;.../apis/operator.example.com/v1alpha1/myresources (без /namespaces/).&lt;/pre&gt;
  &lt;p id=&quot;HBPL&quot;&gt;Более того, &lt;code&gt;kubectl &lt;/code&gt;сам подставляет namespace в тело запроса, хотя в моём манифесте его нет!&lt;/p&gt;
  &lt;p id=&quot;0Wnf&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;u1pe&quot;&gt;Откуда namespace в запросе?&lt;/h3&gt;
  &lt;p id=&quot;7KAf&quot;&gt;Проверяю текущий контекст:&lt;/p&gt;
  &lt;pre id=&quot;DIrL&quot;&gt;kubectl config get-contexts
CURRENT   NAME                           NAMESPACE
*         my-staging-cluster-context     my-namespace&lt;/pre&gt;
  &lt;p id=&quot;8y4R&quot;&gt;Ага. У меня в контексте задан default namespace my-namespace.&lt;/p&gt;
  &lt;p id=&quot;t7IX&quot;&gt;kubectl видит:&lt;/p&gt;
  &lt;ul id=&quot;Bo7v&quot;&gt;
    &lt;li id=&quot;2csd&quot;&gt;в контексте есть namespace&lt;/li&gt;
    &lt;li id=&quot;uVFo&quot;&gt;в манифесте &lt;code&gt;metadata.namespace&lt;/code&gt; не указан&lt;/li&gt;
    &lt;li id=&quot;YxWF&quot;&gt;думает: &amp;quot;&lt;em&gt;ресурс же namespaced, надо подставить namespace из контекста!&lt;/em&gt;&amp;quot;&lt;/li&gt;
    &lt;li id=&quot;WhP4&quot;&gt;подставляет &lt;code&gt;namespace &lt;/code&gt;и строит URL для &lt;code&gt;namespaced &lt;/code&gt;ресурса&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;2suw&quot;&gt;Но откуда kubectl взял, что ресурс &lt;code&gt;namespaced&lt;/code&gt;? &lt;/p&gt;
  &lt;p id=&quot;gtOC&quot;&gt;Хер ли ты такой инициативный-то?&lt;/p&gt;
  &lt;p id=&quot;nvB2&quot;&gt;Пришлось углубиться в документацию, стаковерфлоу и немного потыкать бесплатный джемини...&lt;/p&gt;
  &lt;p id=&quot;esix&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;sGmf&quot;&gt;Discovery cache  вот где собака зарыта &lt;/h3&gt;
  &lt;blockquote id=&quot;cCXo&quot;&gt;я уже дед, если так говорю? да?😢 &lt;/blockquote&gt;
  &lt;p id=&quot;wgiY&quot;&gt;kubectl делает discovery при первом запросе к новой API-группе  запрашивает список всех ресурсов этой группы и их параметры:&lt;/p&gt;
  &lt;ul id=&quot;fcBV&quot;&gt;
    &lt;li id=&quot;CHWN&quot;&gt;какие есть kinds&lt;/li&gt;
    &lt;li id=&quot;ZdPW&quot;&gt;какие shortNames (например, po для pods, svc для service)&lt;/li&gt;
    &lt;li id=&quot;iFFd&quot;&gt;namespaced или cluster-scoped&lt;/li&gt;
    &lt;li id=&quot;XHMi&quot;&gt;какие verbs доступны (create, get, list, etc.)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;kAaF&quot;&gt;Эта информация сохраняется в &lt;code&gt;кэше&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;jUUL&quot;&gt;~/.kube/cache/discovery/&amp;lt;server-host&amp;gt;/v1/serverresources.json&lt;/pre&gt;
  &lt;p id=&quot;KnYR&quot;&gt;Я даже не знал о его существовании.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(34,  84%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;WLcZ&quot;&gt;Кэш позволяет kubectl не дёргать API-сервер при каждом kubectl get po, а сразу знать, что po = pods.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;LvpY&quot;&gt;Проверяю discovery для моего ресурса:&lt;/p&gt;
  &lt;pre id=&quot;iGTT&quot;&gt;kubectl get --raw /apis/operator.example.com/v1alpha1&lt;/pre&gt;
  &lt;p id=&quot;2wo4&quot;&gt;Вывод:&lt;/p&gt;
  &lt;pre id=&quot;Gr8z&quot;&gt;{
  &amp;quot;resources&amp;quot;: [
    {
      &amp;quot;name&amp;quot;: &amp;quot;myresources&amp;quot;,
      &amp;quot;singularName&amp;quot;: &amp;quot;myresource&amp;quot;,
      &amp;quot;namespaced&amp;quot;: false,
      &amp;quot;kind&amp;quot;: &amp;quot;MyResource&amp;quot;,
      &amp;quot;verbs&amp;quot;: [&amp;quot;delete&amp;quot;,&amp;quot;deletecollection&amp;quot;,&amp;quot;get&amp;quot;,&amp;quot;list&amp;quot;,&amp;quot;patch&amp;quot;,&amp;quot;create&amp;quot;,&amp;quot;update&amp;quot;,&amp;quot;watch&amp;quot;]
    }
  ]
}&lt;/pre&gt;
  &lt;p id=&quot;iRo8&quot;&gt;API-сервер правильно отвечает: &lt;/p&gt;
  &lt;pre id=&quot;Ef58&quot;&gt;&amp;quot;namespaced&amp;quot;: false.&lt;/pre&gt;
  &lt;p id=&quot;7c8S&quot;&gt;Значит проблема в кэше kubectl. &lt;/p&gt;
  &lt;p id=&quot;1Hxt&quot;&gt;Он закэшировал старую версию CRD (когда тот был namespaced), и теперь считает, что ресурс всё ещё namespaced!&lt;/p&gt;
  &lt;p id=&quot;KDCH&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;5u94&quot;&gt;TTL кэша discovery&lt;/h3&gt;
  &lt;p id=&quot;HAgf&quot;&gt;До Kubernetes v1.22 (август 2021) TTL кэша discovery в kubectl составлял 10 минут.&lt;br /&gt;Многие до сих пор думают, что достаточно подождать 10 минут, и кэш обновится сам. Хуй вам. &lt;/p&gt;
  &lt;p id=&quot;8YG9&quot;&gt;С этого коммита&lt;/p&gt;
  &lt;ul id=&quot;sSsS&quot;&gt;
    &lt;li id=&quot;lUAt&quot;&gt;&lt;a href=&quot;https://github.com/kubernetes/kubernetes/commit/94f7f922054d0aa4aa07d572a940ec0dda842646&quot; target=&quot;_blank&quot;&gt;https://github.com/kubernetes/kubernetes/commit/94f7f922054d0aa4aa07d572a940ec0dda842646&lt;/a&gt; &lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;virs&quot;&gt;(PR #107141, merged в августе 2021) &lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(34,  84%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;ThVT&quot;&gt;дефолтное значение TTL увеличили до 6 часов:&lt;/p&gt;
  &lt;/section&gt;
  &lt;pre id=&quot;VNnX&quot;&gt;return diskcached.NewCachedDiscoveryClientForConfig(
    config, 
    discoveryCacheDir, 
    httpCacheDir, 
    time.Duration(6*time.Hour)  // было 10*time.Minute
)&lt;/pre&gt;
  &lt;p id=&quot;NrfD&quot;&gt;&lt;br /&gt;Изменение вступило в силу:&lt;/p&gt;
  &lt;ul id=&quot;huTs&quot;&gt;
    &lt;li id=&quot;mRuL&quot;&gt;для kubectl  с Kubernetes v1.22 (август 2021)&lt;/li&gt;
    &lt;li id=&quot;RnX7&quot;&gt;для всех client-go клиентов  с Kubernetes v1.25&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;MuAy&quot;&gt;Причина: &lt;/p&gt;
  &lt;ul id=&quot;l8XN&quot;&gt;
    &lt;li id=&quot;dqCc&quot;&gt;на больших кластерах с сотнями CRD каждый discovery-запрос может включать сотни GET запросов для всех group-versions. Это вызывало client-side rate limiting и тормозило kubectl.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;CBfH&quot;&gt;Увеличение TTL снизило частоту discovery, но создало новую проблему: &lt;br /&gt;устаревший кэш может жить до 6 часов, и ждать не имеет смысла.&lt;/p&gt;
  &lt;p id=&quot;8DBr&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;HUhP&quot;&gt;Решение 1: Применить с пустым cache-dir&lt;/h3&gt;
  &lt;p id=&quot;VwhD&quot;&gt;Самый быстрый способ  заставить kubectl не использовать старый кэш:&lt;/p&gt;
  &lt;pre id=&quot;9G1A&quot;&gt;kubectl apply -f test.yaml --cache-dir=/tmp/kubectl-cache-fresh&lt;/pre&gt;
  &lt;p id=&quot;GGRb&quot;&gt;kubectl создаст свежий кэш в /tmp/kubectl-cache-fresh, сделает discovery заново, увидит, что ресурс cluster-scoped, и применит манифест корректно:&lt;/p&gt;
  &lt;pre id=&quot;2TWC&quot;&gt;myresource.operator.example.com/my-test-resource created&lt;/pre&gt;
  &lt;p id=&quot;krck&quot;&gt;После первого успешного apply новый кэш уже содержит правильную информацию. &lt;br /&gt;Дальше можно работать без --cache-dir.&lt;/p&gt;
  &lt;p id=&quot;UQ0V&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;ttdZ&quot;&gt;Решение 2: Сбросить namespace в контексте&lt;/h3&gt;
  &lt;p id=&quot;CJ7R&quot;&gt;Если не хочется трогать кэш, можно убрать default namespace из контекста:&lt;/p&gt;
  &lt;pre id=&quot;MgEB&quot;&gt;kubectl config set-context --current --namespace=&amp;quot;&amp;quot;
kubectl apply -f test.yaml&lt;/pre&gt;
  &lt;p id=&quot;OxHe&quot;&gt;Без namespace в контексте kubectl не будет подставлять его автоматически, и запрос пойдёт на cluster-scoped URL.&lt;/p&gt;
  &lt;p id=&quot;nHjY&quot;&gt;После применения можно вернуть namespace:&lt;/p&gt;
  &lt;pre id=&quot;coVQ&quot;&gt;kubectl config set-context --current --namespace=&amp;quot;my-namespace&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;VHmh&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;mJte&quot;&gt;&lt;br /&gt;Решение 3: Очистить кэш вручную&lt;/h3&gt;
  &lt;p id=&quot;dWYL&quot;&gt;Удалить старый кэш для данного API-сервера:&lt;/p&gt;
  &lt;pre id=&quot;oHM0&quot;&gt;# Посмотреть, где кэш
ls ~/.kube/cache/discovery/

# Удалить кэш для нужного хоста (подставьте свой каталог, я вообще всё дропнул))
rm -rf ~/.kube/cache/discovery/&amp;lt;host-from-above&amp;gt;
rm -rf ~/.kube/cache/http/&amp;lt;same-host&amp;gt;

kubectl apply -f test.yaml&lt;/pre&gt;
  &lt;p id=&quot;tjFf&quot;&gt;kubectl при следующем запросе сделает discovery заново.&lt;/p&gt;
  &lt;p id=&quot;Q2nc&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;1ejH&quot;&gt;Решение 4: Обновить discovery через api-resources&lt;/h3&gt;
  &lt;p id=&quot;aR8I&quot;&gt;Официальный способ форсировать обновление discovery cache без удаления файлов:&lt;/p&gt;
  &lt;pre id=&quot;ttpW&quot;&gt;kubectl api-resources --api-group=operator.example.com
kubectl apply -f test.yaml&lt;/pre&gt;
  &lt;p id=&quot;pcwd&quot;&gt;Команда kubectl api-resources принудительно обновляет discovery cache для указанной API-группы.&lt;br /&gt;Это безопаснее, чем удаление файлов кэша вручную, и работает для конкретной группы, не затрагивая остальные.&lt;/p&gt;
  &lt;p id=&quot;MzuD&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;QsGW&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;VhzC&quot;&gt;Как работает discovery cache **. &lt;/h2&gt;
  &lt;blockquote id=&quot;n08I&quot;&gt;Материал со звёздочками для самых любознательных. &lt;/blockquote&gt;
  &lt;p id=&quot;Qe1f&quot;&gt;Разберём подробнее, чтобы понять, почему эта проблема вообще возникает.&lt;/p&gt;
  &lt;h3 id=&quot;tyUX&quot;&gt;Архитектура кэша&lt;/h3&gt;
  &lt;p id=&quot;sZ7m&quot;&gt;kubectl хранит кэш в двух местах:&lt;/p&gt;
  &lt;ul id=&quot;nMBl&quot;&gt;
    &lt;li id=&quot;mMUH&quot;&gt;Discovery cache&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;ZT0j&quot;&gt;~/.kube/cache/discovery/&amp;lt;server-host&amp;gt;/&lt;/pre&gt;
  &lt;ul id=&quot;Oeaq&quot;&gt;
    &lt;ul id=&quot;hnOe&quot;&gt;
      &lt;li id=&quot;L2ab&quot;&gt;файлы serverresources.json для каждой group-version&lt;/li&gt;
      &lt;li id=&quot;RgjJ&quot;&gt;содержат информацию о ресурсах: kinds, shortNames, verbs, scope&lt;/li&gt;
    &lt;/ul&gt;
    &lt;li id=&quot;qa70&quot;&gt;HTTP cache&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;RtRd&quot;&gt; ~/.kube/cache/http/&amp;lt;server-host&amp;gt;/&lt;/pre&gt;
  &lt;ul id=&quot;4IAp&quot;&gt;
    &lt;ul id=&quot;JyM4&quot;&gt;
      &lt;li id=&quot;eZe0&quot;&gt;кэш HTTP-запросов к API-серверу&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;V0pk&quot;&gt;&lt;/h3&gt;
  &lt;p id=&quot;mFSZ&quot;&gt;Процесс работы&lt;/p&gt;
  &lt;ul id=&quot;Wcyz&quot;&gt;
    &lt;li id=&quot;YCCk&quot;&gt;Пользователь запускает&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;AQ70&quot;&gt;kubectl apply -f test.yaml&lt;/pre&gt;
  &lt;ul id=&quot;RnGT&quot;&gt;
    &lt;li id=&quot;FvPI&quot;&gt;kubectl читает манифест, видит &lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;72eG&quot;&gt;kind: MyResource&lt;/pre&gt;
  &lt;ul id=&quot;jNJZ&quot;&gt;
    &lt;li id=&quot;HXt7&quot;&gt;Проверяет локальный кэш:&lt;/li&gt;
    &lt;ul id=&quot;AjqM&quot;&gt;
      &lt;li id=&quot;f1fi&quot;&gt;есть ли файл &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;Vrx9&quot;&gt;~/.kube/cache/discovery/&amp;lt;host&amp;gt;/operator.example.com/v1alpha1/serverresources.json?&lt;/pre&gt;
  &lt;ul id=&quot;6TtT&quot;&gt;
    &lt;ul id=&quot;kYFo&quot;&gt;
      &lt;li id=&quot;KpXa&quot;&gt;валиден ли этот файл (не истёк ли TTL)?&lt;/li&gt;
    &lt;/ul&gt;
    &lt;li id=&quot;O47C&quot;&gt;Если кэш валиден, kubectl использует закэшированную информацию&lt;/li&gt;
    &lt;li id=&quot;ygqk&quot;&gt;Если кэш устарел или отсутствует, делает GET запрос к API-серверу:&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;1Ujf&quot;&gt;   GET /apis/operator.example.com/v1alpha1&lt;/pre&gt;
  &lt;ul id=&quot;xzzP&quot;&gt;
    &lt;li id=&quot;ci62&quot;&gt;Сохраняет результат в кэш с TTL = 6 часов&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;yejD&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Ffvx&quot;&gt;Проблема при смене scope&lt;/h3&gt;
  &lt;p id=&quot;7Fvl&quot;&gt;Когда CRD меняет scope с Namespaced на Cluster:&lt;/p&gt;
  &lt;ul id=&quot;4ruN&quot;&gt;
    &lt;li id=&quot;omTt&quot;&gt;Старый кэш содержит&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;hGwR&quot;&gt; &amp;quot;namespaced&amp;quot;: true&lt;/pre&gt;
  &lt;ul id=&quot;c0wI&quot;&gt;
    &lt;li id=&quot;vxnX&quot;&gt;Новый API отдаёт &lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;DaGz&quot;&gt;&amp;quot;namespaced&amp;quot;: false&lt;/pre&gt;
  &lt;ul id=&quot;bF7I&quot;&gt;
    &lt;li id=&quot;BdZ7&quot;&gt;но kubectl использует &lt;strong&gt;старый кэш&lt;/strong&gt; (TTL ещё не протух)&lt;/li&gt;
    &lt;li id=&quot;vakC&quot;&gt;kubectl строит URL для namespaced ресурса: &lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;vlR5&quot;&gt;.../namespaces/&amp;lt;ns&amp;gt;/myresources&lt;/pre&gt;
  &lt;ul id=&quot;jpa2&quot;&gt;
    &lt;li id=&quot;Rywc&quot;&gt;Для cluster-scoped ресурса такой endpoint не существует и он выплёвывает &lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;Fyqz&quot;&gt;404&lt;/pre&gt;
  &lt;p id=&quot;d36a&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;3Hwe&quot;&gt;Почему kubectl подставляет namespace&lt;/h3&gt;
  &lt;p id=&quot;5icf&quot;&gt;Когда kubectl видит:&lt;/p&gt;
  &lt;ul id=&quot;iqvB&quot;&gt;
    &lt;li id=&quot;SIYe&quot;&gt;ресурс namespaced (по кэшу)&lt;/li&gt;
    &lt;li id=&quot;8t3i&quot;&gt;в манифесте нет metadata.namespace&lt;/li&gt;
    &lt;li id=&quot;uzg5&quot;&gt;в текущем контексте задан default namespace&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;Aifv&quot;&gt;Он делает вывод: &amp;quot;&lt;em&gt;пользователь забыл указать namespace, подставлю из контекста&lt;/em&gt;&amp;quot;.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(34,  84%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;9BVl&quot;&gt;Для cluster-scoped ресурсов это некорректное поведение, но kubectl этого не знает, потому что кэш врёт.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;kLFn&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;pB7x&quot;&gt;Discovery cache и масштабирование&lt;/h2&gt;
  &lt;p id=&quot;F46V&quot;&gt;Эта проблема  часть более широкой темы: discovery cache как ограничение расширяемости Kubernetes.&lt;/p&gt;
  &lt;p id=&quot;4nPe&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;vLCr&quot;&gt;Проблема с большим количеством CRD&lt;/h3&gt;
  &lt;p id=&quot;n48y&quot;&gt;На кластерах с сотнями CRD (например, с Crossplane, GCP Config Connector, Azure Service Operator) discovery превращается в бутылочное горлышко:&lt;/p&gt;
  &lt;ul id=&quot;Dyuv&quot;&gt;
    &lt;li id=&quot;ELTI&quot;&gt;kubectl запускает discovery для всех, сука, group-versions в кластере&lt;/li&gt;
    &lt;li id=&quot;BGHG&quot;&gt;Если у вас 500 CRD, это 500+ GET-запросов&lt;/li&gt;
    &lt;li id=&quot;DstN&quot;&gt;Каждый kubectl get pods может инициировать discovery (если кэш устарел)&lt;/li&gt;
    &lt;li id=&quot;OXsq&quot;&gt;Это вызывает client-side rate limiting:&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;izpV&quot;&gt;   Waited for 1.140352693s due to client-side throttling, not priority and fairness&lt;/pre&gt;
  &lt;p id=&quot;7qEw&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;lOZt&quot;&gt;Увеличение TTL решило одну проблему (частые discovery-запросы), но создало другую (долгоживущий устаревший кэш).&lt;/p&gt;
  &lt;p id=&quot;uPhn&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;AjtM&quot;&gt;Будущие улучшения&lt;/h3&gt;
  &lt;p id=&quot;w6ru&quot;&gt;Обсуждаются следующие подходы:&lt;/p&gt;
  &lt;ul id=&quot;l6o8&quot;&gt;
    &lt;li id=&quot;m8k4&quot;&gt;Инкрементальный discovery обновлять только изменившиеся group-versions&lt;/li&gt;
    &lt;li id=&quot;h7qn&quot;&gt;server-side filtering  позволить клиенту запрашивать discovery только для нужных kinds&lt;/li&gt;
    &lt;li id=&quot;DlRd&quot;&gt;Configurable TTL сделать TTL настраиваемым через флаг&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;haRI&quot;&gt;Подробнее: &lt;/p&gt;
  &lt;ul id=&quot;TzLB&quot;&gt;
    &lt;li id=&quot;D1Tz&quot;&gt;&lt;a href=&quot;https://github.com/kubernetes/kubernetes/issues/107077&quot; target=&quot;_blank&quot;&gt;https://github.com/kubernetes/kubernetes/issues/107077&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;aiaF&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;0b6l&quot;&gt;Выводы  и практические советы&lt;/h3&gt;
  &lt;ul id=&quot;0jjX&quot;&gt;
    &lt;li id=&quot;uyXD&quot;&gt;При смене scope CRD сразу очищайте кэш, используйте --cache-dir или запустите kubectl api-resources&lt;/li&gt;
    &lt;li id=&quot;Oumn&quot;&gt;На CI/CD не используйте общий кэш между пайплайнами или очищайте перед каждым запуском&lt;/li&gt;
    &lt;li id=&quot;268D&quot;&gt;При траблшутинге kubectl первым делом включайте -v=9 и смотрите на URL запросов&lt;/li&gt;
    &lt;li id=&quot;rZvp&quot;&gt;Если видите &amp;quot;&lt;em&gt;client-side throttlin&lt;/em&gt;g&amp;quot;  проблема в discovery, не в API-сервере&lt;/li&gt;
    &lt;li id=&quot;itgo&quot;&gt;Не ждите 10 минут, это устаревшая информация (до Kubernetes v1.22), сейчас TTL = 6 часов&lt;/li&gt;
    &lt;li id=&quot;01Tp&quot;&gt;Официальный способ обновить кэш (форсирует re-discovery)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;MRWG&quot;&gt; kubectl api-resources --api-group=&amp;lt;ваша-группа&amp;gt; &lt;/pre&gt;
  &lt;p id=&quot;yD2u&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;igOm&quot;&gt;Альтернатива для операторов&lt;/h3&gt;
  &lt;p id=&quot;aMe5&quot;&gt;Если пишете оператор, который программно работает с cluster-scoped ресурсами:&lt;/p&gt;
  &lt;ul id=&quot;Knkc&quot;&gt;
    &lt;li id=&quot;KBJM&quot;&gt;используйте &lt;code&gt;typed clients&lt;/code&gt; вместо &lt;code&gt;dynamic clients&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;13GF&quot;&gt;если нужен dynamic client, явно указывайте scope через REST mapper&lt;/li&gt;
    &lt;li id=&quot;0S7M&quot;&gt;не полагайтесь на автоматическую подстановку namespace&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;fB8x&quot;&gt;Пример:&lt;/p&gt;
  &lt;pre id=&quot;noDU&quot;&gt;import (
    &amp;quot;k8s.io/apimachinery/pkg/apis/meta/v1/unstructured&amp;quot;
    &amp;quot;k8s.io/apimachinery/pkg/runtime/schema&amp;quot;
)

gvr := schema.GroupVersionResource{
    Group:    &amp;quot;operator.example.com&amp;quot;,
    Version:  &amp;quot;v1alpha1&amp;quot;,
    Resource: &amp;quot;myresources&amp;quot;,
}

// Cluster-scoped resource  без namespace
obj, err := dynamicClient.Resource(gvr).Create(ctx, &amp;amp;unstructured.Unstructured{
    Object: map[string]interface{}{
        &amp;quot;apiVersion&amp;quot;: &amp;quot;operator.example.com/v1alpha1&amp;quot;,
        &amp;quot;kind&amp;quot;:       &amp;quot;MyResource&amp;quot;,
        &amp;quot;metadata&amp;quot;: map[string]interface{}{
            &amp;quot;name&amp;quot;: &amp;quot;my-instance&amp;quot;,
            // НЕТ namespace!
        },
    },
}, metav1.CreateOptions{})

&lt;/pre&gt;
  &lt;p id=&quot;ruCu&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;yGRK&quot;&gt;Ссылки&lt;/h3&gt;
  &lt;ul id=&quot;rc2J&quot;&gt;
    &lt;li id=&quot;KtMa&quot;&gt;&lt;a href=&quot;https://serverfault.com/questions/1049006/what-does-kubectl-store-in-the-cache&quot; target=&quot;_blank&quot;&gt;https://serverfault.com/questions/1049006/what-does-kubectl-store-in-the-cache&lt;/a&gt; &lt;/li&gt;
    &lt;li id=&quot;OhJO&quot;&gt;&lt;a href=&quot;https://jonnylangefeld.com/blog/the-kubernetes-discovery-cache-blessing-and-curse&quot; target=&quot;_blank&quot;&gt;https://jonnylangefeld.com/blog/the-kubernetes-discovery-cache-blessing-and-curse&lt;/a&gt; &lt;/li&gt;
    &lt;li id=&quot;DnBx&quot;&gt;&lt;a href=&quot;https://github.com/kubernetes/kubernetes/pull/107141&quot; target=&quot;_blank&quot;&gt;https://github.com/kubernetes/kubernetes/pull/107141&lt;/a&gt; &lt;/li&gt;
    &lt;li id=&quot;GaXU&quot;&gt;&lt;a href=&quot;https://github.com/kubernetes/kubernetes/issues/107077&quot; target=&quot;_blank&quot;&gt;https://github.com/kubernetes/kubernetes/issues/107077&lt;/a&gt; &lt;/li&gt;
    &lt;li id=&quot;2l7q&quot;&gt;&lt;a href=&quot;https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/config_flags.go&quot; target=&quot;_blank&quot;&gt;https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/config_flags.go&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;

</content></entry></feed>