Terraform简略备忘录:环境构建,Terraform定义的模块化形式,现有资源的导入

我在工作中使用Terraform几个月了。我将它们作为备忘录用途记下来,以便在忘记内容时查看。
由于在工作中主要使用了模块化的文件夹结构,因此我主要记录了这些内容。
(没有提及工作区。)

环境建构

安装AWSCLI

请根据以下内容安装AWSCLI。

设置aws个人资料

请在~/.aws文件夹中创建如下所示的credential和config文件。

[dev]
aws_access_key_id = アクセスキーを設定
aws_secret_access_key = シークレットアクセスキーを設定

通常情况下,可以这样表述。

[dev]
output = json
region = ap-northeast-1

如果使用开关卷,请按照以下方式进行描述。

[dev]
output = json
region = ap-northeast-1

[profile dev_switchroll]
role_arn = arn:aws:iam::アカウントID:role/スイッチロール先で使用するロール名
source_profile = dev       <- スイッチ前のプロファイルを指定
region = ap-northeast-1
output = json

由於設置的配置文件有名稱,因此在使用AWS命令時需要指定–profile選項。

$ aws sts get-caller-identity --profile dev
$ aws sts get-caller-identity --profile dev_switchroll

安装Terraform

使用tfenv可以方便地切换Terraform的版本,这样安装Terraform变得很方便。

# git リポジトリをクローンしてパスを通すだけ。
$ git clone https://github.com/tfutils/tfenv.git ~/.tfenv
$ echo 'export PATH="$HOME/.tfenv/bin:$PATH"' >> ~/.bash_profile

# 再ログインまたは.bash_profileを読み込み設定を反映すると完了。
$ source ~/.bash_profile

执行以下操作,以使得可以使用Terraform命令。

# リモートにあるTerraformのバージョン確認
$ tfenv list-remote

# Terraformのインストール(latestまたはバージョンを指定)
$ tfenv install latest
$ tfenv install バージョン

# インストール済みのTerraformのバージョン確認
$ tfenv list

# Terraformのバージョン切り替え(latestまたはバージョン指定)
$ tfenv use latest
$ tfenv use バージョン

# Terraformのバージョン確認
$ terraform version

创建state文件保存位置

建议将state文件保存在S3上,这样团队成员可以共享。在后面提到的terraform_settings.tf文件中指定保存位置。如果不指定,state文件将会被创建在本地。

以下是一个CloudFormation的简单示例。

AWSTemplateFormatVersion: '2010-09-09'
Description: 'create S3 Bucket'

Parameters:
  Env:
    Type: String
    AllowedValues:
      - none
      - dev
      - stg
      - prd
    Default: none
  Projectname:
    Type: String
    Default: 'sample'

Resources:
  StoreSourcesBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Join ['-', [!Ref Projectname, !Ref Env, 'tfstate', !Ref "AWS::AccountId" ]]
      # バージョニングしない場合はEnabledではなくSuspendedを指定する。
      VersioningConfiguration: {"Status": "Enabled"}

执行示例

$ ls
template.yml

$ aws cloudformation create-stack \
  --profile dev \
  --stack-name create-s3-bucket-for-test \
  --template-body file://./template.yml \
  --parameters ParameterKey=Env,ParameterValue=dev

经常使用的Terraform命令

常用的Terraform命令列表。

terraform init            # 初期化
terraform plan            # 計画実行(定義内容の確認)
terraform apply           # 適用
terraform destroy         # リソースの廃棄(削除)
terraform fmt -recursive  # フォーマット
terraform state list      # リソースを一覧表示
terraform state mv        # リソースブロックなどの名前変更
terraform show            # リソース情報表示
terraform import          # 既存リソースからTerraform定義を作成する

在这些中间,init、plan、apply和destroy的使用频率较高。

除了上面提到的命令外,还有其他命令可用。
具体信息请参考文档。
(虽然有点难理解,但可以通过左侧菜单跳转到各个命令的详细信息。)

