文章詳情

AWS帳號購買開通 AWS Route 53 API自動化更新DNS記錄設定教學

亞馬遜雲AWS2026-07-08 13:11:12阿里雲

第一章:為什麼要做 DNS 自動化

做過網站或服務的人都知道,DNS 看似只是幾條簡單的設定,但它往往是「上線節點」的核心:從測試環境切換到正式環境、讓外部流量導向新機房、更新負載均衡或雲端資源的 IP,幾乎都要改 DNS 記錄。

手動更新通常會帶來幾個問題。第一是速度:當你需要在短時間內完成切換,人工操作可能讓你錯過窗口。第二是一致性:同一套服務的多個子網域、不同環境(dev/stage/prod)若靠人腦記憶,難免發生漏改、改錯或覆蓋舊值。第三是可追溯:你需要知道是誰、何時、改成什麼,最好能留存變更原因與對應的部署版本。

使用 Route 53 API 來更新 DNS 記錄,就是把「人手點選」改成「程式化流程」。一旦你的部署系統(CI/CD)或更新腳本能控制 Route 53,就能把 DNS 切換納入同一個發佈流程,並且在失敗時能更安全地回滾。

第二章:Route 53 的基本概念(先弄懂,再談自動)

Route 53 主要負責管理「托管區域」(Hosted Zone)。你在主控台看到的每個網域(例如 example.com)都對應到一個 Hosted Zone。Hosted Zone 底下是一組 DNS 記錄(Record Sets),例如:

  • A 記錄:網域名稱對應 IPv4 地址
  • AAAA 記錄:對應 IPv6 地址
  • CNAME 記錄:對應另一個網域名稱
  • MX、TXT、NS 等:用於郵件、驗證或委派
  • Alias:AWS 內部資源(例如 ALB、CloudFront)常用,能在不手動維護目標 IP 的情況下完成解析

當你要更新 DNS 設定時,不是直接「改一個欄位」,而是提交一個變更批次(Change Batch)。你會呼叫 Route 53 API 送出若干條 Record change(新增、修改或刪除),Route 53 會產生一個 Change 資源,並在一段時間內完成同步。

因此,自動化腳本要處理兩件事:第一,正確描述要變更的 record set;第二,針對提交後的狀態回覆做等待或驗證。

第三章:需求拆解:你到底要自動化什麼

在動手寫程式前,我建議你先把需求拆成可計算、可驗證的部分。常見的需求樣式如下:

3.1 固定記錄,依環境切換值

例如:dev.example.com、staging.example.com、www.example.com 對應不同目的地 IP 或負載平衡器。此時你要的是「同一組 record 名稱、不同目標值」。

3.2 目的地由部署產生

例如你更新了 EC2 / ECS / EKS 的服務端點,會得到新的 ALB DNS 名稱或 IP。此時腳本要能把部署產生的輸出值帶入 Route 53 API。

3.3 只改特定記錄型別

有些團隊只允許更新 A 或 AAAA,其他如 TXT 或 MX 可能牽涉到驗證流程。你可以在腳本中限制可變更的型別與範圍,減少誤操作。

3.4 要不要支援多值(例如多個 A 記錄)

Route 53 允許同一個名稱下有多筆值。你必須決定更新策略:要清掉舊值只保留新值,還是保留舊值並新增新值。自動化的錯誤往往發生在「到底要刪哪些舊值」沒有被嚴格定義。

第四章:IAM 最小權限與安全設計

自動化腳本最怕的是權限太大。你可以把權限壓到只允許更新特定 Hosted Zone 的記錄。Route 53 API 常用到的動作與資源型態如下:

  • route53:ChangeResourceRecordSets:提交變更
  • route53:ListHostedZones 或 route53:ListResourceRecordSets:查詢(可選,若你有 ZoneId 與 record 結構就能減少查詢需求)
  • route53:GetChange:查詢變更狀態

策略上,建議做法是:

  • 為每個環境(dev/stage/prod)建立不同角色或不同權限範圍
  • 限制 Hosted Zone ID(Resource)只允許目標 zone
  • 用短期憑證(例如 CI 角色、OIDC)避免長期金鑰

此外,你的腳本要避免把敏感資訊印到 log。尤其是 AWS access key、session token,應完全不要輸出。

第五章:選擇你的程式介面與流程

AWS帳號購買開通 Route 53 的核心方法是 ChangeResourceRecordSets。流程通常是:

  1. 準備輸入:ZoneId、RecordName、RecordType、TTL(若非 Alias)、新值或 Alias 指標
  2. 組合 ChangeBatch:每條變更包含 Action(UPSERT / DELETE / CREATE 等)與 ResourceRecordSet
  3. 送出 ChangeResourceRecordSets,取得 ChangeId
  4. 輪詢 GetChange,直到狀態變為 INSYNC 或超時
  5. (可選)再做驗證:查詢 DNS 或檢查 Route 53 記錄內容

