AWS帳號購買開通 AWS Route 53 API自動化更新DNS記錄設定教學
第一章:為什麼要做 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。流程通常是:
- 準備輸入:ZoneId、RecordName、RecordType、TTL(若非 Alias)、新值或 Alias 指標
- 組合 ChangeBatch:每條變更包含 Action(UPSERT / DELETE / CREATE 等)與 ResourceRecordSet
- 送出 ChangeResourceRecordSets,取得 ChangeId
- 輪詢 GetChange,直到狀態變為 INSYNC 或超時
- (可選)再做驗證:查詢 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,你就需要把它放進部署流程中。一般流程可以設計成:
- 建置新版本並部署到新環境端點(例如啟動新的 ALB target 或建立新服務端點)
- 對新端點做健康檢查,確定可回應
- 呼叫 Route 53 API 更新 DNS 記錄(切流)
- 等待 INSYNC,並在一段時間內做外部解析與服務健康檢查
- 若成功,完成部署標記;若失敗,立即回滾 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 也會變成你能掌控的系統,而不是不確定的最後一哩。

