FLINTERS Engineer's Blog

FLINTERSのエンジニアによる技術ブログ

EC2のNAMEタグ情報を引っ張ってきて、PowerDNSに登録する

こんにちわ!さげはしです。

今回は、前回インストールしたEC2 API Toolsを利用して自分の登録されているNAMEタグ情報をAPIを叩いて取得し、うちの環境で使っているPowerDNSにデータを登録する、という一連の処理を自動化してみます。

というのも、うちの場合には本番環境のSnapshotから現行本番環境をそのまま作って検証する機会が多く、以前はホスト名をそのままPowerDNSに定期的に入れる処理をCronで動かしていましたが、Snapshotから起動するタイミングで勝手に新しい方のインスタンスDNSが振り変わってしまうので、その対策というか、本当ならもっと早くやれって各所からツッコミを受ける話です。

記事にあるスクリプトを転用すれば、hostsファイルを書き換えたり、その他色々出来ると思います…。


それで、今回の処理をやる上でのポイントというかやってない人はやったほうがいいと思ったことは、AWS_ACCESS_KEY】及びAWS_SECRET_KEY】スクリプト内で指定するのですが、これはAWS Identity and Access Management (IAM)を利用して最小限の権限を付与した形で各サーバに配布したほうがいいな、と。

ウチも最初は初期ID/PASSを共有していましたが、これだと退職者が出た場合とかにリスクになっちゃいますので、個別ユーザに関しても、IAMを使ってアカウントを切り出した方がいいですね。
って、みんな既にやっていますよね?私は如何せん知らなかったもので…。

なお、今回の処理を作るに辺り、EC2インスタンスにtagで付けた名前をサーバー内から取得するを参考にさせて頂きました!ありがとうございました!

では、実際の処理は以下のとおりです。
Perlでなんで書いてるとか特に理由はないですし、そもそも動かしているのがCentOSなので、それに寄せた書き方に(IP取得の部分とか)なってしまっていますが、その点はご容赦下さい!

#!/usr/bin/env perl

use strict;
use warnings;
use DBI;

# パラメータ
my $dsn   = 'DBI:mysql:database=mydns;host=<HOSTNAME>';
my $dbid  = '<USERNAME>';
my $dbpw  = '<DBPASSWD>';
my $DEBUG = 0;

# 環境変数設定
$ENV{JAVA_HOME}      = '/usr/java/default';
$ENV{EC2_HOME}       = '/home/ec2';
$ENV{PATH}           = "$ENV{PATH}:$ENV{EC2_HOME}/bin";
$ENV{AWS_ACCESS_KEY} = '<AWS_ACCESS_KEY>';
$ENV{AWS_SECRET_KEY} = '<AWS_SECRET_KEY>';
$ENV{EC2_URL}        = 'https://ec2.us-west-1.amazonaws.com';        #ec2-describe-regions

# NAMEタグを取得、整形
my $instance_id = `/usr/bin/curl -s http://169.254.169.254/latest/meta-data/instance-id`;
my $name_tag    = `/home/ec2/bin/ec2-describe-instances | grep $instance_id | grep TAG | grep Name | cut -f5`;
chomp $name_tag;
my $short_name = ( split( /\./, $name_tag ) )[0];
&debug("id - $instance_id / NAME $name_tag / short $short_name");

# ローカルIPアドレスを取得
my $ip_addr = ( split( /\s+/, `/sbin/ifconfig | grep 'Bcast'`) )[2];
$ip_addr =~ s/addr://;
&debug("IP $ip_addr");

# PowerDNSのデータを確認し、なければINSERT、あってIPが違う場合はUPDATE
my $dbh = DBI->connect( $dsn, $dbid, $dbpw, { 'RaiseError' => 1, 'PrintError' => 0, 'AutoCommit' => 1 } )
    || die $DBI::errstr;
my $sth = $dbh->prepare('SELECT data FROM rr WHERE name = ?');
$sth->execute($short_name);

if ( $sth->rows == 1 ) {

    # 既にデータあり(更新or何もしない)
    my $ref    = $sth->fetchrow_hashref();
    my $dns_ip = $ref->{data};
    if ( $dns_ip ne $ip_addr ) {
        &debug("update pdns $short_name - $ip_addr");
        $dbh->do( 'UPDATE rr set data = ? where name = ?', undef, $ip_addr, $short_name );
    }
} elsif ( $sth->rows > 1 ) {

    # データが2件以上あり(例外)
    warn "duplicate data entry!";
} else {

    # データなし(INSERT)
    &debug("insert $ip_addr - $short_name");
    $dbh->do( 'INSERT INTO rr (zone, name, data, type) VALUES (1, ?, ?, "A")', undef, $short_name, $ip_addr );
}
$sth->finish;
$dbh->disconnect;

# /etc/hostsファイルの更新
if ( &count_word_from_file( '/etc/hosts', $name_tag ) == 0 ) {
    &debug("remake hosts file");
    open my $fh, ">", '/etc/hosts' or die $!;
    print $fh "127.0.0.1    localhost    localhost.localdomain $short_name $name_tag\n";
    close $fh;
}

# /etc/sysconfig/networkファイルの更新
if ( &count_word_from_file( '/etc/sysconfig/network', $name_tag ) == 0 ) {
    &debug("remake sysconfig/network file");
    open my $fh, ">", '/etc/sysconfig/network' or die $!;
    print $fh "HOSTNAME=$name_tag\n";
    print $fh "NETWORKING=yes\n";
    print $fh "NOZEROCONF=true\n";
    print $fh "NETWORKING_IPV6=no\n";
    close $fh;
}

# hostnameの変更
my $now_hostname = `hostname`;
chomp $now_hostname;
if ( $now_hostname ne $name_tag ) {
    &debug("change hostname");
    system("hostname $name_tag");
}
exit;

sub count_word_from_file() {
    my ( $file, $word ) = @_;
    my $count = `grep $word $file | wc -l`;
    chomp $count;
    return $count;
}

sub debug {
    return unless $DEBUG;
    my $msg = shift;
    print "debug: $msg\n";
}