<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>運維 on 亂筆</title>
        <link>https://blog.l3zc.com/zh-hant-hk/tags/%E9%81%8B%E7%B6%AD/</link>
        <description>Recent content in 運維 on 亂筆</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-hant-hk</language>
        <lastBuildDate>Tue, 14 Apr 2026 10:07:12 +0000</lastBuildDate><atom:link href="https://blog.l3zc.com/zh-hant-hk/tags/%E9%81%8B%E7%B6%AD/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>Terraform IaC 初體驗</title>
            <link>https://blog.l3zc.com/zh-hant-hk/2026/04/iac-with-terraform/</link>
            <pubDate>Thu, 09 Apr 2026 07:13:35 +0000</pubDate>
            <guid>https://blog.l3zc.com/zh-hant-hk/2026/04/iac-with-terraform/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2026/04/iac-with-terraform/cover_hu_a8e83f97ed61569c.webp&#34; alt=&#34;Featured image of post Terraform IaC 初體驗&#34; /&gt;&lt;p&gt;Terraform 是一款 IaC 工具，所謂 IaC 就是「基礎設施即代碼」，將我們的基礎設施以宣告式的代碼寫出來，隨後使用&lt;code&gt;terraform apply&lt;/code&gt;即可完成基礎設施的部署，同一份配置，部署出來的一定是一模一樣的基礎設施（Nix OS 用戶狂喜）。&lt;/p&gt;&#xA;&lt;h2 id=&#34;為什麼要用-terraform&#34;&gt;為什麼要用 Terraform&#xA;&lt;/h2&gt;&lt;p&gt;傳統的基礎設施管理絕大部分基於人力和各種雲服務商的 Dashboard，這就帶來了下面這些痛點：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th style=&#34;text-align: left&#34;&gt;痛點&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: left&#34;&gt;説明&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;難以重現&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;透過 Dashboard 點點點進行配置，容易改錯或者改漏，而且難以復現&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;環境漂移&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;手動操作導致生產和測試環境配置逐漸偏離，導致極端情況下測試沒問題生產直接崩&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;難以擴充套件&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;新增一套環境需要重複大量手工操作，耗時且容易出錯&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;難以審計&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;沒有變更記錄，&lt;del&gt;出了事不好甩鍋&lt;/del&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;難以協作&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;基礎設施由少數「懂的人」掌控，誰想要改都得去找這些人，效率低&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;為了解決這些問題，「基礎設施即代碼」&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;的概念被提了出來，Terraform 就是其中一個著名的解決方案。&lt;/p&gt;&#xA;&lt;p&gt;假設一個現實的使用場景，例如你每年都會購買新的 GCP 的 $300 試用金賬號，雖然便宜，但是每年都要重新跑到 GCP 的面板裏開機器。而有了 Terraform，想要部署同樣配置的機器，只要在每次更換賬號之後，將 API Token 替換成新的，隨後&lt;code&gt;terraform apply&lt;/code&gt;，只需要幾分鐘就能開出和你上個賬號一模一樣的機器、VPC、S3、防火牆配置等等。&lt;/p&gt;&#xA;&lt;p&gt;再比如 Cloudflare DNS 和 Tunnel 的管理和遷移，也只需要將原 Tunnel 的 Ingress Rule 複製到新 Tunnel 的 Ingress Rule。即使將來要遷移到 AliDNS、Route 53 等其他服務商，我們也可以原封不動的將資料複製過去&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;&#xA;&lt;p&gt;尤其是到了多人協作的時候，配合 Git，每次更改都有跡可循，更不需要擔心合併衝突，PR 可以自動生成更改預覽，出現問題可以立刻回滾到上一個版本，這些都是傳統依賴人力去直接操作服務提供商 Dashboard 的做法完全無法做到的。&lt;/p&gt;&#xA;&lt;h2 id=&#34;terraform-的安裝&#34;&gt;Terraform 的安裝&#xA;&lt;/h2&gt;&lt;p&gt;Terraform 是用 Go 寫的，編譯出來的產物自然也是單個可執行檔案，所以安裝起來也非常容易，Windows 下可以直接使用 Winget 安裝：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-pwsh&#34;&gt;winget install Hashicorp.Terraform&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果不想使用 Winget，也可以使用 Scoop 等其他包管理器，或者將預編譯的二進位制檔案放入&lt;code&gt;$PATH&lt;/code&gt;，即可完成安裝。&lt;/p&gt;&#xA;&lt;p&gt;如果你在 Linux 環境下，則使用對應的包管理器安裝即可，如果使用將二進位制檔案放入&lt;code&gt;$PATH&lt;/code&gt;的安裝方法，則要記得&lt;code&gt;sudo chmod +x terraform&lt;/code&gt;賦予檔案的執行許可權。&lt;/p&gt;&#xA;&lt;h2 id=&#34;terraform-的兩個基本概念&#34;&gt;Terraform 的兩個基本概念&#xA;&lt;/h2&gt;&lt;h3 id=&#34;providers&#34;&gt;Provider(s)&#xA;&lt;/h3&gt;&lt;p&gt;我們前面已經提到，Terraform 是一個款礎設施即代碼工具，作為一個工具本身，他並不繫結某個平台，而是透過 Provider(s) 與各個平台對接，要檢視有哪些 Provider(s)，可以瀏覽 &lt;a class=&#34;link&#34; href=&#34;https://registry.terraform.io/browse/providers&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Terraform 的 Registry&lt;/a&gt;。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2026/04/iac-with-terraform/image_hu_463ebc576c22c691.webp&#34; alt=&#34;Terraform 擁有豐富的 Provider(s) 生態&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;狀態管理&#34;&gt;狀態管理&#xA;&lt;/h3&gt;&lt;p&gt;Terraform 將每次執行基礎設施變更操作時的狀態資訊儲存在一個狀態檔案中，預設情況下會儲存在當前工作目錄下的&lt;code&gt;terraform.tfstate&lt;/code&gt;檔案裏&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;，我們也可以修改配置檔案自行指定其他的儲存後端，例如 S3、Postgres。每次我們執行&lt;code&gt;terraform apply&lt;/code&gt;時，Terraform 都會將目前配置檔案宣告的狀態同現有的狀態檔案進行比對，從而計算變動的部分，自動確定調整順序之後操作 Provider 落實狀態變動。&lt;/p&gt;&#xA;&lt;h2 id=&#34;terraform-的資源匯入難題&#34;&gt;Terraform 的資源匯入難題&#xA;&lt;/h2&gt;&lt;p&gt;現有資源的匯入一直是 Terraform 為人詬病的難題，Hashi Corp. 似乎一直在堅持着一個固執且愚蠢的觀點：你所有的基礎設施從一開始就應當是用我們的 Terraform 建立的，所以也不存在什麼資源匯入的問題。&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;時間&lt;/th&gt;&#xA;          &lt;th&gt;版本&lt;/th&gt;&#xA;          &lt;th&gt;進展&lt;/th&gt;&#xA;          &lt;th&gt;問題&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;2014–2022&lt;/td&gt;&#xA;          &lt;td&gt;v0.x–v1.4&lt;/td&gt;&#xA;          &lt;td&gt;只有 &lt;code&gt;terraform import&lt;/code&gt;，一次一條，而且完全不生成配置&lt;/td&gt;&#xA;          &lt;td&gt;匯入完還得自己手寫 HCL&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;2023.06&lt;/td&gt;&#xA;          &lt;td&gt;v1.5&lt;/td&gt;&#xA;          &lt;td&gt;引入 &lt;code&gt;import&lt;/code&gt; 塊和 &lt;code&gt;-generate-config-out&lt;/code&gt;引數，可以生成配置了&lt;/td&gt;&#xA;          &lt;td&gt;然而還是得一條一條寫，而且還得自己提供現有資源的 ID&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;2024.01&lt;/td&gt;&#xA;          &lt;td&gt;v1.7&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;import&lt;/code&gt; 塊支援 &lt;code&gt;for_each&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;批次了，但 ID 還得自己搞&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;2024 下半年&lt;/td&gt;&#xA;          &lt;td&gt;v1.12&lt;/td&gt;&#xA;          &lt;td&gt;引入了 &lt;code&gt;terraform query&lt;/code&gt; 和 &lt;code&gt;list&lt;/code&gt; 塊，終於實現了自動發現資源的功能&lt;/td&gt;&#xA;          &lt;td&gt;然而這個功能要 Provider 要自己實現，大量 Provider 根本沒跟上&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;這麼多年的時間，社羣一直在罵，然而，「我有一堆現有資源，怎麼匯入 Terraform」這個問題一直沒有被認真對待。Terraform 作為「基礎設施即代碼」工具，設計哲學就是宣告式和冪等性&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;，Hashi Corp. 他們篤信「一切以代碼宣告的狀態為準，就應該用 Terraform 從零開始建立資源」。但現實是絕大多數公司都有大量歷史存量資源，先有資源、後有代碼才是常態。這個矛盾 Hashi Corp. 承認得很晚，&lt;code&gt;v1.12&lt;/code&gt; 的 &lt;code&gt;terraform query&lt;/code&gt; 才算是官方第一次認真面對這個問題——但 Provider 生態的跟進嘛……真的是一言難盡。&lt;/p&gt;&#xA;&lt;h2 id=&#34;terraform-的檔案結構&#34;&gt;Terraform 的檔案結構&#xA;&lt;/h2&gt;&lt;p&gt;Terraform 的檔案結構很簡單，其主程式執行時會無腦讀取工作目錄下所有的&lt;code&gt;.tf&lt;/code&gt;檔案，只要資訊齊全，想給檔案取什麼名字都可以，比如我的檔案結構就長這樣：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;❯ tree -a -I .git&#xA;.&#xA;├── .editorconfig&#xA;├── .github&#xA;│   ├── dependabot.yml&#xA;│   └── workflows&#xA;│       ├── terraform-apply.yml&#xA;│       ├── terraform-plan.yml&#xA;│       └── your-fork.yml&#xA;├── .gitignore&#xA;├── .terraform.lock.hcl&#xA;├── cf_dns_zones.tf&#xA;├── cf_tunnel.tf&#xA;├── dns_example_com.tf&#xA;├── dns_example_net.tf&#xA;├── dns_example_top.tf&#xA;├── dns_example_cn.tf&#xA;├── main.tf                 # 基礎配置（terraform 塊）&#xA;├── moved.tf                # 移動過的資源&#xA;├── provider.tf             # 每個 Provider 的配置&#xA;├── README.md&#xA;├── rename_resources.ps1&#xA;└── variables.tf            # 自定義的變數&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;main.tf&lt;/code&gt;用於存放基礎配置:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;terraform {&#xA;  required_providers {&#xA;    cloudflare = {&#xA;      source  = &#34;cloudflare/cloudflare&#34;&#xA;      version = &#34;~&gt; 5&#34;&#xA;    }&#xA;&#xA;    tencentcloud = {&#xA;      source  = &#34;tencentcloudstack/tencentcloud&#34;&#xA;      version = &#34;&gt;= 1.81.43&#34;&#xA;    }&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;provider.tf&lt;/code&gt;用於存放每個 Provider 的配置：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;provider &#34;cloudflare&#34; {}&#xA;provider &#34;tencentcloud&#34; {}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這裏留空是因為我們可以透過環境變數來傳遞 Credentials，而不是直接將 Credentials 寫在配置檔案中，例如 Cloudflare 的 Provider 就接受&lt;code&gt;CLOUDFLARE_API_TOKEN&lt;/code&gt;作為&lt;code&gt;api_token&lt;/code&gt;這個變數的替代。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;variables.tf&lt;/code&gt;用於宣告自定義的變數：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;variable &#34;cloudflare_zone_example_com&#34; {&#xA;  description = &#34;Cloudflare zone ID for example.com&#34;&#xA;  type        = string&#xA;}&#xA;&#xA;variable &#34;cloudflare_zone_example_top&#34; {&#xA;  description = &#34;Cloudflare zone ID for example.top&#34;&#xA;  type        = string&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這些變數可以透過字首為&lt;code&gt;TF_VAR_&lt;/code&gt;的環境變數傳入，Terraform 也會自動讀取&lt;code&gt;terraform.tfvars&lt;/code&gt;檔案中的變數，變數的主要用途是在別的配置檔案中呼叫，例如：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;resource &#34;cloudflare_dns_record&#34; &#34;example_cname&#34; {&#xA;  content = &#34;${cloudflare_zero_trust_tunnel_cloudflared.Production_Tunnel.id}.cfargotunnel.com&#34;&#xA;  name    = &#34;example.example.com&#34;&#xA;  proxied = true&#xA;  tags    = []&#xA;  ttl     = 1&#xA;  type    = &#34;CNAME&#34;&#xA;  zone_id = var.cloudflare_zone_id_example_com  #這裏呼叫了cloudflare_zone_example_com這個變數&#xA;  settings = {&#xA;    flatten_cname = false&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;有了這些基礎配置，我們就可以執行&lt;code&gt;terraform init&lt;/code&gt;初始化 Terraform 環境並鎖定依賴版本，剩下的就都是我們資源的宣告瞭。&lt;/p&gt;&#xA;&lt;h2 id=&#34;將現有資源匯入-terraform&#34;&gt;將現有資源匯入 Terraform&#xA;&lt;/h2&gt;&lt;p&gt;前文提到過 Terraform 的狀態管理這個概念，狀態管理雖好，但在我們初始化時也帶來了一個問題——在預設狀態下，Terraform 的狀態檔案顯然是空的。&lt;/p&gt;&#xA;&lt;p&gt;此時我們需要做的是將雲服務提供商上現有資源的狀態匯入 Terraform，這樣 Terraform 才能無縫接手並管理我們的基礎設施。相信大家也都看到了前文對 Terraform 資源匯入問題的吐槽。以匯入 Cloudflare 上託管的 DNS 記錄為例，前文提到過，如果 Provider 支援，可以使用 Terraform 在 1.12 版本之後引入的&lt;code&gt;terraform query&lt;/code&gt;，然而大多數情況下，Providers 都是沒有跟進這個新功能的，Cloudflare 就屬於不支援的那一類。&lt;/p&gt;&#xA;&lt;p&gt;好在雖然 Hashi Corp. 不認真解決問題，各路高強度使用 Terraform 管理基礎設施的公司就各顯神通。Cloudflare 就維護了名為 &lt;a class=&#34;link&#34; href=&#34;https://github.com/cloudflare/cf-terraforming&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;cf-terraforming&lt;/a&gt; 的匯入工具，免去了很多手動匯入的麻煩。首先安裝&lt;code&gt;cf-terraforming&lt;/code&gt;，這個工具是用 Go 語言編寫的，需要在有 Go 語言環境的情況下安裝，或者將官方編譯好的二進位制檔案其放入&lt;code&gt;$PATH&lt;/code&gt;並賦予其執行許可權。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;go install github.com/cloudflare/cf-terraforming/cmd/cf-terraforming@latest&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這個工具的用法還是比較簡單的，首先你需要以下環境變數：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# 如果你使用 API Token&#xA;export CLOUDFLARE_API_TOKEN=&#39;Hzsq3Vub-7Y-hSTlAaLH3Jq_YfTUOCcgf22_Fs-j&#39;&#xA;&#xA;# 如果你使用 API Key&#xA;export CLOUDFLARE_EMAIL=&#39;user@example.com&#39;&#xA;export CLOUDFLARE_API_KEY=&#39;1150bed3f45247b99f7db9696fffa17cbx9&#39;&#xA;&#xA;# 指定需要匯入的域名的區域 ID，如果匯入的是賬户資源（例如 Cloudflare Tunnel）則不需要&#xA;export CLOUDFLARE_ZONE_ID=&#39;81b06ss3228f488fh84e5e993c2dc17&#39;&lt;/code&gt;&lt;/pre&gt;&lt;blockquote class=&#34;alert alert-tip&#34;&gt;&#xA;        &lt;div class=&#34;alert-header&#34;&gt;&#xA;            &lt;span class=&#34;alert-icon&#34;&gt;💡&lt;/span&gt;&#xA;            &lt;span class=&#34;alert-title&#34;&gt;提示&lt;/span&gt;&#xA;        &lt;/div&gt;&#xA;        &lt;div class=&#34;alert-body&#34;&gt;&#xA;            &lt;p&gt;此處的命令假設你正在使用 Bash，如果使用的是與 Bash 語法不相容的 Shell，則需要做出調整，例如對於 Windows 上的 PowerShell，匯入環境變數的語法如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-pwsh&#34;&gt;$env:CLOUDFLARE_API_TOKEN=&#39;Hzsq3Vub-7Y-hSTlAaLH3Jq_YfTUOCcgf22_Fs-j&#39;&lt;/code&gt;&lt;/pre&gt;&#xA;        &lt;/div&gt;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;通常我們只需要設定&lt;code&gt;CLOUDFLARE_API_TOKEN&lt;/code&gt;和&lt;code&gt;CLOUDFLARE_ZONE_ID&lt;/code&gt;就可以了，在控制枱建立 API Token 時，記得賦予這個 Token 必要的許可權，本次我們只是匯入 DNS 記錄，所以只賦予編輯區域 DNS 的許可權即可。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2026/04/iac-with-terraform/image-1_hu_15298d4bd366a377.webp&#34; alt=&#34;賦予操作資源所需要的許可權&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;好了，準備工作全部完成，現在可以開始生成配置檔案：&lt;/p&gt;&#xA;&lt;p&gt;首先匯入賬户中域名的配置，也就是&lt;code&gt;cloudflare_zone&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cf-terraforming generate \&#xA;  --key $CLOUDFLARE_API_KEY \&#xA;  --resource-type &#34;cloudflare_zone&#34; &gt; zone.tf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這一步會在當前目錄下生成一個名為&lt;code&gt;zone.tf&lt;/code&gt;的檔案，裏面會有如下格式的內容：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;resource &#34;cloudflare_zone&#34; &#34;REDACTED&#34; {&#xA;  name                = &#34;REDACTED&#34;&#xA;  paused              = false&#xA;  type                = &#34;full&#34;&#xA;  vanity_name_servers = []&#xA;  account = {&#xA;    id   = &#34;REDACTED&#34;&#xA;    name = &#34;REDACTED&#34;&#xA;  }&#xA;}&#xA;&#xA;resource &#34;cloudflare_zone&#34; &#34;REDACTED&#34; {&#xA;  name                = &#34;REDACTED&#34;&#xA;  paused              = false&#xA;  type                = &#34;full&#34;&#xA;  vanity_name_servers = []&#xA;  account = {&#xA;    id   = &#34;REDACTED&#34;&#xA;    name = &#34;REDACTED&#34;&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;現在域名資源已經匯入了，但是裏面的配置並沒有。接下來，匯入域名下的 DNS 記錄：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cf-terraforming generate \&#xA;  --zone $CLOUDFLARE_ZONE_ID \&#xA;  --key $CLOUDFLARE_API_KEY \&#xA;  --resource-type &#34;cloudflare_dns_record&#34; &gt;&gt; dns.tf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這一步會在當前目錄下生成一個名為&lt;code&gt;dns.tf&lt;/code&gt;的配置檔案，裏面會有如下格式的內容：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_5deb14xxxxxb629bf123xxxxxxxc8f_0&#34; {&#xA;  content  = &#34;67.24.33.108&#34;&#xA;  name     = &#34;example.example.com&#34;&#xA;  proxied  = true&#xA;  tags     = []&#xA;  ttl      = 1&#xA;  type     = &#34;A&#34;&#xA;  zone_id  = &#34;81c7f2de8dfxxxxxx52629xxxxxxfc&#34;&#xA;  settings = {}&#xA;}&#xA;&#xA;resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_89xxxxx0bf9cxxxxxx9a_1&#34; {&#xA;  content  = &#34;35.27.108.33&#34;&#xA;  name     = &#34;terraform.example.com&#34;&#xA;  proxied  = true&#xA;  tags     = []&#xA;  ttl      = 1&#xA;  type     = &#34;A&#34;&#xA;  zone_id  = &#34;8xxxxxx7644e428526xxxxxx&#34;&#xA;  settings = {}&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果有多個域名需要匯入，則多次分別設定環境變數&lt;code&gt;CLOUDFLARE_ZONE_ID&lt;/code&gt;再重複執行命令即可。&lt;/p&gt;&#xA;&lt;p&gt;這裏生成的配置檔案可以直接使用，也就是我們之後需要的 Terraform 配置檔案。然而，此時我們僅僅只是生成了配置檔案，但是目前 Terraform 的狀態依然是空的，這時要是直接&lt;code&gt;terraform apply&lt;/code&gt;，Terraform 會不管三七二十一將我們剛剛匯入的宣告一律視為新增資源，然後甩出一大堆「Alredy Exists」報錯。所以接下來，我們需要將生成的配置檔案匯入 Terraform 的&lt;code&gt;terraform.tfstate&lt;/code&gt;狀態。&lt;/p&gt;&#xA;&lt;p&gt;Terraform 在 1.5 版本引入了&lt;code&gt;import&lt;/code&gt;塊，相比以往一行一行輸入命令的方式更加現代。其匯入流程是先生成一個包含&lt;code&gt;import&lt;/code&gt;塊的&lt;code&gt;.tf&lt;/code&gt;檔案，下次進行&lt;code&gt;terraform apply&lt;/code&gt;時，Terraform 就會自動為我們執行匯入操作。&lt;/p&gt;&#xA;&lt;p&gt;生成&lt;code&gt;cloudflare_zone&lt;/code&gt;的&lt;code&gt;import&lt;/code&gt;塊：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cf-terraforming import \&#xA;  --resource-type &#34;cloudflare_zone&#34; \&#xA;  --modern-import-block \&#xA;  --key $CLOUDFLARE_API_KEY \&#xA;  --zone $CLOUDFLARE_ZONE_ID &gt;&gt; import.tf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;生成&lt;code&gt;cloudflare_dns_record&lt;/code&gt;的&lt;code&gt;import&lt;/code&gt;塊：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cf-terraforming import \&#xA;  --resource-type &#34;cloudflare_dns_record&#34; \&#xA;  --modern-import-block \&#xA;  --key $CLOUDFLARE_API_KEY \&#xA;  --zone $CLOUDFLARE_ZONE_ID &gt;&gt; import.tf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這一步會在當前目錄下生成&lt;code&gt;import.tf&lt;/code&gt;，它包含了所需要的匯入資訊，作用就是告訴 Terraform 上一步生成的每個&lt;code&gt;resource&lt;/code&gt;塊到底對應的是哪一個雲服務提供商的資源 ID。這個 ID 是雲服務提供商內部標記資源的代碼，平常是不會在控制面板上顯示的，只有在用 API 特別請求時才會知道。Terraform 在匯入過程中需要用到這個 ID 以確認本地的定義對應的雲端資源，以實現嚴格的冪等性。&lt;/p&gt;&#xA;&lt;p&gt;好了，現在我們執行&lt;code&gt;terraform plan&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ terraform plan&#xA;cloudflare_dns_record.minio_a: Refreshing state... [id=xxxxxxxxxxx53]&#xA;cloudflare_zero_trust_tunnel_cloudflared_config.raspberrypi: Refreshing state...&#xA;......&#xA;&#xA;Terraform will perform the following actions:&#xA;&#xA;  # cloudflare_dns_record.terraform_managed_resource_0 will be imported&#xA;    resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_REDACTED_0&#34; {&#xA;        content     = &#34;67.24.33.108&#34;&#xA;        created_on  = &#34;2026-04-08T10:18:12Z&#34;&#xA;        id          = &#34;5deb14c21xxxxxxx20f1c8f&#34;&#xA;        meta        = jsonencode({})&#xA;        modified_on = &#34;2026-04-08T10:18:12Z&#34;&#xA;        name        = &#34;example.example.com&#34;&#xA;        proxiable   = true&#xA;        proxied     = true&#xA;        settings    = {}&#xA;        tags        = []&#xA;        ttl         = 1&#xA;        type        = &#34;A&#34;&#xA;        zone_id     = &#34;REDACTED&#34;&#xA;    }&#xA;&#xA;  # cloudflare_dns_record.terraform_managed_resource_1 will be imported&#xA;    resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_89c149exxxxxxxxxxxba13xxxxxa_1&#34; {&#xA;        content     = &#34;35.27.108.33&#34;&#xA;        created_on  = &#34;2026-04-08T10:17:54Z&#34;&#xA;        id          = &#34;89cxxxxxxxxxxxxxxxxxx09a&#34;&#xA;        meta        = jsonencode({})&#xA;        modified_on = &#34;2026-04-08T10:17:54Z&#34;&#xA;        name        = &#34;terraform.example.com&#34;&#xA;        proxiable   = true&#xA;        proxied     = true&#xA;        settings    = {}&#xA;        tags        = []&#xA;        ttl         = 1&#xA;        type        = &#34;A&#34;&#xA;        zone_id     = &#34;81xxxxxxxxxxxxxxxxxxxxxfc&#34;&#xA;    }&#xA;&#xA;Plan: 2 to import, 0 to add, 0 to change, 0 to destroy.&#xA;&#xA;────────────────────────────────────────────────────────────────────────────────────────────────────────&#xA;&#xA;Note: You didn&#39;t use the -out option to save this plan, so Terraform can&#39;t guarantee to take exactly&#xA;these actions if you run &#34;terraform apply&#34; now.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果需要 Add、Change 和 Destroy 的資源數量都是 0，説明我們的匯入操作沒有問題，直接&lt;code&gt;terraform apply --auto-approve&lt;/code&gt;，資源就匯入完成了。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ terraform apply --auto-approve&#xA;cloudflare_dns_record.push_a: Refreshing state... [id=REDACTED]&#xA;&#xA;Terraform will perform the following actions:&#xA;&#xA;  # cloudflare_dns_record.terraform_managed_resource_REDACTED_0 will be imported&#xA;    resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_REDACTED_0&#34; {&#xA;        content     = &#34;67.24.33.108&#34;&#xA;        created_on  = &#34;2026-04-08T10:18:12Z&#34;&#xA;        id          = &#34;REDACTED&#34;&#xA;        meta        = jsonencode({})&#xA;        modified_on = &#34;2026-04-08T10:18:12Z&#34;&#xA;        name        = &#34;example.example.com&#34;&#xA;        proxiable   = true&#xA;        proxied     = true&#xA;        settings    = {}&#xA;        tags        = []&#xA;        ttl         = 1&#xA;        type        = &#34;A&#34;&#xA;        zone_id     = &#34;REDACTED&#34;&#xA;    }&#xA;&#xA;  # cloudflare_dns_record.terraform_managed_resource_REDACTED_1 will be imported&#xA;    resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_REDACTED_1&#34; {&#xA;        content     = &#34;35.27.108.33&#34;&#xA;        created_on  = &#34;2026-04-08T10:17:54Z&#34;&#xA;        id          = &#34;REDACTED&#34;&#xA;        meta        = jsonencode({})&#xA;        modified_on = &#34;2026-04-08T10:17:54Z&#34;&#xA;        name        = &#34;terraform.example.com&#34;&#xA;        proxiable   = true&#xA;        proxied     = true&#xA;        settings    = {}&#xA;        tags        = []&#xA;        ttl         = 1&#xA;        type        = &#34;A&#34;&#xA;        zone_id     = &#34;REDACTED&#34;&#xA;    }&#xA;&#xA;Plan: 2 to import, 0 to add, 0 to change, 0 to destroy.&#xA;cloudflare_dns_record.terraform_managed_resource_REDACTED_1: Importing... [id=REDACTED/REDACTED]&#xA;cloudflare_dns_record.terraform_managed_resource_REDACTED_1: Import complete [id=REDACTED/REDACTED]&#xA;cloudflare_dns_record.terraform_managed_resource_REDACTED_0: Importing... [id=REDACTED/REDACTED]&#xA;cloudflare_dns_record.terraform_managed_resource_REDACTED_0: Import complete [id=REDACTED/REDACTED]&#xA;&#xA;Apply complete! Resources: 2 imported, 0 added, 0 changed, 0 destroyed.&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;狀態儲存和持續整合&#34;&gt;狀態儲存和持續整合&#xA;&lt;/h2&gt;&lt;p&gt;IaC 的核心價值之一就在於可以輕易實現基於 Git 的多人協作，以及 CI 的持續整合，但是在此之前，又有一個新的問題需要解決——&lt;code&gt;terraform.tfstate&lt;/code&gt;到底放哪裏：沒人想要換一次環境辛辛苦苦匯入的狀態就丟一次。&lt;/p&gt;&#xA;&lt;p&gt;Terraform 目前支援以下幾種儲存狀態的後端：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;local&lt;/li&gt;&#xA;&lt;li&gt;remote&lt;/li&gt;&#xA;&lt;li&gt;azurerm&lt;/li&gt;&#xA;&lt;li&gt;consul&lt;/li&gt;&#xA;&lt;li&gt;cos&lt;/li&gt;&#xA;&lt;li&gt;gcs&lt;/li&gt;&#xA;&lt;li&gt;http&lt;/li&gt;&#xA;&lt;li&gt;Kubernetes&lt;/li&gt;&#xA;&lt;li&gt;oci&lt;/li&gt;&#xA;&lt;li&gt;oss&lt;/li&gt;&#xA;&lt;li&gt;pg&lt;/li&gt;&#xA;&lt;li&gt;s3&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;沒有特殊需求可以像我一樣選擇&lt;code&gt;s3&lt;/code&gt;，畢竟 Cloudflare R2 有免費額度，不用白不用。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;terraform {&#xA;  backend &#34;s3&#34; {&#xA;    bucket = &#34;terraform&#34;&#xA;    key    = &#34;terraform.tfstate&#34;&#xA;    region = &#34;auto&#34;&#xA;    endpoints = {&#xA;      s3 = &#34;https://REDACTED.r2.cloudflarestorage.com&#34;&#xA;    }&#xA;&#xA;    # R2 不需要這些 AWS 的驗證&#xA;    skip_credentials_validation = true&#xA;    skip_metadata_api_check     = true&#xA;    skip_region_validation      = true&#xA;    skip_requesting_account_id  = true&#xA;    use_path_style              = true&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;對於 S3 的後端，建議用&lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt;和&lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;兩個環境變數來儲存 Credentials：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;export AWS_ACCESS_KEY_ID=&#39;REDACTED&#39;&#xA;export AWS_SECRET_ACCESS_KEY=&#39;REDACTED&#39;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;配置完成，執行&lt;code&gt;terraform init -migrate-state&lt;/code&gt;，配置就成功儲存到雲上了，以後不論在何處修改配置、執行&lt;code&gt;terraform apply&lt;/code&gt;都無須擔心 Terraform 的 State 不同步的問題。&lt;/p&gt;&#xA;&lt;p&gt;接下來就是 Github CI 的配置，其實很簡單，無非就是每次&lt;code&gt;git push&lt;/code&gt;時觸發一次&lt;code&gt;terraform init&lt;/code&gt;、&lt;code&gt;terraform fmt&lt;/code&gt;和&lt;code&gt;terraform apply&lt;/code&gt;，以下是我的&lt;code&gt;.github/workflows/apply.yml&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;name: &#34;Terraform Apply&#34;&#xA;&#xA;on:&#xA;  push:&#xA;    branches:&#xA;      - main&#xA;&#xA;env:&#xA;  TF_IN_AUTOMATION: &#34;true&#34;&#xA;  CLOUDFLARE_API_TOKEN: &#34;${{ secrets.CLOUDFLARE_API_TOKEN }}&#34;&#xA;  AWS_ACCESS_KEY_ID: &#34;${{ secrets.AWS_ACCESS_KEY_ID }}&#34;&#xA;  AWS_SECRET_ACCESS_KEY: &#34;${{ secrets.AWS_SECRET_ACCESS_KEY }}&#34;&#xA;  TF_VAR_cloudflare_zone_id_example_com: &#34;${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_COM }}&#34;&#xA;  TF_VAR_cloudflare_zone_id_example_top: ${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_TOP }}&#xA;&#xA;jobs:&#xA;  terraform:&#xA;    name: &#34;Terraform Apply&#34;&#xA;    runs-on: ubuntu-latest&#xA;    permissions:&#xA;      contents: read&#xA;    concurrency:&#xA;      group: terraform-apply&#xA;      cancel-in-progress: false&#xA;    steps:&#xA;      - name: Checkout&#xA;        uses: actions/checkout@v6&#xA;&#xA;      - name: Setup Terraform&#xA;        uses: hashicorp/setup-terraform@v4&#xA;&#xA;      - name: Terraform Init&#xA;        run: terraform init -input=false&#xA;&#xA;      - name: Terraform Apply&#xA;        run: terraform apply -input=false -auto-approve&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;對於 PR 則應當讓 CI 自動為每次 PR 附上&lt;code&gt;terraform plan&lt;/code&gt;的輸出：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;name: Terraform Plan&#xA;&#xA;on:&#xA;  pull_request:&#xA;    paths:&#xA;      - &#34;**/*.tf&#34;&#xA;      - &#34;.github/workflows/terraform-plan.yml&#34;&#xA;&#xA;permissions:&#xA;  contents: read&#xA;  pull-requests: write&#xA;&#xA;env:&#xA;  TF_IN_AUTOMATION: &#34;true&#34;&#xA;  CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}&#xA;  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}&#xA;  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}&#xA;  TF_VAR_cloudflare_zone_id_example_com: &#34;${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_COM }}&#34;&#xA;  TF_VAR_cloudflare_zone_id_example_top: ${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_TOP }}&#xA;&#xA;jobs:&#xA;  plan:&#xA;    name: Terraform Plan&#xA;    runs-on: ubuntu-latest&#xA;    steps:&#xA;      - uses: actions/checkout@v6&#xA;&#xA;      - uses: hashicorp/setup-terraform@v4&#xA;&#xA;      - name: Terraform fmt&#xA;        id: fmt&#xA;        run: terraform fmt -check -recursive&#xA;        continue-on-error: true&#xA;&#xA;      - name: Terraform Init&#xA;        id: init&#xA;        run: terraform init -input=false&#xA;&#xA;      - name: Terraform Validate&#xA;        id: validate&#xA;        run: terraform validate -no-color&#xA;&#xA;      - name: Terraform Plan&#xA;        id: plan&#xA;        run: terraform plan -input=false -no-color&#xA;        continue-on-error: true&#xA;&#xA;      - name: Post Plan to PR&#xA;        uses: actions/github-script@v8&#xA;        with:&#xA;          github-token: ${{ secrets.GITHUB_TOKEN }}&#xA;          script: |&#xA;            const { data: comments } = await github.rest.issues.listComments({&#xA;              owner: context.repo.owner,&#xA;              repo: context.repo.repo,&#xA;              issue_number: context.issue.number,&#xA;            });&#xA;            const botComment = comments.find(c =&gt;&#xA;              c.user.type === &#39;Bot&#39; &amp;&amp; c.body.includes(&#39;&lt;!-- terraform-plan --&gt;&#39;)&#xA;            );&#xA;&#xA;            const planOutput = `${{ steps.plan.outputs.stdout }}`.substring(0, 65000);&#xA;&#xA;            const body = `&lt;!-- terraform-plan --&gt;&#xA;            #### Terraform Plan&#xA;&#xA;            | Step     | Result                            |&#xA;            | -------- | --------------------------------- |&#xA;            | fmt      | \`${{ steps.fmt.outcome }}\`      |&#xA;            | init     | \`${{ steps.init.outcome }}\`     |&#xA;            | validate | \`${{ steps.validate.outcome }}\` |&#xA;            | plan     | \`${{ steps.plan.outcome }}\`     |&#xA;&#xA;            &lt;details&gt;&lt;summary&gt;展開 Plan 詳情&lt;/summary&gt;&#xA;&#xA;            \`\`\`terraform&#xA;            ${planOutput}&#xA;            \`\`\`&#xA;            &lt;/details&gt;`;&#xA;&#xA;            if (botComment) {&#xA;              await github.rest.issues.updateComment({&#xA;                owner: context.repo.owner,&#xA;                repo: context.repo.repo,&#xA;                comment_id: botComment.id,&#xA;                body&#xA;              });&#xA;            } else {&#xA;              await github.rest.issues.createComment({&#xA;                issue_number: context.issue.number,&#xA;                owner: context.repo.owner,&#xA;                repo: context.repo.repo,&#xA;                body&#xA;              });&#xA;            }&#xA;&#xA;      - name: Fail if plan failed&#xA;        if: steps.plan.outcome == &#39;failure&#39;&#xA;        run: exit 1&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2026/04/iac-with-terraform/image-2_hu_c922b7d4de19a009.webp&#34; alt=&#34;每次 PR 都會有 Plan 的輸出&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h2 id=&#34;後續的工作流程&#34;&gt;後續的工作流程&#xA;&lt;/h2&gt;&lt;p&gt;到這一步，Terraform 的「接管初始化」已經結束了，後面就進入了日常維護階段。這個階段其實就三件事：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;建立新資源&lt;/li&gt;&#xA;&lt;li&gt;修改現有的資源&lt;/li&gt;&#xA;&lt;li&gt;刪除不再需要的資源&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;常用的操作就兩個：&lt;code&gt;terraform plan&lt;/code&gt;和&lt;code&gt;terraform apply&lt;/code&gt;，如果是個人使用，小的改動直接提交就算了；如果是團隊協作，每次修改則應當遵循能 PR 就不直接 Commit 的原則。&lt;/p&gt;&#xA;&lt;h3 id=&#34;建立基礎設施&#34;&gt;建立基礎設施&#xA;&lt;/h3&gt;&lt;p&gt;假設你現在要新增一條 DNS 記錄，或者新建一個 Tunnel、一個物件儲存桶，流程如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;  C1[新建分支&#xA;如 feat/add-minio-record] --&gt; C2[新增 resource 塊]&#xA;  C2 --&gt; C3[terraform fmt + validate]&#xA;  C3 --&gt; C4[terraform plan]&#xA;  C4 --&gt; C5{僅新增預期資源?}&#xA;  C5 -- 否 --&gt; C6[修正配置後重跑 plan]&#xA;  C6 --&gt; C4&#xA;  C5 -- 是 --&gt; C7[提交 PR]&#xA;  C7 --&gt; C8[合併後 CI apply]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;最理想的&lt;code&gt;plan&lt;/code&gt;輸出是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;X to add&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;0 to change&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;0 to destroy&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;如果你只是想加東西，結果出現了&lt;code&gt;to destroy&lt;/code&gt;，那就先別衝動，通常是引用寫錯、變數搞錯，或者不小心改了資源地址，仔細檢查是什麼地方出了問題。&lt;/p&gt;&#xA;&lt;h3 id=&#34;修改與刪除基礎設施&#34;&gt;修改與刪除基礎設施&#xA;&lt;/h3&gt;&lt;p&gt;修改流程和建立類似，但要多一步——評估變更是否會觸發重建。&lt;/p&gt;&#xA;&lt;p&gt;因為很多 Provider 欄位是&lt;code&gt;ForceNew&lt;/code&gt;，你以為只是改個欄位，Terraform 看完説：「好的，刪了重建。」這在我們修改 DNS 的這個場景並不是什麼很大的問題，但是到了雲例項這種資源，如果刪除重建勢必會造成損失。&lt;/p&gt;&#xA;&lt;p&gt;建議按下面這個順序來：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;  M1[修改 .tf] --&gt; M2[terraform plan]&#xA;  M2 --&gt; M3{出現 replace/destroy?}&#xA;  M3 -- 否 --&gt; M7[確認影響範圍]&#xA;  M7 --&gt; M8[terraform apply]&#xA;  M3 -- 是 --&gt; M4[暫停並複核變更]&#xA;  M4 --&gt; M5[必要時加 lifecycle 保護]&#xA;  M5 --&gt; M6[安排變更視窗]&#xA;  M6 --&gt; M8&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;對生產環境來説，建立、修改得慢一點並不是什麼很大的問題，最應當看重的是操作的正確性，慢一點，不要出錯。IaC 不是比手速，IaC 比的是可預期性。&lt;/p&gt;&#xA;&lt;p&gt;如果是刪除資源（比如下線某個 DNS 記錄、清理廢棄 Tunnel），走下面這個流程&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;  D1[確認資源已廢棄&#xA;  檢查業務/監控/指令碼依賴] --&gt; D2[刪除 resource 塊或調整 count/for_each]&#xA;  D2 --&gt; D3[terraform plan]&#xA;  D3 --&gt; D4{to destroy 是否符合預期?}&#xA;  D4 -- 否 --&gt; D5[回滾修改並繼續排查依賴]&#xA;  D5 --&gt; D1&#xA;  D4 -- 是 --&gt; D6[準備回滾方案並選低峯視窗]&#xA;  D6 --&gt; D7[PR 稽核透過]&#xA;  D7 --&gt; D8[terraform apply]&#xA;  D8 --&gt; D9[刪除資源後的可用性檢查]&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;日常協作建議&#34;&gt;日常協作建議&#xA;&lt;/h3&gt;&lt;ul&gt;&#xA;&lt;li&gt;把 Credentials 放環境變數或 CI Secret，別寫進&lt;code&gt;.tf&lt;/code&gt;和倉庫&lt;/li&gt;&#xA;&lt;li&gt;對關鍵資源開啓保護策略，防止誤刪&lt;/li&gt;&#xA;&lt;li&gt;將目錄按資源型別拆分&lt;/li&gt;&#xA;&lt;li&gt;定期執行&lt;code&gt;terraform plan&lt;/code&gt;做基礎設施漂移檢查&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;和校正，避免在面板誤操作手動修改&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;雖然這套流程看起來很麻煩，但每一次變更都有記錄、可審計、可回滾，最重要的是可復現，這才是 IaC 最有價值的地方。&lt;/p&gt;&#xA;&lt;h2 id=&#34;蔘考&#34;&gt;蔘考&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://candinya.com/posts/manage-cloudflare-dns-with-terraform/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;使用 Terraform 管理 CloudFlare 上的 DNS 解析記錄 - Candinya&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://developer.hashicorp.com/terraform/tutorials/automation/github-actions&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Automate Terraform with GitHub Actions&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://developer.hashicorp.com/terraform/language/files/tfquery&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Query configuration files&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://developer.hashicorp.com/terraform/language/block/tfquery/list&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;list block reference&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://github.com/cloudflare/cf-terraforming&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;cloudflare/cf-terraforming&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://developers.cloudflare.com/terraform/advanced-topics/import-cloudflare-resources/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Import Cloudflare resources&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Cloudflare Provider - Terraform Registry&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://en.wikipedia.org/wiki/Infrastructure_as_code&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Infrastructure as Code&lt;/a&gt;，是指採用機器可讀的配置檔案定義所需要的基礎設施的部署方法。&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;每個供應商的配置檔案欄位命名和格式都有區別，但這可以很容易的編寫指令碼進行轉換。&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;需要特別注意的是，狀態檔案中可能包含數據庫密碼、API 金鑰等明文儲存的敏感資訊，因此絕對不要將 &lt;code&gt;.tfstate&lt;/code&gt; 檔案提交到公開的代碼倉庫中。&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;HashiCorp Configuration Language，HashiCorp 自家開發的一種宣告式配置語言，旨在兼顧機器可讀性與人類可讀性。&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:5&#34;&gt;&#xA;&lt;p&gt;冪等性（Idempotence）指計算機系統或介面在接收到同一請求的多次操作時，產生的影響與一次執行的結果相同，不論執行多少次，系統的最終狀態始終保持一致。&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:6&#34;&gt;&#xA;&lt;p&gt;如果你僅僅是想讓 Terraform 不再管理某個資源，而不是真正在雲端銷燬它，應該使用 &lt;code&gt;terraform state rm&lt;/code&gt; 命令，而不是在代碼中刪掉資源塊然後 &lt;code&gt;apply&lt;/code&gt;，否則雲環境上的真實資源也會被一併銷燬。&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:7&#34;&gt;&#xA;&lt;p&gt;基礎設施漂移（Infrastructure Drift）是指現實中透過控制枱點按等非 IaC 途徑修改了基礎設施，導致其實際狀態與代碼中宣告的狀態不一致的情況。&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item></channel>
</rss>
