我要谈论的是关于 Terraform,我将会告诉你它所涉及的所有内容
※ 2020-12-22 附录更新
首先 .
本文是2020年GLOBIS圣诞日历-17号的Qiita文章。
GLOBIS SRE团队自2020年初开始致力于完全改进基础设施环境,采用Kubernetes(Amazon EKS)。在这个全新的环境中,我们设立了以基础设施即代码为目标的目标,将环境的90%进行代码化,积极利用Terraform。
在这篇文章中,我们将总结如何在我们团队中使用 Terraform。因为一旦开始写作,想要表达的事情就像泉水一样涌现出来,所以我尝试以常见问题的形式进行总结。请随意浏览并选择您感兴趣的部分。
Terraform 一般可以用来
你使用 Terraform 管理什么?
因为AWS是主要环境,所以我编写的Terraform中大约有百分之八到九是针对AWS的。其余一小部分是Datadog的监视设置,还有一点点GCP的IAM管理。
Q. 亚马逊 EKS 的配置是怎样的?
大致上就是这样的。

AWS账户根据生产环境、预发布环境和开发环境的单位进行分割。每个环境都有一个EKS集群,并在各个产品之间共享。尽管图中未显示,但产品间通过命名空间进行分离。
谁在编写 Terraform?
我们是SRE团队的所有成员共同撰写的。目前几乎没有非SRE工程师参与撰写。
然而,例如 IAM 用户的授权委托等,有一些由团队之外的”请求”触发 SRE 对 Terraform 的操作。我们正在尝试将这些逐渐转变为 Terraform 的 PR 来接收,也就是说,我们正在试图改变这种情况。
你为什么选择了Terraform?
由于它接近事实标准,所以在选择上很少进行比较考虑。作为可替代的工具,我认为可以考虑 CloudFormation (CFn)、 aws-cdk、pulumi 等,但由于没有遇到太多的问题,所以似乎没有考虑迁移。当生态系统变得如此庞大时,似乎很难摆脱其中的舒适感。
我从来没有正式接触过CDK,但我对使用真正的编程语言编写的 code as code 有些好奇。至于CFn,个人认为它不是直接使用的工具,而是逐渐成为被CDK或Serverless Framework等间接调用的工具。
将Terraform责任划分清楚
Kubernetes和Terraform如何进行区分?
在Kubernetes(k8s)清单和Terraform中,存在冲突的可管理资源。如果使用AWS Service Operator,可以在k8s清单中管理AWS资源,而使用Kubernetes Provider则可以实现相反的功能。
因此,Terraform需要定义与其他工具的责任界限来管理什么?但在我们的团队中,简单来说,我们遵循以下原则。
-
- k8s manifest で AWS リソースは管理しない
- Terraform で k8s のレイヤーは管理しない
理由是因为生命周期不同。我们的团队将EKS集群视为一次性使用的。我们以B/G部署的概念进行Kubernetes版本升级,每三个月创建一个新的集群并进行迁移。与EKS集群外的Aurora、S3和ALB等资源不必共享相同的生命周期和命运。因此,我们决定不在k8s上管理AWS资源,这些资源位于集群之外。
一个明显的例子是选择不采用ALB Ingress Controller。由于想要在ALB下进行B/G部署,需要避免ALB与k8s的生命周期绑定的情况。作为替代方案,不得已使用了NodePort,因此AWS Load Balancer Controller的出现真是一个好消息。
Q. k8s 和 Terraform 之间会发生依赖关系吗?
有一定的可能性。
比如,我们将 Secrets 的管理结合了 Kubernetes External Secrets 和 AWS Parameter Store 的 SecureString。虽然我们调用了 external secrets,但有时因为还没有配置关联的 Parameter Store,会导致 Secrets 无法生成。不过,这并不是一个很大的问题。
我认为,随着允许将安全组设置到Pod中以及Kubernetes和AWS资源之间的边界始终变动不居,我们将不断思考如何使两者相互依赖和管理,并在运营中持续进行。
Terraform(状态)如何进行分割?
在Terraform社区中,状态分割边界一直是一个热门话题。总的来说,它的配置非常简单,如下所示。
├── account_bootstrap
│ ├── dev
│ ├── modules
│ ├── prod
│ └── stg
├── base
│ ├── dev
│ ├── modules
│ ├── prod
│ └── stg
├── product_foo
│ ├── dev
│ ├── modules
│ ├── prod
│ └── stg
└── product_bar
├── dev
├── modules
├── prod
└── stg
产品相关资源以开发环境、测试环境和生产环境的形式进行分割。基本上只有这些。虽然经常听说在同一个产品中,会根据生命周期将状态分离,但由于频繁更改的资源现在存在于Kubernetes上,因此即使没有这个视角,现状也没有太多问题。
除了产品之外,还存在两个选项:account_bootstrap和base,它们都负责跨产品提供AWS账户的基础设施支持。account_bootstrap应该在创建AWS账户后的第一时间运行,其中包含预算警报和AWS配置等设置。base则是基础资源,如EKS集群等。我记得有团队成员说不太明白它们的区别,但例如在AWS组织的根账户上,account_bootstrap是必需的,但不需要托管基础设施,因此base是不必要的。
这些都在同一个仓库中进行管理。唯一与其他仓库分离出来的是与基础设施完全不同的 IAM 用户的生命周期。
你在使用Terraform时有没有管理AWS资源之外的资源?
这几乎没有。不应该有。由于AWS Chatbot等API没有公开,甚至有一些服务已经放弃了引入(正在等待API公开)。这就是Terraform管理的基本原则。
当Terraform无法提供支持,必须手动操作时,我会在确认了团队成员的意见后进行操作,并留下操作步骤。
运行Terraform
Q. 谁执行了terraform apply?
所有的操作都是自动执行的。在我们的团队中,我们尽量避免手动执行 terraform 命令。这是因为手动执行会带来许多繁琐的问题,就如下所示。
-
- Terraform は state にバージョン情報が埋め込まれるので、個人のローカルで実行するとバージョン切り替えの問題が大きい。
- state ごとに適切な AWS profile に切り替えて実行するコストが大きい。
我们使用Terraform Cloud来进行自动化执行。采用该工具的原因是,我们当前的成员数量可以免费开始使用,并且只需将Terraform Cloud与目标GitHub仓库进行关联,无需自己构建流水线,就可以轻松实现自动化执行的便捷性(与PR相匹配的plan将自动执行,与默认分支合并的apply也将自动执行。在执行apply之前,还可以插入批准阶段)。
Q. Terraform Cloud怎么样?
如前所述,可以简单地自动执行非常好。只需引入它,团队的Terraform执行循环就会变得有序。
我有三个主要不满之处。
同时执行限制
同時执行数量非常严格。不仅是针对一个状态的互斥控制,对于其他状态的执行也受到同时执行数量的限制。具体详细信息请参考定价,免费账户无法进行任何同时执行。
因为太累了,所以我现在使用Team & Governance计划。虽然我也曾经询问过更高级的计划的报价,但是考虑到价格问题我选择放弃了。如果你对价格有兴趣的话,可以参考HashiCorp Japan最近提供的参考价格。
- 参考 : Terraformの種類と機能差を解説!価格もわかるよ! – Rumi Oku – Medium
由于迁移到k8s的产品数量尚不多,所以我们勉强能够应付,但如果真的到了无法承受的程度,可能会考虑将流水线迁移到Codebuild或GitHub Actions等平台,这个困难相当大。
工作空间的名称重复问题
在Terraform中,每个Terraform状态都对应一个在Terraform Cloud上创建的执行环境,这被称为工作区(workspace)。值得注意的是,这与terraform workspace命令没有任何关系。在《The Path to Terraform 1.0》中,HashiCorp提到了这个问题,所以我认为它很可能会在某个地方得到解决。
你知道Terraform Cloud中的工作区和Open Source CLI中的工作区是两个不同的东西吗?这是一个痛点。
使用terraform import很麻烦。
由于通常将 Terraform 的执行委托给云端,所以也需要在云端设置 API 密钥等。当在云端执行 plan 或 apply 时,将读取云端的变量。
只有在import时才会出现问题,这个命令是在本地而不是在云端执行,变量将读取云端的变量。只要与Terraform Cloud配合使用,变量将始终优先使用云端的变量,并且无法在本地覆盖。
然而,像 API 密钥这样的敏感变量被注册到 Cloud 上,因此无法从本地访问。 尽管试图读取,但无法读取。 结果就是无法设置 API 密钥,导致导入失败。
我希望进行一些调整,作为避免的策略,需要在提供者处直接嵌入API密钥。并且请优先考虑在本地设置的变量,进行优先级的调整。
provider "aws" {
region = "ap-northeast-1"
# access_key = var.AWS_ACCESS_KEY
access_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
# secret_key = var.AWS_SECRET_KEY
secret_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
2020年12月22日新增记录
经过进一步调查,我发现在 Terraform 中,可以通过环境变量而非 variables 在本地进行覆盖。因此,我们可以删除 provider “aws” 部分中的 access_key 等设置,并在 workspace 中设置 AWS_ACCESS_KEY_ID 等环境变量即可。
然而,如果工作区跨越多个 AWS 账户运行,那么这种方法将无法解决。
中文:在使用Terraform进行CI/CD时,还有其他的实施内容吗?
除了利用Terraform Cloud自动执行”plan apply”外,我们还在创建PR时使用GitHub Actions自动执行”terraform fmt -check”。
写 Terraform
你在使用module吗?
使用中。几乎是自制的私有模块。
你不使用Terraform Registry的公共模块吗?
Terraform Registry 上的模块……我们称之为公共模块,但并不是说没有使用公共模块。例如,terraform-aws-eks 是我初次看到时非常激动的,我现在非常方便地使用它。
大型私有模块成为主要选择之一的主要原因之一是希望尽可能标准化设置。例如,在使用CloudFront时,如果不想使用旧的加密套件,我们可以将最新的加密套件设置为模块的默认值,并以这种方式暗示。换句话说,我们使用私有模块作为我们的最佳实践配置集合。即使没有操作手册或配置文件,使用私有模块也可以自然地创建基础架构。
Q. 刚才,product文件夹里面也有modules吗?
这是关于这里的谈话,对吧。是的,没错。
对于不需要在多个产品之间共享或无需共享的模块,我们并没有将其注册到私有模块注册表中,而是将其直接包含在产品内部。这样做是因为对于这些模块,其变更所带来的影响范围相对较小。
制作一个通用的模块会不会很困难?
很困难。我有意识到了许多事情。
-
- 認知負荷を上げない
-
- 変更容易性を保つ
- 安易な DRY で使わない
然而,要從一開始就實現完全的通用性仍然是困難的,所以我們將其視為一個隨著需求增加而更新模塊的循環。更新頻率相對較高。
A. 如果频繁更新模块,对系统会不会造成较大的影响?
由于模块在多个产品中被广泛使用,仅进行更改会导致影响范围变大。因此,我们使用Terraform Cloud的私有模块注册表。
這本質上是 Terraform Registry 的私有版本。通過在 GitHub 上創建私有模塊的存儲庫並與之集成,可以通過 Terraform Cloud 下載模塊。
module "elasticache" {
source = "app.terraform.io/globis/elasticache/aws"
version = "2.0.0"
}
重点是能够与 git 标签配合,进行版本管理。这样一来,即使模块进行更新,也可以避免立即带来重大影响。另一方面,也确实存在“追踪麻烦”的一面。
更新模块时,您如何进行操作确认?
主要有两个选择。首先,在模块的仓库中,我们将一个虚拟的 terraform 文件作为示例,以该模块为 source。当创建 PR 时,GitHub Actions 会针对这个示例执行 terraform validate,以确保至少通过基本的验证。

另一个功能是版本控制的创新。在前一个问题中,我提到私有模块注册表可以进行版本管理,但它还支持诸如2.0.0-alpha之类的预发布版本的记法。在开发过程中,将其发布为预发布版本,然后在开发环境中验证其行为,如果没有问题,则发布正式版本,这是我们的流程。
Q. 您对无法以平文形式写入HCL的信息该如何处理?
我正在使用 SOPS 这个软件。虽然直接宣传,但请随便来我的博客。
- 参考 : Terraform の秘匿情報を mozilla/sops で管理する | the world as code
Q. 你用什么编辑器?
有一群人們喜歡 VSCode,並有另一群人喜歡 IntelliJ IDEA。我是 VSCode 的派系。terraform-ls 好像逐漸有所改善呢。
Q. 在使用 Terraform 进行编写时,您还有什么特别注意的事项吗?
包括了常听到的一些观点,大致如下所述。
-
- remote state はあまり使わない(参照されている側が、何を参照されているのか把握するのが難しく、依存関係が管理しづらい)
count ではなく for_each を使う
required_version は ~> で指定して、不意のメジャーバージョンアップを防ぐ
function は積極的に使っているが、可読性が極端に落ちそうであれば要相談
关于Terraform,某天会做的。
Q. 你的 Terraform 版本是否跟得上最新的?
我还没做完。是挺麻烦的,不过也并不是什么都没做,目前大致是0.13版本。听说最新版是0.14.2。
只是工作量太大成了瓶颈。是否可以通过tfupdate等方式解决呢?事实并非如此简单。具体步骤如下。
-
- 提高 module 的 required_version。
-
- 提高每个 Terraform 文件所使用的版本。
- 为每个工作区单独提高 Terraform Cloud 在执行时使用的版本。
由于通过云端,需要做很多的操作。特别是通过GUI来点击Terraform云端的版本非常麻烦,所以我做了一个CLI工具(这是第二次宣传)。通过GUI操作云端相当困难,所以另一位成员正在制作一款能够一键完成我们公司设置的命令行工具(尚未发布)。
- 参考 : Terraform Cloud を Alfred や CLI から操作する | the world as code
除此之外,还有提供商的升级版本。虽然很困难,但现在只能说必须去做了。
Q. 你是怎样做测试的?
还没完成。
夢话像Serverspec那样,在Terraform执行后,动态验证AWS资源是否处于预期状态。但我认为这有点困难,所以我认为应该尽快安装类似tflint的静态检查工具。这是明年的任务。
目前的步骤是将其应用于开发环境进行操作验证,如果没有问题,则部署到其他环境。