Headlamp: Kubernetes UI без kubectl

Published: 2026-02-05

Headlamp — это open-source расширяемый дашборд для Kubernetes. Он stateless, запускается как Deployment и аутентифицирует пользователей через OIDC — каждый инженер видит свой кластер в рамках своих прав, без общего токена cluster-admin.

Этот пост — полная настройка для продакшена: конфигурация HelmRelease, интеграция OIDC с GitLab, RBAC, поддержка мультикластера и операционные паттерны, которые мы используем ежедневно.

Почему Headlamp, а не стандартный Dashboard

Официальный Kubernetes Dashboard требует либо токен-аутентификацию, либо proxy-тоннель. Ни то, ни другое не масштабируется: токен-аутентификация означает общие долгоживущие учётные данные, а proxy-тоннель требует, чтобы у каждого инженера были kubectl и доступ к кластеру — просто чтобы открыть вкладку в браузере.

Headlamp поддерживает OIDC из коробки: пользователь входит через GitLab, Headlamp передаёт токен API-серверу кластера и получает ровно те права, что разрешает RBAC. Никакого токена для ротации, никакого proxy для поддержки, никакого kubectl для read-доступа.

Плюс — система плагинов. Самый полезный встроенный: headlamp-plugin/prometheus — показывает графики ресурсов прямо на странице пода. Тренды CPU/памяти видны без открытия Grafana.

Другие встроенные возможности, заменяющие ежедневные kubectl-команды:

  • Стриминг логов в реальном времени — аналог kubectl logs -f, с подсветкой и поиском
  • Exec в контейнеры — аналог kubectl exec -it, терминал в браузере
  • Редактирование ресурсов — YAML-редактор с валидацией схемы
  • Лента событий — поток событий по namespace с фильтрацией

HelmRelease

yamlapiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: headlamp
  namespace: observability
spec:
  chart:
    spec:
      chart: headlamp
      version: "0.25.*"
      sourceRef:
        kind: HelmRepository
        name: headlamp
        namespace: flux-system
  values:
    replicaCount: 1
    config:
      oidc:
        clientID: "${HEADLAMP_OIDC_CLIENT_ID}"
        clientSecret: "${HEADLAMP_OIDC_CLIENT_SECRET}"
        issuerURL: "https://gitlab.example.com"
        scopes: "openid profile email groups"
    ingress:
      enabled: false   # обрабатывается через ApisixRoute
    resources:
      requests:
        cpu: 50m
        memory: 64Mi
      limits:
        cpu: 200m
        memory: 128Mi

Client ID и Client Secret для OIDC берутся из ExternalSecret, который тянет данные из Vault. issuerURL — адрес вашего экземпляра GitLab: GitLab поддерживает OIDC нативно, дополнительная конфигурация не нужна.

Обратите внимание на scope groups: без него OIDC-токен не включает членство в GitLab-группах, и Kubernetes RBAC не сможет сопоставить группы с ролями.

Настройка OIDC-приложения в GitLab

В GitLab создайте приложение через Admin > Applications (для instance-level) или Group > Settings > Applications (для group-level):

  • Redirect URI: https://headlamp.dev.example.com/oidc-callback
  • Scopes: openid, profile, email, groups
  • Сохраните Application ID и Secret в Vault по пути secret/headlamp/oidc

ExternalSecret:

yamlapiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: headlamp-oidc
  namespace: observability
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: headlamp-oidc-secret
    creationPolicy: Owner
  data:
    - secretKey: clientID
      remoteRef:
        key: secret/headlamp/oidc
        property: client_id
    - secretKey: clientSecret
      remoteRef:
        key: secret/headlamp/oidc
        property: client_secret

Затем в HelmRelease через valuesFrom:

yamlvaluesFrom:
  - kind: Secret
    name: headlamp-oidc-secret
    valuesKey: clientID
    targetPath: config.oidc.clientID
  - kind: Secret
    name: headlamp-oidc-secret
    valuesKey: clientSecret
    targetPath: config.oidc.clientSecret

ApisixRoute

Headlamp получает собственный роут в слое routes:

yamlapiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  name: headlamp
  namespace: ingress-apisix
spec:
  http:
    - name: headlamp
      match:
        hosts:
          - headlamp.dev.example.com
        paths:
          - "/*"
      backends:
        - serviceName: headlamp
          serviceNamespace: observability
          servicePort: 80

На уровне APISIX нет плагина аутентификации — её полностью обрабатывает OIDC-flow Headlamp. APISIX только проксирует запрос.

Если нужно ограничить доступ к URL Headlamp по IP до OIDC-редиректа (эшелонированная защита), добавьте плагин ip-restriction:

yamlplugins:
  - name: ip-restriction
    enable: true
    config:
      whitelist:
        - "10.0.0.0/8"
        - "192.168.0.0/16"

RBAC для OIDC-пользователей

Headlamp передаёт claim groups из GitLab в Kubernetes API. API-сервер использует этот claim для RBAC. ClusterRoleBinding связывает GitLab-группу с ClusterRole:

yamlapiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: headlamp-dev-readonly
subjects:
  - kind: Group
    name: "example/dev-team"
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: view
  apiGroup: rbac.authorization.k8s.io

