🦏

もっともミニマルな構成でTerraformとAWS CLIによるIaC的なデプロイを体感してみる

に公開

はじめに

個人開発でつくったWebアプリをAWSにデプロイしようとした際、『そもそもAWSにデプロイするのが初めて』という状態で「せっかくだからTerraformなるものでIaCに触れてみよう」、「せっかくだからGitHubActionsなるものでCI/CDを構築してみよう」と欲張った結果、デプロイできるまでに学ぶべき要素技術が増えてつらくなってしまう、という経験がありました。

未来の自分や、自分のように「はじめてのデプロイ」に直面した若手の開発者が同じ轍を踏むことが減るように、アプリのソースコード自体やCI/CDのことはいったん抜きにして、「そもそもTerraformとAWSを使ってコードをデプロイする」ということを最小構成で体感してみることにしました。

めざすゴール

TerraformとAWS CLIによって『インフラ構築〜デプロイまでをコードで管理する』ことを実際に手を動かして体感する。

具体的に何をやるか

デプロイするソースコードをhtmlファイル1つだけにして、それをS3にデプロイし、削除するまでをTerraformとAWS CLIを使って行います。

S3というのはAWSが提供するオブジェクトストレージのサービスで、静的なファイルを格納したり、そのまま静的Webサイトのホスティングに使えたりするサービスです。

前提とすること

  • AWSへのユーザー登録が済んでいること
  • AWS CLIのインストールが済んでいること
  • AWS CLIへの鍵の登録(aws configure)が済んでいること
  • terraformのインストールが済んでいること

前提としないこと

  • デプロイ対象のソースコードが手元にある
  • CI/CDについて知見がある
  • GitHubにリポジトリをもっている

注意事項

手順①:デプロイ対象のhtmlを用意する

プロジェクトのディレクトリを適当につくったのち、その直下にsrcディレクトリをつくり、以下のようなhtmlファイルを作成します。

index.html
<div>Hello Terraform</div>

手順②:main.tfファイルを用意する

プロジェクトのディレクトリ直下にmain.tfファイルを下記の内容で作成します。

main.tfファイルとはTerraformでリソース定義をするためのファイルです。厳密には変数を別ファイルに切り出したりと細かいプラクティスがありますが、今回はmain.tfですべてを完結させます。

main.tf
# プロバイダーの定義(今回はAWS)
provider "aws" {
  region = "ap-northeast-1" # 東京リージョン
}

# S3バケットの作成
resource "aws_s3_bucket" "example" {
  bucket = "my-minimal-terraform-bucket-hoge" # 世界でユニークになるようなIDを適当に決めてバケットIDに設定(例なので適宜変えてください)
}

手順③:Terraformを実行してS3のバケットを作成する

main.tfと同階層にいる状態で、以下のコマンドでTerraformを実行します。

terraform init
terraform apply

しばらく待つと以下のようなプロンプトが表示されるので、問題なければ「yes」と記入します。
applyコマンドの途中確認

以下のような表示がされればS3にバケットが作成できています。
applyコマンド後の表示

AWSのマネジメントコンソールを表示すると、実際にS3バケットが出来ていることが確認できます。

手順④:作成したS3のバケットにindex.htmlを同期する

AWS CLIを使って、以下のコマンドでindex.htmlをバケットに同期します。

# srcフォルダの中身を、バケットへ同期する
aws s3 sync ./src s3://my-minimal-terraform-bucket-hoge # ここは自分で作成したS3バケットのIDに変えてください。

コマンド実行後、以下のようなメッセージが表示されればS3にファイルを格納成功しています。
sync後の表示

マネジメントコンソール上でも、
以下のようにS3にファイルが出来ていることが確認できます。
syncコマンド後のマネジメントコンソールでの表示

手順⑤:S3のバケットを公開する

以下のようにmain.tfを書き換えます。

main.tf
provider "aws" {                                                        # AWSプロバイダの設定開始
  region = "ap-northeast-1"                                             # 東京リージョンを使う
}                                                    

resource "aws_s3_bucket" "example" {                                    # S3バケットリソースの定義開始
  bucket = "my-minimal-terraform-bucket-hoge"                       
}                                                    

resource "aws_s3_bucket_public_access_block" "example" {                # 公開ブロック設定(安全装置)の定義
  bucket = aws_s3_bucket.example.id                                     # 上で作ったバケットIDを指定

  block_public_acls       = false                                       # ACLでの公開ブロックをOFF
  block_public_policy     = false                                       # ポリシーでの公開ブロックをOFF
  ignore_public_acls      = false                                       # 公開ACLを無視する設定をOFF
  restrict_public_buckets = false                                       # 公開バケットの制限をOFF
}                                                                       # 安全装置の解除終了

