【Fabric】作業効率アップ!ELB配下のEC2に対して処理を実行させる方法

FabricAWS CLIを使って、特定のELB(Elastic Load Balancing)配下のEC2インスタンス全てに対して、特定の処理を実行させるスクリプトを紹介します。用途としては、「ELB配下のEC2インスタンス全てでデプロイを実行する」などが考えられます。

サーバ構成と処理の流れ

サーバ構成

今回のスクリプトで登場するAWSのサーバ(EC2)は以下の通りです。

  • ステージングサーバ
  • ELB
    • プロダクションサーバ
    • プロダクションサーバ
    • ...

また、FabricはローカルのPCで実行します。

処理の流れ

Fabricの処理は、以下のような流れで進みます。

  1. ステージングサーバにアクセスして、ELB配下のプロダクションサーバのホスト名を取得します。
  2. 取得したホスト名を使って、ELB配下のプロダクションサーバで、例えばデプロイを実行させます。

Fabricの処理フロー

準備

Fabricのインストール

ローカルPCにFabricをインストールします。pipを使うと簡単にインストールできます。(Fabric Installing

$ pip install fabric

SSHログインするための設定

証明書

ローカルPCからステージングサーバ、プロダクションサーバにSSHログインするために、証明書をローカルPCにダウンロードしておきます。今回のスクリプトでは、~/.ssh/<KEYFILE>.pemに保存したものとして進めます。

セキュリティグループの設定

ステージングサーバ、プロダクションサーバのセキュリティグループを設定し、22番ポートを開けておきます。

AWS CLIの設定

ポリシーの設定

今回のスクリプトでは、ステージングサーバからAWS CLIを実行します。そのため、ステージングサーバのロールなどにポリシーを追加します。

なお、使うコマンドはaws ec2 describe-instancesaws elb describe-load-balancersなので、追加するポリシーは以下のようになります。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "000000001",
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Sid": "000000002",
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:DescribeLoadBalancers"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

AWS CLIのデフォルト設定

AWS CLIの、デフォルトのリージョンと出力形式をそれぞれ、ap-northeast-1jsonに変更します。ステージングサーバにログインして、~/.aws/configに以下のように設定してください。

$ mkdir ~/.aws
$ vi ~/.aws/config

# 設定内容
[default]
output = json
region = ap-northeast-1

2015/06/05 13:59 既に~/.aws/configが存在している場合を考慮して、コマンドを変更しました。

スクリプトの実装

ELB配下のEC2インスタンスで、hostnameを実行するスクリプトを作成していきます。Fabricのルール通り、ファイルはfabfile.pyという名前で作成します。

ELB配下のインスタンスID取得関数

まずは、ELB配下のインスタンスIDを取得する関数を実装します。ここでは、aws elb describe-load-balancers --load-balancer-names <ELB_NAME>というコマンドを使用します。なお、<ELB_NAME>は、Load BalancersLoad Balancer Nameに表示されている名前を指定します。

def _get_elb_instance_ids():
    """ELB配下のインスタンスのインスタンスIDを取得します。"""
    elb_name = '<ELB_NAME>'  # 対象のELB名を指定します。
    cmd = 'aws elb describe-load-balancers --load-balancer-names %s'
    res = run(cmd % elb_name)  # ステージングサーバでコマンドを実行します。
    instances = itertools.chain(  # describe-load-balancersの結果からインスタンス情報を取り出します。
        *[d['Instances'] for d in json.loads(res)['LoadBalancerDescriptions']])
    instance_ids = [i['InstanceId'] for i in instances]  # インスタンス情報からインスタンスIDを取り出します。
    if not instance_ids:
        abort('ELB配下のEC2インスタンスが見つかりませんでした。')
    return instance_ids

ELB配下のホスト名取得関数

次に、取得したインスタンスIDを使って、ELB配下のEC2のホスト名を取得する関数を実装します。ここでは、aws ec2 describe-instances --instance-ids INSTANCE_IDSというコマンドを使います。

def _get_elb_hostnames():
    """ELB配下のインスタンスのホスト名を取得します。"""
    instance_ids = _get_elb_instance_ids()  # 上で定義した関数を呼びます。
    cmd = 'aws ec2 describe-instances --instance-ids %s'
    res = run(cmd % ' '.join(instance_ids))  # ステージングサーバでコマンドを実行します。
    instances = itertools.chain(  # describe-instancesからインスタンス情報を取り出します。
        *[d['Instances'] for d in json.loads(res)['Reservations']])
    hostnames = [i['PublicDnsName'] for i in instances]  # インスタンス情報からホスト名を取り出します。
    return hostnames

ホストを切り替える関数

ELB配下のホスト名を取得できるようになったら、後はホストを切り替えるようにします。ホストの切り替えはwith settings(host_string=ホスト名):というwithステートメントで行います。

def _with_elb_hosts():
    """処理ホストをELBで管理しているサーバに切り替えます。"""
    for host_string in _get_elb_hostnames():
        with settings(host_string=host_string):  # ELB配下のインスタンスを使うようにします。
            yield host_string
            time.sleep(5)  # インスタンス毎に一定秒スリープさせます。

hostnameを実行するタスクを作成

最後に、EC2インスタンスでhostnameを実行するタスクを作ります。また、importenvの設定も追加します。

# coding: utf-8
import json
import time
import itertools
from fabric.api import env, run, settings, task, abort

env.user = 'ec2-user'
env.key_filename = '~/.ssh/<KEYFILE>.pem'
env.hosts = ['<STAGING SERVER>']


@task
def elb_hostname():
    for i in _with_elb_hosts():  # ELB配下のEC2インスタンスに対して処理を実行します。
        run('hostname')

これで完成です。fabfile.pyと同じディレクトリで以下のコマンドを打つと、ELB配下のEC2インスタンスでhostnameを実行させることができるようになりました。

$ fab elb_hostname

完成形ソースコード

完成形のソースコードは以下のようになります。

# coding: utf-8
import json
import time
import itertools
from fabric.api import env, run, settings, task, abort

env.user = 'ec2-user'
env.key_filename = '~/.ssh/<KEYFILE>.pem'
env.hosts = ['<STAGING SERVER>']


@task
def elb_hostname():
    for i in _with_elb_hosts():  # ELB配下のEC2インスタンスに対して処理を実行させます。
        run('hostname')


def _get_elb_instance_ids():
    """ELB配下のインスタンスのインスタンスIDを取得します。"""
    elb_name = '<ELB_NAME>'  # 対象のELB名を指定します。
    cmd = 'aws elb describe-load-balancers --load-balancer-names %s'
    res = run(cmd % elb_name)  # ステージングサーバでコマンドを実行します。
    instances = itertools.chain(  # describe-load-balancersの結果からインスタンス情報を取り出します。
        *[d['Instances'] for d in json.loads(res)['LoadBalancerDescriptions']])
    instance_ids = [i['InstanceId'] for i in instances]  # インスタンス情報からインスタンスIDを取り出します。
    if not instance_ids:
        abort('ELB配下のEC2インスタンスが見つかりませんでした。')
    return instance_ids


def _get_elb_hostnames():
    """ELB配下のインスタンスのホスト名を取得します。"""
    instance_ids = _get_elb_instance_ids()
    cmd = 'aws ec2 describe-instances --instance-ids %s'
    res = run(cmd % ' '.join(instance_ids))  # ステージングサーバでコマンドを実行します。
    instances = itertools.chain(  # describe-instancesからインスタンス情報を取り出します。
        *[d['Instances'] for d in json.loads(res)['Reservations']])
    hostnames = [i['PublicDnsName'] for i in instances]  # インスタンス情報からホスト名を取り出します。
    return hostnames


def _with_elb_hosts():
    """処理ホストをELBで管理しているサーバに切り替えます。"""
    for host_string in _get_elb_hostnames():
        with settings(host_string=host_string):  # ELB配下のインスタンスを使うようにします。
            yield host_string
            time.sleep(5)  # インスタンス毎に一定秒スリープさせます。

このソースはgithubにも公開してあります。fabric_elb_deploy/fabfile.py

この記事が役に立った場合、シェアしていただけると励みになります!!