Метки, селекторы и организация ресурсов

Рейтинг: 61% · 6 голосов
Kubernetes для разработчиков: поды, деплойменты, сервисы, ingress, конфиги и отладка. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
anton_k8s
Сообщения: 55
Зарегистрирован: 12 май 2026, 03:23

Метки, селекторы и организация ресурсов

Сообщение anton_k8s »

Оглавление курса (48)
  1. Зачем нужен Kubernetes и из чего состоит кластер
  2. Поднимаем локальный кластер: minikube и kind
  3. Поды: базовая единица запуска
  4. Deployment и ReplicaSet: управляем репликами
  5. Service: сетевой доступ к подам
  6. ConfigMap и Secret: выносим конфигурацию
  7. Ingress: пускаем трафик снаружи
  8. Хранилище: Volumes и PersistentVolumeClaim
  9. Namespaces, requests и limits
  10. Health checks: liveness и readiness пробы
  11. Отладка: почему под не стартует
  12. Helm: пакетный менеджер для Kubernetes
  13. Базовая безопасность: RBAC и доступы
  14. Job и CronJob: разовые и периодические задачи
  15. StatefulSet и DaemonSet: stateful-нагрузки и системные агенты
  16. Стратегии обновления и планирование: rollout и rollback, graceful shutdown, nodeSelector, affinity, taints
  17. Автомасштабирование: HPA по метрикам, обзор VPA и Cluster Autoscaler
  18. Наблюдаемость: логи, метрики, events, обзор Prometheus и Grafana
  19. Безопасность глубже: securityContext, Pod Security Standards, NetworkPolicy, шифрование секретов
  20. Архитектура control plane: api-server, etcd, scheduler
  21. Узел кластера: kubelet, kube-proxy и container runtime
  22. Декларативная модель: reconciliation, контроллеры, CRD
  23. kubectl профессионально: get, describe, explain, jsonpath
  24. Под глубже: init, sidecar, lifecycle hooks и QoS
  25. Метки, селекторы и организация ресурсов (вы здесь)
  26. Сервисы и kube-proxy глубже: типы, IPVS, EndpointSlices
  27. Ingress, ingress-контроллеры и Gateway API
  28. Секреты в кластере: шифрование, External Secrets, Vault
  29. Хранилище глубже: PV, StorageClass, CSI и StatefulSet
  30. Сеть кластера: CNI, NetworkPolicy и CoreDNS
  31. Планировщик: affinity, taints, topology spread
  32. Ресурсы, QoS и вытеснение: requests, limits, eviction
  33. Helm глубже: шаблоны, хуки, зависимости, OCI
  34. Kustomize и управление конфигурацией без шаблонов
  35. RBAC и аутентификация глубже
  36. Admission и Pod Security: контроль на входе
  37. Policy as code: Kyverno и OPA Gatekeeper
  38. GitOps: Argo CD и Flux
  39. Операторы и CRD: расширяем Kubernetes
  40. Сервис-меш: Istio, Linkerd и когда он нужен
  41. Эксплуатация кластера: апгрейд, узлы, бэкап etcd
  42. Где запускать кластер: managed, self-hosted, k3s, Deckhouse
  43. Траблшутинг кластера: Pending, CrashLoop, узлы NotReady
  44. Стоимость и эффективность кластера: FinOps
  45. CI/CD в Kubernetes: сборка, деплой, прогрессивные релизы
  46. Лучшие практики и антипаттерны Kubernetes
  47. Сквозной проект и путь дальше: от манифеста до прода
  48. Деплой приложений через Argo CD: Application, sync и App-of-Apps
Невидимый клей кластера: зачем вообще метки

Представь, что у тебя в кластере сотня подов. Они приходят и уходят: упал узел - под пересоздался с новым именем и новым IP, отскейлился Deployment - появилось ещё пять штук. Никто не знает имён заранее, имена эфемерны. И тут возникает вопрос: как Service понимает, на какие именно поды слать трафик? Как Deployment отличает "свои" поды от чужих? Как Prometheus собирает метрики только с нужных эндпоинтов? Если бы k8s связывал объекты по именам или IP, всё развалилось бы при первой же пересборке пода.

