几何图形计算器
40.01M · 2026-03-05
在上一章中,我们讨论了多智能体系统的测试、调试与故障排查。我们探索了诸如幻觉(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 集群,并可自动检测与修复问题。
本章涵盖的关键点包括:
请按照以下说明操作:
github.com/PacktPublis… 。
让我们构建一个类生产部署:MAKDO 运行在一个集群上,并管理另一个集群。这种隔离在真实环境中非常关键,用于将管理基础设施与生产工作负载隔离开来。它可以减少爆炸半径(blast radius),并且即使 MAKDO 不可用,生产系统也能继续运行。此外,如果生产集群发生资源耗尽或类似问题,MAKDO 仍能分析并可能修复它,因为 MAKDO 不受同样问题影响。
在创建集群之前,你需要安装 Docker、kubectl 和 KinD。我们不会逐个工具讲解安装步骤(不同平台差异大且会随时间变化),而是提供一个验证脚本来检查环境是否就绪。
首先,如果你还没有克隆本书仓库,请先克隆:
git clone git@github.com:PacktPublishing/Design-Multi-Agent-AI-Systems-using-MCP-and-A2A.git
使用官方文档安装所需工具:
安装完成后,运行我们的前置依赖检查脚本来验证一切正常:
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! 后,就可以开始创建集群了。
控制集群承载 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 状态。集群已启动,但还是空的。
工作集群运行实际业务工作负载。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。我们将在下一步完成所需配置。
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 等)已就绪,等待服务使用。
在部署 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 部署完成后测试。现在两个集群已为下一步做好准备。
为了验证 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 了。MAKDO 将以控制集群中的一个 Deployment 形式运行,并通过 k8s-ai 的 A2A server 坚控工作集群。
MAKDO 需要配置文件来知道要坚控哪些集群、如何连接到 k8s-ai,以及如何通过 Slack 通信。我们会根据“双集群”部署方式对示例配置做适配。
细节请参考:
github.com/PacktPublis…
下面是关键配置点:
kind-makdo-worker 这个 context 与 KinD 创建的 kubectl context 一致。/root/.kube/config,因为 kubeconfig 将被挂载到 MAKDO 容器内的这个路径。我们会通过 volume 挂载 kubeconfig。host.docker.internal:8100 去访问工作集群上的 k8s-ai。这个特殊 DNS 名称在 Docker 容器内会解析到宿主机。8100 是我们为 k8s-ai 的 A2A server 映射的端口。${VAR} 语法。MAKDO 会从环境变量读取这些值,我们将通过 Kubernetes Secret 提供。接下来我们需要智能体配置文件。MAKDO 为每个智能体的 prompt 与工具配置使用独立的 YAML 文件。
创建 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。
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,可从以下位置获取:
ch@t:write 与 channels:read)以及用于 socket mode 的 app-level token在控制集群中创建 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 中把它们挂成环境变量。
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 将以一个副本(one replica)的 Deployment 方式运行。Deployment 能在 pod 崩溃时自动重启,并且便于通过滚动更新发布新版本或新配置。
部署清单见:
github.com/PacktPublis… 。
关键 Deployment 配置包括:
ConfigMap:包含 makdo.yaml 配置文件,使我们无需重建镜像即可更新配置。
replicas: 1:单实例 MAKDO。多副本会导致重复健康检查与协调冲突。生产环境建议使用 leader election,确保有 standby 实例可接管。
imagePullPolicy: Never:我们会在本地构建 MAKDO 镜像并加载到 KinD,不从镜像仓库拉取。
env:从 makdo-secrets secret 注入环境变量,覆盖配置中的 ${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 需要打包成 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 | 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 需要 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 健康并准备好坚控工作集群。检查 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
重点关注这些指标:
你会看到 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 就能开始真正坚控与修复了。
现在 MAKDO 已在控制集群中运行,我们需要把 k8s-ai 部署到工作集群。k8s-ai 通过 A2A 协议暴露 Kubernetes 诊断能力,使 MAKDO 的 Analyzer 与 Fixer 智能体能够检查集群健康状况并执行修复动作。我们将把 k8s-ai 容器化,将其作为 Kubernetes 服务部署,并对外暴露,以便 MAKDO 能够跨集群与其通信。
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 )/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 环境。k8s_ai 包与 pyproject.toml 文件。使用 pip install -e . 以 editable 方式安装 k8s-ai。/app/data 用于存储认证所需的 API key(keys.json)。9999 用于 A2A 协议 server,9998 用于 Admin API(创建 session 时使用)。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 需要若干 Kubernetes 资源才能运行:一个拥有访问集群权限的 ServiceAccount、一个运行容器的 Deployment、以及一个对外暴露的 Service。我们将使用 RBAC(role-based access control) 为 k8s-ai 授予只读的集群访问权限。
k8s-ai 的部署清单在这里:
github.com/PacktPublis… 。
关键配置点包括:
ClusterRole k8s-ai-reader 获得只读权限。这允许 k8s-ai 执行 kubectl get/list/watch,但禁止任何修改操作。ClusterRoleBinding 把 ServiceAccount 绑定到 ClusterRole。keys.json(从你的 .env 文件来的 API key)。MAKDO 的 Analyzer 与 Fixer 将使用该 key 进行认证。该 key 以 ${K8S_AI_API_KEY} 占位符形式出现,应用清单时会从环境变量替换。imagePullPolicy: Never 运行一个副本的 k8s-ai(KinD 使用本地镜像)。容器从 Secret 获取 OpenAI API key。资源限制防止 k8s-ai 过度消耗内存/CPU。/.well-known/agent.json 检查 k8s-ai 是否响应;readiness probe 确保 pod 在对外提供服务前已就绪。NodePort 类型暴露 k8s-ai。30100 映射到 A2A server(9999),30099 映射到 Admin API(9998)。回想一下 worker-cluster.yaml:端口 30100 会映射到宿主机端口 8100,使控制集群能通过 访问 k8s-ai。接下来,我们看看如何部署刚构建好的 k8s-ai 镜像。
镜像构建完成且清单准备就绪后,我们就可以把 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。.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 已在工作集群中运行,但控制集群中的 MAKDO 需要访问它。在我们的 KinD 环境里,跨集群通信通过 Docker 宿主机网络进行。工作集群的 node port 30100 通过我们之前配置的 extraPortMappings 映射到了宿主机端口 8100。
从控制集群视角看,工作集群可以通过 host.docker.internal:8100 访问。该特殊 DNS 名会解析到 Docker 宿主机 IP,使得一个 KinD 集群中的容器可以访问另一个集群中暴露到宿主机的服务。
验证网络是否正常。首先检查 k8s-ai 的 agent card endpoint 是否能响应:
curl
输出如下(为便于阅读已格式化):
{
"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 智能体会使用 访问 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 后,验证各组件是否正常运行且服务可访问。
首先,确认 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 的详细状态与健康探针:
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。
查看 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 请求即表明该服务健康。
验证从集群外部可访问 agent card:
curl -s | 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...",
这确认了:
9999 端口运行30100 映射到容器端口8100 → 30100)允许外部访问检查 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 管理能力,让 MAKDO 可以创建与工作集群交互的隔离 session。每个 session 都有独立 kubeconfig 与过期时间。
默认情况下,工作集群配置暴露了 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 需要提供工作集群 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:
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 过期前手动删除:
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。
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 的方式带来若干好处:
到这里,Admin API 已测试通过且可用,k8s-ai 也已经准备好跨集群接收来自 MAKDO 的诊断请求。
在控制集群中部署了 MAKDO、在工作集群中部署了 k8s-ai 之后,我们需要确保两者之间的通信是安全的。本节涵盖服务发现、网络、访问控制,以及已经就位的安全机制,并演示这些机制在 KinD 环境中如何工作。
在多集群部署中,智能体需要跨越集群边界发现并彼此通信。我们的部署使用显式配置与宿主机网络来实现跨集群通信。
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"
关键网络组件包括:
30100 映射到宿主机端口 810030099 映射出来我们来跟踪一次诊断请求是如何从 MAKDO 流向 k8s-ai 的:
MAKDO Pod(控制集群)
↓
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 | 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,我们首先需要发现它。不同用例下有若干常见模式。下面来看看。
我们的部署演示了静态配置的服务发现。生产环境中还包括:
# 同一集群内的服务
k8s_ai:
base_url: "http://k8s-ai.default.svc.cluster.local:9999"
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
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 的不同网络方式。
不同环境需要不同的网络策略:
host.docker.internal)extraPortMappingsextraPortMappings:
- containerPort: 30100
hostPort: 8100
LoadBalancer service + 公网 IPapiVersion: 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
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
values:
global:
multiCluster:
clusterName: worker-cluster
network: network1
对分布式系统来说,端到端连通性测试非常重要。
验证从 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 已经提供了什么。
我们的部署实现了多层安全:
.env 文件这遵循安全最佳实践,尽量降低远程访问、数据外泄、权限提升、横向移动与敏感信息暴露的风险。
API key 认证是为远程服务提供安全访问的常见方式。我们看看 k8s-ai 是如何实现的。
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 在其配置里存储 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 等),并且绝不能提交到版本控制系统。
k8s-ai 项目实现了基于 session 的访问控制,在 API key 之外增加了一层安全。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
}
这种限时访问降低了凭据泄露后的影响面。
k8s-ai 在工作集群中以最小权限运行。
查看分配给 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。
验证 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 集群运行在同一个 Docker 网络上,我们仍通过端口映射与服务配置来控制通信。
查看暴露的端口:
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
关键观察点:
127.0.0.1(仅本机可访问,不对外暴露)0.0.0.0(宿主机可访问,模拟外部访问)这展示了如何在 KinD 环境中管理 Kubernetes 的连通性与网络,同时保持隔离。下面我们来测试一下。
尝试从宿主机直接访问 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 凭据才能访问。
对于超出 KinD 的生产部署,还应实现以下额外安全措施:
# 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 或云厂商证书管理服务。
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
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
capabilities:
drop:
- ALL
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
# 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
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
这里的经验是:要维持一个紧凑且安全的生产环境并不简单,必须采用多种措施。
下面是一份上线前安全检查清单:
KinD 部署演示的安全架构提供了坚实基础,但生产环境仍需要这些额外的加固措施。
在本章中,我们使用 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 系统的未来与即将塑造这些架构演进的新趋势。