Google Cloud DNS API レコードの追加、削除

世の中には Amazon Route 53 という便利な代物があり、私自身最初の頃は使っていたのですが、今は Google Cloud DNS を使っています。

その理由は・・・ゴクリ・・・・


Google Cloud DNS の方がゾーン毎の価格が安いっ!!!からです(結局どちらも安いんですがね・・・)
私はドメインを 5 個ほど所有(内 3 個が moe ドメイン)しているため、一月パン 2 個分くらいのインパクトはあるわけです。
倹約家の私としては、これは Google に移らざるを得ないというわけで、今は Google Cloud DNS を利用しています。


基本的な操作はどちらも GUI から可能です。
Zone ファイルの移行は、Google Cloud DNS だとコマンド経由になってしまいます(Googleクラウドサービスは DNS に限らず細かい事やり出すと基本的に CLI 経由ですけど)


私は(主に聖地巡礼などで)外出先から自宅の PC に接続したり、自宅サーバーの運用もしているため、グローバルIPが変わってしまうと困ります。
しかし、こんなちまちました出費も削りたがる私ですから、自宅のIPアドレスを固定化するなど不可能です。
というわけで、Aレコードを自動更新してしまいましょう。

昔は dyndns なんてありましたが、今は有料なので、自分で作ってしまいましょう。
参入障壁が低いからか他の代替となるフリーサービスもあるようですが・・・この際気にしません。
グローバルIP更新サーバーの調達及び運用費用もこの際考えないでください。本当お願いします。


自分のグローバル IP を知る方法ですが、Google に my ip でリクエストを投げる等、他サービスを活用します。
自分のグローバル IP が分かったならば、Google Cloud DNS に A レコードを設定しましょう。


Google Cloud DNS API では、レコードの更新はありません。
更新は削除と追加で対応します。
以下に実装を示します。
FIXME な所はご自身に会う形に修正してご利用ください。

GoogleDnsUtils.java

import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.dns.Dns;
import com.google.api.services.dns.model.Change;
import com.google.api.services.dns.model.ManagedZone;
import com.google.api.services.dns.model.ManagedZonesListResponse;
import com.google.api.services.dns.model.ResourceRecordSet;

public class GoogleDnsUtils {
    private static final String KEY_PATH = "FIXME.p12";

    private static final String SERVICE_ACCOUNT_EMAIL = "FIXME@developer.gserviceaccount.com";

    private static final String DNS_SCOPE = "https://www.googleapis.com/auth/ndev.clouddns.readwrite";

    private static final HttpTransport HTTP_TRANSPORT;

    private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();

    static {
        try {
            HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
        } catch (final GeneralSecurityException e) {
            throw new RuntimeException(e.getLocalizedMessage(), e);
        } catch (final IOException e) {
            throw new RuntimeException(e.getLocalizedMessage(), e);
        }
    }

    private GoogleDnsUtils() {
        throw new AssertionError();
    }