resource "aws_s3_bucket_website_configuration" "example" {              # Webサイト機能の設定開始
  bucket = aws_s3_bucket.example.id                                     # 対象のバケットを指定

  index_document {                                                      # インデックスドキュメントの設定
    suffix = "index.html"                                               # アクセス時に表示するファイル名
  }                                                                     # インデックス設定終了
}

resource "aws_s3_bucket_policy" "allow_access" {                        # バケットポリシー(許可証)の定義開始
  bucket = aws_s3_bucket.example.id                                     # 対象のバケットを指定

  policy = jsonencode({                                                 # ポリシーの中身をJSON形式で書く
    Version = "2012-10-17"                                              # ポリシー構文のバージョン
    Statement = [                                                       # ステートメント(命令)のリスト
      {
        Effect    = "Allow"                                             # 「許可する」
        Principal = "*"                                                 # 「すべてのユーザー」に対して
        Action    = "s3:GetObject"                                      # 「ファイルを読み取る操作」を
        Resource  = "${aws_s3_bucket.example.arn}/*"                    # 「このバケット内の全ファイル」で
      },
    ]
  })

  depends_on = [aws_s3_bucket_public_access_block.example]              # 安全装置解除が終わるまで待つ
}

output "website_url" {                                                  # コンソールへの出力設定
  value = "http://${aws_s3_bucket_website_configuration.example.website_endpoint}" # 作成されたURLを表示
}

上記の変更後、もう一度下記コマンドを実行します。
applyコマンドはリソース定義の変更差分を検出してAWS側に反映してくれます。

terraform apply

上記を実行すると、コンソールに公開されたURLが表示されるので、ブラウザでアクセスしてみると以下のようにindex.htmlの内容が表示されます
(httpなのでブラウザ上は警告が出ますが、今回はhttps対応は省きます。本番環境では別途対応してください。)

applyコマンド後のブラウザでの表示

手順⑥:htmlを編集し、S3に変更を同期する

以下のようにindex.htmlを編集します。
(2行目が追記内容)

index.html
<div>Hello Terraform</div>
<div>File Updated</div> 

その後、以下のコマンドをもう一度叩いてsrcフォルダの変更内容を同期します。

aws s3 sync ./src s3://my-minimal-terraform-bucket-hoge # ここは先ほど自分で作成したS3バケットのIDに変えてください

実行後、もう一度公開されたS3バケットのURLにブラウザでアクセスすると、以下のように更新されていることが確認できます。
syncコマンド後のブラウザでの表示

手順⑦:Terraformで作ったリソースを削除する

無事にAWSにデプロイができたので、後処理として作成したリソースを消していきます。

まずは以下のコマンドでS3バケットの中身を空にします。
(バケットの中身がまだある状態だと「空じゃないよ」というエラーになってしまうため)

aws s3 rm s3://my-minimal-terraform-bucket-hoge --recursive 

その後、下記コマンドを実行してバケット自体を削除します。

terraform destroy

途中、確認のプロンプトが出てきますが、yesと入力します。
下記のような表示がされれば、削除完了です。
destroyコマンド後の表示

まとめ

TerraformとAWS CLIを使用してごく簡単なWebサイトをデプロイし削除するまでを学習用に体感してみました。

AWSに慣れている人からすればここまでシンプルな構成だとGUIでポチポチ設定するのに比べてもメリットは感じにくいのかもしれませんが、TerraformによってGit管理にインフラの構築手順が押し込められること、AIにインフラ構築手順を聞く際にそのままコードで吐かせられることなど、恩恵は実際に開発をするうえでは計り知れないと思います。

そしてそうした恩恵は、クラウドのインフラ設計のプラクティスすらまだ分かっていない、ガチャガチャと試行錯誤が必要な私のような初学者にこそ効いてくる気もしています。

次回やりたいこと

次は以下のどちらかに進みたいと考えています。

  • このサンプルをすこしだけ拡張して、S3の手前にCloudFront(AWSのCDNサービス)を噛ませるような形をTerraformを使って書いてみる
  • この最小構成のまま、GithubActionsを使ってCD(mainにプッシュしたらデプロイ内容が更新される状態)を整備してみる

Discussion