こうこく
作 ▸

自宅サーバーのグローバルIPが変わったらRoute 53のAレコードを自動更新する

個人用途で自宅にWebサーバー立ててるのだが、IP固定するプロバイダ契約はしてないので、ケチってDDNSもどきの仕組みを作った。

なおドメインをAWSのRoute 53でホストしてる前提。

Fedora 39

前提

当該サーバーでAWS CLIを使えるようにしておくこと。また、必要なIAMポリシーは以下。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": "route53:ChangeResourceRecordSets",
            "Resource": "arn:aws:route53:::hostedzone/{対象のホストゾーンID}",
            "Effect": "Allow"
        },
        {
            "Action": "sns:Publish",
            "Resource": "arn:aws:sns:{リージョン}:{アカウントID}:{エラー通知先のSNSトピック名}",
            "Effect": "Allow"
        }
    ]
}

エラー通知しなくていいならSNSのポリシーは不要。

本題

以下はマシンのグローバルIPを確認して、前回チェック時から変化していればRoute 53のAレコードを更新するbashスクリプト。

エラー処理は正直よくわかってないので、もっと良い方法があれば教えてほしい。ここではSNSトピックにエラー内容を投げてる。

~/scripts/update-dns.sh
#!/bin/bash

set -euo pipefail
LF=$'\n'

# AWS CLIのパスを通す (cronで実行する時に必要)
PATH=$PATH:/usr/local/bin
# Route 53のホストゾーンID
HOSTED_ZONE_ID=ZZZZZZZZZZZZZZZZZZZZ
# Route 53のレコード名
RECORD_NAME=hogehoge.example.com

# AWS CLIでdefault以外のプロファイルを使う場合はプロファイル名を指定
export AWS_PROFILE=piyopiyo

# エラー通知先のSNSトピックARN
NOTIFICATION_TOPIC_ARN=arn:aws:sns:ap-northeast-1:123456789012:hogehoge
# エラー出力の一時保存ファイル
ERROR_LOG=$(mktemp)

# 終了時に実行する処理
cleanup() {
  # エラーログが空でない場合、内容をSNSで送信する
  if [ -s "${ERROR_LOG}" ]; then
    echo "DNS自動更新でエラーが発生しました。通知を送信します。"
    echo "エラー詳細 ->"
    cat "${ERROR_LOG}"
    local error_message=$(cat "${ERROR_LOG}")
    aws sns publish --topic-arn "${NOTIFICATION_TOPIC_ARN}" --message "DNS自動更新でエラー ->${LF}${error_message}"
  fi
  rm -f "$ERROR_LOG"
}
trap cleanup EXIT

# エラー出力をエラーログファイルにリダイレクト
exec 2> "$ERROR_LOG"

# 現在のグローバルIP取得
CURRENT_IP=$(curl -s http://checkip.amazonaws.com)

# 前回のグローバルIP取得
PREVIOUS_IP_FILE=/tmp/previous_ip
if [ -f $PREVIOUS_IP_FILE ]; then
    PREVIOUS_IP=$(cat $PREVIOUS_IP_FILE)
else
    PREVIOUS_IP=""
fi

# IPアドレスが変わっていたら…
if [ "$CURRENT_IP" != "$PREVIOUS_IP" ]; then
    # AWS CLI用の入力ファイル作成
    cat <<EOF > /tmp/change-batch.json
{
  "Comment": "Update My Home IP Address",
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "$RECORD_NAME",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [{"Value": "$CURRENT_IP"}]
      }
    }
  ]
}
EOF

    # Route53のレコードセット更新
    aws route53 change-resource-record-sets \
        --hosted-zone-id $HOSTED_ZONE_ID \
        --change-batch file:///tmp/change-batch.json

    # 現在のIPアドレスを保存
    echo $CURRENT_IP > $PREVIOUS_IP_FILE
fi

上記のファイルを作成したら、実行権限を付与しておく。

chmod +x ~/scripts/update-dns.sh

これをcronで毎日5時に実行するように設定。必要ならもっと頻繁に実行してもよいと思うが、グローバルIPの確認は外部のURLに頼ってるので、あんまり叩いていいものか迷う。

crontabの末尾に追加
(crontab -l 2>/dev/null; echo "0 5 * * * /bin/bash ~/scripts/update-dns.sh") | crontab -

なお、ここでは作業用ユーザーのcrontabに追加しているので、実行ユーザーはrootではなく作業用ユーザーとなる。

この記事に何かあればこちらまで (非公開)