<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Kubernetes on Меланхоличный блог</title><link>https://notes.melancholic.tech/tags/kubernetes/</link><description>Recent content in Kubernetes on Меланхоличный блог</description><generator>Hugo</generator><language>ru-RU</language><lastBuildDate>Mon, 06 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://notes.melancholic.tech/tags/kubernetes/index.xml" rel="self" type="application/rss+xml"/><item><title>Что такое Valkey и почему он заменил Redis</title><link>https://notes.melancholic.tech/projects/valkey-intro/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/valkey-intro/</guid><description>История форка Redis → Valkey: причины, лицензия, участники и что это значит для пользователей Redis в Kubernetes.</description></item><item><title>Микросервисные БД с Kubernetes, Postgres и CloudNativePG</title><link>https://notes.melancholic.tech/projects/microservice-databases/</link><pubDate>Wed, 28 Feb 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/microservice-databases/</guid><description>Перевод статьи Gabriele Bartolini о том, почему PostgreSQL в Kubernetes уместен, что такое микросервисная БД и какую роль здесь играет CloudNativePG.</description></item><item><title>Запуск Redis/Valkey в Kubernetes: StatefulSet и Sentinel</title><link>https://notes.melancholic.tech/projects/redis-statefulset-sentinel/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/redis-statefulset-sentinel/</guid><description>Пошаговое руководство по запуску Redis/Valkey в Kubernetes: от простого StatefulSet до HA-кластера с Sentinel, включая ConfigMap, Service, мониторинг и проверку фейловера.</description></item><item><title>Рецепт 1. Локальная песочница за пару минут</title><link>https://notes.melancholic.tech/projects/recipe-1-local-playground/</link><pubDate>Sun, 03 Mar 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-1-local-playground/</guid><description>Поднимаем локальную песочницу для CloudNativePG за несколько минут: kind для локального Kubernetes, установка оператора, первый PostgreSQL-кластер, плагин cnpg для kubectl.</description></item><item><title>Кластер Valkey в Kubernetes: полное руководство</title><link>https://notes.melancholic.tech/projects/valkey-clusters-kubernetes-guide/</link><pubDate>Tue, 03 Dec 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/valkey-clusters-kubernetes-guide/</guid><description>Подробное руководство по запуску продакшн-кластера Valkey в Kubernetes: ConfigMap, StatefulSet с Parallel-подменеджментом, init-скрипты, headless-сервис, Prometheus-мониторинг, масштабирование и обработка сбоев.</description></item><item><title>Рецепт 2. Что CloudNativePG создаёт по умолчанию</title><link>https://notes.melancholic.tech/projects/recipe-2-default-resources/</link><pubDate>Fri, 08 Mar 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-2-default-resources/</guid><description>Разбираем, какие ConfigMaps, Secrets и глобальные объекты PostgreSQL автоматически создаются вместе с кластером CloudNativePG: TLS-сертификаты, mTLS для replication, пользователь app и БД app.</description></item><item><title>Рецепт 3. А как же без суперпользователя?!</title><link>https://notes.melancholic.tech/projects/recipe-3-no-superuser/</link><pubDate>Mon, 11 Mar 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-3-no-superuser/</guid><description>Почему в CloudNativePG у пользователя postgres по умолчанию нет сетевого доступа, как устроен pg_hba и когда стоит включать enableSuperuserAccess.</description></item><item><title>Рецепт 4. Подключение к кластеру через pgAdmin4</title><link>https://notes.melancholic.tech/projects/recipe-4-pgadmin4/</link><pubDate>Fri, 22 Mar 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-4-pgadmin4/</guid><description>Запускаем pgAdmin4 рядом с кластером CloudNativePG в режиме desktop одной командой плагина cnpg для evaluation/demo/учебных задач.</description></item><item><title>Рецепт 5. Миграция PostgreSQL в Kubernetes с ~0 downtime</title><link>https://notes.melancholic.tech/projects/recipe-5-zero-downtime-migration/</link><pubDate>Wed, 27 Mar 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-5-zero-downtime-migration/</guid><description>Как через логическую репликацию и плагин cnpg перевезти PostgreSQL из RDS, виртуалки или другого Kubernetes-оператора в CloudNativePG практически без простоя приложения.</description></item><item><title>Рецепт 6. Вертикальное масштабирование Postgres со storage. Часть 1</title><link>https://notes.melancholic.tech/projects/recipe-6-vertical-scaling-part-1/</link><pubDate>Mon, 08 Apr 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-6-vertical-scaling-part-1/</guid><description>Часть 1 истории про вертикальное масштабирование Postgres в Kubernetes: отдельный volume для WAL, временные tablespaces, разделение I/O для таблиц и индексов.</description></item><item><title>Рецепт 7. Вертикальное масштабирование Postgres со storage. Часть 2</title><link>https://notes.melancholic.tech/projects/recipe-7-vertical-scaling-part-2/</link><pubDate>Tue, 16 Apr 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-7-vertical-scaling-part-2/</guid><description>Часть 2: горизонтальное партиционирование таблиц в PostgreSQL и связка с tablespaces для разнесения горячих и холодных данных по разным storage-классам.</description></item><item><title>Рецепт 8. Тестируем PostgreSQL 17 в Kubernetes</title><link>https://notes.melancholic.tech/projects/recipe-8-pg17-testing/</link><pubDate>Mon, 10 Jun 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-8-pg17-testing/</guid><description>Поднимаем кластер CloudNativePG на бета-образах PostgreSQL 17 для участия в тестировании major-релиза. Подходит и как шаблон для тестов будущих версий.</description></item><item><title>Kubernetes исполнилось 10: где сейчас PostgreSQL?</title><link>https://notes.melancholic.tech/projects/kubernetes-turned-ten/</link><pubDate>Tue, 25 Jun 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/kubernetes-turned-ten/</guid><description>Десятилетие Kubernetes глазами автора CloudNativePG: почему БД переезжают в Kubernetes, чем оператор отличается от голых VM, что нужно DBA, инфраструктурным и продуктовым командам.</description></item><item><title>Делаю мир моей единой точкой отказа на PostgreSQL</title><link>https://notes.melancholic.tech/projects/world-single-point-of-failure/</link><pubDate>Thu, 27 Jun 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/world-single-point-of-failure/</guid><description>CloudNativePG 1.24 умеет декларативный switchover между Kubernetes-кластерами в разных регионах через demotion/promotion-токены. Разбор на примере банка с двумя ЦОДами в Европе.</description></item><item><title>Рецепт 9. Сеть и storage в кластере CloudNativePG</title><link>https://notes.melancholic.tech/projects/recipe-9-network-storage/</link><pubDate>Tue, 09 Jul 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-9-network-storage/</guid><description>Какие Service&amp;rsquo;ы и PVC создаёт CloudNativePG для PostgreSQL-кластера: -rw / -ro / -r сервисы, label-selector для primary, PVC для PGDATA / WAL / tablespaces.</description></item><item><title>Рецепт 10. Симуляция продакшн-среды PostgreSQL в Kubernetes с помощью Kind</title><link>https://notes.melancholic.tech/projects/recipe-10-kind-multi-node/</link><pubDate>Wed, 14 Aug 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-10-kind-multi-node/</guid><description>Как создать многонодовый Kubernetes-кластер в Kind с выделенными узлами для PostgreSQL, используя node labels и nodeSelector для изоляции рабочих нагрузок.</description></item><item><title>Рецепт 11. Изоляция PostgreSQL-нагрузок в Kubernetes с Kind</title><link>https://notes.melancholic.tech/projects/recipe-11-workload-isolation/</link><pubDate>Fri, 16 Aug 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-11-workload-isolation/</guid><description>Как использовать taints, tolerations и pod anti-affinity для полной изоляции PostgreSQL-инстансов на выделенных узлах Kubernetes — поверх node labels из Рецепта 10.</description></item><item><title>Рецепт 12. Доступ к PostgreSQL извне Kubernetes через Service Templates</title><link>https://notes.melancholic.tech/projects/recipe-12-service-templates/</link><pubDate>Mon, 26 Aug 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-12-service-templates/</guid><description>Как использовать managed services и service templates в CloudNativePG 1.24 для создания LoadBalancer-сервиса и подключения к PostgreSQL извне кластера Kubernetes.</description></item><item><title>Рецепт 13. Настройка синхронной репликации PostgreSQL</title><link>https://notes.melancholic.tech/projects/recipe-13-synchronous-replication/</link><pubDate>Tue, 10 Sep 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-13-synchronous-replication/</guid><description>Как настроить синхронную репликацию в CloudNativePG 1.24: quorum-based и priority-based методы, synchronous_commit и баланс между долговечностью данных и производительностью.</description></item><item><title>Рецепт 14. Полезные CLI-утилиты для CloudNativePG</title><link>https://notes.melancholic.tech/projects/recipe-14-cli-tools/</link><pubDate>Fri, 11 Oct 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-14-cli-tools/</guid><description>Три CLI-утилиты, которые сильно ускоряют ежедневную работу с CloudNativePG: view-secret для расшифровки secret&amp;rsquo;ов, view-cert для инспекции TLS-сертификатов и stern для одновременного просмотра логов с нескольких подов.</description></item><item><title>Рецепт 15. Major-апгрейды PostgreSQL онлайн через логическую репликацию</title><link>https://notes.melancholic.tech/projects/recipe-15-major-online-upgrades/</link><pubDate>Wed, 11 Dec 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-15-major-online-upgrades/</guid><description>Декларативный online major-апгрейд PostgreSQL 15 → 17 средствами CloudNativePG 1.25: новые ресурсы Publication и Subscription, импорт схемы через bootstrap, проверка синхронизации данных.</description></item><item><title>Рецепт 16. Баланс между durability и self-healing в синхронной репликации</title><link>https://notes.melancholic.tech/projects/recipe-16-data-durability-self-healing/</link><pubDate>Thu, 26 Dec 2024 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-16-data-durability-self-healing/</guid><description>Новая опция dataDurability в CloudNativePG 1.25: required vs preferred. Как мигрировать с deprecated minSyncReplicas / maxSyncReplicas без потери self-healing.</description></item><item><title>Иммутабельное будущее расширений PostgreSQL в Kubernetes</title><link>https://notes.melancholic.tech/projects/immutable-future-extensions/</link><pubDate>Mon, 03 Mar 2025 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/immutable-future-extensions/</guid><description>Как PostgreSQL extension_control_path и Kubernetes ImageVolume вместе решают проблему расширений в иммутабельных контейнерах: расширения как отдельные OCI-образы, монтируемые декларативно через CloudNativePG.</description></item><item><title>Рецепт 17. In-place major-апгрейды PostgreSQL через pg_upgrade</title><link>https://notes.melancholic.tech/projects/recipe-17-in-place-major-upgrades/</link><pubDate>Thu, 03 Apr 2025 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-17-in-place-major-upgrades/</guid><description>CloudNativePG 1.26: декларативный in-place major-апгрейд PostgreSQL. Меняешь imageName в манифесте — оператор сам прогоняет pg_upgrade &amp;ndash;link. Тест на 2.2 ТБ: апгрейд за 33 секунды.</description></item><item><title>Рецепт 18. pgvector в Kubernetes через CloudNativePG</title><link>https://notes.melancholic.tech/projects/recipe-18-pgvector/</link><pubDate>Thu, 05 Jun 2025 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-18-pgvector/</guid><description>Декларативная установка pgvector в PostgreSQL-кластер на Kubernetes через ресурс Database из CloudNativePG. Без ручного CREATE EXTENSION.</description></item><item><title>Рецепт 19. Тонкий контроль старта Postgres через startup probes</title><link>https://notes.melancholic.tech/projects/recipe-19-startup-probes/</link><pubDate>Tue, 17 Jun 2025 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-19-startup-probes/</guid><description>CloudNativePG 1.26: расширенные startup-пробы для Postgres. Базовый и продвинутый режимы, стратегии pg_isready / query / streaming, контроль допустимого лага через maximumLag.</description></item><item><title>Рецепт 20. Тонкий контроль кластеров Postgres через readiness probes</title><link>https://notes.melancholic.tech/projects/recipe-20-readiness-probes/</link><pubDate>Wed, 25 Jun 2025 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-20-readiness-probes/</guid><description>CloudNativePG 1.26: расширенные readiness-пробы. Стратегии pg_isready / query / streaming, тонкая настройка для реплик с maximumLag, опасность flapping при синхронной репликации.</description></item><item><title>Рецепт 21. Тонкий контроль кластеров Postgres через liveness probes</title><link>https://notes.melancholic.tech/projects/recipe-21-liveness-probes/</link><pubDate>Fri, 15 Aug 2025 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-21-liveness-probes/</guid><description>CloudNativePG 1.27: liveness-пробы для PostgreSQL. Новый primary isolation check уменьшает окно split-brain до ~30 секунд через built-in аналог Patroni failsafe mode — без внешних зависимостей.</description></item><item><title>PostgreSQL 18 в Kubernetes уже сегодня — через CloudNativePG</title><link>https://notes.melancholic.tech/projects/pg18-on-kubernetes/</link><pubDate>Fri, 26 Sep 2025 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/pg18-on-kubernetes/</guid><description>PostgreSQL 18 вышел 25 сентября 2025: async I/O, B-tree skip scans, OAuth 2.0, extension_control_path. Минимальный образ — на 45% меньше, чем для 17. Деплой через CloudNativePG в три шага.</description></item><item><title>Рецепт 22. Новый supply chain и image catalogs CloudNativePG</title><link>https://notes.melancholic.tech/projects/recipe-22-supply-chain-image-catalogs/</link><pubDate>Mon, 06 Oct 2025 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-22-supply-chain-image-catalogs/</guid><description>Уход с Docker Hub postgres на собственный supply chain CloudNativePG: Snyk, cosign-подписи, SBOM, multi-arch. Image Catalogs позволяют декларативно управлять PostgreSQL-образами в кластерах без жёстких имён. Spread upgrades для парков БД.</description></item><item><title>Рецепт 23. Управление расширениями через ImageVolume в CloudNativePG</title><link>https://notes.melancholic.tech/projects/recipe-23-extensions-imagevolume/</link><pubDate>Mon, 01 Dec 2025 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-23-extensions-imagevolume/</guid><description>Реальная практика того, что обещали в «Иммутабельном будущем расширений»: PostgreSQL 18 + Kubernetes ImageVolume + CloudNativePG 1.27. Расширения — отдельные OCI-образы (pgvector ~613KB), монтируются в минимальный operand (260MB) без пересборки. Полный пример с pgvector и PostGIS.</description></item><item><title>Рецепт 24. Миграция из Crunchy PGO в PostgreSQL 18 с CloudNativePG</title><link>https://notes.melancholic.tech/projects/recipe-24-migrate-from-pgo/</link><pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-24-migrate-from-pgo/</guid><description>Пошаговое руководство по миграции кластера PostgreSQL 17 под управлением Crunchy PGO v6 на PostgreSQL 18 под управлением CloudNativePG. Рассматриваются два пути: офлайн-миграция через pg_dump и онлайн-миграция через логическую репликацию.</description></item><item><title>Рецепт 25. Декларативные роли и беспарольный TLS в CloudNativePG 1.30</title><link>https://notes.melancholic.tech/projects/recipe-25-declarative-roles-passwordless-tls/</link><pubDate>Mon, 06 Jul 2026 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/recipe-25-declarative-roles-passwordless-tls/</guid><description>CloudNativePG 1.30: новый CRD DatabaseRole выносит роли из спеки Cluster в отдельный namespaced-ресурс, а встроенная выдача TLS-клиентских сертификатов позволяет подключаться к PostgreSQL вообще без паролей. Полный рабочий пример с pg_hba и cert-аутентификацией.</description></item><item><title>Redis (Valkey) в Kubernetes</title><link>https://notes.melancholic.tech/projects/redis-kubernetes/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/redis-kubernetes/</guid><description>Практическое руководство по запуску Redis и Valkey в Kubernetes — от базового StatefulSet до операторов (Spotahome, OT-CONTAINER-KIT, Opstree) и автономных кластеров с Sentinel.</description></item><item><title>Atlantis: автоматизация Terraform-PR — как работает и как задеплоить</title><link>https://notes.melancholic.tech/posts/atlantis-%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%B0%D1%82%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F-terraform-pr-%D0%BA%D0%B0%D0%BA-%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%B0%D0%B5%D1%82-%D0%B8-%D0%BA%D0%B0%D0%BA-%D0%B7%D0%B0%D0%B4%D0%B5%D0%BF%D0%BB%D0%BE%D0%B8%D1%82%D1%8C/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/posts/atlantis-%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%B0%D1%82%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F-terraform-pr-%D0%BA%D0%B0%D0%BA-%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%B0%D0%B5%D1%82-%D0%B8-%D0%BA%D0%B0%D0%BA-%D0%B7%D0%B0%D0%B4%D0%B5%D0%BF%D0%BB%D0%BE%D0%B8%D1%82%D1%8C/</guid><description>&lt;blockquote
 class="border-primary bg-muted/30 hover:bg-muted/40 my-6 rounded-r-lg border-l-4 py-4 pr-4 pl-6 italic transition-all duration-200 ease-out hover:translate-x-1"&gt;
 &lt;div class="prose prose-sm max-w-none"&gt;
 &lt;p&gt;Сводный пост по мотивам двух статей:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws-builders/streamlining-terraform-pr-automation-with-atlantis-on-ecs-fargate-5c21"target="_blank"
 class="inline-flex items-center gap-1"
