Learning Platform
Глоссарий Troubleshooting
Урок 22.02 · 25 мин
Продвинутый
KubernetesMulti-tenancyFlink OperatorRBACResourceQuota

Multi-tenant K8s setup для Flink

Multi-tenancy в Kubernetes — это набор изоляционных границ: namespaces, RBAC, ResourceQuota, NetworkPolicy, PodSecurityPolicy. Для Flink-платформы каждая команда должна получить свой namespace, в котором она может деплоить Flink-jobs самостоятельно, не имея доступа к ресурсам других команд.

В этом уроке настроим Flink Kubernetes Operator в cluster-wide режиме, создадим namespace template для нового tenant, настроим RBAC и quotas, и определим tenant onboarding процедуру.

Capabilities и namespaces Linux: изоляция на уровне ядра

Архитектура: cluster-wide Operator + per-tenant namespaces

Flink Kubernetes Operator имеет два режима работы:

  • Namespaced — operator watches только один namespace. Простой, но требует один operator на каждый namespace. Не подходит для multi-tenant.
  • Cluster-wide — operator watches все namespaces (или whitelist). Один operator управляет всеми tenant’ами. Подходит для нашего сценария.
Cluster-wide Operator архитектура
flink-operator (flink-system ns)Flink Operator deployment в namespace flink-system. Имеет cluster role, может watch и manage FlinkDeployment CRs во всех whitelist namespaces.
watches CRs
cluster RBACServiceAccount + ClusterRole + ClusterRoleBinding. Operator имеет permissions на pods, services, configmaps, secrets во всех tenant namespaces.
ns: fraud-teamTenant namespace fraud-team. FlinkDeployments deployed здесь. ResourceQuota limit на cpu/memory. RBAC: fraud-team service accounts могут CRUD только в этом namespace.
ns: ads-teamTenant namespace ads-team. Independent quotas, RBAC, secrets. Не видит ресурсы fraud-team.
ns: analytics-teamTenant namespace analytics-team. Третий tenant с разными SLA.
FlinkDeployment в fraud-teamFlinkDeployment fraud-detection. Operator создаёт JobManager и TaskManager pods в namespace fraud-team.
reconcile
Reconcile loopOperator periodically reconciles desired vs actual state. Создаёт pods, services, configmaps в tenant namespace.

Helm chart Flink Kubernetes Operator:

helm repo add flink-operator-repo \
  https://downloads.apache.org/flink/flink-kubernetes-operator-1.10.0/

helm install flink-kubernetes-operator \
  flink-operator-repo/flink-kubernetes-operator \
  --namespace flink-system \
  --create-namespace \
  --set watchNamespaces=null \
  --set operatorPod.resources.requests.cpu=500m \
  --set operatorPod.resources.requests.memory=1Gi \
  --set operatorPod.resources.limits.cpu=2 \
  --set operatorPod.resources.limits.memory=2Gi \
  --set image.tag=1.10.0

watchNamespaces=null означает cluster-wide. Можно сузить до whitelist:

--set watchNamespaces="{fraud-team,ads-team,analytics-team}"

Whitelist предпочтительнее в production: операторам не нужно reconcile resources в системных namespaces.

После установки проверьте, что operator running:

kubectl get pods -n flink-system
# NAME                                         READY   STATUS    RESTARTS   AGE
# flink-kubernetes-operator-7d8b9c5d4f-xyz12   2/2     Running   0          1m

kubectl get crd flinkdeployments.flink.apache.org
# NAME                                  CREATED AT
# flinkdeployments.flink.apache.org    2026-05-19T10:00:00Z

Tenant namespace template

Каждый новый tenant получает namespace, созданный по template. Template применяется через GitOps (ArgoCD) или ручной kubectl apply. Базовый template:

# tenant-namespace-template.yaml
---
apiVersion: v1
kind: Namespace
metadata:
  name: ${TENANT_NAME}
  labels:
    flink-platform/tenant: "${TENANT_NAME}"
    flink-platform/managed: "true"
---
apiVersion: v1
kind: ResourceQuota
metadata:
  name: tenant-quota
  namespace: ${TENANT_NAME}
spec:
  hard:
    requests.cpu: "${TENANT_CPU_REQUESTS}"
    requests.memory: "${TENANT_MEMORY_REQUESTS}"
    limits.cpu: "${TENANT_CPU_LIMITS}"
    limits.memory: "${TENANT_MEMORY_LIMITS}"
    persistentvolumeclaims: "${TENANT_PVC_COUNT}"
    count/pods: "${TENANT_POD_COUNT}"
    count/services: "20"
    count/secrets: "30"
---
apiVersion: v1
kind: LimitRange
metadata:
  name: tenant-limits
  namespace: ${TENANT_NAME}
