Иллюзия гибридной облачной платформы: почему ваш on-prem и облако до сих пор живут порознь
Как мы наконец научили нагрузки нашего датацентра общаться с Google Cloud — без единого ключа сервисного аккаунта.
|
Примечание
|
Артефакты Kubernetes и Terraform доступны в репозитории GitHub — ссылка приведена в конце статьи. |
Последние несколько месяцев я занимался тем, что должно было быть давно решённой задачей: дать приложениям, работающим в наших on-premises-кластерах Kubernetes, доступ к сервисам Google Cloud. Вместо готового решения я обнаружил повсеместную культуру обходных путей, построенных на антипаттернах безопасности — и на удивление элегантное решение, лежавшее на виду.
Если всё сделано правильно, гибридное облако выглядит примерно так:
Зачем нужен гибридный облачный подход
Причин внедрить гибридное облако в организации немало. Вот лишь некоторые из них:
-
Передача «тяжёлых» вычислений в BigQuery. Аналитические приложения остаются on-prem по соображениям суверенитета данных, а массивные датасеты передаются в BigQuery. Это даёт вычислительную мощь мирового уровня без необходимости покупать и обслуживать хотя бы один дополнительный сервер в собственной стойке.
-
Единая сеть с Cloud Interconnect. Используя Cloud Interconnect или Cloud VPN, собственный датацентр буквально становится расширением VPC в GCP. Это позволяет приложениям для выставления счетов обращаться к облачным пользовательским сервисам с низкой задержкой и без выхода в публичный интернет.
-
Экономичное масштабирование через Cloud Storage. Можно навсегда забыть о переполнении дисков on-prem: Cloud Storage в роли бэкенда для локальных приложений позволяет хранить неограниченные объёмы логов, резервных копий и исторических данных, платя только за реально используемый объём.
-
Синхронизация по событиям с Pub/Sub. Когда on-prem происходит какое-либо событие (например, формируется новый счёт), можно отправить сообщение через Cloud Pub/Sub. Облачные сервисы реагируют мгновенно, поддерживая синхронизацию двух платформ без громоздкого ручного опроса.
Экономика гибридного подхода: GPU изменили всё
Прежде чем переходить к технической стороне вопроса, стоит поговорить о том, почему гибридные облака важны сейчас как никогда.
Ваша компания, как и большинство крупных предприятий, сделала серьёзные инвестиции в собственные датацентры. Серверы куплены. Стойки заполнены. Сетевая инфраструктура оплачена. Предельные затраты на запуск ещё одной нагрузки практически равны нулю — капитал уже вложен.
Потом накрыла волна AI.
Внезапно каждой команде нужны GPU. Не один-два — десятки A100 для обучения, целые парки inference-эндпоинтов, векторные базы данных, которые должны располагаться рядом с моделями. И здесь возникает проблема: GPU в дефиците. Время ожидания on-prem-оборудования с GPU измеряется месяцами. Облачные провайдеры, в свою очередь, предоставляют его за минуты.
Поэтому архитектура, которая действительно имеет экономический смысл, выглядит вот так:
On-prem-датацентр берёт на себя основную вычислительную нагрузку — веб-серверы, бизнес-логику, базы данных, пакетную обработку. Это стандартные вычисления. Мы за них уже заплатили.
Облако занимается дефицитными ресурсами — GPU-ускоренным инференсом, обучением моделей, AI/ML-эндпоинтами. Мы платим за каждый запрос, масштабируемся по требованию и не ждём полгода железа.
Именно такая гибридная платформа имеет финансовый смысл. Облако — не конечная цель миграции, а расширение для возможностей, которые сложно реализовать on-prem.
Конечно, дело не ограничивается GPU. Настоящая гибридная облачная платформа — это когда приложения просто могут общаться друг с другом: надёжно и безопасно. Возможно, вы используете BigQuery и хотите потреблять его данные.
Но вот в чём загвоздка: on-prem-нагрузки должны аутентифицироваться в облачных сервисах. Каждый вызов API из датацентра к эндпоинту Vertex AI, каждый запрос к GPU-сервису инференса, каждая запись артефактов модели в Cloud Storage — всё это требует учётных данных.
И именно здесь мы возвращаемся к ключевой проблеме.
Ключевая проблема (и это каламбур)
Сценарий, разворачивающийся в тысячах предприятий ежедневно:
Команда разработчиков хочет, чтобы их on-prem-приложение писало в Google Cloud Storage. «Очевидное» решение? Сгенерировать ключ сервисного аккаунта GCP, закодировать его в base64, запихнуть в Kubernetes Secret и смонтировать в поде.
apiVersion: v1
kind: Secret
metadata:
name: gcp-credentials
type: Opaque
data:
key.json: eyJ0eXBlIjoic2VydmljZV9hY2NvdW50IiwicHJvamVjdF9pZCI6…
Это «работает». Но при этом:
-
Ключ никогда не истекает — он действителен до тех пор, пока кто-нибудь не вспомнит о его ротации (не вспомнит) или пока он не окажется скомпрометирован (а это случится).
-
Его легко похитить — любой, у кого есть доступ на чтение в этот неймспейс, может выполнить
kubectl get secret -o yamlи уйти с постоянным доступом к GCP. -
Отсутствует привязка к конкретной нагрузке в аудите — GCP видит «service-account-xyz обратился к этому бакету», а не «под frontend-abc-123 в неймспейсе production».
-
Это кошмар в масштабе — 50 команд × 3 окружения × 4 проекта GCP = 600 ключей, которые нужно отслеживать, ротировать и молиться, что ни один из них не попал в git.
Службы безопасности прекрасно это понимают. Поэтому многие организации сделали единственно разумное: полностью запретили генерацию ключей сервисных аккаунтов.
Случайный воздушный зазор
Вот где становится интересно. Запретив генерацию ключей, вы не решили проблему гибридной облачной платформы — вы просто переложили её на чужие плечи. Как правило, эти плечи принадлежат платформенной команде, уставившейся в задачу Jira с текстом «нет доступа к GCP из on-prem, P1, блокирует релиз».
В результате ваша «гибридная облачная платформа» вовсе не является гибридной. Это две разрозненные системы:
Команды начинают городить промежуточные сервисы, API-шлюзы для проксирования запросов или — будем честными — изобретательно доставать ключи в обход запрета. Это не платформа. Это скотч и изолента.
Технология существовала всё это время
А что если я скажу вам, что каждый кластер Kubernetes уже выдаёт криптографически подписанные токены идентификации каждому поду? И что в Google Cloud есть сервис, специально созданный для того, чтобы доверять этим токенам?
Это Workload Identity Federation (федерация рабочих удостоверений) — и в связке с OIDC (OpenID Connect) именно она является тем недостающим звеном, которое делает гибридные платформы по-настоящему рабочими.
Как работает идентификация в Kubernetes
Каждый под с ServiceAccount (сервисным аккаунтом) автоматически получает JWT-токен, смонтированный по пути /run/secrets/kubernetes.io/serviceaccount/token. Это не просто непрозрачный набор байт — это криптографически подписанное удостоверение личности:
{
"iss": "https://kubernetes.default.svc.cluster.local",
"sub": "system:serviceaccount:production:backend-api",
"aud": ["https://iam.googleapis.com/..."],
"kubernetes.io": {
"namespace": "production",
"serviceaccount": {
"name": "backend-api"
}
},
"exp": 1735689600
}
Ключевой момент: этот токен может верифицировать любой, у кого есть публичные ключи вашего кластера (JWKS — JSON Web Key Set). Кластер публикует их по стандартному эндпоинту:
kubectl get --raw /openid/v1/jwks
Google Cloud Security Token Service (STS) умеет валидировать эти токены. Никакого обмена ключами. Никаких хранимых секретов. Только криптографическое доказательство идентификации.
Строим мост: Workload Identity Federation
Вот архитектура, которую можно реализовать:
Сторона GCP: пул идентификации и OIDC-провайдер
Workload Identity Pool (пул рабочих удостоверений) — это граница доверия, декларация вида «я принимаю идентификации из внешних источников». OIDC-провайдер задаёт, как именно валидировать эти идентификации.
resource "google_iam_workload_identity_pool" "pool" {
workload_identity_pool_id = "hybrid-platform-pool"
project = "my-project"
}
resource "google_iam_workload_identity_pool_provider" "k8s_provider" {
project = "my-project"
workload_identity_pool_id = google_iam_workload_identity_pool.pool.workload_identity_pool_id
workload_identity_pool_provider_id = "on-prem-cluster"
attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.namespace" = "assertion['kubernetes.io']['namespace']"
}
attribute_condition = "attribute.namespace in [\"production\", \"staging\"]"
oidc {
issuer_uri = "https://kubernetes.default.svc.cluster.local"
jwks_json = file("jwks.json") # Публичные ключи вашего кластера
}
}
Два момента, заслуживающих внимания:
-
attribute_mapping— извлекает claims (утверждения) из Kubernetes JWT и делает их доступными как атрибуты GCP. Мы вытаскиваем неймспейс, чтобы использовать его для управления доступом. -
attribute_condition— именно здесь живёт политика безопасности. Подробнее об этом ниже.
Common Expression Language: тонкое управление доступом
Поле attribute_condition использует CEL (Common Expression Language — язык общих выражений), и он на удивление мощный. Вот эта одна строка политики заменяет то, что потребовало бы десятков IAM-привязок:
attribute.namespace in ["production", "staging"]
С этим условием под в неймспейсе kube-system вообще не сможет аутентифицироваться в GCP — обмен токенов будет отклонён ещё до того, как IAM будет опрошен.
Можно усложнить:
// Только неймспейс production и только конкретные сервисные аккаунты
attribute.namespace == "production" &&
attribute.service_account in ["payment-processor", "order-service"]
// Разрешить staging, но только в рабочее время (да, серьёзно)
attribute.namespace == "staging" &&
request.time.getHours("America/New_York") >= 9 &&
request.time.getHours("America/New_York") < 17
Это глубокая эшелонированная защита (defense in depth). Даже если кто-то создаст мошеннический ServiceAccount, даже если у него есть доступ к kubectl — он не сможет аутентифицироваться в GCP, пока не пройдёт условие CEL. Граница безопасности обеспечивается инфраструктурой Google, а не надеждой на то, что разработчики соблюдают политику.
Сторона Kubernetes: автоматическое внедрение учётных данных
Рабочая федерация идентификации — это лишь половина дела. Разработчики не должны разбираться в OIDC, STS или файлах конфигурации учётных данных. Они должны просто задеплоить приложение — и оно должно работать.
Здесь в игру вступает Kyverno. Единственный ClusterPolicy (кластерная политика) автоматически внедряет всё, что нужно поду:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: workload-identity-federation
spec:
rules:
- name: inject-gcp-credentials
match:
any:
- resources:
kinds:
- Deployment
selector:
matchLabels:
workload-identity-federation: "enabled"
mutate:
patchStrategicMerge:
spec:
template:
spec:
volumes:
- name: workload-identity-credential-configuration
configMap:
name: workload-identity-federation-config
containers:
- (name): "*"
volumeMounts:
- name: workload-identity-credential-configuration
mountPath: /etc/workload-identity
readOnly: true
env:
- name: GOOGLE_APPLICATION_CREDENTIALS
value: "/etc/workload-identity/credential-configuration.json"
С точки зрения разработчика, вот и всё, что нужно для интеграции:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
labels:
workload-identity-federation: "enabled" # Это всё. Буквально.
spec:
# ... обычная спецификация деплоя
Файл конфигурации учётных данных (создаваемый Terraform в виде ConfigMap) сообщает клиентским библиотекам Google, как обменивать токены:
{
"type": "external_account",
"audience": "//iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID",
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
"token_url": "https://sts.googleapis.com/v1/token",
"credential_source": {
"file": "/run/secrets/kubernetes.io/serviceaccount/token"
}
}
Этот формат понимают все SDK и клиентские библиотеки Google Cloud. Python, Go, Java, Node — всё работает без дополнительных настроек.
IAM: выдача реальных прав доступа
Федеративным идентификациям нужны права доступа к ресурсам. Мы привязываем IAM-роли к атрибутам пула идентификации:
resource "google_project_iam_member" "secret_access" {
for_each = toset(["production", "staging"])
project = "my-project"
role = "roles/secretmanager.secretAccessor"
member = "principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_ID}/attribute.namespace/${each.value}"
}
Это даёт доступ к Secret Manager всем подам, аутентифицированным из неймспейсов production или staging. Синтаксис principalSet позволяет делать выборку по атрибутам. Можно также ограничить доступ конкретными сервисными аккаунтами:
member = "principal://iam.googleapis.com/.../subject/system:serviceaccount:production:payment-processor"
Проверяем работу: листинг секретов из on-prem
Убедимся в работоспособности с помощью простого Python-скрипта, который выводит список секретов из Secret Manager. Скрипт выполняется внутри пода в вашем on-premises-кластере:
# list_secrets.py - выполняется on-prem, обращается к GCP Secret Manager
from google.cloud import secretmanager
def list_secrets(project_id: str):
"""
Вывести список всех секретов в проекте GCP.
Учётные данные явно не передаются. Библиотека google-cloud-secret-manager
автоматически:
1. Читает переменную GOOGLE_APPLICATION_CREDENTIALS (установленную Kyverno)
2. Загружает JSON-конфигурацию учётных данных
3. Читает токен K8s ServiceAccount из /run/secrets/...
4. Обменивает его на токен доступа GCP через STS
5. Использует этот токен для вызова API Secret Manager
"""
client = secretmanager.SecretManagerServiceClient()
parent = f"projects/{project_id}"
print(f"Secrets in {project_id}:")
print("-" * 40)
for secret in client.list_secrets(request={"parent": parent}):
# Извлекаем только имя секрета из полного пути ресурса
secret_name = secret.name.split("/")[-1]
print(f" - {secret_name}")
print("-" * 40)
print("Authentication: Workload Identity Federation")
print("Credentials: None stored, token exchanged at runtime")
if __name__ == "__main__":
list_secrets("my-project-id")
Запускаем внутри помеченного пода:
$ kubectl exec -it my-app-xyz -- python list_secrets.py
Secrets in my-project-id:
----------------------------------------
- database-password
- api-key-stripe
- oauth-client-secret
- ml-model-api-key
----------------------------------------
Authentication: Workload Identity Federation
Credentials: None stored, token exchanged at runtime
Никакого ключа сервисного аккаунта. Никакого смонтированного секрета. Просто токен Kubernetes ServiceAccount, обмениваемый на учётные данные GCP во время выполнения. Тот же подход работает для любого сервиса GCP: Secret Manager, Cloud Storage, BigQuery, Pub/Sub и, конечно, Vertex AI.
Возвращаемся к GPU: гибридная AI-платформа
Вспомним архитектуру из начала статьи — on-prem-приложения вызывают облачные GPU-сервисы. Посмотрим, как это реально работает с Workload Identity Federation.
Рассмотрим типичный сценарий: on-prem-сервис обработки заказов должен вызвать эндпоинт Vertex AI для обнаружения мошенничества. Модель работает на GPU в Google Cloud (потому что там можно получить A100 за минуты, а не месяцы). Логика приложения остаётся on-prem (потому что за эти вычислительные мощности мы уже заплатили).
При правильно настроенных IAM-привязках в Google Cloud любой под из разрешённых неймспейсов сможет вызвать Vertex AI. Вот код приложения:
# fraud_detector.py - выполняется on-prem, вызывает облачные GPU
from google.cloud import aiplatform
def check_fraud(transaction: dict) -> float:
"""
Вызов эндпоинта Vertex AI для обнаружения мошенничества.
Модель работает на A100 GPU в Google Cloud.
Этот код выполняется on-prem в нашем датацентре.
Аутентификация происходит автоматически:
1. Kyverno внедрил GOOGLE_APPLICATION_CREDENTIALS
2. SDK aiplatform читает конфигурацию учётных данных
3. Токен K8s SA обменивается на токен GCP через STS
4. Запрос аутентифицируется в Vertex AI
"""
endpoint = aiplatform.Endpoint(
endpoint_name="projects/my-project/locations/us-central1/endpoints/fraud-model"
)
prediction = endpoint.predict(instances=[transaction])
return prediction.predictions[0]["fraud_score"]
def generate_embeddings(texts: list[str]) -> list[list[float]]:
"""
Генерация текстовых эмбеддингов с использованием облачной модели.
Модели эмбеддингов требовательны к GPU. Запускать их on-prem
означало бы выделять под них специальное железо. В облаке платим за запрос.
"""
from vertexai.language_models import TextEmbeddingModel
model = TextEmbeddingModel.from_pretrained("text-embedding-004")
embeddings = model.get_embeddings(texts)
return [e.values for e in embeddings]
Разработчику не нужно думать об аутентификации вообще. Он просто добавляет лейбл к своему деплою:
metadata:
labels:
workload-identity-federation: "enabled"
И его on-prem-под может теперь обращаться к:
-
Vertex AI endpoints — для ML-инференса на облачных GPU
-
Cloud Storage — для артефактов моделей и обучающих данных
-
BigQuery — для хранилищ фич и аналитики
-
Pub/Sub — для потоковой передачи событий между окружениями
-
Secret Manager — для API-ключей и конфигурации
Вот так и должна работать гибридная платформа. Датацентр занимается высокообъёмными, экономически оптимизированными вычислениями. Облако предоставляет дефицитные GPU-ресурсы по требованию. А слой аутентификации невидим для разработчиков.
Масштабирование GPU-моста
Условия CEL становятся особенно мощными в этом контексте. Возможно, вы захотите, чтобы только ML-связанные неймспейсы имели доступ к Vertex AI:
attribute.namespace in ["ml-inference", "ml-training", "data-science"] &&
attribute.service_account.startsWith("ml-")
Или настроить разные уровни доступа:
# Неймспейс ml-inference получает доступ только для предсказаний
resource "google_project_iam_member" "ml_inference" {
project = "my-project"
role = "roles/aiplatform.user"
member = "principalSet://iam.googleapis.com/.../attribute.namespace/ml-inference"
}
# Неймспейс data-science получает полный доступ к Vertex AI (для экспериментов)
resource "google_project_iam_member" "data_science" {
project = "my-project"
role = "roles/aiplatform.admin"
member = "principalSet://iam.googleapis.com/.../attribute.namespace/data-science"
}
Командам on-prem-разработчиков не нужно знать о GCP IAM и думать о нём. Они деплоят в нужный неймспейс, добавляют лейбл — платформа делает всё остальное.
Почему это важно: свойства безопасности
Сравним два подхода:
Особого внимания заслуживает короткое время жизни токенов. Даже в худшем сценарии, когда токен каким-то образом был похищен, — он истечёт. Kubernetes ServiceAccount токены имеют настраиваемое время жизни, а токены доступа GCP, выданные STS, действительны один час. Сравните это с ключом сервисного аккаунта, который остаётся действительным до тех пор, пока кто-нибудь не вспомнит о его ротации — нередко годами.
Полная картина: инфраструктура как код
Всё решение кодифицировано в Terraform, управляющем как GCP, так и ресурсами Kubernetes:
workload-identity-federation/
├── providers.tf # Провайдеры Google + Kubernetes
├── locals.tf # Конфигурация (неймспейсы, ID проекта и т.д.)
├── gcp.tf # Пул идентификации, провайдер, IAM-привязки
└── kubernetes.tf # ConfigMap с конфигурацией учётных данных
Один terraform apply:
-
Создаёт Workload Identity Pool в GCP
-
Настраивает OIDC-провайдер с JWKS вашего кластера
-
Устанавливает IAM-привязки для разрешённых неймспейсов
-
Создаёт ConfigMap в каждом неймспейсе с конфигурацией учётных данных
В связке с политикой Kyverno получается полностью автоматизированный конвейер:
Новый неймспейс добавлен в список разрешённых
│
▼
Terraform создаёт ConfigMap в этом неймспейсе
│
▼
Разработчик деплоит с лейблом
│
▼
Kyverno автоматически внедряет учётные данные
│
▼
Под аутентифицируется в GCP через OIDC
│
▼
Приложение обращается к сервисам GCP
Никаких тикетов. Никаких запросов ключей. Никаких секретов для управления.
Делаем это реальным: доказательство концепции
Чтобы подтвердить работоспособность, я развернул демонстрацию с использованием vcluster — виртуального кластера Kubernetes, работающего внутри другого кластера Kubernetes. Это доказывает, что решение работает для любого кластера, а не только для GKE:
# vcluster.yaml
experimental:
docker:
nodes:
- name: worker-1
- name: worker-2
deploy:
cni:
flannel:
enabled: true
controlPlane:
distro:
k8s:
version: "v1.35.0"
Внутри vCluster — простой тестовый деплой:
apiVersion: apps/v1
kind: Deployment
metadata:
name: gcp-test
labels:
workload-identity-federation: "enabled"
spec:
replicas: 1
selector:
matchLabels:
app: gcp-test
template:
metadata:
labels:
app: gcp-test
spec:
containers:
- name: test
image: google/cloud-sdk:slim
command: ["sleep", "infinity"]
Заходим в под и проверяем:
$ kubectl exec -it gcp-test-xxx -- bash
# Внутри пода:
$ gcloud auth login --cred-file=$GOOGLE_APPLICATION_CREDENTIALS
Authenticated with external account credentials for: [principal://iam.googleapis.com/...]
$ gcloud secrets list --project=my-project
NAME CREATED
database-password 2024-01-15T10:30:00Z
api-key 2024-01-14T09:15:00Z
Никаких ключей. Никаких смонтированных секретов. Просто федерация идентификации выполняет свою работу.
Сложные моменты (и как мы их решили)
Получение JWKS для изолированных кластеров
Если OIDC-эндпоинт вашего кластера недоступен публично (большинство on-prem-кластеров именно таковы), нужно вручную экспортировать JWKS и загрузить в GCP:
kubectl get --raw /openid/v1/jwks > jwks.json
Этот файл нужно обновлять при ротации ключей подписи кластера. В нашей реализации есть периодическое задание, которое проверяет изменения ключей и обновляет конфигурацию Terraform.
Соответствие URL эмитента
Утверждение iss в Kubernetes-токене должно точно совпадать с URL эмитента, настроенным в OIDC-провайдере. Для кластеров с внутренним DNS:
issuer_uri = "https://kubernetes.default.svc.cluster.local"
Этот URL не обязан быть достижим из GCP — валидационные ключи предоставляет файл JWKS. Но он должен совпадать с тем, что записано в токене.
Диагностика сбоев обмена токенов
При сбоях аутентификации сообщения об ошибках могут быть загадочными. Типичные причины проблем:
Заключение: гибридное облако, сделанное правильно
После внедрения этого решения наши on-prem-нагрузки аутентифицируются в Google Cloud точно так же, как нагрузки GKE, — без единого долгоживущего учётного данного. Служба безопасности довольна (нет ключей для аудита), разработчики довольны (просто добавь лейбл), платформенная команда довольна (никаких тикетов по управлению учётными данными).
Вот как должна выглядеть гибридная платформа: не две разрозненные системы с хрупким мостом из секретов между ними, а единая плоскость идентификации, охватывающая оба окружения.
Экономическая реальность очевидна: мы не собираемся переносить всё в облако (датацентр у нас уже есть), и мы не можем ждать месяцами on-prem GPU (бизнесу нужны AI-возможности прямо сейчас). Гибридная модель — это не компромисс, а оптимальная архитектура. Используйте облако для того, чего у вас нет. Используйте on-prem для того, за что уже заплачено.
Но эта архитектура работает только тогда, когда два окружения могут безопасно общаться друг с другом. Ключи сервисных аккаунтов были старым ответом — хрупким, небезопасным и всё чаще запрещаемым политиками безопасности. Workload Identity Federation — новый ответ: криптографическая идентификация, краткоживущие учётные данные, тонкое управление доступом и нулевое управление ключами.
Все технологии не новы. OIDC присутствует в Kubernetes с версии 1.20. Workload Identity Federation в GCP существует уже несколько лет. Kyverno и Terraform — зрелые инструменты. Не хватало лишь связать их вместе в комплексное решение, которое разработчики могут внедрить с минимальными усилиями.
Если ваша организация запретила ключи сервисных аккаунтов (или должна это сделать) — это правильный путь вперёд. Если вы строите AI-приложения, которым нужны облачные GPU, или аналитические приложения, работающие on-prem и требующие доступа к данным в бакетах GCS, при этом желая сохранить основные вычислительные мощности on-prem — именно так их и нужно соединять. Ваши on-prem и облачные кластеры наконец могут стать тем, чем всегда должны были быть: расширениями друг друга.
Полная реализация доступна как Terraform-модуль с политиками Kyverno. Репозиторий GitHub: github.com/shkatara/hybrid-platform-gcp-workload-identity-federation