&gt;Streamlining Terraform PR Automation with Atlantis on ECS Fargate&lt;svg class="h-3 w-3 flex-shrink-0" id="external-link" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 3h6v6m-11 5L21 3m-3 10v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/&gt;&lt;/svg&gt;
&lt;/a&gt; — Husain Yusuf, апрель 2025.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://heshandharmasena.medium.com/atlantis-on-kubernetes-4dc9b248fd13"target="_blank"
 class="inline-flex items-center gap-1"
&gt;Terraform Automation Using Atlantis&lt;svg class="h-3 w-3 flex-shrink-0" id="external-link" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 3h6v6m-11 5L21 3m-3 10v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/&gt;&lt;/svg&gt;
&lt;/a&gt; — Heshan Dharmasena, февраль 2025.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Все скриншоты Atlantis UI и примеры PR-обсуждений см. в оригиналах — здесь только выжимка и код-примеры для быстрого старта.&lt;/p&gt;</description></item><item><title>cnpg: kubectl-плагин для CloudNativePG — подробный разбор всех команд</title><link>https://notes.melancholic.tech/posts/cnpg-kubectl-%D0%BF%D0%BB%D0%B0%D0%B3%D0%B8%D0%BD-%D0%B4%D0%BB%D1%8F-cloudnativepg-%D0%BF%D0%BE%D0%B4%D1%80%D0%BE%D0%B1%D0%BD%D1%8B%D0%B9-%D1%80%D0%B0%D0%B7%D0%B1%D0%BE%D1%80-%D0%B2%D1%81%D0%B5%D1%85-%D0%BA%D0%BE%D0%BC%D0%B0%D0%BD%D0%B4/</link><pubDate>Thu, 30 Apr 2026 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/posts/cnpg-kubectl-%D0%BF%D0%BB%D0%B0%D0%B3%D0%B8%D0%BD-%D0%B4%D0%BB%D1%8F-cloudnativepg-%D0%BF%D0%BE%D0%B4%D1%80%D0%BE%D0%B1%D0%BD%D1%8B%D0%B9-%D1%80%D0%B0%D0%B7%D0%B1%D0%BE%D1%80-%D0%B2%D1%81%D0%B5%D1%85-%D0%BA%D0%BE%D0%BC%D0%B0%D0%BD%D0%B4/</guid><description>&lt;p&gt;&lt;code&gt;cnpg&lt;/code&gt; — официальный плагин для &lt;code&gt;kubectl&lt;/code&gt;, который ставится через &lt;a href="https://notes.melancholic.tech/posts/krew-%D0%BF%D0%B0%D0%BA%D0%B5%D1%82%D0%BD%D1%8B%D0%B9-%D0%BC%D0%B5%D0%BD%D0%B5%D0%B4%D0%B6%D0%B5%D1%80-%D0%BF%D0%BB%D0%B0%D0%B3%D0%B8%D0%BD%D0%BE%D0%B2-%D0%B4%D0%BB%D1%8F-kubectl-%D0%B8-%D0%BE%D0%B1%D0%B7%D0%BE%D1%80-%D0%BF%D0%BE%D0%BB%D0%B5%D0%B7%D0%BD%D1%8B%D1%85-%D0%BF%D0%BB%D0%B0%D0%B3%D0%B8%D0%BD%D0%BE%D0%B2/"&gt;krew&lt;/a&gt; и сильно облегчает повседневную работу с PostgreSQL-кластерами под управлением &lt;a href="https://cloudnative-pg.io/"target="_blank"
 class="inline-flex items-center gap-1"
