logo
Published on

『さわって学ぶクラウドインフラ Amazon Web Service 基礎からのネットワーク&サーバー構築』with Terraform

Authors
  • 個人的にNotionに書いているものをほぼそのまま転機しているのでフォーマットが崩れているかもしれません。ご了承ください

TL;DR

WordPressの構築を通して、VPCやサブネット、EC2などインフラ構築を行う上で不可欠な知識をハンズオン形式で学びながら、AWSを用いた基本的なネットワークとサーバーの構築を行っていく。

これまでAWSを触ったことがない初学者でもキャプチャ付きで丁寧な解説を挟みながら構築していくので、AWSのみならずネットワークやサーバーの仕組みの基礎も理解できる1冊。

書籍ではすべてコンソールから操作しているが、今回はTerraformの思い出し作業も兼ねているので、Terraformで構築しながら読んでいく。

最終的な構成図は以下のようになる。 WordPress構築のインフラ構成図

感想

今回はAWSとTerraform双方の基礎的な部分の理解(思い出し)がメインだったこともあり、書籍を見ながらすべてTerraformを用いてインフラ構築を行った。

AWSの操作画面のキャプチャも細かく掲載されているので、これくらいの規模感のものであればTerraformのチュートリアル終わりくらいの人であればTerraformでの構築経験を積むという意味でもコンソールよりTerraformを使う方がいいと思う。なんならこれからAWSとかのクラウドでのインフラ構築とかやる人であれば、Terraformは遠からず求められるはずなので先にTerraformのチュートリアルをいくつかこなして、こっちはTerraformで組むのが良いと思う。

最後まで取り組むためには複数インスタンスを立てたり、NATゲートウェイを利用するなど無料枠外の部分もある。そのため無駄な課金を減らすという意味でもTerraformで構築して、1日の学習が終われば一旦すべて破棄( terraform destroy )し、再度学習再開するときに terraform apply してから始める、とかが良いと思う

ちなみに3~4年前にはじめてAWSキャッチアップしたときもこの書籍を使って行ったが、コンソールから操作していた+NATゲートウェイの課金の仕組みの理解が浅く、1万円近く課金されたのは良い思い出...

このあとはTerraformのベストプラクティスなり、いろんな企業のブログなど見ながらTerraformでのモジュール管理を意識したコード分割とかやったり、実際にいろんなインフラ組んでみるとかやってもいいのでは。

最近ではEC2やDBにSSH接続することもだいぶ減っていると思うので、そのあたりをアレンジしたりとかも良さそう。

メモ

ここからは読み進めていきながらのメモ。

Chapter1

Chapter1では書籍を通じて構築するものの全体像からキーとなるものについての解説が中心。

インフラ周りの知識がない状態でも構築する構成図を見せながら全体感を解説することでイメージが具体化されるので「何を学ぶのか」「何を学ぶべきか」が明確になって理解が促進される。

Chapter2からは実際にネットワークやサーバーの構築などに着手する。

完全にネットワークやサーバーの知識がゼロの場合には難しい部分も多いかもしれないが、構築しながらルーティングの仕組みなどの解説も挟んでくれるため、AWSのみならずネットワーク周りについてもハンズオン形式で学ぶようなイメージが近い。

Chapter2: ネットワークを構築する

実際にAWSを操作しながらネットワークの構築に着手する。

構築にあたり必要なIPアドレス周りの基本的な知識の記述もあるため、現時点でネットワーク周りの知識が深くなくても作りながら理解できる構成になっている。

今回はTerraformを使いながら進めたいので、このタイミングで本書用のプロジェクト作った。

モジュール分割については、今回は対応せず、基本的に main.tf にまとめていく形で進める。

// main.tf
terraform {
    required_providers {
        aws = {
        source  = "hashicorp/aws"
        version = "~> 5.0"
        }
    }

    required_version = ">= 1.2.0"
}

provider "aws" {
  region = "ap-northeast-1"
}

AWSのリソースについては以下から確認する

Chapter2終了時点でのTerraformは以下。タグはわかりやすいように適当につけていく

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }

  required_version = ">= 1.2.0"
}

provider "aws" {
  region = "ap-northeast-1"
}

// [Resource: aws_vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc)
resource "aws_vpc" "VPC" {
  cidr_block = "10.0.0.0/16" // CIDR表記で、今回はネットワークのビット長を16ビットに設定
  tags = {
    Name = "aws-network-server-VPC"
  }
}

// 公開用サブネット
// [Resource: aws_subnet](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet)
resource "aws_subnet" "public1" {
  vpc_id     = aws_vpc.VPC.id
  cidr_block = "10.0.1.0/24"
  tags = {
    Name = "aws-network-server-subnet-public1"
  }
}

// [Resource: aws_internet_gateway](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/internet_gateway)
resource "aws_internet_gateway" "test_internet_gateway" {
  vpc_id = aws_vpc.VPC.id
  tags = {
    Name = "aws-network-server-test-internet-gateway"
  }
}

// [Resource: aws_route_table](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table#example-usage)
resource "aws_route_table" "public_route_table" {
  vpc_id = aws_vpc.VPC.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.test_internet_gateway.id
  }
  tags = {
    Name = "aws-network-server-public-route-table"
  }
}

// 公開用サブネットとルートテーブルの関連付け
// [Resource: aws_route_table_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association)
resource "aws_route_table_association" "public_subnet_association" {
  subnet_id      = aws_subnet.public1.id
  route_table_id = aws_route_table.public-route-table.id
}

Chapter3: サーバーを構築する

  1. 仮想サーバーの構築
    • Amazon EC2を用いて仮想サーバー(= インスタンス)を作成
    • イメージファイル(Amazon Machine Image)を用いて、OS等初期設定済みの仮想サーバーを起動する
    • キーペア設定することでSSH接続も可能に
    • 今回起動するEC2は外部からのアクセスを許可するため、パブリックサブネットに配置する
    • セキュリティ対策のため、セキュリティグループを設定する
      • SSH接続を許可するため、SSH接続用を通す設定を付与する
      • EC2インスタンスへのSSH接続は可能な限りセキュアにしておくべきものなので、自分のIPのみを通すように設定しておく
      • 今後インターネットからのアクセスを通すための設定も付与する予定
  2. SSH接続
    • EC2インスタンスのパブリックIPを取得し、SSH接続する

      • AWSではセキュリティのためSSM(AWS Session Manager)で接続する方法を推奨しているが、今回はSSHを使用
      $ ssh -i <key_file> ec2-user@<public_ip_address>
      
      • sshd というプログラムにより、SSH接続が受け入れられ、サーバー操作が可能になる
  3. IPアドレスとポート番号
    • インターネットにおいて、ルータ同士が通信して自動でルートテーブルの情報をやりとりし更新する仕組みがある
      • EGP(Exterior Gateway Protocol)
        • AmazonやGoogleなどある程度大きなサービス間での経路情報をやりとりする仕組み
        • ネットワークを管理するサービスごとにAS(Autonomous System)として管理されており、「どのネットワークの先に、どのネットワークが接続されているか」をおおまかにやりとりしている
      • IGP(Interior Gateway Protocol)
        • EGPでやりとりする各サービスの内部ネットワークにおいて使用されるもの
        • プロバイダーやAWSなどの内部での詳細なやり取りに使われる
  4. ファイアウォールで接続制限
    • ファイアウォール
      • 通して良いデータだけを通して、それ以外を遮断する機能の総称
      • 簡単な構造のものの1つが「パケットフィルタリング」
    • パケットフィルタリング
      • IPアドレスやポート番号など、流れるパケットを見て通過の可否を決める仕組み
      • AWSではセキュリティグループで対応する
        • インバウンドルールとアウトバウンドルールが設定できる
          • インバウンドルールは「外からインスタンスに接続する向き」
          • アウトバウンドルールは「インスタンスから外に出ていく向き」
          • この段階ではSSH接続のみを許可するインバウンドルールのみ設定しているため、SSH接続以外のアクセスも、すべてのアウトバウンドも拒否される

