在業務中需要構建雲端環境,調查了基於代碼的基礎設施 (IaC) 工具 Terraform。這可能已經是老生常談,但我整理了一些AWS和GCP的基本環境配置方法,使初次接觸Terraform的人也能夠理解。
以上情況的讀者,我將分享我實際調查的內容。
Terraform是由HashiCorp公司開發的開源IaC工具,可以用代碼來定義和管理基礎設施。
IaC是什麼?
基於代碼的基礎設施(Infrastructure as Code)的縮寫,是指用程式碼來描述和管理伺服器、網路等基礎設施設定的方法。這樣可以擺脫繁瑣的手動操作。
儘管AWS CDK(AWS專用)和Pulumi(多種語言支援)等競爭對手存在,但由於我們公司除了使用AWS的項目,還有GCP的項目,因此選擇了能夠支援多雲的Terraform。
特點 | 優勢 |
---|---|
支援多雲 | 可以統一管理AWS、GCP、Azure等多個雲端 |
聲明式描述 | 只需描述「想要什麼」,「如何構建」由Terraform自動處理 |
狀態管理 | 自動追蹤基礎設施當前狀態並檢測更改差異 |
變更可視化 | 在應用之前可以預覽變更內容以防止事故 |
可重現性 | 可以從相同的代碼多次創建相同的環境 |
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
terraform --version
terraform --version
執行結果範例:
Terraform v1.13.3
顯示以上內容即表示安裝成功。
使用VS Code時推薦安裝以下擴展功能:
以下是要構建的架構:
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 |
獲取訪問金鑰的方法:
⚠️ 安全注意:請嚴格管理訪問金鑰。若洩露將面臨以下風險:
- 因不當使用而產生的高額帳單(曾發生數百萬元的損失案例)
- 對資源的非法存取
- 數據洩漏
為什麼要分開文件?
通過按角色劃分代碼,使管理更加容易,維護性提高。
terraform-aws/
├── main.tf # 資源定義(VPC、EC2等的設定)
├── variables.tf # 變數定義(重複使用的值定義為變數)
├── outputs.tf # 輸出值定義(顯示創建資源的IP地址等)
└── terraform.tfvars # 變數值(寫入實際的值,含機密資訊建議使用.gitignore)
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" }
}
使用變數的優勢:
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
}
Terraform的執行有三個步驟:
init
(初始化)plan
(檢查執行計畫)apply
(實際創建)# 步驟1:初始化(第一次執行時只需執行一次)
terraform init
會發生什麼?
.terraform
目錄# 步驟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
將顯示創建的資源的詳細信息。
讓我們在瀏覽器中確認一下:
terraform-web-server
# 步驟5:刪除資源(為了降低成本務必執行)
terraform destroy
在確認提示中輸入yes
即可刪除創建的所有資源。
⚠️ 重要:驗證後必須執行destroy。若不執行將持續計費。
基本思路與AWS相同,但服務名稱和設置有所不同。
在GCP中,即使在免費試用期間,幾乎所有資源(Compute Engine、VPC等)都需要啟用計費。若不啟用計費則會出現以下錯誤。
錯誤: 創建實例時出錯: googleapi: 錯誤403: 計算引擎API未在項目[PROJECT_ID]中使用過或已禁用。
在試用結束後,以下資源可持續免費使用
詳情請參考
Google Cloud的免費試用和免費層產品(Google官方)
Google Cloud SDK是什麼?
一組從命令行操作GCP的工具。
# 安裝(macOS)
brew install --cask google-cloud-sdk
# 初始化(瀏覽器將打開,使用Google帳戶進行身份驗證)
gcloud init
# 設置應用的默認認證
gcloud auth application-default login
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。只需第一次執行即可。
服務帳戶是什麼?
應用程式(這裡是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等平台。
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訪問權限
}
}
variable "project_id" {
description = "GCP專案ID"
type = string
}
variable "region" {
description = "GCP區域"
type = string
default = "asia-northeast1" # 東京區域
}
GCP的區域列表:
asia-northeast1
:東京asia-northeast2
:大阪us-central1
:艾奧瓦(美國)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
project_id = "your-project-id" # 用實際的項目ID替換
region = "asia-northeast1"
與AWS相同的流程:
terraform init
terraform plan # 必須確認!
terraform apply # 輸入yes
terraform show # 查看創建的資源
terraform destroy # 刪除
在GCP控制台中的確認:
必須添加到.gitignore:
# Terraform變數文件(可能包含機密信息)
*.tfvars
# Terraform狀態文件(包含資源ID等)
*.tfstate
*.tfstate.backup
# Terraform插件目錄
.terraform/
# GCP服務帳戶金鑰
terraform-key.json
# AWS密鑰對
*.pem
為什麼要添加到.gitignore?
這些文件可能包含以下機密信息:
如果意外推送到GitHub,將會在幾分鐘內被自動掃描並濫用。
在生產環境中僅賦予必要的最低權限。這對減少洩露事故和配置錯誤至關重要。
為什麼最小權限重要?
AWS IAM政策示例(只允許EC2和VPC操作):
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"ec2:*",
"vpc:*"
],
"Resource": "*"
}]
}
❌ 避免的範例:
{
"Effect": "Allow",
"Action": "*", # 允許所有操作(危險!)
"Resource": "*"
}
開發環境: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地址(全球範圍)模組是什麼?
可重用的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
優勢:
為何分環境?
開發環境使用較小實例,生產環境使用較大實例,根據需求變更設定。
# 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"
狀態文件是什麼?
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" # 鎖定管理用表
}
}
事前準備:
my-terraform-state
terraform-lock
(主鍵:LockID
)GCS後端示例:
terraform {
backend "gcs" {
bucket = "my-terraform-state" # GCS桶名
prefix = "prod" # 前綴
}
}
事前準備:
my-terraform-state
實際上常遇到的錯誤及解決方案。
錯誤消息:
錯誤: 鎖定狀態時發生錯誤: 鎖定狀態時出錯
原因:
上次的terraform apply
異常終止(如Ctrl+C中斷),鎖定文件仍然存在。
解決方法:
# 複製錯誤消息中顯示的鎖定ID
terraform force-unlock <LOCK_ID>
# 例
terraform force-unlock a1b2c3d4-e5f6-7890-abcd-ef1234567890
注意:執行前需確認其他人是否正在運行。
錯誤消息:
錯誤: 提供者配置不存在
原因:
提供者的配置未正確初始化或者.terraform
目錄已損壞。
解決方法:
# 重新初始化提供者
terraform init -reconfigure
-reconfigure
選項將忽略現有的配置,重新初始化。
錯誤消息:
錯誤: 配置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