<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Alexandr Kruchkov</title><generator>teletype.in</generator><description><![CDATA[Alexandr Kruchkov]]></description><image><url>https://img2.teletype.in/files/d7/1a/d71aebd3-1e54-44df-8b93-df48a3b6628e.png</url><title>Alexandr Kruchkov</title><link>https://teletype.in/@kruchkov_alexandr</link></image><link>https://teletype.in/@kruchkov_alexandr?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kruchkov_alexandr</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/kruchkov_alexandr?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/kruchkov_alexandr?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Tue, 28 Apr 2026 22:57:20 GMT</pubDate><lastBuildDate>Tue, 28 Apr 2026 22:57:20 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@kruchkov_alexandr/mwVCBuS1y6T</guid><link>https://teletype.in/@kruchkov_alexandr/mwVCBuS1y6T?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kruchkov_alexandr</link><comments>https://teletype.in/@kruchkov_alexandr/mwVCBuS1y6T?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kruchkov_alexandr#comments</comments><dc:creator>kruchkov_alexandr</dc:creator><title>Просто алерт. Просто Арго.</title><pubDate>Tue, 17 Mar 2026 09:27:02 GMT</pubDate><description><![CDATA[Прилетает алёрт: HPA maxed out.]]></description><content:encoded><![CDATA[
  <p id="fgfn">Прилетает алёрт: <u>HPA maxed out.</u></p>
  <pre id="qSY5">HPA: keda-hpa-vmagent-scaler
Cluster: stg-**-uswest1
Current value: 10 (max)</pre>
  <p id="hsHT">Сперва я вообще задумался - а нахрена этот алерт? Что он мне дает? Ну уперлось в максимум, и что? Все остальное работает ок, никаких других алертов.</p>
  <p id="tR0R">Спросил у умных людей, умные люди дали умные советы, что может быть неверные триггеры трешхолда, может быть сервис в максимуме и скоро будут ошибки. Ладно, аргумент. </p>
  <p id="5S3a">Ну ок, пошёл смотреть.</p>
  <p id="aNr1"><br />Проблема первая: </p>
  <ul id="SRvp">
    <li id="h9rO">vmagent жрёт памяти больше, чем ему отведено</li>
  </ul>
  <p id="DlpZ">Первым делом смотрю что там с HPA:</p>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="nABD">NAME                      REFERENCE        TARGETS                           MIN   MAX   REPLICAS<br />keda-hpa-vmagent-scaler   VMAgent/...      5694m/40 (avg), memory: 50%/40%   2     10    10</p>
  </section>
  <p id="xnPr">Два триггера. </p>
  <ul id="h8Wd">
    <li id="eHBO">Один prometheus-based - 5.7/40, всё хорошо. </li>
    <li id="lhcD">Второй - memory: 50%/40%.<br />Вот он виновник.</li>
  </ul>
  <p id="voM3">Смотрю дальше:</p>
  <ul id="OUPk">
    <li id="mZLY">Лимит на каждый pod: 256Mi</li>
    <li id="D7Pt">Фактическое потребление: ~130 MiB на pod</li>
  </ul>
  <p id="ubbX">130/256 = 50%. Цель триггера - 40%, то есть 102 MiB.</p>
  <p id="DQW1">Это физически недостижимо - <code>vmagent</code> столько и держит в памяти просто чтобы работать, независимо от нагрузки. Горизонтальный скейлинг тут не поможет: добавишь реплик, каждая всё равно будет жрать те же 130 MiB.</p>
  <p id="bHQH">Решение простое - поднять лимит. 384Mi &gt; утилизация падает до 34%, HPA успокоится.</p>
  <p id="r2Qi">Правлю values в репозитории с ArgoCD-приложениями для кластера stg-*-uswest1:</p>
  <pre id="zLvy">resources:
  limits:
    memory: 384Mi
  requests:
    memory: 384Mi</pre>
  <p id="dfub">Коммичу, засинкал ArgoCD.</p>
  <p id="4wiN">И вот тут началось.</p>
  <p id="ellz"><br />Проблема вторая: </p>
  <ul id="pv9t">
    <li id="tK1w">ArgoCD и KEDA устроили драку</li>
  </ul>
  <p id="IMUo">После синка:</p>
  <pre id="s79w">Operation cannot be fulfilled on scaledobjects.keda.sh &quot;vmagent-scaler&quot;:
the object has been modified; please apply your changes to the latest version
and try again. Retrying attempt #1</pre>
  <p id="FYba">Классический 409. ArgoCD читает объект, хочет запатчить - а за это время KEDA уже успел его обновить.<br />Проверяю как часто это происходит:</p>
  <pre id="6NgM">kubectl -n vm get scaledobject vmagent-scaler -o jsonpath=&#x27;{.metadata.resourceVersion}&#x27;
sleep 5
kubectl -n vm get scaledobject vmagent-scaler -o jsonpath=&#x27;{.metadata.resourceVersion}&#x27;

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

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

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

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

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

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

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

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

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

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

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

]]></content:encoded></item></channel></rss>