Chapter3終了時のコードは以下。

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }

  required_version = ">= 1.2.0"
}

provider "aws" {
  region = "ap-northeast-1"
}


// [Resource: aws_vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc)
resource "aws_vpc" "VPC" {
  cidr_block = "10.0.0.0/16" // CIDR表記で、今回はネットワークのビット長を16ビットに設定
  tags = {
    Name = "aws-network-server-VPC"
  }
}

// 公開用サブネット
// [Resource: aws_subnet](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet)
resource "aws_subnet" "public1" {
  vpc_id     = aws_vpc.VPC.id
  cidr_block = "10.0.1.0/24"
  tags = {
    Name = "aws-network-server-subnet-public1"
  }
}

// [Resource: aws_internet_gateway](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/internet_gateway)
resource "aws_internet_gateway" "test_internet_gateway" {
  vpc_id = aws_vpc.VPC.id
  tags = {
    Name = "aws-network-server-test-internet-gateway"
  }
}

// [Resource: aws_route_table](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table#example-usage)
resource "aws_route_table" "public_route_table" {
  vpc_id = aws_vpc.VPC.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.test_internet_gateway.id
  }
  tags = {
    Name = "aws-network-server-public-route-table"
  }
}

// 公開用サブネットとルートテーブルの関連付け
// [Resource: aws_route_table_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association)
resource "aws_route_table_association" "public_subnet_association" {
  subnet_id      = aws_subnet.public1.id
  route_table_id = aws_route_table.public_route_table.id
}

// AMIの一覧取得コマンド: aws ssm get-parameters-by-path --path /aws/service/ami-amazon-linux-latest --query 'Parameters[].Name'
data "aws_ssm_parameter" "amazonlinux_2023" {
  name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64" // Amazon Linux 2023の最新のAMI IDを取得
}


// 任意のAMIを指定する場合は以下のように記述する
# data "aws_ami" "amazon_linux_2023" {
#   most_recent = true
#   owners      = ["amazon"]
#   filter {
#     name   = "name"
#     values = ["al2023-ami-*-kernel-6.1-x86_64"]
#   }
# }

resource "aws_instance" "web_server" {
  ami                         = data.aws_ssm_parameter.amazonlinux_2023.value
  instance_type               = "t2.micro"
  private_ip                  = "10.0.1.10"                          // 指定しない場合、パブリックサブネットに割り当てた「10.0.1.0~10.0.1.255」の範囲内でインスタンス起動時に自動で割り当てられる。今回は手動で指定
  subnet_id                   = aws_subnet.public1.id                // 外部公開用サブネットにインスタンスを配置
  associate_public_ip_address = true                                 // 外部からのアクセス用にパブリックIPを自動で割り当てる
  vpc_security_group_ids      = [aws_security_group.web_sg.id]       // EC2インスタンスに適用するセキュリティグループを指定
  key_name                    = aws_key_pair.web_server_key.key_name // EC2インスタンスに適用するキーペアを指定.SSH接続を行うために必要
  tags = {
    Name = "aws-network-server-web-server"
  }
}

// 上記で生成したインスタンスで利用する仮想ディスク(EBS: Elastic Block Store)を作成する
resource "aws_ebs_volume" "web_server_volume" {
  availability_zone = aws_instance.web_server.availability_zone
  size              = 8
  type              = "gp2" // default: gp2
  tags = {
    Name = "aws-network-server-web-server-volume"
  }
}

// 作成したインスタンスとEBSを紐付ける
// [Resource: aws_volume_attachment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/volume_attachment)
resource "aws_volume_attachment" "web_server_attachment" {
  device_name = "/dev/sdh"
  instance_id = aws_instance.web-server.id
  volume_id   = aws_ebs_volume.web-server-volume.id
}

// このリソースで生成された秘密鍵は、Terraformのステートファイルに暗号化されずに保存される。
// 今回の実装では本運用しない設定のため、`tls-private_key`を使用しているが、本運用の際にはセキュアな方法で秘密鍵を管理する
// [tls_private_key (Resource)](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key)
resource "tls_private_key" "web_server_key" {
  algorithm = "ED25519" // RSAだとキーの長さ制限にかかることがあるため、ED25519を使用
}

// 上記で生成した秘密鍵をローカルファイルに保存する
// [local_file (Resource)](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file)
resource "local_file" "private" {
  filename        = "id_ed25519_aws_network_server"
  content         = tls_private_key.web_server_key.private_key_openssh
  file_permission = "0600"
}

// 上記で生成した秘密鍵を使用して、AWSのキーペアを作成する。これをEC2インスタンスに紐付けることで、SSH接続を行うことができるようにする
// [Resource: aws_key_pair](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/key_pair)
resource "aws_key_pair" "web_server_key" {
  key_name   = "by_terraform"
  public_key = tls_private_key.web_server_key.public_key_openssh
}

// [Resource: aws_security_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group)
resource "aws_security_group" "web_sg" {
  name        = "web-sg"
  description = "Maneged by Terraform!"
  vpc_id      = aws_vpc.VPC.id // セキュリティグループを作成する対象のVPCを指定
  tags = {
    Name = "aws-network-server-web-sg"
  }
}

// ec2インスタンスにssh接続を許可するためのセキュリティグループルールを設定する。resourceにはaws_security_group_ruleを使用しないこと
// [Resource: aws_vpc_security_group_ingress_rule](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule)
resource "aws_vpc_security_group_ingress_rule" "web_sg_ssh" {
  security_group_id = aws_security_group.web_sg.id
  from_port         = 22
  to_port           = 22
  ip_protocol       = "tcp"
  cidr_ipv4         = "XXX.XXX.XXX.XXX/32" // SSH接続を許可するIPアドレスを指定.全てのIPアドレスを許可する場合は"0.0.0.0/0"を指定
  description       = "Allow SSH from my IP"
}

/**
 * ec2インスタンスから外部への通信を許可するためのセキュリティグループルールを設定する。resourceにはaws_security_group_ruleを使用しないこと
 * セキュリティグループは、デフォルトですべてのアウトバウンド通信を許可することもあり、書籍上では設定する記述はなかったが、
 * `aws_vpc_security_group_ingress(egress)_rule`を使って個別にルールを設定すると、セキュリティグループのデフォルトルールが無効化されるため、追加設定が必要
 */
resource "aws_vpc_security_group_egress_rule" "all_traffic_rule" {
  security_group_id = aws_security_group.web_sg.id
  from_port         = -1          // すべてのポート(0から65535まで)を許可。
  to_port           = -1          // すべてのポート(0から65535まで)を許可。
  ip_protocol       = "-1"        // すべてのプロトコル
  cidr_ipv4         = "0.0.0.0/0" // すべての宛先に送信を許可
  description       = "Allow all outbound traffic"
}

Chapter4: Webサーバーソフトをインストールする