&gt;CloudNativePG&lt;svg class="h-3 w-3 flex-shrink-0" id="external-link" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 3h6v6m-11 5L21 3m-3 10v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/&gt;&lt;/svg&gt;
&lt;/a&gt;. Без него почти каждая операция превращается в длинную цепочку &lt;code&gt;kubectl get/patch/exec&lt;/code&gt; с YAML-патчами и ручным &lt;code&gt;base64 -d&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;В статье — установка и подробный разбор &lt;strong&gt;всех практически полезных команд&lt;/strong&gt; плагина, с примерами и краткими комментариями, когда что использовать.&lt;/p&gt;</description></item><item><title>krew: пакетный менеджер плагинов для kubectl и обзор полезных плагинов</title><link>https://notes.melancholic.tech/posts/krew-%D0%BF%D0%B0%D0%BA%D0%B5%D1%82%D0%BD%D1%8B%D0%B9-%D0%BC%D0%B5%D0%BD%D0%B5%D0%B4%D0%B6%D0%B5%D1%80-%D0%BF%D0%BB%D0%B0%D0%B3%D0%B8%D0%BD%D0%BE%D0%B2-%D0%B4%D0%BB%D1%8F-kubectl-%D0%B8-%D0%BE%D0%B1%D0%B7%D0%BE%D1%80-%D0%BF%D0%BE%D0%BB%D0%B5%D0%B7%D0%BD%D1%8B%D1%85-%D0%BF%D0%BB%D0%B0%D0%B3%D0%B8%D0%BD%D0%BE%D0%B2/</link><pubDate>Thu, 30 Apr 2026 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/posts/krew-%D0%BF%D0%B0%D0%BA%D0%B5%D1%82%D0%BD%D1%8B%D0%B9-%D0%BC%D0%B5%D0%BD%D0%B5%D0%B4%D0%B6%D0%B5%D1%80-%D0%BF%D0%BB%D0%B0%D0%B3%D0%B8%D0%BD%D0%BE%D0%B2-%D0%B4%D0%BB%D1%8F-kubectl-%D0%B8-%D0%BE%D0%B1%D0%B7%D0%BE%D1%80-%D0%BF%D0%BE%D0%BB%D0%B5%D0%B7%D0%BD%D1%8B%D1%85-%D0%BF%D0%BB%D0%B0%D0%B3%D0%B8%D0%BD%D0%BE%D0%B2/</guid><description>&lt;p&gt;&lt;a href="https://krew.sigs.k8s.io/"target="_blank"
 class="inline-flex items-center gap-1"
&gt;krew&lt;svg class="h-3 w-3 flex-shrink-0" id="external-link" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 3h6v6m-11 5L21 3m-3 10v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/&gt;&lt;/svg&gt;
&lt;/a&gt; — это &lt;strong&gt;пакетный менеджер плагинов для &lt;code&gt;kubectl&lt;/code&gt;&lt;/strong&gt;, официальный проект Kubernetes SIG CLI. Делает с плагинами &lt;code&gt;kubectl&lt;/code&gt; примерно то же, что &lt;code&gt;apt&lt;/code&gt;/&lt;code&gt;brew&lt;/code&gt; делают с программами в системе: ищет, ставит, обновляет и удаляет. После установки плагин становится подкомандой — &lt;code&gt;kubectl &amp;lt;plugin&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;На момент написания в официальном индексе &lt;a href="https://github.com/kubernetes-sigs/krew-index"target="_blank"
 class="inline-flex items-center gap-1"
&gt;krew-index&lt;svg class="h-3 w-3 flex-shrink-0" id="external-link" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 3h6v6m-11 5L21 3m-3 10v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/&gt;&lt;/svg&gt;
&lt;/a&gt; ~&lt;strong&gt;359 плагинов&lt;/strong&gt;: для отладки, сети, безопасности, multi-cluster, работы с секретами, логами, манифестами и т. д. Без krew пришлось бы вручную собирать бинарники, класть их в &lt;code&gt;$PATH&lt;/code&gt; и следить за обновлениями.&lt;/p&gt;</description></item><item><title>Timoni: пакетный менеджер для Kubernetes на CUE</title><link>https://notes.melancholic.tech/posts/timoni-%D0%BF%D0%B0%D0%BA%D0%B5%D1%82%D0%BD%D1%8B%D0%B9-%D0%BC%D0%B5%D0%BD%D0%B5%D0%B4%D0%B6%D0%B5%D1%80-%D0%B4%D0%BB%D1%8F-kubernetes-%D0%BD%D0%B0-cue/</link><pubDate>Thu, 30 Apr 2026 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/posts/timoni-%D0%BF%D0%B0%D0%BA%D0%B5%D1%82%D0%BD%D1%8B%D0%B9-%D0%BC%D0%B5%D0%BD%D0%B5%D0%B4%D0%B6%D0%B5%D1%80-%D0%B4%D0%BB%D1%8F-kubernetes-%D0%BD%D0%B0-cue/</guid><description>&lt;p&gt;&lt;a href="https://timoni.sh"target="_blank"
 class="inline-flex items-center gap-1"
&gt;Timoni&lt;svg class="h-3 w-3 flex-shrink-0" id="external-link" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 3h6v6m-11 5L21 3m-3 10v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/&gt;&lt;/svg&gt;
&lt;/a&gt; — пакетный менеджер для Kubernetes от Stefan Prodan (автора Flux CD). Вместо Go-шаблонов и YAML использует язык &lt;a href="https://cuelang.org/"target="_blank"
 class="inline-flex items-center gap-1"
&gt;CUE&lt;svg class="h-3 w-3 flex-shrink-0" id="external-link" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 3h6v6m-11 5L21 3m-3 10v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/&gt;&lt;/svg&gt;
&lt;/a&gt; для описания, валидации и дистрибуции Kubernetes-приложений через OCI-реестры.&lt;/p&gt;</description></item><item><title>Postgres в Kubernetes</title><link>https://notes.melancholic.tech/projects/postgres-kubernetes/</link><pubDate>Tue, 28 Apr 2026 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/projects/postgres-kubernetes/</guid><description>Перевод и адаптация серии статей Gabriele Bartolini (EDB / CloudNativePG Maintainer) о запуске PostgreSQL в Kubernetes: от микросервисной парадигмы и локальной песочницы до миграций ~0 downtime, вертикального масштабирования и распределённых топологий.</description></item><item><title>Масштабирование PostgreSQL в Kubernetes</title><link>https://notes.melancholic.tech/posts/%D0%BC%D0%B0%D1%81%D1%88%D1%82%D0%B0%D0%B1%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-postgresql-%D0%B2-kubernetes/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/posts/%D0%BC%D0%B0%D1%81%D1%88%D1%82%D0%B0%D0%B1%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-postgresql-%D0%B2-kubernetes/</guid><description>&lt;h2 id="аргументы-в-пользу-вертикального-масштабирования"&gt;Аргументы в пользу вертикального масштабирования&lt;/h2&gt;
&lt;p&gt;Если вы читали какие-либо статьи или книги по проектированию систем, то, вероятно, знаете, что такое вертикальное и горизонтальное масштабирование и каковы преимущества горизонтального масштабирования. Прежде чем я объясню, как правильно настроить горизонтальное масштабирование в Postgres, позвольте мне привести пример, когда этого делать не следует.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Простота: База данных на одном узле означает, что вы можете запустить свою базу данных «из коробки». Хотя я рекомендую использовать &lt;a href="https://pgtune.leopard.in.ua/"target="_blank"
 class="inline-flex items-center gap-1"
&gt;PGTune&lt;svg class="h-3 w-3 flex-shrink-0" id="external-link" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 3h6v6m-11 5L21 3m-3 10v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/&gt;&lt;/svg&gt;
&lt;/a&gt; для быстрой настройки или посетить &lt;a href="http://postgresqlco.nf/"target="_blank"
 class="inline-flex items-center gap-1"
