Модуль 3 / 19
Namespaces, labels, annotations, selectors и ownership
Содержание
1. Цели и результат модуля
После модуля вы сможете проектировать понятную metadata-схему, выбирать ресурсы без разбора имён, различать labels и annotations, находить ошибки selectors и доказывать ownership по uid.
- Namespace задаёт scope имени, но не является границей безопасности сам по себе
- Labels предназначены для идентификации и выборки
- Annotations хранят неидентифицирующие данные
- OwnerReferences формируют lifecycle graph для garbage collector
- Finalizers и ownership решают разные задачи
2. Теория: namespaces, scope и имена
Identity namespaced object образуют API resource, namespace и name. Cluster-scoped объекты, например Node, Namespace и StorageClass, namespace не имеют. Неверный namespace обычно даёт NotFound, даже если одноимённый объект существует в другом scope.
Namespace организует команды, окружения и policy scope, но изоляция появляется только вместе с RBAC, NetworkPolicy, quotas и admission. Namespace default — обычный namespace, а не место для всех приложений.
- metadata.name после создания не меняется
- generateName позволяет API Server добавить уникальный суффикс
- имена многих ресурсов подчиняются DNS-ограничениям
- удаление Namespace каскадно удаляет namespaced content
3. Теория: labels и таксономия
Label — индексируемая пара key/value для стабильных признаков, по которым controllers, Services, policies и операторы выбирают объекты. Изменение label может немедленно изменить поведение системы, поэтому это часть API-контракта.
Используйте app.kubernetes.io/name, instance, version, component, part-of и managed-by, а организационные ключи помещайте под собственным DNS prefix. Secrets, длинные тексты и значения высокой cardinality для labels не подходят.
4. Теория: annotations
Annotations подходят для runbook URL, владельца, change ticket, checksum или настроек интеграции. Они допускают более объёмные значения, но не участвуют в label selectors.
Annotation не является секретным хранилищем. Значения, которые читает automation, нужно документировать и версионировать как контракт.
5. Теория: equality и set-based selectors
Equality selectors используют =, == и !=. Set-based selectors поддерживают in, notin, наличие key и отсутствие !key. Условия через запятую соединяются AND; универсального OR между разными ключами и substring search нет.
Операторы != и notin также совпадают с объектами, у которых указанный key отсутствует; это отличается от требования existence. Пустой результат kubectl get обычно возвращает exit code 0: запрос корректен, но совпадений нет. Скрипт должен проверять количество и identity, оператор — namespace и --show-labels.
6. Теория: ownership, garbage collection и finalizers
OwnerReference содержит kind, name и uid владельца. Deployment владеет ReplicaSet, ReplicaSet — Pods. Совпадающие labels помогают найти объекты, но ownership не создают.
При background deletion владелец исчезает раньше dependents, foreground ждёт их удаления, orphan оставляет их. Cross-namespace ownership ограничена.
Finalizer задерживает окончательное удаление после deletionTimestamp, пока controller выполняет cleanup. Это обязательство controller, а не механизм владения; неизвестный finalizer нельзя удалять без анализа последствий.
7. Методические указания
- Определите scope через kubectl api-resources
- Зафиксируйте словарь labels и ответственных за него
- Применяйте собственный DNS prefix
- Не кодируйте всю metadata в name
- Не меняйте selector работающего workload для выпуска версии
- При пустой выборке проверяйте namespace, key, value и operator
- Различайте label selection и ownerReferences
- Перед удалением Namespace перечислите ресурсы шире, чем kubectl get all
8. Лабораторная работа: metadata-контракт
Создайте Namespace и три ConfigMap с общей таксономией. Сначала выполняется server-side validation, затем запись.
mkdir -p /tmp/k8s-m03
cat > /tmp/k8s-m03/namespace.yaml <<'EOF'
apiVersion: v1
kind: Namespace
metadata:
name: k8s-m03
labels:
course.goretskiy.pro/module: "03"
environment: training
annotations:
course.goretskiy.pro/purpose: "metadata and selectors laboratory"
EOF
cat > /tmp/k8s-m03/resources.yaml <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: storefront-api-config
namespace: k8s-m03
labels:
app.kubernetes.io/name: storefront
app.kubernetes.io/component: api
app.kubernetes.io/part-of: course-platform
app.kubernetes.io/managed-by: kubectl
course.goretskiy.pro/environment: dev
annotations:
course.goretskiy.pro/owner: platform-team
course.goretskiy.pro/runbook: "https://example.invalid/runbooks/storefront"
data:
LOG_LEVEL: info
---
apiVersion: v1
kind: ConfigMap
metadata:
name: storefront-worker-config
namespace: k8s-m03
labels:
app.kubernetes.io/name: storefront
app.kubernetes.io/component: worker
app.kubernetes.io/part-of: course-platform
app.kubernetes.io/managed-by: kubectl
course.goretskiy.pro/environment: dev
course.goretskiy.pro/temporary: "true"
annotations:
course.goretskiy.pro/owner: platform-team
data:
QUEUE: orders
---
apiVersion: v1
kind: ConfigMap
metadata:
name: billing-api-config
namespace: k8s-m03
labels:
app.kubernetes.io/name: billing
app.kubernetes.io/component: api
app.kubernetes.io/part-of: course-platform
app.kubernetes.io/managed-by: kubectl
course.goretskiy.pro/environment: stage
annotations:
course.goretskiy.pro/owner: finance-team
data:
LOG_LEVEL: warn
EOFkubectl apply --server-side --field-manager=course-lab -f /tmp/k8s-m03/namespace.yaml
kubectl wait --for=jsonpath='{.status.phase}'=Active namespace/k8s-m03 --timeout=30s
kubectl apply --dry-run=server -f /tmp/k8s-m03/resources.yaml
kubectl apply --server-side --field-manager=course-lab -f /tmp/k8s-m03/resources.yaml
kubectl get namespace k8s-m03 --show-labels
kubectl -n k8s-m03 get configmaps --show-labels9. Лабораторная работа: selectors и mutation
Проверьте equality, AND, set membership, inequality и отсутствие ключа. Затем измените label и убедитесь, что объект сразу перешёл в другую выборку.
kubectl -n k8s-m03 get configmaps -l app.kubernetes.io/name=storefront
kubectl -n k8s-m03 get configmaps -l app.kubernetes.io/name=storefront,app.kubernetes.io/component=api
kubectl -n k8s-m03 get configmaps -l 'course.goretskiy.pro/environment in (dev,stage)'
kubectl -n k8s-m03 get configmaps -l 'app.kubernetes.io/component!=worker'
kubectl -n k8s-m03 get configmaps -l '!course.goretskiy.pro/temporary'
kubectl -n k8s-m03 label configmap/storefront-worker-config course.goretskiy.pro/environment=stage --overwrite
kubectl -n k8s-m03 annotate configmap/storefront-worker-config course.goretskiy.pro/change-ticket=CHG-1042
kubectl -n k8s-m03 get configmaps -l course.goretskiy.pro/environment=dev --show-labels10. Лабораторная работа: граф владельцев
Deployment используется только для наблюдения ownership; rollout подробно рассматривается в модуле 5. Сравните uid объектов с uid в ownerReferences и докажите цепочку Deployment → ReplicaSet → Pod.
cat > /tmp/k8s-m03/deployment.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: k8s-m03
labels:
app.kubernetes.io/name: storefront
app.kubernetes.io/component: web
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: storefront
app.kubernetes.io/component: web
template:
metadata:
labels:
app.kubernetes.io/name: storefront
app.kubernetes.io/component: web
spec:
containers:
- name: nginx
image: nginx:1.29-alpine
EOF
kubectl apply --server-side --field-manager=course-lab -f /tmp/k8s-m03/deployment.yaml
kubectl -n k8s-m03 rollout status deployment/web --timeout=120s
rs=$(kubectl -n k8s-m03 get replicasets -l app.kubernetes.io/component=web -o jsonpath='{.items[0].metadata.name}')
pod=$(kubectl -n k8s-m03 get pods -l app.kubernetes.io/component=web -o jsonpath='{.items[0].metadata.name}')
kubectl -n k8s-m03 get replicaset "$rs" -o jsonpath='{range .metadata.ownerReferences[*]}owner={.kind}/{.name} uid={.uid}{"\n"}{end}'
kubectl -n k8s-m03 get pod "$pod" -o jsonpath='{range .metadata.ownerReferences[*]}owner={.kind}/{.name} uid={.uid}{"\n"}{end}'
kubectl -n k8s-m03 get deployment web -o jsonpath='deployment-uid={.metadata.uid}{"\n"}'
kubectl -n k8s-m03 get replicaset "$rs" -o jsonpath='replicaset-uid={.metadata.uid}{"\n"}'11. Диагностика: пустая выборка, scope и immutable selector
Первый запрос корректен, но выбирает ноль объектов. Затем воспроизводится namespace mismatch. Попытка изменить selector Deployment отклоняется как immutable, при этом рабочий объект остаётся неизменным.
kubectl -n k8s-m03 get configmaps -l course.goretskiy.pro/environment=qa
kubectl -n k8s-m03 get configmaps --show-labels
kubectl -n default get configmap storefront-api-config 2>&1 || true
kubectl -n k8s-m03 get configmap storefront-api-config
kubectl -n k8s-m03 patch deployment web --type=merge \
-p '{"spec":{"selector":{"matchLabels":{"course.goretskiy.pro/track":"canary"}},"template":{"metadata":{"labels":{"course.goretskiy.pro/track":"canary"}}}}}' || true
kubectl -n k8s-m03 get deployment web -o jsonpath='selector={.spec.selector.matchLabels}{"\n"}template={.spec.template.metadata.labels}{"\n"}'Для новой identity workload создают новый объект и планируют миграцию, а не обходят validation существующего Deployment.
12. Контроль, критерии освоения и cleanup
- Из чего состоит identity namespaced object?
- Почему Namespace не гарантирует isolation?
- Что хранить в label, а что в annotation?
- Как объединяются selector requirements?
- Почему нулевая выборка опасна для script?
- Создают ли одинаковые labels ownership?
- Зачем ownerReference содержит uid?
- Чем finalizer отличается от ownerReference?
- Почему selector Deployment immutable?
kubectl delete namespace k8s-m03 --wait=true
rm -rf /tmp/k8s-m03
kubectl get namespace k8s-m03 2>&1 || trueМодуль освоен, если вы проектируете taxonomy, используете разные selectors, доказываете ownership по uid и безопасно диагностируете пустую выборку.