ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

使用 Google Cloud Run 与 Redis 构建高可用、可水平扩展的 Atlantis 部署架构

使用 Google Cloud Run 与 Redis 构建高可用、可水平扩展的 Atlantis 部署架构 DevOpsCI/CD基础设施【免费下载链接】atlantisTerraform Pull Request Automation项目地址https://gitcode.com/gh_mirrors/at/atlantis点击查看免费下载本文基于 runatlantis.io 官方博客《Atlantis on Google Cloud Run》整理并辅以当前仓库源码佐证完整讲解如何将 Terraform Pull Request Automation 工具 Atlantis 从自管 VM 迁移到 Google Cloud Run 这类无服务器容器平台以 Redis 作为集中式锁后端、以共享负载均衡器统一路由多个实例、并用服务账号模拟impersonation实现最小权限隔离。读完本文你将掌握一套可复制的、消除单点故障并支持横向扩容的 Atlantis 部署方案并理解其底层锁数据库切换机制。::: info 虽然本文以 Google Cloud Run 为例编写该部署架构同样适用于 AWS Fargate、Azure Container Instances 与 Kubernetes。 :::本文覆盖 Terraform 代码中最关键的部分完整的可运行示例位于 runatlantis 社区的 atlantis-contrib 仓库atlantis-on-cloud-run目录建议配合阅读。为什么不再用自管 VM大多数 Atlantis 部署运行在自管 VM 上。这一方案虽然熟悉且直接却伴随一系列挑战身份权限过强VM 服务账号往往通过直接授权或模拟impersonation间接拥有大量资源与项目的访问权缺少高可用Atlantis 默认把锁后端直接写到磁盘BoltDBVM 一旦宕机Atlantis 便不可用单点故障单一实例既无法横向扩容也带来单点风险持续运维负担打补丁、操作系统升级、备份等运维工作长期存在。本文展示的替代方案是在 Google Cloud Run 这类无服务器容器平台上运行多个 Atlantis 实例使用集中式数据库Redis存放锁并把多个实例放到负载均衡器后面。每个实例以自己的身份、有限的权限运行只管理属于自己的项目。这一架构消除了单点故障、支持水平伸缩并落地了最小权限least-privilege安全模型。我们要构建什么整体架构如下外部 HTTP(S) 负载均衡器负责把请求路由到多个 Atlantis 实例多个 Atlantis 实例运行在 Google Cloud Run 上Memorystore for Redis 实例提供集中式锁后端让多个实例协调锁、避免冲突。我们略过的内容为控制篇幅原文刻意省略了部分重要细节网络搭建、DNS、Docker 镜像拉取、通配符 TLS 证书以及 Atlantis 的每一个配置旋钮——本文聚焦于与架构最相关的部分。需要特别强调的是强烈建议把 Atlantis 部署在隔离的 VPC 中并开启 Private Service Access。这样 Atlantis 只与 Google API 通信来完成工作不会触达你的其他基础设施。BoltDB只有一个写入者时很棒Atlantis 默认使用 BoltDB 作为锁后端。BoltDB 是一个简单、内嵌的键值存储直接写入磁盘server/server.go中boltdb.New(userConfig.DataDir)即把数据目录作为参数传入。它适合单实例部署但把 Atlantis 锁死在单节点架构上——要获得高可用与水平伸缩必须把它替换为多个 Atlantis 实例可以安全共享的托管分布式数据库。从当前仓库源码可以确认默认值cmd/server.go中DefaultLockingDBType boltdb即不显式配置时 Atlantis 走 BoltDB 分支。服务端在启动时按--locking-db-type的值做分支选择见 server/server.goredis创建 Redis 数据库客户端支持单节点与集群两种模式boltdb在数据目录下创建内嵌 BoltDB。一个“管理一切”的 Atlantis为了便于创建和管理多个 Atlantis 实例可以额外部署一个专用的“管理management”Atlantis 实例负责其他 Atlantis 实例的生命周期管理创建、更新、删除。作者通常把它放在独立的 Google Cloud 项目如atlantis-mgmt中并配一个专门的 Git 仓库。只要搭好一个实例复制它并放到共享负载均衡器之后即可后续复制成本很低。Redis分布式锁后端自 Atlantis v0.19.0 起Redis 就是受支持的锁后端。Redis 是内存数据结构存储这里用作多个 Atlantis 实例的中央锁后端每个实例连接同一个 Redis 实例从而协调锁、避免冲突。Redis 还支持持久化RDBRedis Database按指定间隔对数据集做时间点快照AOFAppend Only File记录 Redis 服务器收到的每次写操作。这意味着即使 Redis 实例重启锁也不会丢失。要创建的第一个资源就是 Redis 实例resource google_redis_instance atlantis { name atlantis tier STANDARD_HA redis_version REDIS_7_2 memory_size_gb 1 region your-region authorized_network your-network-id connect_mode PRIVATE_SERVICE_ACCESS persistence_config { persistence_mode RDB rdb_snapshot_period TWENTY_FOUR_HOURS } maintenance_policy { # ... } project your-project-id lifecycle { prevent_destroy true } }关键点该实例拥有1 GiB内存开启RDB持久化每24 小时做一次快照connect_mode PRIVATE_SERVICE_ACCESS使 Redis 只能在 VPC 内部访问——创建实例前必须先为 VPC 配置好 Private Service Accesstier STANDARD_HA提供高可用副本prevent_destroy true保护锁数据不被意外销毁。源码视角Atlantis 如何连接 Redis从源码看Redis 后端完整实现位于 server/core/redis/redis.go。其Config结构体支持Hostname、Port、Password、Username、TLSEnabled、InsecureSkipVerify、DB与ClusterAddressesredis.go。NewWithConfig会做如下选择若ClusterAddresses非空 → 使用 Redis Cluster 客户端否则 → 使用单节点客户端host:port并执行Ping校验连接可用性。server/server.go在switch userConfig.LockingDBType中把ATLANTIS_REDIS_HOST、ATLANTIS_REDIS_PORT、ATLANTIS_REDIS_PASSWORD、ATLANTIS_REDIS_USERNAME、ATLANTIS_REDIS_TLS_ENABLED、ATLANTIS_REDIS_INSECURE_SKIP_VERIFY、ATLANTIS_REDIS_DB、ATLANTIS_REDIS_CLUSTER_ADDRESSES等配置装配进redis.NewWithConfigserver/server.go。这些 flag 的定义与默认值位于 cmd/server.go配置项环境变量 / flag默认值说明--locking-db-typeATLANTIS_LOCKING_DB_TYPEboltdb锁数据库类型boltdb/redis--redis-hostATLANTIS_REDIS_HOST无Redis 主机名锁类型为 redis 时使用--redis-portATLANTIS_REDIS_PORT6379Redis 端口--redis-dbATLANTIS_REDIS_DB0使用的 Redis 逻辑数据库编号--redis-passwordATLANTIS_REDIS_PASSWORD无Redis 密码--redis-usernameATLANTIS_REDIS_USERNAME无Redis 用户名--redis-tls-enabledATLANTIS_REDIS_TLS_ENABLEDfalse是否启用 TLS 连接--redis-insecure-skip-verifyATLANTIS_REDIS_INSECURE_SKIP_VERIFYfalse是否跳过 TLS 证书校验--redis-cluster-addressesATLANTIS_REDIS_CLUSTER_ADDRESSES无逗号分隔的集群节点地址host:port设置后启用集群模式从 cmd/server.go 的 flag 描述可以看到--locking-db-type用于“存储 plan/apply 锁的锁数据库类型”--redis-host等仅在锁类型为redis时生效。锁键的存储设计也值得了解项目锁键格式为pr/{repoFullName}/{path}/{workspace}/{projectName}由models.GenerateLockKey生成见 redis.goTryLock通过GETSET实现原子加锁redis.goUnlockIfOwnedByPull用一段 Lua 脚本保证“只有该 PR 持有的锁才被删除”的原子性redis.go。此外启动时NewWithConfig还会执行一次旧格式锁键到新格式的迁移使用Scan而非Keys以兼容 Redis Cluster迁移失败不会阻塞启动会在下次启动重试redis.go。部署到 Cloud RunCloud Run 是无服务器容器平台自动伸缩应用、处理 HTTP 请求、抽象底层基础设施只按实际使用的计算资源计费每个服务运行在单一服务账号下由该账号定义其权限——非常适合部署一个或多个 Atlantis 实例。服务端配置 atlantis/management.yaml先创建服务端 Atlantis 配置atlantis/management.yamlrepos: - id: github.com/acme/example apply_requirements: [approved, mergeable] import_requirements: [approved, mergeable] allowed_overrides: [workflow] allowed_workflows: [example] delete_source_branch_on_merge: true workflows: example: plan: steps: - init - plan apply: steps: - apply这里用allowed_workflows把仓库限定到名为example的工作流并设置了apply_requirements: [approved, mergeable]需批准且可合并等门槛。创建管理实例的 Cloud Run 服务下面的 Terraform 配置只突出与本文架构相关的 Atlantis 环境变量完整可运行示例见 atlantis-contrib 仓库的atlantis-on-cloud-run目录。resource google_cloud_run_v2_service atlantis_management { provider google-beta name atlantis-management location your-region deletion_protection false ingress INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER invoker_iam_disabled true launch_stage GA template { scaling { min_instance_count 1 max_instance_count 1 } execution_environment EXECUTION_ENVIRONMENT_GEN2 service_account google_service_account.atlantis_management.email containers { image ghcr.io/runatlantis/atlantis:v0.35.1 resources { limits { cpu 1 memory 2Gi } } volume_mounts { name atlantis mount_path /app/atlantis } env { name ATLANTIS_PORT value 8080 } env { name ATLANTIS_DATA_DIR value /app/atlantis } env { name ATLANTIS_USE_TF_PLUGIN_CACHE value true } env { name ATLANTIS_LOCKING_DB_TYPE value redis } env { name ATLANTIS_REDIS_HOST value google_redis_instance.atlantis.host } env { name ATLANTIS_REDIS_DB value 0 } env { name ATLANTIS_ATLANTIS_URL value https://management.atlantis.acme.com } env { name ATLANTIS_REPO_CONFIG_JSON value jsonencode(yamldecode(file(${path.module}/atlantis/management.yaml))) } } vpc_access { egress ALL_TRAFFIC network_interfaces { network your-network-id subnetwork your-subnetwork-id } } volumes { name atlantis empty_dir { medium MEMORY size_limit 5Gi } } } project your-project-id }这些环境变量与源码中的配置项一一对应ATLANTIS_LOCKING_DB_TYPEredis等价于--locking-db-type redis触发 server/server.go 中的 Redis 分支使锁后端从 BoltDB 切换到 RedisATLANTIS_REDIS_HOST/ATLANTIS_REDIS_DB指定集中式锁数据库的地址与逻辑库--redis-host/--redis-db默认库为 0见 cmd/server.goATLANTIS_REPO_CONFIG_JSON把management.yaml先yamldecode再jsonencode注入为服务端仓库配置对应源码中的--repo-config-jsonuser_config.goATLANTIS_DATA_DIR/app/atlantis数据目录指向挂载的临时卷ATLANTIS_USE_TF_PLUGIN_CACHEtrue开启 Terraform provider 插件缓存对应--use-tf-plugin-cachecmd/server.go减少重复下载。ingress INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER让服务只接受来自内部负载均衡器的流量vpc_access把出站流量全部导向 VPC 网络确保 Atlantis 只按需访问 Google API。临时存储Ephemeral StorageAtlantis 是 I/O 密集型应用它会 checkout 仓库、运行 Terraform 命令、下载 provider、拉取模块因此需要可写的文件系统存放临时数据。在 Cloud Run 上这由临时存储承担——容器实例停止或重启时会被清空。好在 Atlantis 的运行方式决定了其临时存储需求有限PR 数据在合并或关闭后即从文件系统移除provider 可以用ATLANTIS_USE_TF_PLUGIN_CACHE缓存Terraform 二进制已包含在容器镜像中。因此配置一个5 GiB 的内存empty_dir卷挂载到/app/atlantis并设为ATLANTIS_DATA_DIR# ... volumes { name atlantis empty_dir { medium MEMORY size_limit 5Gi } } # ... volume_mounts { name atlantis mount_path /app/atlantis } # ... env { name ATLANTIS_DATA_DIR value /app/atlantis } env { name ATLANTIS_USE_TF_PLUGIN_CACHE value true }内存卷速度快且不依赖持久磁盘由于锁等持久状态已迁往 Redis临时存储清空不会影响可用性。让实例保持“温热”Cloud Run 实例在不使用时可以缩容到零这会导致冷启动以及内存临时存储的丢失。为避免这一点设置min_instance_count 1保证至少一个实例始终运行、随时可以处理请求scaling { min_instance_count 1 max_instance_count 1 }模拟身份与最小权限Impersonation and Least Privilege每个 Cloud Run 服务都运行在一个定义其身份的服务账号下。与其给这个账号过大的权限不如使用一个只拥有“模拟其他更受限服务账号”权限的服务账号。对管理 Atlantis 实例来说不必如此它本来就只部署在单个项目上但对那些管理多个项目/环境的其他 Atlantis 实例来说这一点至关重要。通过模拟身份可以确保每个 Atlantis 实例只拥有管理其特定项目所需的权限。典型做法创建一个基础 Atlantis 服务账号atlantis-example再为每个环境创建独立服务账号如atlantis-example-dev、atlantis-example-prod。基础账号在这些环境账号上被授予roles/iam.serviceAccountTokenCreator角色并在 Atlantis 运行 Terraform 命令时模拟它们反过来这些环境专属账号只被授予其管理项目所需的权限。这样即使某个 Atlantis 实例被攻破爆炸半径也仅限该实例管理的资源。Terraform 配置示例locals { atlantis_network_service_accounts [ atlantis-example-dev, atlantis-example-prod, ] } # Base Atlantis service account resource google_service_account atlantis_example { account_id atlantis-example project local.project_id } # Per-environment service accounts resource google_service_account atlantis_example_service_accounts { for_each toset(local.atlantis_example_service_accounts) account_id each.value project local.project_id } # Allow base SA to impersonate the env-specific ones resource google_service_account_iam_member atlantis_example_impersonation { for_each google_service_account.atlantis_example_service_accounts service_account_id each.value.name role roles/iam.serviceAccountTokenCreator member serviceAccount:${google_service_account.atlantis_example.email} }模拟本身通过在atlantis/example.yaml工作流中设置GOOGLE_IMPERSONATE_SERVICE_ACCOUNT环境变量启用。在该配置中Atlantis 管理dev与prod两个环境——在plan和apply期间切换到对应的环境服务账号repos: - id: github.com/acme/example apply_requirements: [approved, mergeable] import_requirements: [approved, mergeable] delete_source_branch_on_merge: true allowed_overrides: [workflow] allowed_workflows: [example-dev, example-prod] workflows: example-dev: plan: steps: - env: name: GOOGLE_IMPERSONATE_SERVICE_ACCOUNT value: example-devacme-atlantis-mgmt.iam.gserviceaccount.com - run: rm -rf .terraform - init: extra_args: [-lockfalse, -backend-configenv/dev/backend-config.tfvars] - plan: extra_args: [-lockfalse, -var-fileenv/dev/vars.tfvars] apply: steps: - env: name: GOOGLE_IMPERSONATE_SERVICE_ACCOUNT value: example-devacme-atlantis-mgmt.iam.gserviceaccount.com - apply: extra_args: [-lockfalse] example-prod: plan: steps: - env: name: GOOGLE_IMPERSONATE_SERVICE_ACCOUNT value: example-prodacme-atlantis-mgmt.iam.gserviceaccount.com - run: rm -rf .terraform - init: extra_args: [-lockfalse, -backend-configenv/prod/backend-config.tfvars] - plan: extra_args: [-lockfalse, -var-fileenv/prod/vars.tfvars] apply: steps: - env: name: GOOGLE_IMPERSONATE_SERVICE_ACCOUNT value: example-prodacme-atlantis-mgmt.iam.gserviceaccount.com - apply: extra_args: [-lockfalse]工作流中env步骤对应 Atlantis 自定义工作流的envstep实现见 server/core/runtime/env_step_runner.go它会在执行 Terraform 命令前注入环境变量。这里还演示了init/plan/apply的extra_args用法通过-backend-config与-var-file为不同环境传入不同的后端配置与变量文件并用-lockfalse避免在多实例架构下与集中锁产生冗余交互。共享负载均衡器运行多个 Atlantis 实例各自负责不同的项目或环境时关键是把流量路由到正确的实例。与其给每个实例独立的公网端点不如通过一个全局 HTTPS 负载均衡器集中流量。下面的负载均衡器使用基于主机的路由按子域名把请求分发到对应实例。例如network.atlantis.acme.com的请求被路由到管理 network 项目的 Atlantis 实例workloads.atlantis.acme.com则进入管理 workload 项目的实例。resource google_compute_url_map atlantis { name atlantis default_url_redirect { host_redirect atlantis.acme.com https_redirect true redirect_response_code MOVED_PERMANENTLY_DEFAULT strip_query false } host_rule { hosts [network.atlantis.acme.com] path_matcher atlantis-network-webhooks } host_rule { hosts [workloads.atlantis.acme.com] path_matcher atlantis-workloads-webhooks } host_rule { hosts [management.atlantis.acme.com] path_matcher atlantis-management-webhooks } path_matcher { name atlantis-network-webhooks default_service google_compute_backend_service.atlantis_network.id path_rule { paths [/events] service google_compute_backend_service.atlantis_network_webhooks.id } } path_matcher { name atlantis-workloads-webhooks default_service google_compute_backend_service.atlantis_workloads.id path_rule { paths [/events] service google_compute_backend_service.atlantis_workloads_webhooks.id } } path_matcher { name atlantis-management-webhooks default_service google_compute_backend_service.atlantis_management.id path_rule { paths [/events] service google_compute_backend_service.atlantis_management_webhooks.id } } project your-project-id } resource google_compute_ssl_policy restricted { name restricted profile RESTRICTED min_tls_version TLS_1_2 project your-project-id } resource google_compute_target_https_proxy atlantis { name atlantis url_map google_compute_url_map.atlantis.id ssl_certificates [ google_compute_managed_ssl_certificate.atlantis_network.id, google_compute_managed_ssl_certificate.atlantis_workloads.id, google_compute_managed_ssl_certificate.atlantis_management.id, ] ssl_policy google_compute_ssl_policy.restricted.id project your-project-id } resource google_compute_global_forwarding_rule atlantis { name atlantis target google_compute_target_https_proxy.atlantis.id port_range 443 ip_address google_compute_global_address.atlantis.address load_balancing_scheme EXTERNAL_MANAGED project your-project-id }配置要点default_url_redirect把未知主机重定向到atlantis.acme.com并强制 HTTPS每个主机规则对应一个path_matcher其中把/events路径单独路由到 webhook 专用后端使用RESTRICTED配置文件、TLS_1_2最低版本的 SSL 策略全局转发规则监听 443绑定托管证书与固定 IP。为什么每个实例需要两个后端每个 Atlantis 实例在负载均衡器中被注册为两个独立的 backend service一个服务主 HTTP 端点另一个服务/eventswebhook 端点。这种分离的意义在于主界面可以放在 Identity-Aware ProxyIAP后面只有授权用户才能访问webhook 端点保持公网可达让 GitHub 或 GitLab 能无阻碍地投递事件。示例workloads 实例resource google_compute_backend_service atlantis_workloads { name atlantis-workloads protocol HTTP port_name http timeout_sec 30 load_balancing_scheme EXTERNAL_MANAGED security_policy google_compute_security_policy.atlantis.id backend { group google_compute_region_network_endpoint_group.atlantis_workloads.id } iap { enabled true } project your-project-id } resource google_compute_backend_service atlantis_workloads_webhooks { name atlantis-workloads-webhooks protocol HTTP port_name http timeout_sec 30 load_balancing_scheme EXTERNAL_MANAGED security_policy google_compute_security_policy.atlantis_events_webhook.id backend { group google_compute_region_network_endpoint_group.atlantis_workloads.id } project your-project-id }可见主后端开启了iap { enabled true }webhook 后端则不开启。保护 /events 端点强烈建议用安全策略security policy保护/events端点阻挡非法流量至少限制为 Git 提供商使用的 IP 网段并拦截一些常见的 Web 漏洞模式可参考 Cloud Armor 预置 WAF 规则。Atlantis 的事件路由入口在仓库中对应 server/controllers/events/events_controller.go/events端点由 server/router.go 注册它会校验 webhook 请求并把 GitHub、GitLab 等平台的事件转发给命令执行管线因此该端点必须既能被 Git 平台访问又要受到安全策略约束。总结把 Atlantis 部署在 Google Cloud Run 上配合共享的 Redis 锁后端与共享负载均衡器可以交付一个高可用、可水平伸缩、安全的 Atlantis 部署高可用锁与状态存于托管 Redis带持久化与高可用副本不再依赖单机磁盘水平伸缩多个实例共享同一锁后端负载均衡器按子域名分发请求最小权限每个实例以独立服务账号运行通过 impersonation 只获得管理自身项目所需的最小权限缩小爆炸半径无服务器运维Cloud Run 自动扩缩容、按用量计费无需维护虚拟机。由于篇幅所限本文并未覆盖全部细节网络、DNS、证书等。完整的可运行 Terraform 示例请查阅 atlantis-contrib 仓库的atlantis-on-cloud-run目录社区作者也曾在公开演讲中介绍过该架构。相关讨论可以到 Cloud Native Computing Foundation Slack 工作区的 #atlantis 频道进行。赞分享DevOpsCI/CD基础设施【免费下载链接】atlantisTerraform Pull Request Automation项目地址https://gitcode.com/gh_mirrors/at/atlantis点击查看免费下载相关推荐如何用Genkit和Google Cloud Run构建高扩展AI服务容器化部署终极指南如何用Genkit和Google Cloud Run构建高扩展AI服务容器化部署终极指南 Genkit是一款开源AI应用框架它让开发者能够使用熟悉的代码模式人工智能大模型后端AI AgentRAG工具调用企业级Kubernetes安全防护Kubescape高可用部署架构全解析企业级Kubernetes安全防护Kubescape高可用部署架构全解析 Kubescape作为开源Kubernetes安全平台提供从IDE到CI/CD流水网络安全云原生应用安全运维StreamEx高级特性完全手册掌握前缀操作、折叠和自定义收集器的终极指南StreamEx高级特性完全手册掌握前缀操作、折叠和自定义收集器的终极指南 StreamEx作为Java Stream API的增强库为开发者提供了更强大、开发工具上一篇pkg 错误代码完全指南10个常见 exit code 含义与快速解决方法下一篇3000亿参数MoE模型开源ERNIE 4.5如何重塑AI产业格局创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表