每个交付软件的团队最终都会搭建一个staging环境。而每个搭建了staging环境的团队,最终都会发现同一个问题。staging是共享的。一个环境,许多pull request。有人部署了一个半成品功能去测试,另一个人又在上面叠加部署,第三个人跑了数据库迁移。到了周四下午,没人说得清实际运行的到底是哪个分支的哪个版本,"在staging上能跑"这句话已经不再携带任何信息。那些变通办法都很熟悉:一个Slack频道,人们像预订会议室一样认领staging;一个轮换的部署排班;或者再开第二个staging环境,然后是第三个,每一个都比上一个离生产配置更远。
有一套更好的做法,Vercel、Netlify、Shopify这类公司的平台团队已经把它变成了标准:给每一个pull request配一个独立环境。开一个PR,应用的一份全新隔离副本自动部署;再推一个commit,它跟着更新;合并或关闭PR,整套东西自己删掉。不排队,不认领,不用清理,也不会漂移。这正是本教程要构建的东西。
![]()
从评审者的视角看,它长这样:pull request下面出现一条机器人评论,里面是一个能直接点开的可用URL。
你将构建什么
读完这本手册,你会得到一个可运行的系统,其中:
打开一个pull request会触发GitHub Actions工作流,构建一个专门为该PR打标签的容器镜像;镜像被推送到Google Artifact Registry;在GKE集群里创建一个专属的Kubernetes命名空间(pr-42、pr-43,以此类推),部署任何东西之前先套上资源配额;应用部署进这个命名空间,和其他所有PR的环境完全隔离;一个共享的Ingress控制器把pr-42.preview.yourdomain.com路由到对应命名空间,TLS证书自动签发;机器人把可用URL评论到pull request上,每次push更新同一条评论;推新commit,环境用更新后的代码重新部署;关闭或合并PR,命名空间连同里面的一切被删除;还有一个定时任务兜底,清理任何被遗漏的环境。
底层基础设施用Terraform搭建,其中三个关键设计决策各自独立成节。
不用服务账号密钥:GitHub Actions通过Workload Identity Federation向Google Cloud认证,用的是短时效的OIDC令牌,对照仓库声明做验证。把服务账号JSON密钥塞进GitHub secrets,是当下CI/CD流水线里最常见的安全问题之一,而这里根本不需要。
一个负载均衡器,而不是每个PR一个:单个共享Ingress控制器按主机名路由所有环境,一条通配符DNS记录覆盖未来会出现的每一个主机名。天真的做法是给每个环境配一个云负载均衡器,账单会涨得很快。
清理逻辑假设失败一定会发生:PR关闭时触发拆除,但另有一个定时回收器,删除那些PR已消失或超过最大存活时长的命名空间。因为即便自动化清理有95%的有效率,剩下的5%依然会泄漏,而泄漏最终会出现在账单上。
下面是我们要实现的架构:
这张图追踪了两条独立路径穿过同一套系统。实线蓝色路径是自动化:pull request触发GitHub Actions,通过Workload Identity Federation向Google Cloud认证,把打了PR标签的镜像推送到Artifact Registry,再部署进GKE集群里的专属命名空间。每个命名空间(pr-42、pr-43……)持有自己的Deployment、Service和Pod,彼此完全隔离。旁边还有两个自动化组件:一个在pull request上发布并更新预览URL的机器人,一个查找并删除孤儿命名空间的定时回收器。
虚线绿色路径是公网流量:浏览器请求预览URL,经一条通配符DNS记录解析到单个Google Cloud负载均衡器,再交给共享的NGINX Ingress控制器。控制器读取请求的Host头,把它路由到正确的命名空间。cert-manager在旁边运行,向Let's Encrypt申请证书,让每个环境拿到自己的有效TLS证书。
关键细节在于什么没有被复制:一个负载均衡器、一条DNS记录、一个Ingress控制器,服务所有环境,无论同时开着多少个pull request。
目录
你将构建什么 / 前置条件 / 第1步:搭建项目结构 / 第2步:编写应用 / 第3步:编写Dockerfile / 第4步:用Terraform配置基础设施 / 第5步:用Workload Identity Federation设置无密钥认证 / 第6步:应用Terraform配置 / 第7步:手动验证生命周期 / 第8步:用Kustomize编写Kubernetes清单 / 第9步:构建启动工作流 / 第10步:构建拆除工作流 / 第11步:开一个Pull Request看它跑起来 / 第12步:预留静态IP地址 / 第13步:安装Ingress控制器 / 第14步:把通配符DNS指向负载均衡器 / 第15步:安装cert-manager并创建ClusterIssuer / 第16步:在基础清单里加入Ingress / 第17步:按Pull Request打补丁修改主机名 / 第18步:用ResourceQuota和LimitRange加上护栏 / 第19步:更新部署工作流 / 第20步:在Pull Request上评论URL / 第21步:构建回收器 / 第22步:测试整套系统 / 这套东西的成本 / 清理 / 结语 / 项目文件结构
前置条件
开始之前,确认你已准备好以下内容:
一个启用了计费的Google Cloud项目。如果没有,去console.cloud.google.com创建。运行本教程大约花费5到10美元,取决于你把集群开着多久。不要跳过最后的清理部分。已安装并完成认证的gcloud CLI。运行gcloud auth login登录,运行gcloud config set project YOUR_PROJECT_ID把它指向你的项目。Terrafo
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.