⛈️

Terraform:GCPでプロキシサーバー構築の自動化を試した

2025/01/06に公開

はじめに

前回、GCP上でプロキシサーバーを構築し、後段のバックエンドへリクエストを行うモックを作ってみました。
そこで、Terraformをつかって自動で環境構築できるよう試してみました。

https://zenn.dev/takudooon/articles/0db76368acaa62

1

コード全体

Terraformで実行する設定の流れは下記のようになっています。

  1. GCP上でプロジェクト作成(ここだけ手動)
    a. プロキシサーバーとバックエンドサーバー用に2つ作成
  2. VPCネットワークの作成
  3. VPCネットワークピアリングを設定
  4. firewallの設定を追加
  5. VMインスタンスの作成
    a. プロキシサーバー側では、Nginxのインストールと設定
    b. バックエンドサーバー側では、Node.jsのインストールと簡易なバックエンドサーバーの実装
コード全体

# 定数
locals {
   project_name_1 = "test-project-441812"
   project_name_2 = "test-reverse-proxy-441812"
   vpc_network_name_1 = "backend-server-network"
   vpc_network_name_2 = "proxy-server-network"
   subnet_name_1 = "backend-server-subnet"
   subnet_ip_1 = "10.1.0.0/24"
   subnet_name_2 = "proxy-server-subnet"
   subnet_ip_2 = "10.0.0.0/24"
   region = "us-central1"
}

# VPCネットワーク1 作成
resource "google_compute_network" "vpc_network_1" {
  name = local.vpc_network_name_1
  project =  local.project_name_1
  auto_create_subnetworks = false
}

# サブネット1 作成
resource "google_compute_subnetwork" "subnet_1" {
  name =  local.subnet_name_1
  project = local.project_name_1
  ip_cidr_range = local.subnet_ip_1
  region = local.region
  network = google_compute_network.vpc_network_1.id
}

# VPCネットワーク2 作成
resource "google_compute_network" "vpc_network_2" {
  name = local.vpc_network_name_2
  project =  local.project_name_2
  auto_create_subnetworks = false
}

# サブネット2 作成
resource "google_compute_subnetwork" "subnet_2" {
  name =  local.subnet_name_2
  project = local.project_name_2
  ip_cidr_range = local.subnet_ip_2
  region = local.region
  network = google_compute_network.vpc_network_2.id
}

# VPC peering
resource "google_compute_network_peering" "peering_1" {
  name = "peering-1"
  network = google_compute_network.vpc_network_1.self_link
  peer_network = google_compute_network.vpc_network_2.self_link
}

resource "google_compute_network_peering" "peering_2" {
  name = "peering-2"
  network = google_compute_network.vpc_network_2.self_link
  peer_network = google_compute_network.vpc_network_1.self_link
}

# firewall settings
resource "google_compute_firewall" "firewall_backend_ssh" {
  project = local.project_name_1
  name = "firewall-backend-ssh"
  network = google_compute_network.vpc_network_1.name

  allow {
    protocol = "tcp"
    ports = ["22"]
  }

  source_tags = ["ssh-backend-server"]
  source_ranges = ["0.0.0.0/0"]
}

resource "google_compute_firewall" "firewall_proxy_server_ssh" {
  project = local.project_name_2
  name = "firewall-proxy-server-ssh"
  network = google_compute_network.vpc_network_2.name

  allow {
    protocol = "tcp"
    ports = ["22"]
  }

  source_tags = ["ssh-proxy-server"]
  source_ranges = ["0.0.0.0/0"]
}

resource "google_compute_firewall" "firewall_backend" {
  project = local.project_name_1
  name = "firewall-backend"
  network = google_compute_network.vpc_network_1.name

  allow {
    protocol = "tcp"
    ports = ["8080"]
  }

  source_tags = ["tcp-backend-server"]
  source_ranges = ["0.0.0.0/0"]
}

resource "google_compute_firewall" "firewall_proxy_server" {
  project = local.project_name_2
  name = "firewall-proxy-server"
  network = google_compute_network.vpc_network_2.name

  allow {
    protocol = "tcp"
    ports = ["80"]
  }

  source_tags = ["tcp-proxy-server"]
  source_ranges = ["0.0.0.0/0"]
}

