部署内ハンズオンで使った資料です。せっかくなので公開してみました。
Terraform とは
HashiCorp社製の様々なクラウドサービスをコードで管理するためのツールです。
インフラなどの設定をコード化することで、共同作業や使い回しやバージョン管理などが容易になります。
dry run やリソースの依存性の可視化など便利な機能も多く、いわゆる Infrastructure as Code を実現するために用いられます。
TL;DR
.tf
ファイルにインフラの設定を記述し、コマンドを実行するとその通りにインフラを構築してくれるツールです。
環境構築
Terraform用のIAMユーザーの作成
Terraform用のIAMユーザーを作成します。以下URLを参考にしながらIAMユーザーを作成します。
今回はハンズオンなので、Admin権限を持つユーザーを作成します。
SecretAccessKey は再発行されないので、無くしてしまったらアカウントを作り直してください。
AWS CLI のインストール
AWS CLI をインストールし、設定を行います。インストール方法は下記 URL を参考にしてください
macOS に AWS CLI をインストールする
AWS CLI の設定
インストールが完了したら AWS CLI の設定を行い、AWS CLI が実行できることを確認します。
$ aws --version
$ aws configure
$ aws s3api list-buckets # S3 のバケット一覧を表示するコマンド
tfenv を介してインストール
Terraform は更新が早く、バグもまだまだ多いため、気軽にバージョンを変えられるように tfenv
を使うことを強く勧めます。
$ brew install tfenv
$ tfenv -v
$ tfenv list-remote # インストール可能なバージョンをリモートから取得して列挙します。
$ tfenv install 0.12.10 # 0.12.10 をインストール
$ tfenv use 0.12.10 # 0.12.10 を使用する
$ tfenv list # インストール済み
$ terraform --version # バージョン一覧
.terraform-version
ファイルを作ることで、.ruby-version
のようにバージョン管理なども可能です。
$ touch .terraform-version
$ tfenv use 0.12.10 # このディレクトリ以下で 0.12.10 を使用する
HelloWorld
HelloWorld として S3バケットを Terraform で作ります。
$ mkdir handson
$ cd handson
$ touch main.tf
main.tf
ファイルを作成し、以下のように記述します。
tekitouna.domain.nyuryoku
の箇所はS3バケット名になるので、urlに使える文字列 かつ グローバルでユニークな任意の値を入れてください。
# Provider
provider "aws" {
profile = "default"
region = "ap-northeast-1"
}
# Resource
resource "aws_s3_bucket" "tekitouna_domain_nyuryoku" {
bucket = "tekitouna.domain.nyuryoku"
}
terraform init
コマンドでAWSのプロバイダーをダウンロードし、terraform plan
コマンドで main.tf
に新しく作成されるリソースを確認します。
$ terraform init
$ terraform plan
以下のように新しく作成されるリソースの情報が表示されます。
tekitouna.domain.nyuryoku
という名前の新しいバケットが作成されることがわかります。
Terraform will perform the following actions:
# aws_s3_bucket.tekitouna_domain_nyuryoku will be created
+ resource "aws_s3_bucket" "tekitouna_domain_nyuryoku" {
+ acceleration_status = (known after apply)
+ acl = "private"
+ arn = (known after apply)
+ bucket = "tekitouna.domain.nyuryoku"
+ bucket_domain_name = (known after apply)
+ bucket_regional_domain_name = (known after apply)
+ force_destroy = false
+ hosted_zone_id = (known after apply)
+ id = (known after apply)
+ region = (known after apply)
+ request_payer = (known after apply)
+ website_domain = (known after apply)
+ website_endpoint = (known after apply)
+ versioning {
+ enabled = (known after apply)
+ mfa_delete = (known after apply)
}
}
Plan: 1 to add, 0 to change, 0 to destroy.
問題なければ terraform apply
コマンドを実行して、S3バケットを作成します。
$ terraform apply
新しくS3バケットが作成されました。
以下のコマンドを実行すると新しくバケットが作られていることがわかります。
aws s3api list-buckets | grep tekitouna.domain.nyuryoku
Applyに成功すると terraform.tfstate
が生成されます。
terraform.tfstate
にはTerraformでは管理しているインフラの状態が記述されています。
ハンズオンなので詳しい説明はスキップしますが、複数人での開発ではtfstate
ファイルを S3 などに置くことをお勧めします。
{
"version": 4,
"terraform_version": "0.12.10",
"serial": 1,
"lineage": "6a7bf262-a78d-decf-166f-9f1eac238641",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "aws_s3_bucket",
"name": "tekitouna_domain_nyuryoku",
"provider": "provider.aws",
"instances": [
{
"schema_version": 0,
"attributes": {
"acceleration_status": "",
"acl": "private",
"arn": "arn:aws:s3:::tekitouna.domain.nyuryoku.xxxx",
"bucket": "tekitouna.domain.nyuryoku.xxxx",
"bucket_domain_name": "tekitouna.domain.nyuryoku.xxxx.s3.amazonaws.com",
"bucket_prefix": null,
"bucket_regional_domain_name": "tekitouna.domain.nyuryoku.xxxx.s3.ap-northeast-1.amazonaws.com",
"cors_rule": [],
"force_destroy": false,
"hosted_zone_id": "Z2M4EHUR26P7ZW",
"id": "tekitouna.domain.nyuryoku.xxxx",
"lifecycle_rule": [],
"logging": [],
"object_lock_configuration": [],
"policy": null,
"region": "ap-northeast-1",
"replication_configuration": [],
"request_payer": "BucketOwner",
"server_side_encryption_configuration": [],
"tags": null,
"versioning": [
{
"enabled": false,
"mfa_delete": false
}
],
"website": [],
"website_domain": null,
"website_endpoint": null
},
"private": "bnVsbA=="
}
]
}
]
}
次に作成したバケットを削除します。
$ terraform destroy
削除されるリソースの内容が表示されます。
Terraform will perform the following actions:
# aws_s3_bucket.tekitouna_domain_nyuryoku will be destroyed
- resource "aws_s3_bucket" "tekitouna_domain_nyuryoku" {
- acl = "private" -> null
- arn = "arn:aws:s3:::tekitouna.domain.nyuryoku.xxxx" -> null
- bucket = "tekitouna.domain.nyuryoku.xxxx" -> null
- bucket_domain_name = "tekitouna.domain.nyuryoku.xxxx.s3.amazonaws.com" -> null
- bucket_regional_domain_name = "tekitouna.domain.nyuryoku.xxxx.s3.ap-northeast-1.amazonaws.com" -> null
- force_destroy = false -> null
- hosted_zone_id = "Z2M4EHUR26P7ZW" -> null
- id = "tekitouna.domain.nyuryoku.xxxx" -> null
- region = "ap-northeast-1" -> null
- request_payer = "BucketOwner" -> null
- tags = {} -> null
- versioning {
- enabled = false -> null
- mfa_delete = false -> null
}
}
Plan: 0 to add, 0 to change, 1 to destroy.
変更内容に問題がなければ実行します。
これで Terraform による作成と削除はできました。
コンソールから作ったリソースを Terraform に import してみる
以下の画像の手順を参考に、Webサーバーとして公開されているS3バケットを作成します。
バケットポリシーを設定します。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadForGetBucketObjects",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::tekitouna.domain.nyuryoku/*"
}
]
}
ポリシーの設定が完了したら、index.html
, error.html
という名前で html ファイルを作成し、バケットにアップデートします。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>タイトル</title>
</head>
<body>
<h1>タイトル</h1>
<p>Amazon S3を使って静的なウェブページを公開してみた</p>
</body>
</html>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Error</title>
</head>
<body>
<h1>Error</h1>
<p>エラーページです</p>
</body>
</html>
しばらく待ってから、S3のファイルのURLにアクセスし、ファイルが表示されることを確認します。
http://tekitouna.domain.nyuryoku.s3-website-ap-northeast-1.amazonaws.com/index.html
http://tekitouna.domain.nyuryoku.s3-website-ap-northeast-1.amazonaws.com/error.html
次に作成したS3バケットをTerraform で管理してみようと思います。
以下のコマンドで 作成した S3バケットを Terraform の管理下に取り込みます。
$ terraform import aws_s3_bucket.tekitouna_domain_nyuryoku tekitouna.domain.nyuryoku
plan
コマンドで、main.tf
ファイルと手動で作成したS3バケットとの差分を確認します。
$ terraform plan
差分を埋める -> terraform plan
-> 差分を埋める 作業を繰り返します。
以下の差分を消すことができなかったので、無視しました。
+ acl = "none"
+ force_destroy = true
完成したのがこちらです。
先ほどの手作業をコード化することができました。
resource "aws_s3_bucket" "tekitouna_domain_nyuryoku" {
bucket = "tekitouna.domain.nyuryoku"
website {
index_document = "index.html"
}
}
resource "aws_s3_bucket_policy" "tekitouna_domain_nyuryoku" {
bucket = aws_s3_bucket.tekitouna_domain_nyuryoku.id
policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadForGetBucketObjects",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::tekitouna.domain.nyuryoku/*"
}
]
}
POLICY
}
main.tf
と S3バケットとの差分がなくなりました。
[おまけ]機能を追加してみる
前項までにWebサーバーとして公開されているS3バケットを作成しました。
しかしファイルが存在しないページにアクセスすると Access Denied
エラーが表示されてしまいます。
ファイルが存在しないページにアクセスしようとしたらエラーページにリダイレクトするように設定します。
リダイレクトの仕組みはS3に用意されています。
以下サイトを参考にコンソールからの設定を行います。
設定後、terraform plan
を実行し現在の main.tf
ファイルとS3バケットの差分を確認します。
# aws_s3_bucket.tekitouna_domain_nyuryoku will be updated in-place
~ resource "aws_s3_bucket" "tekitouna_domain_nyuryoku" {
acl = "private"
arn = "arn:aws:s3:::tekitouna.domain.nyuryoku"
bucket = "tekitouna.domain.nyuryoku"
bucket_domain_name = "tekitouna.domain.nyuryoku.s3.amazonaws.com"
bucket_regional_domain_name = "tekitouna.domain.nyuryoku.s3.ap-northeast-1.amazonaws.com"
force_destroy = false
hosted_zone_id = "Z2M4EHUR26P7ZW"
id = "tekitouna.domain.nyuryoku"
region = "ap-northeast-1"
request_payer = "BucketOwner"
tags = {}
website_domain = "s3-website-ap-northeast-1.amazonaws.com"
website_endpoint = "tekitouna.domain.nyuryoku.s3-website-ap-northeast-1.amazonaws.com"
versioning {
enabled = false
mfa_delete = false
}
~ website {
error_document = "error.html"
index_document = "index.html"
- routing_rules = jsonencode(
[
- {
- Condition = {
- HttpErrorCodeReturnedEquals = "404"
}
- Redirect = {
- ReplaceKeyPrefixWith = "error.html?referer="
}
},
]
) -> null
}
}
Plan: 0 to add, 1 to change, 0 to destroy.
表示された差分を埋めるため、以下のように記述し、terraform plan
を実行しても差分が出ないことを確認します。
resource "aws_s3_bucket" "tekitouna_domain_nyuryoku" {
bucket = "tekitouna.domain.nyuryoku"
website {
error_document = "error.html"
index_document = "index.html"
routing_rules = <<EOF
[
{
"Condition": {
"HttpErrorCodeReturnedEquals": "404"
},
"Redirect": {
"ReplaceKeyPrefixWith": "error.html?referer="
}
}
]
EOF
}
}
resource "aws_s3_bucket_policy" "tekitouna_domain_nyuryoku" {
bucket = aws_s3_bucket.tekitouna_domain_nyuryoku.id
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadForGetBucketObjects",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::tekitouna.domain.nyuryoku/*"
}
]
}
EOF
}
お掃除
terraform destroy
コマンドを実行することで、.tfstate
で管理されているリソースを全て削除することができます。
参考