4.1. Apache HTTP Serverのインストール

  • サーバーにWebサーバーソフトをインストールすることでWebブラウザからの要求を受け取り、サーバー上のコンテンツを返したりサーバー上でウェブアプリケーションを実行したりできるようになる

  • インストール前に、 ping コマンドを用いて外部ネットワークにアクセスできるか確認する

    # e.g.)
    $ ping -c 4 8.8.8.8
    
    # response
    PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
    64 bytes from 8.8.8.8: icmp_seq=1 ttl=117 time=1.89 ms
    64 bytes from 8.8.8.8: icmp_seq=2 ttl=117 time=1.94 ms
    64 bytes from 8.8.8.8: icmp_seq=3 ttl=117 time=1.97 ms
    64 bytes from 8.8.8.8: icmp_seq=4 ttl=117 time=1.96 ms
    
    --- 8.8.8.8 ping statistics ---
    4 packets transmitted, 4 received, 0% packet loss, time 3005ms
    rtt min/avg/max/mdev = 1.890/1.938/1.968/0.030 ms
    
    • ping
      • サーバーやネットワークデバイスとの接続確認するコマンド

      • ICMPプロトコルのエコーリクエスト(Echo Request)を送信し、その応答(エコーレスポンス)が帰ってくるまでの時間を計測する

        Internet Control Message Protocol(ICMP)は、ネットワーク通信の問題を診断するためにネットワークデバイスが使用するネットワーク層のプロトコルです。ICMPは主に、データが目的の宛先にタイムリーに到達しているかどうかを判断するために使用されます。一般に、ICMPプロトコルルーターなどのネットワークデバイスで使用されます。ICMPは、エラーの報告やテストに極めて重要ですが、分散サービス妨害(DDoS)攻撃にも使用されることがあります。

      • レスポンスとして、各パケットの送信結果(応答時間、TTLなど)と、最終的な統計情報(送信・受信パケット数、遅延時間の平均など)が表示される

    • -c 4 オプション
      • ICMPリクエスト(パケット)の送信回数を指定。この場合、4回リクエストを送信する
      • -c を指定しない場合、ping は終了せず、手動で停止(通常は Ctrl+C)するまでリクエストを送り続けます。
    • 8.8.8.8
      • 接続先のIPアドレス。
      • Googleの提供するパブリックDNSサーバーのIPアドレスで、一般的に接続テストに利用される
  • $ sudo dnf -y install httpd コマンドでApacheをインストールする

    • dnf コマンドはアプリケーションのインストールやアンインストール時に使用する管理者コマンド

    • -y オプションはインストール時にユーザー確認を不要とするオプション

    • aws_instanceuser_data に以下を設定することで起動時にインストールと起動まで実行できる

      resource "aws_instance" "web_server" {
      ami                         = data.aws_ssm_parameter.amazonlinux_2023.value
      instance_type               = "t2.micro"
      private_ip                  = "10.0.1.10"                          // 指定しない場合、パブリックサブネットに割り当てた「10.0.1.0~10.0.1.255」の範囲内でインスタンス起動時に自動で割り当てられる。今回は手動で指定
      subnet_id                   = aws_subnet.public1.id                // 外部公開用サブネットにインスタンスを配置
      associate_public_ip_address = true                                 // 外部からのアクセス用にパブリックIPを自動で割り当てる
      vpc_security_group_ids      = [aws_security_group.web_sg.id]       // EC2インスタンスに適用するセキュリティグループを指定
      key_name                    = aws_key_pair.web_server_key.key_name // EC2インスタンスに適用するキーペアを指定.SSH接続を行うために必要
      // 以下のユーザーデータを実行することで、インスタンス起動時にApacheをインストールし、起動する
      user_data = <<-EOF
                  #!/bin/bash
                  sudo dnf update -y
                  sudo dnf install -y httpd
                  sudo systemctl start httpd.service
                  sudo systemctl enable httpd.service
                  EOF
      tags = {
          Name = "aws-network-server-web-server"
      }
      }
      
      • systemctl コマンドは、指定したコマンドを「起動(start)」「停止(stop)」「再起動(restart)」するコマンド

4.2. ファイアウォールを設定する

ここまでの設定では、インバウンドルール(外部からインスタンスにアクセスする向き)はSSH接続のみしか許可されていないため、Webサーバーソフトを起動してもアクセスできない。

Webからのアクセスはport番号80(httpsの場合は443)で待ち受けるため、port番号80を待ち受けるように変更する

// ec2インスタンスにhttp接続を許可するためのセキュリティグループルールを設定する。resourceにはaws_security_group_ruleを使用しないこと
resource "aws_vpc_security_group_ingress_rule" "http" {
  security_group_id = aws_security_group.web_sg.id
  from_port         = 80
  to_port           = 80
  ip_protocol       = "tcp"
  cidr_ipv4         = "0.0.0.0/0" // すべてのIPアドレスからのアクセスを許可
  description       = "Allow HTTP from all"
}

4.3. ドメイン名と名前解決

  • TCP/IPの世界では、相手先を確認するのはあくまでも「IPアドレス」で、ドメイン名でアクセスするときも最終的にIPアドレスに変換して接続する
    • この時使われる仕組みが「DNS(Domain Name System)」
    • DNSを用いて、あるドメイン名からそれに対応するIPアドレスを引き出すことを「名前解決」と呼ぶ
  • DNSサーバー
    • IPアドレスとドメイン名を変換する役割
    • DNSのシステムは世界中に分散したDNSサーバー群で構成された大きな分散型DB
    • 各DNSサーバーは担当する範囲が決まっており、その範囲でIPアドレスとドメイン名の変換を行う。管轄外の場合には他のDNSサーバーに問い合わせる
    • DNSサーバーはドメイン名の名前解決をするため、「DNSリゾルバ(resolver)」とも呼ばれる
  • DNSサーバーの構築
    • ここまでの手順で作成したEC2インスタンスではDNS名が設定されておらず、パブリックIPでのみアクセスできる状態。そこでインスタンスにDNS名を設定し、ドメイン名でのアクセスが可能になるようにする

      resource "aws_vpc" "VPC" {
        cidr_block = "10.0.0.0/16" // CIDR表記で、今回はネットワークのビット長を16ビットに設定
        enable_dns_hostnames = true // 追加。default: false。DNSホスト名を有効にする
        tags = {
          Name = "aws-network-server-VPC"
        }
      }
      
      • enable_dns_hostnamestrue にすることで、対象のVPC内で起動したインスタンスにDNS名が割り当てられるようになる

Chapter4終了時のコード

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }

  required_version = ">= 1.2.0"
}

provider "aws" {
  region = "ap-northeast-1"
}


// [Resource: aws_vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc)
resource "aws_vpc" "VPC" {
  cidr_block = "10.0.0.0/16" // CIDR表記で、今回はネットワークのビット長を16ビットに設定
  enable_dns_hostnames = true
  tags = {
    Name = "aws-network-server-VPC"
  }
}

// 公開用サブネット
// [Resource: aws_subnet](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet)
resource "aws_subnet" "public1" {
  vpc_id     = aws_vpc.VPC.id
  cidr_block = "10.0.1.0/24"
  # map_public_ip_on_launch = true // サブネットに起動したインスタンスにパブリックIPアドレスを割り当てる場合はtrueを指定する。デフォルトはfalse。インスタンスの設定で associate_public_ip_address = true を指定すると、この設定を上書き
  tags = {
    Name = "aws-network-server-subnet-public1"
  }
}

// [Resource: aws_internet_gateway](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/internet_gateway)
resource "aws_internet_gateway" "test_internet_gateway" {
  vpc_id = aws_vpc.VPC.id
  tags = {
    Name = "aws-network-server-test-internet-gateway"
  }
}

// [Resource: aws_route_table](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table#example-usage)
resource "aws_route_table" "public_route_table" {
  vpc_id = aws_vpc.VPC.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.test_internet_gateway.id
  }
  tags = {
    Name = "aws-network-server-public-route-table"
  }
}

// 公開用サブネットとルートテーブルの関連付け
// [Resource: aws_route_table_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association)
resource "aws_route_table_association" "public_subnet_association" {
  subnet_id      = aws_subnet.public1.id
  route_table_id = aws_route_table.public-route-table.id
}

// AMIの一覧取得: aws ssm get-parameters-by-path --path /aws/service/ami-amazon-linux-latest --query 'Parameters[].Name'
data "aws_ssm_parameter" "amazonlinux_2023" {
  name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64" // Amazon Linux 2023の最新のAMI IDを取得
}