&gt;postconf&lt;svg class="h-3 w-3 flex-shrink-0" id="external-link" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 3h6v6m-11 5L21 3m-3 10v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/&gt;&lt;/svg&gt;
&lt;/a&gt; для полного описания.&lt;/li&gt;
&lt;li&gt;Более простое резервное копирование и восстановление: Нет необходимости думать о состоянии реплик при создании или применении резервных копий.&lt;/li&gt;
&lt;li&gt;Отсутствие сетевой нагрузки, особенно при операциях с интенсивной записью.&lt;/li&gt;
&lt;li&gt;Временное решение: Если вам нужно решение прямо сейчас, это то, что нужно.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="предварительная-подготовка"&gt;Предварительная подготовка&lt;/h2&gt;
&lt;p&gt;Необходимо установить следющий тулинг&lt;/p&gt;</description></item><item><title>Kubernetes в проде: 7 ошибок, которые совершают даже опытные</title><link>https://notes.melancholic.tech/posts/kubernetes-%D0%B2-%D0%BF%D1%80%D0%BE%D0%B4%D0%B5-7-%D0%BE%D1%88%D0%B8%D0%B1%D0%BE%D0%BA-%D0%BA%D0%BE%D1%82%D0%BE%D1%80%D1%8B%D0%B5-%D1%81%D0%BE%D0%B2%D0%B5%D1%80%D1%88%D0%B0%D1%8E%D1%82-%D0%B4%D0%B0%D0%B6%D0%B5-%D0%BE%D0%BF%D1%8B%D1%82%D0%BD%D1%8B%D0%B5/</link><pubDate>Wed, 25 Feb 2026 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/posts/kubernetes-%D0%B2-%D0%BF%D1%80%D0%BE%D0%B4%D0%B5-7-%D0%BE%D1%88%D0%B8%D0%B1%D0%BE%D0%BA-%D0%BA%D0%BE%D1%82%D0%BE%D1%80%D1%8B%D0%B5-%D1%81%D0%BE%D0%B2%D0%B5%D1%80%D1%88%D0%B0%D1%8E%D1%82-%D0%B4%D0%B0%D0%B6%D0%B5-%D0%BE%D0%BF%D1%8B%D1%82%D0%BD%D1%8B%D0%B5/</guid><description>&lt;p&gt;Если ты деплоишь сервисы в Kubernetes, скорее всего, ты уже наступал на эти грабли. А если нет — вот чеклист, чтобы не наступить.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Не включён livenessProbe и readinessProbe&lt;br&gt;
Без этих пробы Kubernetes не понимает, когда перезапустить контейнер или исключить под из сервисов. В итоге — трафик идёт в мёртвые инстансы, а ты ловишь 500-е.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 10&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;resources.requests и limits не заданы&lt;br&gt;
Если не выставить ресурсы, поды будут жрать всё подряд, а kube-scheduler может завалить ноды. Определи baseline и поставь лимиты с запасом:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;resources:
requests:
cpu: &amp;ldquo;100m&amp;rdquo;
memory: &amp;ldquo;128Mi&amp;rdquo;
limits:
cpu: &amp;ldquo;500m&amp;rdquo;
memory: &amp;ldquo;512Mi&amp;rdquo;&lt;/p&gt;</description></item><item><title>k9s: управление Kubernetes из терминала</title><link>https://notes.melancholic.tech/posts/k9s-%D1%83%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5-kubernetes-%D0%B8%D0%B7-%D1%82%D0%B5%D1%80%D0%BC%D0%B8%D0%BD%D0%B0%D0%BB%D0%B0/</link><pubDate>Fri, 13 Feb 2026 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/posts/k9s-%D1%83%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5-kubernetes-%D0%B8%D0%B7-%D1%82%D0%B5%D1%80%D0%BC%D0%B8%D0%BD%D0%B0%D0%BB%D0%B0/</guid><description>&lt;p&gt;k9s - это интерактивный терминальный интерфейс для управления Kubernetes кластерами. Представьте себе файловый менеджер Midnight Commander, но для Kubernetes ресурсов. Всё управление кластером в одном окне терминала с горячими клавишами.&lt;/p&gt;
&lt;h2 id="установка"&gt;Установка&lt;/h2&gt;
&lt;h3 id="macos"&gt;macOS&lt;/h3&gt;
&lt;div
 class="code-block-container border-border bg-card my-6 overflow-hidden rounded-xl border shadow-sm transition-all duration-200 ease-out hover:-translate-y-0.5 hover:shadow-md"&gt;
 
 &lt;div
 class="code-block-header bg-muted/30 border-border flex items-center justify-between border-b px-4 py-3"&gt;
 
 &lt;div class="flex items-center gap-2"&gt;
 &lt;div class="text-muted-foreground flex-shrink-0"&gt;
 &lt;svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"&gt;
 &lt;path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" /&gt;
&lt;/svg&gt;
 &lt;/div&gt;
 &lt;span class="text-muted-foreground text-sm font-medium"&gt;
 BASH
 &lt;/span&gt;
 &lt;/div&gt;

 
 &lt;div class="flex items-center gap-2"&gt;
 &lt;button
 class="collapse-code-btn text-muted-foreground hover:text-primary hover:bg-primary/10 focus:ring-primary/20 flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium transition-all duration-200 ease-out focus:ring-2 focus:outline-none"
 data-code-id="code-0"
 data-default-state="expanded"
 data-collapsed="false"
 data-auto-collapse-lines="30"
 data-auto-collapse-height="400"
 data-collapsed-height="120"
 title="Свернуть"
 aria-label="Свернуть"&gt;
 &lt;span class="collapse-icon"&gt;
 &lt;svg class="h-3 w-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="currentColor" d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6l-6 6z"/&gt;&lt;/svg&gt;
 &lt;/span&gt;
 &lt;span class="collapse-text hidden sm:inline"
 &gt;Свернуть&lt;/span
 &gt;
 &lt;/button&gt;
 &lt;button
 class="copy-code-btn text-muted-foreground hover:text-primary hover:bg-primary/10 focus:ring-primary/20 flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium transition-all duration-200 ease-out focus:ring-2 focus:outline-none"
 data-code-id="code-0"
 title="Копировать"
 aria-label="Копировать"&gt;
 &lt;span class="copy-icon"&gt;
 &lt;svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"&gt;
 &lt;path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" /&gt;
&lt;/svg&gt;
 &lt;/span&gt;
 &lt;span class="copy-text hidden sm:inline"
 &gt;Копировать&lt;/span
 &gt;
 &lt;/button&gt;
 &lt;/div&gt;
 &lt;/div&gt;

 
 &lt;div class="code-block-content relative" id="code-0"&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew install k9s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 
 &lt;div
 class="collapse-overlay to-card/90 pointer-events-none absolute inset-0 bg-gradient-to-b from-transparent via-transparent opacity-0 transition-opacity duration-300"&gt;
 &lt;div
 class="text-muted-foreground bg-card/80 border-border/50 hover:bg-primary/10 hover:text-primary hover:border-primary/30 absolute bottom-4 left-1/2 -translate-x-1/2 cursor-pointer rounded-full border px-3 py-1.5 text-xs backdrop-blur-sm transition-all duration-200"&gt;
 Нажмите, чтобы развернуть и увидеть больше
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;/div&gt;
&lt;/div&gt;