任务流程

定义完成后的资源创建是按照以下步骤进行的:初始化,确认,应用,不再需要时删除。

# 初期化(プロファイルを指定する場合は -backend-config="profile=dev"を付ける)
$ terraform init

# 計画実行(定義内容の確認)
$ terraform plan

# 適用
$ terraform apply

# リソースの廃棄(削除)
$ terraform destroy

如果你想要应用或删除特定资源、模块或仅资源,可以使用-target选项。请参考下面的详细信息。

$ terraform plan -target="module.s3_bucket"
$ terraform plan -target={module.sample.aws_ecs_cluster.ecs_cluster,module.sample.aws_ecs_service.ecs_service}

在AWS管理控制台上手动创建资源,并使用terraform import进行定义(稍后会讲到),还可以通过在terraform中搜索服务名称并参考资源文档进行创建。
还可以从以下文档的左侧菜单中查找目标服务。

文件夹结构

我在参与的业务中经常使用Terraform作为模块化形式,并使用以下文件夹结构。关于每个tf文件的详情将在后文中提及。

terraform-repository gitリポジトリとかで管理。
 ┗ sample        # システムや任意の単位でフォルダを作成(この単位でterraformを実行、stateファイルが作成される)
  ┗ env
   ┗ dev         # applyの実行場所。環境単位でフォルダを作成。
    ┗ files
     ┗ iam_role_policy_for_hoge.json    # IAM用のjsonファイルなど定義で使用するファイルを保存
    ┗ terraform_settings.tf              # Terraformのバージョンやプロバイダーのバージョンを指定
    ┗ provider.tf        # プロバイダーの情報を設定
    ┗ data.tf            # 既存のリソースの情報取得を定義
    ┗ variable.tf        # 変数を定義
    ┗ main.tf            # モジュール呼び出しを定義
    ┗ output.tf          # 作成したリソースをモジュール外などで使用するための出力を定義
  ┗ module
   ┗ sample1              # モジュール単位でフォルダを作成
    ┗ iam.tf             # 各リソース作成用の定義を記入したtfファイルを配置
    ┗ ec2.tf
    ┗ output.tf
   ┗ sample2
    ┗ sample_sub1        # さらに階層を増やしても良い(モジュール呼び出し時にsourceで指定する)
     ┗ iam.tf
    ┗ sample_sub2

在Terraform中,似乎在执行plan或apply时会读取全部.tf文件。您可以自由指定文件名和文件夹名。(调用模块时,在main.tf中会读取模块的.tf文件)

你可以将所有内容都写在一个文件中,但为了更容易理解,我们将其分成了几个部分。
由于可以从main.tf多次调用相同的模块,如果要创建多个具有不同配置值的资源,将模块分开会更方便。

只是一个例子,我认为可以将terraform_settings.tf和provider.tf放在一起,或者在main.tf中包含变量定义而不创建variable.tf。

环境/开发/terraform设置.tf

我们指定了Terraform的版本和提供程序的版本。
我们还设置了状态文件的保存位置。

terraform {
  required_version = "0.14.7"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "3.35.0"
    }
  }

  backend "s3" {
    bucket = "dev-terraform-state-xxxxxxxx"
    key    = "sample/terraform.tfstate"
    region = "ap-northeast-1"
  }
}

环境/开发/供应商.tf

正在设置供应商信息。通过指定别名可以进行多个设置,并在更改资源创建区域时使用。

provider "aws" {
  region = "ap-northeast-1"
  profile = "dev"
}

provider "aws" {
  alias  = "useast1"
  region = "us-east-1"
  profile = "dev"
}

在模块调用或资源定义等情况下,可以指定配置文件。

module "sample" {
  source = "../../module/sample1"

  env = var.env

  providers = {
    aws = aws.useast1
  }
}

在中国语言环境中释义如下:
即使可以使用resource进行指定。
如果是模块形式的话,使用resource进行指定可能会变得复杂,所以最好不要经常这样做。

