1. “部署跑通之后,我才真正开始做 Agent”——这句行话背后的真实分水岭

刚入行那会儿,我总以为写完一个调用大模型的函数、加个工具调用逻辑、再套个 ReAct 模板,就能叫“做了个 Agent”。直到第一次在 K3S 集群里把自研的 Hermes 风格 Agent 服务从 Dockerfile 构建、GitHub Actions 自动推送镜像、ConfigMap 注入运行时配置、到通过 Kuboard Spray 完成集群初始化全链路走通——那一刻才真正明白: Agent 的“开发完成”,和 Agent 的“可用、可维护、可演进”之间,隔着一整套生产级基础设施的鸿沟。 这不是夸张,而是几乎所有团队踩过的真实坑。你写的 Agent 逻辑再精巧,如果它无法在稳定、可观测、可灰度、可回滚的环境中持续响应请求,那它本质上还只是个 Demo,不是产品。关键词里的 K3S、Dockerfile、GitHub Actions、ConfigMap ,每一个都不是孤立的技术点,而是共同构成 Agent 从“能跑”到“敢用”的四根支柱。K3S 是轻量但完整的 Kubernetes 内核,它让 Agent 不再是单机进程,而是具备弹性伸缩与服务发现能力的微服务;Dockerfile 是 Agent 的“数字身份证”,它固化了所有依赖、环境变量与启动逻辑,确保从本地开发机到生产集群的零差异;GitHub Actions 是自动化流水线的“神经中枢”,它把每次代码提交自动转化为一次可验证、可追溯的部署;而 ConfigMap,则是 Agent 的“呼吸系统”,它让配置与代码解耦,使同一个镜像能在测试、预发、生产环境无缝切换。这四个词串起来,就是一条从“写完代码”到“交付价值”的完整路径。如果你还在用 python main.py 启动 Agent,或者把 API Key 硬编码在 Python 文件里,那你离真正的 Agent 工程师,可能就差这四步。这篇文章不讲 LLM 原理,不画 Agent Loop 流程图,只聚焦于:当你的核心逻辑已经写好,如何用最务实、最贴近一线生产环境的方式,把它变成一个真正能扛住流量、能快速迭代、能被团队协作维护的服务。下面,我们就从这四块基石出发,一层层拆解。

2. K3S:为什么不是 Minikube 或 Docker Desktop?轻量集群的取舍逻辑

很多人看到“要上 Kubernetes”,第一反应是装 Minikube 或者开 Docker Desktop 的 Kubernetes 功能。我试过,也劝退过至少三支团队。Minikube 是个极好的学习玩具,但它默认使用 VirtualBox 或 HyperKit 虚拟机,内存开销大、网络调试复杂、节点扩容几乎不可行;Docker Desktop 的 K8s 则更像一个“演示模式”,它缺乏真实的节点管理、存储卷调度、以及对 Helm Chart 的原生支持,当你需要挂载 NFS 存储来持久化 Agent 的记忆库(比如 ChromaDB),或者想用 NodePort 对外暴露多个 Agent 服务端口时,它就会开始报错或行为诡异。而 K3S,是 Rancher 团队为边缘计算和 CI/CD 场景专门打磨的发行版,它的设计哲学非常清晰: 去掉一切非必需组件,保留 Kubernetes 的核心语义。 它用 SQLite 替代 etcd 作为后端存储,用 containerd 替代 Docker Engine,整个二进制文件只有 50MB 左右,单节点安装命令一行搞定: curl -sfL https://get.k3s.io | sh - 。但这行命令背后,是它对真实场景的深刻理解。我们团队在一台 4C8G 的云服务器上部署 K3S,同时运行着 3 个不同版本的 Agent 服务(用于 A/B 测试)、1 个 Prometheus 监控栈、1 个 Grafana 可视化面板,资源占用稳定在 60% 以内。关键在于,K3S 的 k3s server 进程本身就是一个自包含的控制平面,它不依赖外部数据库,也不需要复杂的证书签发流程——所有证书都由内置的 k3s server 自动生成并轮换。这意味着,当你用 GitHub Actions 触发一次部署时,CI 环境不需要预先配置任何 K8s 认证信息,只需要一个简单的 kubectl 配置文件指向你的 K3S 集群地址,就能直接执行 kubectl apply -f agent-deployment.yaml 。这种“开箱即用”的确定性,是 Minikube 和 Docker Desktop 永远无法提供的。更重要的是,K3S 完全兼容标准的 Kubernetes YAML。你写的 Deployment、Service、Ingress,拿到 EKS 或 AKS 上,几乎不用改就能跑。这就避免了“本地开发一套,上线又是一套”的经典陷阱。我们曾有个项目,Agent 在 Minikube 里一切正常,但一上生产 EKS 就出现 DNS 解析超时,排查了两天才发现是 Minikube 的 CoreDNS 配置和 EKS 的默认策略有细微差别。而 K3S,它就是 Kubernetes,只是更轻、更快、更适合小团队落地。所以,当你决定“跑通 Agent”,第一步不是写代码,而是先在一台干净的 Linux 机器上,用官方脚本装好 K3S,并手动执行一遍 kubectl get nodes kubectl get pods -A ,确认集群状态健康。这一步花不了十分钟,但它为你后续所有自动化部署打下了绝对可信的基座。别跳过它,这是所有可靠性的起点。