    public static GoogleCredential getGoogleCredential() throws GeneralSecurityException, IOException {

        return new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT).setJsonFactory(JSON_FACTORY).setServiceAccountId(SERVICE_ACCOUNT_EMAIL).setServiceAccountScopes(Collections.singleton(DNS_SCOPE)).setServiceAccountPrivateKeyFromP12File(new File(KEY_PATH)).build();
    }

    public static Dns getDns() throws GeneralSecurityException, IOException {
        return new Dns.Builder(HTTP_TRANSPORT, JSON_FACTORY, getGoogleCredential()).setApplicationName("FIXME_アプリケーション名_別に何でもいいです").build();
    }

    public static ManagedZonesListResponse getManagedZonesListResponse(final String projectId, final Dns dns) throws GeneralSecurityException, IOException {
        return dns.managedZones().list(projectId).execute();
    }

    public static ManagedZone getManagedZone(final ManagedZonesListResponse mzlr, final String dnsName) throws GeneralSecurityException, IOException {
        for (final ManagedZone mz : mzlr.getManagedZones()) {
            if (mz.getDnsName().equals(dnsName)) {
                return mz;
            }
        }

        return null;
    }

    public static List<ResourceRecordSet> getResourceRecordSets(final String projectId, final Dns dns, final ManagedZone mz) throws GeneralSecurityException, IOException {
        return dns.resourceRecordSets().list(projectId, mz.getName()).execute().getRrsets();
    }

    public static ResourceRecordSet getResourceRecordSet(final List<ResourceRecordSet> resourceRecordSets, final String name) throws GeneralSecurityException, IOException {
        for (final ResourceRecordSet rrs : resourceRecordSets) {
            if (rrs.getName().equals(name)) {
                return rrs;
            }
        }

        return null;
    }

    public static void addResourceRecordSet(final String projectId, final Dns dns, final String zoneName, final String rrsName, final String rrsRrdata, final int rrsTtl, final String rrsType) throws GeneralSecurityException, IOException {
        final ResourceRecordSet rrs = new ResourceRecordSet();

        rrs.setKind("dns#resourceRecordSet");
        rrs.setName(rrsName);
        rrs.setRrdatas(Arrays.asList(rrsRrdata));
        rrs.setTtl(Integer.valueOf(rrsTtl));
        rrs.setType(rrsType);

        addResourceRecordSet(projectId, dns, zoneName, rrs);
    }

    public static void addResourceRecordSet(final String projectId, final Dns dns, final String zoneName, final ResourceRecordSet resourceRecordSet) throws GeneralSecurityException, IOException {
        addResourceRecordSet(projectId, dns, zoneName, Arrays.asList(resourceRecordSet));
    }

    public static void addResourceRecordSet(final String projectId, final Dns dns, final String zoneName, final List<ResourceRecordSet> resourceRecordSets) throws GeneralSecurityException, IOException {
        final Change change = new Change();

        change.setAdditions(resourceRecordSets);
        dns.changes().create(projectId, zoneName, change).execute();
    }

    public static void deleteResourceRecordSet(final String projectId, final Dns dns, final String zoneName, final String rrsName, final String rrsRrdata, final int rrsTtl, final String rrsType) throws GeneralSecurityException, IOException {
        final ResourceRecordSet rrs = new ResourceRecordSet();

        rrs.setKind("dns#resourceRecordSet");
        rrs.setName(rrsName);
        rrs.setRrdatas(Arrays.asList(rrsRrdata));
        rrs.setTtl(Integer.valueOf(rrsTtl));
        rrs.setType(rrsType);

        deleteResourceRecordSet(projectId, dns, zoneName, rrs);
    }

    public static void deleteResourceRecordSet(final String projectId, final Dns dns, final String zoneName, final ResourceRecordSet resourceRecordSet) throws GeneralSecurityException, IOException {
        deleteResourceRecordSet(projectId, dns, zoneName, Arrays.asList(resourceRecordSet));
    }

    public static void deleteResourceRecordSet(final String projectId, final Dns dns, final String zoneName, final List<ResourceRecordSet> resourceRecordSets) throws GeneralSecurityException, IOException {
        final Change change = new Change();

        change.setDeletions(resourceRecordSets);
        dns.changes().create(projectId, zoneName, change).execute();
    }

    public static void main(final String[] args) throws GeneralSecurityException, IOException {
        final Dns dns = getDns();
        final ManagedZonesListResponse mzlr = getManagedZonesListResponse("test-dns", dns); // Google Cloud DNS を有効にしているプロジェクトID
        final ManagedZone mz = getManagedZone(mzlr, "FIXME.example.com."); // [ネットワーキング] - [Cloud DNS] の一覧に表示されている [DNS 名]

        if (mz == null) {
            return;
        }

        addResourceRecordSet("test-dns", dns, "FIXME-example-com", "myhome.FIXME.example.com.", "FIXME", 300, "A"); // [ネットワーキング] - [Cloud DNS] の一覧に表示されている [ゾーン 名]、サブドメイン(ここでは myhome)、Aレコード値(取得したグローバルIP)
        final ResourceRecordSet created = getResourceRecordSet(getResourceRecordSets("test-dns", dns, mz), "FIXME.example.com."); // 今作成したレコードを検索
        deleteResourceRecordSet("test-dns", dns, "FIXME-example-com", created); // 今作ったばかりのレコードを消す
        // deleteResourceRecordSet("test-dns", dns, "FIXME-example-com", "FIXME.example.com.", "FIXME", 300, "A"); // レコードの各値が分かっていれば検索せずに直接指定して消す
    }
}