Ответ - kubernetes labels. Метки это произвольные пары ключ-значение, которые ты вешаешь на объекты. А kubernetes selector это запрос "дай мне всё, у чего есть такие метки". Связь Service -> Pod, Deployment -> Pod, NetworkPolicy -> Pod, PodMonitor -> Pod - всё это держится не на именах, а на совпадении меток. Это не украшение и не теги для красоты. Это несущая конструкция. Понимаешь метки и селекторы - понимаешь, как k8s вообще что-либо связывает.

В этом уроке разберём механику: чем метки отличаются от annotations kubernetes, как работают два вида селекторов, что такое field selectors, зачем нужен kubernetes namespace как граница, и какие грабли тут ждут на проде - от классического рассинхрона селектора с подом до меток для учёта стоимости.

Изображение

Метки и селекторы: equality против set-based

Метка живёт в metadata.labels. Ключ может иметь префикс (домен через слеш) и имя, значение - строка до 63 символов. Вот под с метками:

Код: Выделить всё

apiVersion: v1
kind: Pod
metadata:
  name: web-7f9c
  labels:
    app: shop
    tier: frontend
    env: prod
    track: stable
spec:
  containers:
    - name: web
      image: registry.example.ru/shop-web:1.4.2
Теперь метки kubernetes ничего не делают сами по себе. Магия начинается, когда кто-то по ним отбирает. Селекторы бывают двух видов.

Equality-based (по равенству). Простейший вид: ключ равен или не равен значению. Именно его понимает Service в поле spec.selector - там можно задать только набор "ключ: значение", соединённых логическим И. Из командной строки это выглядит так:

Код: Выделить всё

kubectl get pods -l 'app=shop,env=prod'
Запятая это И. Команда вернёт поды, у которых одновременно app=shop И env=prod. Есть и отрицание: app!=shop.

Set-based (по множеству). Гибче: операторы In, NotIn, Exists, DoesNotExist. Это то, что современные контроллеры (Deployment, ReplicaSet, DaemonSet, Job) используют в selector.matchExpressions. Пример из Deployment:

Код: Выделить всё

apiVersion: apps/v1
kind: Deployment
metadata:
  name: shop-web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: shop
      tier: frontend
    matchExpressions:
      - key: env
        operator: In
        values: ["prod", "staging"]
      - key: track
        operator: Exists
  template:
    metadata:
      labels:
        app: shop
        tier: frontend
        env: prod
        track: stable
    spec:
      containers:
        - name: web
          image: registry.example.ru/shop-web:1.4.2
Разберём ключевое. matchLabels и matchExpressions объединяются логическим И - под должен удовлетворять всем условиям сразу. matchLabels это сокращение для простых равенств. matchExpressions это набор выражений, каждое со своим оператором. Здесь контроллер заберёт под, если app=shop И tier=frontend И env in (prod, staging) И существует метка track с любым значением.

То же из CLI:

Код: Выделить всё

kubectl get pods -l 'env in (prod,staging),track'
Голое имя track без значения это проверка Exists. А env notin (dev) отберёт всё, кроме dev.

Важнейший нюанс про template.metadata.labels внутри Deployment. Это метки, которые контроллер навешивает на создаваемые поды. Они обязаны включать в себя то, что написано в selector, иначе контроллер создаст под, который сам же не сможет "узнать". Об этом - в граблях ниже, потому что на этом спотыкаются практически все.

Как Service находит поды на самом деле

Service со spec.selector работает не "магически". Под капотом есть отдельный контроллер эндпоинтов. Когда ты создаёшь Service с селектором, контроллер постоянно следит за подами, чьи метки подходят под селектор и которые при этом готовы (passed readiness probe), и складывает их IP в объекты EndpointSlice. В современных версиях это именно EndpointSlice (масштабируемая замена старому единому Endpoints), куда попадают адреса, порты и зоны.

Посмотрим живьём. Создаём Service:

Код: Выделить всё