3. Dockerfile:从“能跑”到“可复现”的唯一契约

很多开发者写 Dockerfile,习惯性地从 FROM python:3.11-slim 开始,然后 COPY . /app RUN pip install -r requirements.txt ,最后 CMD ["python", "main.py"] 。这套流程在本地开发时没问题,但一旦放到 K3S 集群里,问题就接踵而至。最典型的是: pip install 时网络不稳定导致构建失败;不同时间构建的镜像,因为 requirements.txt 中的包没有锁死版本,导致 requests==2.31.0 requests==2.32.0 行为不一致;甚至更隐蔽的, COPY . /app .git 目录、 __pycache__ 文件夹、本地的 .env 配置文件一起打包进去,不仅增大镜像体积,还可能泄露敏感信息。真正的生产级 Dockerfile,核心目标只有一个: 保证“同一份 Dockerfile + 同一份源码”,在任何时间、任何地点构建出的镜像,其内容、依赖、行为都完全一致。 这就是“可复现性”,它是 DevOps 的基石。我们的标准实践是采用多阶段构建(Multi-stage Build)+ Poetry 锁定依赖 + 显式用户隔离。第一阶段,我们用 python:3.11-slim-bookworm 作为构建器,安装 Poetry,然后 poetry export -f requirements.txt --without-hashes > requirements.txt ,这一步会生成一个带精确版本号的 requirements.txt ,比如 openai==1.35.1 langchain==0.1.19 。第二阶段,我们切换到更小的 python:3.11-slim-bookworm 基础镜像,只 COPY 第一阶段生成的 requirements.txt ,然后 pip install --no-cache-dir -r requirements.txt 。这样做的好处是,第二阶段镜像里完全没有 Poetry、编译工具链等构建期依赖,体积能减少 40% 以上。最后,我们 COPY 应用代码,并创建一个非 root 用户 agentuser ,用 USER agentuser 指令切换身份,强制以低权限运行 Agent 进程。这不仅是安全最佳实践,更是为了规避某些 LLM SDK(比如早期的 transformers )在 root 用户下会尝试写入 /root/.cache 目录而导致的权限错误。一个典型的、经过实战检验的 Agent Dockerfile 如下:

# 构建阶段
FROM python:3.11-slim-bookworm AS builder

# 安装 Poetry
RUN pip install poetry

# 复制 pyproject.toml 和 poetry.lock
COPY pyproject.toml poetry.lock ./

# 使用 Poetry 安装依赖并导出 requirements.txt
RUN poetry export -f requirements.txt --without-hashes > requirements.txt

# 运行时阶段
FROM python:3.11-slim-bookworm

# 创建非 root 用户
RUN adduser --disabled-password --gecos "" agentuser && \
    mkdir -p /app && \
    chown -R agentuser:agentuser /app

# 切换到非 root 用户
USER agentuser
WORKDIR /app

