TerraformはIaC (Infrastructure as Code)を実現するためのツールです。Terraformを使用して、インフラストラクチャの構成をコードに定義し、宣言的にリソースを作成します。コードによってインフラストラクチャのパラメータを定義するため、チーム内でのコードの共有や再利用、各種パラメータの更新、自動化システムへの組み込みなどを実現できます。数多くのプロバイダーをサポートしており、各種クラウド環境にてTerraformを使うことができます。
AWS上でWebブラウザからリソースを作成したことはありますか。例えばEC2を作成する場合、SSHキーペアの作成やVPCの作成、セキュリティグループの定義など様々な作業を行う必要があります。手作業での設定はエラーの発生が高く、再利用不可で、設定項目のレビューの手間もあります。
手作業でEC2の作成を行う場合、以下のような手順があります。
- VPCの作成
- サブネットの作成
- セキュリティグループの作成
- SSHキーペアの作成
- EC2の作成
Terraformを使用すると、これらの作業をterraform apply
のコマンド一つにまとめて行うことができます。
1. AWS CLIとTerraformのインストール
2. AWS_ACCESS_KEY_ID と AWS_SECRET_ACCESS_KEYの取得
AWSコンソールからAWS_ACCESS_KEY_ID と AWS_SECRET_ACCESS_KEYの取得を行います。
3. Terraformのコードを作成
terraform-aws-ec2`という名前のディレクトリを作成します。このディレクトリ以下にファイルを作成していきます。
1 2 |
mkdir terraform-aws-ec2 cd `terraform-aws-ec2` |
main.tf
– プロバイダーの指定
使用するプロバイダーの情報を記述します。認証情報をファイルに記述することも可能ですが、セキュリティの観点から非推奨です。また、デフォルトで使用するリージョンもしています。今回はap-northeast-1
です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 4.16" } } required_version = ">= 1.2.0" } provider "aws" { region = "ap-northeast-1" } |
vpc.tf
– VPCとサブネットの作成
VPC
VPCを作成します。
1 2 3 4 5 6 7 8 |
# VPC resource "aws_vpc" "my-ec2-vpc" { cidr_block = "10.0.0.0/16" tags = { Name = "my-ec2-vpc" } } |
サブネット
作成したVPCの内部にサブネットを作成します。今回は、パブリックサブネットを3つとプライベートサブネットを3つ、それぞれを3つのAZ(Availability Zone)に分散して作成します。
東京リージョン ap-northeast-1
で使用可能なAZは以下の3つです。利用するリージョンに合わせて、AZを指定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# Public Subnet: public_1a, public_1b, public_1c resource "aws_subnet" "public_1a" { vpc_id = aws_vpc.my-ec2-vpc.id availability_zone = "ap-northeast-1a" cidr_block = "10.0.1.0/24" map_public_ip_on_launch = "true" tags = { Name = "my-ec2-vpc-public-1a" } } resource "aws_subnet" "public_1b" { vpc_id = aws_vpc.my-ec2-vpc.id availability_zone = "ap-northeast-1c" cidr_block = "10.0.2.0/24" map_public_ip_on_launch = "true" tags = { Name = "my-ec2-vpc-public-1b" } } resource "aws_subnet" "public_1c" { vpc_id = aws_vpc.my-ec2-vpc.id availability_zone = "ap-northeast-1d" cidr_block = "10.0.3.0/24" map_public_ip_on_launch = "true" tags = { Name = "my-ec2-vpc-public-1c" } } # Private Subnets: private_1a, private_1b, private_1c resource "aws_subnet" "private_1a" { vpc_id = aws_vpc.my-ec2-vpc.id availability_zone = "ap-northeast-1a" cidr_block = "10.0.10.0/24" tags = { Name = "my-ec2-vpc-private-1a" } } resource "aws_subnet" "private_1b" { vpc_id = aws_vpc.my-ec2-vpc.id availability_zone = "ap-northeast-1d" cidr_block = "10.0.20.0/24" tags = { Name = "my-ec2-vpc-private-1b" } } resource "aws_subnet" "private_1c" { vpc_id = aws_vpc.my-ec2-vpc.id availability_zone = "ap-northeast-1c" cidr_block = "10.0.30.0/24" tags = { Name = "my-ec2-vpc-private-1c" } } |
IGW
インターネットゲートウェイを作成します。これは、サブネット内のリソースがインターネットにアクセスするために必要です。
1 2 3 4 5 6 7 8 |
# Internet Gateway resource "aws_internet_gateway" "my-ec2-vpc-igw" { vpc_id = aws_vpc.my-ec2-vpc.id tags = { Name = "my-ec2-vpc-igw" } } |
ルートテーブル
パブリックサブネットがインターネットゲートウェイを使用するように、ルートテーブルを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# Route table resource "aws_route_table" "public_rt" { vpc_id = aws_vpc.my-ec2-vpc.id route { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.my-ec2-vpc-igw.id } tags = { Name = "my-ec2-vpc-pulic-rt" } } resource "aws_route_table_association" "public_rt_1a" { subnet_id = aws_subnet.public_1a.id route_table_id = aws_route_table.public_rt.id } resource "aws_route_table_association" "public_rt_1b" { subnet_id = aws_subnet.public_1b.id route_table_id = aws_route_table.public_rt.id } resource "aws_route_table_association" "public_rt_1c" { subnet_id = aws_subnet.public_1c.id route_table_id = aws_route_table.public_rt.id } |
sg.tf
– セキュリティグループの作成
EC2インスタンスのセキュリティグループを作成します。今回は、EC2インスタンスをパブリックサブネット上に作成します。そのため、必要なポートにのみインターネットからのアクセス(ingress)を許可します。SSHのポート22番とNginx用のポート80番を許可します。また、EC2からインターネットへのアクセス(egress)も許可します。
- セキュリティグループ: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
resource "aws_security_group" "ec2-sg" { name = "allow-ssh-http" description = "Allow ssh and http" vpc_id = aws_vpc.my-ec2-vpc.id tags = { Name = "ec2-sg" } } # インターネットから22番ポートへアクセスを許可 resource "aws_vpc_security_group_ingress_rule" "allow_tls_ipv4" { security_group_id = aws_security_group.ec2-sg.id cidr_ipv4 = "0.0.0.0/0" from_port = 22 ip_protocol = "tcp" to_port = 22 } # インターネットから80番ポートへアクセスを許可 resource "aws_vpc_security_group_ingress_rule" "allow_http_ipv4" { security_group_id = aws_security_group.ec2-sg.id cidr_ipv4 = "0.0.0.0/0" from_port = 80 ip_protocol = "tcp" to_port = 80 } # VPCからインターネットにアクセスを許可 resource "aws_vpc_security_group_egress_rule" "allow_all_traffic_ipv4" { security_group_id = aws_security_group.ec2-sg.id cidr_ipv4 = "0.0.0.0/0" ip_protocol = "-1" # 全てのポート } |
ec2.tf
– EC2の作成
SSHキーペア
EC2インスタンスにSSHする場合は、SSHキーペアの登録が必要です。以下のコードでSSHキーペアを作成します。
実際のプロジェクトで、TerraformでSSHキーペアを作成する場合は注意が必要です。Terraformのstateファイルに秘密鍵が含まれるため、適切に管理しないと鍵が漏洩する可能性もあります。より安全な方法は、AWSコンソール上で別途SSHキーペアを作成し、それをインスタンス作成時に指定します。鍵はTerraformと分離して管理します。
すでに存在するキーペアを使用する場合は、鍵の生成をスキップすることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
resource "tls_private_key" "private_key" { algorithm = "RSA" rsa_bits = 4096 } resource "aws_key_pair" "generated_key" { key_name = "my-terraform-ec2-key" public_key = tls_private_key.private_key.public_key_openssh } output "private_key" { value = tls_private_key.private_key.private_key_pem sensitive = true } |
EC2インスタンス
EC2インスタンスを作成します。既存のSSHキーペアを使用する場合は、コメントアウトの箇所を参考にしてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
resource "aws_instance" "ec2-instance" { ami = "ami-08ce76bae392de7dc" # AMIのIDを指定 - AWS Linux 2023 instance_type = "t2.micro" # インスタンスタイプの指定 subnet_id = aws_subnet.public_1a.id key_name = aws_key_pair.generated_key.key_name vpc_security_group_ids = [aws_security_group.ec2-sg.id] root_block_device { volume_size = 20 volume_type = "gp3" encrypted = true } tags = { Name = "EC2_terraform_test" } } # Final output output "PublicIP" { value = aws_instance.ec2-instance.public_ip } |
remote-execでNginxのインストール自動化
EC2インスタンスにSSH経由でNginxをインストールします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
resource "null_resource" "setup-nginx" { # ssh into the ec2 instance connection { type = "ssh" user = "ec2-user" private_key = tls_private_key.private_key.private_key_pem host = aws_instance.ec2-instance.public_ip } provisioner "remote-exec" { inline = [ "sudo dnf update -y", "sudo dnf install docker -y", "sudo systemctl start docker", "sudo docker run -d -p 80:80 nginx:latest", ] } # wait for ec2 to be created depends_on = [aws_instance.ec2-instance] } |
4. terraformコマンドの実行
コードを作成したらterraform
コマンドを実行して、リソースを作成していきます。
Terraformを初期化
1 |
terraform init |
Terraformのコードのフォーマット
1 |
terraform fmt |
Terraformのコードを検証
1 |
terraform validate |
Success! The configuration is valid.
と表示されたら成功です。
コードにエラーがある場合は、次のようなエラーメッセージが表示されます。このエラーは、「sg.tf
の4行目のVPC IDの指定をしている箇所にて、"ec2-vpc"
というリソースが見つからない」という意味です。該当の箇所をチェックして修正します。
1 2 3 4 5 6 7 8 |
╷ │ Error: Reference to undeclared resource │ │ on sg.tf line 4, in resource "aws_security_group" "ec2-sg": │ 4: vpc_id = aws_vpc.ec2-vpc.id │ │ A managed resource "aws_vpc" "ec2-vpc" has not been declared in the root module. ╵ |
Terraformの実行計画を行う
Terraformが何を作成するかが表示されます。出力をレビューして、問題ないかどうかを確認します。
1 |
terraform plan |
Terraformでリソースを作成する
1 |
terraform apply |
Terraformが作成する予定のリソースが表示された後に、次のような確認のプロンプトが表示されます。問題ない場合は、yes
と入力します。
1 2 3 4 5 |
Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: |
その後は、Terraformが自動的にリソースを作成します。しばらくすると、TerraformがSSHしてEC2上でDockerとNginxをインストールしているログなども流れてくるはずです。
最終的に次のような出力がされたら成功です。
1 2 3 4 5 6 |
Apply complete! Resources: 20 added, 0 changed, 0 destroyed. Outputs: PublicIP = "xxx.xxx.xxx.xxx" private_key = <sensitive> |
5. デプロイされた環境の検証
正しくリソースが作成されたか確認します。AWSのコンソールから該当のリソースが存在するかどうか確認してみてください。また、出力されたIPアドレスにアクセスしてみます
Nginxをポート80番で公開しているので、curl
コマンドでアクセスを確認します。
1 |
curl -v http://xxx.xxx.xxx.xxx |
SSHでEC2にログインする場合は、次の手順で行います。まず、SSHプライベートキーをローカルに用意します。Terraformで作成した場合は、次のコマンドでファイルに出力できます。
1 |
terraform output -raw private_key > my-terraform-test.pem |
生成された鍵のパーミッションを調整します。
1 |
chmod 400 my-terraform-test.pem |
SSHログインします。
1 |
ssh -i my-terraform-test.pem ec2-user@xxx.xxx.xxx.xxx |
6. リソースの削除
テストが終了したら、作成したリソースを削除します。削除し忘れると無駄に料金を請求される場合があります。コストの管理は自己責任で行ってください。
1 |
terraform destroy |
削除してよろしいかどうか聞かれます。問題なければ、yes
と入力します。
1 2 3 4 5 |
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: |
応用
vars.tf
– 変数の使用
Terraformでは変数を使用することができます。繰り返し出現する値やデプロイ時に指定する値などに使用できます。
例として、vars.tf
というファイルを作成して、EC2のAMIを変数から指定します。
1 2 3 |
variable "AMI_ID" { default = "ami-08ce76bae392de7dc" } |
ec2.tf
内で、変数AMI_ID
を使用します。
1 2 3 4 5 6 7 |
resource "aws_instance" "ec2-instance" { # ami = "ami-08ce76bae392de7dc" # AMIのIDを指定 - AWS Linux 2023 ami = "{var.AMI_ID}" instance_type = "t2.micro" # インスタンスタイプの指定 subnet_id = aws_subnet.public_1a.id key_name = aws_key_pair.generated_key.key_name vpc_security_group_ids = [aws_security_group.ec2-sg.id] |
この方法を使用して、これらの値を変数からとれるようにコードを修正してみましょう。
- EC2
- AMI
- インスタンスタイプ
- サブネットID
- ボリュームサイズ
- VPC
- AZ ID
まとめ
Terraform を使用して、AWS上にEC2インスタンスを作成しました。