# create vm instace
resource "google_compute_instance" "backend_server" {
  name         = "vm-backend-server"
  machine_type = "e2-micro"
  zone         = "us-central1-a"
  project =  local.project_name_1

  boot_disk {
    initialize_params {
      image = "ubuntu-minimal-2210-kinetic-amd64-v20230126"
    }
  }

  tags = [ "tcp-backend-server", "ssh-backend-server" ]

  network_interface {
    network = google_compute_network.vpc_network_1.name
    subnetwork = google_compute_subnetwork.subnet_1.name
    subnetwork_project = local.project_name_1
    access_config {}
  }
  
  # 公開鍵を設定
  metadata = {
    ssh-keys = "{$USER_NAME}:ssh-rsa {$PUBLIC_KEY}"
  }

  provisioner "remote-exec" {
    connection {
      type = "ssh"
      # SSH接続を行うユーザー名
      user = "{$USER_NAME}"
      # 秘密鍵を設定
      private_key = file("./sshkey/google_compute_engine_for_terraform")
      host = self.network_interface.0.access_config.0.nat_ip
      timeout = "10m"
    }

    # Node.jsのインストールと簡易なバックエンドサーバーの実装
    inline = [
      # Node.jsをインストール
      "curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash",
      "export NVM_DIR=\"$HOME/.nvm\"",
      "[ -s \"$NVM_DIR/nvm.sh\" ] && . \"$NVM_DIR/nvm.sh\"",
      "nvm install 23",
      "node -v",
      "npm -v",
      "mkdir sample", 
      "npm init -y", # 自動的にデフォルト設定でpackage.jsonを作成
      "npm install express",
      "npm install pm2 -g",
      # 簡易なバックエンドサーバーの実装
      "echo \"const express = require('express')\" > sample/index.js",
      "echo \"const app = express()\" >> sample/index.js",
      "echo \"const port = 8080\" >> sample/index.js",
      "echo \"app.get('/', (req, res) => { res.send('Hello World!') })\" >> sample/index.js",
      "echo \"app.listen(port, () => { console.log(`Example app listening on port `) }) \" >> sample/index.js",
      # サーバーの永続化
      "pm2 start sample/index.js --name my-app",
      "pm2 startup",
      "pm2 save"
    ]
  }
}

resource "google_compute_instance" "proxy_server" {
  name         = "vm-proxy-server"
  machine_type = "e2-micro"
  zone         = "us-central1-a"
  project =  local.project_name_2

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-11"
    }
  }

  tags = [ "tcp-proxy-server", "ssh-proxy-server" ]

  network_interface {
    network = google_compute_network.vpc_network_2.name
    subnetwork = google_compute_subnetwork.subnet_2.name
    subnetwork_project = local.project_name_2
    access_config {}
  }

  # 公開鍵を設定
  metadata = {
    ssh-keys = "{$USER_NAME}:ssh-rsa {$PUBLIC_KEY}"
  }

  provisioner "remote-exec" {
    connection {
      type = "ssh"
      # SSH接続を行うユーザー名
      user = "{$USER_NAME}"
      # 秘密鍵を設定
      private_key = file("./sshkey/google_compute_engine_for_terraform")
      host = self.network_interface.0.access_config.0.nat_ip
      timeout = "10m"
    }

    # Nginxのインストールとプロキシサーバーの実装
    inline = [
      # Nginxのインストール
      "echo \" IP ADDRESS ${google_compute_instance.proxy_server.network_interface.0.access_config.0.nat_ip}\"",
      "sudo apt install -y curl gnupg2 ca-certificates lsb-release debian-archive-keyring",
      "curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null",
      "gpg --dry-run --quiet --no-keyring --import --import-options import-show /usr/share/keyrings/nginx-archive-keyring.gpg",
      "echo \"deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/debian `lsb_release -cs` nginx\" | sudo tee /etc/apt/sources.list.d/nginx.list",
      "echo \"Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n\" | sudo tee /etc/apt/preferences.d/99nginx | sudo apt install -y nginx", 
      "sudo apt update",
      "sudo apt install -y nginx",
      # パスを通す
      "echo 'export PATH=$PATH:/usr/sbin' | sudo tee -a /etc/profile",
      # Nginxの起動
      "sudo systemctl start nginx",
      # proxy serverの実装
      "sudo touch /etc/nginx/conf.d/costom.conf",
      "echo \"server{\" | sudo tee -a /etc/nginx/conf.d/costom.conf > /dev/null",
      "echo \"listen 80;\" | sudo tee -a /etc/nginx/conf.d/costom.conf > /dev/null",
      "echo \"server_name ${google_compute_instance.proxy_server.network_interface.0.access_config.0.nat_ip};\" | sudo tee -a /etc/nginx/conf.d/costom.conf > /dev/null",
      "echo \"proxy_set_header Host \\$host;\" | sudo tee -a /etc/nginx/conf.d/costom.conf > /dev/null",
      "echo \"proxy_set_header X-Real-IP \\$remote_addr;\" | sudo tee -a /etc/nginx/conf.d/costom.conf > /dev/null",
      "echo \"proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\" | sudo tee -a /etc/nginx/conf.d/costom.conf > /dev/null",
      "echo \"proxy_set_header X-Forwarded-Proto \\$scheme;\" | sudo tee -a /etc/nginx/conf.d/costom.conf > /dev/null",
      "echo \"location /sample/ { proxy_pass http://${google_compute_instance.backend_server.network_interface.0.network_ip}:8080/; }\" | sudo tee -a /etc/nginx/conf.d/costom.conf > /dev/null",
      "echo \"}\" | sudo tee -a /etc/nginx/conf.d/costom.conf > /dev/null",
      # Nginxの再起動
      "sudo nginx -s reload",
    ]
  }
}