# 复制构建阶段生成的 requirements.txt 并安装
COPY --from=builder requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 复制应用代码
COPY --chown=agentuser:agentuser . .

# 暴露端口
EXPOSE 8000

# 启动命令
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0:8000", "--port", "8000", "--workers", "2"]

注意几个细节: --chown=agentuser:agentuser 确保复制的文件所有权正确; --workers 2 是 Uvicorn 的推荐配置,对于 CPU 密集型的 LLM 推理,通常 2-4 个 worker 是平衡吞吐与内存的最佳点; EXPOSE 8000 虽然在 K3S 中不是必须的(Service 会处理端口映射),但它是一种文档化的约定,告诉所有协作者这个容器监听哪个端口。写完这个 Dockerfile,不要急着推送到仓库。先在本地用 docker build -t my-agent:dev . 构建,再用 docker run -p 8000:8000 my-agent:dev 启动,用 curl http://localhost:8000/health 检查健康接口是否返回 {"status": "ok"} 。这一步,是你对“可复现性”承诺的第一次自我验证。只有当本地构建、本地运行、本地验证全部通过,这个 Dockerfile 才算真正合格。否则,后面所有的自动化部署,都是在沙上筑塔。

4. GitHub Actions:让每一次代码提交都成为一次可靠的部署

把 Agent 部署到 K3S,最原始的方式是:改完代码,本地 git push ,然后 SSH 登录到服务器, cd /path/to/agent git pull docker build -t my-agent:latest . docker stop my-agent docker rm my-agent docker run -d --name my-agent -p 8000:8000 my-agent:latest 。这个过程,我干过,也崩溃过。有一次,我在服务器上执行 git pull 时网络中断,导致工作目录处于半更新状态, docker build 时引用了一个不存在的模块,服务直接起不来。更可怕的是,这个操作没有任何记录,也没有回滚机制。GitHub Actions 的价值,就在于它把这一系列手工操作,变成了一个可审计、可重放、可自动化的流水线。它的核心思想是: 将部署逻辑代码化(Code as Infrastructure),并将其与源码版本强绑定。 我们的标准流水线分为三个阶段: build-and-test push-to-registry deploy-to-k3s 。第一个阶段,在 PR 提交时触发,它会拉取代码,用 poetry install 安装依赖,然后运行 pytest tests/ 执行单元测试和集成测试(比如测试 Agent 是否能正确调用一个 Mock 的天气 API)。只有所有测试通过,流水线才会继续。第二个阶段,在 main 分支有新 commit 时触发,它会用 docker buildx build 构建多平台镜像(支持 amd64 和 arm64),并推送到私有 Harbor 仓库(关于 K3S 安装 Harbor,我们会在下一节详述)。这里的关键是 buildx ,它解决了传统 docker build 无法跨平台构建的问题,让你的 Agent 镜像可以在 x86 服务器和 ARM 架构的树莓派上都能运行。第三个阶段,也是最关键的,它会连接到你的 K3S 集群,执行 kubectl apply -f k8s/deployment.yaml 。这个 deployment.yaml 文件里, image 字段不再是硬编码的 my-agent:latest ,而是动态注入的 my-agent:${{ github.sha }} ,即当前 commit 的 SHA 值。这意味着,每一次部署,都对应着一个唯一的、可追溯的代码版本。如果线上出问题,你只需看一眼 kubectl get pods -o wide 输出的镜像 ID,就能立刻定位到是哪次提交引入的 Bug。以下是 deploy-to-k3s 阶段的核心 YAML 片段:

- name: Deploy to K3S
  uses: appleboy/kubectl-action@v2.9.0
  with:
    server: ${{ secrets.K3S_SERVER }}
    token: ${{ secrets.K3S_TOKEN }}
    ca_crt: ${{ secrets.K3S_CA_CERT }}
    namespace: default
    manifests: k8s/deployment.yaml,k8s/service.yaml,k8s/configmap.yaml
    args: --force

