Представь, что у тебя в кластере сотня подов. Они приходят и уходят: упал узел - под пересоздался с новым именем и новым 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.2Equality-based (по равенству). Простейший вид: ключ равен или не равен значению. Именно его понимает Service в поле spec.selector - там можно задать только набор "ключ: значение", соединённых логическим И. Из командной строки это выглядит так:
Код: Выделить всё
kubectl get pods -l 'app=shop,env=prod'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То же из CLI:
Код: Выделить всё
kubectl get pods -l 'env in (prod,staging),track'Важнейший нюанс про 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 12mAnnotations: метаданные для инструментов, а не для отбора
Рядом с 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"Рекомендуемые метки 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, 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 сразуПро организацию манифестов на диске. Базовый принцип - структура по приложению, затем по окружению. С Kustomize это выглядит как база плюс оверлеи:
Код: Выделить всё
shop-web/
base/
deployment.yaml
service.yaml
kustomization.yaml
overlays/
prod/
kustomization.yaml # patch: replicas, image tag, env=prod
staging/
kustomization.yamlField 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Грабли и антипаттерны
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Мини-лаба: связать всё руками
- Создай 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 - и невидимый клей кластера будет работать как часы.