はじめに
EC2で何かすぐ検証したい場合などにTerraformを使ってEC2を作成してSSH接続できると便利です。
手動作成に頼っていると手順確認に時間がかかったり操作ミスをしたりゴミが残ったりしてしまいがちですが、Terraformだとその不安を限りなく小さくできます。
多少知識は必要になるため、本記事ではサンプルコードを用意してすぐにEC2を起動してSSHできるよう手順を簡潔にまとめてみました。Terraformを触ったことがない方でもわかるように導入手順からまとめたつもりです。
環境
本記事の手順は以下の環境で動作確認を行っています。
- OS: macOS Monterey 12.3.1
- Terraform: 1.1.9
- AWS CLI: 2.5.4
Terraformのインストール
Homebrewを使って、以下だけでインストールできます。
$ brew tap hashicorp/tap
$ brew install hashicorp/tap/terraform
手順の最新情報は以下を参照して下さい。
AWS CLIのインストール
以下からpkgファイルをダウンロードしてGUIで簡単にインストールできます。
手順の最新情報は以下を参照して下さい。
インストール後、AWS CLIにAWSアクセスキーの設定が必要です。
アクセスキーをIAMで取得して、aws configure
で設定します。
以下のように--profile
を指定してプロファイルを作成しておくのがおすすめです。
$ aws configure --profile my-profile-name
AWS Access Key ID [None]: my-access-key
AWS Secret Access Key [None]: my-secret-key
Default region name [None]: ap-northeast-1
Default output format [None]: json
環境変数に上で設定したプロファイル名を設定します。
$ export AWS_DEFAULT_PROFILE=my-profile-name
環境変数を毎回設定するのが面倒な場合は、ログインシェルの設定ファイル(.zshrc
など)に登録すると良いと思います。
Terraformのセットアップ
私が作成したサンプルコードがGitHubにあります。
これをクローンして準備します。
terraform init
ではTerraformの実行に必要なバイナリが.terraform
配下にダウンロードされます。
$ git clone https://github.com/inayuky/terraform-sampler
$ cd terraform-sampler/ec2-ssh
$ terraform init
鍵の作成
SSH接続に必要な秘密鍵と公開鍵を作成します。
以下のコマンドだけで作成できます。
秘密鍵のec2_key
と公開鍵のec2_key.pub
という2つのファイルが作成されます。
$ ssh-keygen -N "" -f ec2_key
以上で準備は完了です。
インスタンス作成
terraform plan
でどんなリソースが作成されるかを確認できます。
エラーが出力されないことを確認します。
$ terraform plan
terraform apply
で作成できます。
$ terraform apply
本当に実行していいか確認が求められるので、yes
を入力します。
(ここで課金が発生することになるので注意してください。デフォルトではt3.micro
という小さいインスタンスタイプを指定しているため、1時間あたり2円未満のはずですが。)
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
作成が完了すると以下のような出力が表示されます。
Apply complete! Resources: 11 added, 0 changed, 0 destroyed.
Outputs:
public_ip = "13.112.146.55"
ssh_command = "ssh -i ec2_key ec2-user@13.112.146.55"
表示されたSSHコマンドをそのまま入力するとSSH接続できます。(接続できるまで数秒かかるかもしれません)
以下のように表示されるのでyes
と入力します。
inayuky ec2-ssh % ssh -i ec2_key ec2-user@13.112.146.55
The authenticity of host '13.112.146.55 (13.112.146.55)' can't be established.
ED25519 key fingerprint is SHA256:btJ3uBhFnw1O+LyvHnpmFNwGmvYl/Hv0MdvdPj99RZo.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])?
これでインスタンスが作成され、SSH接続できることを確認できました。
Warning: Permanently added '13.112.146.55' (ED25519) to the list of known hosts.
__| __|_ )
_| ( / Amazon Linux 2 AMI
___|\___|___|
https://aws.amazon.com/amazon-linux-2/
4 package(s) needed for security, out of 4 available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-10-0-0-137 ~]$
インスタンス削除
作成するとインスタンスは起動状態になり、課金が発生するので(停止したとしてもEBSボリュームの料金はかかりますが)、不要になったら削除します。
$ terraform destroy
サンプルコードの解説
コメントにある程度わかるように情報を記載したつもりですが、もう少し解説しておこうと思います。
変数
- 鍵の名前とリソース名は変数で定義している
- デフォルト値を設定しているので、なにも指定しない場合この値が採用される
- 値を変更する場合、ソースを直接変更する以外にも
terraform apply -var 'key_name=my_key' -var 'resource_name=sample2'
のようにコマンド実行時に指定することもできる。
# 鍵の名前
# 秘密鍵のファイル名に相当する名前
variable "key_name" {
type = string
default = "ec2_key"
}
# リソース名
# 簡単のため全リソースでここで設定した共通の名前を使用する
variable "resource_name" {
type = string
default = "sample1"
}
EC2
- 最新AmazonLinux2のイメージ(AMI)を使用して起動する
- インスタンスタイプは
t3.micro
を指定しているが、任意に変更できる - ElasticIP(EIP)はコメントを外すだけで使える(EIPはインスタンス停止時に課金が発生するので注意)
-
sg.tf
でセキュリティグループを追加した場合は、インスタンス側でも指定する必要がある - EBSは指定しなくてもデフォルトのものが使用される。コメントを外して任意のサイズとタイプの設定も可能。
# 最新のAmazonLinux2のイメージ
data "aws_ami" "latest_amzn2" {
owners = ["amazon"]
most_recent = true
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
# キーペア
# 変数で指定した値を設定
resource "aws_key_pair" "this" {
key_name = var.key_name
public_key = file("${var.key_name}.pub")
}
# EIPを使う場合は以下を追加する
# resource "aws_eip" "this" {
# instance = aws_instance.this.id
# vpc = true
# }
# インスタンス
resource "aws_instance" "this" {
instance_type = "t3.micro" # インスタンスタイプは任意に設定する
vpc_security_group_ids = [aws_security_group.ssh.id]
# httpのSGを追加する場合は以下のようにする
# vpc_security_group_ids = [aws_security_group.ssh.id, aws_security_group.http.id]
# EBSのサイズとタイプを指定する場合は以下のように追加する
# root_block_device {
# volume_size = 30 # 単位GB
# volume_type = "gp3" # gp2がデフォルト。 他はstandard, gp3, io1, io2, sc1, st1。
# }
ami = data.aws_ami.latest_amzn2.id
subnet_id = aws_subnet.public.id
key_name = aws_key_pair.this.key_name
tags = {
Name = var.resource_name # インスタンス名
}
lifecycle {
ignore_changes = [
ami # インスタンスに変更を加えようとしたら、AMIが新しくなっていてインスタンス再作成が要求されるのを防止するため
]
}
}
セキュリティグループ
- SSH接続用のSGのみ作成している
- HTTPなど追加したい場合は、コメントを参照
# SSHのセキュリティグループ
resource "aws_security_group" "ssh" {
name = "ssh_sg"
vpc_id = aws_vpc.this.id
}
resource "aws_security_group_rule" "ingress" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # 接続元を限定する場合は変更する
security_group_id = aws_security_group.ssh.id
}
resource "aws_security_group_rule" "egress" {
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.ssh.id
}
# セキュリティグループの追加方法
# たとえば、httpのセキュリティグループが必要な場合は以下のように追加する
# resource "aws_security_group" "http" {
# name = "http_sg"
# vpc_id = aws_vpc.this.id
# }
# resource "aws_security_group_rule" "http_ingress" {
# type = "ingress"
# from_port = 80
# to_port = 80
# protocol = "tcp"
# cidr_blocks = ["0.0.0.0/0"]
# security_group_id = aws_security_group.http.id
# }
# resource "aws_security_group_rule" "http_egress" {
# type = "egress"
# from_port = 0
# to_port = 0
# protocol = "-1"
# cidr_blocks = ["0.0.0.0/0"]
# security_group_id = aws_security_group.http.id
# }
VPC
- EC2を起動してSSH接続するのに必要な最低限のリソースを作成している
- VPCはCIDRが同一のものが存在しても複数作成が可能であり、VPCが異なれば他の環境と干渉することはない(あえてそういう設定を加えなければ)ため、安心して作成/削除ができる
# VPC
resource "aws_vpc" "this" {
cidr_block = "10.0.0.0/16" # 値が既存VPCと重複しても作成可能
enable_dns_support = true # AWSのDNSサーバによる名前解決を有効
enable_dns_hostnames = true # VPC内のリソースにパブリックDNSホスト名を自動的に割り当てる
tags = {
Name = var.resource_name
}
}
# サブネット
resource "aws_subnet" "public" {
vpc_id = aws_vpc.this.id
cidr_block = "10.0.0.0/24"
map_public_ip_on_launch = true # 起動したインスタンスにパブリックIPを自動割当
availability_zone = "ap-northeast-1a" # AZ指定
tags = {
Name = var.resource_name
}
}
# インターネットゲートウェイ
resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id
tags = {
Name = var.resource_name
}
}
# ルートテーブル
resource "aws_route_table" "public" {
vpc_id = aws_vpc.this.id
tags = {
Name = var.resource_name
}
}
# ルートテーブルのエントリ
resource "aws_route" "public" {
route_table_id = aws_route_table.public.id
gateway_id = aws_internet_gateway.this.id
destination_cidr_block = "0.0.0.0/0"
}
# サブネットとルートテーブルの関連付け
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
output
- apply時などにターミナルに出力する値
- 構成変更してもたまに値が更新されないことがあったが、
terraform refresh
かterraform apply -refresh-only
を実行すると更新される模様
# パブリックIPを出力
output "public_ip" {
value = aws_instance.this.public_ip
}
# sshコマンドを出力
output "ssh_command" {
value = "ssh -i ${var.key_name} ec2-user@${aws_instance.this.public_ip}"
}
provider
- リージョン等を指定する。(リージョン指定しなくてもAWS CLIのデフォルトリージョンで動作はすると思う)
- AWSの認証情報も指定できたりするが、ソースに入れるとやっかいなことが多いので、認証情報は環境変数やAWS CLI側に持たせるのが好み
provider "aws" {
region = "ap-northeast-1" # 東京リージョン
}
関連Tips
関連して覚えておくと便利なTipsを以下に記載します。
EC2の状態確認や電源操作をコマンドから簡単に実行する方法
以下の記事で、簡単にEC2の状態確認や電源操作をする方法を書いたので、Terraformと合わせて使用すると、EC2の作成/状態確認/起動停止/削除までコマンドで簡単にできて便利なので、参考にしてください。
作成したリソースの確認方法
以下で、作成したリソースの一覧が確認できます。
$ terraform show
これだと全てのリソースが表示されるので、リソース毎に確認したい場合は以下です。
まず、以下でリソース名一覧を表示します。
$ terraform state list
実行例
inayuky ec2-ssh % terraform state list
data.aws_ami.latest_amzn2
aws_instance.this
aws_internet_gateway.this
aws_key_pair.this
aws_route.public
aws_route_table.public
aws_route_table_association.public
aws_security_group.ssh
aws_security_group_rule.egress
aws_security_group_rule.ingress
aws_subnet.public
aws_vpc.this
以下のようにリソース名を指定して確認できます。
$ terraform state show aws_instance.this
実行例
inayuky ec2-ssh % terraform state show aws_instance.this
# aws_instance.this:
resource "aws_instance" "this" {
ami = "ami-0f9a314ce79311c88"
arn = "arn:aws:ec2:ap-northeast-1:123454912345:instance/i-09cac7dead23465a76"
associate_public_ip_address = true
availability_zone = "ap-northeast-1a"
cpu_core_count = 1
cpu_threads_per_core = 2
disable_api_termination = false
ebs_optimized = false
get_password_data = false
hibernation = false
id = "i-09cac7dead23465a76"
・・・以下省略・・・
SSH接続時の確認を省略する方法
SSHのデフォルトの動作だと新しいIPアドレスに接続するたびに確認を求められると思いますが、~/.ssh/config
に以下の設定を追加しておくと省略できます。
host *
StrictHostKeyChecking no
UserKnownHostsFile=/dev/null
一応以下のようにSSHコマンド実行時にオプションで渡すこともできます。
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null host_name
参考情報
https://booth.pm/ja/items/1318735
Pragmatic Terraform on AWS
https://qiita.com/koki_develop/items/45cdde3d27bd75f1bfd5
TerraformでVPC・EC2インスタンスを構築してssh接続する
https://blog.serverworks.co.jp/automate-latest-ami-ec2
【CloudFormation / Terraform】EC2の最新版のAMI IDを自動的に取得、構築する (Windows / Linux)