&lt;script&gt;
(function() {
 const codeId = 'code-0';
 const copyBtn = document.querySelector('.copy-code-btn[data-code-id="' + codeId + '"]');
 const collapseBtn = document.querySelector('.collapse-code-btn[data-code-id="' + codeId + '"]');
 const codeContainer = document.getElementById(codeId);

 if (!codeContainer) return;

 
 if (copyBtn) {
 const copyIcon = copyBtn.querySelector('.copy-icon');
 const copyText = copyBtn.querySelector('.copy-text');

 copyBtn.addEventListener('click', async function() {
 try {
 
 let codeText = '';

 
 const codeTableCell = codeContainer.querySelector('.lntd:last-child code');
 if (codeTableCell) {
 codeText = codeTableCell.textContent || codeTableCell.innerText;
 } else {
 
 const codeElement = codeContainer.querySelector('code');
 if (codeElement) {
 
 const hasInlineLineNumbers = codeElement.querySelector('.ln');
 if (hasInlineLineNumbers) {
 
 const codeLines = codeElement.querySelectorAll('.cl');
 if (codeLines.length &gt; 0) {
 codeText = Array.from(codeLines)
 .map(line =&gt; {
 const text = line.textContent || line.innerText;
 
 return text.replace(/\n+$/, '');
 })
 .join('\n')
 .replace(/\n+$/, ''); 
 } else {
 
 const allText = codeElement.textContent || codeElement.innerText;
 codeText = allText.replace(/^\d+/gm, '').replace(/^\s+/gm, '');
 }
 } else {
 
 codeText = codeElement.textContent || codeElement.innerText;
 }
 } else {
 
 codeText = codeContainer.textContent || codeContainer.innerText;
 }
 }

 
 codeText = codeText.trim();

 
 await navigator.clipboard.writeText(codeText);

 
 copyIcon.innerHTML = `\u003csvg class=\u0022h-3 w-3\u0022 fill=\u0022none\u0022 stroke=\u0022currentColor\u0022 viewBox=\u00220 0 24 24\u0022 xmlns=\u0022http:\/\/www.w3.org\/2000\/svg\u0022\u003e\n \u003cpath stroke-linecap=\u0022round\u0022 stroke-linejoin=\u0022round\u0022 stroke-width=\u00222\u0022 d=\u0022M5 13l4 4L19 7\u0022 \/\u003e\n\u003c\/svg\u003e`;
 if (copyText) {
 copyText.textContent = 'Скопировано';
 }
 copyBtn.classList.add('text-green-600');

 
 setTimeout(() =&gt; {
 copyIcon.innerHTML = `\u003csvg class=\u0022h-3 w-3\u0022 fill=\u0022none\u0022 stroke=\u0022currentColor\u0022 viewBox=\u00220 0 24 24\u0022 xmlns=\u0022http:\/\/www.w3.org\/2000\/svg\u0022\u003e\n \u003cpath stroke-linecap=\u0022round\u0022 stroke-linejoin=\u0022round\u0022 stroke-width=\u00222\u0022 d=\u0022M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\u0022 \/\u003e\n\u003c\/svg\u003e`;
 if (copyText) {
 copyText.textContent = 'Копировать';
 }
 copyBtn.classList.remove('text-green-600');
 }, 2000);

 } catch (err) {
 console.error('复制失败:', err);

 
 const range = document.createRange();
 const codeElement = codeContainer.querySelector('code') || codeContainer;
 range.selectNodeContents(codeElement);
 const selection = window.getSelection();
 selection.removeAllRanges();
 selection.addRange(range);

 
 if (copyText) {
 copyText.textContent = 'Выбрано';
 }

 setTimeout(() =&gt; {
 if (copyText) {
 copyText.textContent = 'Копировать';
 }
 selection.removeAllRanges();
 }, 2000);
 }
 });
 }

 
 if (collapseBtn) {
 const collapseIcon = collapseBtn.querySelector('.collapse-icon');
 const collapseText = collapseBtn.querySelector('.collapse-text');
 const collapseOverlay = codeContainer.querySelector('.collapse-overlay');

 
 let codeElement = codeContainer.querySelector('pre.chroma');
 if (!codeElement) {
 codeElement = codeContainer.querySelector('pre');
 }

 const defaultState = collapseBtn.dataset.defaultState || 'expanded';
 const isCollapsedAttr = collapseBtn.dataset.collapsed === 'true';
 const autoCollapseLines = parseInt(collapseBtn.dataset.autoCollapseLines) || 30;
 const autoCollapseHeight = parseInt(collapseBtn.dataset.autoCollapseHeight) || 400;
 const collapsedHeight = parseInt(collapseBtn.dataset.collapsedHeight) || 120;

 let isCollapsed = false;

 
 function initCollapse() {
 
 const shouldCollapse = isCollapsedAttr ||
 defaultState === 'collapsed' ||
 shouldAutoCollapse();

 if (shouldCollapse) {
 setCollapsed(true, false); 
 }
 }

 function shouldAutoCollapse() {
 
 if (codeElement) {
 const lines = codeElement.querySelectorAll('.line, .cl');
 const height = codeElement.offsetHeight;
 return lines.length &gt; autoCollapseLines || height &gt; autoCollapseHeight;
 }

 
 const containerHeight = codeContainer.offsetHeight;
 if (containerHeight &gt; autoCollapseHeight) {
 return true;
 }

 
 const textContent = codeContainer.textContent || codeContainer.innerText || '';
 const estimatedLines = textContent.split('\n').length;
 return estimatedLines &gt; autoCollapseLines;
 }

 function setCollapsed(collapsed, animate = true) {
 if (!collapseOverlay) return;

 isCollapsed = collapsed;

 if (collapsed) {
 
 codeContainer.style.maxHeight = collapsedHeight + 'px';
 codeContainer.style.overflow = 'hidden';
 collapseOverlay.style.opacity = '1';
 collapseOverlay.style.pointerEvents = 'auto';

 
 collapseIcon.innerHTML = `\u003csvg class=\u0022h-3 w-3\u0022 fill=\u0022none\u0022 stroke=\u0022currentColor\u0022 viewBox=\u00220 0 24 24\u0022 xmlns=\u0022http:\/\/www.w3.org\/2000\/svg\u0022\u003e\n \u003cpath stroke-linecap=\u0022round\u0022 stroke-linejoin=\u0022round\u0022 stroke-width=\u00222\u0022 d=\u0022M19 9l-7 7-7-7\u0022 \/\u003e\n\u003c\/svg\u003e`;
 if (collapseText) {
 collapseText.textContent = 'Развернуть';
 }
 collapseBtn.title = 'Развернуть';

 } else {
 
 codeContainer.style.maxHeight = '';
 codeContainer.style.overflow = '';
 collapseOverlay.style.opacity = '0';
 collapseOverlay.style.pointerEvents = 'none';

 
 collapseIcon.innerHTML = `\u003csvg class=\u0022h-3 w-3\u0022 xmlns=\u0022http:\/\/www.w3.org\/2000\/svg\u0022 viewBox=\u00220 0 24 24\u0022\u003e\u003cpath fill=\u0022currentColor\u0022 d=\u0022M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6l-6 6z\u0022\/\u003e\u003c\/svg\u003e`;
 if (collapseText) {
 collapseText.textContent = 'Свернуть';
 }
 collapseBtn.title = 'Свернуть';
 }

 
 if (animate) {
 codeContainer.style.transition = 'max-height 0.3s ease-out';
 setTimeout(() =&gt; {
 codeContainer.style.transition = '';
 }, 300);
 }
 }

 function toggleCollapse() {
 setCollapsed(!isCollapsed, true);
 }

 
 collapseBtn.addEventListener('click', toggleCollapse);

 
 if (collapseOverlay) {
 collapseOverlay.addEventListener('click', () =&gt; {
 if (isCollapsed) {
 setCollapsed(false, true);
 }
 });
 }

 
 initCollapse();
 }
})();
&lt;/script&gt;
&lt;h3 id="linux-snap"&gt;Linux (snap)&lt;/h3&gt;
&lt;div
 class="code-block-container border-border bg-card my-6 overflow-hidden rounded-xl border shadow-sm transition-all duration-200 ease-out hover:-translate-y-0.5 hover:shadow-md"&gt;
 
 &lt;div
 class="code-block-header bg-muted/30 border-border flex items-center justify-between border-b px-4 py-3"&gt;
 
 &lt;div class="flex items-center gap-2"&gt;
 &lt;div class="text-muted-foreground flex-shrink-0"&gt;
 &lt;svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"&gt;
 &lt;path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" /&gt;
&lt;/svg&gt;
 &lt;/div&gt;
 &lt;span class="text-muted-foreground text-sm font-medium"&gt;
 BASH
 &lt;/span&gt;
 &lt;/div&gt;

 
 &lt;div class="flex items-center gap-2"&gt;
 &lt;button
 class="collapse-code-btn text-muted-foreground hover:text-primary hover:bg-primary/10 focus:ring-primary/20 flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium transition-all duration-200 ease-out focus:ring-2 focus:outline-none"
 data-code-id="code-1"
 data-default-state="expanded"
 data-collapsed="false"
 data-auto-collapse-lines="30"
 data-auto-collapse-height="400"
 data-collapsed-height="120"
 title="Свернуть"
 aria-label="Свернуть"&gt;
 &lt;span class="collapse-icon"&gt;
 &lt;svg class="h-3 w-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="currentColor" d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6l-6 6z"/&gt;&lt;/svg&gt;
 &lt;/span&gt;
 &lt;span class="collapse-text hidden sm:inline"
 &gt;Свернуть&lt;/span
 &gt;
 &lt;/button&gt;
 &lt;button
 class="copy-code-btn text-muted-foreground hover:text-primary hover:bg-primary/10 focus:ring-primary/20 flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium transition-all duration-200 ease-out focus:ring-2 focus:outline-none"
 data-code-id="code-1"
 title="Копировать"
 aria-label="Копировать"&gt;
 &lt;span class="copy-icon"&gt;
 &lt;svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"&gt;
 &lt;path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" /&gt;
&lt;/svg&gt;
 &lt;/span&gt;
 &lt;span class="copy-text hidden sm:inline"
 &gt;Копировать&lt;/span
 &gt;
 &lt;/button&gt;
 &lt;/div&gt;
 &lt;/div&gt;

 
 &lt;div class="code-block-content relative" id="code-1"&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo snap install k9s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 
 &lt;div
 class="collapse-overlay to-card/90 pointer-events-none absolute inset-0 bg-gradient-to-b from-transparent via-transparent opacity-0 transition-opacity duration-300"&gt;
 &lt;div
 class="text-muted-foreground bg-card/80 border-border/50 hover:bg-primary/10 hover:text-primary hover:border-primary/30 absolute bottom-4 left-1/2 -translate-x-1/2 cursor-pointer rounded-full border px-3 py-1.5 text-xs backdrop-blur-sm transition-all duration-200"&gt;
 Нажмите, чтобы развернуть и увидеть больше
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;/div&gt;
&lt;/div&gt;


