Terraformでのssh接続できる基礎的ネットワークの構築

記事タイトルとURLをコピーする

こんにちは。ディベロップメントサービス2課の長谷です。 今回はTerraformでの基礎的ネットワークの構築と構築環境にssh接続するまでの流れをご紹介したいと思います。

Terraformで作成するリソースの構成図

自分のPCからEC2インスタンスにSSH接続できるようにする環境のイメージです。 VPCを作成してその中にEC2インスタンスを起動し、そのインスタンスにインターネット経由でアクセスできる環境を構築します。

ディレクトリ構造

今回は実際のプロジェクトを考え、管理面やチームでの作業効率の観点からディレクトリを構造化してみました。 ディレクトリ構造は以下になります。

terraform_handson
│ 
├── environments
│   ├── dev
│   │   └── main.tf
│   ├── prd
│   └── stg
├── modules
    ├── ec2
    │   ├── main.tf
    │   ├── output.tf
    │   └── variables.tf
    ├── vpc
        ├── main.tf
        ├── output.tf
        └── variables.tf       

実行環境

本記事に記載している手順は以下の環境にて実施しています。

  • OSバージョン:Amazon Linux 2023
    • VS CodeからSSHで既存のEC2に接続し、その環境上で今回紹介する構築を実行しています。
  • AWS CLIバージョン:2.9.19
  • Terraformバージョン:1.6.2
  • AWS Providerバージョン:5.37.0

AWS Providerについては下記をご参照ください。 registry.terraform.io

環境準備

AWS CLIをインストール

インストール完了後、ターミナルでaws --versionを実行し、AWS CLIが使用できることを確認します。 下記のように、AWS CLIのバージョンが表示されればインストール完了です。

>aws --version
aws-cli/2.9.19 Python/3.9.16 Linux/6.1.21-1.45.amzn2023.x86_64 source/x86_64.amzn.2023 prompt/off

IAMユーザーとアクセスキーの作成

今回はテスト用として管理者権限(AdministratorAccess)を持つユーザーを新規に作成します。 既存で使用できるユーザーがあればそちらを使用して問題ありません。

IAMユーザーの作成が完了したら、この後のステップでアクセスキー IDとシークレットアクセスキーが必要になりますので、作成します。 作成が完了したら、参照できるようにテキストエディタなどにコピペして保存しておきます。

AWS CLI 認証情報の設定

ターミナルでaws configureを実行し、AWS CLI 認証情報を設定します。

>aws configure
AWS Access Key ID [None]: 上記で作成した [アクセスキー ID]
AWS Secret Access Key [None]: 上記で作成した [シークレットアクセスキー]
Default region name [None]: ap-northeast-1
Default output format [None]: 何も入力せずにEnterキーを押下

次にaws configure listを実行し、AWS CLI 認証情報が設定されたことを確認します。

>aws configure list
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     ****************QS6W shared-credentials-file    
secret_key     ****************/TKD shared-credentials-file    
    region           ap-northeast-1      config-file    ~/.aws/config

念のため、aws iam get-userも実行し、IAMユーザー情報も正常に設定されているか確認します。

>aws iam get-user
{
    "User": {
        "Path": "/",
        "UserName": "UserName",
        "UserId": "xxxxxxxxxxxxxxxxx",
        "Arn": "arn:aws:iam::xxxxxxxxxxxx:user/UserName",
        "CreateDate": "2023-03-07T09:32:27+00:00",
        "PasswordLastUsed": "2024-03-22T06:13:57+00:00"
    }
}

Terraformのインストール

下記の公式サイトにアクセスし、Linux > Amazon Linux 記載されているコマンドを実行してTerraformをインストールします。 (WindowsやmacOSを使用されている方は自身の環境にあったパッケージをダウンロードしてください。)

developer.hashicorp.com

ターミナルにて、terraform -vを実行してインストールされていることを確認します。

>terraform -v
Terraform v1.6.2
on linux_amd64

.tfファイルの作成

ディレクトリ構造に従って下記の.tfファイルを作成してください。

main.tf(root)

main.tf(root) (path:terraform_handson/environments/dev/main.tf)

各モジュールの定義と、外部からモジュールに渡す変数の定義等を記述します

locals {
  prefix = "terraform_handson"
}

#==========================================#
# Base Network Resource
#==========================================#
module "vpc" {
  source = "../../modules/vpc"
  prefix = local.prefix
}

module "ec2" {
  source                = "../../modules/ec2"
  vpc_id                = module.vpc.id
  subnet_id             = module.vpc.a_id
  prefix                = local.prefix
  bastion_ami           = "ami-020283e959651b381"
}

EC2モジュール

main.tf (path:terraform_handson/modules/ec2/main.tf)

#==========================================#
# EC2
#==========================================#

resource "aws_instance" "bastion" {
  ami                         = var.bastion_ami
  instance_type               = "t2.micro"
  disable_api_termination     = false
  subnet_id                   = var.subnet_id
  key_name                    = "dev-linux"
  vpc_security_group_ids      = [aws_security_group.bastion.id]

  tags = {
    Name = "${var.prefix}-bastion"
  }
}

#==========================================#
# Security Group
#==========================================#