// 任意のAMIを指定する場合は以下のように記述する
# data "aws_ami" "amazon_linux_2023" {
#   most_recent = true
#   owners      = ["amazon"]
#   filter {
#     name   = "name"
#     values = ["al2023-ami-*-kernel-6.1-x86_64"]
#   }
# }

// [Resource: aws_instance](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance)
resource "aws_instance" "web_server" {
  ami                         = data.aws_ssm_parameter.amazonlinux_2023.value
  instance_type               = "t2.micro"
  private_ip                  = "10.0.1.10"                          // 指定しない場合、パブリックサブネットに割り当てた「10.0.1.0~10.0.1.255」の範囲内でインスタンス起動時に自動で割り当てられる。今回は手動で指定
  subnet_id                   = aws_subnet.public1.id                // 外部公開用サブネットにインスタンスを配置
  associate_public_ip_address = true                                 // 外部からのアクセス用にパブリックIPを自動で割り当てる
  vpc_security_group_ids      = [aws_security_group.web_sg.id]       // EC2インスタンスに適用するセキュリティグループを指定
  key_name                    = aws_key_pair.web_server_key.key_name // EC2インスタンスに適用するキーペアを指定.SSH接続を行うために必要
  // 以下のユーザーデータを実行することで、インスタンス起動時にApacheをインストールし、起動する
  user_data = <<-EOF
              #!/bin/bash
              sudo dnf update -y
              sudo dnf install -y httpd
              sudo systemctl start httpd.service
              sudo systemctl enable httpd.service
              EOF
  tags = {
    Name = "aws-network-server-web-server"
  }
}

// 上記で生成したインスタンスで利用する仮想ディスク(EBS: Elastic Block Store)を作成する
resource "aws_ebs_volume" "web_server_volume" {
  availability_zone = aws_instance.web_server.availability_zone
  size              = 8
  type              = "gp2" // default: gp2
  tags = {
    Name = "aws-network-server-web-server-volume"
  }
}

// 作成したインスタンスとEBSを紐付ける
// [Resource: aws_volume_attachment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/volume_attachment)
resource "aws_volume_attachment" "web_server_attachment" {
  device_name = "/dev/sdh"
  instance_id = aws_instance.web_server.id
  volume_id   = aws_ebs_volume.web_server_volume.id
}

// このリソースで生成された秘密鍵は、Terraformのステートファイルに暗号化されずに保存される。
// 今回の実装では本運用しない設定のため、`tls-private_key`を使用しているが、本運用の際にはセキュアな方法で秘密鍵を管理する
// [tls_private_key (Resource)](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key)
resource "tls_private_key" "web_server_key" {
  algorithm = "ED25519" // RSAだとキーの長さ制限にかかることがあるため、ED25519を使用
}

// 上記で生成した秘密鍵をローカルファイルに保存する
// [local_file (Resource)](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file)
resource "local_file" "private" {
  filename        = "id_ed25519_aws_network_server"
  content         = tls_private_key.web_server_key.private_key_openssh
  file_permission = "0600"
}

// 上記で生成した秘密鍵を使用して、AWSのキーペアを作成する。これをEC2インスタンスに紐付けることで、SSH接続を行うことができるようにする
// [Resource: aws_key_pair](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/key_pair)
resource "aws_key_pair" "web_server_key" {
  key_name   = "by_terraform"
  public_key = tls_private_key.web_server_key.public_key_openssh
}

// [Resource: aws_security_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group)
resource "aws_security_group" "web_sg" {
  name        = "web-sg"
  description = "Maneged by Terraform!"
  vpc_id      = aws_vpc.VPC.id // セキュリティグループを作成する対象のVPCを指定
  tags = {
    Name = "aws-network-server-web-sg"
  }
}

// ec2インスタンスにssh接続を許可するためのセキュリティグループルールを設定する。resourceにはaws_security_group_ruleを使用しないこと
// [Resource: aws_vpc_security_group_ingress_rule](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule)
resource "aws_vpc_security_group_ingress_rule" "web_sg_ssh" {
  security_group_id = aws_security_group.web_sg.id
  from_port         = 22
  to_port           = 22
  ip_protocol       = "tcp"
  cidr_ipv4         = "XXX.XXX.XX.XXX/32" // SSH接続を許可するIPアドレスを指定.全てのIPアドレスを許可する場合は"0.0.0.0/0"を指定
  description       = "Allow SSH from my IP"
}

// ec2インスタンスにhttp接続を許可するためのセキュリティグループルールを設定する。resourceにはaws_security_group_ruleを使用しないこと
resource "aws_vpc_security_group_ingress_rule" "http" {
  security_group_id = aws_security_group.web_sg.id
  from_port         = 80
  to_port           = 80
  ip_protocol       = "tcp"
  cidr_ipv4         = "0.0.0.0/0" // すべてのIPアドレスからのアクセスを許可
  description       = "Allow HTTP from all"
}

/**
 * ec2インスタンスから外部への通信を許可するためのセキュリティグループルールを設定する。resourceにはaws_security_group_ruleを使用しないこと
 * セキュリティグループは、デフォルトですべてのアウトバウンド通信を許可することもあり、書籍上では設定する記述はなかったが、
 * `aws_vpc_security_group_ingress(egress)_rule`を使って個別にルールを設定すると、セキュリティグループのデフォルトルールが無効化されるため、追加設定が必要
 * この設定を付与することで、dnfコマンドを使用して、外部からhttpsなどWebサーバーソフトウェアをインストールすることができる
 *
 * [Resource: aws_vpc_security_group_egress_rule](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule)
 */
resource "aws_vpc_security_group_egress_rule" "all_traffic_rule" {
  security_group_id = aws_security_group.web_sg.id
  from_port         = -1          // すべてのポート(0から65535まで)を許可。
  to_port           = -1          // すべてのポート(0から65535まで)を許可。
  ip_protocol       = "-1"        // すべてのプロトコル
  cidr_ipv4         = "0.0.0.0/0" // すべての宛先に送信を許可
  description       = "Allow all outbound traffic"
}

Chapter5: HTTPの動きを確認する

5.1 HTTPとは

リクエスト・レスポンスでの書式の解説メイン

5.2 ブラウザの開発者ツールでHTTPのやりとりをのぞいてみる

開発者ツールを使い、実際にリクエスト・レスポンスの中身を見ながらの解説

5.3 Telnetを使ってHTTPをしゃべってみる

  • Telnet
    • リモートのコンピュータとやりとりする仕組み = 遠く離れたコンピュータを手元のコンピュータのように操作できる仕組み
    • 汎用的な双方向8ビット通信を提供する端末間およびプロセス間の通信プロトコル
      1. 端末間通信:
        • 例えば、Aさんのコンピュータ(クライアント)からBさんのコンピュータ(サーバー)に接続し、まるでBさんのコンピュータの前に座って操作しているかのように、コマンドを送ったり、結果を受け取ったりできる仕組みです。
      2. 双方向通信:
        • Aさんが入力したコマンドがBさんのコンピュータに送られ、Bさんのコンピュータがその結果を返す。お互いにデータを送受信できる「行ったり来たりの通信」が可能です。
      3. 8ビット通信:
        • 通信データを8ビットの単位で送受信します。これは「英数字や記号などのテキストデータをやり取りする」のが基本で、シンプルなデータ交換を行います。
    • Telnetは暗号化されていない通信を行うため、やり取りされるデータ(例えばパスワード)がそのままネットワーク上に流れます。このため、現在では**より安全なSSH(Secure Shell)**がTelnetの代わりに使われることがほとんど
  • Telnetを使ってHTTPの動作を確認する
    • SSH接続したインスタンスにTelnetをインストール

      $ sudo dnf -y install telnet
      
    • $ telnet localhost 80 を実行

      • ローカルホスト(localhost)上のポート80に対してTelnetで接続を試みるコマンド
      • 現在自分のコンピュータで動作しているサービス(例えばウェブサーバー)がポート80で待ち受けているか確認するために使われる