这里, secrets.K3S_SERVER K3S_TOKEN K3S_CA_CERT 都是 GitHub 仓库的加密密钥,它们在 Actions 运行时被安全注入,不会出现在日志中。 --force 参数确保即使 Deployment 已存在,也会强制更新,这是实现滚动更新(Rolling Update)的前提。整个流水线的设计哲学是: 宁可让一次部署慢几秒,也绝不能让一次部署不可靠。 所以,我们会在 deploy-to-k3s 阶段之后,增加一个 verify-deployment 步骤,它用 curl 命令向新部署的 Service 发送一个健康检查请求,如果 5 秒内没有收到 200 OK ,整个流水线就标记为失败,并自动回滚到上一个成功的版本(通过 kubectl rollout undo deployment/my-agent 实现)。这个闭环,才是自动化部署的终极形态。它意味着,你不再需要记住任何服务器密码,不再需要担心自己手抖敲错命令,你唯一需要做的,就是写好代码,然后按 git push 。剩下的,交给 GitHub Actions。

5. ConfigMap:让 Agent 真正学会“看环境脸色行事”

很多初学者会把 Agent 的所有配置,比如 LLM 的 API Key、Base URL、超时时间、工具列表,都硬编码在 Python 代码里,或者写在一个 config.py 文件里,然后 import config 。这种方式在单机开发时很爽,但一旦进入 K3S 环境,它就成了最大的技术债。想象一下:你想在测试环境用 OpenAI 的 gpt-3.5-turbo ,在生产环境用自托管的 llama3:70b ,你难道要为每个环境维护一份不同的代码?或者,你想临时把超时时间从 30 秒调到 60 秒来调试一个慢响应的工具,你得改代码、提 PR、走流水线、等部署……这显然违背了敏捷开发的原则。ConfigMap 的存在,就是为了解决这个问题: 它让配置与代码彻底分离,让同一个镜像,能根据不同的环境配置,表现出完全不同的行为。 在 K3S 中,ConfigMap 是一个独立的、命名空间级别的 API 对象,它就像一个键值对的“配置中心”。你可以用 kubectl create configmap agent-config --from-file=config.yaml 命令,把一个 config.yaml 文件的内容注入到集群中。然后,在你的 Agent Deployment 的 YAML 里,通过 volumeMounts 将这个 ConfigMap 挂载为一个文件,比如挂载到 /app/config.yaml 。这样,你的 Python 代码就可以用标准的 yaml.safe_load(open("/app/config.yaml")) 来读取配置,而无需关心这个文件是从哪里来的。我们的 config.yaml 结构如下:

llm:
  provider: "ollama"
  base_url: "http://ollama-service.default.svc.cluster.local:11434"
  model: "llama3:70b"
  timeout: 60
tools:
  - name: "weather"
    endpoint: "http://weather-service.default.svc.cluster.local:8000"
  - name: "database"
    endpoint: "http://db-service.default.svc.cluster.local:5432"
secrets:
  # 注意:真正的 Secret 不应该放在这里!这只是示意结构
  # 实际的 API Key 会放在 Kubernetes Secret 中,通过 envFrom 引入
  ollama_api_key: ""

这里有几个关键点。第一, base_url 使用的是 Kubernetes 的内部 DNS 格式 service-name.namespace.svc.cluster.local ,这确保了 Agent 服务能通过集群内部网络,高效、安全地访问其他服务,而无需走公网。第二, tools 列表里的 endpoint 也遵循同样的规则,这使得多 Agent 协作变得极其简单——你只需要在集群里部署一个 weather-service ,所有需要天气功能的 Agent,只要读取这个 ConfigMap,就能自动发现并调用它。第三, secrets 字段是一个占位符,它提醒你: 任何敏感信息,如 API Key,都必须放在 Kubernetes Secret 中,而不是 ConfigMap 里。 ConfigMap 的内容是明文存储在 etcd(K3S 里是 SQLite)中的,任何人都可以通过 kubectl get configmap agent-config -o yaml 查看。而 Secret 会被 Base64 编码(虽然不是加密,但至少是混淆),并且可以配合 RBAC 权限进行更细粒度的访问控制。在 Deployment 中,你会这样引用它们:

envFrom:
- configMapRef:
    name: agent-config
- secretRef:
    name: agent-secrets