resource "aws_kinesis_firehose_delivery_stream" "firehose_sample" {
  provider = aws.useast1


这也可以在数据源中指定。

data "aws_cloudformation_stack" "sample1_stack" {
  name     = "sample1-fucntion-stack"
  provider = aws.useast1
}

开发环境的数据.tf

如果想在Terraform定义中使用现有资源的ARN等,请进行如下定义。有关获取方法,请参考每个资源的数据源文档。以下是一些示例。

# state情報を取得
data "terraform_remote_state" "sample_state" {
  backend = "s3"

  config = {
    bucket  = "dev-terraform-state-xxxxxxxx"
    key     = "sample_state/terraform.tfstate"
    region  = "ap-northeast-1"
    profile = "dev"
  }
}

# VPCの情報を取得
data "aws_vpc" "main" {
  filter {
    name   = "tag:Name"
    values = ["main_vpc"]
  }
}

# サブネットIDs(fileterでワイルドカード「*」が指定できる)
data "aws_subnet_ids" "subnet_ids" {
  vpc_id = data.aws_vpc.main.id
  filter {
    name   = "tag:Name"
    # アベイラビリティーゾーンの文字識別子を「*」で指定し複数取得している
    values = ["dev-sample-ap-northeast-1*"]
  }
}

# Lambda関数の情報を取得
data "aws_lambda_function" "sample" {
  function_name = "dev-sample-function"
}

# Cloudformationのstackを取得
data "aws_cloudformation_stack" "sample_cloudformation_stack" {
  name = "sample_stack"
}

用SAM(Serverless Application Model)创建Lambda函数比使用Terraform创建Lambda函数更容易,因此我们通常会使用SAM来创建Lambda函数,并在数据中导入和使用这些由SAM创建的Lambda函数。

环境/开发/变量.tf

我正在设定变量。变量的设置方式有两种:Variables和Local Values。

变量

请按照以下方式进行设置(从文件中引用)。

variable "image_id" {
  type = string
}

variable "availability_zone_names" {
  type    = list(string)
  default = ["us-west-1a"]
}

variable "docker_ports" {
  type = list(object({
    internal = number
    external = number
    protocol = string
  }))
  default = [
    {
      internal = 8300
      external = 8300
      protocol = "tcp"
    }
  ]
}
resource "aws_instance" "example" {
  instance_type = "t2.micro"
  ami           = var.image_id
}

如果要指定变量值并执行apply操作,可以按照以下方式进行。

$ terraform apply -var="image_id=ami-abc123"

当地价值观

请按照以下方式进行设置。(引用自文档)

locals {
  # Ids for multiple sets of EC2 instances, merged together
  instance_ids = concat(aws_instance.blue.*.id, aws_instance.green.*.id)
}

locals {
  # Common tags to be assigned to all resources
  common_tags = {
    Service = local.service_name
    Owner   = local.owner
  }
}
resource "aws_instance" "example" {
  # ...

  tags = local.common_tags
}

在中文中,变量(variable)和本地变量(locals)的使用方式不同。

在需要使用本地值(Local Values)并且希望能够能通过terraform apply -var来改变这些值时,使用Variables似乎是一个很好的选择。
参考网址:https://febc-yamamoto.hatenablog.jp/entry/2018/01/30/185416

环境/开发/main.tf

以下是关于模块和main.tf的一个整体示例。
在main.tf中指定所创建的模块,并设置所需变量的值。
以下是创建S3模块并调用main.tf的示例。
*省略了terraform_settings.tf等。

sample1
 ┗env/dev/main.tf
 ┗module/sample_s3/s3.tf
module "sample" {
  # sourceにモジュールのパスを指定する。
  source = "../../module/sample_s3"

  # モジュールで設定した変数を設定する。
  bucket_name = "sample-s3-bucket"
  env         = "dev"
}
resource "aws_s3_bucket" "s3_bucket" {
  bucket = var.bucket_name
  acl    = "private"

  tags = {
    env  = var.env
  }
}

variable "bucket_name" {}
variable "env" {}

环境/开发/输出.tf

如果想在模块外部等地使用已创建的资源,进行配置。
使用以下格式进行配置,即 资源类型.资源名称。
以下是在env/dev/main.tf示例中输出创建的模块的示例。

output "aws_s3_bucket" {
  value = aws_s3_bucket.s3_bucket
}

如果要在另一个模块中调用输出的内容,可以按以下方式操作。

module "sample" {
  # 略(env/dev/main.tfと同じ)
}

module "sample_use_module_output" {
  source = "../../module/sample"

  # outputした内容を使用する例
  sample_val = module.sample.aws_s3_bucket.arn
}

如果您在别的层面(※不确定这种说法是否正确)想要使用,请进一步输出。

output "sample_s3_bucket" {
  value = module.sample.aws_s3_bucket
}

从数据源获取并使用非国家情报。

# state情報を取得
data "terraform_remote_state" "sample_state" {
  backend = "s3"

  config = {
    bucket  = "dev-terraform-state-xxxxxxxx"
    key     = "sample_state/terraform.tfstate"
    region  = "ap-northeast-1"
    profile = "dev"
  }
}

# state情報の内容を使用
module "sample" {
  source = "../../module/sample2"

  s3_bucket_name  = data.terraform_remote_state.sample_state.outputs.sample_s3_bucket.arn

 # ~~略~~
}

函数

在Terraform中,有许多实用函数可供使用,并且可以在Terraform定义中使用。请参考以下文档。(可以在左侧菜单的“Functions”中查看每个函数的详细信息)

以下是一些常用的函数示例介绍。

文件(Object)

请提供文件。(Please provide the file.)

可以给我文件吗?(Can you give me the file?)

你有文件吗?(Do you have the file?)

传给我文件。(Transfer the file to me.)

读取文件。
https://www.terraform.io/docs/language/functions/file.html

file("${path.module}/files/hello.txt")

模板文件

读取文件。在文件内定义变量,并在读取时进行赋值。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "${policy_principal}"
            },
            "Action": "s3:GetObject",
            "Resource": "${bucket_arn}/*"
        }
    ]
}
templatefile(
  "${path.module}/files/role_policy.json",
  {
    bucket_arn       = local.bucket_arn
    policy_principal = local.policy_principal
  }
)