apiVersion: v1
kind: Service
metadata:
  name: shop-web
spec:
  selector:
    app: shop
    tier: frontend
  ports:
    - port: 80
      targetPort: 8080
Проверяем, кого он реально подцепил:

Код: Выделить всё

kubectl get endpointslices -l kubernetes.io/service-name=shop-web -o wide
Примерный вывод:

Код: Выделить всё

NAME             ADDRESSTYPE   PORTS   ENDPOINTS                          AGE
shop-web-x8k2p   IPv4          8080    10.244.1.7,10.244.2.4,10.244.3.9   12m
Три адреса - три готовых пода под селектором. Если тут пусто, а поды есть - значит метки пода и селектор Service не совпадают, либо поды не прошли readiness. Это первый диагностический шаг при "сервис не отвечает": не лезь в DNS и не перезапускай ничего, сначала глянь endpointslices. Пустой список адресов почти всегда означает рассинхрон меток или непройденную проверку готовности.

Annotations: метаданные для инструментов, а не для отбора

Рядом с metadata.labels живёт metadata.annotations. Снаружи похоже - тоже пары ключ-значение. Но смысл противоположный. Annotations kubernetes это произвольные данные для людей и инструментов, по которым НЕЛЬЗЯ делать выборку. Селектор по аннотации невозможен в принципе - механизма нет.

Когда что использовать. Метка это то, по чему ты или контроллер будете отбирать и группировать. Аннотация это то, что нужно положить рядом с объектом, но никогда не искать селектором: длинные значения, JSON, git-коммит сборки, контактный email команды, флаги для контроллеров. Ограничение на длину метки (63 символа на значение) тоже намекает: всё крупное и структурное - в аннотации, там лимит на весь блок около 256 КБ.

Код: Выделить всё

metadata:
  labels:
    app: shop                 # по этому отбираем
    env: prod
  annotations:
    kubernetes.io/change-cause: "rollout 1.4.2 hotfix CVE-2026-1337"
    prometheus.io/scrape: "true"
    contact: "platform-team@example.ru"
Многие инструменты читают именно аннотации как конфигурацию: контроллер Ingress, cert-manager, внешний DNS - все берут параметры из annotations целевого объекта. Метка для них была бы неудобна и небезопасна (короткая, попадает в селекторы). И ещё важное правило безопасности: метки это не средство защиты. Любой, у кого есть доступ к объекту, может их переписать. Разграничение доступа делается через RBAC, а не через метки и не через namespace сам по себе.

Рекомендуемые метки app.kubernetes.io и единый стандарт

Чтобы зоопарк меток в разных командах не превратился в хаос (где-то app, где-то application, где-то service), сообщество закрепило стандартный набор с префиксом app.kubernetes.io. Их понимают многие дашборды и инструменты из коробки:
  • app.kubernetes.io/name - имя приложения (shop-web)
  • app.kubernetes.io/instance - конкретный экземпляр (shop-web-prod)
  • app.kubernetes.io/version - версия (1.4.2)
  • app.kubernetes.io/component - роль в архитектуре (frontend, database, cache)
  • app.kubernetes.io/part-of - часть большей системы (online-shop)
  • app.kubernetes.io/managed-by - чем управляется (Helm, Argo CD, kustomize)
Различие name и instance тонкое, но важное. name это "что это за софт" (например redis), instance это "какая конкретно установка" (redis-cart-prod). Если в одном namespace стоят два редиса под разные сервисы, у них одинаковый name=redis, но разный instance - и ты их различишь селектором.

На практике не обязательно тащить все шесть на каждый объект. Минимально полезный набор - name, instance, component, managed-by. Helm и Argo CD проставляют managed-by автоматически. Главное - договориться в команде и держать единообразие, иначе селекторы и дашборды начнут "терять" объекты.

Namespace как граница и организация манифестов

Метки группируют объекты внутри пространства, а kubernetes namespace делит сам кластер на логические зоны. Это граница имён (внутри namespace имена уникальны, а между namespace могут повторяться - может быть три Service с именем api в трёх namespace), граница для квот ресурсов (ResourceQuota), для сетевых политик и для RBAC. По namespace тоже удобно резать вывод:

