どうも、キタです
昨今はAIがあるので、コーディングもかなり楽になってきましたね。
今回は Terraform を利用して、EC2インスタンスを簡単に作ってみましょう。
1時間程度でサクッと試せるハンズオンです。
※IaCやTerraformに関する説明は省略しています。
この記事がオススメの方
・AWSでインフラ構築をしたことがある方(VPC,EC2等)
・IaC に興味があり、Terraform を初めて触る方
この記事の目標
・Terraform を使ってAWS上に簡単なリソースを作成・変更・削除できるようになる
作成リソース
よくある最低限の構成です。
★VPC - 10.0.0/16
★パブリックサブネット×2 - (1a:10.0.0/24)(1c:10.0.1/24)
★IGW,RT,SG
★EC2 - インスタンスタイプ:t3.micro
セットアップ
各ツールのインストール・セットアップ方法は以下の通りです。
※既にセットアップが済んでいる方は飛ばしてOKです
・unzip
・git
・aws-cli
・tfenv
unzip,git
// Almalinuxの場合
$ sudo dnf install -y unzip git
// Ubuntuの場合
$ sudo apt install -y unzip git
aws-cli
aws-cliは以下マニュアル通りにインストールします
$ curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
$ unzip awscliv2.zip
$ sudo ./aws/install
クレディシャル(認証情報の設定)は適切な権限を付与したIAMユーザーを作成し、
Short-term credentialsで行っています。
$ aws configure
AWS Access Key ID [None]: <IAMユーザのアクセスキー>
AWS Secret Access Key [None]: <IAMユーザのシークレットアクセスキー>
Default region name [None]: 何も入力せずEnter
Default output format [None]: 何も入力せずEnter
tfenv
tfenv は Terraform のバージョン管理ツールです。
複数環境で異なるバージョンを使用する場合に、簡単にバージョン切り替えが出来るのでとてもオススメです。
Manualの1から順番に行いインストールします。
基本的にはコマンド通りに git clone してPATHを通してあげればOKです
$ git clone --depth=1 https://github.com/tfutils/tfenv.git ~/.tfenv
$ echo 'export PATH="$HOME/.tfenv/bin:$PATH"' >> ~/.bash_profile
$ source .bash_profile
今回はバージョン 1.11.3 をインストールして
インストールリストの確認をします。
$ tfenv install 1.11.3
$ tfenv list
* 1.11.3 (set by /home/user/.tfenv/version)
インストールを確認したので、useで使用していきます。
$ tfenv use 1.11.3
Switching default version to v1.11.3
Default version (when not overridden by .terraform-version or TFENV_TERRAFORM_VERSION) is now: 1.11.3
スイッチできたら現在のTerraformのバージョン確認をします。
$ terraform version
Terraform v1.11.3
on linux_amd64
バージョン確認も問題なさそうですね。
では事前準備を行っていきます。
事前準備
テスト用のディレクトリを作成して移動します
$ mkdir terraform-test-project
$ cd terraform-test-project
githubからコードをcloneする
$ git clone https://github.com/shinkitada/education-terrafom.git
$ cd education-terrafom
$ mv terraform.tfvars.example terraform.tfvars
以下リポジトリにコードを上げています。
リソース作成
Terraform コマンドの実行への一連の流れについては以下の通りです。
今回は作成から削除まで、一連の流れで実行していきましょう。
・initでTerraformの初期化、必要なプロバイダー情報やプラグインをインストールします
$ terrafom init
init を実行すると以下lockファイルとキャッシュディレクトリが自動作成されます。
・fmt / validate でフォーマットの整形と構文を検証します
$ terraform fmt
$ terraform validate
あえてec2.tfがfmtで整えられるようにしているので整形されるはずです。
ではplanをしてみましょう。
$ terraform plan
エラーがなければapplyで実行します。
$ terraform apply
アクションの実行確認が出るので「yes」と入力してEnterします
ちなみに、apply を実行すると「terraform.tfstate」が自動作成されます。
tfstate には、実行で作成されたリソース内容が全て書き込まれています。
つまり、以降 Terraform は tfstate ファイルを参照して、リソースの差分確認(追加リソースの作成や削除)を行います。
複数人で特定の環境でTerraformを実行する場合は tfstate ファイルを S3 で管理する のが望ましいですが、今回は割愛します。
では、作成したインスタンスのパブリックIPを確認してみましょう
$ terraform state show aws_instance.my_instance | grep public_ip
$ ssh -i "./key/<自分の名前>-key.id_rsa" ec2-user@<コピーしたIPアドレス>
例:$ ssh -i "./key/kitada-key.id_rsa" ec2-user@203.0.113.0
接続できればOKです。
ではexitで作成したEC2から抜けて、削除までしてしまいましょう。
$ exit
削除前のドライランを実行します
$ terraform plan -destroy
エラーが出ないことを確認して削除します。
$ terraform destroy
アクションの実行確認が出てくるので「yes」と入力してEnter
destroyedになれば成功です!
お疲れ様でした。
コード解説
では最後にコード解説をして終わろうと思います。
それぞれのファイル構成は以下の通りです。
.
├── ec2.tf (EC2インスタンスと関連リソースの定義)
├── provider.tf (プロバイダーの設定)
├── terraform.tfvars(変数の値)
├── variables.tf (変数の定義)
└── vpc.tf (VPCとネットワーク関連リソースの定義)
・ec2.tf (EC2インスタンスと関連リソースの定義)
#httpプロバイダーを使用してmyipのURLを取得する
data "http" "my_ip" {
url = "https://api.ipify.org?format=json"
}
#ローカル変数
locals {
public_key_file = "./.key/${var.name}-key.id_rsa.pub"
private_key_file = "./.key/${var.name}-key.id_rsa"
my_ip = "${jsondecode(data.http.my_ip.response_body)["ip"]}/32"
}
#秘密鍵の作成
resource "tls_private_key" "keygen" {
algorithm = "RSA"
rsa_bits = 4096
}
resource "local_file" "private_key_pem" {
filename = local.private_key_file
content = tls_private_key.keygen.private_key_pem
provisioner "local-exec" {
command = "chmod 600 ${local.private_key_file}"
}
}
resource "local_file" "public_key_pem" {
filename = local.public_key_file
content = tls_private_key.keygen.public_key_pem
provisioner "local-exec" {
command = "chmod 600 ${local.public_key_file}"
}
}
resource "aws_key_pair" "key_pair" {
key_name = "${var.name}-key"
public_key = tls_private_key.keygen.public_key_openssh
}
# SSH用のセキュリティグループを作成
resource "aws_security_group" "allow_ssh" {
name = "${var.name}-allow-ssh"
description = "Allow SSH inbound traffic"
vpc_id = aws_vpc.my-vpc.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [local.my_ip]
}
}
variable "ami_id" {
description = "The AMI ID for the instance"
type = string
default = "ami-03ec4a957caaadb88"
}
#インスタンス作成
resource "aws_instance" "my_instance" {
ami = var.ami_id
instance_type = "t3.micro"
key_name = aws_key_pair.key_pair.key_name
subnet_id = aws_subnet.my_subnet_1a.id
vpc_security_group_ids = [aws_security_group.allow_ssh.id]
tags = {
Name = "terraform-edu-${var.name}-instance"
}
}
このファイルでは、AWS上にLinuxの仮想サーバー(EC2インスタンス)を立てるための設定です。
SSHで安全に接続できるように接続元の現在のIPアドレスからのアクセスのみを許可する設定を入れています。
#httpプロバイダーを使用してmyipのURLを取得する
data "http" "my_ip" {
url = "https://api.ipify.org?format=json"
}
#ローカル変数
locals {
public_key_file = "./.key/${var.name}-key.id_rsa.pub"
private_key_file = "./.key/${var.name}-key.id_rsa"
my_ip = "${jsondecode(data.http.my_ip.response_body)["ip"]}/32"
}
このコードではローカル変数(locals)というものを利用しています。
これは、外部のHTTPのURLから情報を取得するための設定です。「my_ip」という名前でこの設定を後から参照します。
url = "https://api.ipify.org?format=json":
ここで指定されたURLにアクセスすると、接続元の現在のインターネット接続のIPアドレスがJSON形式で返ってきます。
これを利用してSGで許可設定をする際に、マイIPで許可設定を入れるというようなことを実現しています。
それぞれローカル(ファイル内のみ)変数を利用して
public_key_file
とprivate_key_file
ではEC2への接続に必要な公開鍵と秘密鍵のファイルの保存場所を定義しています。
my_ip
ではJSONデータの中から「ip」というキーの値(あなたのIPアドレス)を取り出しています。
/32
では特定のIPアドレスのみを許可するCIDR表記にしています。
resource "tls_private_key" "keygen" {
algorithm = "RSA"
rsa_bits = 4096
}
これは、サーバーへの安全な接続に必要な秘密鍵と公開鍵のペアをTerraformに作成させる設定です。
「keygen」という名前でこの鍵ペアを参照できます。
認証方式は RSA にして 4096bit で作成しています。
resource "local_file" "private_key_pem" {
filename = local.private_key_file
content = tls_private_key.keygen.private_key_pem
provisioner "local-exec" {
command = "chmod 600 ${local.private_key_file}"
}
}
resource "local_file" "public_key_pem" {
filename = local.public_key_file
content = tls_private_key.keygen.public_key_pem
provisioner "local-exec" {
command = "chmod 600 ${local.public_key_file}"
}
}
これはローカル(実行元のコントロールサーバー)内にファイルを作成するための設定です。
その権限を600にするコマンドを実行しています。
resource "aws_key_pair" "key_pair" {
key_name = "${var.name}-key"
public_key = tls_private_key.keygen.public_key_openssh
}
これは、AWSの「キーペア」という仕組みに公開鍵を登録するための設定です。
EC2インスタンスにSSH接続する際に使われます。
public_key = tls_private_key.keygen.public_key_openssh
で先ほど作成した公開鍵の内容をAWSに登録します。
resource "aws_security_group" "allow_ssh" {
name = "${var.name}-allow-ssh"
description = "Allow SSH inbound traffic"
vpc_id = aws_vpc.my-vpc.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [local.my_ip]
}
}
ここではAWSのSGの設定をしています。
22番ポートを指定して許可元のIPは先ほどローカル変数で参照したmy_ipからのみを許可する設定にしています。
variable "ami_id" {
description = "The AMI ID for the instance"
type = string
default = "ami-03ec4a957caaadb88"
}
resource "aws_instance" "my_instance" {
ami = var.ami_id
instance_type = "t3.micro"
key_name = aws_key_pair.key_pair.key_name
subnet_id = aws_subnet.my_subnet_1a.id
vpc_security_group_ids = [aws_security_group.allow_ssh.id]
tags = {
Name = "terraform-edu-${var.name}-instance"
}
}
これが、実際にAWS上に仮想サーバー(EC2インスタンス)を作成する設定です。
amiはそれぞれIDが割り当てられており、今回はAlmalinux9のAMI IDを指定しています。
インスタンスタイプはt3.microで秘密鍵やSGを先ほど設定したものにしています。
・provider.tf (プロバイダーの設定)
#AWSプロバイダーの設定
provider "aws" {
region = var.region
default_tags {
tags = {
environment = var.environment
}
}
}
#httpプロバイダーの設定
provider "http" {}
ここではプロバイダーの設定を行っています。
今回はAWSで、リージョンは変数からap-northeast-1(東京リージョン)を指定しています。
また、default_tagsを使用して、リソースを作成する際に自動的にタグを付与しています。
httpプロバイダーの設定では、TerraformがHTTPエンドポイントとやり取りすることを宣言しておりTerraformの構成内でHTTPリクエストを送信ンしたりレスポンスを処理するために入れています。
設定ブロック内ではリクエストヘッダーやタイムアウトなどの設定のオプションを入れることが出来ますが、空にしており、デフォルトで動作するようにしています。
・terraform.tfvars (変数の値)
#変数一覧
region = "ap-northeast-1"
name = "hogehoge"
environment = "prd"
ここではそれぞれに参照している変数の値を参照しています。
・variables.tf (変数の定義)
#リージョン
variable "region" {
type = string
description = "メインリージョン"
default = "ap-northeast-1"
}
#名前
variable "name" {
type = string
description = "自分の名前"
default = "hoge"
}
#環境名
variable "environment" {
type = string
description = "環境名"
default = "dev"
}
・vpc.tf (VPCとネットワーク関連リソースの定義)
resource "aws_vpc" "my-vpc" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "${var.name}-vpc"
}
}
resource "aws_subnet" "my_subnet_1a" {
vpc_id = aws_vpc.my-vpc.id
cidr_block = "10.0.0.0/24"
availability_zone = "${var.region}a"
map_public_ip_on_launch = true
tags = {
Name = "${var.name}-public-1a"
}
}
resource "aws_subnet" "my_subnet_1c" {
vpc_id = aws_vpc.my-vpc.id
cidr_block = "10.0.1.0/24"
availability_zone = "${var.region}c"
map_public_ip_on_launch = true
tags = {
Name = "${var.name}-public-1c"
}
}
resource "aws_internet_gateway" "my_igw" {
vpc_id = aws_vpc.my-vpc.id
tags = {
Name = "${var.name}-igw"
}
}
resource "aws_route_table" "my_public_rt" {
vpc_id = aws_vpc.my-vpc.id
tags = {
Name = "${var.name}-public-rt"
}
}
resource "aws_route" "my_route" {
route_table_id = aws_route_table.my_public_rt.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.my_igw.id
}
resource "aws_route_table_association" "my_route_public_1a" {
subnet_id = aws_subnet.my_subnet_1a.id
route_table_id = aws_route_table.my_public_rt.id
}
resource "aws_route_table_association" "my_route_public_1c" {
subnet_id = aws_subnet.my_subnet_1c.id
route_table_id = aws_route_table.my_public_rt.id
}
ここではVPCとサブネット、igw(インターネットゲートウェイ),rt(ルートテーブル)の設定をしています。
resource "aws_vpc" "my-vpc" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "${var.name}-vpc"
}
}
resource "aws_subnet" "my_subnet_1a" {
vpc_id = aws_vpc.my-vpc.id
cidr_block = "10.0.0.0/24"
availability_zone = "${var.region}a"
map_public_ip_on_launch = true
tags = {
Name = "${var.name}-public-1a"
}
}
resource "aws_subnet" "my_subnet_1c" {
vpc_id = aws_vpc.my-vpc.id
cidr_block = "10.0.1.0/24"
availability_zone = "${var.region}c"
map_public_ip_on_launch = true
tags = {
Name = "${var.name}-public-1c"
}
}
VPCは 10.0.0/16 にしていて
サブネットは1a(10.0.0.0/24) と1c(10.0.0.1/24)で作成しています。
resource "aws_internet_gateway" "my_igw" {
vpc_id = aws_vpc.my-vpc.id
tags = {
Name = "${var.name}-igw"
}
}
resource "aws_route_table" "my_public_rt" {
vpc_id = aws_vpc.my-vpc.id
tags = {
Name = "${var.name}-public-rt"
}
}
こちらはigwとrtを作成しています。
resource "aws_route" "my_route" {
route_table_id = aws_route_table.my_public_rt.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.my_igw.id
}
resource "aws_route_table_association" "my_route_public_1a" {
subnet_id = aws_subnet.my_subnet_1a.id
route_table_id = aws_route_table.my_public_rt.id
}
resource "aws_route_table_association" "my_route_public_1c" {
subnet_id = aws_subnet.my_subnet_1c.id
route_table_id = aws_route_table.my_public_rt.id
}
このコードは、AWSのVPC(仮想プライベートクラウド)におけるインターネットへの経路設定を行っています。
パブリックルートテーブル (my_public_rt) に対して、「すべての送信先 (0.0.0.0/0) へのトラフィックをインターネットゲートウェイ (igw) に送る」というルールを追加しています。
またサブネットの my_subnet_1a と my_subnet_1c をパブリックルートテーブル (rt) に関連付け、このサブネット内のリソースがインターネットにアクセスできるようにしています。
ということでお疲れ様でした!
前に解説した内容とほぼ同様でしたがもしご興味があれば参考にしていただければ幸いです!
ではでは~