&lt;script&gt;
(function() {
 const codeId = 'code-1';
 const copyBtn = document.querySelector('.copy-code-btn[data-code-id="' + codeId + '"]');
 const collapseBtn = document.querySelector('.collapse-code-btn[data-code-id="' + codeId + '"]');
 const codeContainer = document.getElementById(codeId);

 if (!codeContainer) return;

 
 if (copyBtn) {
 const copyIcon = copyBtn.querySelector('.copy-icon');
 const copyText = copyBtn.querySelector('.copy-text');

 copyBtn.addEventListener('click', async function() {
 try {
 
 let codeText = '';

 
 const codeTableCell = codeContainer.querySelector('.lntd:last-child code');
 if (codeTableCell) {
 codeText = codeTableCell.textContent || codeTableCell.innerText;
 } else {
 
 const codeElement = codeContainer.querySelector('code');
 if (codeElement) {
 
 const hasInlineLineNumbers = codeElement.querySelector('.ln');
 if (hasInlineLineNumbers) {
 
 const codeLines = codeElement.querySelectorAll('.cl');
 if (codeLines.length &gt; 0) {
 codeText = Array.from(codeLines)
 .map(line =&gt; {
 const text = line.textContent || line.innerText;
 
 return text.replace(/\n+$/, '');
 })
 .join('\n')
 .replace(/\n+$/, ''); 
 } else {
 
 const allText = codeElement.textContent || codeElement.innerText;
 codeText = allText.replace(/^\d+/gm, '').replace(/^\s+/gm, '');
 }
 } else {
 
 codeText = codeElement.textContent || codeElement.innerText;
 }
 } else {
 
 codeText = codeContainer.textContent || codeContainer.innerText;
 }
 }

 
 codeText = codeText.trim();

 
 await navigator.clipboard.writeText(codeText);

 
 copyIcon.innerHTML = `\u003csvg class=\u0022h-3 w-3\u0022 fill=\u0022none\u0022 stroke=\u0022currentColor\u0022 viewBox=\u00220 0 24 24\u0022 xmlns=\u0022http:\/\/www.w3.org\/2000\/svg\u0022\u003e\n \u003cpath stroke-linecap=\u0022round\u0022 stroke-linejoin=\u0022round\u0022 stroke-width=\u00222\u0022 d=\u0022M5 13l4 4L19 7\u0022 \/\u003e\n\u003c\/svg\u003e`;
 if (copyText) {
 copyText.textContent = 'Скопировано';
 }
 copyBtn.classList.add('text-green-600');

 
 setTimeout(() =&gt; {
 copyIcon.innerHTML = `\u003csvg class=\u0022h-3 w-3\u0022 fill=\u0022none\u0022 stroke=\u0022currentColor\u0022 viewBox=\u00220 0 24 24\u0022 xmlns=\u0022http:\/\/www.w3.org\/2000\/svg\u0022\u003e\n \u003cpath stroke-linecap=\u0022round\u0022 stroke-linejoin=\u0022round\u0022 stroke-width=\u00222\u0022 d=\u0022M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\u0022 \/\u003e\n\u003c\/svg\u003e`;
 if (copyText) {
 copyText.textContent = 'Копировать';
 }
 copyBtn.classList.remove('text-green-600');
 }, 2000);

 } catch (err) {
 console.error('复制失败:', err);

 
 const range = document.createRange();
 const codeElement = codeContainer.querySelector('code') || codeContainer;
 range.selectNodeContents(codeElement);
 const selection = window.getSelection();
 selection.removeAllRanges();
 selection.addRange(range);

 
 if (copyText) {
 copyText.textContent = 'Выбрано';
 }

 setTimeout(() =&gt; {
 if (copyText) {
 copyText.textContent = 'Копировать';
 }
 selection.removeAllRanges();
 }, 2000);
 }
 });
 }

 
 if (collapseBtn) {
 const collapseIcon = collapseBtn.querySelector('.collapse-icon');
 const collapseText = collapseBtn.querySelector('.collapse-text');
 const collapseOverlay = codeContainer.querySelector('.collapse-overlay');

 
 let codeElement = codeContainer.querySelector('pre.chroma');
 if (!codeElement) {
 codeElement = codeContainer.querySelector('pre');
 }

 const defaultState = collapseBtn.dataset.defaultState || 'expanded';
 const isCollapsedAttr = collapseBtn.dataset.collapsed === 'true';
 const autoCollapseLines = parseInt(collapseBtn.dataset.autoCollapseLines) || 30;
 const autoCollapseHeight = parseInt(collapseBtn.dataset.autoCollapseHeight) || 400;
 const collapsedHeight = parseInt(collapseBtn.dataset.collapsedHeight) || 120;

 let isCollapsed = false;

 
 function initCollapse() {
 
 const shouldCollapse = isCollapsedAttr ||
 defaultState === 'collapsed' ||
 shouldAutoCollapse();

 if (shouldCollapse) {
 setCollapsed(true, false); 
 }
 }

 function shouldAutoCollapse() {
 
 if (codeElement) {
 const lines = codeElement.querySelectorAll('.line, .cl');
 const height = codeElement.offsetHeight;
 return lines.length &gt; autoCollapseLines || height &gt; autoCollapseHeight;
 }

 
 const containerHeight = codeContainer.offsetHeight;
 if (containerHeight &gt; autoCollapseHeight) {
 return true;
 }

 
 const textContent = codeContainer.textContent || codeContainer.innerText || '';
 const estimatedLines = textContent.split('\n').length;
 return estimatedLines &gt; autoCollapseLines;
 }

 function setCollapsed(collapsed, animate = true) {
 if (!collapseOverlay) return;

 isCollapsed = collapsed;

 if (collapsed) {
 
 codeContainer.style.maxHeight = collapsedHeight + 'px';
 codeContainer.style.overflow = 'hidden';
 collapseOverlay.style.opacity = '1';
 collapseOverlay.style.pointerEvents = 'auto';

 
 collapseIcon.innerHTML = `\u003csvg class=\u0022h-3 w-3\u0022 fill=\u0022none\u0022 stroke=\u0022currentColor\u0022 viewBox=\u00220 0 24 24\u0022 xmlns=\u0022http:\/\/www.w3.org\/2000\/svg\u0022\u003e\n \u003cpath stroke-linecap=\u0022round\u0022 stroke-linejoin=\u0022round\u0022 stroke-width=\u00222\u0022 d=\u0022M19 9l-7 7-7-7\u0022 \/\u003e\n\u003c\/svg\u003e`;
 if (collapseText) {
 collapseText.textContent = 'Развернуть';
 }
 collapseBtn.title = 'Развернуть';

 } else {
 
 codeContainer.style.maxHeight = '';
 codeContainer.style.overflow = '';
 collapseOverlay.style.opacity = '0';
 collapseOverlay.style.pointerEvents = 'none';

 
 collapseIcon.innerHTML = `\u003csvg class=\u0022h-3 w-3\u0022 xmlns=\u0022http:\/\/www.w3.org\/2000\/svg\u0022 viewBox=\u00220 0 24 24\u0022\u003e\u003cpath fill=\u0022currentColor\u0022 d=\u0022M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6l-6 6z\u0022\/\u003e\u003c\/svg\u003e`;
 if (collapseText) {
 collapseText.textContent = 'Свернуть';
 }
 collapseBtn.title = 'Свернуть';
 }

 
 if (animate) {
 codeContainer.style.transition = 'max-height 0.3s ease-out';
 setTimeout(() =&gt; {
 codeContainer.style.transition = '';
 }, 300);
 }
 }

 function toggleCollapse() {
 setCollapsed(!isCollapsed, true);
 }

 
 collapseBtn.addEventListener('click', toggleCollapse);

 
 if (collapseOverlay) {
 collapseOverlay.addEventListener('click', () =&gt; {
 if (isCollapsed) {
 setCollapsed(false, true);
 }
 });
 }

 
 initCollapse();
 }
})();
&lt;/script&gt;
&lt;h3 id="скачать-бинарник"&gt;Скачать бинарник&lt;/h3&gt;
&lt;div
 class="code-block-container border-border bg-card my-6 overflow-hidden rounded-xl border shadow-sm transition-all duration-200 ease-out hover:-translate-y-0.5 hover:shadow-md"&gt;
 
 &lt;div
 class="code-block-header bg-muted/30 border-border flex items-center justify-between border-b px-4 py-3"&gt;
 
 &lt;div class="flex items-center gap-2"&gt;
 &lt;div class="text-muted-foreground flex-shrink-0"&gt;
 &lt;svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"&gt;
 &lt;path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" /&gt;
&lt;/svg&gt;
 &lt;/div&gt;
 &lt;span class="text-muted-foreground text-sm font-medium"&gt;
 BASH
 &lt;/span&gt;
 &lt;/div&gt;

 
 &lt;div class="flex items-center gap-2"&gt;
 &lt;button
 class="collapse-code-btn text-muted-foreground hover:text-primary hover:bg-primary/10 focus:ring-primary/20 flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium transition-all duration-200 ease-out focus:ring-2 focus:outline-none"
 data-code-id="code-2"
 data-default-state="expanded"
 data-collapsed="false"
 data-auto-collapse-lines="30"
 data-auto-collapse-height="400"
 data-collapsed-height="120"
 title="Свернуть"
 aria-label="Свернуть"&gt;
 &lt;span class="collapse-icon"&gt;
 &lt;svg class="h-3 w-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="currentColor" d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6l-6 6z"/&gt;&lt;/svg&gt;
 &lt;/span&gt;
 &lt;span class="collapse-text hidden sm:inline"
 &gt;Свернуть&lt;/span
 &gt;
 &lt;/button&gt;
 &lt;button
 class="copy-code-btn text-muted-foreground hover:text-primary hover:bg-primary/10 focus:ring-primary/20 flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium transition-all duration-200 ease-out focus:ring-2 focus:outline-none"
 data-code-id="code-2"
 title="Копировать"
 aria-label="Копировать"&gt;
 &lt;span class="copy-icon"&gt;
 &lt;svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"&gt;
 &lt;path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" /&gt;
&lt;/svg&gt;
 &lt;/span&gt;
 &lt;span class="copy-text hidden sm:inline"
 &gt;Копировать&lt;/span
 &gt;
 &lt;/button&gt;
 &lt;/div&gt;
 &lt;/div&gt;

 
 &lt;div class="code-block-content relative" id="code-2"&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Для Linux AMD64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget https://github.com/derailed/k9s/releases/latest/download/k9s_Linux_amd64.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tar -xzf k9s_Linux_amd64.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo mv k9s /usr/local/bin/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 
 &lt;div
 class="collapse-overlay to-card/90 pointer-events-none absolute inset-0 bg-gradient-to-b from-transparent via-transparent opacity-0 transition-opacity duration-300"&gt;
 &lt;div
 class="text-muted-foreground bg-card/80 border-border/50 hover:bg-primary/10 hover:text-primary hover:border-primary/30 absolute bottom-4 left-1/2 -translate-x-1/2 cursor-pointer rounded-full border px-3 py-1.5 text-xs backdrop-blur-sm transition-all duration-200"&gt;
 Нажмите, чтобы развернуть и увидеть больше
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;/div&gt;
&lt;/div&gt;