最常見的策略是用 UPSERT。UPSERT 的意思是:如果該 record set 存在就更新,不存在就新增。對自動化來說,它能降低「先查再改」的複雜度,也能提高冪等性。

第六章:ChangeResourceRecordSets 解析(你需要知道的欄位)

你提交的 ResourceRecordSet 會包含以下重點欄位(依 record type 與是否 Alias 而定):

  • Name:DNS 名稱(例如 www.example.com),通常要確保是否有尾端的點(.)。Route 53 API 對格式較敏感,建議使用一致的寫法:要麼全部加尾端點,要麼全部不加,但要確保符合你既有資料。
  • Type:A、AAAA、CNAME、MX、TXT…
  • TTL:TTL 秒數(若是 Alias,TTL 可能由目標資源決定,Alias 設定會使用 AliasTarget 取代 TTL)
  • AWS帳號購買開通 ResourceRecords:提供具體的值陣列(例如 A 記錄是 '1.2.3.4')
  • AliasTarget:當你要指向 AWS 資源時使用,包含 DNSName、HostedZoneId、EvaluateTargetHealth(是否評估健康狀態)
  • SetIdentifier:若你使用加權路由或其他多樣策略,必須包含該識別碼

AWS帳號購買開通 另外,ChangeBatch 裡有 Changes 陣列。每筆 change 通常用 UPSERT。若你需要徹底刪除某值,才會使用 DELETE,但自動化一般避免直接做高風險刪除,除非你能完整掌握當前狀態。

第七章:實作範本(以 Node.js 為例)

下面給一個偏實務的範本,假設你要做的是「更新或新增 A 記錄」。你可以把腳本丟到 CI/CD,在每次部署完成後呼叫它。

前提:你已經設定 AWS 認證(例如 CI 使用 AssumeRole)。

7.1 A 記錄 UPSERT 範本

這個程式會把 target IP 寫入到指定的 Hosted Zone 裡的指定 record。你只要改變參數,就能對不同環境執行。

const { Route53Client, ChangeResourceRecordSetsCommand, GetChangeCommand } = require('@aws-sdk/client-route-53');

const client = new Route53Client({ region: process.env.AWS_REGION || 'us-east-1' });

async function upsertARecord({ zoneId, recordName, ip, ttl = 60, timeoutMs = 120000 }) {
  const changeCommand = new ChangeResourceRecordSetsCommand({
    HostedZoneId: zoneId,
    ChangeBatch: {
      Changes: [
        {
          Action: 'UPSERT',
          ResourceRecordSet: {
            Name: recordName,
            Type: 'A',
            TTL: ttl,
            ResourceRecords: [{ Value: ip }]
          }
        }
      ]
    }
  });

  const changeResp = await client.send(changeCommand);
  const changeId = changeResp.ChangeInfo?.Id;

  if (!changeId) throw new Error('Route53 change id missing');

  const start = Date.now();
  while (true) {
    if (Date.now() - start > timeoutMs) {
      throw new Error(`Timeout waiting for Route53 change to be INSYNC: ${changeId}`);
    }

    const getResp = await client.send(new GetChangeCommand({ Id: changeId }));
    const status = getResp.ChangeInfo?.Status;

    if (status === 'INSYNC') return { changeId, status };
    if (status === 'FAILED') {
      const message = getResp.ChangeInfo?.Comment || 'Unknown failure';
      throw new Error(`Route53 change FAILED: ${message}`);
    }

    await new Promise(r => setTimeout(r, 3000));
  }
}

// Example usage
(async () => {
  const zoneId = process.env.ZONE_ID;
  const recordName = process.env.RECORD_NAME; // e.g. 'www.example.com.' or 'www.example.com'
  const ip = process.env.TARGET_IP; // e.g. '1.2.3.4'

  await upsertARecord({ zoneId, recordName, ip, ttl: 60 });
  console.log('Route53 record updated successfully');
})().catch(err => {
  console.error(err);
  process.exit(1);
});

你可能注意到我沒有先去查舊值,因為 UPSERT 能降低「狀態競爭」的成本。當然,如果你的場景涉及加權或多記錄組合,UPSERT 仍可用,但你要更精準地填 SetIdentifier 或符合既有記錄結構。

7.2 使用 Alias(指向 ALB / CloudFront)

