diff --git a/kubernetes/apps/ai/kustomization.yaml b/kubernetes/apps/ai/kustomization.yaml index 2894bf3..d019a33 100644 --- a/kubernetes/apps/ai/kustomization.yaml +++ b/kubernetes/apps/ai/kustomization.yaml @@ -8,4 +8,5 @@ components: resources: - ./namespace.yaml + - ./mcp-k8s/ks.yaml - ./toolhive/ks.yaml diff --git a/kubernetes/apps/ai/mcp-k8s/app/kustomization.yaml b/kubernetes/apps/ai/mcp-k8s/app/kustomization.yaml new file mode 100644 index 0000000..1aff7a8 --- /dev/null +++ b/kubernetes/apps/ai/mcp-k8s/app/kustomization.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./serviceaccount.yaml + - ./rbac.yaml + - ./mcpserver.yaml diff --git a/kubernetes/apps/ai/mcp-k8s/app/mcpserver.yaml b/kubernetes/apps/ai/mcp-k8s/app/mcpserver.yaml new file mode 100644 index 0000000..cbf9342 --- /dev/null +++ b/kubernetes/apps/ai/mcp-k8s/app/mcpserver.yaml @@ -0,0 +1,38 @@ +--- +# Kubernetes MCP Server: containers/kubernetes-mcp-server (Apache-2.0, Go, без обёрток над kubectl). +# Транспорт streamable-http — сервер сам отдаёт MCP по HTTP на --port 8080, путь /mcp, +# ровно то, что ожидает прокси ToolHive (обёртка над stdio не нужна). +# +# --read-only — штатный режим сервера: ни одной write-операции (create/update/delete). +# Это второй барьер после RBAC SA (см. rbac.yaml); третий — denied_resources в TOML, +# если понадобится спрятать ресурс от инструментов совсем. +# +# --toolsets core оставляет базовый набор: без helm (чарты ставит Flux, агенту там +# делать нечего), tekton, kubevirt, kiali и netobserv. +# +# ВАЖНО: без oidcConfigRef прокси ToolHive работает без аутентификации — любой, кто +# достучится до порта, получит доступ к API под правами SA ниже. Поэтому наружу сервер +# не выводим: внешний доступ появится вместе с OIDC после Authelia. +apiVersion: toolhive.stacklok.dev/v1beta1 +kind: MCPServer +metadata: + name: mcp-k8s +spec: + image: quay.io/containers/kubernetes_mcp_server:v0.0.66@sha256:6d650f4bd6ac303ad82713c997e73a2d001602f9bf17392c9b9a0e30e29c6423 + transport: streamable-http + args: + - --port + - "8080" + - --read-only + - --toolsets + - core + serviceAccount: mcp-k8s + permissionProfile: + type: builtin + name: network + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + memory: 256Mi diff --git a/kubernetes/apps/ai/mcp-k8s/app/rbac.yaml b/kubernetes/apps/ai/mcp-k8s/app/rbac.yaml new file mode 100644 index 0000000..3092ce9 --- /dev/null +++ b/kubernetes/apps/ai/mcp-k8s/app/rbac.yaml @@ -0,0 +1,47 @@ +--- +# Базовое чтение даём встроенной ролью view — её же рекомендует официальный гайд +# containers/kubernetes-mcp-server (docs/getting-started-kubernetes.md). view +# описывает namespaced-ресурсы и secrets в неё не входят, поэтому именно она, а не +# самодельный список с resources: ["*"]. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: mcp-k8s-view +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: view +subjects: + - kind: ServiceAccount + name: mcp-k8s + namespace: ai +--- +# view включает только namespaced-ресурсы, поэтому кластерные объекты и метрики +# добавляем отдельно. Ни secrets, ни записи здесь нет. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: mcp-k8s-cluster-read +rules: + - apiGroups: [""] + resources: ["nodes", "namespaces", "persistentvolumes"] + verbs: ["get", "list", "watch"] + - apiGroups: ["storage.k8s.io"] + resources: ["storageclasses", "volumeattachments", "csidrivers", "csistoragecapacities"] + verbs: ["get", "list", "watch"] + - apiGroups: ["metrics.k8s.io"] + resources: ["*"] + verbs: ["get", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: mcp-k8s-cluster-read +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: mcp-k8s-cluster-read +subjects: + - kind: ServiceAccount + name: mcp-k8s + namespace: ai diff --git a/kubernetes/apps/ai/mcp-k8s/app/serviceaccount.yaml b/kubernetes/apps/ai/mcp-k8s/app/serviceaccount.yaml new file mode 100644 index 0000000..29d62d5 --- /dev/null +++ b/kubernetes/apps/ai/mcp-k8s/app/serviceaccount.yaml @@ -0,0 +1,8 @@ +--- +# SA создаётся здесь намеренно: spec.serviceAccount в MCPServer означает «уже +# существующий» — если его не создать, оператор сделает свой и права выдавать +# будет некуда. +apiVersion: v1 +kind: ServiceAccount +metadata: + name: mcp-k8s diff --git a/kubernetes/apps/ai/mcp-k8s/ks.yaml b/kubernetes/apps/ai/mcp-k8s/ks.yaml new file mode 100644 index 0000000..005cb92 --- /dev/null +++ b/kubernetes/apps/ai/mcp-k8s/ks.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: mcp-k8s +spec: + interval: 1h + path: ./kubernetes/apps/ai/mcp-k8s/app + prune: true + sourceRef: + kind: GitRepository + name: flux-system + namespace: flux-system + targetNamespace: ai + wait: true + # Зависимость внутри своего ns, поэтому поле namespace не нужно (по CRD оно + # по умолчанию равно ns самого объекта-зависимости). + dependsOn: + - name: toolhive-operator