resource "aws_security_group" "bastion" {
  name        = "${var.prefix}-bastion-sg"
  description = "Security group for EC2"
  vpc_id      = var.vpc_id

  tags = {
    Name = "${var.prefix}-bastion"
  }

  ingress {
    from_port   = "22"
    protocol    = "tcp"
    to_port     = "22"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

今回、キーペアは既存のもの(dev-linux)を使用しています。 ご自身で作成していただき、key_nameにキーペア名を記載してください。 新規キーペアを作成する設定については、今後別の記事でご紹介できればと思います。

variables.tf (path:terraform_handson/modules/ec2/variables.tf)

main.tf(root)で定義した変数を受け取るように設定します。

variable "vpc_id" {
  type        = string
  description = "vpc id"
}

variable "subnet_id" {
  type        = string
  description = "subnet id"
}

variable "prefix" {
  type        = string
  description = "resource prefix"
}

variable "bastion_ami" {
  type        = string
  description = "resource prefix"
}

VPCモジュール

main.tf (path:terraform_handson/modules/vpc/main.tf)

#==========================================#
# VPC
#==========================================#

resource "aws_vpc" "main" {
  cidr_block                       = "10.1.0.0/16"
  enable_dns_support               = "true"
  enable_dns_hostnames             = "true"
  instance_tenancy                 = "default"
  assign_generated_ipv6_cidr_block = "false"

  tags = {
    Name = "${var.prefix}-main"
  }
}

#==========================================#
# Internet Gateway
#==========================================#

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "${var.prefix}-main"
  }
}

#==========================================#
# Public Subnet
#==========================================#

resource "aws_subnet" "public_a" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.1.30.0/24"
  map_public_ip_on_launch = true
  availability_zone       = "ap-northeast-1a"

  tags = {
    Name = "${var.prefix}-public-a"
  }
}

#==========================================#
# Route Table
#==========================================#

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  tags = {
    "Name" = "${var.prefix}-public"
  }
}

resource "aws_route" "default" {
  route_table_id         = aws_route_table.public.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.main.id
}

resource "aws_route_table_association" "public_a" {
  route_table_id = aws_route_table.public.id
  subnet_id      = aws_subnet.public_a.id
}

outputs.tf (path:terraform_handson/modules/vpc/outputs.tf)

他のモジュールに変数を共有するためoutputsで設定します。 ここで設定した変数はmain.tf(root)を中継し、必要とする各モジュールへと渡されます。

output "id" {
  value = aws_vpc.main.id
}

output "a_id" {
  value = aws_subnet.public_a.id
}

variables.tf (path:terraform_handson/modules/vpc/variables.tf)

variable "prefix" {
  type        = string
  description = "resource prefix"
}

各リソースの作成

.tfファイルの作成が完了したら、Terraformのコマンドを使用してリソース作成します。

terraform init

Terraformの設定に必要なプラグインやモジュールをダウンロードし、Terraformの環境を初期化します。

コマンド実行ディレクトリ:terraform_handson/environments/dev

>terraform init
Initializing the backend...
Initializing modules...

Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Using previously-installed hashicorp/aws v5.37.0

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

terraform fmt

.tfファイルの中身を自動的に整形してくれます。 -recursive を付けることで、.tfファイル全体にフォーマットをかけることができます。

コマンド実行ディレクトリ:terraform_handson

>terraform fmt -recursive

terraform validate

.tfファイルの構文と設定の妥当性をチェックをしてくれます。

コマンド実行ディレクトリ:terraform_handson/environments/dev

>terraform validate
Success! The configuration is valid.

terraform plan

Terraformがリソースを作成、変更、削除する前に実行計画を確認できます。

コマンド実行ディレクトリ:terraform_handson/environments/dev

>terraform plan
module.vpc.aws_vpc.main: Refreshing state... [id=xxxxxxxxxxxxxxxxx]
module.public_subnet.aws_route.default: Refreshing state... [id=xxxxxxxxxxxxxxxxx]
module.public_subnet.aws_route_table.public: Refreshing state... [id=xxxxxxxxxxxxxxxxx]
module.public_subnet.aws_route_table_association.public_a: Refreshing state... [id=xxxxxxxxxxxxxxxxx]
module.security_group.aws_security_group.bastion: Refreshing state... [id=xxxxxxxxxxxxxxxxx]
module.internet_gateway.aws_internet_gateway.main: Refreshing state... [id=xxxxxxxxxxxxxxxxx]
module.public_subnet.aws_subnet.public_a: Refreshing state... [id=xxxxxxxxxxxxxxxxx]
module.ec2.aws_instance.bastion: Refreshing state... [id=xxxxxxxxxxxxxxxxx]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create
  - destroy
-/+ destroy and then create replacement

Terraform will perform the following actions:

以下略

terraform apply

実際にリソースの追加・更新・削除が実行されます。 Enter a value:が表示されたあとにyesを入力してEnterキーを押下すると、リソースの作成が開始されます。

コマンド実行ディレクトリ:terraform_handson/environments/dev

>terraform apply

略

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value:

略

Apply complete! Resources: 8 added, 0 changed, 0 destroyed.

EC2にSSH接続

作成したEC2のパブリックIPアドレスにSSH接続してみます。 SSH接続時に使用する秘密鍵は、EC2モジュールでも述べた既存のカギを使用します。

以下のように、EC2に接続できれば完了です。

各リソースの削除 

terraform destroy

これまでに作成したリソースはterraform destroyで一括して削除できます。 こちらもapplyと同様に、コマンドを実行するとEnter a value:が表示されますので、yesを入力してEnterキーを押下します。

コマンド実行ディレクトリ:terraform_handson/environments/dev

>terraform destroy

略

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value:

略

Destroy complete! Resources: 8 destroyed.

さいごに

以上がTerraformを使った基本的なネットワーク構築の一通りの流れでした。 今後も、初心者でも試せる手法や簡単なリソースの構築などを発信していきますので、引き続きご覧いただければ幸いです!

長谷悠大(執筆記事の一覧)

アプリケーションサービス部

猫2匹と暮らしています