&lt;script&gt;
(function() {
 const codeId = 'code-2';
 const copyBtn = document.querySelector('.copy-code-btn[data-code-id="' + codeId + '"]');
 const collapseBtn = document.querySelector('.collapse-code-btn[data-code-id="' + codeId + '"]');
 const codeContainer = document.getElementById(codeId);

 if (!codeContainer) return;

 
 if (copyBtn) {
 const copyIcon = copyBtn.querySelector('.copy-icon');
 const copyText = copyBtn.querySelector('.copy-text');

 copyBtn.addEventListener('click', async function() {
 try {
 
 let codeText = '';

 
 const codeTableCell = codeContainer.querySelector('.lntd:last-child code');
 if (codeTableCell) {
 codeText = codeTableCell.textContent || codeTableCell.innerText;
 } else {
 
 const codeElement = codeContainer.querySelector('code');
 if (codeElement) {
 
 const hasInlineLineNumbers = codeElement.querySelector('.ln');
 if (hasInlineLineNumbers) {
 
 const codeLines = codeElement.querySelectorAll('.cl');
 if (codeLines.length &gt; 0) {
 codeText = Array.from(codeLines)
 .map(line =&gt; {
 const text = line.textContent || line.innerText;
 
 return text.replace(/\n+$/, '');
 })
 .join('\n')
 .replace(/\n+$/, ''); 
 } else {
 
 const allText = codeElement.textContent || codeElement.innerText;
 codeText = allText.replace(/^\d+/gm, '').replace(/^\s+/gm, '');
 }
 } else {
 
 codeText = codeElement.textContent || codeElement.innerText;
 }
 } else {
 
 codeText = codeContainer.textContent || codeContainer.innerText;
 }
 }

 
 codeText = codeText.trim();

 
 await navigator.clipboard.writeText(codeText);

 
 copyIcon.innerHTML = `\u003csvg class=\u0022h-3 w-3\u0022 fill=\u0022none\u0022 stroke=\u0022currentColor\u0022 viewBox=\u00220 0 24 24\u0022 xmlns=\u0022http:\/\/www.w3.org\/2000\/svg\u0022\u003e\n \u003cpath stroke-linecap=\u0022round\u0022 stroke-linejoin=\u0022round\u0022 stroke-width=\u00222\u0022 d=\u0022M5 13l4 4L19 7\u0022 \/\u003e\n\u003c\/svg\u003e`;
 if (copyText) {
 copyText.textContent = 'Скопировано';
 }
 copyBtn.classList.add('text-green-600');

 
 setTimeout(() =&gt; {
 copyIcon.innerHTML = `\u003csvg class=\u0022h-3 w-3\u0022 fill=\u0022none\u0022 stroke=\u0022currentColor\u0022 viewBox=\u00220 0 24 24\u0022 xmlns=\u0022http:\/\/www.w3.org\/2000\/svg\u0022\u003e\n \u003cpath stroke-linecap=\u0022round\u0022 stroke-linejoin=\u0022round\u0022 stroke-width=\u00222\u0022 d=\u0022M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\u0022 \/\u003e\n\u003c\/svg\u003e`;
 if (copyText) {
 copyText.textContent = 'Копировать';
 }
 copyBtn.classList.remove('text-green-600');
 }, 2000);

 } catch (err) {
 console.error('复制失败:', err);

 
 const range = document.createRange();
 const codeElement = codeContainer.querySelector('code') || codeContainer;
 range.selectNodeContents(codeElement);
 const selection = window.getSelection();
 selection.removeAllRanges();
 selection.addRange(range);

 
 if (copyText) {
 copyText.textContent = 'Выбрано';
 }

 setTimeout(() =&gt; {
 if (copyText) {
 copyText.textContent = 'Копировать';
 }
 selection.removeAllRanges();
 }, 2000);
 }
 });
 }

 
 if (collapseBtn) {
 const collapseIcon = collapseBtn.querySelector('.collapse-icon');
 const collapseText = collapseBtn.querySelector('.collapse-text');
 const collapseOverlay = codeContainer.querySelector('.collapse-overlay');

 
 let codeElement = codeContainer.querySelector('pre.chroma');
 if (!codeElement) {
 codeElement = codeContainer.querySelector('pre');
 }

 const defaultState = collapseBtn.dataset.defaultState || 'expanded';
 const isCollapsedAttr = collapseBtn.dataset.collapsed === 'true';
 const autoCollapseLines = parseInt(collapseBtn.dataset.autoCollapseLines) || 30;
 const autoCollapseHeight = parseInt(collapseBtn.dataset.autoCollapseHeight) || 400;
 const collapsedHeight = parseInt(collapseBtn.dataset.collapsedHeight) || 120;

 let isCollapsed = false;

 
 function initCollapse() {
 
 const shouldCollapse = isCollapsedAttr ||
 defaultState === 'collapsed' ||
 shouldAutoCollapse();

 if (shouldCollapse) {
 setCollapsed(true, false); 
 }
 }

 function shouldAutoCollapse() {
 
 if (codeElement) {
 const lines = codeElement.querySelectorAll('.line, .cl');
 const height = codeElement.offsetHeight;
 return lines.length &gt; autoCollapseLines || height &gt; autoCollapseHeight;
 }

 
 const containerHeight = codeContainer.offsetHeight;
 if (containerHeight &gt; autoCollapseHeight) {
 return true;
 }

 
 const textContent = codeContainer.textContent || codeContainer.innerText || '';
 const estimatedLines = textContent.split('\n').length;
 return estimatedLines &gt; autoCollapseLines;
 }

 function setCollapsed(collapsed, animate = true) {
 if (!collapseOverlay) return;

 isCollapsed = collapsed;

 if (collapsed) {
 
 codeContainer.style.maxHeight = collapsedHeight + 'px';
 codeContainer.style.overflow = 'hidden';
 collapseOverlay.style.opacity = '1';
 collapseOverlay.style.pointerEvents = 'auto';

 
 collapseIcon.innerHTML = `\u003csvg class=\u0022h-3 w-3\u0022 fill=\u0022none\u0022 stroke=\u0022currentColor\u0022 viewBox=\u00220 0 24 24\u0022 xmlns=\u0022http:\/\/www.w3.org\/2000\/svg\u0022\u003e\n \u003cpath stroke-linecap=\u0022round\u0022 stroke-linejoin=\u0022round\u0022 stroke-width=\u00222\u0022 d=\u0022M19 9l-7 7-7-7\u0022 \/\u003e\n\u003c\/svg\u003e`;
 if (collapseText) {
 collapseText.textContent = 'Развернуть';
 }
 collapseBtn.title = 'Развернуть';

 } else {
 
 codeContainer.style.maxHeight = '';
 codeContainer.style.overflow = '';
 collapseOverlay.style.opacity = '0';
 collapseOverlay.style.pointerEvents = 'none';

 
 collapseIcon.innerHTML = `\u003csvg class=\u0022h-3 w-3\u0022 xmlns=\u0022http:\/\/www.w3.org\/2000\/svg\u0022 viewBox=\u00220 0 24 24\u0022\u003e\u003cpath fill=\u0022currentColor\u0022 d=\u0022M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6l-6 6z\u0022\/\u003e\u003c\/svg\u003e`;
 if (collapseText) {
 collapseText.textContent = 'Свернуть';
 }
 collapseBtn.title = 'Свернуть';
 }

 
 if (animate) {
 codeContainer.style.transition = 'max-height 0.3s ease-out';
 setTimeout(() =&gt; {
 codeContainer.style.transition = '';
 }, 300);
 }
 }

 function toggleCollapse() {
 setCollapsed(!isCollapsed, true);
 }

 
 collapseBtn.addEventListener('click', toggleCollapse);

 
 if (collapseOverlay) {
 collapseOverlay.addEventListener('click', () =&gt; {
 if (isCollapsed) {
 setCollapsed(false, true);
 }
 });
 }

 
 initCollapse();
 }
})();
&lt;/script&gt;
&lt;h3 id="проверка"&gt;Проверка&lt;/h3&gt;
&lt;div
 class="code-block-container border-border bg-card my-6 overflow-hidden rounded-xl border shadow-sm transition-all duration-200 ease-out hover:-translate-y-0.5 hover:shadow-md"&gt;
 
 &lt;div
 class="code-block-header bg-muted/30 border-border flex items-center justify-between border-b px-4 py-3"&gt;
 
 &lt;div class="flex items-center gap-2"&gt;
 &lt;div class="text-muted-foreground flex-shrink-0"&gt;
 &lt;svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"&gt;
 &lt;path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" /&gt;
&lt;/svg&gt;
 &lt;/div&gt;
 &lt;span class="text-muted-foreground text-sm font-medium"&gt;
 BASH
 &lt;/span&gt;
 &lt;/div&gt;

 
 &lt;div class="flex items-center gap-2"&gt;
 &lt;button
 class="collapse-code-btn text-muted-foreground hover:text-primary hover:bg-primary/10 focus:ring-primary/20 flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium transition-all duration-200 ease-out focus:ring-2 focus:outline-none"
 data-code-id="code-3"
 data-default-state="expanded"
 data-collapsed="false"
 data-auto-collapse-lines="30"
 data-auto-collapse-height="400"
 data-collapsed-height="120"
 title="Свернуть"
 aria-label="Свернуть"&gt;
 &lt;span class="collapse-icon"&gt;
 &lt;svg class="h-3 w-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="currentColor" d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6l-6 6z"/&gt;&lt;/svg&gt;
 &lt;/span&gt;
 &lt;span class="collapse-text hidden sm:inline"
 &gt;Свернуть&lt;/span
 &gt;
 &lt;/button&gt;
 &lt;button
 class="copy-code-btn text-muted-foreground hover:text-primary hover:bg-primary/10 focus:ring-primary/20 flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium transition-all duration-200 ease-out focus:ring-2 focus:outline-none"
 data-code-id="code-3"
 title="Копировать"
 aria-label="Копировать"&gt;
 &lt;span class="copy-icon"&gt;
 &lt;svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"&gt;
 &lt;path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" /&gt;
&lt;/svg&gt;
 &lt;/span&gt;
 &lt;span class="copy-text hidden sm:inline"
 &gt;Копировать&lt;/span
 &gt;
 &lt;/button&gt;
 &lt;/div&gt;
 &lt;/div&gt;

 
 &lt;div class="code-block-content relative" id="code-3"&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;k9s version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 
 &lt;div
 class="collapse-overlay to-card/90 pointer-events-none absolute inset-0 bg-gradient-to-b from-transparent via-transparent opacity-0 transition-opacity duration-300"&gt;
 &lt;div
 class="text-muted-foreground bg-card/80 border-border/50 hover:bg-primary/10 hover:text-primary hover:border-primary/30 absolute bottom-4 left-1/2 -translate-x-1/2 cursor-pointer rounded-full border px-3 py-1.5 text-xs backdrop-blur-sm transition-all duration-200"&gt;
 Нажмите, чтобы развернуть и увидеть больше
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;/div&gt;
&lt;/div&gt;


