目次
- はじめに
- Terraformコードのパッケージ構成について
- Variablesとtfvarsでコードを柔軟にする
- モジュール化で再利用性アップ
- Remote Stateでチーム開発の壁を越える
- import機能の使い方〜既存AWSリソースのIaC化
- まとめ
- 参考リンク
はじめに
前回の記事ではIaCツールであるTerraformの基礎と、Terraformを使ったシンプルなVPC環境をAWSに構築する例を紹介しました。
シンプルなコードでサーバーなど、簡単にリソースを立ち上げられることを実感していただけたのではないでしょうか。
応用編となるこの記事では、実際のチーム開発や本番運用を想定し、Terraformの「運用力」や「再利用性」を高める活用法を紹介します。
Terraformコードのパッケージ構成について
実際の現場では環境は1つではなく、「開発環境」「ステージング環境」「本番環境」のように、環境を複数構築するのが一般的だと思います。
ベストプラクティスというわけではありませんが、以下のようなパッケージ構成で環境依存部分とそれ以外を分け、かつリソースごとにモジュールファイルを分けて管理するのが一般的だと思います。
※変数や環境依存情報、モジュールについては後述します。
├── README.md├── environments│ ├── develop # 環境依存情報(開発環境用)│ │ ├── .tfvars # 環境依存変数│ │ ├── main.tf # メインファイル。プロバイダー設定やリソース定義などを記載。│ │ ├── variables.tf # 変数定義│ │ └── backend.tf # Terraformで構築したリソースの状態を管理する場所を指定(S3など)│ ││ └── production # 環境依存情報(本番環境用)│ ├── .tfvars│ ├── main.tf│ ├── variables.tf│ └── backend.tf│└── modules # モジュール(リソース)定義。リソースごとにファイルを分ける ├── security_group.tf ├── ec2.tf ├── s3.tf └── ……Variablesとtfvarsでコードを柔軟にする
Variablesやtfvarsを使って変数や環境依存値を定義できます。
他のプログラミング言語と同様、IaCでも極力ハードコーディングは除外すべきで、Variablesやtfvarsを使ってコードを柔軟にする必要があります。
例えばリージョンやインスタンスタイプなどは環境によって異なる可能性があるため、環境ごと、もしくは動的に変更できるようにすべきです。
変数を使った柔軟な構成管理
Terraformではvariableを使うことで、同じコードを開発・本番など複数の環境で簡単に再利用できます。
例:variables.tf
variable "region" { description = "AWS region" type = string}
variable "vpc_cidr" { description = "CIDR block for VPC" type = string default = "10.0.0.0/16"}例:main.tf(利用例、参照元)
provider "aws" { region = var.region}
resource "aws_vpc" "main" { cidr_block = var.vpc_cidr
tags = { Name = "example-vpc" }}tfvarsで環境ごとに値を切り替える
各設定値は環境依存ファイル(.tfvars)に定義します。当該ファイルはplanやapplyコマンド実行時のオプションで切り替えることができます。
環境ごとの.tfvarsファイル
# dev.tfvarsregion = "ap-northeast-1"vpc_cidr = "10.0.0.0/16"
# prod.tfvarsregion = "us-east-1"vpc_cidr = "10.1.0.0/16"実行方法
terraform plan -var-file=dev.tfvarsterraform apply -var-file=dev.tfvarsモジュール化で再利用性アップ
Terraformモジュールは「再利用可能なIaCの部品」で、共通的なネットワーク構成やEC2インスタンス構築など、使いまわすものをまとめます。
基本的に、リソースはモジュールとして定義するのが良いでしょう。
モジュール利用例
modules/vpc/main.tf
resource "aws_vpc" "this" { cidr_block = var.vpc_cidr_block}
variable "vpc_cidr_block" {}呼び出し元main.tf
module "my_vpc" { source = "./modules/vpc" vpc_cidr_block = var.vpc_cidr}公開モジュールを活用
Terraform Registryには数多くの高品質なAWSモジュールが公開されており、これらを活用することもできます。
module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "3.0.0" name = "my-vpc" cidr = var.vpc_cidr}Remote Stateでチーム開発の壁を越える
tfstateファイル(terraform.tfstate)は「どのようなAWSリソースがあるか」を記録しており、チーム開発ではtfstateファイルを共有する必要があります。
tfstateファイルとは
tfstateファイルはTerraformが管理しているリソースの現在の状態を表すファイルです。
デフォルトではローカルにtfstateファイルが生成されます。
tfstateファイルがあるおかげで、terraform apply実行時にすでに構築済みのリソースが再度構築されることはありません。
共同で同一環境を構築するには、tfstateファイルは、開発メンバー間で共有し、常に最新の状態を維持する必要があります。
AWSではS3で管理するのが一般的です。backend.tfに以下の内容を記載することで、tfstateファイルの管理場所を定義できます。
※tfstateファイルの更新競合を防ぐためDynamoDBを使うこともありますが、ここでは割愛します。
terraform { backend "s3" { bucket = "my-terraform-state-bucket" key = "vpc/terraform.tfstate" region = "ap-northeast-1" dynamodb_table = "terraform-lock" encrypt = true }}import機能の使い方〜既存AWSリソースのIaC化
Terraformを使ってインフラをコード管理する場合、多くは新規構築から始めます。
しかし現場では、すでに手動で構築済みのAWSリソースおよび、CloudFormation、CLIで作った資産をIaC管理下に置く必要が出てきます。
「Terraform import」はそんな状況を解決するための機能です。
既存リソースをTerraformの状態ファイル(tfstateファイル)に登録し、その後コード化して管理できるようにします。
今回はAWSの例として、すでにあるVPCやS3バケットをTerraformへ取り込む手順を紹介します。
AWS(既存リソース) ↓ import(IDで紐付け)Terraform State ↓ HCLコードを作成完全なIaC管理へ例えば、すでに構築されているS3バケット(my-existing-bucket)を取り込むために、以下のリソースを定義します。
resource "aws_s3_bucket" "existing" { bucket = "my-existing-bucket"}以下のようにして取り込み、Terraform管理下にします。
terraform import aws_s3_bucket.existing my-existing-bucketこれで、Terraformから見ると、別途手動などで作られたmy-existing-bucketの存在は認識されました。
しかし、当該S3のコードは自動生成されるわけではないため、コーディングする必要があります。
importブロック
importコマンドではリソースの定義はtfstateファイル上にしか生成されず、tfファイルは手書きで編集する必要がありました。しかし、importブロックを使用するとtfファイル上にもリソース定義を自動生成することが可能になります。
やり方は2パターンあり、resourceブロックをimportブロックによって自動生成するパターンと、resourceブロックを事前に作成しておくパターンがあります。
resourceブロックをimportブロックによって自動生成するパターン
以下のようにimportブロックを定義します。
import { id = "i-xxx" # 取り込み対象のリソースID to = aws_instance.sample1 # 取り込み対象のリソースブロックtype.name}以下のコマンドでリソース定義を出力できます。
$ terraform plan -generate-config-out=任意の名前.tf
aws_instance.sample1: Preparing import... [id=i-xxx]aws_instance.sample1: Refreshing state... [id=i-xxx]
~~ 省略 ~~Plan: 1 to import, 0 to add, 0 to change, 0 to destroy.terraform applyを実行して、tfstateファイルにもリソースを取り込みます。
$ terraform applyaws_instance.sample1: Preparing import... [id=i-xxx]aws_instance.sample1: Refreshing state... [id=i-xxx]~~ 省略 ~~Apply complete! Resources: 1 imported, 0 added, 0 changed, 0 destroyed.resourceブロックを事前に作成しておくパターン
以下のようにimportブロック、resourceブロックを定義します。
import { id = "i-xxx" to = aws_instance.sample1}
resource "aws_instance" "sample1" { ami = "ami-yyy" instance_type = "t2.micro"
tags = { Name = "test-instance" }}terraform applyを実行してリソース定義を取り込みます。
$ terraform applyaws_instance.sample1: Preparing import... [id=i-xxx]aws_instance.sample1: Refreshing state... [id=i-xxx]~~ 省略 ~~Apply complete! Resources: 1 imported, 0 added, 0 changed, 0 destroyed.※参考
まとめ
この記事では実際に現場で運用する場合の例を紹介しました。
再利用可能なモジュールと変数を用いて汎用的な環境定義を行い、さらに環境依存情報を切り出すことで、開発・ステージング・本番といった同様の構成を持つ環境を簡単にデプロイできます。
あとは設計に従い、各モジュールの設定値を環境ごとに定義すれば、インフラを構築できます。
次回はCI/CDとの連携など、実運用の観点で必要な要素も取り入れた実践編をご紹介できればと思います。