Terraformなんもわからん
Terraformがなんもわかりません。理解するためにおててを動かしていきます。
目標は、terraform経由でAWSのサービスを利用し、最終的にはデータ基盤を自作することです。
Terraformとは?
クラウド上のリソースをコードベースで構築できるもので、コードをもとに環境構築を可能にするもの。
これにより、いろいろと便利になる。
らしいです。研修で教わりました!
gpt4oに聞いてみた
🌱 Terraformの特徴
・Infrastructure as Code(IaC):
サーバー、ネットワーク、DBなどのインフラ構成を、コード(通常は.tfファイル)で定義できます。・クラウドを横断して使える:
AWS、Azure、Google Cloud など、複数のクラウドサービスに対応。・宣言的な構文:
「こういう状態にしたい」と宣言するだけで、Terraformが足りない部分だけを自動で構築・変更してくれます。・プラン機能:
実行前に「何がどう変わるのか」を事前に確認できるので、安全にインフラを更新できます。
だそうです。色々と便利ですね!
Terraform、何が分からないの?
シンプルに言語に慣れてなくてよくわかりません。
なので、慣れる意味でまず色々とみていきます。
まずはいつもの通りサンプルを生成させます。
AWS VPCコード
まずはvpcを生成するコードをこんな感じで生み出してもらいました。
# awsを使うための基本的な設定
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# Configure the AWS Provider
# 使うAWSのリージョンとプロファイルの設定
provider "aws" {
region = "ap-northeast-1"
profile = "default"
}
# Create a VPC
# 実際のvpcを作るための部分
resource "aws_vpc" "sample_vpc" {
cidr_block = "10.0.0.0/16" # IPアドレスを、10.0.0.0 ~ 10.0.255.255の中から適当に決める 10.0.0.0はプライベート範囲
tags = {
Name = "sample_vpc"
Environment = "development"
Project = "sample_project"
}
}
こんな感じで、基本的にはresorceとresorce名、そして作成時の名前を入れて、そのあとは各種設定を無限に入れる、という感じで成り立っていました。
なるほどね。言語としては、まあ見ればわかる感じになっているので、とても分かりやすい。
VPC作成を複数ファイルに分割して頑張ろう
先ほどのコードをgpt4oに投げて、改善案を出してもらったところ、いろいろと分割してきたので、それぞれ見ていきます。
resource "aws_vpc" "dev_sample_vpc" {
cidr_block = var.dev_vpc_cidr
enable_dns_support = true
enable_dns_hostnames = true
# DNS解決を使えて便利・・・?
tags = {
Name = var.dev_vpc_name # 変数の受け取りは別ファイルでも同階層ならこれで受けられるらしい
Environment = "development"
Project = "sample_project"
}
}
# 変数をファイルとして分離することで、後々楽になる
variable "dev_vpc_cidr" {
default = "10.0.0.0/16"
}
variable "dev_vpc_name" {
default = "sample_vpc"
}
# AWSを使うための初期設定部分
provider "aws" {
region = "ap-northeast-1"
profile = "default"
}
こんな感じで、機能ごとに分割することで、後々管理しやすくなるらしいです。楽だね!
本格的?にいろいろ作る
ファイルを見る最後として、awsのec2(サーバー、というか要するにパソコン本体)を立てて、そこに安全につなげるような構成を依頼しました。
具体的には、VPC内部にEC2入りのプライベートサブネット、natゲートウェイを置くパブリックサブネットを立てて、そこに安全にアクセスできるよう、インターネットゲートウェイとnatゲートウェイを作る、という感じにしています。
いやサブネットって何?????
聞いてみました!
🔷 VPC(Virtual Private Cloud)とは?
AWS上の仮想的なネットワーク空間。
企業やアプリケーションごとに独立したネットワークを持てる感じ。
例えば:
IPレンジ(CIDR)を自分で決められる(例:10.0.0.0/16)
ルートテーブルやセキュリティグループを持つ
中にサブネットやインスタンス、NAT、IGWなどを作っていく🧠 イメージ:
VPCは「会社の敷地全体」みたいなもの。
その中に建物(サブネット)がある。
🔶 サブネット(Subnet)とは?
VPC内を分割した小さなネットワーク単位。
実際にEC2などのリソースが配置される場所。
サブネットごとに
パブリック or プライベートかを決められる(IGW/NATの有無で)利用するAZ(アベイラビリティゾーン)を指定できる
🧠 イメージ:
サブネットは「敷地内の建物」や「部屋」みたいなもの。
たとえば:
A棟(パブリックサブネット)→ インターネットと直結
B棟(プライベートサブネット)→ インターネットには直接出れない(NAT経由)
ということらしいです。VPCがネットワーク全体で、サブネットはその中に作る、実際にリソースを配置する場所のようです。
なるほどね
マンションとマンションの部屋、家具みたいな感じの関係性っぽい
さらに、サブネットにはプライベートとパブリックがあるけど、パブリックは外部からアクセス可能、プライベートは内部から外部へのアクセスのみ可能らしい。
🌐 パブリックサブネットとプライベートサブネットの違い
🔷 パブリックサブネット(Public Subnet)
- インターネットと直接通信ができるサブネット
- EC2に**グローバルIP(パブリックIP)**を付けると、外部からアクセス可能になる
- Internet Gateway(IGW)経由で通信できるルートがある
🧠 例:- Webサーバー(外からアクセスしたい)
- 踏み台サーバー(Bastion)
🔶 プライベートサブネット(Private Subnet)
- インターネットに直接アクセスできないサブネット
- 外部と通信するにはNAT Gatewayなどの中継が必要
- よって、外部から直接アクセスされにくくセキュア
🧠 例:
- アプリサーバーやDBサーバー(外から直接アクセスさせたくないもの)
- バックエンド処理系のEC2など
あとさらっとルートテーブルとかいうものを出してきた。
多分アクセスされたときにどこにつなぐかのテーブルっぽい。
パブリックなら、0.0.0.0/0をインターネットゲートウェイに、プライベートなら0.0.0.0/0をNATゲートウェイに投げるらしい。
0.0.0.0/0って何? → すべてのIPアドレス、を意味するらしく、マッチするものがない場合にこれになるらしい。VPC内部で見つからなかったら外部に出す、という感じらしい。へー
インターネットゲートウェイもNATゲートウェイも、これを見る限りただ接続元と接続先のIPアドレスに応じて振り分けてるだけっぽい。
[プライベートEC2] → 0.0.0.0/0 → NAT GW → IGW → インターネット(出れる)
[パブリックEC2] ← 0.0.0.0/0 ← IGW ← インターネット(入れる)
って感じ。ほーん。
何となくわかったような気もする。実際のファイルを見る
本格的に作る(ファイル観察編)
gpt4oに作らせたので、それをまとめてみていく。
まずはprovider設定。さっきと同じ
provider "aws" {
region = "ap-northeast-1"
}
次にVPCとインターネットゲートウェイの設定
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "main-vpc"
}
}
# インターネットゲートウェイ(プライベートサブネットと外部をつなぐ扉)
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
tags = {
Name = "main-igw"
}
}
次にサブネットの設定
# publicサブネットの作成
# サブネット自体は共通で、外部からアクセス可能にするかどうかの違い
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
map_public_ip_on_launch = true
availability_zone = "ap-northeast-1a"
tags = {
Name = "public-subnet"
}
}
# プライベートサブネットの作成
# サブネット自体は共通で、外部アクセスを設定していない
resource "aws_subnet" "private" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
availability_zone = "ap-northeast-1a"
tags = {
Name = "private-subnet"
}
}
次に、natゲートウェイ(プライベートサブネットへの通信を制限するためのゲート)
resource "aws_eip" "nat_eip" {
domain = "vpc"
}
resource "aws_nat_gateway" "nat" {
allocation_id = aws_eip.nat_eip.id
subnet_id = aws_subnet.public.id # 外部アクセス可能にするためにpublicサブネットにおく
tags = {
Name = "nat-gateway"
}
depends_on = [aws_internet_gateway.igw]
}
次に、ルートテーブルの設定 ルートテーブル自体の設定と、サブネットに紐づける設定の二つが必要
# まず、インターネットゲートウェイのルートテーブル設定
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
tags = {
Name = "public-rt"
}
}
# パブリックサブネットに対して、どのルートテーブルを使うかを紐づけ
resource "aws_route_table_association" "public_assoc" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
# プライベートサブネットのルートテーブル設定
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.nat.id
}
tags = {
Name = "private-rt"
}
}
# プライベートサブネットに対して、使うルートテーブルを設定
resource "aws_route_table_association" "private_assoc" {
subnet_id = aws_subnet.private.id
route_table_id = aws_route_table.private.id
}
次に、セキュリティグループの設定
いやそもそも何のこと?聞いてみました!
AWSのファイアウォール的な機能で、インスタンス(EC2など)の通信を制御するルールのかたまり。
- インバウンド(受信):外から入ってくる通信を許可
- アウトバウンド(送信):インスタンスから外へ出ていく通信を許可
📌 ポイント:
- 許可するルールだけを書く(denyは書かない)
- デフォルトではすべて拒否されてる
- インスタンスにアタッチする
ここでいじることで、特定サーバーからだけアクセスできるようにしたり、逆にすべてからアクセスできるようにしたりできるらしい。
基本は全部拒否らしい。なる
resource "aws_security_group" "ec2_sg" {
name = "ec2-sg"
description = "Allow SSH and HTTP"
vpc_id = aws_vpc.main.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["YOUR_IP/32"] # 自分のIPアドレスに置き換えてください
}
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 = "ec2-sg"
}
}
最後に、ec2の設定
resource "aws_instance" "web" {
ami = "ami-0f9c61b5a562a16af" # Amazon Linux 2のAMI ID(東京リージョン)
instance_type = "t2.micro"
subnet_id = aws_subnet.private.id
vpc_security_group_ids = [aws_security_group.ec2_sg.id]
associate_public_ip_address = false
tags = {
Name = "web-server"
}
}
こんな感じらしいです。
いやー量が多い!!!!!でも構成としては割とこれでシンプルっぽいです。アクセス方法もSSHなのでわかりやすいかなと思います。
こっからは実際に一部コードを実行していきます。
(今回、初期設定にめちゃくちゃ苦労したので軽めです。)
おててを使って遊ぶ(はんずおん!)
おててを動かします
まずはterraformとAWS CLIの入った環境を作る!
今回は、terraformを使いたいわけですが、パソコンがwindowsだとWSL2経由となっていろいろとめんどくさいようです。
ですので、コンテナ環境にterraformとAWS CLIを入れていこうと思います。
まず、ファイル構成はこんな感じ
DOCKER_TEST4
│ .env # AWSのパスとか入れるとこ
│ docker-compose.yml
└─src
dockerfile
ファイルごとにそれぞれ見ていきます。
まずは.env
AWS_ACCESS_KEY_ID=secret1
AWS_SECRET_ACCESS_KEY=secret2
DEFAULT_REGION_NAME=ap-northeast-1
DEFAULT_OUTPUT_FORMAT=json
ここに、ローカルにはおきたいけどリポジトリとかには乗せたくない環境変数を入れてます。
次に、dockerfile
# ubuntu ベースでtfenvを使ってterraformをインストールする
# tfenvはterraformのバージョン管理ツール
FROM ubuntu:latest
# install curl and unzip
RUN apt-get update && apt-get install -y curl unzip
RUN apt-get install -y git
RUN apt-get install -y make
RUN apt-get install -y jq
# tfenvのインストールとterraformのinstall
RUN git clone --depth=1 https://github.com/tfutils/tfenv.git ~/.tfenv
RUN echo 'export PATH="$HOME/.tfenv/bin:$PATH"' >> ~/.bash_profile
RUN /bin/bash -c "source ~/.bash_profile" \
&& /bin/bash -c "~/.tfenv/bin/tfenv install latest" \
&& /bin/bash -c "~/.tfenv/bin/tfenv use latest"
# AWS CLIのinstall
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
RUN unzip -u awscliv2.zip
RUN ./aws/install
RUN rm -rf awscliv2.zip
# コンテナ起動時に、pathを通すためのもの
ENTRYPOINT ["/bin/bash", "-c" , "source ~/.bash_profile && /bin/bash"]
最後に、docker-compose.yml
# version: "3.8"
services:
terraform:# コンテナ起動時の名前?
build: ./src
container_name: terraform_AWS
env_file: # 環境変数の読み込み(build時のみ)
- ./.env
volumes: # バインドマウント
- ./src:/AWS
working_dir: /AWS
command: ["/bin/bash", "-c" , "source ~/.bash_profile && /bin/bash"]
# 初回起動時のpath通し
これで、docker-compose.ymlのあるディレクトリで起動することで、terraformの入ったコンテナを起動できる
docker-compose build
docker-compose run terraform
>>> root@user_id:/AWS#
この状態では、きちんとterraformが導入されていることが分かる。
root@user_id:/AWS# terraform -v
Terraform v1.11.4
on linux_amd64
次に、AWS CLIを使える状態にする!
次に、AWS CLIをちゃんと使える状態にするように作業していきます。
今回、私のアカウントは二段階認証がオンになっている関係上、パスワードとIDだけではうまく動いてくれません。
ですので、一時的なセッションキーを用いる必要があります。
設定方法については、こちらに記載がありましたが、awsコンソール上の、自分のアカウント明→セキュリティ認証情報→多要素認証(MFA)→識別子の部分のarn:aws:iam::~を使います。
適当にやる場合は、以下のコマンドをまず打ちます
aws configure
すると、アクセスキー、シークレットアクセスキー、リージョン、出力形式について聞かれるので、適当に入力してEnterを押します。
すると、~/.awsにconfigとcredentialというファイルが生成されます。
これらには、プロファイルごとの情報が記録されるようになっていますので、無理やりvimなどで編集することで、AWSへの接続が可能になります。
うまくいけば、以下のコマンドがエラーなく走るはずです
aws s3 ls --profile <決めたユーザー名>
しかしながら、エンジニアとしては可能な限り自動化したいわけです。
なので、必要情報(先述したMFAの認証情報(arn:aws:iam::~))を.envに突っ込んだうえで、MFAのコードだけ入れたらアクセスできるようにしました。
まず、ファイル構成は以下の通りです。
C:.
│ .dockerignore # .env, dockerfileを登録
│ .env # IAMのMFA認証情報を追加
│ docker-compose.yml
│
└─src
│ app.py
│ aws_settings.sh # セッションキー作成の自動化スクリプト
│ dockerfile
│ streamlit_app.py
│ test.ipynb
│
├─environment
└─module
次に、aws_settings.shです。以下の通りです。
#!/bin/bash
# 必要な環境変数を確認
if [[ -z "$AWS_ACCESS_KEY_ID" || -z "$AWS_SECRET_ACCESS_KEY" || -z "$AWS_IAM" ]]; then
echo "環境変数 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_MFA_ARN を設定してください。"
exit 1
fi
# アクセス用環境変数の設定
# .envファイルにあるものを念のため追加
export AWS_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID"
export AWS_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY"
export AWS_IAM="$AWS_IAM"
export AWS_DEFAULT_REGION="$DEFAULT_REGION_NAME"
# ワンタイムパスワードを入力
read -p "MFAコードを入力してください: " MFA_CODE
# MFAコードの入力確認
if [[ -z "$MFA_CODE" ]]; then
echo "MFAコードが入力されていません。"
exit 1
fi
# AWSセッションキーを取得
SESSION_JSON=$(aws sts get-session-token \
--serial-number "$AWS_IAM" \
--token-code "$MFA_CODE" \
--output json --region ap-northeast-1 \
--duration-seconds 129600)
if [[ $? -ne 0 ]]; then
echo "AWSセッションキーの取得に失敗しました。MFAコードを確認してください。"
exit 1
fi
# セッションキーを抽出
AWS_ACCESS_KEY_ID=$(echo "$SESSION_JSON" | jq -r '.Credentials.AccessKeyId')
AWS_SECRET_ACCESS_KEY=$(echo "$SESSION_JSON" | jq -r '.Credentials.SecretAccessKey')
AWS_SESSION_TOKEN=$(echo "$SESSION_JSON" | jq -r '.Credentials.SessionToken')
# ~/.aws/credentials に登録
mkdir -p ~/.aws
cat > ~/.aws/credentials <<EOL
[default]
aws_access_key_id = $AWS_ACCESS_KEY_ID
aws_secret_access_key = $AWS_SECRET_ACCESS_KEY
aws_session_token = $AWS_SESSION_TOKEN
EOL
echo "AWSセッションキーを ~/.aws/credentials に登録しました。"
# ~/.aws/config にリージョンを登録
cat > ~/.aws/config <<EOL
[default]
region = ap-northeast-1
output = json
EOL
echo "AWSリージョンを ~/.aws/config に登録しました。"
これによって、aws cliが一時セッションの形式で使えるようになる。
使用する際は、profileとしてdefaultを明示する。
例えばこんな感じ
bash aws_settings.sh
# MFAコード入力
aws s3 ls --profile default
これによって、アクセスができるようになった。公式資料はこんな感じ。
これで、コンテナ上でterraformとaws cliが使えるようになりました。
まずはVPCを立ててみようの階
まずは、簡単にVPCだけ立ててみようと思います。
ec2もサブネットもない、本当にVPCだけです。
ファイルとしてはこんな感じ
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
} # awsを使うための初期設定
}
}
# Configure the AWS Provider
# まずはawsの基本設定を追加
# ここではregionとprofileを設定
provider "aws" {
region = "ap-northeast-1"
profile = "default"
}
# Create a VPC
# resouce リソースの種類 そのリソースの名前
resource "aws_vpc" "sample_vpc" {
cidr_block = "10.0.0.0/16"
# IPv4でのIPの指定の仕方
# ほぼ16bit分の機器、2**16乗個の機器をつなげる
tags = {
Name = "sample_vpc"
Environment = "development"
Project = "sample_project"
} # aws console上でのtag設定
}
この状態で、terraformを使って起動から削除まで行えます。
cd ~/your_workspace # main.tfのおいてある階層に移動
terraform init # 初期化
terraform validate # コード自体の検証を実施
terraform apply # 実際にリソースを起動できる
terraform destroy # 今回のterraform経由で作成したリソースをまとめて爆破
以上!終わり!VPCを立てて爆破しました。
リソースの立て方自体はこれですべてです。
まとめ
terraformの言語体系とか、実際に構成を作るときのコードを見ながら、クラウド用語を把握しつつ、最後に簡単な設定で実際に起動してみました。
terraformの利点として、リソースを一括で管理できるので、インスタンスの消し忘れからの料金爆上げの未来を避けることが出来ます。(最後はdestroyするだけ)
AWS CLIの初期設定で苦戦した関係で、今回はほとんどハンズオン出来てないので、もうちょっとここら辺を自分で深堀していけたらと思います。