&lt;script&gt;
(function() {
 const codeId = 'code-3';
 const copyBtn = document.querySelector('.copy-code-btn[data-code-id="' + codeId + '"]');
 const collapseBtn = document.querySelector('.collapse-code-btn[data-code-id="' + codeId + '"]');
 const codeContainer = document.getElementById(codeId);

 if (!codeContainer) return;

 
 if (copyBtn) {
 const copyIcon = copyBtn.querySelector('.copy-icon');
 const copyText = copyBtn.querySelector('.copy-text');

 copyBtn.addEventListener('click', async function() {
 try {
 
 let codeText = '';

 
 const codeTableCell = codeContainer.querySelector('.lntd:last-child code');
 if (codeTableCell) {
 codeText = codeTableCell.textContent || codeTableCell.innerText;
 } else {
 
 const codeElement = codeContainer.querySelector('code');
 if (codeElement) {
 
 const hasInlineLineNumbers = codeElement.querySelector('.ln');
 if (hasInlineLineNumbers) {
 
 const codeLines = codeElement.querySelectorAll('.cl');
 if (codeLines.length &gt; 0) {
 codeText = Array.from(codeLines)
 .map(line =&gt; {
 const text = line.textContent || line.innerText;
 
 return text.replace(/\n+$/, '');
 })
 .join('\n')
 .replace(/\n+$/, ''); 
 } else {
 
 const allText = codeElement.textContent || codeElement.innerText;
 codeText = allText.replace(/^\d+/gm, '').replace(/^\s+/gm, '');
 }
 } else {
 
 codeText = codeElement.textContent || codeElement.innerText;
 }
 } else {
 
 codeText = codeContainer.textContent || codeContainer.innerText;
 }
 }

 
 codeText = codeText.trim();

 
 await navigator.clipboard.writeText(codeText);

 
 copyIcon.innerHTML = `\u003csvg class=\u0022h-3 w-3\u0022 fill=\u0022none\u0022 stroke=\u0022currentColor\u0022 viewBox=\u00220 0 24 24\u0022 xmlns=\u0022http:\/\/www.w3.org\/2000\/svg\u0022\u003e\n \u003cpath stroke-linecap=\u0022round\u0022 stroke-linejoin=\u0022round\u0022 stroke-width=\u00222\u0022 d=\u0022M5 13l4 4L19 7\u0022 \/\u003e\n\u003c\/svg\u003e`;
 if (copyText) {
 copyText.textContent = 'Скопировано';
 }
 copyBtn.classList.add('text-green-600');

 
 setTimeout(() =&gt; {
 copyIcon.innerHTML = `\u003csvg class=\u0022h-3 w-3\u0022 fill=\u0022none\u0022 stroke=\u0022currentColor\u0022 viewBox=\u00220 0 24 24\u0022 xmlns=\u0022http:\/\/www.w3.org\/2000\/svg\u0022\u003e\n \u003cpath stroke-linecap=\u0022round\u0022 stroke-linejoin=\u0022round\u0022 stroke-width=\u00222\u0022 d=\u0022M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\u0022 \/\u003e\n\u003c\/svg\u003e`;
 if (copyText) {
 copyText.textContent = 'Копировать';
 }
 copyBtn.classList.remove('text-green-600');
 }, 2000);

 } catch (err) {
 console.error('复制失败:', err);

 
 const range = document.createRange();
 const codeElement = codeContainer.querySelector('code') || codeContainer;
 range.selectNodeContents(codeElement);
 const selection = window.getSelection();
 selection.removeAllRanges();
 selection.addRange(range);

 
 if (copyText) {
 copyText.textContent = 'Выбрано';
 }

 setTimeout(() =&gt; {
 if (copyText) {
 copyText.textContent = 'Копировать';
 }
 selection.removeAllRanges();
 }, 2000);
 }
 });
 }

 
 if (collapseBtn) {
 const collapseIcon = collapseBtn.querySelector('.collapse-icon');
 const collapseText = collapseBtn.querySelector('.collapse-text');
 const collapseOverlay = codeContainer.querySelector('.collapse-overlay');

 
 let codeElement = codeContainer.querySelector('pre.chroma');
 if (!codeElement) {
 codeElement = codeContainer.querySelector('pre');
 }

 const defaultState = collapseBtn.dataset.defaultState || 'expanded';
 const isCollapsedAttr = collapseBtn.dataset.collapsed === 'true';
 const autoCollapseLines = parseInt(collapseBtn.dataset.autoCollapseLines) || 30;
 const autoCollapseHeight = parseInt(collapseBtn.dataset.autoCollapseHeight) || 400;
 const collapsedHeight = parseInt(collapseBtn.dataset.collapsedHeight) || 120;

 let isCollapsed = false;

 
 function initCollapse() {
 
 const shouldCollapse = isCollapsedAttr ||
 defaultState === 'collapsed' ||
 shouldAutoCollapse();

 if (shouldCollapse) {
 setCollapsed(true, false); 
 }
 }

 function shouldAutoCollapse() {
 
 if (codeElement) {
 const lines = codeElement.querySelectorAll('.line, .cl');
 const height = codeElement.offsetHeight;
 return lines.length &gt; autoCollapseLines || height &gt; autoCollapseHeight;
 }

 
 const containerHeight = codeContainer.offsetHeight;
 if (containerHeight &gt; autoCollapseHeight) {
 return true;
 }

 
 const textContent = codeContainer.textContent || codeContainer.innerText || '';
 const estimatedLines = textContent.split('\n').length;
 return estimatedLines &gt; autoCollapseLines;
 }

 function setCollapsed(collapsed, animate = true) {
 if (!collapseOverlay) return;

 isCollapsed = collapsed;

 if (collapsed) {
 
 codeContainer.style.maxHeight = collapsedHeight + 'px';
 codeContainer.style.overflow = 'hidden';
 collapseOverlay.style.opacity = '1';
 collapseOverlay.style.pointerEvents = 'auto';

 
 collapseIcon.innerHTML = `\u003csvg class=\u0022h-3 w-3\u0022 fill=\u0022none\u0022 stroke=\u0022currentColor\u0022 viewBox=\u00220 0 24 24\u0022 xmlns=\u0022http:\/\/www.w3.org\/2000\/svg\u0022\u003e\n \u003cpath stroke-linecap=\u0022round\u0022 stroke-linejoin=\u0022round\u0022 stroke-width=\u00222\u0022 d=\u0022M19 9l-7 7-7-7\u0022 \/\u003e\n\u003c\/svg\u003e`;
 if (collapseText) {
 collapseText.textContent = 'Развернуть';
 }
 collapseBtn.title = 'Развернуть';

 } else {
 
 codeContainer.style.maxHeight = '';
 codeContainer.style.overflow = '';
 collapseOverlay.style.opacity = '0';
 collapseOverlay.style.pointerEvents = 'none';

 
 collapseIcon.innerHTML = `\u003csvg class=\u0022h-3 w-3\u0022 xmlns=\u0022http:\/\/www.w3.org\/2000\/svg\u0022 viewBox=\u00220 0 24 24\u0022\u003e\u003cpath fill=\u0022currentColor\u0022 d=\u0022M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6l-6 6z\u0022\/\u003e\u003c\/svg\u003e`;
 if (collapseText) {
 collapseText.textContent = 'Свернуть';
 }
 collapseBtn.title = 'Свернуть';
 }

 
 if (animate) {
 codeContainer.style.transition = 'max-height 0.3s ease-out';
 setTimeout(() =&gt; {
 codeContainer.style.transition = '';
 }, 300);
 }
 }

 function toggleCollapse() {
 setCollapsed(!isCollapsed, true);
 }

 
 collapseBtn.addEventListener('click', toggleCollapse);

 
 if (collapseOverlay) {
 collapseOverlay.addEventListener('click', () =&gt; {
 if (isCollapsed) {
 setCollapsed(false, true);
 }
 });
 }

 
 initCollapse();
 }
})();
&lt;/script&gt;
&lt;h2 id="первый-запуск"&gt;Первый запуск&lt;/h2&gt;
&lt;p&gt;Просто наберите &lt;code&gt;k9s&lt;/code&gt; в терминале:&lt;/p&gt;</description></item><item><title>Kubernetes best practices, которые я хотел бы знать заранее</title><link>https://notes.melancholic.tech/posts/kubernetes-best-practices-%D0%BA%D0%BE%D1%82%D0%BE%D1%80%D1%8B%D0%B5-%D1%8F-%D1%85%D0%BE%D1%82%D0%B5%D0%BB-%D0%B1%D1%8B-%D0%B7%D0%BD%D0%B0%D1%82%D1%8C-%D0%B7%D0%B0%D1%80%D0%B0%D0%BD%D0%B5%D0%B5/</link><pubDate>Mon, 20 Jan 2025 00:00:00 +0000</pubDate><guid>https://notes.melancholic.tech/posts/kubernetes-best-practices-%D0%BA%D0%BE%D1%82%D0%BE%D1%80%D1%8B%D0%B5-%D1%8F-%D1%85%D0%BE%D1%82%D0%B5%D0%BB-%D0%B1%D1%8B-%D0%B7%D0%BD%D0%B0%D1%82%D1%8C-%D0%B7%D0%B0%D1%80%D0%B0%D0%BD%D0%B5%D0%B5/</guid><description>&lt;blockquote
 class="border-primary bg-muted/30 hover:bg-muted/40 my-6 rounded-r-lg border-l-4 py-4 pr-4 pl-6 italic transition-all duration-200 ease-out hover:translate-x-1"&gt;
 &lt;div class="prose prose-sm max-w-none"&gt;
 &lt;p&gt;Перевод и адаптация статьи &lt;a href="https://www.pulumi.com/blog/kubernetes-best-practices-i-wish-i-had-known-before/"target="_blank"
 class="inline-flex items-center gap-1"
&gt;«Kubernetes Best Practices I Wish I Had Known Before»&lt;svg class="h-3 w-3 flex-shrink-0" id="external-link" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 3h6v6m-11 5L21 3m-3 10v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/&gt;&lt;/svg&gt;
&lt;/a&gt; Engin Diri (Pulumi). Авторские права на оригинал принадлежат автору; здесь — пересказ для русскоязычных читателей.&lt;/p&gt;</description></item></channel></rss>