在上一章中,我们讨论了多智能体系统的测试、调试与故障排查。我们探索了诸如幻觉(hallucination)与工具误用(tool misuse)等常见失效模式、监控与可观测性(instrumentation and observability)策略、协调失败(coordination failures)的调试技巧,以及通过冗余与优雅降级来构建韧性(resilience)的设计方法。我们全程以 MAKDO 作为具体示例来说明这些概念。
现在,是时候把 MAKDO 真正部署起来了。但把多智能体系统跑在生产环境会带来新的挑战:部署架构、服务发现、网络与安全。你如何把智能体部署到多个环境?它们如何安全通信?你如何管理凭证与访问控制?
在本章中,我们将在一个逼真的、类生产(production-like)的环境中部署 MAKDO。我们会使用 Kubernetes in Docker(KinD)创建两个 Kubernetes 集群:控制集群(control cluster) 运行 MAKDO 本身;工作集群(worker cluster) 是 MAKDO 监控与管理的目标集群。k8s-ai(一个 agent-to-agent server)运行在工作集群上,通过 Agent-to-Agent(A2A)协议为 MAKDO 提供诊断与修复问题的能力。这个架构模拟了真实生产场景:管理系统与其管理的工作负载分离运行。
我们会依次完成:搭建两个集群、把 MAKDO 组件部署到控制集群、把 k8s-ai 部署到工作集群、配置安全的跨集群通信、以及管理服务发现与访问控制。到本章结束时,你将拥有一个完整可用的多智能体系统,它能管理另一个独立的 Kubernetes 集群,并可自动检测与修复问题。
本章涵盖的关键点包括:
- 搭建用于多智能体部署的双 KinD 集群
- 将 MAKDO 部署到控制集群
- 将 k8s-ai 部署到工作集群
- 启用集群间的安全通信
技术要求
请按照以下说明操作:
github.com/PacktPublis… 。
搭建用于多智能体部署的双 KinD 集群(Setting up dual KinD clusters for multi-agent deployment)
让我们构建一个类生产部署:MAKDO 运行在一个集群上,并管理另一个集群。这种隔离在真实环境中非常关键,用于将管理基础设施与生产工作负载隔离开来。它可以减少爆炸半径(blast radius),并且即使 MAKDO 不可用,生产系统也能继续运行。此外,如果生产集群发生资源耗尽或类似问题,MAKDO 仍能分析并可能修复它,因为 MAKDO 不受同样问题影响。
安装 KinD 与前置依赖(Installing KinD and prerequisites)
在创建集群之前,你需要安装 Docker、kubectl 和 KinD。我们不会逐个工具讲解安装步骤(不同平台差异大且会随时间变化),而是提供一个验证脚本来检查环境是否就绪。
首先,如果你还没有克隆本书仓库,请先克隆:
git clone git@github.com:PacktPublishing/Design-Multi-Agent-AI-Systems-using-MCP-and-A2A.git
使用官方文档安装所需工具:
- Docker:docs.docker.com/get-docker/
- kubectl:kubernetes.io/docs/tasks/…
- KinD:kind.sigs.k8s.io/docs/user/q…
安装完成后,运行我们的前置依赖检查脚本来验证一切正常:
cd ch11
chmod +x check-prerequisites.sh
./check-prerequisites.sh
该脚本会检查:Docker 是否已安装并运行、kubectl 是否可用、KinD 是否已安装(建议 0.20.0 或更高版本)、磁盘空间是否充足(两个集群约需 ~10 GB)、Docker 内存分配是否至少 4 GB,以及是否存在会与本次设置冲突的 KinD 集群。
如果脚本报告缺少任何依赖,请使用上述链接安装后重新运行脚本。看到 All prerequisites met! 后,就可以开始创建集群了。
创建控制集群配置(Creating the control cluster configuration)
控制集群承载 MAKDO 智能体。它需要被配置为允许外部访问 MAKDO 的 API 端点。KinD 使用 YAML 配置文件定义集群拓扑与网络。
创建 control-cluster.yaml:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: makdo-control
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "cluster-role=control"
extraPortMappings:
# Port for accessing MAKDO API/dashboard if needed
- containerPort: 30080
hostPort: 8080
protocol: TCP
# Port for MAKDO health check endpoint
- containerPort: 30090
hostPort: 9090
protocol: TCP
关键配置点如下:
name:makdo-control标识该集群。KinD 会用这个名字创建 Docker 容器并生成 kubectl context。node-labels:标签用于识别 pod 运行在哪个集群上,便于调试与监控。extraPortMappings:将集群端口暴露到宿主机。8080 映射到容器端口 30080(Kubernetes NodePort 范围),9090 映射到 30090 用于健康检查。这样你可以从本地访问 MAKDO 服务,或在需要时允许工作集群访问 MAKDO。
接下来创建控制集群:
kind create cluster --config control-cluster.yaml
这通常需要 1–2 分钟。KinD 会下载 Kubernetes 节点镜像(若未缓存)、创建 Docker 容器并初始化 Kubernetes。完成后你会看到类似输出:
Creating cluster "makdo-control" ...
✓ Ensuring node image (kindest/node:v1.33.1) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-makdo-control"
示例中的 Kubernetes 版本(如 v1.33.1)取决于你安装的 KinD 版本;KinD 会自动选择兼容的 Kubernetes 版本。
验证集群:
kubectl cluster-info --context kind-makdo-control
kubectl get nodes --context kind-makdo-control
期望输出类似:
Kubernetes control plane is running at https://127.0.0.1:60905
CoreDNS is running at https://127.0.0.1:60905/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
NAME STATUS ROLES AGE VERSION
makdo-control-control-plane Ready control-plane 1m v1.33.1
一个节点处于 Ready 状态。集群已启动,但还是空的。
创建工作集群配置(Creating the worker cluster configuration)
工作集群运行实际业务工作负载。MAKDO 监控这个集群,并在问题出现时修复它。该集群需要暴露 k8s-ai 的 MCP server,以便 MAKDO 与其通信。
创建 worker-cluster.yaml:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: makdo-worker
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "cluster-role=worker"
extraPortMappings:
# Port for k8s-ai MCP server
- containerPort: 30100
hostPort: 8100
protocol: TCP
# Port for exposing problematic workloads (for testing)
- containerPort: 30200
hostPort: 8200
protocol: TCP
与控制集群相比,关键差异是:
name:makdo-worker与控制集群区分开来。node-labels:cluster-role=worker标识其为被管理集群。Port 8100:映射到容器端口30100,k8s-ai 的 A2A server 将运行在这里。MAKDO 的 Analyzer 与 Fixer 智能体将通过该端口使用 A2A 协议调用 k8s-ai。Port 8200:用于测试工作负载。我们会部署一些故障 pod,供 MAKDO 检测与修复。
创建工作集群:
kind create cluster --config worker-cluster.yaml
此时你有两个独立的 Kubernetes 集群,它们以 Docker 容器形式运行:
kind get clusters
期望输出(你也可能还有其它集群):
makdo-control
makdo-worker
检查两个 kubectl context:
kubectl config get-contexts | grep makdo
示例输出:
kind-makdo-control kind-makdo-control kind-makdo-control
* kind-makdo-worker kind-makdo-worker kind-makdo-worker
* 表示当前激活的 context。你可以这样切换:
# Use control cluster
kubectl config use-context kind-makdo-control
# Use worker cluster
kubectl config use-context kind-makdo-worker
两个集群是隔离的。不做显式网络配置时,一个集群内的 pod 无法直接访问另一个集群的 pod。我们将在下一步完成所需配置。
配置集群网络与端口转发(Setting up cluster networking and port forwarding)
KinD 集群作为 Docker 容器运行在宿主机上。它们处于同一个 Docker 网络中,这意味着它们可以通过 Docker 网络互相访问。但集群内部的服务要对外可达,需要额外配置。
首先,找出两个集群使用的 Docker 网络:
docker network ls | grep kind
示例输出:
aadf3e99800d kind bridge local
两个集群容器都连接在该 kind 网络上:
docker network inspect kind | grep -A 3 "makdo"
这会显示两个集群容器的 IP 地址。不过我们不希望硬编码 IP,而是使用前面配置好的端口映射。
在跨集群通信中,MAKDO(控制集群)需要访问 k8s-ai(工作集群)。由于两个集群运行在同一台宿主机上,MAKDO 可以通过 host.docker.internal:8100 访问 k8s-ai。这个特殊 DNS 名称会在 Docker 容器内解析到宿主机。
验证端口映射是否生效。检查映射端口上是否有服务在监听:
# Control cluster ports
lsof -i :8080 2>/dev/null || echo "Port 8080: not yet in use"
lsof -i :9090 2>/dev/null || echo "Port 9090: not yet in use"
# Worker cluster ports
lsof -i :8100 2>/dev/null || echo "Port 8100: not yet in use"
lsof -i :8200 2>/dev/null || echo "Port 8200: not yet in use"
此时这些端口还未被占用,因为我们还没有部署任何服务。端口映射已配置,但只有当服务绑定到集群内端口(30080、30090、30100、30200)时才会“激活”。
检查 Docker 容器是否运行并具备这些端口映射:
docker ps --format "table {{.Names}}\t{{.Ports}}" | grep makdo
示例输出:
makdo-worker-control-plane 127.0.0.1:61462->6443/tcp, 0.0.0.0:8100->30100/tcp, 0.0.0.0:8200->30200/tcp
makdo-control-control-plane 127.0.0.1:60905->6443/tcp, 0.0.0.0:8080->30080/tcp, 0.0.0.0:9090->30090/tcp
你可以看到两个容器及其端口映射。6443 是 Kubernetes API Server;我们的自定义映射(8080→30080、8100→30100 等)已就绪,等待服务使用。
验证跨集群连通性(Verifying cross-cluster connectivity)
在部署 MAKDO 与 k8s-ai 之前,先确认两个集群健康,并具备潜在的互通条件。
检查控制集群健康状况:
kubectl cluster-info --context kind-makdo-control
kubectl get nodes --context kind-makdo-control
示例输出:
Kubernetes control plane is running at https://127.0.0.1:60905
CoreDNS is running at https://127.0.0.1:60905/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
NAME STATUS ROLES AGE VERSION
makdo-control-control-plane Ready control-plane 15m v1.33.1
再检查工作集群:
kubectl cluster-info --context kind-makdo-worker
kubectl get nodes --context kind-makdo-worker
示例输出:
Kubernetes control plane is running at https://127.0.0.1:61462
CoreDNS is running at https://127.0.0.1:61462/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
NAME STATUS ROLES AGE VERSION
makdo-worker-control-plane Ready control-plane 12m v1.33.1
两个集群都显示 control plane 正常运行,并且一个节点处于 Ready 状态。
测试每个集群内部的基础网络(DNS):
# Test DNS in control cluster
kubectl run dns-test --image=busybox:latest --restart=Never --rm -i --context kind-makdo-control --command -- \
nslookup kubernetes.default.svc.cluster.local
# Test DNS in worker cluster
kubectl run dns-test --image=busybox:latest --restart=Never --rm -i --context kind-makdo-worker --command -- \
nslookup kubernetes.default.svc.cluster.local
两者都应成功解析 Kubernetes service DNS 名称。
对于跨集群通信,MAKDO 不会直接访问工作集群中的 pod,而是通过暴露端口访问 k8s-ai 的 A2A server。我们会在 k8s-ai 部署完成后测试。现在两个集群已为下一步做好准备。
在工作集群部署测试负载(Deploying a test workload to the worker cluster)
为了验证 MAKDO 是否工作正确,我们需要一些可供其检测与修复的“故障负载”。让我们在工作集群部署一个测试命名空间,其中包含三个 pod:一个会崩溃、一个会拉取镜像失败、一个正常运行。
创建 test-workload.yaml:
apiVersion: v1
kind: Namespace
metadata:
name: test-workload
---
# A pod that will crashloop - missing required environment variable
apiVersion: v1
kind: Pod
metadata:
name: crashloop-pod
namespace: test-workload
labels:
app: crashloop-test
spec:
containers:
- name: app
image: busybox:latest
command: ["sh", "-c"]
args:
- |
if [ -z "$REQUIRED_VAR" ]; then
echo "ERROR: REQUIRED_VAR not set"
exit 1
fi
echo "Running successfully"
sleep 3600
---
# A pod with an image that doesn't exist
apiVersion: v1
kind: Pod
metadata:
name: imagepull-pod
namespace: test-workload
labels:
app: imagepull-test
spec:
containers:
- name: app
image: nonexistent-registry.example.com/fake-image:v1.0.0
command: ["sleep", "3600"]
---
# A healthy pod for comparison
apiVersion: v1
kind: Pod
metadata:
name: healthy-pod
namespace: test-workload
labels:
app: healthy-test
spec:
containers:
- name: app
image: nginx:alpine
ports:
- containerPort: 80
部署到工作集群:
kubectl apply -f test-workload.yaml --context kind-makdo-worker
查看 pod 状态:
kubectl get pods -n test-workload --context kind-makdo-worker
示例输出:
NAME READY STATUS RESTARTS AGE
crashloop-pod 0/1 CrashLoopBackOff 7 (27s ago) 11m
healthy-pod 1/1 Running 0 11m
imagepull-pod 0/1 ImagePullBackOff 0 11m
完美!我们得到如下结果:
crashloop-pod:CrashLoopBackOff(因为缺少环境变量而立即退出)healthy-pod:Running(nginx 正常启动)imagepull-pod:ImagePullBackOff(镜像不存在)
观察 crashloop-pod 反复重启:
kubectl get pods -n test-workload --context kind-makdo-worker --watch
按 Ctrl + C 结束 watch。这些失败的 pod 正是 MAKDO 的理想目标:Analyzer 会检测并分类失败类型,Fixer 可能会尝试修复。
查看 crashloop pod 的日志,确认失败原因:
kubectl logs crashloop-pod -n test-workload --context kind-makdo-worker
示例输出:
ERROR: REQUIRED_VAR not set
工作集群现在已经具备故障工作负载。接下来,我们会把 MAKDO 部署到控制集群、把 k8s-ai 部署到工作集群,然后观察 MAKDO 如何自动检测并处理这些问题。
将 MAKDO 部署到控制集群(Deploying MAKDO to the control cluster)
现在我们已经启动了两个集群,并且工作集群中的测试工作负载处于失败状态,是时候部署 MAKDO 了。MAKDO 将以控制集群中的一个 Deployment 形式运行,并通过 k8s-ai 的 A2A server 监控工作集群。
准备 MAKDO 配置(Preparing the MAKDO configuration)
MAKDO 需要配置文件来知道要监控哪些集群、如何连接到 k8s-ai,以及如何通过 Slack 通信。我们会根据“双集群”部署方式对示例配置做适配。
细节请参考:
github.com/PacktPublis…
下面是关键配置点:
- Clusters:只列出工作集群。MAKDO 不需要监控它自己的控制集群。
kind-makdo-worker这个 context 与 KinD 创建的 kubectl context 一致。 - kubeconfig_path:设置为
/root/.kube/config,因为 kubeconfig 将被挂载到 MAKDO 容器内的这个路径。我们会通过 volume 挂载 kubeconfig。 - k8s_ai.base_url:使用
host.docker.internal:8100去访问工作集群上的 k8s-ai。这个特殊 DNS 名称在 Docker 容器内会解析到宿主机。8100是我们为 k8s-ai 的 A2A server 映射的端口。 - Environment variables:API key 与 token 使用
${VAR}语法。MAKDO 会从环境变量读取这些值,我们将通过 Kubernetes Secret 提供。 - monitoring.check_interval:设置为 120 秒(2 分钟)。MAKDO 每两分钟轮询一次工作集群,以便快速发现问题。
接下来我们需要智能体配置文件。MAKDO 为每个智能体的 prompt 与工具配置使用独立的 YAML 文件。
创建智能体配置文件(Agent configuration files)
创建 makdo-config/coordinator.yaml:
name: "MAKDO Coordinator"
description: |
You are the MAKDO Coordinator, the central orchestrator for multi-cluster Kubernetes operations.
Your responsibilities:
1. Periodically check cluster health across all registered clusters
2. Coordinate between Analyzer, Fixer, and Slack Bot agents
3. Escalate critical issues to appropriate agents
4. Maintain operational awareness of all clusters
When asked to perform health checks:
1. Use the Analyzer agent to assess cluster health
2. If issues are found, use the Slack Bot to notify users
3. For critical issues, use the Fixer agent if remediation is needed
4. Always report status back to the Slack channel
Be proactive, clear, and focused on cluster reliability.
model_id: "gpt-4o"
sub_agents:
- name: "MAKDO_Analyzer"
config_path: "src/makdo/agents/analyzer.yaml"
- name: "MAKDO_Fixer"
config_path: "src/makdo/agents/fixer.yaml"
- name: "MAKDO_Slack_Bot"
config_path: "src/makdo/agents/slack.yaml"
history:
max_messages: 10
summarize_after: 5
创建 makdo-config/analyzer.yaml:
name: "MAKDO Analyzer"
description: |
You are the MAKDO Analyzer agent. Your role is to diagnose Kubernetes cluster health issues.
Your responsibilities:
1. Check cluster resource health (pods, deployments, services, nodes)
2. Identify problems like CrashLoopBackOff, ImagePullBackOff, resource exhaustion
3. Analyze logs and events to understand root causes
4. Provide clear diagnostic reports to the Coordinator
When analyzing issues:
1. Always check ALL namespaces, not just default
2. Look at pod status, events, and logs
3. Identify patterns across multiple failing pods
4. Classify issues by severity (critical, warning, info)
5. Provide actionable recommendations
Use the k8s diagnostic tools available to you through the A2A protocol.
model_id: "gpt-4o"
tools:
- name: "kubernetes_resource_health"
type: "a2a"
endpoint: "http://host.docker.internal:8100"
description: "Check health of Kubernetes resources (pods, deployments, services, nodes)"
- name: "kubernetes_diagnose_issue"
type: "a2a"
endpoint: "http://host.docker.internal:8100"
description: "Diagnose specific Kubernetes issues by analyzing events, logs, and resource state"
history:
max_messages: 8
summarize_after: 4
创建 makdo-config/fixer.yaml:
name: "MAKDO Fixer"
description: |
You are the MAKDO Fixer agent. Your role is to remediate Kubernetes cluster issues safely.
Your responsibilities:
1. Apply fixes for diagnosed issues from the Analyzer
2. Always prioritize safety and avoid destructive actions
3. Request approval for critical operations
4. Verify fixes were successful after applying
Common remediation actions:
- Restart failing pods
- Update pod configurations to fix missing env vars
- Scale deployments
- Apply configuration changes
CRITICAL SAFETY RULES:
1. Never delete resources without explicit approval
2. Always verify before applying changes
3. If unsure, ask for human approval
4. Test fixes on non-production resources first when possible
Use the k8s remediation tools available through the A2A protocol.
model_id: "gpt-4o"
tools:
- name: "kubernetes_apply_fix"
type: "a2a"
endpoint: "http://host.docker.internal:8100"
description: "Apply fixes to Kubernetes resources (restart pods, update configs, scale)"
- name: "kubernetes_verify_fix"
type: "a2a"
endpoint: "http://host.docker.internal:8100"
description: "Verify that applied fixes resolved the issue"
history:
max_messages: 6
summarize_after: 3
创建 makdo-config/slack.yaml:
name: "MAKDO Slack Bot"
description: |
You are the MAKDO Slack Bot agent. Your role is to communicate with users via Slack.
Your responsibilities:
1. Post cluster health reports to the #makdo-devops channel
2. Alert users to critical issues
3. Provide status updates on ongoing operations
4. Respond to user questions about cluster health
Message formatting:
- Use clear, concise language
- Highlight critical issues with appropriate urgency
- Include relevant details (pod names, namespaces, error messages)
- Provide actionable next steps when possible
Always post to #makdo-devops channel unless told otherwise.
model_id: "gpt-4o"
mcp_servers:
slack:
command: "npx"
args: ["@korotovsky/slack-mcp-server"]
env:
SLACK_BOT_TOKEN: "${AI6_BOT_TOKEN}"
SLACK_TEAM_ID: "${SLACK_TEAM_ID}"
history:
max_messages: 5
summarize_after: 3
这些配置文件定义了每个智能体的行为、工具以及通信方式。Analyzer 与 Fixer 使用指向 k8s-ai 的 A2A 工具;Slack_Bot 使用一个 MCP server 来集成 Slack。
为 API Key 创建 Kubernetes Secrets(Creating Kubernetes secrets for API keys)
MAKDO 需要 OpenAI 的 API key(用于运行基于 LLM 的智能体)以及 Slack 的 token(用于发消息)。我们会把它们存成控制集群中的 Kubernetes Secret。
首先,创建一个包含 API key 的 .env 文件:
cat > .env <<EOF
OPENAI_API_KEY=sk-...your-key-here...
AI6_BOT_TOKEN=xoxb-...your-slack-bot-token...
AI6_APP_TOKEN=xapp-...your-slack-app-token...
SLACK_TEAM_ID=T...your-team-id...
EOF
把占位符替换成你自己的 key。若你还没有这些 key,可从以下位置获取:
- OpenAI API key:在 platform.openai.com/api-keys 获取
- Slack tokens:在 api.slack.com/apps 创建 Slack app,配置 bot token scopes(
chat:write与channels:read)以及用于 socket mode 的 app-level token - Slack Team ID:在 Slack 工作区设置里查看 Workspace ID
在控制集群中创建 Secret:
# Switch to control cluster
kubectl config use-context kind-makdo-control
# Load environment variables
set -a
source .env
set +a
# Create secret
kubectl create secret generic makdo-secrets \
--from-literal=openai-api-key="$OPENAI_API_KEY" \
--from-literal=slack-bot-token="$AI6_BOT_TOKEN" \
--from-literal=slack-app-token="$AI6_APP_TOKEN" \
--from-literal=slack-team-id="$SLACK_TEAM_ID" \
-n default
验证 secret 创建成功:
kubectl get secret makdo-secrets -n default
输出如下:
NAME TYPE DATA AGE
makdo-secrets Opaque 4 5s
Secret 已创建。值被隐藏无需担心——这正是 secret 的目的。你可以通过查看 key 是否存在来进一步确认:
kubectl describe secret makdo-secrets -n default
输出如下:
Name: makdo-secrets
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
openai-api-key: 51 bytes
slack-app-token: 53 bytes
slack-bot-token: 56 bytes
slack-team-id: 11 bytes
Secret 已就绪。我们会在 MAKDO 的 Deployment 中把它们挂成环境变量。
创建 kubeconfig ConfigMap(Creating the kubeconfig ConfigMap)
MAKDO 需要访问工作集群的 kubeconfig 文件才能管理它。我们会创建一个包含 kubeconfig 的 ConfigMap 并挂载到 MAKDO pod 中。注意:在生产环境中,出于安全考虑,更推荐用 Secret 来存放 kubeconfig。
提取工作集群 kubeconfig 并创建 ConfigMap:
# Create kubeconfig ConfigMap from your local kubeconfig
kubectl create configmap makdo-kubeconfig \
--from-file=config=$HOME/.kube/config \
--context kind-makdo-control
这个 ConfigMap 会被挂载到 MAKDO 容器内的 /root/.kube,使其能够访问控制集群与工作集群。
验证 ConfigMap 创建成功:
kubectl get configmap makdo-kubeconfig --context kind-makdo-control
输出如下:
NAME DATA AGE
makdo-kubeconfig 1 5s
kubeconfig 已可供 MAKDO 连接工作集群使用。
将 MAKDO 作为 Kubernetes Deployment 部署(Deploying MAKDO as a Kubernetes deployment)
MAKDO 将以一个副本(one replica)的 Deployment 方式运行。Deployment 能在 pod 崩溃时自动重启,并且便于通过滚动更新发布新版本或新配置。
部署清单见:
github.com/PacktPublis… 。
关键 Deployment 配置包括:
-
ConfigMap:包含
makdo.yaml配置文件,使我们无需重建镜像即可更新配置。 -
replicas: 1:单实例 MAKDO。多副本会导致重复健康检查与协调冲突。生产环境建议使用 leader election,确保有 standby 实例可接管。
-
imagePullPolicy: Never:我们会在本地构建 MAKDO 镜像并加载到 KinD,不从镜像仓库拉取。
-
env:从
makdo-secretssecret 注入环境变量,覆盖配置中的${VAR}占位符。 -
volumeMounts:挂载两个卷:
/app/config:来自 ConfigMap 的 MAKDO 配置/root/.kube:来自 ConfigMap 的 kubeconfig,用于访问两个集群
-
resources:CPU/内存限制防止 MAKDO 占用过多资源。对 Python 进程与 LLM 调用来说,512 Mi 到 1 Gi 通常合理。
-
volumes:挂载两个 ConfigMap:
makdo-config:包含 MAKDO 配置makdo-kubeconfig:包含 kubeconfig
在部署之前,我们需要先构建 MAKDO 的 Docker 镜像并加载到控制集群。
构建并加载 MAKDO 镜像(Building and loading the MAKDO image)
MAKDO 需要打包成 Docker 镜像。我们会创建一个 Dockerfile,安装依赖并启动 MAKDO 主进程。
创建 makdo/Dockerfile:
FROM python:3.13-slim
# Install system dependencies
RUN apt-get update && apt-get install -y \
curl \
git \
kubectl \
&& rm -rf /var/lib/apt/lists/*
# Install Node.js for Slack MCP server
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y nodejs \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy MAKDO source from the book repository
# Adjust path based on where you cloned the book repo
COPY ../../packt/Design-Multi-Agent-AI-Systems-Using-MCP-and-A2A/ch09/makdo/src ./src
COPY ../../packt/Design-Multi-Agent-AI-Systems-Using-MCP-and-A2A/ch09/makdo/pyproject.toml .
# Install MAKDO dependencies
RUN pip install --no-cache-dir ai-six==0.14.4 pyyaml python-dotenv requests
# Create required directories
RUN mkdir -p /app/config /app/logs
# Set Python to run unbuffered so logs appear immediately
ENV PYTHONUNBUFFERED=1
# Run MAKDO main entry point
CMD ["python", "-m", "src.makdo.main"]
该 Dockerfile 使用 Python 3.13。这个版本对所有依赖都有预构建的二进制 wheel,因此无需编译。我们安装 kubectl 用于访问 Kubernetes 集群,并安装 Node.js 用于 Slack MCP server。Python 依赖(ai-six、pyyaml、python-dotenv、requests)会直接从 wheel 安装,无需额外构建工具。
构建镜像:
cd ch11/makdo
docker build -t makdo:latest .
这会花几分钟下载基础镜像并安装依赖。若构建失败提示找不到 MAKDO 源码,请确认 src 目录路径正确。
构建完成后,把镜像加载到控制集群:
kind load docker-image makdo:latest --name makdo-control
这会把镜像从本地 Docker 传到 KinD 集群的内部镜像缓存。输出类似:
Image: "makdo:latest" with ID "sha256:..." not yet present on node "makdo-control-control-plane", loading...
验证镜像可用:
docker exec -it makdo-control-control-plane crictl images | grep makdo
你应该能看到 makdo 镜像。现在可以部署了。
应用 deployment:
kubectl apply -f deployment.yaml --context kind-makdo-control
输出如下:
configmap/makdo-config created
deployment.apps/makdo created
检查 deployment 状态:
kubectl get deployment makdo --context kind-makdo-control
输出如下:
NAME READY UP-TO-DATE AVAILABLE AGE
makdo 1/1 1 1 15s
MAKDO 已运行,且一个副本处于就绪状态。
配置 MAKDO 连接工作集群(Configuring MAKDO to connect to the worker cluster)
MAKDO 需要 kubeconfig 文件来访问工作集群,并需要 k8s-ai 的 session token 来使用 A2A 工具。kubeconfig 已通过宿主机挂载完成。现在我们需要确保 MAKDO 能与 k8s-ai 建立会话。
不过 k8s-ai 还没有部署。我们将在下一节部署它。现在先验证 MAKDO 能启动,并能通过 kubectl 访问工作集群。
查看 MAKDO pod 日志:
kubectl logs -l app=makdo --context kind-makdo-control --tail=50
你很可能会看到 k8s-ai 不可达的错误,这是预期行为。重点是要看到 MAKDO 成功启动的日志,例如:
2025-11-02 10:15:32,123 - makdo - INFO - Starting MAKDO - Multi-Agent Kubernetes DevOps System
2025-11-02 10:15:32,456 - makdo - INFO - Coordinator and sub-agents created successfully
2025-11-02 10:15:32,500 - makdo - INFO - Setting up tool call monitoring...
2025-11-02 10:15:32,550 - makdo - INFO - ✅ Tool call monitoring enabled for all agents
2025-11-02 10:15:32,600 - makdo - INFO - Getting kubeconfig for context: kind-makdo-worker
如果你看到 kubectl 或 kubeconfig 相关错误,说明 kubeconfig ConfigMap 可能缺失或不正确。请确认你已经用本地 kubeconfig 创建了 makdo-kubeconfig ConfigMap。
要在 MAKDO pod 内验证 kubectl 访问工作集群,可以运行:
kubectl exec -it deployment/makdo --context kind-makdo-control -- kubectl get nodes --context kind-makdo-worker
它会在 MAKDO 容器内运行 kubectl 来查询工作集群。输出如下:
NAME STATUS ROLES AGE VERSION
makdo-worker-control-plane Ready control-plane 45m v1.33.1
成功!MAKDO 可以访问工作集群。等我们部署好 k8s-ai 后,A2A 连接也会正常工作。
验证 MAKDO 部署与日志(Verifying MAKDO deployment and logs)
验证 MAKDO 健康并准备好监控工作集群。检查 pod 状态:
kubectl get pods -l app=makdo --context kind-makdo-control
输出如下:
NAME READY STATUS RESTARTS AGE
makdo-7c9f8b6d5d-x2k4j 1/1 Running 0 2m
pod 正在运行。检查资源使用情况:
kubectl top pod -l app=makdo --context kind-makdo-control
输出可能如下(需要 metrics-server,KinD 中可能不可用):
Error from server (ServiceUnavailable): the server is currently unable to handle the request (get pods.metrics.k8s.io)
没关系,KinD 默认不包含 metrics-server。我们可以从 pod spec 查看资源限制:
kubectl describe pod -l app=makdo --context kind-makdo-control | grep -A 5 "Limits"
输出如下:
Limits:
cpu: 500m
memory: 1Gi
Requests:
cpu: 250m
memory: 512Mi
资源配置正确。再检查 MAKDO 的日志是否有异常:
kubectl logs -l app=makdo --context kind-makdo-control --tail=100
重点关注这些指标:
- Coordinator started:Starting MAKDO - Multi-Agent Kubernetes DevOps System
- Sub-agents created:Coordinator and sub-agents created successfully
- Attempting k8s-ai session:Creating k8s-ai session for kind-makdo-worker…
- Health check loop started:Starting health check loop
你会看到 k8s-ai 连接失败的错误,这是因为 k8s-ai 还没部署。关键是 MAKDO 没有崩溃并能正常启动。
如果 MAKDO 发生 crash-loop,常见原因包括:
-
缺少 secrets:确认
makdo-secrets存在并包含所有 key:kubectl get secret makdo-secrets --context kind-makdo-control -
缺少 kubeconfig ConfigMap:确认已在部署前创建
makdo-kubeconfig。 -
Python 错误:日志中如果出现 import error 或缺依赖,可能需要重建镜像。
要实时跟踪 MAKDO 行为,可以使用:
kubectl logs -l app=makdo --context kind-makdo-control --follow
按 Ctrl + C 停止。MAKDO 已部署完成,并在等待 k8s-ai。下一节我们会把 k8s-ai 部署到工作集群,这样 MAKDO 就能开始真正监控与修复了。
将 k8s-ai 部署到工作集群(Deploying k8s-ai to the worker cluster)
现在 MAKDO 已在控制集群中运行,我们需要把 k8s-ai 部署到工作集群。k8s-ai 通过 A2A 协议暴露 Kubernetes 诊断能力,使 MAKDO 的 Analyzer 与 Fixer 智能体能够检查集群健康状况并执行修复动作。我们将把 k8s-ai 容器化,将其作为 Kubernetes 服务部署,并对外暴露,以便 MAKDO 能够跨集群与其通信。
构建 k8s-ai Docker 镜像(Building the k8s-ai Docker image)
k8s-ai 需要在工作集群中以容器方式运行。我们将创建一个 Dockerfile,把 k8s-ai 与所有依赖打包,安装 kubectl 以访问集群,并把它配置为 A2A server 运行。
创建 k8s-ai/Dockerfile:
FROM python:3.13-slim
# Install system dependencies
RUN apt-get update && apt-get install -y \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Install kubectl
RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/arm64/kubectl" && \
install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl && \
rm kubectl
WORKDIR /app
# Copy k8s-ai source
COPY k8s_ai ./k8s_ai
COPY pyproject.toml .
# Install k8s-ai and dependencies
RUN pip install --no-cache-dir -e .
# Create directory for API keys
RUN mkdir -p /app/data
# Expose A2A server port and Admin API port
EXPOSE 9999 9998
# Run k8s-ai server in A2A mode
CMD ["k8s-ai-server", "--host", "0.0.0.0", "--port", "9999", "--admin-port", "9998"]
上面这段代码的关键点如下:
- 基础镜像:
Python 3.12-slim提供一个最小化的 Python 环境。 - kubectl 安装:k8s-ai 通过执行 kubectl 命令与集群交互。我们下载最新稳定版并安装。
- 源码:复制
k8s_ai包与pyproject.toml文件。使用pip install -e .以 editable 方式安装 k8s-ai。 - 数据目录:
/app/data用于存储认证所需的 API key(keys.json)。 - 端口:
9999用于 A2A 协议 server,9998用于 Admin API(创建 session 时使用)。 - CMD:使用参数启动
k8s-ai-server,绑定到所有网卡(0.0.0.0),以便容器外部可访问。
k8s-ai 的源码位于主 k8s-ai 仓库中。把它复制到 ch11/k8s-ai 目录:
# From the k8s-ai repository root
cp -r k8s_ai /path/to/Design-Multi-Agent-AI-Systems-Using-MCP-and-A2A/ch11/k8s-ai/
cp pyproject.toml /path/to/Design-Multi-Agent-AI-Systems-Using-MCP-and-A2A/ch11/k8s-ai/
构建镜像:
cd ch11/k8s-ai
docker build -t k8s-ai:latest .
这会花几分钟下载基础镜像并安装依赖。完成后,验证镜像:
docker images | grep k8s-ai
输出如下:
k8s-ai latest abc123def456 2 minutes ago 450MB
镜像已构建完成,可以部署到工作集群。
创建 k8s-ai 部署清单(Creating k8s-ai deployment manifests)
k8s-ai 需要若干 Kubernetes 资源才能运行:一个拥有访问集群权限的 ServiceAccount、一个运行容器的 Deployment、以及一个对外暴露的 Service。我们将使用 RBAC(role-based access control) 为 k8s-ai 授予只读的集群访问权限。
k8s-ai 的部署清单在这里:
github.com/PacktPublis… 。
关键配置点包括:
- ServiceAccount 与 RBAC:k8s-ai 的 service account 通过
ClusterRole k8s-ai-reader获得只读权限。这允许 k8s-ai 执行kubectl get/list/watch,但禁止任何修改操作。ClusterRoleBinding把ServiceAccount绑定到ClusterRole。 - API keys secret:包含
keys.json(从你的.env文件来的 API key)。MAKDO 的 Analyzer 与 Fixer 将使用该 key 进行认证。该 key 以${K8S_AI_API_KEY}占位符形式出现,应用清单时会从环境变量替换。 - Deployment:以
imagePullPolicy: Never运行一个副本的 k8s-ai(KinD 使用本地镜像)。容器从 Secret 获取 OpenAI API key。资源限制防止 k8s-ai 过度消耗内存/CPU。 - 健康探针:liveness probe 通过请求
/.well-known/agent.json检查 k8s-ai 是否响应;readiness probe 确保 pod 在对外提供服务前已就绪。 - Service:使用
NodePort类型暴露 k8s-ai。30100映射到 A2A server(9999),30099映射到 Admin API(9998)。回想一下worker-cluster.yaml:端口30100会映射到宿主机端口8100,使控制集群能通过http://host.docker.internal:8100访问 k8s-ai。
接下来,我们看看如何部署刚构建好的 k8s-ai 镜像。
将 k8s-ai 部署到工作集群(Deploying k8s-ai to the worker cluster)
镜像构建完成且清单准备就绪后,我们就可以把 k8s-ai 部署到工作集群。首先把镜像加载进工作集群,创建所需的 secrets,然后应用部署。
把 k8s-ai 镜像加载到工作集群:
kind load docker-image k8s-ai:latest --name makdo-worker
输出如下:
Image: "k8s-ai:latest" with ID "sha256:1fe7b6c86eb2..." not yet present on node "makdo-worker-control-plane", loading...
这会把镜像从本地 Docker 传到工作集群的内部镜像缓存。没有这一步的话,pod 会因为 KinD 默认无法访问外部镜像仓库而出现 ImagePullBackOff。
创建 OpenAI API key secret。k8s-ai 需要它来调用 LLM 做自然语言处理:
kubectl create secret generic k8s-ai-secrets \
--from-literal=openai-api-key="${OPENAI_API_KEY}" \
--context kind-makdo-worker
验证 secret 创建成功:
kubectl get secret k8s-ai-secrets --context kind-makdo-worker
输出如下:
NAME TYPE DATA AGE
k8s-ai-secrets Opaque 1 5s
在部署前,请确保你在 ch11 目录下有一个包含必要凭据的 .env 文件。复制示例文件:
cd ch11
cp .env.example .env
编辑 .env 并设置你的凭据。
- 将
K8S_AI_API_KEY设为test-key(用于开发)。 - 生产环境中,可运行
k8s-ai-server --generate-key生成 key,并赋值给K8S_AI_API_KEY。 - 将
OPENAI_API_KEY设为你的 OpenAI API key。 - 只有在使用 Slack bot 时才需要 Slack 凭据。
.env 文件已在 .gitignore 中,因此你的 secrets 不会被提交。
现在,使用 envsubst 替换环境变量并应用部署清单:
cd k8s-ai
export $(grep -v '^#' ../.env | xargs)
envsubst < deployment.yaml | kubectl apply -f - --context kind-makdo-worker
输出如下:
serviceaccount/k8s-ai created
clusterrole.rbac.authorization.k8s.io/k8s-ai-reader created
clusterrolebinding.rbac.authorization.k8s.io/k8s-ai-reader-binding created
secret/k8s-ai-api-keys created
deployment.apps/k8s-ai created
service/k8s-ai created
共创建了 6 个资源:ServiceAccount、ClusterRole、ClusterRoleBinding、Secret(API keys)、Deployment、Service。检查 pod 状态:
kubectl get pods -l app=k8s-ai --context kind-makdo-worker
输出如下:
NAME READY STATUS RESTARTS AGE
k8s-ai-7b9c8d6f5d-x2k4j 1/1 Running 0 45s
pod 已运行。如果看到 ImagePullBackOff,请确认已经用 kind load 加载镜像。如果看到 CrashLoopBackOff,检查日志定位错误(通常是缺少 OpenAI API key)。
检查 service:
kubectl get svc k8s-ai --context kind-makdo-worker
输出如下:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
k8s-ai NodePort 10.96.145.123 <none> 9999:30100/TCP,9998:30099/TCP 1m
service 暴露了两个 node port:30100 给 A2A server,30099 给 Admin API。由于我们在 worker-cluster.yaml 里配置了 extraPortMappings,这些端口会在宿主机上分别对应 localhost:8100 与 localhost:8099。
对外暴露 k8s-ai 以实现跨集群访问(Exposing the k8s-ai service for cross-cluster access)
k8s-ai 已在工作集群中运行,但控制集群中的 MAKDO 需要访问它。在我们的 KinD 环境里,跨集群通信通过 Docker 宿主机网络进行。工作集群的 node port 30100 通过我们之前配置的 extraPortMappings 映射到了宿主机端口 8100。
从控制集群视角看,工作集群可以通过 host.docker.internal:8100 访问。该特殊 DNS 名会解析到 Docker 宿主机 IP,使得一个 KinD 集群中的容器可以访问另一个集群中暴露到宿主机的服务。
验证网络是否正常。首先检查 k8s-ai 的 agent card endpoint 是否能响应:
curl http://localhost:8100/.well-known/agent.json
输出如下(为便于阅读已格式化):
{
"name": "k8s-ai Diagnostic Agent",
"description": "Kubernetes AI diagnostic agent with read-only cluster analysis with session-based cluster management",
"url": "http://0.0.0.0:9999/",
"version": "2.0.0",
"capabilities": {
"streaming": false
},
"skills": [
{
"id": "kubernetes_diagnostics",
"name": "Kubernetes Diagnostics",
"description": "Perform read-only Kubernetes cluster diagnostics, troubleshooting, and health analysis with detailed insights",
"tags": ["kubernetes", "diagnostics", "troubleshooting", "monitoring", "health"]
}
]
}
agent card 说明 k8s-ai 正在响应。现在,通过 Admin API 创建 session 来测试认证。Admin API 需要 .env 里的 API key:
curl -X POST http://localhost:8099/sessions \
-H "Authorization: Bearer test-key" \
-H "Content-Type: application/json" \
-d '{
"cluster_name": "makdo-worker",
"ttl_hours": 24
}'
如果你尚未配置 ServiceAccount 属性,这一步可能会返回认证错误。没关系——这里只是验证 endpoint 是否可达。下一小节我们会测试完整的 session 创建流程。
这里的关键点是:MAKDO 智能体会使用 http://host.docker.internal:8100 访问 k8s-ai 的 A2A server。这一点已经在 MAKDO 的 coordinator.yaml 里配置好了:
a2a_servers:
- name: "kind-makdo-worker"
url: "http://host.docker.internal:8100"
timeout: 30.0
api_key: "test-key"
网络已经就绪。MAKDO 现在可以跨集群与 k8s-ai 通信了。
验证 k8s-ai 部署(Verifying k8s-ai deployment)
部署 k8s-ai 后,验证各组件是否正常运行且服务可访问。
检查 pod 状态(Check pod status)
首先,确认 k8s-ai pod 在工作集群中运行:
kubectl get pods -l app=k8s-ai --context kind-makdo-worker -o wide
期望输出:
NAME READY STATUS RESTARTS AGE IP NODE
k8s-ai-75558cb559-n8jl9 1/1 Running 0 20m 10.244.0.5 makdo-worker-control-plane
pod 应显示 1/1 Running 且无重启。
验证 pod 健康状态(Verify pod health)
检查 pod 的详细状态与健康探针:
kubectl describe pod -l app=k8s-ai --context kind-makdo-worker | grep -A 5 "Conditions:"
期望输出:
Conditions:
Type Status
PodReadyToStartContainers True
Initialized True
Ready True
ContainersReady True
PodScheduled True
所有条件都应为 True,表示已通过 liveness/readiness probe。
查看服务端日志(Check server logs)
查看 k8s-ai 日志确认两个 server 都已启动:
kubectl logs -l app=k8s-ai --context kind-makdo-worker --tail=50
查找类似的健康探针访问日志,说明 A2A endpoint 正常响应:
INFO: 10.244.0.1:41262 - "GET /.well-known/agent.json HTTP/1.1" 200 OK
Kubernetes 定期的 health probe 请求即表明该服务健康。
测试 A2A endpoint(Test the A2A endpoint)
验证从集群外部可访问 agent card:
curl -s http://localhost:8100/.well-known/agent.json | python3 -m json.tool | head -30
期望响应(截断):
{
"capabilities": {
"streaming": false
},
"defaultInputModes": [
"text/plain"
],
"defaultOutputModes": [
"text/plain"
],
"description": "Kubernetes AI diagnostic agent with read-only cluster analysis with session-based cluster management",
"name": "k8s-ai Diagnostic Agent",
"preferredTransport": "JSONRPC",
"protocolVersion": "0.3.0",
"security": [
{
"BearerAuth": []
}
],
"securitySchemes": {
"BearerAuth": {
"scheme": "bearer",
"type": "http"
}
],
"skills": [
{
"description": "Perform read-only Kubernetes cluster diagnostics...",
这确认了:
- A2A server 在 pod 内的
9999端口运行 - NodePort service 把
30100映射到容器端口 - 宿主机端口映射(
8100 → 30100)允许外部访问 - agent card 符合 A2A 协议规范
- bearer token 认证已启用
验证 service 配置(Verify service configuration)
检查 k8s-ai service 的配置:
kubectl get svc k8s-ai --context kind-makdo-worker -o yaml | grep -A 10 "spec:"
期望输出:
spec:
ports:
- name: a2a
nodePort: 30100
port: 9999
protocol: TCP
targetPort: 9999
- name: admin
nodePort: 30099
port: 9998
protocol: TCP
targetPort: 9998
selector:
app: k8s-ai
type: NodePort
这表明 A2A 端口(9999)和 Admin API 端口(9998)都通过 NodePort 暴露。
k8s-ai 已验证完成并在运行,工作集群现在已经有一个可供 MAKDO 跨集群调用的诊断智能体。
测试 k8s-ai Admin API 与 session 管理(Testing the k8s-ai Admin API and session management)
k8s-ai Admin API 提供 session 管理能力,让 MAKDO 可以创建与工作集群交互的隔离 session。每个 session 都有独立 kubeconfig 与过期时间。
暴露 Admin API 端口(Exposing the Admin API port)
默认情况下,工作集群配置暴露了 A2A 端口(8100),但不一定暴露 Admin API 端口。把 worker-cluster.yaml 更新为包含 8099:
# ch11/worker-cluster.yaml
extraPortMappings:
# Port for k8s-ai A2A server
- containerPort: 30100
hostPort: 8100
protocol: TCP
# Port for k8s-ai Admin API
- containerPort: 30099
hostPort: 8099
protocol: TCP
如果你已经创建了 worker 集群,需要用更新后的配置重新创建:
kind delete cluster --name makdo-worker
kind create cluster --config worker-cluster.yaml
然后按前面的步骤重新部署 k8s-ai。
创建 session(Creating a session)
创建 session 需要提供工作集群 kubeconfig。先导出工作集群 kubeconfig:
kubectl config view --context kind-makdo-worker --minify --flatten > /tmp/makdo-worker-kubeconfig.yaml
创建 session 请求 JSON 文件:
import json
# Read kubeconfig
with open('/tmp/makdo-worker-kubeconfig.yaml', 'r') as f:
kubeconfig = f.read()
# Create session request
request = {
"cluster_name": "makdo-worker",
"ttl_hours": 24,
"kubeconfig": kubeconfig
}
# Write to file
with open('/tmp/session-request.json', 'w') as f:
json.dump(request, f)
print("Session request created")
现在通过 Admin API 创建 session:
curl -s -X POST 'http://localhost:8099/sessions' \
-H 'Authorization: Bearer test-key' \
-H 'Content-Type: application/json' \
-d '@/tmp/session-request.json' | python3 -m json.tool
期望响应:
{
"success": true,
"session_token": "k8s-ai-session--qSTOZ0ggxnL-w5mMmMNboUBz0GVIGXvxkxfjYY5ND0",
"cluster_name": "makdo-worker",
"api_server": "https://127.0.0.1:65089",
"namespace": "default",
"connectivity_status": "connected",
"expires_at": "2025-11-10T04:37:27.297559Z",
"error": null
}
session_token(k8s-ai-session--...)将用于对 A2A endpoint 的请求进行认证。
列出活跃 session(Listing active sessions)
查看所有活跃 session:
curl -s 'http://localhost:8099/sessions' \
-H 'Authorization: Bearer test-key' | python3 -m json.tool
期望响应:
{
"total_sessions": 1,
"sessions": [
{
"session_token": "k8s-ai-session--qSTOZ0ggxnL-w5mMmMNboUBz0GVIGXvxkxfjYY5ND0",
"cluster_name": "makdo-worker",
"api_server": "https://127.0.0.1:65089",
"namespace": "default",
"created_at": "2025-11-09T04:37:27.297568Z",
"expires_at": "2025-11-10T04:37:27.297559Z",
"is_expired": false
}
]
}
下面看看如何删除一个 session。
删除 session(Deleting a session)
在 session 过期前手动删除:
SESSION_TOKEN="k8s-ai-session--qSTOZ0ggxnL-w5mMmMNboUBz0GVIGXvxkxfjYY5ND0"
curl -s -X DELETE "http://localhost:8099/sessions/${SESSION_TOKEN}" \
-H 'Authorization: Bearer test-key' | python3 -m json.tool
期望响应:
{
"success": true,
"message": "Session deleted successfully"
}
接下来看看如何使用 A2A protocol 搭配 session。
在 A2A 协议中使用 session(Using sessions with the A2A protocol)
MAKDO 会在对 k8s-ai 发起 A2A 请求时使用 session token。token 会通过 Authorization header 传递,同时在方法参数里携带 session 相关信息:
curl -s -X POST 'http://localhost:8100/' \
-H 'Authorization: Bearer test-key' \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"method": "kubernetes_diagnose_issue",
"params": {
"session_token": "'"${SESSION_TOKEN}"'",
"issue_description": "Check pod health in default namespace"
},
"id": 1
}'
这种基于 session 的方式带来若干好处:
- 隔离性:每个 MAKDO 实例都能有自己的 session,互不干扰
- 安全性:session 自动过期,限制访问窗口
- 灵活性:不同 session 可针对不同 namespace 或集群
- 可审计性:session 的创建与使用会记录日志,便于安全审计
到这里,Admin API 已测试通过且可用,k8s-ai 也已经准备好跨集群接收来自 MAKDO 的诊断请求。
启用集群间的安全通信(Enabling secure communication between clusters)
在控制集群中部署了 MAKDO、在工作集群中部署了 k8s-ai 之后,我们需要确保两者之间的通信是安全的。本节涵盖服务发现、网络、访问控制,以及已经就位的安全机制,并演示这些机制在 KinD 环境中如何工作。
服务发现与网络(Service discovery and networking)
在多集群部署中,智能体需要跨越集群边界发现并彼此通信。我们的部署使用显式配置与宿主机网络来实现跨集群通信。
MAKDO 如何发现 k8s-ai(How MAKDO discovers k8s-ai)
MAKDO 使用静态配置来发现 k8s-ai 服务。查看配置:
kubectl get configmap makdo-config --context kind-makdo-control \
-o jsonpath='{.data.makdo.yaml}' | grep -A 5 "k8s_ai:"
期望输出如下:
k8s_ai:
base_url: "http://host.docker.internal:8100"
admin_api_url: "http://host.docker.internal:8100/admin"
关键网络组件包括:
- host.docker.internal:一个特殊 DNS 名,在容器内部解析到 Docker 宿主机
- 端口 8100:从工作集群的 NodePort
30100映射到宿主机端口8100 - 端口 8099:从 Admin API 的 NodePort
30099映射出来
跟踪一次请求路径(Tracing a request path)
我们来跟踪一次诊断请求是如何从 MAKDO 流向 k8s-ai 的:
MAKDO Pod(控制集群)
↓ http://host.docker.internal:8100
Docker 宿主机网络
↓ localhost:8100
工作集群节点(Docker 容器)
↓ NodePort 30100
k8s-ai Service(工作集群)
↓ ClusterIP:9999
k8s-ai Pod(工作集群)
验证每一步:
# 1. 检查 MAKDO 是否能解析 host.docker.internal
kubectl exec deployment/makdo --context kind-makdo-control -- \
python3 -c "import socket; print(f'host.docker.internal resolves to: {socket.gethostbyname("host.docker.internal")}')"
# 2. 检查宿主机能否访问 8100 端口
curl -s http://localhost:8100/.well-known/agent.json | python3 -m json.tool | head -10
# 3. 检查工作集群中的 NodePort service
kubectl get svc k8s-ai --context kind-makdo-worker -o yaml | grep -A 5 "nodePort: 30100"
# 4. 检查 k8s-ai pod 是否在接收流量
kubectl logs deployment/k8s-ai --context kind-makdo-worker --tail=5
在生产系统中,这种跨网络调用链可能会为对延迟敏感的操作增加显著时延。为此,可以引入延迟预算(latency budget) 并进行显式监控与管理。
为了访问 k8s-ai server,我们首先需要发现它。不同用例下有若干常见模式。下面来看看。
服务发现模式(Service discovery patterns)
我们的部署演示了静态配置的服务发现。生产环境中还包括:
- 基于 DNS 的发现(DNS-based discovery) :使用 Kubernetes DNS 发现集群内服务:
# 同一集群内的服务
k8s_ai:
base_url: "http://k8s-ai.default.svc.cluster.local:9999"
- 服务网格发现(Service mesh discovery) :使用 Istio 或 Linkerd 等服务网格:
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: k8s-ai-external
spec:
hosts:
- k8s-ai.worker-cluster.global
location: MESH_EXTERNAL
ports:
- number: 9999
name: a2a
protocol: HTTP
resolution: DNS
endpoints:
- address: k8s-ai.worker-cluster.svc.cluster.local
- 动态服务注册表(Dynamic service registry) :使用 Consul 等服务注册中心:
import consul
# 注册 k8s-ai 服务
client = consul.Consul()
client.agent.service.register(
name='k8s-ai',
service_id='k8s-ai-worker-1',
address='k8s-ai.worker-cluster',
port=9999,
tags=['a2a', 'diagnostics']
)
# 发现服务
services = client.health.service('k8s-ai', passing=True)
for service in services:
print(f"Found k8s-ai at {service['Service']['Address']}:{service['Service']['Port']}")
下面我们考虑暴露 k8s-ai server 的不同网络方式。
跨集群网络方案(Cross-cluster networking approaches)
不同环境需要不同的网络策略:
KinD(当前方案)
- 使用 Docker 宿主机网络(
host.docker.internal) - NodePort service +
extraPortMappings - 简单,适合本地开发:
extraPortMappings:
- containerPort: 30100
hostPort: 8100
云厂商(AWS / GCP / Azure)
- 使用
LoadBalancerservice + 公网 IP - 或使用 VPC Peering 的私网互联
- 或用 Transit Gateway 做多集群连通:
apiVersion: v1
kind: Service
metadata:
name: k8s-ai
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
service.beta.kubernetes.io/aws-load-balancer-internal: "true"
spec:
type: LoadBalancer
selector:
app: k8s-ai
ports:
- port: 9999
targetPort: 9999
多集群服务网格(Multi-cluster service mesh)
- 跨集群自动可发现
- 集群间双向 TLS(mTLS)
- 流量治理与可观测性:
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
values:
global:
multiCluster:
clusterName: worker-cluster
network: network1
对分布式系统来说,端到端连通性测试非常重要。
端到端连通性测试(Testing end-to-end connectivity)
验证从 MAKDO 到 k8s-ai 的完整连通性:
# 创建一个测试 session
kubectl config view --context kind-makdo-worker --minify --flatten > /tmp/test-conn.yaml
python3 << 'EOF'
import json
with open('/tmp/test-conn.yaml', 'r') as f:
kubeconfig = f.read()
request = {
"cluster_name": "connectivity-test",
"ttl_hours": 1,
"kubeconfig": kubeconfig
}
with open('/tmp/test-conn.json', 'w') as f:
json.dump(request, f)
EOF
# 通过 Admin API 创建 session
RESPONSE=$(curl -s -X POST 'http://localhost:8099/sessions' \
-H 'Authorization: Bearer test-key' \
-H 'Content-Type: application/json' \
-d '@/tmp/test-conn.json')
echo $RESPONSE | python3 -m json.tool
# 提取 session token
SESSION_TOKEN=$(echo $RESPONSE | python3 -c "import sys, json; print(json.load(sys.stdin)['session_token'])")
echo "Session token: ${SESSION_TOKEN}"
接着,测试 MAKDO(运行在控制集群中)是否能通过同一路径访问该 endpoint:
# 从宿主机测试(模拟 MAKDO 的视角)
curl -s 'http://localhost:8100/.well-known/agent.json' \
-H 'Authorization: Bearer test-key' | python3 -m json.tool | head -15
命令输出(截断)显示 agent card:
{
"capabilities": {
"streaming": false
},
"defaultInputModes": [
"text/plain"
],
"defaultOutputModes": [
"text/plain"
],
"description": "Kubernetes AI diagnostic agent with read-only cluster analysis with session-based cluster management",
"name": "k8s-ai Diagnostic Agent",
这确认了完整路径:MAKDO → Docker 宿主机 → 工作集群 → k8s-ai pod。
工业级系统必须提供纵深防御(defense in depth) 以满足安全要求。下面看看 MAKDO 与 k8s-ai 已经提供了什么。
理解安全架构(Understanding the security architecture)
我们的部署实现了多层安全:
- API key 认证:MAKDO 与 k8s-ai 都使用 bearer token 认证
- 基于 session 的访问:k8s-ai 使用带过期时间的临时 session
- RBAC:k8s-ai 在工作集群中只有只读权限
- 网络隔离:通信只通过特定端口流动且可控
- secret 管理:凭据存放于 Kubernetes Secrets 与
.env文件
这遵循安全最佳实践,尽量降低远程访问、数据外泄、权限提升、横向移动与敏感信息暴露的风险。
API key 认证(API key authentication)
API key 认证是为远程服务提供安全访问的常见方式。我们看看 k8s-ai 是如何实现的。
k8s-ai 认证(k8s-ai authentication)
k8s-ai 的 A2A endpoint 与 Admin API 都需要 bearer token 认证。API keys 存放在部署期间创建的 Kubernetes secret 中。
查看已存储的 API keys:
kubectl get secret k8s-ai-api-keys --context kind-makdo-worker \
-o jsonpath='{.data.keys.json}' | base64 -d | python3 -m json.tool
期望输出:
{
"api_keys": [
{
"key": "test-key",
"name": "MAKDO Client",
"created": "2025-01-01T00:00:00"
}
]
}
不带凭据发起请求测试认证:
curl -s 'http://localhost:8099/sessions' | python3 -m json.tool
期望响应:
{
"detail": "Not authenticated"
}
再用有效凭据测试:
curl -s 'http://localhost:8099/sessions' \
-H 'Authorization: Bearer test-key' | python3 -m json.tool
这会返回活跃 session 列表,证明认证生效。
MAKDO 配置(MAKDO configuration)
MAKDO 在其配置里存储 k8s-ai API key。检查 MAKDO ConfigMap:
kubectl get configmap makdo-config --context kind-makdo-control \
-o jsonpath='{.data.makdo.yaml}' | grep -A 3 "a2a_servers:"
期望输出展示 API key 配置:
a2a_servers:
- name: "kind-makdo-worker"
url: "http://host.docker.internal:8100"
timeout: 30.0
api_key: "test-key"
在生产环境中,请记住:API keys 应使用强随机值(而不是 test-key),定期轮换,存放在专门的密钥管理系统中(例如 HashiCorp Vault、AWS Secrets Manager 等),并且绝不能提交到版本控制系统。
基于 session 的安全(Session-based security)
k8s-ai 项目实现了基于 session 的访问控制,在 API key 之外增加了一层安全。session 生命周期包括:
- 创建(Creation) :MAKDO 通过 Admin API 创建 session,并提供 kubeconfig
- 使用(Usage) :所有诊断操作都使用 session token
- 过期(Expiration) :session 在 TTL 后自动过期
- 清理(Cleanup) :过期 session 自动移除
下面演示 session 安全性。
创建一个短 TTL session:
# 导出 kubeconfig
kubectl config view --context kind-makdo-worker --minify --flatten > /tmp/test-kubeconfig.yaml
# 创建 1 分钟 TTL session
python3 << 'EOF'
import json
with open('/tmp/test-kubeconfig.yaml', 'r') as f:
kubeconfig = f.read()
request = {
"cluster_name": "test-session",
"ttl_hours": 0.0167, # 1 minute = 1/60 hours
"kubeconfig": kubeconfig
}
with open('/tmp/short-session.json', 'w') as f:
json.dump(request, f)
EOF
curl -s -X POST 'http://localhost:8099/sessions' \
-H 'Authorization: Bearer test-key' \
-H 'Content-Type: application/json' \
-d '@/tmp/short-session.json' | python3 -m json.tool
响应里包含 expires_at 时间戳。过期后 session 会失效:
# 等待 90 秒
sleep 90
# 尝试使用已过期 session
SESSION_TOKEN=" < token-from-creation-response >"
curl -s -X POST 'http://localhost:8100/' \
-H 'Authorization: Bearer test-key' \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"method": "kubernetes_diagnose_issue",
"params": {
"session_token": "'"${SESSION_TOKEN}"'",
"issue_description": "test"
},
"id": 1
}'
期望响应表明 session 已过期:
{
"jsonrpc": "2.0",
"error": {
"code": -32002,
"message": "Session not found or expired"
},
"id": 1
}
这种限时访问降低了凭据泄露后的影响面。
RBAC 与最小权限(RBAC and least privilege)
k8s-ai 在工作集群中以最小权限运行。
检查 RBAC 配置(Inspecting RBAC configuration)
查看分配给 k8s-ai 的 ClusterRole:
kubectl get clusterrole k8s-ai-reader --context kind-makdo-worker -o yaml
关键权限如下:
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "services", "nodes", "namespaces", "events"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets", "statefulsets", "daemonsets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["batch"]
resources: ["jobs", "cronjobs"]
verbs: ["get", "list", "watch"]
注意这里只有只读动词:get、list、watch。没有 create、update、delete 或 patch。
测试权限边界(Testing permission boundaries)
验证 k8s-ai 无法修改资源:
# 取一个 pod 名
POD_NAME=$(kubectl get pods --context kind-makdo-worker -o jsonpath='{.items[0].metadata.name}')
# 使用 k8s-ai 的 service account 尝试删除
kubectl delete pod ${POD_NAME} --context kind-makdo-worker \
--as=system:serviceaccount:default:k8s-ai
期望报错:
Error from server (Forbidden): pods "k8s-ai-f77d65f7d-p6lkk" is forbidden:
User "system:serviceaccount:default:k8s-ai" cannot delete resource "pods"
in API group "" in the namespace "default"
这说明即使攻击者获得 k8s-ai 的访问权限,也无法修改或删除集群资源。
KinD 中的网络分段(Network segmentation in KinD)
虽然 KinD 集群运行在同一个 Docker 网络上,我们仍通过端口映射与服务配置来控制通信。
端口映射安全(Port mapping security)
查看暴露的端口:
docker ps --filter name=makdo-control-plane --format "table {{.Names}}\t{{.Ports}}"
docker ps --filter name=makdo-worker-control-plane --format "table {{.Names}}\t{{.Ports}}"
输出展示受控暴露:
NAMES PORTS
makdo-control-control-plane 127.0.0.1:xxx->6443/tcp, 0.0.0.0:9000->30000/tcp
makdo-worker-control-plane 127.0.0.1:xxx->6443/tcp, 0.0.0.0:8099->30099/tcp, 0.0.0.0:8100->30100/tcp
关键观察点:
- Kubernetes API:绑定在
127.0.0.1(仅本机可访问,不对外暴露) - 应用端口:绑定在
0.0.0.0(宿主机可访问,模拟外部访问) - 仅暴露指定端口:并非所有容器端口都被暴露
这展示了如何在 KinD 环境中管理 Kubernetes 的连通性与网络,同时保持隔离。下面我们来测试一下。
测试网络隔离(Testing network isolation)
尝试从宿主机直接访问 Kubernetes API(应该失败):
# 获取工作集群 API server 地址
WORKER_API=$(kubectl config view --context kind-makdo-worker --minify \
-o jsonpath='{.clusters[0].cluster.server}')
echo "Worker API: ${WORKER_API}"
# 无正确凭据访问(应失败)
curl -k "${WORKER_API}/api/v1/namespaces"
由于认证要求,我们会得到预期错误:
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "Unauthorized",
"reason": "Unauthorized",
"code": 401
}
Kubernetes API 受到保护,只有持有正确 kubeconfig 凭据才能访问。
生产环境安全增强(Production security enhancements)
对于超出 KinD 的生产部署,还应实现以下额外安全措施:
- TLS/SSL 加密:本地(KinD)中 MAKDO 与 k8s-ai 的通信是 HTTP;生产中要为两端启用 TLS:
# k8s-ai with TLS
containers:
- name: k8s-ai
env:
- name: TLS_CERT_FILE
value: "/certs/tls.crt"
- name: TLS_KEY_FILE
value: "/certs/tls.key"
volumeMounts:
- name: tls-certs
mountPath: /certs
readOnly: true
volumes:
- name: tls-certs
secret:
secretName: k8s-ai-tls
使用 cert-manager 或云厂商证书管理服务。
- NetworkPolicy:限制 pod-to-pod 通信:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: k8s-ai-network-policy
spec:
podSelector:
matchLabels:
app: k8s-ai
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: makdo-namespace
ports:
- protocol: TCP
port: 9999
- Pod 安全标准(Pod security standards) :强制安全上下文:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
capabilities:
drop:
- ALL
- Secrets 管理:用外部 secret manager 替换 Kubernetes secrets:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: k8s-ai-secrets
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: k8s-ai-secrets
data:
- secretKey: api-key
remoteRef:
key: k8s-ai/api-key
- 审计日志(Audit logging) :启用全面日志:
# In k8s-ai code
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Log all authentication attempts
def authenticate(request):
logger.info(f"Authentication attempt from {request.client.host}")
# ... authentication logic
- 限流(Rate limiting) :防止滥用:
apiVersion: v1
kind: ConfigMap
metadata:
name: k8s-ai-rate-limits
data:
rate_limits.yaml: |
limits:
- key: ip
rate: 100
per: minute
- key: api_key
rate: 1000
per: hour
这里的经验是:要维持一个紧凑且安全的生产环境并不简单,必须采用多种措施。
下面是一份上线前安全检查清单:
- 所有通信均使用 TLS/SSL 加密
- API keys 强随机、定期轮换
- secrets 使用外部管理(Vault、AWS Secrets Manager 等)
- RBAC 遵循最小权限
- NetworkPolicies 限制不必要通信
- Pod security context 强制非 root 运行
- 审计日志记录所有访问尝试
- 限流防止滥用
- 镜像做漏洞扫描
- 依赖更新并打好安全补丁
- 出站(egress)过滤以降低外泄风险
KinD 部署演示的安全架构提供了坚实基础,但生产环境仍需要这些额外的加固措施。
总结(Summary)
在本章中,我们使用 KinD 在两个 Kubernetes 集群上部署了一个完整的多智能体系统,展示了分布式 AI 智能体架构的真实世界模式。我们首先通过正确的网络与端口映射建立双集群,然后把 MAKDO(含四个专用智能体与用于 memory、Slack 通信的 MCP servers)部署到控制集群。接着在工作集群中部署 k8s-ai 作为诊断智能体,配置了只读 RBAC 权限,同时暴露 A2A 协议 endpoint 与用于 session 管理的 Admin API。整个部署过程中,我们实现了多层安全:API key 认证、自动过期的 session 访问控制、以及通过受控端口暴露与服务边界实现的网络隔离。
我们构建的架构将编排与决策的控制智能体与具备直接集群访问能力的执行型工作智能体分离。通信通过基于 HTTP 的 JSON-RPC A2A 协议完成,并在 KinD 环境中通过 host.docker.internal 进行静态服务发现。我们演示了如何跨集群跟踪请求路径、测试认证与授权机制、验证 RBAC 权限以阻止未授权修改,以及通过 Kubernetes Secrets 与环境变量安全地管理 secrets。部署还展示了跨集群网络的实践路径:从适用于本地开发的 NodePort 方案,到生产环境可选的服务网格与动态服务注册表等更复杂方案。
虽然这个基于 KinD 的部署非常适合用来理解多智能体系统架构,但要迁移到生产仍需要额外考量:包括全链路 TLS 加密、更强的 API key 与轮换策略、外部 secrets 管理、完善的监控与日志、高可用配置以及自动化部署流水线。本章展示的模式与实践可以从本地开发扩展到跨云厂商、私有化和混合云的企业级生产系统。
在下一章(也是最后一章)中,我们将探讨多智能体 AI 系统的未来与即将塑造这些架构演进的新趋势。