连接

可以将多个列表合并成一个列表。

concat(["a", ""], ["b", "c"])
# 結果
[
  "a",
  "",
  "b",
  "c",
]

把…压平

你可以将多个嵌套列表合并为一个列表。

flatten([["a", "b"], [], ["c"]])
# 結果
["a", "b", "c"]

flatten([[["a", "b"], []], ["c"]])
# 結果
["a", "b", "c"]

路径模块

使用file和templatefile指令时,path.module指的是所放置的模块的文件系统路径。请参考链接https://www.terraform.io/docs/language/expressions/references.html。

当使用main.tf中的path.module时,文件的位置关系如下所示。

sample
 ┗ env/dev
    ┗ main.tf
    ┗ files/hello.txt

尝试将输出.tf输出,可以看到path.module中包含了“。”(还尝试输出了其他路径等)。

path_module         = "."
path_root           = "."
path_cwd            = "/home/username/terraform定義を保存しているパス/sample/env/dev"
terraform_workspace = "default"

动态块 (Dynamic Blocks)

使用动态块可以重复指定部分资源类型等设置,重复次数根据给定列表的元素数进行设置。
支持在resource、data、provider和provisioner块内部。
(文件中指出有可能降低可读性和可维护性,因此滥用是不允许的)

以下是可以重复指定的块的示例:
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elastic_beanstalk_environment#example-with-options

