以前、こちらの記事にてALBからEC2(Apache)の環境をAWSコンソール画面から構築しました。
今回は全く同じ環境を Terraform を使用して IaC(インフラのソースコード化)してみたいと思います。
AWSのコード化には Cloudformation というAWS公式のサービスもありますが、Terraformとの比較記事をいろいろ見た結果、結局、細かいメリデメは使ってみないとわからなさそう。
ということで、最終的にTerraformのほうがリーダブルという意見に惹かれ使いはじめました。
Terraform 主要ポイント説明
Terraformを書き始める前に、まず必要な知識を説明します。
Terraformを構成する概念・要素はたくさんありますが、自分なりに最低限必要かなと思ったものを挙げてみます。
Terrafrom を構成するパーツ
今回作成したソースコードは、こちらのGitHubにおきました。
Terrafrom 本体
複数VersionのTerraformを管理できたほうがよいので、こちらの記事のようにtfenvを使用してのインストールをオススメします。
AWS認証情報
AWSの認証情報(アクセスキー ID とシークレットアクセスキーからなるアクセスキー)が必要になります。
アクセスキーを直接Terraformの構成定義ファイルに記述できますが、セキュリティ上よろしくないのでこちらの記事のように名前付きプロファイルを作成しておき、その名前を指定するようにしましょう。
構成定義ファイル(ソースコード)
拡張子を.tf として作成します。
Teffaform は 拡張子.tf が着くファイルを自動的に読み込みます。
ファイル名は何でもよいです。
ただモジュール機能を利用する場合はファイル名をmain.tfとすることを推奨しているようです
状態情報
Terraform はインフラの状態情報を持ちます。
デフォルトではTerraformを実行するローカルにterraform.tfstateというファイル名で出力され、状態情報が管理されます。
ただチーム開発やセキュリティでメリットがあるのでこちらの記事のようにリモート管理に移行したほうがよいでしょう。
主要構成ブロック
Terraformは構成をいくつかのブロックに分けて記述します。まずは以下を抑えておくとよいかと思います。
terraformブロック
terraformブロックは、Terraform自体の全体的な設定を記述します。
terraform {
required_version = "0.13.5"
backend "local" {} ・・・特に書かなくてもデフォルトlocalであるがわかりやすくするため明記
}
注意点としてTerraform実行時に、かなり早い段階でロードされるため変数が使用できません。
providerブロック
providerブロックは、AWSやGoogle Cloud Platformなど管理するインフラのプラットフォーム情報を定義します。
使用できる値はプロバイダーにより異なります。AWSは以下のようになります。
provider "aws" {
region = "ap-northeast-1"
profile = "default" ・・・プロファイル名
# keyを直接定義することもできるが非推奨となっています
# access_key = "AABBCC"
# secret_key = "XXYYZZ"
}
resourceブロック
resourceブロックは、各プロバイダーが提供するインフラストラクチャオブジェクトを定義します。
AWSであればVPCやEC2など多数あります。
## resource "オブジェクトタイプ" "識別名"
resource "aws_instance" "web-server" {
## 設定内容は、オブジェクトタイプによって異なります
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
}
またresorceブロックの出力値を別のresouceブロックのインプットとしたいときがあります。
例えば、あるVPCの上にサブネットを作成するなど。
この場合、以下のようにドット表記で記述します。
resource "aws_subnet" "test-sub-east" {
cidr_block = "10.1.1.0/24"
availability_zone = "ap-northeast-1a"
## オブジェクトタイプ.識別名.属性 とドットでつなげます。
vpc_id = aws_vpc.test-vpc.id
}
属性はマニュアルの一番下の Attributes Reference に記載されています。例えばVPCであればこちらに記載されています。
variableブロック
変数を定義します。宣言と同時にdefault値を定義できます。
## variable "変数名"
variable "instance_type" {
type = string
default = "t2.micro"
}
変数を使うときは以下のように var.変数名として使用します。
resource "aws_instance" "example" {
## var.変数名
instance_type = var.instance_type
ami = "ami-a1b2c3d4"
}
なお変数は、variableブロックで定義した上で このように.tfvarsファイルや環境変数、コマンドの引数として動的に設定を上書きできます。
outputブロック
変数やterraformで作成されたリソースの中身を出力できます。
## output 変数名
output "ec2-pulic-ip-1" {
value = aws_instance.test-server-1.public_ip
}
output "ec2-pulic-ip-2" {
value = aws_instance.test-server-2.public_ip
}
terraform applyコマンド実行時や、terraform outputコマンド実行時に以下のように出力されます。
% terraform output
ec2-pulic-ip-1 = 11.22.33.44
ec2-pulic-ip-2 = 55.66.77.88
%
モジュールでは、プログラムの戻り値のように子モジュールの値を、呼び出し元の親モジュールに返すときに利用します。
で、それをまた別のモジュールへ渡すなどしてモジュール間連携します。
またデバッグ時にも有効です。
プログラムのechoやprintfのように、リソースや変数の中身がよくわからないときにoutputで標準出力して確認できます。
構成定義ファイル作成
前提説明は以上として、構成定義ファイル(.tfファイル)の作成に入りたいと思います。
今回作成したコードがかなりのボリュームになってしまったため、全量はこちらのGithubに置きました。
以下、ポイントを絞って説明していきます。
ファイル構成
main.tf に構成定義を、variables.tf に変数を記述しました。
この分割単位や分割ファイル名(拡張子が.tf)は任意です。
実際のプロジェクトではmain.tfの1ファイルではなく、こちらの例のようにモジュール化して管理することになるとおもいます。
リソースタイプ
AWSプロビジョナーで指定できるリソースタイプは公式ドキュメントの左ペインにある各サービスのResourcesになります。
AWS自体がそうなので当然といえば当然なのですが、リソースタイプが大量にあります。
指定できる値も(必須ではなく)オプションで指定する値がかなりの数あります。
どれから手を付けてよいかわからない場合、まずAWSコンソール画面上から普通に構築してそれをterraformerというエクスポートツールでコードに落とし込むと、画面上の設定とソースコードのつながりが見えてくると思います。
EC2構築
EC2構築の部分について2点補足です。
resource "aws_instance" "test-server-east" {
ami = var.ec2-config["ami"]
...
provisioner "remote-exec" {
connection {
type = "ssh"
host = self.public_ip
user = "ec2-user"
private_key = file("./test-key")
}
inline = ["sudo yum install httpd -y && sudo sh -c \"echo 'Hello East!' > /var/www/html/index.html\" && sudo systemctl start httpd"]
}
subnet_id = aws_subnet.test-sub-east.id
vpc_security_group_ids = [aws_security_group.test-sg-sever.id]
}
Apacheインストール
EC2でのApacheインストールにプロビジョナーというブロックを使用しています。
がプロビジョナーはTerraform曰く最終手段であり、かつEC2作成時に一度しか実行されません。
本格的にEC2上のパッケージを管理する場合は、ansibleなどを利用しましょう。
EC2接続のssh鍵
private_key に指定しているssh鍵について、以前の記事においてはEC2作成時の最後にssh鍵を作成して、AWS側からローカルPCにダウンロードしました。
今回はその逆の手順で実施します。
前もってローカルPCにssh鍵を作成して、それをAWS側に設定します。
セキュリティグループでIPアクセス制限
セキュリティグループで自分のIPのみ許可する設定を入れています。
variables.tfの以下の部分を自分のIPアドレスに書き換えてください。このようなIPを確認するサイトで表示されたIPアドレスを記述します。
variable "my-ip" {
type = string
default = "11.22.33.44/32" ・・・このIPアドレスを書き換えてください
}
Terraform実行
作成したmain.tf、variables.tf を元にTerraformを実行します。
事前準備
Terraform実行の前にEC2に接続するためのssh鍵を作成します。
% ssh-keygen -N "" -f test-key
Generating public/private rsa key pair.
...
% ls test-key*
test-key test-key.pub
%
init – 作業ディレクトリ初期化
terraformを実行するディレクトリを初期化します。
定義された必要なプラグインがダウンロードされます。
$ terraform init
Initializing the backend...
Successfully configured the backend "local"! Terraform will automatically
use this backend unless the backend configuration changes.
Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Installing hashicorp/aws v3.57.0...
- Installed hashicorp/aws v3.57.0 (self-signed, key ID xxx)
apply – インフラ構築
applyで実際に構築します。
コマンドを実行するとずらーっと作成される予定のリソースが出力されます。
正しいことを確認した後、yesを入力して処理を続行します。
% terraform apply
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_instance.test-server-east will be created
+ resource "aws_instance" "test-server-east" {
+ ami = "ami-034968955444c1fd9"
+ arn = (known after apply)
+ associate_public_ip_address = true
...
Enter a value: yes ・・・yesを入力します
...
Apply complete! Resources: 17 added, 0 changed, 0 destroyed.
...
Outputs:
alb-domain = test-lb-111111.ap-northeast-1.elb.amazonaws.com
ec2-east-ip = 11.22.33.44
ec2-west-ip = 55.66.77.88
%
Outputのドメインにアクセスしてみます。
EastとWestがランダムに表示されて2台のWebサーバへアクセスできてることが確認できます。
% for i in `seq 5`; do curl http://test-lb-111111.ap-northeast-1.elb.amazonaws.com/ ; sleep 1 ; done
Hello East!
Hello East!
Hello West!
Hello West!
Hello East!
%
state list – 管理リソースの一覧表示
state list で resource に定義した一覧を出力できます。
% terraform state list
aws_instance.test-server-east
aws_instance.test-server-westaws_internet_gateway.test-gw
aws_key_pair.test-key
...
%
show – インフラ状態情報の全体表示
show で現在のインフラ状態情報を全部確認できます。
% terraform show
# aws_instance.test-server-east:
resource "aws_instance" "test-server-east" {
ami = "ami-034968955444c1fd9"
arn = "arn:aws:ec2:ap-northeast-1:11111:instance/i-01010101"
associate_public_ip_address = true
..
%
state show – インフラ状態情報の個別表示
state show [リソースタイプ].[識別名] でリソースを個別に確認できます。
% terraform state show aws_vpc.test-vpc
# aws_vpc.test-vpc:
resource "aws_vpc" "test-vpc" {
arn = "arn:aws:ec2:ap-northeast-1:111111:vpc/vpc-0101010101"
...
%
state list で出力された内容を指定してあげる形になります。
output – アウトプット定義表示
outputブロックで定義したものを出力します。
% terraform state show aws_vpc.test-vpc
alb-domain = test-lb-111111.ap-northeast-1.elb.amazonaws.com
ec2-east-ip = 11.22.33.44
ec2-west-ip = 55.66.77.88
...
%
destroy – インフラ削除
作成した環境の削除は文字通り destroy となります。
% terraform destroy
aws_key_pair.test-key: Refreshing state... [id=test-key]
aws_vpc.test-vpc: Refreshing state... [id=vpc-0101010101]
...
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes ・・・yesを入力
...
%
今回は以上ですーノシ
参考
(人´∀`)ァリガトネ♪
公式ドキュメント:各ブロック説明
公式ドキュメント:AWSプロバイダー説明
CloudFormation vs Terraform
10分で理解するTerraform