Код: Выделить всё

kubectl get pods -n shop-prod
kubectl get pods -A -l app=shop          # по всем namespace сразу
Типовые стратегии разрезания: по команде/продукту (team-payments, team-search), по окружению (shop-dev, shop-staging, shop-prod) либо комбинация. Разделять prod и dev по namespace в одном кластере можно, но помни: namespace это не стена безопасности и не изоляция по сети по умолчанию. Поды из разных namespace по дефолту прекрасно ходят друг к другу, пока ты не поставишь NetworkPolicy. Для жёсткой изоляции прод-контуров часто берут отдельный кластер.

Про организацию манифестов на диске. Базовый принцип - структура по приложению, затем по окружению. С Kustomize это выглядит как база плюс оверлеи:

Код: Выделить всё

shop-web/
  base/
    deployment.yaml
    service.yaml
    kustomization.yaml
  overlays/
    prod/
      kustomization.yaml      # patch: replicas, image tag, env=prod
    staging/
      kustomization.yaml
Kustomize умеет добавлять общие метки во все ресурсы оверлея разом через commonLabels (а строго - через labels с includeSelectors), так что env=prod проставится консистентно везде. В связке с GitOps (Argo CD, Flux) каждый оверлей становится источником истины для своего окружения: что в git, то и в кластере.

Field selectors: отбор не по меткам, а по полям

Метки ты придумываешь сам. Но иногда надо отобрать по встроенному полю объекта - статусу, имени, узлу. Для этого есть field selectors. Это отдельный механизм, и набор поддерживаемых полей ограничен (не любое поле, а только те, что индексируются - зависит от типа ресурса).

Код: Выделить всё

kubectl get pods --field-selector status.phase=Running
kubectl get pods --field-selector spec.nodeName=cl-node-3
kubectl get events --field-selector type=Warning,involvedObject.kind=Pod
Классика - выгрести все события-предупреждения или найти все поды на конкретном узле перед его выводом на обслуживание. Можно комбинировать с label-селектором: --field-selector status.phase=Running -l app=shop. Запомни границу: -l это про твои метки, --field-selector это про системные поля ресурса.

Грабли и антипаттерны

1. Рассинхрон селектора и меток пода - грабля номер один. Ты правишь Deployment и меняешь template.metadata.labels, но забываешь, что selector в Deployment иммутабелен после создания. Поменять selector у существующего Deployment нельзя - apiserver откажет. А если метки в template разъехались с тем, что отбирает Service, трафик просто перестанет доходить, хотя поды живы и зелёные. Симптом: pod Running, а endpointslices пустые. Всегда держи метки selector подмножеством меток template и не плодь "лишние умные" метки в селекторе.

2. Перетекание чужих подов. Слишком общий селектор (например только app=shop без tier) может случайно подцепить поды другого Deployment с такой же меткой. Два контроллера начнут драться за одни и те же поды. Делай селекторы достаточно специфичными - связку из 2-3 стабильных меток.

3. Версия или хеш в селекторе. Никогда не клади в selector меняющиеся значения - version, git-sha, дату сборки. Селектор должен быть стабильным на весь срок жизни объекта. Версию - в метку template и в аннотацию, но не в отбор.

4. Метки как средство безопасности. Повторюсь, потому что это частая иллюзия: метку env=prod может переписать кто угодно с доступом на edit. Не вешай на метки контроль доступа - для этого RBAC, а для допуска подов Pod Security Admission по меткам namespace (это исключение, где метка namespace реально управляет политикой, но ставит её админ).

5. Хаос в именовании. app vs application vs service в разных командах ломает общие дашборды и алерты. Лечится стандартом app.kubernetes.io и линтером в CI.

Метки для денег: owner и cost-аллокация