Chapter6: プライベートサブネットを構築する

6.1 プライベートサブネットの利点

  • プライベートサブネットに配置することで、DBなど外部からアクセスされたくないものをインターネットの世界から隠すことができ、セキュリティを高められる

6.2 プライベートサブネットを作る

  • インターネットからアクセスできない領域にDBを配置するため、プライベートサブネットを作成する
    • VPCは既存で作成済みのもの
    • プライベートサブネットはインターネットに公開しないので、ルートテーブルはデフォルトで作成されるもののみ
    // 非公開用サブネット
    // [Resource: aws_subnet](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet)
    resource "aws_subnet" "private1" {
        vpc_id     = aws_vpc.VPC.id
        cidr_block = "10.0.2.0/24"
        tags = {
            Name = "aws-network-server-subnet-private1"
        }
    }
    

6.3 プライベートサブネットにサーバーを構築する

  • 上記で作成したプライベートサブネット内にDBを配置する用のインスタンスを構築する

    • 基本的なリソースはパブリックサブネットに配置したものと同じ
    • インターネットから直接このインスタンスに接続させないので、公開用のIPアドレスは設定しない
    • IPアドレスは 10.0.2.0/10 を割り当て
      • 設定しない場合には空いているIPアドレスが自動割り当てされるが、便宜上設定
    // 非公開用サブネットにEC2インスタンスを作成する
    resource "aws_instance" "db_server" {
        ami                         = data.aws_ssm_parameter.amazonlinux_2023.value
        instance_type               = "t2.micro"
        subnet_id                   = aws_subnet.private1.id
        associate_public_ip_address = false
        private_ip                  = "10.0.2.10"
        vpc_security_group_ids      = [aws_security_group.db-sg.id]
    
        tags = {
            Name = "aws-network-server-db-server"
        }
    }
    
    resource "aws_ebs_volume" "db_server_volume" {
        availability_zone = aws_instance.db-server.availability_zone
        size              = 8
        type              = "gp2"
        tags = {
            Name = "aws-network-server-db-server-volume"
        }
    }
    
    resource "aws_volume_attachment" "db_server_attachment" {
        device_name = "/dev/sdh"
        instance_id = aws_instance.db_server.id
        volume_id   = aws_ebs_volume.db_server_volume.id
    }
    
  • インスタンスに対するファイアウォールの設定としてセキュリティグループを追加

    resource "aws_security_group" "db_sg" {
        name        = "db-sg"
        description = "Managed by Terraform!"
        vpc_id      = aws_vpc.VPC.id
        tags = {
            Name = "aws-network-server-db-sg"
        }
    }
    
    resource "aws_vpc_security_group_ingress_rule" "db_sg_mariadb" {
        security_group_id = aws_security_group.db_sg.id
        from_port         = 3306 // MariaDBのデフォルトポート
        to_port           = 3306 // MariaDBのデフォルトポート
        ip_protocol       = "tcp"
        cidr_ipv4         = "0.0.0.0/0" // 接続元制限は今後対応
        description       = "Allow MySQL from all"
    }
    
    resource "aws_vpc_security_group_egress_rule" "all_traffic_rule" {
        security_group_id = aws_security_group.db_sg.id
        from_port         = -1          // すべてのポート(0から65535まで)を許可。
        to_port           = -1          // すべてのポート(0から65535まで)を許可。
        ip_protocol       = "-1"        // すべてのプロトコル
        cidr_ipv4         = "0.0.0.0/0" // すべての宛先に送信を許可
        description       = "Allow all outbound traffic"
    }
    
  • WebサーバーからDBサーバーにアクセスしてデータ取得できるようにするため、WebサーバーからDBサーバーにアクセスできるか検証する

    • ping コマンド: サーバー間の疎通確認によく用いられる
      • ping コマンドでは ICMP(Internet Control Message Protocol)を用いる
      • 参照
    • AWSのデフォルトのセキュリティグループではICMPプロトコルは許可されていないため、DBサーバーのインバウンドルールを変更する
    resource "aws_vpc_security_group_ingress_rule" "db_sg_icmp" {
      security_group_id = aws_security_group.db-sg.id
      from_port         = 8
      to_port           = 0
      ip_protocol       = "icmp"
      cidr_ipv4         = "0.0.0.0/0"
      description       = "Allow ICMP from all"
    }
    
  • ssh接続したWebサーバーからping コマンドで疎通を確認

    # pingを4回実行
    $ ping -c 4 10.0.2.10
    
    ## response
    # PING 10.0.2.10 (10.0.2.10) 56(84) bytes of data.
    # 64 bytes from 10.0.2.10: icmp_seq=1 ttl=127 time=0.639 ms
    # 64 bytes from 10.0.2.10: icmp_seq=2 ttl=127 time=0.562 ms
    # 64 bytes from 10.0.2.10: icmp_seq=3 ttl=127 time=0.851 ms
    # 64 bytes from 10.0.2.10: icmp_seq=4 ttl=127 time=0.288 ms
    
  • Webサーバーに対しても ping コマンドを実行できるようにセキュリティグループを更新

    resource "aws_vpc_security_group_ingress_rule" "web-sg-icmp" {
      security_group_id = aws_security_group.web-sg.id
      from_port         = 8
      to_port           = 0
      ip_protocol       = "icmp"
      cidr_ipv4         = "0.0.0.0/0"
      description       = "Allow ICMP from all"
    }
    

6.4 踏み台サーバーを経由してSSHで接続する

*ここでは書籍に倣って踏み台サーバーを配置するが、現在は踏み台サーバーを用いない方法が推奨

  • Webサーバーはインターネットと接続していたため、SSH接続してWebサーバーソフトのインストールを行った。しかしDBサーバーではWebサーバーのみと接続可能でインターネットとは接続されておらず、DBサーバーへ直接ソフトウェアをインストールすることができない。
  • ただしここまでの実装をみると WebサーバーへのSSH接続 -> DBサーバーへのSSH接続 は可能であるため、**「ローカル環境から接続したWebサーバーを踏み台として、ローカル環境からDBサーバーへとアクセスする」**ことができる
  • WebサーバーからDBサーバーへSSH接続するには秘密鍵をWebサーバーに置いておく必要がある
    • DBサーバーにSSH接続用の設定を追加

      resource "aws_instance" "db_server" {
        ami                         = data.aws_ssm_parameter.amazonlinux_2023.value
        instance_type               = "t2.micro"
        subnet_id                   = aws_subnet.private1.id
        associate_public_ip_address = false
        private_ip                  = "10.0.2.10"
        vpc_security_group_ids      = [aws_security_group.db-sg.id]
        key_name                    = aws_key_pair.web-server-key.key_name // SSHアクセス用のキーペアを追加
      
        tags = {
          Name = "aws-network-server-db-server"
        }
      }
      
      resource "aws_vpc_security_group_ingress_rule" "db_sg_ssh" {
        security_group_id = aws_security_group.db-sg.id
        from_port         = 22
        to_port           = 22
        ip_protocol       = "tcp"
        cidr_ipv4         = "10.0.1.10/32" // SSH接続を許可するIPアドレスはWebサーバーのみ"
        description       = "Allow SSH from web server"
      }
      
    • サーバーにファイルを転送するには「SCP(Secure Copy)」や「SFTP(Secure File Transfer Protocol)」というプロトコルを使用する

    • Macの場合、ターミナルから scp コマンドを使って転送する

      • 今回のプロジェクトは検証用であるため、すでに作成済みのWebサーバーへのSSH接続に使用するものを使用する
      • id_ed25519_keyaws_instancekey_pair に設定したものに対応するもの
      $ ssh -i id_ed25519_key [email protected] ## Webサーバーへコピー
      ## WebサーバーへSSH接続後以下実行し、コピーされたファイルを確認
      
      $ ls
      # id_ed25519_key
      
      # 秘密鍵のファイルのパーミッションを変更し、自分しか読めないようにする
      $ chmod 400 id_ed25519_key
      
    • WebサーバーからDBサーバーにSSH接続する

      1. ローカル環境のターミナルからWebサーバーへSSH接続する
      2. WebサーバーからDBサーバーにSSH接続する