詳細

定数の設定

定数はlocalsで管理してます

# 定数
locals {
   project_name_1 = "test-project-441812"
   project_name_2 = "test-reverse-proxy-441812"
   vpc_network_name_1 = "backend-server-network"
   vpc_network_name_2 = "proxy-server-network"
   subnet_name_1 = "backend-server-subnet"
   subnet_ip_1 = "10.1.0.0/24"
   subnet_name_2 = "proxy-server-subnet"
   subnet_ip_2 = "10.0.0.0/24"
   region = "us-central1"
}

VPCネットワークの作成

VPCネットワークとサブネットの作成は下記のとおりです。

# VPCネットワーク1 作成
resource "google_compute_network" "vpc_network_1" {
  name = local.vpc_network_name_1
  project =  local.project_name_1
  auto_create_subnetworks = false
}

# サブネット1 作成
resource "google_compute_subnetwork" "subnet_1" {
  name =  local.subnet_name_1
  project = local.project_name_1
  ip_cidr_range = local.subnet_ip_1
  region = local.region
  network = google_compute_network.vpc_network_1.id
}

VPCピアリング

プロキシサーバーとバックエンドサーバーを相互にピアリングします。

# VPC peering
resource "google_compute_network_peering" "peering_1" {
  name = "peering-1"
  network = google_compute_network.vpc_network_1.self_link
  peer_network = google_compute_network.vpc_network_2.self_link
}

resource "google_compute_network_peering" "peering_2" {
  name = "peering-2"
  network = google_compute_network.vpc_network_2.self_link
  peer_network = google_compute_network.vpc_network_1.self_link
}

firewallの設定

プロキシサーバー、バックエンドサーバー側、それぞれのネットワークのfirewallをこのように設定しました。

  • プロキシサーバー
    • 2280ポートからのTCP通信を許可
  • バックエンドサーバー
    • 228080ポートからのTCP通信を許可

プロキシサーバー側ではNginxのインストールと設定を行い、
バックエンドサーバー側ではNode.jsのインストールと簡易なバックエンドサーバーの実装を行います。
そのためSSH接続を行う必要があるので、22ポートからのアクセスを許可しています。

またプロキシサーバー側は80ポートからのアクセスを許可します。
そしてバックエンドサーバー側は8000ポートのアクセスを許可することで、プロキシサーバー側から後段のバックエンドサーバーへリクエストできるようにしています。