resource "aws_elastic_beanstalk_environment" "tfenvtest" {
  name                = "tf-test-name"
  application         = aws_elastic_beanstalk_application.tftest.name
  solution_stack_name = "64bit Amazon Linux 2015.03 v2.0.3 running Go 1.4"

  setting {
    namespace = "aws:ec2:vpc"
    name      = "VPCId"
    value     = "vpc-xxxxxxxx"
  }

  setting {
    namespace = "aws:ec2:vpc"
    name      = "Subnets"
    value     = "subnet-xxxxxxxx"
  }
}

以下是设置Lambda@Edge的CloudFront示例。
(由于较长,只摘录相关部分)

resource "aws_cloudfront_distribution" "distribution" {

# ~~略~~

  default_cache_behavior {

  # ~~略~~

    # ダイナミックブロックで複数設定
    dynamic "lambda_function_association" {
      for_each = var.edgelambda_list
      content {
        event_type   = lambda_function_association.value.event_type
        lambda_arn   = lambda_function_association.value.lambda_arn
        include_body = false
      }
    }
  }
}

module "cloudfront_sample" {
  source = "../../module/common/sample"

  # ~~略~~

  edgelambda_list = [
    {
      event_type = local.edgelambda_sample1_event_type
      lambda_arn = local.edgelambda_sample1_arn
    },
    {
      event_type = local.edgelambda_sample2_event_type
      lambda_arn = local.edgelambda_sample2_arn
    }
  ]

}

虽然我使用了Lambda@Edge来设置多个配置,但似乎可以在列表中设置多个配置而不使用动态块。
请将上述代码仅作为使用动态块的示例参考。

lambda_function_associations = [
    {
      event_type = "viewer-request"
      // 注意: lambda_arnはpublishされたものしか使えない
      // またlambda_arnをterraformのinterpolation経由で持ってこようとするとうまくいかないのでベタがきしている
      lambda_arn = "arn:aws:lambda:us-east-1:${var.account_id}:function:cloudfront_s3_basic_auth_dev:2"
    },
  ]

导入现有资源

创建用于导入的tf文件,并执行如下。
这是一个导入ECS集群的示例。

import_sample
 ┗import.tf    # 名前は適当
terraform {
  required_version = "0.13.5"
}

provider "aws" {
  region  = "ap-northeast-1"
  profile = "dev"
}

resource "aws_ecs_cluster" "ecs_cluster_sample" {}

# 初期化
$ terraform init -backend-config="profile=dev"

# terraform import リソース種別.名前 import対象(各リソース種別のドキュメントにあるImportを参照)
# ここでは既に作成されているsample_clusterという名前を指定しています。
$ terraform import aws_ecs_cluster.ecs_cluster_sample sample_cluster

在执行 terraform import 之后,执行 terraform show 来获取内容。

$ terraform show
# aws_ecs_cluster.ecs_cluster_sample:
resource "aws_ecs_cluster" "ecs_cluster_sample" {
    arn                = "arn:aws:ecs:ap-northeast-1:xxxxxxxxxxxx:cluster/sample_cluster"
    capacity_providers = [
        "FARGATE",
        "FARGATE_SPOT",
    ]
    id                 = "arn:aws:ecs:ap-northeast-1:xxxxxxxxxxxx:cluster/sample_cluster"
    name               = "sample_cluster"
    tags               = {}
    tags_all           = {}

    setting {
        name  = "containerInsights"
        value = "enabled"
    }
}

根据获取到的内容,删除不必要的部分(例如arn和id),并定义变量。

resource "aws_ecs_cluster" "ecs_cluster_sample" {
    name               = var.ecs_cluster_sample_name

    setting {
        name  = "containerInsights"
        value = "enabled"
    }

    tags = {
        Name      = var.ecs_cluster_sample_name
        Env       = var.env
        Project   = var.project
        Terraform = "true"
    }
}

variable "ecs_cluster_sample_name" {}

我們將對每個需要創建 Terraform 定義的資源進行上述操作。

结束 (Jieshu)

我认为比起Terraform本身,更需要学习Terraform的定义对象(如AWS)的印象更为深刻。
这就是今天的内容。

广告
将在 10 秒后关闭
bannerAds