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 . /appRUN pip install -r requirements.txt最后CMD [python, main.py]。这套流程在本地开发时没问题但一旦放到 K3S 集群里问题就接踵而至。最典型的是pip install时网络不稳定导致构建失败不同时间构建的镜像因为requirements.txt中的包没有锁死版本导致requests2.31.0和requests2.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比如openai1.35.1、langchain0.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 --frombuilder requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制应用代码 COPY --chownagentuser:agentuser . . # 暴露端口 EXPOSE 8000 # 启动命令 CMD [uvicorn, app.main:app, --host, 0.0.0.0:8000, --port, 8000, --workers, 2]注意几个细节--chownagentuser: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/agentgit pulldocker build -t my-agent:latest .docker stop my-agentdocker rm my-agentdocker 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-actionv2.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-fileconfig.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 的内容是明文存储在 etcdK3S 里是 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.enabledfalse --set externalURLhttp://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{jobmy-agent}[5m]) 0.95过去 5 分钟的成功率低于 95%时就通过企业微信机器人发送告警。这个告警比任何人工巡检都及时、准确。有了 Harbor 和 Kuboard Spray你的 Agent 就不再是一个孤岛。它是一个有身份Harbor 镜像、有家K3S 集群、有管家Kuboard Spray、有健康报告监控告警的完整生命体。此时你才能说“部署跑通了。” 这句话的分量远不止于“服务起来了”它意味着你建立了一套可持续交付、可持续运维、可持续演进的工程体系。这才是“真正开始做 Agent”的起点。因为从此以后你的精力可以真正聚焦在 Agent 的核心价值上设计更聪明的规划Planning逻辑、接入更多样化的工具Tool Calling、优化更流畅的对话体验Orchestration而不是每天疲于应付环境故障、配置错误和部署失败。这条路我们走了两年踩过无数坑也总结出了一套最适合中小团队的、轻量但不失健壮的落地路径。它不追求大而全但每一步都扎实、可验证、可复制。
Agent生产部署四支柱:K3S、Dockerfile、GitHub Actions与ConfigMap
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 . /appRUN pip install -r requirements.txt最后CMD [python, main.py]。这套流程在本地开发时没问题但一旦放到 K3S 集群里问题就接踵而至。最典型的是pip install时网络不稳定导致构建失败不同时间构建的镜像因为requirements.txt中的包没有锁死版本导致requests2.31.0和requests2.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比如openai1.35.1、langchain0.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 --frombuilder requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制应用代码 COPY --chownagentuser:agentuser . . # 暴露端口 EXPOSE 8000 # 启动命令 CMD [uvicorn, app.main:app, --host, 0.0.0.0:8000, --port, 8000, --workers, 2]注意几个细节--chownagentuser: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/agentgit pulldocker build -t my-agent:latest .docker stop my-agentdocker rm my-agentdocker 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-actionv2.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-fileconfig.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 的内容是明文存储在 etcdK3S 里是 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.enabledfalse --set externalURLhttp://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{jobmy-agent}[5m]) 0.95过去 5 分钟的成功率低于 95%时就通过企业微信机器人发送告警。这个告警比任何人工巡检都及时、准确。有了 Harbor 和 Kuboard Spray你的 Agent 就不再是一个孤岛。它是一个有身份Harbor 镜像、有家K3S 集群、有管家Kuboard Spray、有健康报告监控告警的完整生命体。此时你才能说“部署跑通了。” 这句话的分量远不止于“服务起来了”它意味着你建立了一套可持续交付、可持续运维、可持续演进的工程体系。这才是“真正开始做 Agent”的起点。因为从此以后你的精力可以真正聚焦在 Agent 的核心价值上设计更聪明的规划Planning逻辑、接入更多样化的工具Tool Calling、优化更流畅的对话体验Orchestration而不是每天疲于应付环境故障、配置错误和部署失败。这条路我们走了两年踩过无数坑也总结出了一套最适合中小团队的、轻量但不失健壮的落地路径。它不追求大而全但每一步都扎实、可验证、可复制。