CloudInit を使って Amazon EC2 のホスト名を自動的に設定する

Posted on

EC2 のインスタンスを起動したとき、デフォルトでは ip-xx-xx-xx-xx のようなホスト名が設定されます。プロンプトを見てもどのサーバにログインしているのかわかりにくいですし、ログを集約したときにどのホストのログなのか追えなくなります。

そこで Name タグに設定した値をそのサーバのホスト名に設定してみたいと思います。もちろん、自動化するために CloudInit を使います。

CloudInit に設定できる形式

CloudInit に設定できる形式はいくつかありますが、よく使うのは次の 2 つです(ドキュメントを確認すると全部で 8 つありました)。

  • シェルスクリプト
  • Cloud Config

シェルスクリプトは #! から始まるスクリプト、Cloud Config は #cloud-config から始まる YAML 形式のデータです。 Cloud Config については、過去に書いた「CloudInit を使って Amazon EC2 をスマートに立ち上げる」も参考にしてください。

今回は Amazon Linux にインストールされているコマンドを組み合わせたいのでシェルスクリプトで実装します。

シェルスクリプトの解説

CloudInit は /bin/sh/bin/bash のシェルスクリプトが使えます。 Management Console の場合、インスタンスを立ち上げる際に User Data のフィールドに貼り付けるだけです。

コードはこんな感じです。 1 行目の #cloud-boothook については、あとで詳しく書きます。

#cloud-boothook
#!/bin/bash

export AWS_ACCESS_KEY='xxxxxxxxxxxxxxxxxxxx'
export AWS_SECRET_KEY='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
export EC2_HOME='/opt/aws/apitools/ec2'
export JAVA_HOME='/usr/lib/jvm/jre'

INSTANCE=`/opt/aws/bin/ec2-metadata | grep ^instance-id: | cut -d ' ' -f 2`
LOCAL_IP=`/opt/aws/bin/ec2-metadata | grep ^local-ipv4: | cut -d ' ' -f 2`
REGION=`/opt/aws/bin/ec2-metadata | grep ^placement: | cut -d ' ' -f 2 | sed -e 's/.$//g'`
TAG_NAME=`/opt/aws/bin/ec2-describe-instances --region ${REGION} ${INSTANCE} | grep ^TAG | cut -f 5`

hostname ${TAG_NAME}
echo -e "${LOCAL_IP}\t${TAG_NAME}" >> /etc/hosts

export している 4 つの環境変数は ec2-* コマンドを実行する上で必ず必要な環境変数です。この 4 つがないと実行時にエラーになります。

AWS_ACCESS_KEY と AWS_SECRET_KEY はそれぞれ置き換えてください。読み取り権限だけあれば実行できるので、IAM でユーザを作って "Amazon EC2 Read Only Access" の権限を与えると良いでしょう。

やっていることはシンプルです。

  1. ec2-metadata コマンドで自分のインスタンス ID とローカル IP アドレス、属するリージョンを取得
  2. ec2-describe-instances コマンドで Name タグの情報を取得
  3. hostname コマンドで Name タグの値を設定し、hosts ファイルにローカル IP アドレスとホスト名を追記

自分がハマった点を書いておきます。

  • 必須の環境変数がわからなくて何度もエラーになった
  • 必須の環境変数は Java が参照するので export しておく必要がある(ec2-* コマンドは Java で書かれています)
  • ec2-* コマンドはフルパスで書く必要がある
  • dry-run できないのでデバッグするのが大変だった

CloudInit が実行されるタイミング

CloudInit は起動時に実行されると書きましたが、もう少し詳しく見てみます。実は実行タイミングがとても重要で、1 行目の #cloud-boothook にもつながります。

/etc/rc3.d/ を確認すると、User Data は /etc/rc.local の直前に実行されているようです。

$ ls -l /etc/rc3.d/S*
lrwxrwxrwx 1 root root 17 Aug 29  2013 /etc/rc3.d/S01sysstat -> ../init.d/sysstat
lrwxrwxrwx 1 root root 22 Dec 21 18:04 /etc/rc3.d/S02lvm2-monitor -> ../init.d/lvm2-monitor
lrwxrwxrwx 1 root root 17 May 10  2013 /etc/rc3.d/S10network -> ../init.d/network
lrwxrwxrwx 1 root root 16 May 10  2013 /etc/rc3.d/S11auditd -> ../init.d/auditd
lrwxrwxrwx 1 root root 17 Oct  1 08:38 /etc/rc3.d/S12rsyslog -> ../init.d/rsyslog
lrwxrwxrwx 1 root root 20 Nov 17 04:03 /etc/rc3.d/S13irqbalance -> ../init.d/irqbalance
lrwxrwxrwx 1 root root 19 May 10  2013 /etc/rc3.d/S15mdmonitor -> ../init.d/mdmonitor
lrwxrwxrwx 1 root root 20 May 10  2013 /etc/rc3.d/S22messagebus -> ../init.d/messagebus
lrwxrwxrwx 1 root root 26 Dec 21 18:04 /etc/rc3.d/S25blk-availability -> ../init.d/blk-availability
lrwxrwxrwx 1 root root 20 Feb  6 16:05 /etc/rc3.d/S25cloud-init -> ../init.d/cloud-init
lrwxrwxrwx 1 root root 15 May 10  2013 /etc/rc3.d/S25netfs -> ../init.d/netfs
lrwxrwxrwx 1 root root 15 May 10  2013 /etc/rc3.d/S26acpid -> ../init.d/acpid
lrwxrwxrwx 1 root root 19 Oct  1 08:44 /etc/rc3.d/S26udev-post -> ../init.d/udev-post
lrwxrwxrwx 1 root root 14 Oct  1 08:38 /etc/rc3.d/S55sshd -> ../init.d/sshd
lrwxrwxrwx 1 root root 17 May 10  2013 /etc/rc3.d/S57ntpdate -> ../init.d/ntpdate
lrwxrwxrwx 1 root root 14 May 10  2013 /etc/rc3.d/S58ntpd -> ../init.d/ntpd
lrwxrwxrwx 1 root root 16 Aug 29  2013 /etc/rc3.d/S64mysqld -> ../init.d/mysqld
lrwxrwxrwx 1 root root 17 Aug 29  2013 /etc/rc3.d/S80postfix -> ../init.d/postfix
lrwxrwxrwx 1 root root 15 Aug 28  2013 /etc/rc3.d/S85httpd -> ../init.d/httpd
lrwxrwxrwx 1 root root 15 May 10  2013 /etc/rc3.d/S90crond -> ../init.d/crond
lrwxrwxrwx 1 root root 20 Aug 29  2013 /etc/rc3.d/S90munin-node -> ../init.d/munin-node
lrwxrwxrwx 1 root root 13 May 10  2013 /etc/rc3.d/S95atd -> ../init.d/atd
lrwxrwxrwx 1 root root 33 Feb  6 16:05 /etc/rc3.d/S99cloud-init-user-scripts -> ../init.d/cloud-init-user-scripts
lrwxrwxrwx 1 root root 11 May 10  2013 /etc/rc3.d/S99local -> ../rc.local

順番を見れば分かるとおり、各種デーモンが起動してから実行されることになります。デーモンが起動する前にホスト名を設定できないと都合が悪いこともあります。

Cloud Boothook を使う

改めて /etc/rc3.d/ を見てみると、CloudInit のスクリプトがもうひとつあることに気づきます。

$ ls -l /etc/rc3.d/*cloud-init*
lrwxrwxrwx 1 root root 20 Feb  6 16:05 /etc/rc3.d/S25cloud-init -> ../init.d/cloud-init
lrwxrwxrwx 1 root root 33 Feb  6 16:05 /etc/rc3.d/S99cloud-init-user-scripts -> ../init.d/cloud-init-user-scripts

S25 なのでデーモンが起動される前に実行されます。ここで User Data を実行できると良さそうです。

シェルスクリプトの 1 行目に書いた #cloud-boothook は Cloud Boothook と呼ばれ、/etc/rc3.d/S25cloud-init で実行させるための宣言です。ドキュメントを読むと、以下のように書かれています。

It is stored in a file under /var/lib/cloud and then executed immediately. This is the earliest "hook" available.

#cloud-boothook を宣言することで、ネットワークがつながってデーモンが起動していない状態で CloudInit を実行することができるようになります。

まとめ

デーモンが起動する前後のタイミングで CloudInit が実行できるので、より柔軟な処理が行えます。 CloudInit は CDP の Bootstrap パターンで重宝する機能なので、うまく使いこなせるようになりたいものです。