Отдельная практическая тема - метки для учёта стоимости. В кластере крутятся ресурсы десятка команд, и в конце месяца кто-то спросит "сколько стоит наш сервис". Без меток ответа нет. Поэтому на проде заводят обязательные метки вроде team, owner, cost-center, product. Инструменты cost-аллокации (OpenCost, Kubecost, биллинг в Yandex Managed Service for Kubernetes) группируют потребление CPU/RAM именно по таким меткам.

Код: Выделить всё

metadata:
  labels:
    app.kubernetes.io/name: shop-web
    team: payments
    owner: ivanov
    cost-center: cc-3120
    env: prod
Чтобы эти метки реально стояли везде, а не "когда вспомнили", их делают обязательными через политику - Kyverno или ValidatingAdmissionPolicy (на CEL): манифест без метки team или cost-center просто не пройдёт admission и не создастся. Это превращает учёт стоимости из ручного аудита в гарантию на входе.

Мини-лаба: связать всё руками
  • Создай namespace: kubectl create namespace lab-labels
  • Запусти деплой с метками: kubectl create deployment web --image=nginx --replicas=3 -n lab-labels, затем кому-то из подов добавь метку kubectl label pod ИМЯ track=canary -n lab-labels
  • Посмотри метки: kubectl get pods -n lab-labels --show-labels
  • Сделай Service селектором по app=web (kubectl expose deployment web --port=80 -n lab-labels) и проверь kubectl get endpointslices -n lab-labels -o wide - убедись, что подцепились все три
  • Отбери set-based: kubectl get pods -n lab-labels -l 'app=web,track in (canary)' - должен вернуться один под
  • Сломай и почини: убери метку app у одного пода (kubectl label pod ИМЯ app- -n lab-labels) и посмотри, как он выпал из endpointslices. Верни метку - он вернётся
  • Field selector: kubectl get pods -n lab-labels --field-selector status.phase=Running
  • Прибери: kubectl delete namespace lab-labels
Контрольные вопросы
  • Почему Service связывается с подами через метки, а не по их именам или IP?
  • В чём разница между equality-based и set-based селектором и какие операторы есть у второго?
  • Чем annotations отличаются от labels и почему по аннотации нельзя сделать выборку?
  • Что произойдёт, если метки в template.metadata.labels Deployment разъедутся с его selector и с селектором Service?
Итог

Метки это то, на чём держится весь кластер: контроллеры и сервисы связывают эфемерные поды не по именам, а по совпадению меток через селекторы (equality и set-based). Annotations несут метаданные для инструментов и не участвуют в отборе. Стандарт app.kubernetes.io даёт единый язык, namespace задаёт границу имён и квот (но не безопасности - это RBAC), field selectors фильтруют по системным полям, а обязательные метки owner/cost-center дают учёт стоимости. Держи селектор стабильным и подмножеством меток template - и невидимый клей кластера будет работать как часы.
👍4 ❤️3 🔥1 😄 🤔3
Аватара пользователя
Stevecox
Сообщения: 1
Зарегистрирован: 13 май 2026, 11:02

Re: Метки, селекторы и организация ресурсов

Сообщение Stevecox »

Долго не мог понять почему сервис зелёный а 503 - оказалось ровно как тут, endpointslices пустые из-за лишней метки в селекторе. Спасибо, теперь первым делом смотрю их.
👍1 ❤️ 🔥1 😄 🤔1
Аватара пользователя
rust2010
Сообщения: 1
Зарегистрирован: 15 май 2026, 00:44

Re: Метки, селекторы и организация ресурсов

Сообщение rust2010 »

Вопрос: а если я в Helm-чарте поменял selector у Deployment, почему apply падает с ошибкой про immutable? Теперь понятно что селектор нельзя править после создания, надо пересоздавать.
👍 ❤️1 🔥 😄 🤔
Ответить
← Предыдущая глава
Под глубже: init, sidecar, lifecycle hooks и QoS
Следующая глава →
Сервисы и kube-proxy глубже: типы, IPVS, EndpointSlices

Все главы курса «Kubernetes: оркестрация контейнеров от основ до продакшена»

Поделиться темой: ✈ Telegram VK

Вернуться в «Kubernetes на практике»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость