🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

前言

在業務中需要構建雲端環境,調查了基於代碼的基礎設施 (IaC) 工具 Terraform。這可能已經是老生常談,但我整理了一些AWS和GCP的基本環境配置方法,使初次接觸Terraform的人也能夠理解。

本文的對象

  • 想要自動化雲端基礎設施的構建
  • 希望擺脫手動管理資源的束縛
  • 想了解AWS和GCP的構建方法

以上情況的讀者,我將分享我實際調查的內容。


什麼是Terraform?

Terraform是由HashiCorp公司開發的開源IaC工具,可以用代碼來定義和管理基礎設施。

IaC是什麼?
基於代碼的基礎設施(Infrastructure as Code)的縮寫,是指用程式碼來描述和管理伺服器、網路等基礎設施設定的方法。這樣可以擺脫繁瑣的手動操作。

為什麼選擇Terraform

儘管AWS CDK(AWS專用)和Pulumi(多種語言支援)等競爭對手存在,但由於我們公司除了使用AWS的項目,還有GCP的項目,因此選擇了能夠支援多雲的Terraform。

特點 優勢
支援多雲 可以統一管理AWS、GCP、Azure等多個雲端
聲明式描述 只需描述「想要什麼」,「如何構建」由Terraform自動處理
狀態管理 自動追蹤基礎設施當前狀態並檢測更改差異
變更可視化 在應用之前可以預覽變更內容以防止事故
可重現性 可以從相同的代碼多次創建相同的環境

事前準備

1. 安裝Terraform

macOS

brew tap hashicorp/tap
brew install hashicorp/tap/terraform
terraform --version

Windows

  1. Terraform官方網站 下載
  2. 解壓ZIP並添加到PATH
  3. 在命令提示符或PowerShell中確認:
    terraform --version

    執行結果範例

    Terraform v1.13.3

    顯示以上內容即表示安裝成功。

2. 編輯器準備

使用VS Code時推薦安裝以下擴展功能:

  • HashiCorp Terraform(官方擴展)
    • Intellisense:程式碼補全功能
    • 語法高亮:以顏色區分提高可讀性
    • 程式碼導航:可跳轉到定義處

AWS環境構建

以下是要構建的架構:

  • VPC:獨立的網路環境(虛擬的自有專用網路)
  • 公共子網:可從互聯網訪問的區域
  • EC2實例:用於Web伺服器的虛擬機(虛擬伺服器)
  • 安全性群組:防火牆設定(通信的允許/拒絕規則)

1. AWS CLI設定

AWS CLI是什麼?
用於從命令行操作AWS的工具。Terraform在後端與AWS通信時需要它。

# 安裝(macOS)
brew install awscli

# 認證設定
aws configure

設定項目

項目 說明
Access Key ID AWS帳戶的訪問金鑰(類似用戶名) AKIAIOSFODNN7EXAMPLE
Secret Access Key 秘密金鑰(類似密碼) (因為機密資訊不顯示)
Default region 預設使用的區域(數據中心的位置) ap-northeast-1(東京)
Default output format 命令結果的輸出格式 json

獲取訪問金鑰的方法

  1. 登入AWS管理控制台
  2. 點擊右上方的用戶名 → 「安全認證」
  3. 在「訪問金鑰」部分點擊「創建訪問金鑰」
  4. 記錄顯示的金鑰(無法再次顯示請注意!)

⚠️ 安全注意:請嚴格管理訪問金鑰。若洩露將面臨以下風險:

  • 因不當使用而產生的高額帳單(曾發生數百萬元的損失案例)
  • 對資源的非法存取
  • 數據洩漏

2. 目錄結構

為什麼要分開文件?
通過按角色劃分代碼,使管理更加容易,維護性提高。

terraform-aws/
├── main.tf          # 資源定義(VPC、EC2等的設定)
├── variables.tf     # 變數定義(重複使用的值定義為變數)
├── outputs.tf       # 輸出值定義(顯示創建資源的IP地址等)
└── terraform.tfvars # 變數值(寫入實際的值,含機密資訊建議使用.gitignore)

3. 代碼範例

main.tf

HCL(HashiCorp配置語言)是什麼?
Terraform使用的專用配置語言。類似於JSON的可讀性語法是其特徵。

# 指定Terraform的版本和提供者
terraform {
  required_version = ">= 1.5.0"  # 指定Terraform的版本
  required_providers {
    aws = {
      source  = "hashicorp/aws"  # 提供者來源
      version = "~> 5.0"         # 提供者的版本(使用5.x系列)
    }
  }
}

# AWS提供者的設定
provider "aws" {
  region = var.aws_region  # 參考variables.tf中定義的變數
}

# VPC(虛擬專用雲)
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"  # IP地址範圍(65,536個IP地址)
  enable_dns_hostnames = true            # 啟用DNS主機名
  enable_dns_support   = true            # 啟用DNS解析
  tags = { Name = "terraform-vpc" }      # 管理用的標籤(名稱)
}

# 公共子網
resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id  # 參考上面創建的VPC的ID
  cidr_block              = "10.0.1.0/24"    # 256個IP地址
  availability_zone       = "${var.aws_region}a"  # 可用區(物理數據中心)
  map_public_ip_on_launch = true               # 啟動實例時自動分配公共IP
  tags = { Name = "terraform-public-subnet" }
}

# 互聯網閘道
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  tags = { Name = "terraform-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.main.id  # 通過互聯網閘道通信
  }

  tags = { Name = "terraform-public-rt" }
}

# 路由表和子網的關聯
resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

# 安全性群組
resource "aws_security_group" "web" {
  name        = "terraform-web-sg"
  description = "用於Web伺服器的安全性群組"
  vpc_id      = aws_vpc.main.id

  # 入站規則(外部通信):HTTP
  ingress {
    from_port   = 80           # 開始端口
    to_port     = 80           # 結束端口
    protocol    = "tcp"        # 協議
    cidr_blocks = ["0.0.0.0/0"]  # 允許所有IP地址
    description = "允許HTTP"
  }

  # 入站規則:SSH
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]  # 本番環境建議限制為公司IP
    description = "允許SSH"
  }

  # 出站規則
  egress {
    from_port   = 0              # 所有端口
    to_port     = 0
    protocol    = "-1"           # 所有協議
    cidr_blocks = ["0.0.0.0/0"]  # 允許所有目的地
  }

  tags = { Name = "terraform-web-sg" }
}

# EC2實例
resource "aws_instance" "web" {
  ami                    = var.ami_id  # Amazon Machine Image(OS的模版)
  instance_type          = "t2.micro"  # 實例類型(CPU、內存的規格)
  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.web.id]
  tags = { Name = "terraform-web-server" }
}

variables.tf

使用變數的優勢

  • 當同一值需要在多個位置使用時,若在一處更改,則整體皆可反映
  • 環境(開發/本番)之間容易切換值
    
    variable "aws_region" {
    description = "AWS區域"  # 變數的說明
    type        = string      # 數據類型(字串)
    default     = "ap-northeast-1"  # 預設值(東京區域)
    }

variable "ami_id" {
description = "EC2的AMI ID"
type = string
default = "ami-0d52744d6551d851e" # Amazon Linux 2023(東京區域用)
}

**區域是什麼?**  
AWS數據中心的地理位置。日本有東京(ap-northeast-1)和大阪(ap-northeast-3)。