Значение name в subjects должно точно совпадать с путём группы, который GitLab возвращает в OIDC claim groups. GitLab возвращает полный путь: родительская-группа/подгруппа. Если ваша группа acme-corp/platform/dev-team, используйте эту строку дословно.

Наша многоуровневая модель доступа:

GitLab-группа ClusterRole Что может
org/devops-team cluster-admin Всё
org/platform-team edit Деплой, scale, рестарт подов
org/dev-team view Только чтение, без секретов
org/qa-team qa-viewer (custom) Чтение подов/логов, без конфигов

Кастомная роль qa-viewer:

yamlapiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: qa-viewer
rules:
  - apiGroups: [""]
    resources: ["pods", "pods/log", "pods/exec", "services", "endpoints"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["deployments", "replicasets", "statefulsets", "daemonsets"]
    verbs: ["get", "list", "watch"]

QA-инженеры могут смотреть поды и стримить логи, но не могут читать Secrets или что-либо изменять. Все биндинги живут в spoke/rbac/ и применяются spoke-Kustomization — отдельно от HelmRelease Headlamp, чтобы изменения RBAC не требовали роллаута Headlamp.

Несколько кластеров в одном UI

Headlamp обнаруживает кластеры из kubeconfig, примонтированного в под. Для мультикластерного использования Secret с несколькими контекстами монтируется в /headlamp/kubeconfig:

yamlvolumes:
  - name: kubeconfigs
    secret:
      secretName: headlamp-kubeconfigs
volumeMounts:
  - name: kubeconfigs
    mountPath: /headlamp/kubeconfig
    readOnly: true

Секрет содержит объединённый kubeconfig с контекстами всех кластеров. Инженеры видят выпадающий список кластеров в левом верхнем углу и переключаются между dev, test, sre и другими без отдельной вкладки в браузере.

Генерация объединённого kubeconfig:

bashKUBECONFIG=kubeconfigs/dev.yaml:kubeconfigs/test.yaml:kubeconfigs/sre.yaml \
  kubectl config view --flatten > merged-kubeconfig.yaml

kubectl create secret generic headlamp-kubeconfigs \
  --from-file=config=merged-kubeconfig.yaml \
  -n observability \
  --dry-run=client -o yaml | \
  kubeseal --cert flux-sealed-secrets.pem --format yaml > headlamp-kubeconfigs-sealedsecret.yaml

При добавлении кластера — перегенерируйте merged kubeconfig, перезапечатайте секрет, сделайте коммит. Headlamp подхватит изменения при следующем рестарте пода.

Управление плагинами

Плагины Headlamp загружаются из директории /headlamp/plugins внутри контейнера. Есть два способа их установки:

1. Init container (для кастомных и community-плагинов):

yamlinitContainers:
  - name: install-plugins
    image: alpine:3.19
    command:
      - sh
      - -c
      - |
        wget -O /plugins/prometheus.tar.gz \
          https://github.com/headlamp-k8s/plugins/releases/download/v0.8.0/prometheus-0.8.0.tar.gz
        tar -xzf /plugins/prometheus.tar.gz -C /plugins/
    volumeMounts:
      - name: plugins
        mountPath: /plugins

2. ConfigMap (для небольших плагинов, не рекомендуется для продакшена):

Монтируйте JS-файл плагина напрямую из ConfigMap. Подходит для тестирования, но обновление плагина превращается в обновление ConfigMap — неудобно.

Подход с init container чище: версия плагина зафиксирована, контрольная сумма неявно следует из истории git.

Полезные горячие клавиши и советы

  • Ctrl+Shift+K — открыть палитру команд (быстрый поиск любого ресурса)
  • Клик по имени пода → вкладка с живыми логами, без kubectl logs
  • Вкладка «Exec» даёт терминал внутри контейнера — аналог kubectl exec -it
  • Вкладка «Describe» показывает полный YAML ресурса с аннотациями — быстрее kubectl describe

Фильтрация по лейблу

Список ресурсов Headlamp поддерживает фильтрацию по label selector в строке поиска. Введите app=redis — список отфильтруется по совпадающим ресурсам. Работает для всех типов ресурсов — удобно, когда 200 подов и нужно найти все поды конкретного сервиса.

Port forwarding

Headlamp поддерживает port-forward через UI: откройте под → вкладка «Port Forward» → укажите локальный и контейнерный порт. Работает так же, как kubectl port-forward, но без терминала.

Устранение неполадок

Вход через OIDC падает с ошибкой invalid client

Неверный Client ID или Secret, либо Redirect URI в GitLab не совпадает с URL Headlamp (включая суффикс /oidc-callback).

Пользователь входит, но везде видит forbidden

Claim groups не отправляется. Проверьте OIDC scopes — groups должен быть включён. Также проверьте groups claim в токене через jwt.io: декодируйте ID token из вкладки сети браузера и проверьте поле groups.

Мультикластерный kubeconfig показывает старые имена кластеров

Headlamp кэширует kubeconfig при запуске. Перезапустите под после обновления секрета с kubeconfig: kubectl rollout restart deployment/headlamp -n observability.