1. はじめに
目的
このハンズオン記事では、Terraformを使用してAWS上に2層構造のWebアプリケーション環境を構築する手順を学びます。具体的には、VPC、パブリックサブネット、インターネットゲートウェイ、セキュリティグループ、EC2インスタンスをセットアップし、インターネット経由でアクセス可能なWebサーバーを作成します。
このハンズオンが役立つ場面
このハンズオンは、AWS上でインフラストラクチャをコードとして管理する方法を学びたい初心者に最適です。以下のような場面で役立ちます。
-
開発環境の構築
自動化された方法で簡単に再現可能な開発環境を構築する方法を学びます。
-
インフラストラクチャの管理
Terraformを使用して、コードとしてインフラストラクチャを管理する手法を学びます。
このハンズオンが役立つ場面
このハンズオンは、実際の開発現場において、次のような場面で特に役立ちます。
-
リモートワーク環境の構築 開発者がどこからでもAWSリソースに安全にアクセスできるようにするための設定方法を学びます。
-
セキュリティ強化 公開されているリソースへの直接アクセスを避け、踏み台サーバーを介したアクセス方法を実装することでセキュリティを向上させます。
-
インフラストラクチャの自動化 Terraformを使用することで、インフラストラクチャの設定をコードとして管理し、再現性を持たせることができます。
-
チーム環境の統一 同じ設定を全てのチームメンバーが使用することで、環境の一貫性と設定ミスを減少させます。
前提条件
このハンズオンを始める前に、以下の準備が必要です。
-
AWSアカウント AWSのサービスを使用するために必要です。まだ持っていない場合は、こちらから作成してください。
-
基本的なAWSの知識 EC2、VPC、IAMの基本概念についての理解が必要です。
-
基本的なTerraformの知識 Terraformの基本的な操作(リソース定義、変数の使用、計画と適用など)を理解していることが望ましいです。こちらの記事を参考にして下さい。
このハンズオンを通じて、AWS環境への安全なリモートアクセスの設定方法を実践的に学びましょう。それでは、始めていきます。
2. 準備
必要なツール
以下のツールをインストールしておいてください。
-
Terraform インフラストラクチャをコードとして管理するためのツールです。インストール方法は公式サイトのガイドに従ってください。
-
AWS CLI AWSのサービスをコマンドラインから操作するためのツールです。公式サイトからインストールパッケージをダウンロードし、インストールガイドに従ってください。
設定
このセクションでは、AWSアカウントの設定とAWS CLIの設定方法について説明します。準備としてローカルでキーペアも作成します。
-
AWSアカウントの作成
まだAWSアカウントを持っていない場合は、こちらから作成してください。アカウント作成にはクレジットカード情報が必要です。
-
AWS CLIのインストール
AWS CLIは、AWSのリソースを管理するためのコマンドラインツールです。以下のコマンドを使用してインストールします:
-
Windows MSIインストーラーを使用してインストールします。公式サイトからダウンロードできます。
-
macOS
brew install awscli
-
Linux
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" unzip awscliv2.zip sudo ./aws/install
-
-
AWS CLIの設定
AWS CLIをインストールしたら、以下のコマンドを実行して設定を行います。
aws configure
プロンプトに従い、以下の情報を入力します。
- AWSアクセスキー: AWS Management Consoleの「IAM」セクションから取得できます。
- AWSシークレットアクセスキー: 同上。
- デフォルトのリージョン: 利用するリージョン(例: ap-northeast-1)。
- 出力形式:
json
(デフォルトの出力形式)。
AWS Access Key ID [None]: <your-access-key-id> AWS Secret Access Key [None]: <your-secret-access-key> Default region name [None]: ap-northeast-1 Default output format [None]: json
-
Terraformの設定
Terraformの設定ファイル(
.tf
ファイル)を作成するためのプロジェクトディレクトリを準備します。以下のコマンドでディレクトリを作成します。mkdir aws-2tier-web-infra cd aws-2tier-web-infra
このディレクトリ内に、後のステップで使用するTerraform設定ファイルを配置します。
キーペアの作成
ここで、ローカルでSSHキーペアを作成します。これにより、踏み台サーバーにアクセスするためのキーが作成されます。
-
ターミナル(またはコマンドプロンプト)を開き、以下のコマンドを実行してキーペアを生成します。
ssh-keygen -t rsa -b 2048 -f my-key
my-key
という名前でプライベートキー(my-key
)とパブリックキー(my-key.pub
)が生成されます。 -
生成されたパブリックキー(
my-key.pub
)をプロジェクトディレクトリにコピーします。cp ~/.ssh/my-key.pub /path/to/project/
3. リソースの作成
ディレクトリ構成
プロジェクトディレクトリの配下に、以下のファイルを用意します。
aws-2tier-web-infra/
│
├── main.tf
├── variables.tf
├── terraform.tfvars
├── vpc.tf
├── bastion.tf
├── private_server.tf
└── alb.tf
main.tf
provider "aws" {
region = "ap-northeast-1"
}
# 最新のAmazon Linux 2023 AMIを取得
data "aws_ssm_parameter" "latest_amazon_linux_2023" {
name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64" # x86_64
}
variables.tf
variable "vpc_cidr" {
description = "VPC CIDRブロック"
default = "10.0.0.0/16"
}
variable "public_subnet_cidrs" {
description = "パブリックサブネットCIDRブロックのリスト"
type = map(string)
default = {
subnet1 = "10.0.1.0/24"
subnet2 = "10.0.3.0/24"
}
}
variable "private_subnet_cidrs" {
description = "プライベートサブネットのCIDRブロックのリスト"
type = map(string)
default = {
subnet1 = "10.0.2.0/24"
subnet2 = "10.0.4.0/24"
}
}
variable "allowed_ssh_cidr" {
description = "SSHアクセスを許可するCIDRブロック"
default = "0.0.0.0/0"
}
variable "instance_type" {
description = "EC2インスタンスタイプ"
default = "t2.micro"
}
variable "key_name" {
description = "SSHキーペアの名前"
}
terraform.tfvars
vpc_cidr = "10.0.0.0/16"
public_subnet_cidrs = {
public1 = "10.0.1.0/24"
public2 = "10.0.3.0/24"
}
private_subnet_cidrs = {
private1 = "10.0.2.0/24"
private2 = "10.0.4.0/24"
}
allowed_ssh_cidr = "0.0.0.0/0"
instance_type = "t2.micro"
key_name = "my-key" # ここにSSHキーペアの名前を入力
vpc.tf
# VPCの作成
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "main-vpc"
}
}
# インターネットゲートウェイの作成
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "main-igw"
}
}
# パブリックサブネットの作成
resource "aws_subnet" "public" {
for_each = var.public_subnet_cidrs
vpc_id = aws_vpc.main.id
cidr_block = each.value
map_public_ip_on_launch = true
availability_zone = element(["ap-northeast-1a", "ap-northeast-1c"], index(keys(var.public_subnet_cidrs), each.key))
tags = {
Name = "public-subnet-${each.key}"
}
}
# プライベートサブネットの作成
resource "aws_subnet" "private" {
for_each = var.private_subnet_cidrs
vpc_id = aws_vpc.main.id
cidr_block = each.value
availability_zone = element(["ap-northeast-1a", "ap-northeast-1c"], index(keys(var.private_subnet_cidrs), each.key))
tags = {
Name = "private-subnet-${each.key}"
}
}
# パブリックルートテーブルの作成
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "public-route-table"
}
}
# パブリックサブネットとルートテーブルの関連付け
resource "aws_route_table_association" "public" {
for_each = aws_subnet.public
subnet_id = each.value.id
route_table_id = aws_route_table.public.id
}
# NAT Gateway用のEIPの作成
resource "aws_eip" "nat" {
domain = "vpc"
}
# NAT Gatewayの作成
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public["public1"].id
tags = {
Name = "main-nat-gateway"
}
}
# プライベートルートテーブルの作成
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}
tags = {
Name = "private-route-table"
}
}
# プライベートサブネットとルートテーブルの関連付け
resource "aws_route_table_association" "private" {
for_each = aws_subnet.private
subnet_id = each.value.id
route_table_id = aws_route_table.private.id
}
# セキュリティグループの作成
resource "aws_security_group" "bastion_sg" {
vpc_id = aws_vpc.main.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [var.allowed_ssh_cidr]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "bastion-sg"
}
}
resource "aws_security_group" "private_sg" {
vpc_id = aws_vpc.main.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [aws_security_group.bastion_sg.id]
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["10.0.0.0/16"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "private-sg"
}
}
resource "aws_security_group" "alb_sg" {
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "alb-sg"
}
}
output "vpc_id" {
value = aws_vpc.main.id
}
output "public_subnet_ids" {
value = [for subnet in aws_subnet.public : subnet.id]
}
output "private_subnet_ids" {
value = [for subnet in aws_subnet.private : subnet.id]
}
output "bastion_sg_id" {
value = aws_security_group.bastion_sg.id
}
output "private_sg_id" {
value = aws_security_group.private_sg.id
}
output "alb_sg_id" {
value = aws_security_group.alb_sg.id
}
bastion.tf
resource "aws_key_pair" "generated_key" {
key_name = var.key_name
public_key = file("${path.module}/${var.key_name}.pub")
}
resource "aws_instance" "bastion" {
ami = data.aws_ssm_parameter.latest_amazon_linux_2023.value
instance_type = var.instance_type
subnet_id = aws_subnet.public["public1"].id
vpc_security_group_ids = [aws_security_group.bastion_sg.id]
associate_public_ip_address = true
key_name = aws_key_pair.generated_key.key_name
tags = {
Name = "BastionHost"
}
user_data = <<-EOF
#!/bin/bash
yum install -y amazon-ssm-agent
systemctl start amazon-ssm-agent
systemctl enable amazon-ssm-agent
EOF
}
output "bastion_instance_id" {
value = aws_instance.bastion.id
}
output "bastion_public_ip" {
value = aws_instance.bastion.public_ip
}
private_server.tf
resource "aws_instance" "private" {
ami = data.aws_ssm_parameter.latest_amazon_linux_2023.value
instance_type = var.instance_type
subnet_id = aws_subnet.private["private1"].id
vpc_security_group_ids = [aws_security_group.private_sg.id]
key_name = aws_key_pair.generated_key.key_name
tags = {
Name = "PrivateServer"
}
user_data = <<-EOF
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "Hello, World from Private Server" > /var/www/html/index.html
EOF
}
output "private_instance_id" {
value = aws_instance.private.id
}
output "private_instance_private_ip" {
value = aws_instance.private.private_ip
}
resource "aws_lb_target_group" "main" {
name = "main-target-group"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id
health_check {
path = "/"
port = "80"
}
tags = {
Name = "main-target-group"
}
}
resource "aws_lb_target_group_attachment" "main" {
target_group_arn = aws_lb_target_group.main.arn
target_id = aws_instance.private.id
port = 80
}
output "target_group_arn" {
value = aws_lb_target_group.main.arn
}
alb.tf
resource "aws_lb" "main" {
name = "main-lb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb_sg.id]
subnets = [aws_subnet.public["public1"].id, aws_subnet.public["public2"].id]
tags = {
Name = "main-lb"
}
}
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.main.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.main.arn
}
}
output "alb_dns_name" {
value = aws_lb.main.dns_name
}
4. デプロイ手順
初期化とプラン作成
まず、プロジェクトディレクトリに移動し、Terraformを初期化します。これにより、Terraformが使用するプロバイダーのプラグインがダウンロードされます。
cd aws-2tier-web-infra
terraform init
次に、Terraformプランを作成し、どのリソースが作成されるかを確認します。
terraform plan -var-file="terraform.tfvars"
# 変数ファイルがterraform.tfvarsという名称の場合、-var-fileは省略可能
terraform plan
計画を確認して問題がないか確認します。
リソースのデプロイ
Terraformプランを適用し、リソースをデプロイします。
terraform apply -var-file="terraform.tfvars"
# 変数ファイルがterraform.tfvarsという名称の場合、-var-fileは省略可能
terraform apply
プロンプトが表示されたら、yes
と入力してリソースの作成を承認します。
タイミングにより下記エラーがでた場合は、再度デプロイして下さい。
Error: creating EC2 Subnet: InvalidSubnet.Conflict
5. Webサーバーの確認
デプロイが完了したら、ALBのDNS名を使用してWebサーバーにアクセスできます。以下のコマンドでALBのDNS名を取得します。
terraform output alb_dns_name
ブラウザで取得したDNS名にアクセスし、"Hello, World from Private Server"と表示されることを確認します。
6. Bastion Hostを使用したWebサーバーの更新
SSHで踏み台サーバーに接続
まず、AWS Management Consoleから作成したSSHキーペアをダウンロードします。このキーペアは踏み台サーバーとプライベートサーバーの両方で使用します。
次に、以下のコマンドでローカルマシンからBastion Hostに接続します。
ssh -i <path-to-key-pair.pem> ec2-user@<bastion-public-ip>
キーペアを踏み台サーバーにアップロード
踏み台サーバーに接続後、プライベートサーバーにアクセスするために、キーペアを踏み台サーバーにアップロードします。ローカルマシンから踏み台サーバーにキーペアをコピーします。
scp -i <path-to-key-pair.pem> <path-to-key-pair.pem> ec2-user@<bastion-public-ip>:/home/ec2-user/
プライベートサーバーに接続
踏み台サーバーに接続したら、次にプライベートサーバーにSSHで接続します。
ssh -i <path-to-key-pair.pem> ec2-user@<private-instance-private-ip>
Webサーバーの更新
プライベートサーバーに接続したら、以下のコマンドでWebサーバーの内容を更新します。
echo "Updated Content from Private Server" | sudo tee /var/www/html/index.html
ブラウザでALBのDNS名にアクセスし、更新された内容が表示されることを確認します。
7. まとめと後片付け
まとめ
このハンズオンでは、Terraformを使用してAWS環境にセキュアなリモートアクセスを設定する方法を学びました。具体的には、VPC、パブリックサブネット、プライベートサブネット、Bastion Host、およびロードバランサーを作成し、Bastion Hostを経由してプライベートサーバーにアクセスする手順を実践しました。この方法は、開発環境や本番環境でのセキュアなアクセスに非常に役立ちます。
リソースのクリーンアップ
作成したリソースを削除するには、以下のコマンドを実行します。
terraform destroy
プロンプトが表示されたら、yes
と入力してリソースの削除を承認します。
参考資料
【番外編】USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話
プログラミング塾に半年通えば、一人前になれると思っているあなた。それ、勘違いですよ。「なぜ間違いなの?」「正しい勉強法とは何なの?」ITを学び始める全ての人に知って欲しい。そう思って書きました。是非読んでみてください。
「フリーランスエンジニア」
近年やっと世間に浸透した言葉だ。ひと昔まえ、終身雇用は当たり前で、大企業に就職することは一種のステータスだった。しかし、そんな時代も終わり「優秀な人材は転職する」ことが当たり前の時代となる。フリーランスエンジニアに高価値が付く現在、ネットを見ると「未経験でも年収400万以上」などと書いてある。これに釣られて、多くの人がフリーランスになろうとITの世界に入ってきている。私もその中の1人だ。数年前、USBも知らない状態からITの世界に没入し、そこから約2年間、毎日勉学を行なった。他人の何十倍も努力した。そして、企業研修やIT塾で数多くの受講生の指導経験も得た。そこで私は、伸びるエンジニアとそうでないエンジニアをたくさん見てきた。そして、稼げるエンジニア、稼げないエンジニアを見てきた。
「成功する人とそうでない人の違いは何か?」
私が出した答えは、「量産型エンジニアか否か」である。今のエンジニア市場には、量産型エンジニアが溢れている!!ここでの量産型エンジニアの定義は以下の通りである。
比較的簡単に学習可能なWebフレームワーク(WordPress, Rails)やPython等の知識はあるが、ITの基本概念を理解していないため、単調な作業しかこなすことができないエンジニアのこと。
多くの人がフリーランスエンジニアを目指す時代に中途半端な知識や技術力でこの世界に飛び込むと返って過酷な労働条件で働くことになる。そこで、エンジニアを目指すあなたがどう学習していくべきかを私の経験を交えて書こうと思った。続きはこちらから、、、、
エンベーダー編集部
エンベーダーは、ITスクールRareTECHのインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。
関連記事
2020.02.25
完全未経験からエンジニアを目指す爆速勉強法
USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話
- キャリア・学習法
- エンジニア
2024.08.30
【AWS ハンズオン】AWS CloudTrailの基礎を学んでみよう
この記事では、AWS CloudTrailの基礎知識を初心者向けに解説します。AWSを利用するインフラエンジニアにとって、CloudTrailの理解は必要不可欠な知識ですので一緒に基本をしっかりと押さえていきましょう。
- AWS
- ハンズオン
2024.06.17
【Terraformハンズオン】AWS CloudFront&ALB構成を実現しよう
この記事では、CloudFrontとは何かを解説し、IaC(Infrastructure as Code)を実現するためのツールであるTerraformを使ってCloudFrontを構築するまでのハンズオンを行います。
- AWS
- ハンズオン
- Terraform
2024.03.31
【超入門】Reactの基礎を30分で学べる!インストールから基本コンポーネントまで
この記事は、フロントエンドエンジニアを目指したいけど何をすればいいかわからないといった人に、とりあえずReactという技術を用いて雰囲気を知ってもらうために作成しました。
- エンジニア
- フロントエンド
- ハンズオン
- React
- JavaScript