如果你要把記錄指向 AWS 服務端點,Alias 是更常見的選擇,原因是它避免你手動更新目標 IP。你只需提供目標 DNS 名稱與目標 Hosted Zone ID。

const { Route53Client, ChangeResourceRecordSetsCommand, GetChangeCommand } = require('@aws-sdk/client-route-53');

const client = new Route53Client({ region: process.env.AWS_REGION || 'us-east-1' });

async function upsertAliasRecord({ zoneId, recordName, recordType = 'A', aliasDnsName, aliasHostedZoneId, evaluateTargetHealth = false, timeoutMs = 120000 }) {
  const changeCommand = new ChangeResourceRecordSetsCommand({
    HostedZoneId: zoneId,
    ChangeBatch: {
      Changes: [
        {
          Action: 'UPSERT',
          ResourceRecordSet: {
            Name: recordName,
            Type: recordType,
            AliasTarget: {
              DNSName: aliasDnsName,
              HostedZoneId: aliasHostedZoneId,
              EvaluateTargetHealth: evaluateTargetHealth
            }
          }
        }
      ]
    }
  });

  const changeResp = await client.send(changeCommand);
  const changeId = changeResp.ChangeInfo?.Id;
  if (!changeId) throw new Error('Route53 change id missing');

  const start = Date.now();
  while (true) {
    if (Date.now() - start > timeoutMs) {
      throw new Error(`Timeout waiting for Route53 change to be INSYNC: ${changeId}`);
    }

    const getResp = await client.send(new GetChangeCommand({ Id: changeId }));
    const status = getResp.ChangeInfo?.Status;

    if (status === 'INSYNC') return { changeId, status };
    if (status === 'FAILED') {
      const message = getResp.ChangeInfo?.Comment || 'Unknown failure';
      throw new Error(`Route53 change FAILED: ${message}`);
    }

    await new Promise(r => setTimeout(r, 3000));
  }
}

你需要的 aliasDnsName 與 aliasHostedZoneId 通常可以從該 AWS 服務的文件或控制台資訊取得。把它們配置成環境變數或設定檔,避免把硬編碼散落在程式裡。

第八章:冪等性與競態:讓腳本「不怕重跑」

自動化最常發生的事故之一,是腳本重跑或並行執行。比如 CI 因為網路抖動而重試,或你同時兩個 pipeline 更新同一個 record。若你的流程沒有考慮冪等性,就會出現不一致的結果或難以追蹤的狀態。

幾個實作上的建議:

  • 優先使用 UPSERT,而不是先 DELETE 再 CREATE。
  • 若 record 可能同時被多個來源更新,要設計「只允許同一個部署版本寫入」的機制,例如在 pipeline 層面做鎖或版本標記。
  • 在 log 裡記錄欲寫入的目標值、ZoneId、recordName、recordType,以及取得的 changeId。
  • 等待狀態 INSYNC 後再結束腳本,避免下一步流程太早發生。

另外,如果你有「多記錄值」的需求,務必定義完整的 ResourceRecords 陣列。不要只提供一部分值,因為 Route 53 對 record set 的匹配會依 record set 的結構判斷;你給的值不同,最終結果也可能不同。

第九章:等待狀態、超時與錯誤處理

Route 53 的變更不是立刻生效。ChangeResourceRecordSets 會回傳 changeId,你要透過 GetChange 觀察狀態。狀態常見有:

  • PENDING:尚未完全完成
  • AWS帳號購買開通 INSYNC:已完成同步
  • FAILED:失敗,需要查原因

錯誤處理上,你應該把「可重試錯誤」與「不可重試錯誤」分開看。不可重試的例子包括:record set 參數不合法、Hosted Zone ID 錯誤、權限不足。可重試的多半是網路或暫時性 API 問題。

實務上,我會建議你:

  • 對 API 送出本身做有限次重試(例如最多 3 次,退避時間逐步增加)
  • 等待 INSYNC 時設定合理超時,例如 2~5 分鐘依你的環境調整
  • 遇到 FAILED 時把 ChangeInfo.Comment 一起輸出,方便定位(例如是否與現有 record set 衝突)

第十章:驗證策略:比起「改了就算」更可靠

有些團隊以為「提交成功」就代表對外已正確解析,但 DNS 實際解析會受到 TTL、解析器快取影響。你至少可以做兩層驗證:

10.1 Route 53 層驗證

等待 INSYNC 是第一層。它代表 Route 53 內部已完成變更同步。

10.2 查驗外部解析

第二層是在 DNS 解析層面做查驗,例如用 dig 或 DNS 查詢程式測試 recordName 對應結果。注意這可能在 TTL 時間內有延遲,因此你應把查驗放在合適的流程節點,例如切流量後的健康檢查步驟。