这样,你的 Python 代码里,就可以用 os.getenv("OLLAMA_API_KEY") 安全地获取密钥。ConfigMap 的威力,在于它的动态性。你不需要重启 Agent Pod,就能更新配置。 kubectl edit configmap agent-config ,修改 timeout 值,保存退出,K3S 会自动将新的配置文件同步到所有挂载了它的 Pod 中(默认延迟约 1 分钟)。我们的 Agent 代码里,会有一个后台线程,定期检查 /app/config.yaml 文件的修改时间戳,一旦发现变化,就重新加载整个配置对象。这就实现了真正的“热更新”。所以,当你听到“Agent 开发需要哪些技术栈”时,答案不仅仅是 Python 和 LLM SDK,它必须包括对 Kubernetes 原生配置管理机制的深刻理解。ConfigMap 不是锦上添花,它是让 Agent 具备生产环境适应力的氧气。

6. 生产就绪的闭环:Harbor、Kuboard Spray 与监控告警

前面四步,已经构建了一个从代码到部署的完整链路。但一个真正“跑通”的 Agent,还需要最后两块拼图: 私有镜像仓库(Harbor) 可视化集群管理(Kuboard Spray) 。没有 Harbor,你的 Docker 镜像只能推送到 Docker Hub,这不仅有速率限制和隐私风险,更关键的是,它无法与你的 K3S 集群形成一个封闭、可控的内网生态。Docker Hub 的镜像拉取,需要走公网,一旦网络波动,K3S 的 Pod 就会卡在 ImagePullBackOff 状态,整个服务就不可用了。Harbor 是 CNCF 毕业的开源项目,它是一个企业级的 Registry,支持镜像签名、漏洞扫描、基于角色的访问控制(RBAC)。在 K3S 集群中部署 Harbor,我们推荐使用 Helm Chart,因为它能自动处理所有依赖,比如 PostgreSQL、Redis、Nginx Ingress。安装命令很简单: helm repo add harbor https://helm.goharbor.io ,然后 helm install harbor harbor/harbor --set expose.tls.enabled=false --set externalURL=http://harbor.yourdomain.com 。安装完成后,你就可以在 GitHub Actions 的 push-to-registry 阶段,把镜像推送到 http://harbor.yourdomain.com/library/my-agent 。K3S 节点会通过内网 IP 直接访问 Harbor,速度飞快,且完全不受公网影响。而 Kuboard Spray,则是 K3S 的“瑞士军刀”。它不是一个简单的 Dashboard,而是一个集成了集群部署、应用管理、监控告警、日志查询的一站式平台。你可以用它一键安装 K3S 集群(回答几个问题,它就自动生成 Ansible Playbook 并执行),也可以用它图形化地创建 Deployment、Service、ConfigMap,甚至可以直接在界面上编辑 YAML 文件。更重要的是,它内置了 Prometheus 和 Grafana,你无需额外配置,就能看到每个 Agent Pod 的 CPU、内存、网络 I/O、HTTP 请求成功率、P95 延迟等关键指标。我们给 Agent 服务配置了一个简单的告警规则:当 rate(http_request_duration_seconds_count{job="my-agent"}[5m]) < 0.95 (过去 5 分钟的成功率低于 95%)时,就通过企业微信机器人发送告警。这个告警,比任何人工巡检都及时、准确。有了 Harbor 和 Kuboard Spray,你的 Agent 就不再是一个孤岛。它是一个有身份(Harbor 镜像)、有家(K3S 集群)、有管家(Kuboard Spray)、有健康报告(监控告警)的完整生命体。此时,你才能说:“部署跑通了。” 这句话的分量,远不止于“服务起来了”,它意味着你建立了一套可持续交付、可持续运维、可持续演进的工程体系。这才是“真正开始做 Agent”的起点。因为从此以后,你的精力可以真正聚焦在 Agent 的核心价值上:设计更聪明的规划(Planning)逻辑、接入更多样化的工具(Tool Calling)、优化更流畅的对话体验(Orchestration),而不是每天疲于应付环境故障、配置错误和部署失败。这条路,我们走了两年,踩过无数坑,也总结出了一套最适合中小团队的、轻量但不失健壮的落地路径。它不追求大而全,但每一步都扎实、可验证、可复制。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