spec:
  limits:
  - default:
      cpu: 2
      memory: 4Gi
    defaultRequest:
      cpu: 500m
      memory: 1Gi
    max:
      cpu: 8
      memory: 32Gi
    type: Container
---
# RBAC: tenant team может CRUD только Flink resources
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: tenant-flink-role
  namespace: ${TENANT_NAME}
rules:
- apiGroups: ["flink.apache.org"]
  resources: ["flinkdeployments", "flinksessionjobs"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
  resources: ["pods", "pods/log", "services", "configmaps"]
  verbs: ["get", "list", "watch"]
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list"]  # не create/delete — secrets через Vault
- apiGroups: ["batch"]
  resources: ["jobs", "cronjobs"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: tenant-flink-binding
  namespace: ${TENANT_NAME}
subjects:
- kind: Group
  name: ${TENANT_LDAP_GROUP}  # AD/LDAP group для tenant team
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: tenant-flink-role
  apiGroup: rbac.authorization.k8s.io
---
# Service account для Flink jobs в этом namespace
apiVersion: v1
kind: ServiceAccount
metadata:
  name: flink
  namespace: ${TENANT_NAME}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: flink-role-binding
  namespace: ${TENANT_NAME}
subjects:
- kind: ServiceAccount
  name: flink
  namespace: ${TENANT_NAME}
roleRef:
  kind: ClusterRole
  name: flink  # provided by Flink Operator
  apiGroup: rbac.authorization.k8s.io
---
# NetworkPolicy: tenant pods могут общаться только внутри своего namespace
# и с shared services (Kafka, S3)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: tenant-isolation
  namespace: ${TENANT_NAME}
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: ${TENANT_NAME}
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: flink-system  # operator может dial
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: monitoring  # Prometheus scrape
  egress:
  - to:
    - namespaceSelector: {}  # любой namespace для DNS resolution
    ports:
    - protocol: UDP
      port: 53
  - to:
    - podSelector:
        matchLabels:
          app: kafka
      namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: kafka-system
  - to:
    - namespaceSelector: {}
    ports:
    - protocol: TCP
      port: 443  # HTTPS для S3, Vault

Применить для нового tenant:

export TENANT_NAME="fraud-team"
export TENANT_CPU_REQUESTS="50"
export TENANT_MEMORY_REQUESTS="200Gi"
export TENANT_CPU_LIMITS="100"
export TENANT_MEMORY_LIMITS="400Gi"
export TENANT_PVC_COUNT="20"
export TENANT_POD_COUNT="100"
export TENANT_LDAP_GROUP="fraud-team-engineers"

envsubst < tenant-namespace-template.yaml | kubectl apply -f -

ResourceQuota: предотвращение noisy neighbor

ResourceQuota — критичный механизм multi-tenancy. Без неё один tenant может потребить всё CPU и memory кластера, оставив других без ресурсов.

Базовая стратегия квот:

spec:
  hard:
    # CPU/memory requests = гарантированное allocation
    requests.cpu: "50"        # 50 vCPU гарантировано
    requests.memory: "200Gi"  # 200Gi RAM гарантировано

    # CPU/memory limits = максимум при burst
    limits.cpu: "100"         # до 100 vCPU при burst
    limits.memory: "400Gi"    # до 400Gi при burst

    # Object counts — защита от runaway controller
    persistentvolumeclaims: "20"
    count/pods: "100"
    count/services: "20"

requests гарантируется (используется в scheduling), limits — soft cap (kernel убьёт container при превышении memory limit). Соотношение limits/requests = 2x — типичный pattern для bursty workloads.

Дополнительно — PriorityClass для разных SLA:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: flink-critical
value: 1000000
globalDefault: false
description: "Critical Flink jobs — fraud detection, payments"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: flink-batch
value: 100
globalDefault: false
description: "Batch Flink jobs — analytics, ETL"

Tenant использует в FlinkDeployment:

spec:
  podTemplate:
    spec:
      priorityClassName: flink-critical

При resource pressure низкий-priority jobs preempted в пользу высокого.


Tenant onboarding процедура

Self-service onboarding — главный показатель зрелости платформы. Процедура:

1. Tenant team создаёт PR в platform-config repo с заполненным template:

# tenants/fraud-team.yaml
tenant:
  name: fraud-team
  ldap_group: fraud-team-engineers
  oncall_team: fraud-detection-oncall
  contact_slack: "#fraud-team"

quotas:
  cpu_requests: 50
  memory_requests: 200Gi
  cpu_limits: 100
  memory_limits: 400Gi
  max_pods: 100
  max_pvcs: 20

sla:
  tier: critical  # critical | standard | batch
  priority_class: flink-critical
  alert_channel: "#fraud-oncall"
  pagerduty_service: fraud-detection

resources:
  kafka_topics:  # запрос ACL для топиков
  - name: transactions
    permissions: [read]
  - name: fraud-alerts
    permissions: [write]
  s3_buckets:
  - name: flink-state-fraud
    permissions: [read, write]
  - name: flink-savepoints-fraud
    permissions: [read, write]

2. Platform team review — проверяет:

  • Quotas reasonable (не запрашивают 1000 vCPU без оснований)
  • Kafka topics существуют
  • S3 buckets созданы
  • LDAP group существует и содержит tenant team members

3. ArgoCD applies — после merge ArgoCD создаёт namespace, RBAC, quotas, AWS IAM роли (через IRSA для S3 access).

4. Tenant deploys свой первый Flink job:

# fraud-team/jobs/fraud-detection.yaml
apiVersion: flink.apache.org/v1beta1
kind: FlinkDeployment
metadata:
  name: fraud-detection
  namespace: fraud-team
spec:
  image: registry.company.com/flink:2.2.0
  flinkVersion: v2_2
  serviceAccount: flink
  flinkConfiguration:
    taskmanager.numberOfTaskSlots: "4"
    state.savepoints.dir: s3://flink-savepoints-fraud/fraud-detection/
    state.checkpoint.dir: s3://flink-checkpoints-fraud/fraud-detection/
    metrics.reporters: prom
    metrics.reporter.prom.factory.class: org.apache.flink.metrics.prometheus.PrometheusReporterFactory
    metrics.reporter.prom.port: 9249
  jobManager:
    resource:
      memory: 4096m
      cpu: 2
  taskManager:
    resource:
      memory: 8192m
      cpu: 4
  job:
    jarURI: local:///opt/flink/usrlib/fraud-detection-2.2.jar
    parallelism: 16
    upgradeMode: savepoint
  podTemplate:
    spec:
      priorityClassName: flink-critical
      containers:
      - name: flink-main-container
        env:
        - name: KAFKA_BOOTSTRAP_SERVERS
          value: kafka-headless.kafka-system:9092
        - name: VAULT_ADDR
          value: http://vault.vault:8200

5. Monitoring auto-enabled — Prometheus ServiceMonitor создаётся через mutating webhook, Grafana dashboard auto-import через JSON-based provisioning.


Что мониторить per-tenant

Платформа должна показывать per-tenant metrics:

# Total CPU usage по tenant
sum by (namespace) (
  container_cpu_usage_seconds_total{namespace=~".*-team"}
)

# Memory usage vs quota
sum by (namespace) (container_memory_working_set_bytes{namespace=~".*-team"})
/
sum by (namespace) (kube_resourcequota{resource="limits.memory"})

# Количество active Flink jobs
count by (namespace) (kube_pod_status_phase{
  phase="Running",
  pod=~".*-jobmanager-.*"
})

# Checkpoint failure rate per tenant
sum by (namespace) (rate(flink_jobmanager_job_checkpointsFailed[5m]))
/
sum by (namespace) (rate(flink_jobmanager_job_totalNumberOfCheckpoints[5m]))

Эти метрики экспонируются как tenant dashboard в Grafana. Каждый tenant видит свои metrics + cost (через AWS Cost Explorer label-based allocation).

WARNING

Без cost transparency tenant’ы будут запрашивать unlimited quotas «just in case». Показывайте им $ cost (через Kubecost или AWS Cost Allocation Tags), и они сами начнут оптимизировать.


Итоги

Multi-tenant Flink platform строится на трёх pillars: cluster-wide Flink Operator (один operator для всех tenants), namespace-per-team (с RBAC, ResourceQuota, NetworkPolicy для изоляции), self-service tenant onboarding (через GitOps).

Главный принцип: tenants работают независимо через стандартный Kubernetes interface (FlinkDeployment CRD), platform team автоматизирует общие компоненты (operator, monitoring, secrets management).

В следующем уроке добавим autoscaling — без него tenants либо over-provision (дорого), либо страдают от lag spikes (нарушение SLA).

Проверка знанийKnowledge check
Tenant fraud-team запрашивает ResourceQuota requests.cpu=50, limits.cpu=200. Это 4x burst ratio. Platform team возражает. Какие риски и что правильнее?
ОтветAnswer
Burst ratio limits/requests определяет, насколько cluster overcommitted в worst case. Если все tenants одновременно достигают limits (например, во время real-world traffic spike — Black Friday), физических ресурсов не хватит. Pods начнут CPU throttling (медленные) или OOM-killed (memory). Безопасное ratio 1.5-2x предполагает, что concurrent peak использование меньше суммы limits. Для bursty workloads правильное решение — не больший burst ratio, а horizontal scaling: HPA или Flink Autoscaler динамически добавляют pods при load, освобождают при quiet. Это требует resources в кластере для scale-up — обычно через Cluster Autoscaler, который добавляет ноды.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Flink Kubernetes Operator deployment modes — cluster-wide vs namespaced. Какой выбор для multi-tenant production и почему?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5