Chapter7: NATを構築する

7.1 NATの用途と必要性

NAT ゲートウェイ - Amazon Virtual Private Cloud

NAT ゲートウェイは、ネットワークアドレス変換 (NAT) サービスです。NAT ゲートウェイを使用すると、プライベートサブネット内のインスタンスは VPC 外のサービスに接続できますが、外部サービスはそれらのインスタンスとの接続を開始できません。

  • プライベートサブネットに存在するホスト(サーバーやクライアント)がインターネットにパケットを送信しようとしたとき、NATを介することでパケットの送信元IPアドレスをNAT自身のパブリックIPアドレスに置換することで、送信元は(プライベートIPアドレスではなくNATが持つパブリックIPアドレスで)インターネットにアクセスできるようになる
    • NAT経由で出て行ったアクセスはNATに戻ってくるため、NATは応答パケットの宛先を元のホストのIPアドレスに置換して送信元に転送する
    • NATを用いることでプライベートサブネットからインターネットにアクセスできるようになるが、インターネットからプライベートサブネットに向けて接続はできないため、サブネットにパブリックIPアドレスを割り当ててインターネットとの接続を許すよりもセキュアになる
  • AWSではNATを構築する方法が2種類ある
    • NATインスタンス
      • NATソフトウェアがあらかじめインストールされたAMIから起動したEC2インスタンスを使う方法
      • 性能はEC2インスタンスのスペックによる
    • NATゲートウェイ
      • NAT専用に構成された仮想的なコンポーネントで、配置するサブネットを選択するだけで構成できる
      • 負荷に応じてスケールアップする
      • 時間あたりと転送バイト(GBあたり)の転送量で決められる
        • 無料枠対象外で作成した時間が1時間単位で課金されるので注意

7.2 NATゲートウェイを構築する

  • NATゲートウェイの設置

    // Private SubnetがインターネットにアクセスできるようにするためのNAT Gatewayを作成する
    // [Resource: aws_nat_gateway](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/nat_gateway)
    resource "aws_nat_gateway" "network_server_nat_gateway" {
      subnet_id     = aws_subnet.public1.id // NATゲートウェイを置くサブネットの指定
      allocation_id = aws_eip.network-server-eip.id // IP固定
    }
    
    // IPアドレス固定用にElasticIPを設定
    // [Resource: aws_eip](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eip)
    resource "aws_eip" "network_server_eip" {
      vpc = true
    }
    
  • プライベートサブネットのルートテーブルの更新

    • これまではプライベートサブネットのルートテーブルは、内部でのやり取りしか使用しない前提であったのでデフォルトで作成されたものだけだった
    • NATゲートウェイを介してインターネットにアクセスするため、プライベートサブネットのルートテーブルのデフォルトゲートウェイ(宛先が未知のIPアドレスの場合にパケットを転送する先を設定した「デフォルト」のゲートウェイ)をNATゲートウェイに向ける
      • 0.0.0.0/0 に対してNATゲートウェイのIDを指定することで、プライベートサブネットから出ていくアウトバウンドな通信の際にはNATゲートウェイを中継することになる
      • 従来設定されていた 10.0.0.0/16 はローカルなネットワークで、この場合にはNATゲートウェイには向かない
    // 非公開サブネットのデフォルトゲートをNAT Gatewayに設定する
    resource "aws_route_table" "private_route_table" {
      vpc_id = aws_vpc.VPC.id
    
      route {
        cidr_block = "0.0.0.0/0"
        nat_gateway_id = aws_nat_gateway.network_server_nat_gateway.id
      }
      tags = {
        Name = "aws-network-server-private-route-table"
      }
    }
    
    // 非公開サブネットとルートテーブルの関連付け
    resource "aws_route_table_association" "private_subnet_association" {
      subnet_id      = aws_subnet.private1.id
      route_table_id = aws_route_table.private_route_table.id
    }
    

7.3 NATゲートウェイを通じた疎通確認を行う

  • ここまでの設定で、プライベートサブネットからHTTPやHTTPS通信ができるようになっているはずであるため、DBサーバーにログインしてインターネットへの疎通確認を行う
    1. WebサーバーにSSH接続 → DBサーバーにSSH接続する

    2. DBサーバー上から下記コマンドを実行してインターネットへの疎通確認を行う

      $ curl https://www.kantei.go.jp
      
      • 上記の結果、上記サイトからHTMLが表示されれば、想定通りNATゲートウェイが機能している状態
  • NATゲートウェイを使う必要がなくなったら、NATゲートウェイは削除しておくのが良い
    • NATゲートウェイは稼働時間と転送バイト単位の両方で課金されるため、全く使用しなくても課金対象になる
    • NATゲートウェイを削除すればインスタンス側から勝手にインターネットに接続することもなく、セキュアに保てる
    • NATゲートウェイはコンソール上の削除から削除完了するが、ElasticIPを接続していた場合にはElasticIPが生き続けてしまい課金対象になるので注意。ElasticIPも不要なら個別に削除しておく

Chapter8: DBを用いたブログシステムの構築

8.1 概要

  1. DBサーバーの構築
    • MariaDBをインストールし、WordPressからデータを保存できるようにする
    • WordPressで利用できるのはMySQLかMariaDB。Amazon Linux 2023にMariaDBがパッケージとして含まれているので今回はMariaDBを使用
  2. WebサーバーにWordPressをインストールしブログシステムを構築
  3. WordPressの初期設定

8.2 DBサーバーにMariaDBをインストールする

  • DBサーバーにSSH接続し、MariaDBをインストール

    $ sudo dnf -y install mariadb105-server
    
  • 起動

    $ sudo systemctl start mariadb
    
  • MariaDBを初期化

    $ sudo mysql_secure_installation
    
    • 初期設定で色々と入力求められるが、今回はパスワードの設定と設定パスワードの確認のみ入力し、それ以外はEnterで進める
  • MariaDBにログイン

    $ mysql -u root -p
    
    • root ユーザーでMariaDBに接続する
    • ログイン後、MariaDBのプロンプトが表示される。SQLを実行できる
  • WordPress用のDBとユーザーを作成する

    • wordpress という名前のDBを作成

      create database wordpress default character set utf8 collate utf8_general_ci;
      
    • wordpress というユーザーを作成。今回はすべての権限を持たせる

      grant all on wordpress.* to wordpress@"%" identified by 'wordpresspasswd';
      
      • wordpress@"%" 部分が作成するユーザー。 % はすべてのホスト = どこからでも接続できることを示す
      • wordpresspasswd はユーザー wordpress のパスワード
    • 権限の反映

      flush privileges;
      
    • 作成したユーザーの確認

      select user, host from mysql.user;
      
      • 出力で作成したユーザーが表示されればOK
    • 作成したデータベースの確認

      show database;
      
      • 出力で作成したデータベース( wordpress )が表示されればOK
    • DBサーバー起動時に自動でMariaDBも起動するようにする

      • DBサーバー上で以下を実行する
      $ sudo systemctl enable mariadb 
      

