服务
关于
CloudProse博客
聚光灯

初学者's将Terraform与AWS结合使用的指南

AWS上Terraform的入门技巧,常见问题区域以及我们在内部进行培训的误解。
亚伦·凯托(Aaron Caito)Trek10
亚伦·凯托(Aaron Caito) | 2020年4月6日
18分钟阅读

注意: 这篇文章中的代码参考均已针对进行了更新 版本0.12.x.

在Trek10,我们看到了许多使用云的方法。当然, Hashicorp 地貌 会不时出现。人们正在使用Terraform进行一些令人兴奋的事情,但是我们确实看到了一些未被充分理解的领域中的一些模式以及改进的机会。在这篇文章中,我将分享一些常见的问题领域,并且经常会误解我们在内部进行培训和训练的行为。

在进入优先级检查清单以准备就绪之前,我们应该首先对主要Terraform概念进行基线确定。

一般地形概念

提供者

地貌 is designed around the idea of pluggable providers (written in GoLang) to facilitate the use of the tool for many platforms or systems. The provider is the primary dependency of your project; it creates and manages resources. You can use more than one provider at a time. You want to make sure you are using the same version as the code has been tested or ran with previously. Once you have executed a 地貌 project, it captures the provider version to your terraform.tfstate file to make this easier. We’ll explain how remote states help you stay in sync with your teammates later in this post.

资源资源

地貌代码中定义的对象,这些对象将在提供程序中生成基础结构。资源可以是物理对象或逻辑表示或集合。定义资源块时,请提供资源类型并提供名称。按照约定,源提供者名称在资源类型前面。在资源块中,使用一组参数定义资源的配置。

模组

使用模块,经常使用它们。它们是正确使用Terraform的基本构建块。它们使您可以收集资源并统一构建它们。您定义所需的参数,并返回输出。我确实建议花一些时间来命名您的输出,以找到易于参考的内容,并供大家统一使用。

在这里,我使用一个模块来创建一个组,并在一个呼叫中附加一组用户:

调用模块


$ cat users.tf

variable "iam_users" {
 type = list
 default = ["aaron","brenden","jared","lucas"]
}
 
resource "aws_iam_user" "iam_user" {
 for_each = var.iam_users
 name = each.value
}
 
module "groups" {
 source = "./modules/groups-with-users"
 group = "kernel"
 users = ["aaron","brenden","jared","lucas"]
}

模块定义


$ cat ./modules/groups-with-users/variables.tf

variable "users" {}
variable "group" {}

$ cat ./modules/groups-with-users/main.tf

resource "aws_iam_group" "iam_group" {
  name = var.group
}

resource "aws_iam_group_membership" "group_attachment" {
  users = var.users
  group = aws_iam_group.iam_group.name
}

$ cat ./modules/groups-with-users/outputs.tf

output "group_arn" {
  value = aws_iam_group.iam_group.arn
}

资源图

When you execute terraform plan or terraform apply, 地貌 creates a dependency graph from 所有 files matching *.tf in your current working directory. Keep in mind, the files ingested are only in your current directory. There is no recursion into sub-directories, but you can use this to group your resource definitions logically to make development and troubleshooting tasks easier. For those more familiar with CloudFormation this would be similar to generating a composite template from multiple files before running. It is different from CloudFormation nested stacks because changes are applied to the whole set, not sub-templates. If you need further control, use modules, or split resources into many projects.

If you run into a situation where the graph improperly resolves execution order, there is a depends_on flag to force ordering. While improved in 0.12 there may still be edge cases where depends_on still has difficulties when referenced between modules.

执行计划

When you perform $ terraform plan -out=./out.tfplan you generate an execution plan. This can be used as an input for a subsequent $ terraform apply ./out.tfplan to ensure 地貌 will only execute if the environment is still in sync with what was observed during the last plan. This is important when you have many contributors to a project or you want to leverage a 地貌 CICD pipeline and would like a convenient and safe way to review changes.

当您问人们对Terraform的需求时,在前5名中可以很容易地判断出由于您的行为而发生的事情。在此示例中,您可以看到如果我们应用这些更改将创建的资源片段。

接下来是更多输出,最后是摘要:

To make this work, 地貌 needs a mechanism to know what resources in the target AWS account belong to your current project and which do not. 地貌 records the list of resources and their attributes when you perform $ terraform apply. It then compares your current code (the request) with the target account (current state) and with its last known state (your terraform.tfstate file).

