はじめに
今回はTerraformを触ったことがない学生がある程度できるようになるまでの道のりを記事に起こします。
分かりやすく解説していくので、ぜひご覧ください。
投稿主の前提条件
- 大学院生
- 応用情報技術者取得
- アルバイトでゲーム会社のwebアプリエンジニアとして勤務(2~3年)
- aws経験はボチボチ
- 圧倒的Mac派
- 書ける言語
- Python
- Go
- Java
- Javascript
- Vue.js
- Google App Script
Terraformとは?
Terraform(テラフォーム)は、HashiCorp社によって開発された、オープンソースのインフラストラクチャー・コード化ツールです。このツールにより、クラウドリソースやデータセンターのリソースを、コードによって管理・構築することができます。
特徴
1. クラウドプロバイダーに依存しない
Terraformは、AWS、Azure、Google Cloud Platformなど、多くのクラウドプロバイダーと連携することができます。
provider "aws" {
region = "us-west-2"
}
2. 宣言的なコード
Terraformは、宣言的なコードによりインフラストラクチャーを定義します。何をしたいのかを書くだけで、その通りにリソースが構築されます。
resource "aws_instance" "example" {
ami = "ami-123456"
instance_type = "t2.micro"
}
このコードは、Terraformを使用してAWS上に仮想マシンを作成するための宣言的な定義です。
具体的には以下の設定が含まれています。
-
resource "aws_instance" "example"
: AWS上にインスタンスを作成するリソースの定義で、このリソースには名前として"example"が付けられています。 -
ami = "ami-123456"
: 使用するAmazon Machine Image(AMI)のIDを指定しています。このIDは、インスタンス作成時に使用するOSやソフトウェアがプリインストールされたイメージを表します。 -
instance_type = "t2.micro"
: インスタンスのサイズを指定しています。この例では、"t2.micro"タイプのインスタンスが作成されます。
このコードをTerraformで実行すると、指定されたAMIとインスタンスタイプを使用して、AWS上に新しい仮想マシンが作成されます。
詳しい構文は次の章で紹介します。
3. コードによるバージョン管理
インフラのコード化により、バージョン管理が可能となり、変更履歴を容易に追跡できます。
インフラストラクチャーのライフサイクル
Terraformの操作は主に以下のステップから構成されます。
-
初期化:
terraform init
-
プラン作成:
terraform plan
-
適用:
terraform apply
-
破棄:
terraform destroy
Terraformの基本構文
terraformブロック
terraform
ブロックは、Terraformの設定に使用されます。バックエンドの設定、プロバイダーのバージョンの制約など、Terraformの動作に関連する設定を行います。
プロバイダーのバージョン制約
required_providers
ブロックを使用して、プロバイダーのバージョンを指定することができます。
terraform {
required_version = ">=1.3"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 2.0"
}
}
}
この設定により、AWSプロバイダーのバージョン2.0以上、3.0未満を使用するように指定しています。
バックエンドの設定
バックエンドは、Terraformの状態を保存する場所を定義します。以下はS3バケットをバックエンドとして使用する例です。
terraform {
backend "s3" {
bucket = "my-tf-state-bucket"
key = "terraform.tfstate"
region = "us-west-2"
}
}
変数
変数は、コード内で何度も使用する値を一元管理するために使用されます。変数を使用することで、コードの再利用性とメンテナンス性が向上します。
variable "region" {
description = "AWSのリージョン"
default = "us-west-2"
}
リソース
リソースブロックは、クラウドインフラストラクチャー内の物理的なコンポーネントを定義します。以下はAWSのS3バケットを作成する例です。
resource "aws_s3_bucket" "bucket" {
bucket = "my-tf-test-bucket"
acl = "private"
}
データソース
データソースブロックは、既存のクラウドインフラストラクチャーのコンポーネントにアクセスするために使用されます。
data "aws_ami" "example" {
most_recent = true
owners = ["self"]
}
出力
出力ブロックは、Terraformが構築または変更したインフラストラクチャーに関する情報を出力するために使用されます。
output "bucket_name" {
value = aws_s3_bucket.bucket.id
}
Standard Module Structure
基本的にはhashicorpのTerraformのstandard moduleに沿ってフォルダ構成をすると良いみたいです。
以下参照
以下は、最小限のスタンダードモジュールで構成されたものです。
$ tree minimal-module/
.
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
以下は、全てのオプション要素が含まれているモジュール構成です。
$ tree complete-module/
.
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
├── ...
├── modules/
│ ├── nestedA/
│ │ ├── README.md
│ │ ├── variables.tf
│ │ ├── main.tf
│ │ ├── outputs.tf
│ ├── nestedB/
│ ├── .../
├── examples/
│ ├── exampleA/
│ │ ├── main.tf
│ ├── exampleB/
│ ├── .../
セットアップ編 (Mac)
Mac上でTerraformをセットアップするための手順を以下に説明します。
tfenvのインストール
tfenv
はTerraformのバージョン管理ツールで、プロジェクトごとに異なるバージョンのTerraformを使用することができます。Mac上でのインストール方法は以下の通りです。
tfenvの使用は必須ではありませんが、使用した方がバージョンの管理が楽になります。
Homebrewを使用したインストール
Homebrewを使用して、tfenv
をインストールします。
$ brew install tfenv
インストールの確認
以下のコマンドを実行して、tfenv
が正しくインストールされたことを確認します。
$ tfenv --version
Terraformのバージョンのインストールと選択
tfenv
を使用して、特定のバージョンのTerraformをインストールし、使用するバージョンを選択することができます。
$ tfenv install 1.3.9
$ tfenv use 1.3.9
これで、プロジェクトで使用するTerraformのバージョンを0.12.0に設定しました。
使用できるバージョンの確認
$ tfenv list
* 1.3.9 (set by /Users/local/.tfenv/version)
1.3.8
tfenv list-remote
でインストール可能なバージョン一覧が確認できます。
Terraformのインストール
Homebrewを使用して、Terraformをインストールします。
$ brew tap hashicorp/tap
$ brew install hashicorp/tap/terraform
インストールの確認
以下のコマンドを実行して、Terraformが正しくインストールされたことを確認します。
$ terraform -version
このコマンドは、インストールされたTerraformのバージョンを表示します。
初期化
プロジェクトディレクトリに移動し、Terraformを初期化します。
$ cd your_project_directory
$ terraform init
このコマンドは、プロジェクトに必要なプロバイダーをダウンロードし、Terraformを初期化します。
使ってみる ~基本編~
initをする
$ mkdir terraform_test
$ cd terraform_test
terraform initをする前に、
initの為にtfstateを保存するS3バケットを作る必要があります。
今回はterraform-paupau-tfstate
という名前でバケットを作成してみます。
次に、S3の情報をbackend.tf
に落とし込んでいきます。
terraformの公式ドキュメントが充実しているので、テンプレートを引っ張ってきます。
かなりドキュメントが充実しているので、初学者にはとても嬉しいですね!
テンプレを自分の環境に置き換えて、以下のようなものを作りました。
provider "aws"{
region = "ap-northeast-1"
}
terraform {
required_version = "1.5.5"
backend "s3" {
bucket = "terraform-paupau-tfstate"
key = "terraform.tfstate"
region = "ap-northeast-1"
}
}
VScodeの拡張機能にterraformの機能があり、補完が効いて楽なので積極的に使いましょう!
次にterraform init
で初期化をしようとすると...
$ terraform init
Initializing the backend...
Terraform encountered problems during initialisation, including problems
with the configuration, described below.
The Terraform configuration must be valid before initialization so that
Terraform can determine which modules and providers need to be installed.
╷
│ Error: error configuring S3 Backend: error validating provider credentials: error calling sts:GetCallerIdentity: InvalidClientTokenId: The security token included in the request is invalid.
│ status code: 403, request id: xxxxxx
あれ、認証できない...?
そうです。AWS CLIの設定が出来ていませんでした。
configとcredentialsの設定をします。
$ cat ~/.aws/credentials
[default]
aws_access_key_id = hogehoge
aws_secret_access_key = hogehoge
$ cat ~/.aws/config
[default]
region = ap-northeast-1
output = json
これでバッチリです!
.terraform % terraform init -reconfigure
Initializing the backend...
Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Installing hashicorp/aws v5.12.0...
- Installed hashicorp/aws v5.12.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
initが成功しました!
VPCとサブネットを作ってみる
こちらもterraformのドキュメントから引っ張ってきました。
VPCのドキュメントはこちら
サブネットのドキュメントはこちら
vpc.tfを作ってみます。
resource "aws_vpc" "main" {
cidr_block = "192.168.1.0/24"
tags = {
Name = "Main-vpc"
}
}
resource "aws_subnet" "main_1a" {
vpc_id = aws_vpc.main.id
cidr_block = "192.168.1.0/25"
availability_zone = "ap-northeast-1a"
}
次にterraform validate
で構文が正しいか、確認してみます。
Terraformの設定ファイルが正しい構文と構造を持っているかをチェックするために使用されます。
$ terraform validate
Success! The configuration is valid.
順調です。
次にterraform plan
で確認してみます。
tfstateと現在のコードの内容の差分を見て、状態を教えてくれます。
(長いので少し省略してます)
% terraform plan
・
・
・
Plan: 2 to add, 0 to change, 0 to destroy.
2個作成予定が確認できました。
余談ですがterraform fmt
でフォーマットを綺麗にしてくれます。
ようやく、terraform apply
してみます。
$ terraform apply
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_subnet.main_1a will be created
+ resource "aws_subnet" "main_1a" {
+ arn = (known after apply)
+ assign_ipv6_address_on_creation = false
+ availability_zone = "ap-northeast-1a"
+ availability_zone_id = (known after apply)
+ cidr_block = "192.168.1.0/25"
+ enable_dns64 = false
+ enable_resource_name_dns_a_record_on_launch = false
+ enable_resource_name_dns_aaaa_record_on_launch = false
+ id = (known after apply)
+ ipv6_cidr_block_association_id = (known after apply)
+ ipv6_native = false
+ map_public_ip_on_launch = false
+ owner_id = (known after apply)
+ private_dns_hostname_type_on_launch = (known after apply)
+ tags_all = (known after apply)
+ vpc_id = (known after apply)
}
# aws_vpc.main will be created
+ resource "aws_vpc" "main" {
+ arn = (known after apply)
+ cidr_block = "192.168.1.0/24"
+ default_network_acl_id = (known after apply)
+ default_route_table_id = (known after apply)
+ default_security_group_id = (known after apply)
+ dhcp_options_id = (known after apply)
+ enable_dns_hostnames = (known after apply)
+ enable_dns_support = true
+ enable_network_address_usage_metrics = (known after apply)
+ id = (known after apply)
+ instance_tenancy = "default"
+ ipv6_association_id = (known after apply)
+ ipv6_cidr_block = (known after apply)
+ ipv6_cidr_block_network_border_group = (known after apply)
+ main_route_table_id = (known after apply)
+ owner_id = (known after apply)
+ tags = {
+ "Name" = "Main-vpc"
}
+ tags_all = {
+ "Name" = "Main-vpc"
}
}
Plan: 2 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_vpc.main: Creating...
╷
│ Error: creating EC2 VPC: operation error EC2: CreateVpc, https response error StatusCode: 403, RequestID: 71b56b4f-b2c5-4b51-9138-a859a5707eb3, api error UnauthorizedOperation: You are not authorized to perform this operation. Encoded authorization failure message: HFvjs_g1WA1DvLX7IkOqRVkEP6BRUVXdIgl2l2Z6TBm69UahT9QAnCvHbZSDVviMDyucCaXAsOpuz2vls0JTMEiuLMM6CGhluUggZp8VdlaMmmHfDfH
あれ、うまく行ってないようです。詳細内容がエンコードされていますね...
aws sts decode-authorization-message --encoded-message "encode_message"
を使ってデコードしてみましょう。
デコードするには、IAMのポリシーにDecodeAuthorizationMessage
を追加する必要があります。
IAM > ポリシー > ポリシーを作成 > JSON まで行って、
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:DecodeAuthorizationMessage",
"Resource": "*"
}
]
}
次に今作ったポリシーをアタッチします。
無事アタッチできたようです。
先ほどのをデコードし、エラーを解析してみると、
どうやら、vpcやサブネット周りのポリシーが必要だったみたいです。
以下、ポリシーのJSONです。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:CreateVpc",
"ec2:DescribeVpcs",
"ec2:DeleteVpc",
"ec2:ModifyVpcAttribute",
"ec2:CreateTags",
"ec2:DeleteTags",
"ec2:DescribeTags",
"ec2:CreateSubnet",
"ec2:DescribeSubnets",
"ec2:DeleteSubnet",
"ec2:DescribeVpcAttribute"
],
"Resource": "*"
}
]
}
ポリシーを作成しアタッチしました。
再びterraform apply
に挑戦します。
$ terraform apply
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_vpc.main: Destroying... [id=vpc-06e030806]
aws_vpc.main: Destruction complete after 0s
aws_vpc.main: Creating...
aws_vpc.main: Creation complete after 1s [id=vpc-0e77b6339378]
aws_subnet.main_1a: Creating...
aws_subnet.main_1a: Creation complete after 1s [id=subnet-000f00088170b]
Apply complete! Resources: 2 added, 0 changed, 1 destroyed.
vpcとサブネットが上手くできているようです!
S3にあるtfstateも更新されていますね^^
使ってみる ~応用編~
モジュール化・変数化してみる
以下のようなツリーでモジュール化と変数化を実装してみました。
$ tree
.
├── backend.tf
├── modules
│ └── vpc_subnet
│ ├── main.tf
│ └── variables.tf
└── use_module.tf
backend.tfは引き続き同じソースコードです。
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr_block
tags = {
Name = "Main-vpc"
}
}
resource "aws_subnet" "main_1a" {
vpc_id = aws_vpc.main.id
cidr_block = var.subnet_1a_cidr_block
availability_zone = "ap-northeast-1a"
}
variable "vpc_cidr_block" {
type = string
}
variable "subnet_1a_cidr_block" {
type = string
}
module "vpc_subnet1" {
source = "./modules/vpc_subnet/"
vpc_cidr_block = "192.168.1.0/24"
subnet_1a_cidr_block = "192.168.1.0/25"
}
module "vpc_subnet2" {
source = "./modules/vpc_subnet/"
vpc_cidr_block = "192.168.12.0/24"
subnet_1a_cidr_block = "192.168.2.0/25"
}
workspaceの使用
workspaceで作業環境を分けてみたいと思います。
stage
とmaster
を作ってみます。
$ terraform workspace list
* default
$ terraform workspace new "stage"
Created and switched to workspace "stage"!
You're now on a new, empty workspace. Workspaces isolate their state,
so if you run "terraform plan" Terraform will not see any existing state
for this configuration.
$ terraform workspace new "master"
Created and switched to workspace "master"!
You're now on a new, empty workspace. Workspaces isolate their state,
so if you run "terraform plan" Terraform will not see any existing state
for this configuration.
その後、使用するところまで、作ってみます。
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr_block
tags = {
Name = "${terraform.workspace}-test-vpc" //現在選ばれているworkspaceが入る
}
}
まとめ
今回は、Terraform未経験の学生が基礎的な内容に挑戦してみました。
誰かの参考になればと思います。
(間違いあれば指摘お願いいたします!)