8.3 WebサーバーにWordPressをインストールする

  • PHPに最新版をインストールする

    $ sudo dnf -y install php8.1
    
  • WordPressに必要なライブラリ等のインストール

    $ sudo dnf -y install php8.1-mbstring php-mysqli
    
  • WebサーバーからDBサーバー上のMariaDBへの疎通確認

    • MariaDBを操作するためにMariaDBのコマンドをインストール

      $ sudo dnf -y install mariadb105-server
      
    • WebサーバーからMaridDBに疎通確認

      $ mysql -h 10.0.2.10 -u wordpress -p
      
  • WordPressのダウンロード

    • dnf コマンドでインストールできないので、WorPress日本語版ページからダウンロードする

      • 以下Webサーバー上で実行しダウンロードする

        $ cd ~
        $ wget https://ja.wordpress.org/latest-ja.tar.gz
        
    • ダウンロードしたファイルの展開とインストール

      $ tar xzvf latest-ja.tar.gz ## 展開
      
    • 展開したファイルを、Apacheから見える場所に配置する

      $ sudo cp -r wordpress/* /var/www/html/
      
      • デフォルトの構成では /var/www/html ディレクトリがApacheなどのWebサーバーがホストするウェブサイトのデフォルトルート
      • cp -r コマンドで再帰的(recursive)にコピーを行います。このオプションがないと、ディレクトリ全体をコピーできない
    • コピーしたファイル群の所有者/グループを apache/apache に変更する

      $ sudo chown apache:apache /var/www/html -R
      
      • コピーしたWordPressファイルをApache Webサーバーが適切にアクセスできるようする。Apacheがコンテンツにアクセスするには、ディレクトリ及びファイルの所有者またはグループにApacheが設定されている必要があるため、所有者/グループを変こすうる
      • コマンドについて
        • chown: ファイルやディレクトリの所有者を変更するコマンド
        • apache:apache:
          • 所有者(ユーザー)を apache に、グループを apache に設定する。所有権を変更しない場合、Apacheがファイルを読み取れず正しく動作しない
          • apache は一般的なLinuxディストリビューション(例: CentOSやRHEL)で使用されるApache Webサーバーのデフォルトユーザー
        • /var/www/html: 所有者を変更する対象のディレクトリ
        • -R:
          • 再帰的(recursive)に処理します。このオプションを指定することで、/var/www/html 内のすべてのファイルとディレクトリの所有者を変更する

8.4 WordPressを設定する

  1. Apacheの起動

    • 起動済みの場合、再起動しないとインストールしたPHPが有効にならない
    $ sudo systemctl start httpd # 起動
    $ sudo systemctl restart httpd # 再起動
    
  2. WordPressの初期設定をする

    • WebサーバーのパブリックDNSにアクセスする

    • データベースの情報を設定

      データベース名: wordpress // 設定した値
      ユーザー名: wordpress // 設定した値 
      パスワード: wordpresspasswd // 設定した値
      データベースのホスト名: 10.0.2.10 // DBを配置しているEC2インスタンスのIP
      テーブル接頭辞: `wp_` // デフォルト
      
    • 設定したら「インストール実行」をクリック

  3. サイトのタイトルや管理者情報の入力

    • 画面に従い、サイトタイトルやユーザー名、パスワード、メールアドレスを入力
    • 今回は学習用途のため「検索エンジンへのインデックスは行わない」にチェック
    • インストール後、デフォルトでは「/wp-admin」にアクセスすると管理画面ログインに進む(ログイン済みなら管理画面を表示)
    • パブリックDNSでアクセスするとWordPressで構築された初期状態のサイトにアクセスできる

最終的な構成

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }

  required_version = ">= 1.2.0"
}

provider "aws" {
  region = "ap-northeast-1"
}

// [Resource: aws_vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc)
resource "aws_vpc" "VPC" {
  cidr_block           = "10.0.0.0/16" // CIDR表記で、今回はネットワークのビット長を16ビットに設定
  enable_dns_hostnames = true
  tags = {
    Name = "aws-network-server-VPC"
  }
}

// 公開用サブネット
// [Resource: aws_subnet](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet)
resource "aws_subnet" "public1" {
  vpc_id     = aws_vpc.VPC.id
  cidr_block = "10.0.1.0/24"
  # map_public_ip_on_launch = true // サブネットに起動したインスタンスにパブリックIPアドレスを割り当てる場合はtrueを指定する。デフォルトはfalse。インスタンスの設定で associate_public_ip_address = true を指定すると、この設定を上書き
  tags = {
    Name = "aws-network-server-subnet-public1"
  }
}

// 非公開用サブネット
// [Resource: aws_subnet](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet)
resource "aws_subnet" "private1" {
  vpc_id     = aws_vpc.VPC.id
  cidr_block = "10.0.2.0/24"
  tags = {
    Name = "aws-network-server-subnet-private1"
  }
}

// [Resource: aws_internet_gateway](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/internet_gateway)
resource "aws_internet_gateway" "test_internet_gateway" {
  vpc_id = aws_vpc.VPC.id
  tags = {
    Name = "aws-network-server-test-internet-gateway"
  }
}

// [Resource: aws_route_table](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table#example-usage)
resource "aws_route_table" "public_route_table" {
  vpc_id = aws_vpc.VPC.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.test_internet_gateway.id
  }
  tags = {
    Name = "aws-network-server-public-route-table"
  }
}

// 公開用サブネットとルートテーブルの関連付け
// [Resource: aws_route_table_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association)
resource "aws_route_table_association" "public_subnet_association" {
  subnet_id      = aws_subnet.public1.id
  route_table_id = aws_route_table.public_route_table.id
}

// 非公開サブネットのデフォルトゲートをNAT Gatewayに設定する
resource "aws_route_table" "private_route_table" {
  vpc_id = aws_vpc.VPC.id

  route {
    cidr_block = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.network_server_nat_gateway.id
  }
  tags = {
    Name = "aws-network-server-private-route-table"
  }
}

// 非公開サブネットとルートテーブルの関連付け
resource "aws_route_table_association" "private_subnet_association" {
  subnet_id      = aws_subnet.private1.id
  route_table_id = aws_route_table.private_route_table.id
}

// AMIの一覧取得: aws ssm get-parameters-by-path --path /aws/service/ami-amazon-linux-latest --query 'Parameters[].Name'
data "aws_ssm_parameter" "amazonlinux_2023" {
  name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64" // Amazon Linux 2023の最新のAMI IDを取得
}

// 公開用サブネットにEC2インスタンスを作成する
// [Resource: aws_instance](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance)
resource "aws_instance" "web_server" {
  ami                         = data.aws_ssm_parameter.amazonlinux_2023.value
  instance_type               = "t2.micro"
  private_ip                  = "10.0.1.10"                          // 指定しない場合、パブリックサブネットに割り当てた「10.0.1.0~10.0.1.255」の範囲内でインスタンス起動時に自動で割り当てられる。今回は手動で指定
  subnet_id                   = aws_subnet.public1.id                // 外部公開用サブネットにインスタンスを配置
  associate_public_ip_address = true                                 // 外部からのアクセス用にパブリックIPを自動で割り当てる
  vpc_security_group_ids      = [aws_security_group.web_sg.id]       // EC2インスタンスに適用するセキュリティグループを指定
  key_name                    = aws_key_pair.web_server_key.key_name // EC2インスタンスに適用するキーペアを指定.SSH接続を行うために必要
  // 以下のユーザーデータを実行することで、インスタンス起動時にApacheをインストールし、起動する
  user_data = <<-EOF
              #!/bin/bash
              sudo dnf update -y
              sudo dnf install -y httpd
              sudo systemctl start httpd.service
              sudo systemctl enable httpd.service
              EOF
  tags = {
    Name = "aws-network-server-web-server"
  }
}

// 上記で生成したインスタンスで利用する仮想ディスク(EBS: Elastic Block Store)を作成する
resource "aws_ebs_volume" "web_server_volume" {
  availability_zone = aws_instance.web-server.availability_zone
  size              = 8
  type              = "gp2" // default: gp2
  tags = {
    Name = "aws-network-server-web-server-volume"
  }
}

// 作成したインスタンスとEBSを紐付ける
// [Resource: aws_volume_attachment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/volume_attachment)
resource "aws_volume_attachment" "web_server_attachment" {
  device_name = "/dev/sdh"
  instance_id = aws_instance.web_server.id
  volume_id   = aws_ebs_volume.web_server_volume.id
}

// 非公開用サブネットにEC2インスタンスを作成する
resource "aws_instance" "db_server" {
  ami                         = data.aws_ssm_parameter.amazonlinux_2023.value
  instance_type               = "t2.micro"
  subnet_id                   = aws_subnet.private1.id
  associate_public_ip_address = false
  private_ip                  = "10.0.2.10"
  vpc_security_group_ids      = [aws_security_group.db_sg.id]
  key_name                    = aws_key_pair.web_server_key.key_name

  tags = {
    Name = "aws-network-server-db-server"
  }
}

resource "aws_ebs_volume" "db_server_volume" {
  availability_zone = aws_instance.db_server.availability_zone
  size              = 8
  type              = "gp2"
  tags = {
    Name = "aws-network-server-db-server-volume"
  }
}

resource "aws_volume_attachment" "db_server_attachment" {
  device_name = "/dev/sdh"
  instance_id = aws_instance.db_server.id
  volume_id   = aws_ebs_volume.db_server_volume.id
}

// このリソースで生成された秘密鍵は、Terraformのステートファイルに暗号化されずに保存される。
// 今回の実装では本運用しない設定のため、`tls_private_key`を使用しているが、本運用の際にはセキュアな方法で秘密鍵を管理する
// [tls_private_key (Resource)](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key)
resource "tls_private_key" "web_server_key" {
  algorithm = "ED25519" // RSAだとキーの長さ制限にかかることがあるため、ED25519を使用
}

// 上記で生成した秘密鍵をローカルファイルに保存する
// [local_file (Resource)](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file)
resource "local_file" "private" {
  filename        = "id_ed25519_aws_network_server"
  content         = tls_private_key.web_server_key.private_key_openssh
  file_permission = "0600"
}

// 上記で生成した秘密鍵を使用して、AWSのキーペアを作成する。これをEC2インスタンスに紐付けることで、SSH接続を行うことができるようにする
// [Resource: aws_key_pair](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/key_pair)
resource "aws_key_pair" "web_server_key" {
  key_name   = "by_terraform"
  public_key = tls_private_key.web_server_key.public_key_openssh
}

// [Resource: aws_security_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group)
resource "aws_security_group" "web_sg" {
  name        = "web-sg"
  description = "Managed by Terraform!"
  vpc_id      = aws_vpc.VPC.id // セキュリティグループを作成する対象のVPCを指定
  tags = {
    Name = "aws-network-server-web-sg"
  }
}

// ec2インスタンスにssh接続を許可するためのセキュリティグループルールを設定する。resourceにはaws_security_group_ruleを使用しないこと
// [Resource: aws_vpc_security_group_ingress_rule](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule)
resource "aws_vpc_security_group_ingress_rule" "web_sg_ssh" {
  security_group_id = aws_security_group.web_sg.id
  from_port         = 22
  to_port           = 22
  ip_protocol       = "tcp"
  cidr_ipv4         = "XXX.XXX.XX.XXX/32" // SSH接続を許可するIPアドレスを指定.全てのIPアドレスを許可する場合は"0.0.0.0/0"を指定
  description       = "Allow SSH from my IP"
}

// ec2インスタンスにhttp接続を許可するためのセキュリティグループルールを設定する。resourceにはaws_security_group_ruleを使用しないこと
resource "aws_vpc_security_group_ingress_rule" "http" {
  security_group_id = aws_security_group.web_sg.id
  from_port         = 80
  to_port           = 80
  ip_protocol       = "tcp"
  cidr_ipv4         = "0.0.0.0/0" // すべてのIPアドレスからのアクセスを許可
  description       = "Allow HTTP from all"
}

resource "aws_vpc_security_group_ingress_rule" "web_sg_icmp" {
  security_group_id = aws_security_group.web_sg.id
  from_port         = 8
  to_port           = 0
  ip_protocol       = "icmp"
  cidr_ipv4         = "0.0.0.0/0"
  description       = "Allow ICMP from all"
}

/**
 * ec2インスタンスから外部への通信を許可するためのセキュリティグループルールを設定する。resourceにはaws_security_group_ruleを使用しないこと
 * セキュリティグループは、デフォルトですべてのアウトバウンド通信を許可することもあり、書籍上では設定する記述はなかったが、
 * `aws_vpc_security_group_ingress(egress)_rule`を使って個別にルールを設定すると、セキュリティグループのデフォルトルールが無効化されるため、追加設定が必要
 * この設定を付与することで、dnfコマンドを使用して、外部からhttpsなどWebサーバーソフトウェアをインストールすることができる
 *
 * [Resource: aws_vpc_security_group_egress_rule](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule)
 */