> **📌 重要重點**:AMI ID因區域而異。  
最新的AMI ID請查閱 [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-public-parameters-ami.html) 或 [AWS官方文檔](https://aws.amazon.com/amazon-linux-ami/)。

#### outputs.tf
**輸出值是什麼?**  
在執行`terraform apply`後顯示的信息。它會輸出創建的資源的IP地址等後續需要的信息。
```hcl
output "instance_public_ip" {
  description = "EC2的公共IP"
  value       = aws_instance.web.public_ip
}

output "vpc_id" {
  description = "VPC ID"
  value       = aws_vpc.main.id
}

4. 執行步驟

Terraform的執行有三個步驟

  1. init(初始化)
  2. plan(檢查執行計畫)
  3. apply(實際創建)
# 步驟1:初始化(第一次執行時只需執行一次)
terraform init

會發生什麼?

  • 創建.terraform目錄
  • 下載必要的提供者(AWS插件)
  • 準備狀態管理
# 步驟2:檢查執行計畫(必須!)
terraform plan

將顯示什麼?

  • +:新創建的資源
  • -:將被刪除的資源
  • ~:將被更改的資源

執行範例

Terraform將執行以下操作:

  # aws_vpc.main將被創建
  + resource "aws_vpc" "main" {
      + cidr_block = "10.0.0.0/16"
      ...
    }

計畫:新增7個,變更0個,刪除0個。

一定要確認此輸出,再繼續前進,以防止意外刪除不必要的資源。

# 步驟3:創建資源
terraform apply

將出現確認提示:

你想執行這些操作嗎?
  Terraform將執行上面描述的操作。
  只有輸入'yes'將被接受以批准操作。

  輸入值:

確認內容後輸入yes(僅y是不允許的)。

創建需要幾分鐘。顯示如下即表示成功:

應用完成!資源:7個已添加,0個已更改,0個已刪除。

輸出:

instance_public_ip = "54.XXX.XXX.XXX"
vpc_id = "vpc-0a1b2c3d4e5f6g7h8"
# 步驟4:確認狀態
terraform show

將顯示創建的資源的詳細信息。

讓我們在瀏覽器中確認一下

  1. 登入AWS管理控制台
  2. 打開EC2儀表板
  3. 在「實例」中確認出現terraform-web-server
  4. 使用輸出的公共IP在瀏覽器中打開以確認訪問
# 步驟5:刪除資源(為了降低成本務必執行)
terraform destroy

在確認提示中輸入yes即可刪除創建的所有資源。

⚠️ 重要:驗證後必須執行destroy。若不執行將持續計費。


GCP環境構建

基本思路與AWS相同,但服務名稱和設置有所不同。

1. 必須啟用計費

在GCP中,即使在免費試用期間,幾乎所有資源(Compute Engine、VPC等)都需要啟用計費。若不啟用計費則會出現以下錯誤。

錯誤: 創建實例時出錯: googleapi: 錯誤403: 計算引擎API未在項目[PROJECT_ID]中使用過或已禁用。

啟用計費的步驟

  1. 訪問Google Cloud控制台
  2. 從左上方的導航菜單中選擇「計費」
  3. 點擊「關聯計費帳戶」
  4. 註冊信用卡信息(在免費試用期間不會自動計費)

2. 利用免費試用

  • 初次註冊時會贈送$300的信用額度
  • 有效期90天
  • 使用後信用額度將不會自動進行計費。

3. 免費層(Always Free)

在試用結束後,以下資源可持續免費使用

  • 1個e2-micro實例(Compute Engine)
  • 每月5GB的StandardStorage(Cloud Storage)
  • 1TB的查詢處理(Big Query)

詳情請參考
Google Cloud的免費試用和免費層產品(Google官方)


1. Google Cloud SDK安裝

Google Cloud SDK是什麼?
一組從命令行操作GCP的工具。

# 安裝(macOS)
brew install --cask google-cloud-sdk

# 初始化(瀏覽器將打開,使用Google帳戶進行身份驗證)
gcloud init

# 設置應用的默認認證
gcloud auth application-default login

2. 項目準備

GCP的項目是什麼?
管理資源的邏輯單位,相當於AWS帳戶。

# 創建項目
# your-project-id必須唯一(全世界不可重複)
gcloud projects create your-project-id --name="My Terraform Project"

# 設置使用創建的項目
gcloud config set project your-project-id

# 啟用必要的API
# 計算引擎API:創建虛擬機時需要
gcloud services enable compute.googleapis.com
# 資源管理器API:項目管理所需
gcloud services enable cloudresourcemanager.googleapis.com

啟用API是什麼?
在GCP中,使用各項服務之前需要先啟用API。只需第一次執行即可。

3. 創建服務帳戶

服務帳戶是什麼?
應用程式(這裡是Terraform)操作GCP的專用帳戶。與人類用的Google帳戶有所不同。

# 創建服務帳戶
gcloud iam service-accounts create terraform \
  --display-name="Terraform Service Account"

# 賦予權限(在生產環境中建議限制權限)
gcloud projects add-iam-policy-binding your-project-id \
  --member="serviceAccount:[email protected]" \
  --role="roles/editor"

# 創建認證金鑰(以JSON文件下載)
gcloud iam service-accounts keys create terraform-key.json \
  [email protected]

確認執行後
當前目錄將創建terraform-key.json。此文件將被Terraform讀取。

⚠️ 安全注意terraform-key.json請添加到.gitignore,並絕對不要推送到GitHub等平台。

4. 代碼範例

main.tf(GCP版)

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
  }
}

provider "google" {
  credentials = file("terraform-key.json")  # 讀取服務帳戶金鑰
  project     = var.project_id
  region      = var.region
}

# VPC網絡
resource "google_compute_network" "vpc" {
  name                    = "terraform-vpc"
  auto_create_subnetworks = false  # 手動創建子網
}

# 子網
resource "google_compute_subnetwork" "subnet" {
  name                     = "terraform-subnet"
  ip_cidr_range            = "10.0.1.0/24"
  region                   = var.region
  network                  = google_compute_network.vpc.id
  private_ip_google_access = true  # 允許私有連接到Google API
}

# 防火牆規則:SSH
resource "google_compute_firewall" "ssh" {
  name    = "terraform-allow-ssh"
  network = google_compute_network.vpc.name

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

  source_ranges = ["0.0.0.0/0"]  # 本番環境建議限制
  target_tags   = ["ssh"]        # 此標籤的實例適用
}

# 防火牆規則:HTTP/HTTPS
resource "google_compute_firewall" "http" {
  name    = "terraform-allow-http"
  network = google_compute_network.vpc.name

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

  source_ranges = ["0.0.0.0/0"]
  target_tags   = ["http"]
}

# GCE實例(虛擬機)
resource "google_compute_instance" "web" {
  name         = "terraform-web-server"
  machine_type = "e2-micro"  # 免費使用配額內的機器類型
  zone         = "${var.region}-a"  # 區域(物理數據中心)

  # 启动磁盘设置
  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-11"  # Debian 11 OS映像
      size  = 10  # 磁碟大小(GB)
      type  = "pd-standard"  # 標準永久磁碟
    }
  }

  # 網路介面設定
  network_interface {
    subnetwork = google_compute_subnetwork.subnet.id

    # 分配外部IP地址
    access_config {
      // 空區塊以自動分配(臨時IP)
    }
  }

  # 網絡標籤(將用於防火牆規則)
  tags = ["ssh", "http"]

  # 啟動時執行的腳本(安裝Nginx)
  metadata_startup_script = <<-EOF
    #!/bin/bash
    apt-get update
    apt-get install -y nginx
    systemctl start nginx
    systemctl enable nginx
    echo "<h1>Hello from Terraform!</h1>" > /var/www/html/index.html
  EOF

  # 服務帳號設定
  service_account {
    scopes = ["cloud-platform"]  # 所有Google Cloud API訪問權限
  }
}

variables.tf(GCP版)

variable "project_id" {
  description = "GCP專案ID"
  type        = string
}

variable "region" {
  description = "GCP區域"
  type        = string
  default     = "asia-northeast1"  # 東京區域
}

GCP的區域列表

  • asia-northeast1:東京
  • asia-northeast2:大阪
  • us-central1:艾奧瓦(美國)

outputs.tf(GCP版)

output "instance_external_ip" {
  description = "GCE的外部IP"
  value       = google_compute_instance.web.network_interface.access_config[0].nat_ip
}

output "vpc_name" {
  description = "VPC名稱"
  value       = google_compute_network.vpc.name
}

output "ssh_command" {
  description = "SSH連接命令"
  value       = "gcloud compute ssh ${google_compute_instance.web.name} --zone=${google_compute_instance.web.zone}"
}

原描述中的錯誤

# ❌ 錯誤(點記法無法訪問數組)
value = google_compute_instance.web.network_interface.access_config.nat_ip

# ✅ 正確(需要指定數組索引)
value = google_compute_instance.web.network_interface.access_config[0].nat_ip

terraform.tfvars

project_id = "your-project-id"  # 用實際的項目ID替換
region     = "asia-northeast1"

5. 執行步驟

與AWS相同的流程:

terraform init
terraform plan    # 必須確認!
terraform apply   # 輸入yes
terraform show    # 查看創建的資源
terraform destroy # 刪除

在GCP控制台中的確認

  1. 訪問 Google Cloud Console
  2. 選擇項目
  3. 查看「Compute Engine」→「VM實例」
  4. 在瀏覽器中打開外部IP並確認顯示「Hello from Terraform!」

最佳實踐

安全

1. 認證資訊管理

必須添加到.gitignore

# Terraform變數文件(可能包含機密信息)
*.tfvars

# Terraform狀態文件(包含資源ID等)
*.tfstate
*.tfstate.backup

# Terraform插件目錄
.terraform/

# GCP服務帳戶金鑰
terraform-key.json

# AWS密鑰對
*.pem

為什麼要添加到.gitignore?
這些文件可能包含以下機密信息:

  • AWS訪問金鑰
  • GCP服務帳戶金鑰
  • 資源的ID和內部IP
  • 數據庫密碼

如果意外推送到GitHub,將會在幾分鐘內被自動掃描並濫用。

2. 最小權限原則

在生產環境中僅賦予必要的最低權限。這對減少洩露事故和配置錯誤至關重要。

為什麼最小權限重要?

  • 若金鑰洩漏,可最大限度減少損失
  • 防止誤操作導致重要資源的刪除
  • 避免安全審計中的指摘

AWS IAM政策示例(只允許EC2和VPC操作):

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": [
      "ec2:*",
      "vpc:*"
    ],
    "Resource": "*"
  }]
}

❌ 避免的範例

{
  "Effect": "Allow",
  "Action": "*",  # 允許所有操作(危險!)
  "Resource": "*"
}

3. 防火牆嚴格化

開發環境0.0.0.0/0(可從所有IP連接)
生產環境:必須限制為特定IP

# 生產環境用的安全性群組範例
ingress {
  from_port   = 22
  to_port     = 22
  protocol    = "tcp"
  cidr_blocks = ["203.0.113.0/24"]  # 公司辦公IP等
  description = "僅從辦公室允許SSH"
}

IP地址範圍指定方法

  • 203.0.113.10/32:單一IP地址
  • 203.0.113.0/24:256個IP地址(203.0.113.0~203.0.113.255)
  • 0.0.0.0/0:所有IP地址(全球範圍)

代碼管理

1. 模組化

模組是什麼?
可重用的Terraform代碼集合,類似於函數。

terraform-project/
├── modules/             # 可重用模組
│   ├── vpc/            # VPC創建模組
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   └── ec2/            # EC2創建模組
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
└── environments/       # 各環境配置
    ├── dev/           # 開發環境
    │   └── main.tf
    └── prod/          # 生產環境
        └── main.tf

優勢

  • 在多個環境中重用相同代碼
  • 只需在一處更改
  • 團隊內代碼共享變得容易

2. 環境變數管理

為何分環境?
開發環境使用較小實例,生產環境使用較大實例,根據需求變更設定。

# dev.tfvars(開發環境)
environment   = "development"
instance_type = "t2.micro"      # 小型實例
db_size       = 20              # 小型磁碟

# prod.tfvars(生產環境)
environment   = "production"
instance_type = "t3.medium"     # 大型實例
db_size       = 100             # 大型磁碟

執行方法

# 提交給開發環境
terraform apply -var-file="dev.tfvars"

# 提交給生產環境
terraform apply -var-file="prod.tfvars"

3. 狀態文件的遠程管理

狀態文件是什麼?
Terraform管理的資源當前狀態的記錄文件。默認情況下,它儲存在本地為terraform.tfstate

為何遠程管理必須?

  • 團隊開發:多個人管理同一基礎設施時,需要共享狀態
  • 備份:本地文件丟失將使資源管理無法進行
  • 鎖定功能:防止同時執行,避免競爭

AWS S3後端示例

terraform {
  backend "s3" {
    bucket         = "my-terraform-state"      # S3桶名
    key            = "prod/terraform.tfstate"  # 文件路徑
    region         = "ap-northeast-1"
    encrypt        = true                      # 啟用加密
    dynamodb_table = "terraform-lock"          # 鎖定管理用表
  }
}

事前準備

  1. 創建S3桶my-terraform-state
  2. 創建DynamoDB表terraform-lock(主鍵:LockID

GCS後端示例

terraform {
  backend "gcs" {
    bucket = "my-terraform-state"  # GCS桶名
    prefix = "prod"                # 前綴
  }
}

事前準備

  1. 創建GCS桶my-terraform-state

故障排除

實際上常遇到的錯誤及解決方案。

1. 狀態文件被鎖定

錯誤消息

錯誤: 鎖定狀態時發生錯誤: 鎖定狀態時出錯

原因
上次的terraform apply異常終止(如Ctrl+C中斷),鎖定文件仍然存在。

解決方法

# 複製錯誤消息中顯示的鎖定ID
terraform force-unlock <LOCK_ID>

# 例
terraform force-unlock a1b2c3d4-e5f6-7890-abcd-ef1234567890

注意:執行前需確認其他人是否正在運行。

2. 提供者配置錯誤

錯誤消息

錯誤: 提供者配置不存在

原因
提供者的配置未正確初始化或者.terraform目錄已損壞。

解決方法

# 重新初始化提供者
terraform init -reconfigure

-reconfigure選項將忽略現有的配置,重新初始化。

3. AWS認證錯誤

錯誤消息

錯誤: 配置Terraform AWS提供者時發生錯誤: 沒有有效的憑證來源

原因
AWS認證信息未設置或已過期。

確認方法

# 確認當前的認證信息
aws sts get-caller-identity

正常情況下的輸出範例

{
    "UserId": "AIDAI...",
    "Account": "123456789012",
    "Arn": "arn:aws:iam::123456789012:user/terraform-user"
}

解決方法

# 重新配置AWS CLI以更新憑證
aws configure

原文出處:https://qiita.com/Natsuhi-aruku/items/ab32e4bd91c02f0e8e16


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝22   💬9   ❤️6
617
🥈
我愛JS
📝4   💬14   ❤️7
276
🥉
御魂
💬1  
3
#4
2
#5
1
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付