それぞれのfirewallでsource_ranges = ["0.0.0.0/0"]と設定しています。
しかし、これでは全てのIPアドレスからのアクセスを許可してしまうので実用的には見直した方が良いですね。

コード詳細
# firewall settings
resource "google_compute_firewall" "firewall_backend_ssh" {
  project = local.project_name_1
  name = "firewall-backend-ssh"
  network = google_compute_network.vpc_network_1.name

  allow {
    protocol = "tcp"
    ports = ["22"]
  }

  source_tags = ["ssh-backend-server"]
  source_ranges = ["0.0.0.0/0"]
}

resource "google_compute_firewall" "firewall_proxy_server_ssh" {
  project = local.project_name_2
  name = "firewall-proxy-server-ssh"
  network = google_compute_network.vpc_network_2.name

  allow {
    protocol = "tcp"
    ports = ["22"]
  }

  source_tags = ["ssh-proxy-server"]
  source_ranges = ["0.0.0.0/0"]
}

resource "google_compute_firewall" "firewall_backend" {
  project = local.project_name_1
  name = "firewall-backend"
  network = google_compute_network.vpc_network_1.name

  allow {
    protocol = "tcp"
    ports = ["8080"]
  }

  source_tags = ["tcp-backend-server"]
  source_ranges = ["0.0.0.0/0"]
}

resource "google_compute_firewall" "firewall_proxy_server" {
  project = local.project_name_2
  name = "firewall-proxy-server"
  network = google_compute_network.vpc_network_2.name

  allow {
    protocol = "tcp"
    ports = ["80"]
  }

  source_tags = ["tcp-proxy-server"]
  source_ranges = ["0.0.0.0/0"]
}

バックエンドサーバーのVM作成

全体のコードは下記のとおりです。

コード詳細
# create vm instace
resource "google_compute_instance" "backend_server" {
  name         = "vm-backend-server"
  machine_type = "e2-micro"
  zone         = "us-central1-a"
  project =  local.project_name_1

  # ブートディスクの設定
  boot_disk {
    initialize_params {
      image = "ubuntu-minimal-2210-kinetic-amd64-v20230126"
    }
  }

  tags = [ "tcp-backend-server", "ssh-backend-server" ]

  # ネットワークの設定
  network_interface {
    network = google_compute_network.vpc_network_1.name
    subnetwork = google_compute_subnetwork.subnet_1.name
    subnetwork_project = local.project_name_1
    access_config {}
  }
  
  # 公開鍵を設定
  metadata = {
    ssh-keys = "{$USER_NAME}:ssh-rsa {$PUBLIC_KEY}"
  }

  provisioner "remote-exec" {
    connection {
      type = "ssh"
      # SSH接続を行うユーザー名
      user = "{$USER_NAME}"
      # 秘密鍵を設定
      private_key = file("./sshkey/google_compute_engine_for_terraform")
      host = self.network_interface.0.access_config.0.nat_ip
      timeout = "10m"
    }

    # Node.jsのインストールと簡易なバックエンドサーバーの実装
    inline = [
      # Node.jsをインストール
      "curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash",
      "export NVM_DIR=\"$HOME/.nvm\"",
      "[ -s \"$NVM_DIR/nvm.sh\" ] && . \"$NVM_DIR/nvm.sh\"",
      "nvm install 23",
      "node -v",
      "npm -v",
      "mkdir sample", 
      "npm init -y", # 自動的にデフォルト設定でpackage.jsonを作成
      "npm install express",
      "npm install pm2 -g",
      # 簡易なバックエンドサーバーの実装
      "echo \"const express = require('express')\" > sample/index.js",
      "echo \"const app = express()\" >> sample/index.js",
      "echo \"const port = 8080\" >> sample/index.js",
      "echo \"app.get('/', (req, res) => { res.send('Hello World!') })\" >> sample/index.js",
      "echo \"app.listen(port, () => { console.log(`Example app listening on port `) }) \" >> sample/index.js",
      # サーバーの永続化
      "pm2 start sample/index.js --name my-app",
      "pm2 startup",
      "pm2 save"
    ]
  }
}

Discussion