你也可以做更完整的策略:同時做 HTTP 健康檢查、TLS 憑證是否匹配、回傳內容是否符合期望。DNS 只是導流,最後要確保服務本身正常。

第十一章:回滾與保護機制

自動化不是只為了更快,也要更可控。DNS 變更是高影響範圍,所以回滾策略必須先想好。

常見做法:

  • AWS帳號購買開通 在執行變更前,記錄「目前的 record set 值」到儲存(例如 S3、資料庫或至少 CI artifact)。回滾時把舊值寫回。
  • 為每次變更生成版本號或部署 ID,並把它寫到 log。回滾時可迅速確認回滾到哪個版本。
  • 限制變更名單:腳本只允許更新特定 recordName 列表,避免因錯誤參數誤改其他線上服務。

如果你使用 Alias 指向負載均衡器或 CloudFront,回滾會更簡單:你只需把 aliasDnsName 或目標切回上一個資源。

第十二章:實務落地:把 DNS 更新接進 CI/CD

當腳本可以穩定更新 DNS,你就需要把它放進部署流程中。一般流程可以設計成:

  1. 建置新版本並部署到新環境端點(例如啟動新的 ALB target 或建立新服務端點)
  2. 對新端點做健康檢查,確定可回應
  3. 呼叫 Route 53 API 更新 DNS 記錄(切流)
  4. 等待 INSYNC,並在一段時間內做外部解析與服務健康檢查
  5. 若成功,完成部署標記;若失敗,立即回滾 DNS 或停止流程

這樣的好處是:DNS 更新不再是脫離部署的「手動步驟」,而是整個發佈鏈路的一部分。

AWS帳號購買開通 第十三章:常見坑位與避雷

13.1 recordName 的尾端點

Route 53 在 API 與控制台呈現的格式可能會讓人混淆:有的值看起來像 'www.example.com.'(帶尾端點),有的像 'www.example.com'(不帶)。為了避免微妙差異,你要選定一種格式規範,然後所有環境一致使用。

13.2 TTL 與快取時間

即使你把 DNS 更新了,使用者端可能因快取仍解析舊值。TTL 設太高會拖慢切換效果。若你需要較快切換,可以在切換前把 TTL 設低,但這又涉及你整體 DNS 設計。

13.3 多值 record set 的匹配

如果同一個名稱同一個 Type 下有多筆 ResourceRecords(例如多個 A),你提交時必須提供完整的 record set 內容。否則可能造成意外刪除或覆蓋。

13.4 權限不足與區域(region)誤用

Route 53 的 API 在 AWS SDK 中仍需要設定 region,但 Route 53 的 Hosted Zone 是全域概念。多數情況設定成任意可用 region 都能運作,但實務上以你的環境慣例為主,並避免在多個工具之間不一致。

13.5 平行更新造成衝突

當兩個 pipeline 同時更新同一個 recordName,可能會產生覆蓋或狀態錯亂。你應用部署鎖或限制一次只允許一個 pipeline 更新該 zone 的特定記錄。

第十四章:如何讓它更像「產品」而不是腳本

AWS帳號購買開通 當你從一次性需求走向長期維運,自動化工具就會變成團隊資產。你可以做幾個改進,使它更可靠:

  • 把輸入參數改成明確的設定檔結構(例如 YAML/JSON),而不是到處傳環境變數
  • 加入 dry-run 模式:不真的送出,只驗證參數與格式
  • 加入版本與審計:每次提交寫入變更摘要到集中式日誌
  • 把允許更新的 recordName 與 recordType 做白名單
  • 建立測試:可以用 mock AWS SDK 或在測試 Hosted Zone 做端到端測試

當這些都做到位,你的 DNS 更新流程會變得可維護,也更容易在新人接手時保持一致。

結語:把 DNS 切換納入工程紀律

Route 53 API 自動化的價值,不只是「省事」。它讓 DNS 變更能被當作工程流程的一部分:有明確輸入、可追蹤的輸出、可等待的狀態、可回滾的策略。當你把它做成穩定、冪等、可驗證的工具,部署就不再依賴臨場判斷或手動操作。

下一步你可以做的事很具體:先選一條最低風險的 record(例如 dev 環境的子網域),把腳本接到 pipeline;確認 INSYNC 後外部解析符合預期;再逐步擴展到 staging 與 production,並建立回滾流程。只要你持續用同一套紀律寫下每次變更,DNS 也會變成你能掌控的系統,而不是不確定的最後一哩。

Telegram售前客服
客服ID
@cloudcup
联系
Telegram售后客服
客服ID
@yanhuacloud
联系