環境依存の設定を解決する Cloud DIのスマートな実現方法

アイキャッチ AWS EC2

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

もう1年以上前になりますが、Cloud DIをApache HTTP Serverを例にして紹介するブログを書きました。

その中では、環境変数を利用して利用する設定ファイルを読み替えていました。その読み替えを大元のhttpd.confの中で実施しています。

ですが、OS上で動作するミドルウェア/アプリケーションは当然Apache HTTP Server以外にもたくさん存在します。環境依存の設定は至る所に存在することが多いので、そういったもの一つ一つに設定を実施するのは運用時に思わぬ落とし穴にハマることになりそうです。環境依存の設定を実施する場所を一箇所にまとめたいという要望を実現するため、以下の形式で実装してみることにしました。

方針

  • 対象OSはAmazon LinuxおよびRHEL系ディストリビューションのLinuxとする。
  • 自分自身の環境が開発/ステージング/本番のどれにあたるのか、EC2のEnvironmentタグで区別するものとする。
    • 例えば Environent: prd などが設定されているものとする
  • 環境依存の設定を切り替えるためのスクリプトを/etc/environment.d/以下に配置する
  • Environmentタグから取得された内容は/etc/environment.d/environmentにテキストファイルとして配置する
  • OS起動時のサービスとして/etc/environment.d/environmentに書き込み、環境依存設定切替のスクリプトを実行する

実装

前提

以下の構成を実行するためには、あらかじめ以下の設定が必要になります。

  • EC2のDescribeTagsAPIが利用可能なIAM RoleをEC2インスタンスに紐付けていること
  • AWS CLIおよびjqがインストールされていること

起動スクリプト

OSの起動時にサービスとして起動するため、/etc/init.d/environmentを以下のような形で配置します。

#!/bin/bash
#
# /etc/rc.d/init.d/environment
#
# chkconfig: 345 15 20
# description: setting for environment
#

. /etc/rc.d/init.d/functions

log() {
  echo $1 | logger
}

getInstanceTagWithRetry() {
    attempts=0
    maxRetry=5

    while :; do
        aws ec2 describe-tags --region ${region} --filters \
            "Name=resource-type,Values=instance" \
            "Name=resource-id,Values=${instanceId}" > ${resultFile}
        # if API call fails
        if [ $? -ne 0 ]; then
            # if attempts exceeded maxRetry
            if [ "${attempts}" -ge "${maxRetry}" ]; then
                log "failed to get Instance tags"
                exit 1
            fi
        else
            # if API call succeeds
            break
        fi
        attempts=$((attempts + 1))
        sleep 10
    done
}


start() {

  resultFile=$(mktemp --tmpdir=/tmp describe-tags.XXXXXX)
  instanceId=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
  region=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone | sed -e 's/.$//')
  getInstanceTagWithRetry

  # Export Variables
  export APP_ENVIRONMENT=$(cat ${resultFile} | /usr/local/bin/jq -r '.Tags[] | select(.Key == "Environment") | .Value')
  echo ${APP_ENVIRONMENT} > /etc/environment.d/environment

  if [ -d /etc/environment.d ]; then
    for i in /etc/environment.d/*; do
      if [ -x $i ]; then
        $i
      fi
    done
    unset i
  fi
}

case "$1" in
    start)
        start
        ;;
    stop)
        ;;
    restart)
        ;;
    status)
        ;;
    *)
        echo $"Usage: $0 start"
        exit 2
esac

exit $ret

こうすることで、サービス起動時に

  1. 自分自身のEnvironmentタグを取得
  2. /etc/environment.d/environmentに書き込み
  3. /etc/environment.d/以下のスクリプトファイルを実行

という流れで処理が行われます。配置後、chkconfig environment onを実行し、OS起動時にこのスクリプトが実行されるようにしておきましょう。 このスクリプトファイルでは、Environmentを取得することのみを実施しています。環境に応じて設定値を変えたい、という話は様々な方向からよく出てくるものなので、個々の設定値切替は/etc/environment.d/以下のスクリプトで行う方針としています。そうすることで、どのような処理が行われるのかが明確になりやすくなります。また、構成管理ツールとの相性もよくなります。

3のスクリプトの例を出します。例えばApacheの設定ファイルを環境ごとに切り替えたいのであれば以下の用に記載しましょう。

#!/bin/bash

set -u

ENV=$(cat /etc/environment.d/environment)

cp -f /etc/httpd/conf/httpd.conf.${ENV} /etc/httpd/conf/httpd.conf

この場合、本番環境、つまりEnvironmentタグがprdであれば/etc/httpd/conf/httpd.conf.prd/etc/httpd/conf/httpd.confにコピーされます。 当然、コピー元のファイルは環境ぶん用意しておく必要があります。

また、特定の環境でのみ起動させたいサービスも出てくると思います。ライセンスのあるミドルウェアや監視ツールのエージェントなどですね。そういった場合は以下のように記載します。例としてzabbix-agentを本番環境でのみ起動したい場合を取り上げると、

#!/bin/bash

set -u

ENV=$(cat /etc/environment.d/environment)

if [ "${ENV}" = "prd" ]; then
    service zabbix-agent start
fi

Environmentタグがprdの時のみ、サービスが起動するようになりました。当然ですが、この形を利用する際にはOS起動時の自動起動設定(chkconfig等)はoffにしておきましょう。

補足

/etc/environment.d/environmentの中でインスタンスのタグ情報を取得しますが、ネットワーク系などが原因で取得に失敗することがあります。AutoScalingで実行している場合は、その時点でインスタンスを停止することで新しいインスタンスに取り替えるのも方法の一つだと思います。AutoScalingではない環境の場合でも、タグの取得に失敗したことを検知できるような仕組みがあると好ましいと思います。 *1

まとめ

Cloud DIと書くと大仰に聞こえますが、実践するメソッドは非常に単純です。Cloud DIのように環境に応じてファイルを適宜切り替える方式には構成管理を行いやすくするメリットもあります。AWSでシステムを開発/運用する場合には是非取り入れてみてはいかがでしょうか。

脚注

  1. SNS経由でアラートメール送る手もありそうですが、EC2タグが取得できないような状況ではSNSにも届くとは思えないし...悩ましい