GitOps核心原理:声明式基础设施与Git作为唯一真相源
1. 什么是 GitOps?不是新名词,而是运维思维的“归位”
你肯定听过 DevOps——开发和运维拧成一股绳,用自动化流水线把代码从 IDE 直接送到用户手机里;你也大概率接触过 MLOps,它把机器学习模型的训练、验证、上线、监控全链路串起来,让 AI 不再是实验室里的“一次性实验”。但当你在技术会议听到有人突然抛出 “GitOps” 这个词,或者在 Kubernetes 社区看到大家反复强调 “Git 是唯一真相源”,你心里可能闪过一个念头:这又是个包装精美的 buzzword 吗?还是说,它真正在解决某个我们天天踩却习以为常的坑?
答案是后者。GitOps 不是凭空造出来的概念,它是 DevOps 在基础设施层的一次“自然演进”,更是对过去十年云原生实践的一次系统性反思与收敛。它的核心,不是引入一堆新工具,而是把一个早已被开发者奉为圭臬的协作范式——Git 工作流——原封不动、严丝合缝地移植到基础设施管理这个曾经最混乱、最黑盒、最依赖“人肉经验”的领域。
我第一次在生产环境落地 GitOps 是在 2021 年,当时团队正被 Kubernetes 集群的“配置漂移”折磨得夜不能寐。某天凌晨三点,线上服务突然抖动,排查发现是某个节点上的 ConfigMap 被手动修改过,而 Git 仓库里对应的文件还是三个月前的版本。没人记得是谁改的、为什么改、改对了没有。那次事故后,我们花了两周时间,把所有集群的 YAML 文件、Helm values、Terraform state backend 全部收口到一个私有 Git 仓库,并强制所有变更必须走 Pull Request。结果很直接:下一次类似故障发生时,我们 30 秒内就定位到是哪个 PR 引入了问题,5 分钟内完成回滚。这不是魔法,只是把写代码时最基础的纪律——版本控制、代码审查、自动化测试——搬到了运维现场。
所以,GitOps 的本质,是让基础设施管理回归到一种“可读、可审、可测、可逆”的工程状态。它不关心你用的是 Kubernetes 还是 Nomad,不挑剔你的 IaC 工具是 Terraform 还是 Pulumi,甚至不强制要求你必须用容器。它只坚守一条铁律:系统当前运行的真实状态(Actual State),必须能且只能由 Git 仓库中声明的期望状态(Desired State)自动、持续地推导和维持出来。这个“自动、持续”的过程,就是 GitOps 的灵魂。它解决的从来不是“能不能自动化”的问题,而是“自动化是否可信、是否可追溯、是否可审计”的问题。对于正在构建 LLM 应用的你来说,这意味着你的模型服务 API、GPU 资源配额、Ingress 路由规则、甚至 Prometheus 的告警阈值,都不再是散落在不同人脑中的模糊记忆或藏在某个运维同学笔记本里的临时命令,而是一份份清晰、可 diff、可评论、可回溯的文本文件。这才是真正支撑起 AI 工程化落地的底层地基。
2. GitOps 的底层逻辑与核心设计思想
2.1 为什么是 Git?而不是其他任何东西?
很多人初学 GitOps 会疑惑:为什么非得是 Git?用 SVN 行不行?用数据库存配置行不行?甚至用一个 Web UI 管理界面行不行?这个问题的答案,决定了你能否真正理解 GitOps 的设计哲学。
Git 被选中,绝非偶然,而是因为它天然具备 GitOps 所需的全部“基因”。我们可以拆解为四个不可替代的硬性条件:
第一,强一致性与不可变性。Git 的每一次 commit 都是一个 SHA-256 哈希值,它精确地、唯一地标识了仓库在那一刻的完整快照。你无法“编辑”一个已存在的 commit,只能基于它创建一个新的 commit。这种不可变性,为基础设施状态提供了一个绝对可靠的“锚点”。当 Argo CD 或 Flux 检查到集群状态与某个 commit ID 不符时,它知道该向哪个确定的目标去修复,而不是在一个模糊的、可能已被覆盖的“最新版”上做猜测。相比之下,一个 Web UI 的后台数据库,其记录的“最后修改时间”永远无法保证操作的原子性和一致性。
第二,完备的协作工作流。Pull Request(PR)是 Git 的核心协作机制。它天然集成了代码审查(Code Review)、讨论(Comment)、批准(Approve)、合并(Merge)等环节。将基础设施变更纳入 PR 流程,意味着每一次扩容、每一次参数调整、每一次安全策略更新,都必须经过至少一位同事的审视。我见过太多因为一个replicas: 3被手误改成replicas: 30导致集群雪崩的案例,而一个简单的 PR review 就能拦住它。这种“强制性的多人确认”,是任何单点操作界面都无法提供的安全护栏。
第三,强大的历史追溯能力。git log --oneline -p一条命令,就能让你看到过去半年里,每一个配置项是如何被谁、在什么背景下、出于什么原因被修改的。你可以轻松地git bisect定位到导致问题的那次提交,也可以git revert精准地撤销某一次变更。这种能力,在传统运维中是奢侈品。当一个数据库连接池参数被调低后服务开始超时,你很难在一堆日志和聊天记录中还原出当时的决策链。而在 GitOps 下,那条 commit message 里很可能就写着:“#1234 降低连接池以缓解 MySQL 内存压力,待观察 24 小时”。
第四,开箱即用的生态系统。GitHub、GitLab、Bitbucket 等平台早已围绕 Git 构建了完整的 CI/CD、权限管理、审计日志、Webhook 集成生态。你不需要自己造轮子去实现一个“配置变更通知中心”,只需要在仓库设置里勾选一个 Webhook,就能让 Argo CD 实时感知到推送。这种成熟度,是其他任何配置存储方案短期内无法企及的。
所以,GitOps 选择 Git,不是因为它“流行”,而是因为它“可靠”、“可协作”、“可追溯”、“可集成”。它把一个已经被数千万开发者验证过的、最健壮的软件协作协议,直接复用到了基础设施领域。这是一种极简主义的智慧:不发明新轮子,而是把最好的轮子,装到最需要的地方。
2.2 声明式(Declarative)与命令式(Imperative):两种截然不同的世界观
理解 GitOps,绕不开“声明式”与“命令式”这对概念。它们代表了两种完全不同的系统管理哲学,其差异之大,堪比“告诉导航仪‘我要去北京’”和“告诉司机‘先左转,再直行 500 米,然后右转’”。
命令式(Imperative)是传统运维的典型方式。你登录到服务器,执行kubectl scale deployment my-app --replicas=5,或者在 Terraform 中运行terraform apply -var="env=prod"。你下达的是一系列具体的、按顺序执行的操作指令(Commands)。它的特点是“过程导向”:你关心的是“怎么做”。这种方式的问题在于,它无法告诉你“现在系统到底是什么状态”。你执行了 10 条命令,但其中第 7 条失败了,第 9 条执行了两次,那么最终状态是啥?没人能立刻回答。它像是一本没有目录、没有页码、字迹还可能被涂改的笔记,只有写笔记的人才知道故事的全貌。
声明式(Declarative)则是 GitOps 的基石。你不再告诉系统“做什么”,而是告诉系统“你想要什么样子”。你写一个 YAML 文件:
apiVersion: apps/v1 kind: Deployment metadata: name: my-app spec: replicas: 5 selector: matchLabels: app: my-app template: spec: containers: - name: app image: my-registry/my-app:v1.2.3这份文件就是一个“声明”(Declaration),它描述了你期望的 Deployment 应该具备的所有属性:副本数是 5,镜像版本是 v1.2.3,标签是app: my-app。至于如何从当前状态(可能是 3 个副本、v1.1.0 镜像)变成这个目标状态,那是 GitOps 控制器(如 Argo CD)的工作。它会自动计算出差异(Diff),并执行最小集的变更操作(比如拉取新镜像、滚动更新 Pod)。这种方式是“结果导向”:你只关心“最终要什么样”。
这种范式的转变,带来了质的飞跃。首先,它实现了幂等性(Idempotency):无论你对同一个声明执行 1 次还是 100 次,最终结果都是一样的。这彻底消除了“重复执行命令导致意外”的风险。其次,它天然支持状态比对与自愈。控制器可以 24/7 地运行一个循环:读取 Git 中的声明 -> 读取集群中的实际状态 -> 计算差异 -> 执行修复。如果有人绕过 Git,直接在集群里执行kubectl delete pod,控制器会在几秒内发现缺失了一个 Pod,并立即创建一个新的来补足。这就是所谓的“自愈”(Self-healing)能力,它让系统拥有了对抗人为失误和意外故障的免疫力。
我在一个金融客户的项目中亲眼见证了这种威力。他们的合规要求极其严格,所有生产环境的变更必须有双人审批。以前,运维同学 A 审批后,自己 SSH 登录执行命令,B 同学只能通过日志确认。现在,A 提交一个包含所有变更的 PR,B 审批合并后,Flux 控制器自动完成部署。整个过程,A 和 B 都无需碰生产环境,所有操作都固化在 Git 的 commit history 里,审计报告一键生成。这不再是效率的提升,而是安全边界的重构。
2.3 拉取(Pull)与推送(Push):两种模型的本质区别与适用场景
GitOps 的实现,目前主要分为两大流派:拉取(Pull-based)和推送(Push-based)。很多初学者容易陷入“哪个更好”的争论,但更务实的视角是:它们是同一枚硬币的两面,服务于不同阶段、不同规模、不同成熟度的团队。
拉取模型是 GitOps 的“纯正血统”,也是 Argo CD、Flux 等专业 GitOps 工具所采用的方式。它的核心架构非常简洁:一个运行在集群内部的“控制器”(Controller),它像一个不知疲倦的哨兵,持续地、主动地(Pull)从 Git 仓库中拉取最新的配置文件,并将其与集群当前的实际状态进行比对。一旦发现不一致,它就立即执行修复操作。
这个模型的威力在于其持续性(Continuous)和自治性(Autonomous)。它不依赖于任何外部事件触发,也不需要你去“启动”一个部署流程。它是一个永不停歇的闭环。因此,它天然具备三大核心能力:
- 持续校验(Continuous Reconciliation):每 30 秒(可配置)检查一次,确保集群状态永不偏离 Git。
- 漂移检测(Drift Detection):任何绕过 Git 的手动变更,都会被瞬间捕获。
- 自动修复(Auto-Healing):检测到漂移后,自动执行反向操作,将集群“拉回”到 Git 所声明的正确状态。
推送模型则更像是 DevOps CI/CD 流水线的自然延伸。它依赖于 GitHub Actions、GitLab CI 等外部 CI/CD 系统。当开发者向 Git 仓库推送(Push)代码或配置后,CI/CD 系统被 Webhook 触发,它会执行一系列预定义的步骤:构建镜像、运行测试、然后执行kubectl apply -f infra/prod/或helm upgrade命令,将新的配置“推送”到集群。
它的优势在于简单、直接、易上手。如果你已经熟练使用 GitHub Actions,那么只需增加一个部署步骤,就能享受到 GitOps 的大部分好处:版本控制、PR 审查、自动化部署。它不需要在集群里额外部署和维护一个控制器,学习成本极低。
然而,它的短板也十分明显,且是根本性的:
- 无持续性:CI/CD 只在代码推送时触发一次。它不会在之后的时间里持续关注集群。如果集群在部署后因某种原因发生了漂移(比如节点宕机导致 Pod 丢失),CI/CD 不会管它。
- 无自愈能力:它是一次性的“快照式”部署,而非一个“活”的状态同步机制。
- 安全边界更宽:为了让 CI/CD 能够执行
kubectl命令,你必须将集群的访问凭证(如 kubeconfig)以密钥(Secret)的形式存储在 CI/CD 平台中。这相当于在 CI/CD 系统里放了一把通往生产环境的万能钥匙,一旦 CI/CD 平台被攻破,后果不堪设想。
我的建议是:对于个人项目、POC(概念验证)、或刚刚起步的小团队,推送模型是绝佳的起点。它能让你快速获得 GitOps 的核心价值,建立信心。但一旦项目进入关键业务阶段,或者团队规模超过 5 人,就必须认真评估向拉取模型迁移的必要性。这不是为了追求“高大上”,而是为了给你的系统加上一道无法被绕过的、自动化的、全天候的安全与稳定性保障。就像汽车的安全带,你平时感觉不到它的存在,但当意外发生时,它就是那道决定性的防线。
3. 核心组件解析与实操要点
3.1 Git 仓库:不只是代码库,更是唯一的“真相源”
在 GitOps 中,Git 仓库的角色发生了根本性的升维。它不再仅仅是存放应用源代码的地方,而是整个系统生命周期的“单一事实来源”(Single Source of Truth, SSOT)。这意味着,所有关于“系统应该是什么样”的信息,都必须且只能存在于这个仓库里。任何存在于别处的配置,都是潜在的风险点。
一个符合 GitOps 最佳实践的仓库结构,绝不是随意堆放文件的“大杂烩”。它必须经过精心设计,以支撑可维护性、可审计性和环境隔离。以下是我在线上项目中验证过的、最稳健的目录结构:
my-gitops-repo/ ├── app/ # 应用代码与构建定义 │ ├── llm-gradio/ # 具体的 LLM 应用(Gradio 前端) │ │ ├── main.py # 应用主逻辑 │ │ ├── requirements.txt # Python 依赖 │ │ └── Dockerfile # 构建容器镜像的指令 │ └── model-server/ # LLM 模型推理后端 │ ├── server.py │ └── Dockerfile ├── infra/ # 基础设施即代码 (IaC) │ ├── kubernetes/ # Kubernetes 相关配置 │ │ ├── base/ # 公共基础配置(不直接部署) │ │ │ ├── kustomization.yaml │ │ │ └── deployment.yaml # 通用 Deployment 模板 │ │ ├── overlays/ # 环境覆盖层(关键!) │ │ │ ├── dev/ │ │ │ │ ├── kustomization.yaml # 指定 base + dev 特有 patch │ │ │ │ └── patch-cpu-limit.yaml │ │ │ ├── staging/ │ │ │ └── production/ │ │ └── helm/ # Helm Chart(如果使用) │ └── terraform/ # 云资源(VPC, EKS Cluster, S3 Bucket) │ ├── modules/ # 可复用的模块 │ └── environments/ │ ├── dev/ │ └── prod/ ├── ci/ # CI/CD 流水线定义 │ ├── build-image.yaml # 构建 Docker 镜像 │ └── test-app.yaml # 运行单元测试、集成测试 └── docs/ # 架构决策记录 (ADR)、部署手册 └── adr-001-use-kustomize.md这个结构的核心思想是“分层”与“隔离”。
app/层:专注业务逻辑。Dockerfile 在这里,意味着镜像的构建过程是应用代码的一部分,由应用开发者负责。这保证了“构建什么”和“怎么构建”是内聚的,避免了运维团队和开发团队在镜像构建标准上的扯皮。infra/层:专注基础设施。它又被细分为kubernetes/和terraform/。前者管集群内的资源(Pod, Service, Ingress),后者管集群外的云资源(网络、存储、计算实例)。这种分离,遵循了“关注点分离”(Separation of Concerns)原则,让不同领域的专家各司其职。overlays/目录:这是 Kustomize 的精髓所在,也是环境隔离的关键。base/目录存放所有环境共有的、不变的配置(比如 Deployment 的基本结构、容器端口)。而dev/,staging/,production/目录,则只存放各自环境特有的“补丁”(Patch),比如dev环境的 CPU 限制是100m,production环境是2000m。这样,当你需要为production环境部署时,Kustomize 会自动将base和production的补丁合并,生成最终的、完整的 YAML。最大的好处是:你永远不会因为复制粘贴而忘记修改某个环境的特定参数。
提示:切勿在
base/中硬编码任何环境相关的值(如image: my-app:dev)。所有可变的值,都应该通过kustomization.yaml中的images:字段或patchesStrategicMerge:来注入。这保证了base的纯粹性和可复用性。
另一个至关重要的实操要点是分支策略。我强烈推荐采用main(或master)作为生产环境的“黄金分支”。所有直接合并到main的 PR,都必须经过严格的自动化测试(包括安全扫描、合规检查)和至少两位核心成员的手动审批。对于开发和测试,应使用特性分支(Feature Branch)或develop分支。staging环境的部署,应由develop分支的合并触发。这种策略,将“代码质量”与“部署环境”进行了强绑定,确保了生产环境的稳定。
3.2 GitOps 控制器:Argo CD 与 Flux 的选型实战
当你决定采用拉取模型时,就必须在 Argo CD 和 Flux 这两个主流的 GitOps 控制器之间做出选择。它们都是 CNCF(云原生计算基金会)的毕业项目,功能强大且成熟,但设计理念和使用体验有显著差异。我的选型建议,不是基于抽象的“谁更好”,而是基于你团队的技术栈偏好和运维习惯。
Argo CD是一个“开箱即用”的可视化平台。它的核心优势在于用户体验(UX)和企业级功能。安装后,你会得到一个功能完备的 Web UI,它能清晰地展示:
- 所有被管理的应用(Application)及其在 Git 中的源路径、目标集群、同步状态。
- 应用的健康状态(Healthy, Progressing, Degraded)。
- Git 仓库中声明的期望状态(Desired State)与集群中实际状态(Live State)的逐行 Diff。
- 完整的同步历史、日志和事件。
这对于一个刚接触 GitOps 的团队,或者一个需要向上级汇报、需要审计可视化的组织来说,是巨大的福音。它降低了理解门槛,让“GitOps 是什么”变得一目了然。此外,Argo CD 对多集群管理、RBAC(基于角色的访问控制)、SSO(单点登录)的支持非常完善,是大型企业落地的首选。
Flux则是一个“轻量、原生、Kubernetes-first”的工具。它的设计理念是“尽可能少地引入新概念”,一切皆为 Kubernetes 的 Custom Resource Definition(CRD)。当你安装 Flux,你实际上是在集群里创建了一系列新的 Kubernetes 资源,比如GitRepository(指向你的 Git 仓库)、Kustomization(定义如何从仓库中提取和应用配置)、HelmRelease(管理 Helm Chart)。你用kubectl get kustomization就能看到所有被管理的应用,用kubectl describe kustomization my-app就能看到详细的同步状态和事件。
Flux 的优势在于深度集成和极简心智模型。如果你的团队已经非常熟悉kubectl和 Kubernetes 的原生 API,那么 Flux 的学习曲线几乎是平的。你不需要记住一套新的 CLI 命令或 UI 操作,你就在用你每天都在用的工具。它没有 Web UI(官方不提供,社区有第三方),但这恰恰是它的哲学:GitOps 的真相在 Git 里,不在 UI 里。UI 只是视图,而 CRD 才是事实。
我自己的实践是:在内部 PoC 和小规模项目中,我首选 Flux。因为它部署快(flux bootstrap github一条命令搞定)、调试直观(kubectl就是你的调试器)、与现有 Kubernetes 工作流无缝衔接。而在客户交付项目,尤其是金融、政务等对可视化审计有硬性要求的场景,我则坚定选择 Argo CD。它的 UI 是一个强大的沟通工具,能让非技术人员(如产品经理、合规官)也能快速理解系统的当前状态和变更历史。
无论选择哪一个,有一个共同的、不容忽视的实操要点:控制器自身的配置,也必须被 GitOps 化。也就是说,Argo CD 或 Flux 的安装清单、其管理的Application或Kustomization资源定义,本身就应该存放在 Git 仓库中,并由一个更高层级的 GitOps 控制器(或一个初始的、手动的kubectl apply)来管理。这被称为“GitOps 的递归”(Recursive GitOps),它确保了 GitOps 的根基本身也是可审计、可回滚的。否则,你就陷入了“用 GitOps 管理应用,但用人工管理 GitOps 工具”的悖论。
3.3 CI/CD 流水线:GitHub Actions 的精细化配置
对于推送模型,GitHub Actions 是目前最主流、最易上手的选择。但一个“能跑通”的流水线,和一个“生产就绪”的流水线,中间隔着无数个细节陷阱。下面是我总结的、在 LLM 项目中必须配置的几个关键环节。
1. 镜像构建与安全扫描LLM 应用通常依赖大量 Python 包(如transformers,torch),这些包的供应链安全至关重要。一个简单的build-and-push步骤是远远不够的。
# .github/workflows/ci.yaml name: CI Pipeline on: push: paths: - 'app/**' - 'Dockerfile' jobs: build-and-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # 使用 docker/build-push-action 进行构建,它原生支持 BuildKit - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Container Registry uses: docker/login-action@v3 with: username: ${{ secrets.REGISTRY_USERNAME }} password: ${{ secrets.REGISTRY_PASSWORD }} # 关键:启用 BuildKit,它能进行多阶段构建和缓存优化 - name: Build and push uses: docker/build-push-action@v5 with: context: ./app/llm-gradio push: true tags: ${{ secrets.REGISTRY_URL }}/llm-gradio:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max # 关键:在推送前进行安全扫描 - name: Scan image for vulnerabilities uses: anchore/scan-action@v4 with: image: ${{ secrets.REGISTRY_URL }}/llm-gradio:${{ github.sha }} fail-on: high # 使用 Trivy 作为备选,它更轻量 # uses: aquasecurity/trivy-action@master # with: # image-ref: ${{ secrets.REGISTRY_URL }}/llm-gradio:${{ github.sha }} # format: 'sarif' # output: 'trivy-results.sarif' # severity: 'HIGH,CRITICAL'这段配置的亮点在于:
- BuildKit 缓存:
cache-from和cache-to利用了 GitHub Actions 的缓存机制,使得后续构建能复用之前构建的中间层,极大加速pip install等耗时步骤。 - 安全门禁:
fail-on: high意味着如果扫描出任何 High 级别的漏洞,整个流水线将失败,阻止不安全的镜像被推送到仓库。这是保障 LLM 应用供应链安全的第一道闸门。
2. 环境隔离的部署策略cd.yaml的核心任务,是根据不同的分支或标签,将应用部署到正确的环境。一个常见的错误是,所有环境都用同一个kubectl apply命令,只是替换了-f参数。这极易出错。
# .github/workflows/cd.yaml name: CD Pipeline on: push: branches: - 'main' # main 分支 => production tags: - 'v*' # v1.0.0 标签 => production jobs: deploy-to-production: if: github.event_name == 'push' && (github.head_ref == 'main' || startsWith(github.event.ref, 'refs/tags/v')) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # 使用 kubectl-action,它会自动处理 kubeconfig - name: Deploy to Production uses: azure/k8s-deploy@v4 with: namespace: 'llm-prod' manifests: | infra/kubernetes/overlays/production/ images: | ${{ secrets.REGISTRY_URL }}/llm-gradio:${{ github.sha }}这里的关键是if条件判断和manifests的路径指定。main分支和v*标签都指向production/目录,而develop分支的部署,则会指向staging/目录。这种基于 Git 语义的自动化路由,是实现“环境即代码”的关键。
3. 部署后的健康检查仅仅执行kubectl apply并不意味着部署成功。你需要验证应用是否真的 Ready。
- name: Wait for Deployment to be Ready run: | timeout 300s bash -c " while [[ \$(kubectl get deploy llm-gradio -n llm-prod -o jsonpath='{.status.readyReplicas}') != '1' ]]; do echo 'Waiting for deployment to be ready...' sleep 10 done echo 'Deployment is ready!' "这个简单的 Bash 循环,会等待 Deployment 的readyReplicas达到预期值(这里是 1),超时时间为 5 分钟。如果超时,流水线会失败,提醒你去排查问题。这比让一个“看似成功”的部署流入生产环境要安全得多。
4. 在 LLM 项目中落地 GitOps:从零开始的完整实操
4.1 项目初始化:搭建一个可工作的 GitOps 基础框架
让我们从零开始,亲手搭建一个用于部署 LLM Gradio 应用的 GitOps 项目。这个过程,我会带你走过每一个关键决策点,并解释背后的理由。
第一步:创建 Git 仓库并初始化目录结构在 GitHub 上创建一个名为llm-gitops-demo的新仓库。然后,克隆到本地,并按照前文所述的结构,创建骨架:
git clone https://github.com/your-org/llm-gitops-demo.git cd llm-gitops-demo # 创建标准目录 mkdir -p app/llm-gradio infra/kubernetes/{base,overlays/{dev,prod}} ci docs # 初始化 README echo "# LLM GitOps Demo" > README.md echo "This repo manages the infrastructure and application for our LLM Gradio demo." >> README.md git add . git commit -m "chore: init project structure" git push origin main第二步:编写 LLM 应用代码在app/llm-gradio/目录下,创建一个极简但功能完整的 Gradio 应用。我们不使用真实的 LLM(那会涉及 GPU 和模型下载),而是用一个模拟的响应函数,重点在于验证 GitOps 流程。
# app/llm-gradio/main.py import gradio as gr import time def simulate_llm_response(prompt): """模拟一个 LLM 的响应,加入一点延迟以体现真实感""" time.sleep(1) # 模拟模型推理延迟 return f"🤖 Model response to '{prompt}': This is a simulated answer from our GitOps-managed LLM service." # 创建 Gradio 接口 iface = gr.Interface( fn=simulate_llm_response, inputs=gr.Textbox(lines=2, placeholder="Enter your prompt here..."), outputs="text", title="GitOps-Managed LLM Demo", description="A simple demo showing how GitOps automates LLM application deployment." ) if __name__ == "__main__": iface.launch(server_name="0.0.0.0", server_port=7860)同时,创建requirements.txt和Dockerfile:
# app/llm-gradio/requirements.txt gradio==4.35.0# app/llm-gradio/Dockerfile FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY main.py . EXPOSE 7860 CMD ["python", "main.py"]第三步:定义 Kubernetes 基础配置在infra/kubernetes/base/目录下,创建kustomization.yaml和deployment.yaml。这是所有环境共享的“蓝图”。
# infra/kubernetes/base/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - deployment.yaml - service.yaml - ingress.yaml # 这里定义了所有环境都使用的公共镜像 images: - name: llm-gradio newName: ghcr.io/your-org/llm-gradio newTag: latest# infra/kubernetes/base/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: llm-gradio spec: replicas: 1 selector: matchLabels: app: llm-gradio template: metadata: labels: app: llm-gradio spec: containers: - name: app image: llm-gradio:latest # 这个会被 kustomization.yaml 中的 images 替换 ports: - containerPort: 7860 resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "512Mi" cpu: "500m"第四步:为不同环境创建覆盖层在infra/kubernetes/overlays/dev/目录下,创建kustomization.yaml,它指定了如何基于base构建开发环境的配置:
# infra/kubernetes/overlays/dev/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization bases: - ../../base # 开发环境使用 NodePort,方便本地测试 patchesStrategicMerge: - nodeport-patch.yaml # 开发环境使用不同的镜像标签 images: - name: llm-gradio newTag: dev-${TARGET_COMMIT:-latest}# infra/kubernetes/overlays/dev/nodeport-patch.yaml apiVersion: v1 kind: Service metadata: name: llm-gradio spec: type: NodePort ports: - port: 7860 targetPort: 7860 nodePort: 30080第五步:编写 CI/CD 流水线现在,我们编写ci.yaml,它负责构建、扫描和推送镜像。
# .github/workflows/ci.yaml name: CI Pipeline on: push: paths: - 'app/llm-gradio/**' - 'app/llm-gradio/Dockerfile' jobs: build-and-push: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v5 with: context: ./app/llm-gradio push: true tags: | ghcr.io/your-org/llm-gradio:dev-${{ github.sha }} ghcr.io/your-org/llm-gradio:dev-latest cache-from: type=gha cache-to: type=gha,mode=max - name: Scan image for vulnerabilities uses: anchore/scan-action@v4 with: image: ghcr.io/your-org/llm-gradio:dev-${{ github.sha }} fail-on: medium # 开发环境可以放宽到 medium这个流水线会在每次向app/llm-gradio/目录推送代码时触发,构建一个带有dev-<commit-sha>标签的镜像,并进行安全扫描。整个过程,从代码提交到镜像就绪,通常在 3-5 分钟内完成。
4.2 推送模型的部署:GitHub Actions 自动化上线
CI 流水线完成后,镜像已经躺在 GitHub Container Registry 里了。接下来,我们需要一个 CD 流水线,将这个镜像部署到 Kubernetes 集群。
第六步:配置集群访问为了让 GitHub Actions 能够