resource "aws_vpc_security_group_egress_rule" "web_all_traffic_rule" {
  security_group_id = aws_security_group.web_sg.id
  from_port         = -1          // すべてのポート(0から65535まで)を許可。
  to_port           = -1          // すべてのポート(0から65535まで)を許可。
  ip_protocol       = "-1"        // すべてのプロトコル
  cidr_ipv4         = "0.0.0.0/0" // すべての宛先に送信を許可
  description       = "Allow all outbound traffic"
}

resource "aws_security_group" "db-sg" {
  name        = "db-sg"
  description = "Managed by Terraform!"
  vpc_id      = aws_vpc.VPC.id
  tags = {
    Name = "aws-network-server-db-sg"
  }
}

resource "aws_vpc_security_group_ingress_rule" "db_sg_ssh" {
  security_group_id = aws_security_group.db_sg.id
  from_port         = 22
  to_port           = 22
  ip_protocol       = "tcp"
  cidr_ipv4         = "10.0.1.10/32" // SSH接続を許可するIPアドレスはWebサーバーのみ
  description       = "Allow SSH from web server"
}

resource "aws_vpc_security_group_ingress_rule" "db_sg_mariadb" {
  security_group_id = aws_security_group.db-sg.id
  from_port         = 3306 // MariaDBのデフォルトポート
  to_port           = 3306 // MariaDBのデフォルトポート
  ip_protocol       = "tcp"
  cidr_ipv4         = "0.0.0.0/0"
  description       = "Allow MySQL from all"
}

resource "aws_vpc_security_group_ingress_rule" "db_sg_icmp" {
  security_group_id = aws_security_group.db_sg.id
  from_port         = 8
  to_port           = 0
  ip_protocol       = "icmp"
  cidr_ipv4         = "0.0.0.0/0"
  description       = "Allow ICMP from all"
}

resource "aws_vpc_security_group_egress_rule" "db_all_traffic_rule" {
  security_group_id = aws_security_group.db_sg.id
  from_port         = -1          // すべてのポート(0から65535まで)を許可。
  to_port           = -1          // すべてのポート(0から65535まで)を許可。
  ip_protocol       = "-1"        // すべてのプロトコル
  cidr_ipv4         = "0.0.0.0/0" // すべての宛先に送信を許可
  description       = "Allow all outbound traffic"
}

// Private SubnetがインターネットにアクセスできるようにするためのNAT Gatewayを作成する
// [Resource: aws_nat_gateway](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/nat_gateway)
resource "aws_nat_gateway" "network_server_nat_gateway" {
  subnet_id     = aws_subnet.public1.id
  allocation_id = aws_eip.network_server_eip.id
}

// [Resource: aws_eip](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eip)
resource "aws_eip" "network_server_eip" {
  vpc = true
}

関連リンク