前言:
大家好,我是 Evan,今天想快速介紹一下,如何建置當前很紅的自動化工具 n8n,n8n 作為一款強大的開源自動化工具,提供豐富的節點使其能串接各種服務,實現複雜的自動化流程與應用。目前公司內部也正積極推廣。然而,要將 n8n 從單機運行模式提升至可維護、可擴展的企業級服務,一套穩固的基礎設施與標準化的部署策略便不可或缺。
這次的目標不僅是部署 n8n,更是要實踐「用自動化來管理自動化工具」。因此 我將避免手動操作雲端服務商所提供的 UI 介面,轉而採用更自動化的方法,直接透過程式從頭開始建置所有基礎設施與服務。
因此,本文將分享如何運用 LINE 內部的平台,透過 Terraform 的Infrastructure as Code (IaC) 特性與以 ArgoCD 為核心的 GitOps 理念,完整建置一套包含負載均衡並具備高安全性的 n8n 服務。
source: Link
核心理念:
- 基礎設施即代碼 (Infrastructure as Code, IaC): 透過 Terraform 程式碼來定義與管理所有雲端資源(VM、Kubernetes 叢集、LB、DNS)。此舉可確保環境的一致性、可重複性,並實現版本控制。
- GitOps: 以 Git 作為部署的唯一真相來源 (Single Source of Truth)。所有應用程式的配置變更都必須經由 Git 提交,再由 ArgoCD 自動同步至 Kubernetes 叢集,從而建立一個可追蹤、可審計的部署流程。
Part 1: Terraform 自動化建立雲端基礎設施 (IaC)
一切的起點,是定義我們服務所需的雲端資源。我們使用 Terraform 來宣告式地管理所有基礎設施。
Step 1: 環境初始化與 Provider 設定
要與平台溝通,首先必須設定 Terraform 的 provider
。provider.tf
是我們的入口點,它告訴 Terraform 要使用哪個服務商的 API。
# provider.tf
terraform {
required_version = ">= xxx"
required_providers {
選用的服務商 = {
source = "your-company-registry"
version = ">= xxx"
}
}
}
provider "選用的服務商" {
object storage {}
}
💡 實踐技巧:
為了管理敏感憑證與環境變數,我們使用 .envrc 檔案儲存這些資訊,並將其加入 .gitignore。搭配 direnv 工具,可以在進入專案目錄時自動載入環境變數,避免了手動 source 的繁瑣與疏忽,同時有效隔離不同專案的環境。
Step 2: 創建 Kubernetes Service 叢集
n8n 將以容器化的方式運行,因此我們需要一個 Kubernetes cluster。透過 xxx_cluster
資源來定義叢集規格。
# (示意)
resource "xxx_cluster" "n8n-dev" {
k8s_version = "v1.xx.2"
name = "xxx-dev"
network_plugin = "xxx"
# ... 其他設定 ...
}
resource "xxx_pool" "n8n-worker" {
cluster_id = xxx__cluster.n8n-dev.id
flavor_id = "304016xxxx" # VM 的規格
image_id = "e7xxxx-d47f-4573-b1af-xxxxxxxxx # OS 映像檔
quantity = x # 節點數量
# ... 其他設定 ...
}
# 引用 SRE 團隊維護的標準化 Ingress 節點池
module "xxx-pool" {
source = "git::https://xxxxx"
cluster_id = xxx
cmdb_owner = ["xxxxx"]
# ... 其他設定 ...
}
💡 實踐技巧與經驗:
- 模組化 (Modularity): 直接引用由 SRE 團隊提供的 pool Terraform 模組,讓我們無需關心 Ingress Controller (Traefik) 節點的底層細節,只需傳入必要參數,即可獲得一個符合團隊規範的標準化節點池,顯著降低了維護成本並確保一致性。
- 問題排解 (Quota 不足): 執行 terraform apply 時遇到的 Exceeded Quota 錯誤是常見問題。除了刪除閒置資源外,更進階的作法是使用 terraform import 將現存但未被 Terraform 管理的資源,納入當前狀態檔(state)中,以避免重複創建。
Step 3: Terraform State 遠端備份至 object storage
terraform.tfstate
是 Terraform 的核心,記錄了所有受控資源的狀態。將其保存在本地不利於團隊協作且風險極高。因此,我們將其遷移至我們內部儲存空間,它是一個 S3 相容的物件儲存服務。
# backend.tf
terraform {
backend "s3" {
bucket = "xxx-bucket"
endpoint = "https://xxxxx"
region = "xxx"
key = "tfstate.tfstate"
skip_requesting_account_id = true
skip_credentials_validation = true
skip_region_validation = true
use_lockfile = true # 啟用狀態鎖,防止多人同時執行
}
}
💡 關鍵功能:
- 狀態鎖 (State Locking): 遷移至 S3 backend 後,最重要的功能之一就是狀態鎖。當有成員執行 terraform apply 時,Terraform 會在公司內部的 object storage 上創建一個鎖定檔,防止其他人同時修改基礎設施,避免狀態不一致與資源衝突,是團隊協作的必要保障。
- 遷移流程: 從 local backend 遷移至 s3 backend 需謹慎操作。標準流程為:terraform state pull -> 修改backend.tf配置文件 -> terraform init -reconfigure,確保本地的最新狀態能平滑地推送到遠端。
Part 2: 設計高可用網路架構
基礎設施就緒後,我們需要規劃如何將流量安全、高效地引導至我們的 n8n 服務。
Step 4: 雙層負載均衡 (Load Balancer) 架構
為了區分對外提供服務的 UI 和僅供內部系統使用的 Webhook,我們設計了公私網分離的雙層 LB 架構:
- Public LB: 面向公網,負責將
n8n-tw.xxx-beta.com
的流量轉發至 Traefik Ingress Controller。這是給一般使用者訪問 n8n UI 的入口。 - Private LB: 僅在內部網路可見,負責處理
webhook
的流量。這為 Webhook 提供了一個更安全的接入點。
# lb.tf (示意)
# --- Private LB ---
resource "xxx_lb" "xxx" {
name = "xxx-private"
vip_type = "private"
# ...
}
resource "xxx_lb_revision" "xxx" {
load_balancer_id = xxx
# ... 監聽 80/443,轉發至 Traefik 的 10080/10443
dynamic "xxx" {
for_each = local.xxx
content {
hostname = "${xxx}.xxx.com."
port = xxx
}
# ...
}
# --- Public LB (設定類似,但 vip_type = "public") ---
resource "xxx_lb" "xxx-public" {
name = "xxx-public"
vip_type = "public"
# ...
}
# ...
Step 5: DNS 解析鏈路
有了 LB,我們還需要設定 DNS,將 domain 指向複雜的 LB IP address。
# dns.tf (示意)
# 1. A Record: 將 LB 域名指向其 VIP
resource "xxx_dns_record_set" "xxx-public-lb-xxx-com" {
name = "xxx-public.xxx.com."
type = "A"
records = [xxx_lb_revision.xxx]
# ...
}
# 2. CNAME Record: 將最終使用者域名指向 LB 域名
resource "xxx_dns_record_set" "xxx-com" {
name = "n8n-tw.xxx.com."
type = "CNAME"
records = ["xxx-public.xxx."]
# ...
}
架構解析:
這樣的 使用者域名 -> CNAME -> LB 域名 -> A -> LB IP
解析鏈路提供了極大的靈活性。未來如果需要更換 LB,我們只需要修改 A 紀錄,而使用者的 CNAME 域名完全不受影響。
Part 3: GitOps 實現應用程式持續部署
基礎設施完成後,將轉向去對應用程式進行部署。所有 Kubernetes Manifests 將由一個獨立的 Git Repository 管理,並由 ArgoCD 負責同步。
source: Link
這邊就需要自行去寫 Kubernetes manifest 了!
Step 6: Ingress - 流量路由的最後一哩路
外部的負載均衡器(Load Balancer)成功地將流量送到了我們 Kubernetes 叢集的節點上,但接下來流量該何去何從?這時就需要叢集內部的「交通指揮官」—— Ingress Controller。
source: Link
這邊先快速介紹一下 Ingress Controller,在我們的架構中,這個角色由 Traefik 擔任。Ingress Controller 是一個在 Kubernetes 內部運行的應用程式,它專門負責接收來自外部的流量,並根據我們設定的規則,智慧地將這些流量分發到叢集內部的不同服務(Service)。
而在 Part 2 中,其實我們已經透過 Terraform 去引用了我們 SRE 團隊所提供的 node 的 pool 模組。這一步非常重要,因為它正是我們 Ingress 架構的基礎。
這個由 SRE 提供的 node 的 module 不僅僅是建立了一組普通的 node,它實際上為我們部署了一套由 SRE 團隊統一維護、標準化且高可用的 Ingress Controller,在我們的例子中就是 Traefik。
這意味著我們作為服務的開發者,無需自行安裝、設定或維護 Traefik 本身。因為 SRE 團隊已經為我們準備好了這個叢集內的「交通指揮官」,並確保了它的穩定與安全。
source: Link
因此我們的任務,就是創建 Ingress 這種 Kubernetes 資源,也就是去建立 Ingress.yaml
,可以把它理解成一份「路由設定檔」,用來告訴這個已經存在的 Traefik:「當有流量進來時,請根據這些規則,將它轉發到我指定的服務」。
所以 Load Balance 將流量導入叢集內的 Traefik,而 Traefik 則依賴 Ingress 資源來決定如何將流量路 由到最終的目標服務。
# ingress.yaml
apiVersion: xxx
kind: Ingress
metadata:
name: xxx-ingress
annotations:
kubernetes.io/ingress.class: traefik # 指定由 traefik 處理
spec:
rules:
- host: xxx.com
http:
paths:
- path: /
pathType: xxx
backend:
service:
name: n8n
port:
number: 5678
Step 7: 服務暴露的選擇 - ClusterIP vs. NodePort
這是一個關鍵的資安與架構選擇。最初,Service 可能被設定為 NodePort
,這會直接在每個節點上暴露一個端口。
NodePort
的問題:
- 安全風險: 它繞過了我們的 LB 和 Ingress,任何知道節點 IP 的人都可以直接訪問該服務,失去了統一的流量入口和安全控制。
- 架構混亂: 在 Ingress 架構下,流量應該是
LB -> Ingress Controller -> Service -> Pod
。NodePort
創造了一個不必要的旁路。
我們的選擇 ClusterIP
:
我們將 Service 類型修改為 ClusterIP
。這種 Service Type 只會在叢集內部創建一個虛擬 IP,只能從叢集內部訪問。
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: n8n
spec:
type: ClusterIP # 正確的選擇!
selector:
app: n8n
ports:
- protocol: TCP
port: 5678
targetPort: 5678
這確保了所有外部流量都必須經過我們精心設計的 LB -> Traefik 路徑,實現了真正的安全隔離。
Step 8: Git Push 觸發 ArgoCD 同步
當我們將 ingress.yaml
、修改後的 service.yaml
以及 kustomization.yaml
推送到 Git Repo 後,ArgoCD 會自動偵測到變更,將其與叢集內的當前狀態進行比對,並應用這些變更以完成部署。這就是 GitOps 工作流程的核心。
結論
透過整合 Terraform、Kubernetes 與 ArgoCD,不僅是單單部署了一個 n8n 服務,更是實踐了一整套現代化的 Cloud Native 應用交付流程。此架構也在可維護性、可擴展性與安全性方面帶來了顯著的提升,並減少了人為操作時可能發生的失誤,也使每次的更動都有據可查。維護性大幅提升!!
這次建置的過程真的是一次很寶貴的學習經驗,直接實作涵蓋了從底層網路、基礎設施、容器編排到應用層部署的完整生命週期。讓我用一篇文章來記錄一下這次整個開發的流程,也讓更多人能走進 Terraform 的世界。