Skip to content

IaC 起手式:用 Terraform 在 PVE 上自動建出 K3s 容器

建立 K3s 節點前,第一步是把整個流程用程式碼重現。這篇整理核心思維轉變、工具分工,以及在 PVE 上實作自動化前必須掌握的 CLI 與 API Token。


核心思維:基礎設施即程式碼(IaC)

Pets vs. Cattle

寵物(Pets)牛群(Cattle)
對待方式精心照顧,害怕壞掉壞了直接銷毀重建
管理方式手動 SSH 進去修程式碼定義,一鍵重新部署
代表情境手動調整的實體伺服器由 Terraform 產生的 CT/VM

IaC 的目標就是零手動容忍:連「建立機器」這件事都交給程式碼,達到任何節點壞掉都能自動重建的最終狀態。


工具分工:Terraform + Ansible

兩個工具負責不同層次的工作,缺一不可。

Terraform(建築師)— Provisioning

負責跟 PVE 溝通,自動「生出」實體資源:

  • 建立 CT,分配 CPU / RAM
  • 設定靜態 IP、網路介面
  • 定義節點規格(Master 2G RAM、Worker 8G RAM)

Ansible(室內設計師)— Configuration Management

在 CT 建起來後,自動連進去「裝潢」:

  • 修改 SSH 設定(開啟 root 登入)
  • 安裝基礎套件(curl、wget 等)
  • 執行 K3s 安裝指令
bash
# Master 安裝
curl -sfL https://get.k3s.io | sh -

# Worker 加入叢集(需先從 Master 取得 token)
curl -sfL https://get.k3s.io | K3S_URL=https://<master-ip>:6443 K3S_TOKEN=<node-token> sh -

前置作業

1. 連線進入主控台

先確認主控台是否允許 SSH 連線,若有的話,可以使用終端機連進去:

bash
ssh user_name@server_ip

2. 建立 PVE API Token

Terraform 不會使用網頁登入,所以需要一把專屬的 API Token 來操作 PVE。

bash
# 1. 建立專用帳號
pveum user add terraform@pve --comment "Terraform automation user"

# 2. 建立自訂角色並給予必要權限
pveum role add TerraformRole -privs \
  "VM.Allocate VM.Clone VM.Config.CDROM VM.Config.CPU \
   VM.Config.Cloudinit VM.Config.Disk VM.Config.HWType \
   VM.Config.Memory VM.Config.Network VM.Config.Options \
   VM.Audit VM.PowerMgmt \
   Datastore.AllocateSpace Datastore.Audit \
   SDN.Use Sys.Audit \
   Pool.Allocate Sys.Console Sys.Modify VM.Migrate"

# 3. 將角色綁定到帳號(/ 代表對整個 PVE 根目錄授權)
pveum aclmod / -user terraform@pve -role TerraformRole

# 4. 產生 API Token(--privsep=0 讓 Token 繼承帳號全部權限)
pveum user token add terraform@pve terraform-token --privsep=0

# 5. 確認是否建立成功
pvesh get /access/users/terraform@pve/token --output-format json
pvesh user list | less

執行第 4 步後,終端機會輸出 Token 值,只會顯示一次,範例如下:

┌──────────────┬──────────────────────────────────────┐
│ key          │ value                                │
╞══════════════╪══════════════════════════════════════╡
│ full-tokenid │ terraform@pve!terraform-token        │
├──────────────┼──────────────────────────────────────┤
│ value        │ xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx │
└──────────────┴──────────────────────────────────────┘

撰寫 Terraform 腳本

有了 API Token 之後,就可以開始撰寫 Terraform 腳本,讓它自動在 PVE 上建出 K3s 所需的容器。

專案結構

terraform/
├── main.tf           # 主要資源定義
└── terraform.tfvars  # 敏感變數(不納入版控)

main.tf

hcl
terraform {
  required_providers {
    proxmox = {
      source  = "bpg/proxmox"
      version = "~> 0.50.0"
    }
  }
}

provider "proxmox" {
  endpoint  = "<PVE IP,例如 https://192.168.x.x:8006>"
  api_token = "terraform@pve!terraform-token=<token>"
  insecure  = true  # 若使用自簽憑證需開啟
}

# --- Master 節點 ---
resource "proxmox_virtual_environment_container" "k3s_master" {
  node_name = "pve00"
  vm_id     = 204
  started   = true

  initialization {
    hostname = "k3s-master"
    user_account {
      password = "<password>"
    }
    ip_config {
      ipv4 {
        address = "192.168.<網段>.14/24"
        gateway = "192.168.<網段>.1"
      }
    }
    dns {
      servers = ["8.8.8.8", "1.1.1.1"]
    }
  }

  network_interface {
    name   = "eth0"
    bridge = "vmbr0"
  }

  operating_system {
    template_file_id = "local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst"
    type             = "debian"
  }

  cpu   { cores     = 2 }
  memory { dedicated = 2048 }
  disk  { datastore_id = "local-lvm"; size = 5 }

  unprivileged = true
  features     { nesting = true }
}

# --- Worker 節點 ---
resource "proxmox_virtual_environment_container" "k3s_worker" {
  node_name = "pve00"
  vm_id     = 205
  started   = true

  initialization {
    hostname = "k3s-worker"
    user_account {
      password = "<password>"
    }
    ip_config {
      ipv4 {
        address = "192.168.<網段>.15/24"
        gateway = "192.168.<網段>.1"
      }
    }
    dns {
      servers = ["8.8.8.8", "1.1.1.1"]
    }
  }

  network_interface {
    name   = "eth0"
    bridge = "vmbr0"
  }

  operating_system {
    template_file_id = "local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst"
    type             = "debian"
  }

  cpu   { cores     = 2 }
  memory { dedicated = 2048 }
  disk  { datastore_id = "local-lvm"; size = 5 }

  unprivileged = true
  features     { nesting = true }
}

腳本寫好後,執行以下指令讓 Terraform 建出這兩個容器:

bash
terraform init    # 下載 provider
terraform plan    # 預覽會建什麼
terraform apply   # 實際執行

容器建好後,下一篇繼續:在容器內安裝 K3s