如果您有一个由Terraform管理的项目,请不要执行手动更改(通过console / api / etc)!将所有更改捕获为代码,付出额外的努力是值得的,并且不这样做,会使基础架构的许多好处(如代码或更糟)失效( //charity.wtf/2016/03/30/terraform-vpc-and-why-you-want-a-tfstate-file-per-env/ )。

远程状态

当您使用从一个项目从另一个项目生成的状态时,该状态被视为远程状态。 地貌允许您使用堆栈中的输出为特定值提供众所周知的名称。当您在堆栈之间利用这些值(通常作为参数)时,您正在使用远程状态。这些功能非常强大,是松散耦合组件的方法之一。考虑将您的帐户“准备”操作与设置到该环境中的应用程序分开,以建立基本基础结构,日志记录,安全性,VPC。导出应用程序堆栈的值以使用一致的名称使用,您可以使应用程序具有很高的可移植性,同时允许开发人员专注于其优先级。在AWS中,我们通常将S3用作远程状态存储,您可以像这样引用它:

data "terraform_remote_state" "global" {
 backend = "s3"
 config {
   bucket = var.state_bucket.bucket_name
   key = 'global/terraform.tfstate'
   region = var.state_bucket.region
 }
}

And assuming this remote state outputs a value for state_bucket.name use it like this:

s3_bucket_name = data.terraform_remote_state.global.state_bucket.name

地貌准备情况清单

这篇文章定义 需要,想要,很高兴 类别,以帮助您确定工作的优先级。

需求

版本控制

将Terraform项目存储在版本控制中,例如Git。

.gitignore

You should never commit secrets to your version control, this should include your .tfstate and .tfstate.backup files. I use a service called 吉宝 to make this painless. It becomes as easy as 吉宝 dump terraform >> .gitignore.

Alternatively, you can just add the following to your .gitignore manually.

# Local .terraform directories
**/.terraform/*
 
# .tfstate files
*.tfstate
*.tfstate.*

关于秘密和git的一句话: 如果您担心提交机密(或经常发生“事故”),我强烈建议您检查一下AWS' git-秘密 项目。一种开源的,相当健壮的方法,可以防止不幸的git commit。

机密

It might be tempting to pass your certs, tokens, keys, passwords, and other sensitive data out through 地貌 because of convenience. Avoid this as the data passed in can be caught in your .tfstate file. AWS offers services you can use to distribute secrets, certs, and such to your resources. Look at AWS Systems Manager参数存储 您可以将角色分配到您的资源中,以使他们可以轻松/安全地获取这些秘密!

模块版本控制

使用远程模块时,应对其进行版本控制。

GIT遥控器

如果远程源是git repo,则可以引用标签或在远程位置调用中提交SHA。您应该这样做,因为它允许您在上游更改模块,并且仍然可以控制每个应用程序发布更改。版本控制如下所示:

module "local" {
  source = "git::ssh://[email protected]/trek10/terraform/tf-mod-vpc.git?ref=0.1.4"
   ...
}

地貌注册表

地貌注册表是Hashicorp提供的公共模块注册表。您还可以自托管私人注册表。尽管根据我们的经验,如果您不能使用公共注册表,则使用git的开销较小。如果您使用模块注册表协议作为源,则可以这样使用它:

module "local" {
  source = "trek10/terraform/tf-mod-vpc"
  version = "0.1.4"
   ...
}

远程状态和锁定

供应的资源被捕获到状态文件(* .tfstate);对资源执行操作时,将读取此文件。如果Terraform定义指定了在状态文件中找不到的资源,它将尝试创建它。同样,如果您尝试破坏状态文件中已部署的资源,则会收到警告,提示您不同步。管理状态的正确方法是使用 地形后端,如果您未使用Terraform Enterprise,则在AWS中,建议的后端为S3。如果您有1个以上的人从事同一项目,我们建议还添加一个DynamoDB表进行锁定。请在S3存储桶上启用存储桶版本控制,以避免数据丢失!

注意 在此代码中,我们引用了我们正在创建的资源。这将创建循环依赖关系,因此您必须执行两次;一次创建资源,第二次启用远程后端。对于第一次执行,您必须注释掉后端“ s3”。

terraform {
  required_version = ">= 0.12.0"
  backend "s3" {
    bucket = "bmls-tf-state-bucket"
    key = "base"
    region = "us-east-1"
    dynamodb_table = "terraform_lock_table"
  }
}

provider "aws" {
  region = var.region
}

resource "aws_s3_bucket" "state_bucket" {
  bucket = var.state_bucket.bucket_name
  acl = "private"
  versioning {
    enabled = true
  }
}

resource "aws_dynamodb_table" "terraform_lock_table" {
  name = var.state_bucket.dynamodb_table
  billing_mode = "PAY_PER_REQUEST"
  hash_key = "LockID"
  attribute {
    name = "LockID"
    type = "S"
  }
}
想要

项目组织

当您是高级用户时,Terraform会给您带来很大的灵活性,但入门时可能会有些不知所措。你知道你想保持干燥,实现 高平价,并且您想在12个月内没有接触它时就留下一些易于解决的问题,但是您还不知道项目的发展情况。

可用的许多示例都显示了具有每个环境文件夹的Mono存储库,甚至还有映射到帐户,区域和可用区的文件夹。我将建议避免使用这些模式,因为这会在进行更改时给人为错误带来很大的机会。当您忘记更新区域或区域时会发生什么?

现在(改变主意),我使用2种元模式,具体取决于项目在云原生旅程中的位置。无论哪种情况,我都高度利用模块。

共同要素:

/scripts/ 是我存放脚本,工具和配置的地方
/modules/ 通常是我所有模块定义的存储位置,但是这些都是单独的存储库,但是如果您使用单存储库,它们仍然可以使用,而是使用本地路径来引用它们
/network/ 其中有几个项目用于一次性部署项目,这些项目没有多个目标环境

  • master-payer 是我的根帐户配置
  • master-iam 是用于IAM用户定义的帐户,我们将使用跨帐户角色,而不是在每个帐户中生成用户。
  • master-security 是我的日志的受限访问帐户存储区和工具
  • shared everyone ends up with a shared account, this is mine, I’ll make projects within it for different tooling that is stood up here. Depending on how you handle domain names, you may put your root zone definition here though I generally put that in my /apps/ prod workspace.

IaaS /共享VPC租约模型(大致):

├── apps
│   ├── app-stack-1
│   │   ├── app
│   │   ├── data
│   │   └── etc
│   ├── app-stack-2
│   │   ├── app
│   │   ├── data
│   │   └── etc
│   └── multi-tenant.tf
├── modules
│   ├── tf-mod-3-tier-app
│   ├── tf-mod-s3
│   └── tf-mod-vpc
├── network
│   ├── master-iam
│   ├── master-payer
│   ├── master-security
│   ├── shared
│   │   ├── monitoring
│   │   └── security
│   └── transit
└── scripts

In more traditional environments I generally see “dev”, “stage”, “prod” accounts (or something similar) with a shared VPC with or without many subnet groups that is home to application instances. I do that here with /apps/ by leveraging workspaces. I’ll have my multi-tenant infrastructure file to define networking resources (vpcs, subnets, route tables, etc.) along with any other shared components I need to make this account “ready” for application deployment. We will want to export a lot of values here (or write them to SSM参数存储) this will help us with our application deployment later. The applications are then deployed “onto” this infrastructure and they will use output values from the multi-tenant.tf stack via a remote-state data object to ensure they are correctly provisioned.

云原生/自包含:

├── app-stack-1
│   ├── app
│   ├── data
│   └── etc
├── app-stack-2
│   ├── app
│   ├── data
│   └── etc
├── modules
│   ├── tf-mod-3-tier-app
│   ├── tf-mod-s3
│   └── tf-mod-vpc
├── network
│   ├── master-iam
│   ├── master-payer
│   ├── master-security
│   ├── shared
│   │   ├── monitoring
│   │   └── security
│   └── transit
└── scripts

对于您可能没有很多基础架构的更多云原生方法,通常的做法是定义应用程序堆栈在AWS中需要的所有内容。此应用程序堆栈的任何平台要求都应该是可从SSM参数存储,AWS Config等中检索的配置值。在这种情况下,我们不需要定义多租户基础架构,甚至不需要定义VPC。如果我的应用程序需要它,我应该在应用程序堆栈本身中定义它。我可能希望将我的应用程序中的不同组件划分为单独的terraform项目(或不划分为terraform项目),并且可以根据其需求在项目级别轻松进行配置。

命名

命名是一个微妙的话题,通常会引起一些分歧。通常,刚接触AWS的受众希望在其所有资源上使用特定的资源名称,以便他们可以“查看” AWS控制台中的项目并了解项目之间的关联。不用担心,这很好,并且是成为云原生的学习过程的一部分。您想了解一切之间的联系是一件好事。

与AWS在一起时间较长或处理大量资源的组通常希望使用资源的基本/简单标识符,但可能更多地依赖于标记,操作工具或询问其Terraform堆栈以查找关系。这种转变主要是由于团队在日常工作中较少使用AWS控制台,而是更多地依赖CLI,API,脚本和其他方法来管理其帐户。

无论您偏爱哪种方式,都应采用标记策略,并在名称中保留灵活性,以免造成可怕的名称冲突。我当然是这个的受害者。在Terraform中这样做太容易了,所以从一开始就围绕它进行设计!

地貌 CICD管道

将Terraform部署过程从计算机转移到托管管道中是有好处的。

  • 冗余存储您的工作
  • 减少人为错误的机会
  • 支持自动化繁琐的变更管理流程
  • 使您可以减少访问权限,甚至从部署目标中删除访问权限
  • 将工作从您的计算机转移到云(在应用/销毁过程中曾经断电吗?我不建议这样做)

值得庆幸的是,此过程众所周知,并且那里有模板/样板供您借用。通常,模式应如下所示:

有关如何构建的一些说明:

  • 确保在授予对CodeBuild的访问权限(或您选择的任何权限)时使用的角色,并且未在其中分配完整的访问凭据
  • 保护您的主分支,以避免意外更改
  • 仅接受快速更改
  • Require an execution plan $ terraform plan -out=./out.tfplan in your pull request review
  • 考虑对变更进行测试,以确保一切正常,并为回滚提供简便的方法
  • 利用S3后端DDB锁定表来保护您的状态

工作空间

这描述了工作空间在Terraform CLI中的行为,如果您使用的是Terraform Cloud,则行为是不同的 //www.terraform.io/docs/...

工作区优于保留项目副本(在相同的仓库中或在不同的仓库中)使用它们! //www.terraform.io/docs/state/workspaces.html 您已经在使用默认工作空间,而没有指定它。通过创建与您的SDLC匹配的工作空间来扩展此默认工作空间。您希望在开发环境和产品环境之间保持高度平衡;这很容易:

$ terraform workspace new dev
Created and switched to workspace "dev"!

$ terraform workspace new stage
Created and switched to workspace "stage"!

$ terraform workspace new prod
Created and switched to workspace "prod"!

$ terraform workspace select dev
Switched to workspace "dev".
很高兴有

编辑/ Linting

最受欢迎的编辑器都有Terraform / HCL linting的插件。我们发现这些功能非常有用,建议您安装和使用它们。我对Vscode使用以下扩展名:

Name: 地貌
Id: mauve.terraform
Description: Syntax highlighting, linting, formatting, and validation for Hashicorp's 地貌
Version: 1.4.0
Publisher: Mikael Olenfalk
VS Marketplace Link: //marketplace.visualstudio.com/items?itemName=mauve.terraform
Name: Advanced 地貌 Snippets Generator
Id: mindginative.terraform-snippets
Description: Provides 550+ code snippets of Hashicorp's 地貌 cloud orchestration tool.
Version: 2.1.0
Publisher: Richard Sentino
VS Marketplace Link: //marketplace.visualstudio.com/items?itemName=mindginative.terraform-snippets

特芬夫

地貌版本管理器项目(//github.com/tfutils/tfenv),如果您有许多管理的Terraform项目,则非常有用。对于不熟悉版本管理器的用户,它是一个简单的工具,可让您安装和使用Terraform的多个版本。

为了获得更多的便利和速度,请使Terraform为您自动选择正确的版本。与他人合作以帮助他们避免脚趾受伤时很棒。

$ echo "0.12.23" >> .terraform-version

按照说明进行设置;神奇之处在于通过简单的$ PATH操作来拦截呼叫。

项目设置和执行脚本

Many groups find it beneficial to wrap 地貌 execution with scripts to save time and add safeguards. Examples may be enforcing $ terraform plan before $ terraform apply or selecting the appropriate workspace. I find it helpful to accelerate team members less familiar with 地貌 so they can get started quickly without having to learn when and why they need to call $ terraform init. Here's a simple example:

#! /bin/bash

terraform init
# expect a warning, don't worry it's an idempotent operation
terraform workspace new dev 
terraform workspace select dev
terraform plan
作者
亚伦·凯托(Aaron Caito)Trek10
亚伦·凯托(Aaron Caito)