Внешний доступ к KServe
Раздел по ML-сценариям уточняется. Отдельные шаги и примеры могут быть дополнены. При возникновении вопросов обратитесь к команде платформы «Штурвал».
KServe входит в установку Kubeflow. Из Notebook в том же кластере модель доступна по внутреннему имени сервиса. Шаги этой страницы нужны, только если модель вызывают снаружи — другая система, CI, пользовательский сервис.
Снаружи адрес выглядит как: http://kserve.<commonName>/serving/<неймспейс-профиля>/<имя-модели>/v1/models/<имя-модели>:predict.
kubeflow-step-1 и kubeflow-step-2, которые создавали при установке.
Что должно получиться
- Knative Serving знает домен кластера и не преобразует тег образа в digest для перечисленных реестров. Если CA реестра отсутствует в доверенном хранилище контроллера Knative, ему монтируется корневой CA реестра.
- KServe публикует модели по одному хосту и пути
/serving/<неймспейс>/<имя>. - Внешний nginx принимает
kserve.<commonName>, подменяет Host на<commonName>и отдаёт трафик вistio-ingressgateway:80. - AuthorizationPolicy разрешает пути inference. Снаружи запрос несёт cookie сессии Kubeflow. Сама политика ограничивает пути, но не проверяет cookie.
Требования
Ресурсы Kubernetes создавайте в клиентском кластере. Проверочный запрос на шаге 6 выполняйте с внешнего хоста, для которого настраивается доступ.
- установлены
kubeflow-step-0…kubeflow-step-4, режим Авто, состояние Healthy; - известно
<commonName>—status.appliedCustomValues.controller.shturval.commonNameу SSCshturval-ingress-controller; - DNS
kserve.<commonName>указывает на Ingress клиентского кластера (тот же, что уkubeflow.<commonName>); - есть Profile Kubeflow и его неймспейс. Не используйте
defaultиkubeflow-systemкак рабочую область; - в реестре есть образ runtime модели (в примере —
sklearnserver); - известен
<хост-реестра>— FQDN реестра, откуда кластер тянет этот образ (часто FQDN зеркала).
Подставляйте свои значения:
| Плейсхолдер | Откуда взять |
|---|---|
<commonName> |
SSC shturval-ingress-controller, controller.shturval.commonName |
<хост-реестра> |
хост из URL образа runtime, без схемы и пути |
<неймспейс-профиля> |
имя Profile / неймспейса команды |
<тег> |
тег образа sklearnserver в вашем реестре |
<URI-модели> |
URI артефакта модели (S3 или иной storage, который видит кластер) |
Куда подставлять <commonName>:
| Где | Значение |
|---|---|
Knative configDomain |
<commonName> |
KServe ingressDomain |
<commonName> |
Ingress host |
kserve.<commonName> |
Ingress upstream-vhost |
<commonName> |
В configDomain и ingressDomain не указывайте kserve.<commonName>: nginx как раз подменяет Host на голый <commonName>, и VirtualService KServe должен совпасть с этим Host.
Шаг 1. Корневой CA зеркала
Образы моделей кластер скачивает с зеркала. Соединение идёт по HTTPS, поэтому кластер должен доверять сертификату зеркала.
Если сертификат зеркала выпущен удостоверяющим центром, которому контроллер Knative не доверяет, подключите корневой CA зеркала.
Если зеркало и кластер уже используют один и тот же CA, шаг можно пропустить: не создавайте ConfigMap, а блок controller в SSP на шаге 2 удалите.
Запросите корневой сертификат у администратора зеркала. Для диагностики можно получить цепочку сертификатов командой ниже, подставив FQDN зеркала:
echo | openssl s_client -showcerts -servername <хост-зеркала> -connect <хост-зеркала>:443 2>/dev/null
Не определяйте корневой сертификат только по его положению в выводе: сервер может не передать корневой CA. Перед добавлением проверьте subject, issuer и отпечаток сертификата. Сохраните подтверждённый корневой сертификат в файл, например mirror-root-ca.pem.
Неймспейс knative-serving появляется после установки Kubeflow. Создайте в нём ConfigMap с сертификатом.
- Откройте клиентский кластер.
- Импортируйте ConfigMap. Вставьте текст сертификата вместо плейсхолдера:
apiVersion: v1
kind: ConfigMap
metadata:
name: registry-root-ca
namespace: knative-serving
data:
ca-bundle.pem: |
-----BEGIN CERTIFICATE-----
<PEM-корневого-CA-зеркала>
-----END CERTIFICATE-----
- Неймспейс
knative-serving→ Config Maps: естьregistry-root-ca, ключca-bundle.pem.
kubectl --kubeconfig "${KUBECONFIG_CLIENT}" -n knative-serving create configmap registry-root-ca \
--from-file=ca-bundle.pem=<путь>/mirror-root-ca.pem
kubectl --kubeconfig "${KUBECONFIG_CLIENT}" -n knative-serving get configmap registry-root-ca
Параметры манифеста
| Параметр | Зачем нужен |
|---|---|
name: registry-root-ca |
Имя ConfigMap. На него ссылается SSP на шаге 2 (controller.caBundle.configMapName). |
namespace: knative-serving |
Неймспейс Knative Serving. Появляется после установки Kubeflow. |
ключ ca-bundle.pem |
Корневой сертификат зеркала в PEM. |
Шаг 2. SSP для Knative Serving
SSP задаёт домен кластера, список реестров без проверки тега и (если выполняли шаг 1) том с CA.
- Откройте клиентский кластер.
- Импортируйте SSP:
apiVersion: ops.shturval.tech/v1beta2
kind: ShturvalServicePatch
metadata:
name: knative-for-kserve-external
spec:
patchOrder: 100
shturvalServiceConfigName: kubeflow-step-1
customvalues:
knative-serving1:
configDomain:
domains:
- "<commonName>": {}
configDeployment:
extraArgs:
registries-skipping-tag-resolving: "r.shturval.tech,<хост-реестра>,kind.local,ko.local,dev.local"
controller:
caBundle:
configMapName: registry-root-ca
key: ca-bundle.pem
path: ca-bundle.pem
mountPath: /etc/ssl/custom-certs
- Установленные сервисы →
kubeflow-step-1→ вкладка ShturvalServicePatch: естьknative-for-kserve-external. Дождитесь Healthy у сервиса.
kubectl --kubeconfig "${KUBECONFIG_CLIENT}" apply -f - <<'EOF'
apiVersion: ops.shturval.tech/v1beta2
kind: ShturvalServicePatch
metadata:
name: knative-for-kserve-external
spec:
patchOrder: 100
shturvalServiceConfigName: kubeflow-step-1
customvalues:
knative-serving1:
configDomain:
domains:
- "<commonName>": {}
configDeployment:
extraArgs:
registries-skipping-tag-resolving: "r.shturval.tech,<хост-реестра>,kind.local,ko.local,dev.local"
controller:
caBundle:
configMapName: registry-root-ca
key: ca-bundle.pem
path: ca-bundle.pem
mountPath: /etc/ssl/custom-certs
EOF
Подставьте <commonName> и <хост-реестра> перед применением манифеста. Если ConfigMap не создавали, удалите блок controller из манифеста.
kubectl --kubeconfig "${KUBECONFIG_CLIENT}" get shturvalservicepatch knative-for-kserve-external
kubectl --kubeconfig "${KUBECONFIG_CLIENT}" get ssc kubeflow-step-1
Параметры манифеста
| Параметр | Зачем нужен |
|---|---|
shturvalServiceConfigName: kubeflow-step-1 |
SSP накладывается на SSC kubeflow-step-1, сам SSC при установке не правится. |
configDomain.domains |
Knative принимает запросы на <commonName> после подмены Host. |
registries-skipping-tag-resolving |
Knative не преобразует тег образа в digest для этих реестров. Образ по-прежнему скачивает среда выполнения контейнеров на узле. Оставьте r.shturval.tech и служебные kind.local,ko.local,dev.local, добавьте свой <хост-реестра>. |
controller.caBundle |
Подключает ConfigMap registry-root-ca с CA зеркала. Уберите весь блок controller, если шаг 1 пропускали. |
Шаг 3. SSP для KServe
SSP задаёт pathTemplate и ingressDomain, чтобы к разным моделям был доступ по одному хосту и разным путям. Подставьте <commonName> только в ingressDomain. Остальные поля оставьте как в примере: gateway Istio, класс istio, схема http, путь /serving/{{ "{{ .Namespace }}" }}/{{ "{{ .Name }}" }}.
- Откройте клиентский кластер.
- Импортируйте SSP:
apiVersion: ops.shturval.tech/v1beta2
kind: ShturvalServicePatch
metadata:
name: kserve-path-template
spec:
patchOrder: 100
shturvalServiceConfigName: kubeflow-step-2
customvalues:
kserve:
inferenceserviceConfig:
ingress: |-
{
"enableGatewayApi": false,
"kserveIngressGateway": "istio-system/istio-ingressgateway",
"ingressGateway" : "kubeflow/kubeflow-gateway",
"localGateway" : "knative-serving/knative-local-gateway",
"localGatewayService" : "knative-local-gateway.istio-system.svc.cluster.local",
"ingressDomain" : "<commonName>",
"ingressClassName" : "istio",
"domainTemplate": "{{ "{{ .Name }}-{{ .Namespace }}.{{ .IngressDomain }}" }}",
"pathTemplate": "{{ "/serving/{{ .Namespace }}/{{ .Name }}" }}",
"urlScheme": "http",
"disableIstioVirtualHost": false,
"disableIngressCreation": false
}
- Установленные сервисы →
kubeflow-step-2→ вкладка ShturvalServicePatch: естьkserve-path-template. Дождитесь Healthy.
kubectl --kubeconfig "${KUBECONFIG_CLIENT}" apply -f - <<'EOF'
apiVersion: ops.shturval.tech/v1beta2
kind: ShturvalServicePatch
metadata:
name: kserve-path-template
spec:
patchOrder: 100
shturvalServiceConfigName: kubeflow-step-2
customvalues:
kserve:
inferenceserviceConfig:
ingress: |-
{
"enableGatewayApi": false,
"kserveIngressGateway": "istio-system/istio-ingressgateway",
"ingressGateway" : "kubeflow/kubeflow-gateway",
"localGateway" : "knative-serving/knative-local-gateway",
"localGatewayService" : "knative-local-gateway.istio-system.svc.cluster.local",
"ingressDomain" : "<commonName>",
"ingressClassName" : "istio",
"domainTemplate": "{{ "{{ .Name }}-{{ .Namespace }}.{{ .IngressDomain }}" }}",
"pathTemplate": "{{ "/serving/{{ .Namespace }}/{{ .Name }}" }}",
"urlScheme": "http",
"disableIstioVirtualHost": false,
"disableIngressCreation": false
}
EOF
Подставьте <commonName> до apply.
kubectl --kubeconfig "${KUBECONFIG_CLIENT}" get shturvalservicepatch kserve-path-template
kubectl --kubeconfig "${KUBECONFIG_CLIENT}" get ssc kubeflow-step-2
Параметры манифеста
| Параметр | Зачем нужен |
|---|---|
shturvalServiceConfigName: kubeflow-step-2 |
SSP накладывается на SSC kubeflow-step-2. |
ingressDomain |
Домен кластера <commonName>. Не указывайте kserve.<commonName>. |
pathTemplate |
Путь к модели: /serving/<неймспейс>/<имя>. По нему собирается внешний URL. |
domainTemplate |
Шаблон хоста KServe. Оставьте как в примере. |
urlScheme: http |
Внешний Ingress в примере без TLS. |
ingressClassName: istio |
Внутри кластера трафик идёт через Istio. |
kserveIngressGateway / ingressGateway / localGateway |
Шлюзы Istio и Knative. Оставьте как в примере. |
enableGatewayApi: false |
Gateway API не используется. |
disableIstioVirtualHost / disableIngressCreation |
VirtualService KServe создаётся. Оставьте false. |
Шаг 4. Ingress и AuthorizationPolicy
Два объекта в неймспейсе istio-system:
- Ingress
kserve-external— внешняя точка входа nginx на хостkserve.<commonName>; - AuthorizationPolicy
kserve-inference-access— разрешает на подах модели служебные пути inference.
В примере внешний Ingress без TLS, схема http. UI Kubeflow при этом остаётся на https://kubeflow.<commonName>/.
- Откройте клиентский кластер.
- Замените
<commonName>в манифесте и загрузите оба объекта одним файлом: импорт манифестов → вставьте YAML → Проверить → Сохранить.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kserve-external
namespace: istio-system
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/upstream-vhost: "<commonName>"
spec:
ingressClassName: nginx
rules:
- host: kserve.<commonName>
http:
paths:
- path: /serving/
pathType: Prefix
backend:
service:
name: istio-ingressgateway
port:
number: 80
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: kserve-inference-access
namespace: istio-system
labels:
app.kubernetes.io/part-of: kserve-integration
spec:
action: ALLOW
rules:
- to:
- operation:
paths:
- /v1/models/*
- /v2/models/*
- /v2/health/*
- Неймспейс
istio-system→ Сеть → Ingresses: есть Ingresskserve-external, хостkserve.<commonName>, путь/serving/. - Администрирование → Кастомные ресурсы → группа
security.istio.io→ AuthorizationPolicy →kserve-inference-accessв неймспейсеistio-system.
Подставьте <commonName> и выполните:
kubectl --kubeconfig "${KUBECONFIG_CLIENT}" apply -f - <<'EOF'
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kserve-external
namespace: istio-system
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/upstream-vhost: "<commonName>"
spec:
ingressClassName: nginx
rules:
- host: kserve.<commonName>
http:
paths:
- path: /serving/
pathType: Prefix
backend:
service:
name: istio-ingressgateway
port:
number: 80
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: kserve-inference-access
namespace: istio-system
labels:
app.kubernetes.io/part-of: kserve-integration
spec:
action: ALLOW
rules:
- to:
- operation:
paths:
- /v1/models/*
- /v2/models/*
- /v2/health/*
EOF
kubectl --kubeconfig "${KUBECONFIG_CLIENT}" get ingress kserve-external -n istio-system
kubectl --kubeconfig "${KUBECONFIG_CLIENT}" get authorizationpolicy kserve-inference-access -n istio-system
Параметры манифестов
| Параметр | Ресурс | Зачем нужен |
|---|---|---|
host: kserve.<commonName> |
Ingress | Имя, которое указывают снаружи. DNS kserve.<commonName> должен указывать на Ingress клиентского кластера. |
path: /serving/ |
Ingress | KServe в Serverless ищет модель по пути /serving/<неймспейс>/<имя>. Срезать префикс нельзя — иначе модель не найдётся. |
backend.service: istio-ingressgateway:80 |
Ingress | nginx не ходит к поду модели напрямую: сначала Istio, уже оттуда — к KServe. |
upstream-vhost: "<commonName>" |
Ingress | Подменяет Host kserve.<commonName> на голый <commonName>, чтобы сработал VirtualService, который создаёт сам KServe. |
proxy-read-timeout / proxy-send-timeout: 3600 |
Ingress | Длинный ответ модели (в том числе потоковая выдача) не обрывается через минуту. |
ingressClassName: nginx |
Ingress | Заявку обрабатывает shturval-ingress-controller, тот же класс, что у Kubeflow. |
action: ALLOW и пути /v1/models/*, /v2/models/*, /v2/health/* |
AuthorizationPolicy | Предиктор принимает predict/explain и health. Политика в istio-system действует на весь mesh. |
Источник (from.source) не задан |
AuthorizationPolicy | Activator Knative ходит к предиктору без mTLS. Правило с проверкой источника такой запрос бы отбросило. Эта политика не проверяет cookie и сама по себе не аутентифицирует внешний запрос. |
Шаг 5. Тестовая модель
Создайте InferenceService в неймспейсе Profile. minReplicas: 1 — чтобы ревизия не ушла в ноль до проверки. Образ runtime возьмите из своего реестра.
Укажите <URI-модели> — адрес совместимого артефакта модели в доступном кластеру хранилище. Образ среды выполнения задаётся отдельно в поле image.
- Откройте клиентский кластер.
- Импортируйте манифест. Подставьте неймспейс, образ и URI:
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: sklearn-iris
namespace: <неймспейс-профиля>
spec:
predictor:
minReplicas: 1
sklearn:
image: <хост-реестра>/kubeflow/kserve/sklearnserver:<тег>
storageUri: "<URI-модели>"
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
- Администрирование → Кастомные ресурсы → группа
serving.kserve.io→ InferenceService: уsklearn-irisсостояние Ready.
kubectl --kubeconfig "${KUBECONFIG_CLIENT}" apply -f - <<'EOF'
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: sklearn-iris
namespace: <неймспейс-профиля>
spec:
predictor:
minReplicas: 1
sklearn:
image: <хост-реестра>/kubeflow/kserve/sklearnserver:<тег>
storageUri: "<URI-модели>"
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
EOF
Дождитесь Ready:
kubectl --kubeconfig "${KUBECONFIG_CLIENT}" get inferenceservice sklearn-iris -n <неймспейс-профиля> -w
Статус True / Ready означает, что модель поднята внутри кластера. Это ещё не проверка внешнего HTTP.
Если ревизия не переходит в состояние Ready, проверьте события пода предиктора: ошибки ImagePull, проверки сертификата x509 и доступа к хранилищу. Сверьте <хост-реестра> со списком registries-skipping-tag-resolving и проверьте наличие ConfigMap с CA, если контроллер Knative не доверяет CA реестра.
Параметры манифеста
| Параметр | Зачем нужен |
|---|---|
name: sklearn-iris |
Имя модели. Входит во внешний путь /serving/…/sklearn-iris/…. |
namespace |
Неймспейс Profile, не default. |
minReplicas: 1 |
Ревизия не уходит в ноль до проверки. |
image |
Образ runtime из зеркала. |
storageUri |
Откуда runtime читает артефакт модели. |
resources |
Запросы и лимиты CPU и памяти для тестового предиктора. |
Шаг 6. Проверьте внешний вызов
Запрос к модели — это POST, а не открытие адреса в браузере. Cookie сессии выдаёт oauth2-proxy после входа в UI Kubeflow.
- В браузере откройте
https://kubeflow.<commonName>/и войдите. - Откройте инструменты разработчика → Cookies (Chrome: Application → Cookies, Firefox: Storage → Cookies) для хоста
kubeflow.<commonName>. - Скопируйте значение cookie
oauth2_proxy_kubeflow.
export COOKIE='oauth2_proxy_kubeflow=<значение-cookie>'
curl -sS -D - -X POST \
-H "Cookie: ${COOKIE}" \
-H 'Content-Type: application/json' \
-d '{"instances": [[6.8, 2.8, 4.8, 1.4]]}' \
"http://kserve.<commonName>/serving/<неймспейс-профиля>/sklearn-iris/v1/models/sklearn-iris:predict"
Ожидается HTTP 200 и поле predictions в теле ответа. Значение зависит от модели, указанной в <URI-модели>.
Если ответа нет:
| Симптом | Что проверить |
|---|---|
| Нет маршрута, таймаут | DNS kserve.<commonName>, Ingress kserve-external |
| 404 | pathTemplate, путь /serving/<неймспейс>/<имя>/v1/models/<имя>:predict, upstream-vhost равен <commonName> |
| 401 / 403 | cookie oauth2_proxy_kubeflow, AuthorizationPolicy |
| 503, пустой предиктор | InferenceService ещё не Ready, minReplicas |
Связанные страницы: