2018年7月3日火曜日

AWSとVIP

ご無沙汰しております。
最近、AWSの仕事が多くなってきました。
こちら((番外編) AWSに冗長化NFSサーバ)にも追記で少し書きましたが、去年と今年の新サービスで、かなりの弱点がなくなり、オンプレからクラウドへの移行がお勧めできる状態になりました。私が書く記事は冗長化に関するものが多いですが、今回もご多分に漏れず、この方向です。

AWSでHA構成を組みたい、という要望があります。HA構成のために必要となる要素をあげると、VIPと共有ストレージが問題となります。共有ストレージについては、LIO Cluster [LIO, DRBD, Pacemaker による冗長化 iSCSI Target]をAWSに移植すればよいです。この連載は書いている途中でかなりの時間がたってしまい恐縮ですが、取り合えず続きを書く前に、AWS編を書こうかと思っております。本稿では、その移植でも問題となるVIPについての情報をまとめていきたいと思います。

AWSでは、オンプレと同じ方法でVIPを扱うことができません。AWSのAPI呼出しと連携する必要があります。具体的な方法としては、以下の4つのうちのどれかを選択することになります。

(a) EIPを移動する
(b) ルートテーブルを書き換える
(c) セカンダリプライベートIPアドレスを移動する
(d) ENIを移動する

どれも一長一短があって、要件に合わせて選択する必要があります。


【特徴】

(a) EIPを移動する

この方法は、AWSのグローバルIPアドレスであるEIPがVIPの代わりとなります。特徴は以下の通りです。

・AZ(Availability Zone)をまたがったHA構成可能
・インターネットからの通信を受付
・NAT経由

NAT経由の通信を許容しないプロトコルでは利用できません。EC2のOS内部では、EIPを持っているかどうか関知していません。クラスタソフトでは、VIPリソースの代わりにEIPリソースをフェイルオーバーさせることになります。
グローバルIPアドレス経由でしか通信できません。こちらは、HA構成をとっているサーバ側だけでなく、サービスを利用するクライアント側もインターネット経由でのアクセスが必須である点にも注意が必要です。Direct Connect経由で通信する場合、通信業者とよく相談しておくことをお勧めします。特に通信経路の暗号化がどうなっているのかについて吟味する必要があるのではないでしょうか。
(b),(C)案ではどうしても要件を満たせない場合に検討するのがこの(a)案です。

(b) ルートテーブルを書き換える

VIP宛ての通信を、Active機のENI(EC2等に紐づける仮想NIC)宛てに届ける指示となるようにルートテーブルを書き換えます。

・AZ(Availability Zone)をまたがったHA構成可能
・VIPはVPCのCIDR外のIPアドレスを指定
・VPC外からの通信は不能
・HAを構成するEC2で「Change Source/Dest Check」をオフに設定
・EC2のOS側でVIP宛て通信を受け入れるように設定
・切り替え時間が短い

VIP宛てDirect Connect経由での通信、マネージドVPN接続経由での通信、VPCピアリング経由での通信がない場合、一番よい方法と思われます。
本来はNATサーバを構築するための仕組みを利用しています。

(c) セカンダリプライベートIPアドレスを移動する

VIPをEC2のセカンダリプライベートIPアドレスとして設定し、OS側でもVIPを設定します。

・単一のAZ(Availability Zone)内(単一のサブネット内)でのみHA構成可能
・VPC外からの通信を受付可能

マルチAZ構成は取らないが、冗長化はしたいという場合の最適解です。オンプレでHA構成を扱っていたのであれば、一番わかりやすい構成なのではないかと思われます。Direct Connect経由での通信、マネージドVPN接続経由での通信、VPCピアリング経由での通信にも対応しています。

(d) ENIを移動する

仮想NICをEC2間で移動してしまおうという構成です。

・単一のAZ(Availability Zone)内(単一のサブネット内)でのみHA構成可能
・VPC外からの通信を受付可能
・MACアドレスも移動可能
・切り替え時間が長い

仮想環境ならではの発想です。メリットはMACアドレスも移動可能なことくらいで、それを除けば、(c)案を採用すべきです。その理由は、同一セグメントでのマルチホーミングとなるからです。Linuxであれば、iproute2を利用することで対応可能ですが、Windowsだと非常に難しいのではないかと思われます。何を言っているのか分からない方は、無条件でこの方法は却下してください。一部通信が思うようにできない場合、マルチホーミングの対応が誤っている可能性が高いです。
MACアドレスを移動しなければならない要件というのはまず無いはずなので、この案のことは忘れましょう。私は忘れましたので、実装例は他にあたってください。

(番外)VIPを移動させたように見せかけるフェイク

「DRBD_EC2_HA_VIP_White_Paper.pdf」というドキュメントに掲載されている方法で、OS上ではVIPを移動させますが、ルートテーブル等のAWSリソースを動的に書き換えることはせずに固定しておきます。AZ aにあるActive機に対して、AZ aにあるクライアントαがVIP宛て通信できますが、AZ cにあるクライアントβは通信不能です。AZ cにあるStand-by機が昇格してVIPを持つと、AZ cにあるクライアントβがVIP宛て通信可能ですが、AZ aにあるクライアントαは通信できません。これがHA構成のVIPとして有効なのかは非常に疑問で、このような動作を望んでいる読者は皆無に近いのではないかと思います。しかも、このドキュメントにはこのような制限が存在していることを全く述べておりません。有効なケースが全くないとまでは言いませんが、読者を迷宮に誘い込んで、問い合わせを増やそうという目論見以外の何物でもありません。ググるとよく引っかかるドキュメントなので注意が必要です。この会社のドキュメントは、肝心なことをわざと書かないことが多いので、気を付けましょう。


【CentOS7での実装】

AWS CLIコマンドの実行例を示した上で、Pacemakerのリソースエージェントを作ります。
VIPリソースを持たせたいEC2にロールを紐づけて権限を付与済みの前提です。AWS CLIの実行環境もインストール済みとします。

(a) EIPを移動する

EIPを取得します。


export AWS_DEFAULT_REGION=ap-northeast-1

aws ec2 allocate-address --domain vpc
{
    "PublicIp": "52.194.126.86",
    "Domain": "vpc",
    "AllocationId": "eipalloc-01f097befc3a79b1c"
}

aws ec2 create-tags --resources eipalloc-01f097befc3a79b1c --tags Key=Name,Value="vip1"


名前を付けると後で便利です。常に名前を付ける習慣にしましょう。

EIPをEC2に紐づけます。


INS_ID=$(curl http://169.254.169.254/latest/meta-data/instance-id 2> /dev/null)
echo $INS_ID
i-035c6ee3958191ef7

aws ec2 associate-address --instance-id $INS_ID --public-ip 52.194.126.86 --allow-reassociation
{
    "AssociationId": "eipassoc-077d7bb670348f076"
}


EIPの状態を確認します。


aws ec2 describe-addresses --public-ips 52.194.126.86
{
    "Addresses": [
        {
            "Domain": "vpc",
            "Tags": [
                {
                    "Value": "vip1",
                    "Key": "Name"
                }
            ],
            "InstanceId": "i-035c6ee3958191ef7",
            "NetworkInterfaceId": "eni-0a8728301ed37d7f4",
            "AssociationId": "eipassoc-0559555f1e5bcf7f2",
            "NetworkInterfaceOwnerId": "869741784757",
            "PublicIp": "52.194.126.86",
            "AllocationId": "eipalloc-01f097befc3a79b1c",
            "PrivateIpAddress": "100.65.50.228"
        }
    ]
}


ちなみに、EIPがどのEC2とも紐づいていない場合は以下のようになります。


aws ec2 describe-addresses --public-ips 52.194.126.86
{
    "Addresses": [
        {
            "PublicIp": "52.194.126.86",
            "Domain": "vpc",
            "AllocationId": "eipalloc-01f097befc3a79b1c",
            "Tags": [
                {
                    "Value": "vip1",
                    "Key": "Name"
                }
            ]
        }
    ]
}


「InstanceId」の値が自己のインスタンスIDと一致すれば当該EIPを持っている、そうでなければ持っていない、と判断してよいことが分かります。


if [ "$(aws ec2 describe-addresses --public-ips 52.194.126.86 | grep InstanceId | awk -F\" '{print $4}')" = $(curl http://169.254.169.254/latest/meta-data/instance-id 2> /dev/null) ]; then
>   echo active
> else
>   echo not active
> fi
active


以下のようにメタデータを取得した場合、反映に数秒かかるようなので、EIPの状態確認目的で利用するのはやめましょう。


curl http://169.254.169.254/latest/meta-data/public-ipv4
52.194.126.86

curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/ipv4-associations/
52.194.126.86


EIPの紐づけを解除します。


aws ec2 disassociate-address --public-ip 52.194.126.86


リソースエージェントは以下のようになります。


cat << 'EOF' | sudo tee /usr/lib/ocf/resource.d/heartbeat/EIP
#!/bin/bash
#
#     EIP OCF RA. manages AWS EIP.
#
#   (c) 2009-2010 Florian Haas, Dejan Muhamedagic,
#                 and Linux-HA contributors
#
#      modified by Katsuaki Hamada (hamada@pc-office.net), 1 July 2018
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it would be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Further, this software is distributed without any warranty that it is
# free of the rightful claim of any third person regarding infringement
# or the like.  Any license provided herein, whether implied or
# otherwise, applies only to this software file.  Patent licenses, if
# any, provided herein do not apply to combinations of this program with
# other software, or any other product whatsoever.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
#

#######################################################################
# Initialization:
: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs

# Lockfile, used for selecting a target ID
LOCKFILE=${HA_RSCTMP}/eip.lock
#######################################################################

meta_data() {
  cat <<END
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="EIP" version="1.0">
<version>1.0</version>

<longdesc lang="en">
Manages AWS EIP.
</longdesc>
<shortdesc lang="en">AWS EIP agent</shortdesc>

<parameters>
<parameter name="vip" required="1" unique="1">
<longdesc lang="en">
AWS EIP. Specify EIP to substitute for VIP.
</longdesc>
<shortdesc lang="en">EIP</shortdesc>
<content type="string" />
</parameter>
</parameters>

<actions>
<action name="start" timeout="10" />
<action name="stop" timeout="10" />
<action name="status" timeout="10" interval="30" depth="0" />
<action name="monitor" timeout="10" interval="30" depth="0" />
<action name="meta-data" timeout="5" />
<action name="validate-all" timeout="10" />
</actions>
</resource-agent>
END
}

#######################################################################

export AWS_DEFAULT_REGION=$(curl http://169.254.169.254/latest/dynamic/instance-identity/document 2> /dev/null|grep region|awk -F\" '{print $4}')

EIP_usage() {
  cat <<END
usage: $0 {start|stop|status|monitor|validate-all|meta-data}

Expects to have a fully populated OCF RA-compliant environment set.
END
}

EIP_start() {
  if aws ec2 associate-address --instance-id $(curl http://169.254.169.254/latest/meta-data/instance-id 2> /dev/null) --public-ip ${OCF_RESKEY_vip} --allow-reassociation; then
    return $OCF_SUCCESS
  else
    return $OCF_ERR_GENERIC
  fi
}

EIP_stop() {
  if aws ec2 disassociate-address --public-ip ${OCF_RESKEY_vip}; then
    return $OCF_SUCCESS
  else
    return $OCF_ERR_GENERIC
  fi
}

EIP_monitor() {
  if [ "$(aws ec2 describe-addresses --public-ips ${OCF_RESKEY_vip} | grep InstanceId | awk -F\" '{print $4}')" = $(curl http://169.254.169.254/latest/meta-data/instance-id 2> /dev/null) ]; then
    return $OCF_SUCCESS
  else
    return $OCF_NOT_RUNNING
  fi
}

EIP_validate() {
  if ! ocf_is_probe; then
    # Do we have all required binaries?
    check_binary aws
  fi
  return $OCF_SUCCESS
}

case $1 in
  meta-data)  meta_data; exit $OCF_SUCCESS;;
  usage|help) EIP_usage; exit $OCF_SUCCESS;;
esac

# Everything except usage and meta-data must pass the validate test
EIP_validate

case $__OCF_ACTION in
  start)          EIP_start;;
  stop)           EIP_stop;;
  monitor|status) EIP_monitor;;
  reload)         ocf_log err "Reloading..."; EIP_start;;
  validate-all)   ;;
  *)              EIP_usage; exit $OCF_ERR_UNIMPLEMENTED;;
esac
rc=$?
ocf_log debug "${OCF_RESOURCE_INSTANCE} $__OCF_ACTION : $rc"
exit $rc
EOF
sudo chmod 755 /usr/lib/ocf/resource.d/heartbeat/EIP


(b) ルートテーブルを書き換える

VIP宛てに通信するクライアントが所属しているサブネットに紐づけられているルートテーブルのIDを取得します。名前をキーにして検索してみます。


export AWS_DEFAULT_REGION=ap-northeast-1

RTB_NAME=rtb-private-vpc
aws ec2 describe-route-tables --filters "Name=tag:Name,Values=${RTB_NAME}" | grep '"RouteTableId":' | awk -F\" '{print $4}' | sort | uniq
rtb-061bbb47b6e9db5bc


VIP宛てに通信するクライアントが増えて、別のサブネットからも通信してくることがあるたびに、クラスタのリソース設定を変更するのはよくないので、名前で検索するのではなく、特定のタグが付いているルートテーブルのID一覧を取得してみます。値は「true」固定とします。


TAG_NAME=VIP10.0.0.1
aws ec2 describe-route-tables --filters "Name=tag:${TAG_NAME},Values=true" | grep '"RouteTableId":' | awk -F\" '{print $4}' | sort | uniq
rtb-0446b0cfad48c9634
rtb-061bbb47b6e9db5bc


VPC内の全てのルートテーブルを書き換える場合は、以下のコマンドでルートテーブルのID一覧を取得可能です。該当するタグが一つも見つからなかった場合はこちらを利用することにします。


aws ec2 describe-route-tables --filters Name=vpc-id,Values=$(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/vpc-id 2> /dev/null) | grep RouteTableId | awk -F\" '{print $4}' | sort | uniq


取得したルートテーブルが持っているルート一覧を表示してみます。


for i in $(aws ec2 describe-route-tables --filters "Name=tag:${TAG_NAME},Values=true" | grep '"RouteTableId":' | awk -F\" '{print $4}' | sort | uniq)
> do
>   echo $i
>   aws ec2 describe-route-tables --route-table-ids $i | grep DestinationCidrBlock
>   echo
> done
rtb-0446b0cfad48c9634
                    "DestinationCidrBlock": "100.65.0.0/16",

rtb-061bbb47b6e9db5bc
                    "DestinationCidrBlock": "100.65.0.0/16",


VIPへのルートを追加します。


for i in $(aws ec2 describe-route-tables --region ${AWS_DEFAULT_REGION} --filters "Name=tag:${TAG_NAME},Values=true" | grep '"RouteTableId":' | awk -F\" '{print $4}' | sort | uniq)
> do
>   echo $i
>   aws ec2 create-route --route-table-id $i --destination-cidr-block 10.0.0.1/32 --instance-id $(curl http://169.254.169.254/latest/meta-data/instance-id 2> /dev/null)
>   echo
> done
rtb-0446b0cfad48c9634
{
    "Return": true
}

rtb-061bbb47b6e9db5bc
{
    "Return": true
}


ルート一覧を再度表示してみます。


for i in $(aws ec2 describe-route-tables --region ${AWS_DEFAULT_REGION} --filters "Name=tag:${TAG_NAME},Values=true" | grep '"RouteTableId":' | awk -F\" '{print $4}' | sort | uniq)
> do
>   echo $i
>   aws ec2 describe-route-tables --route-table-ids $i | grep DestinationCidrBlock
>   echo
> done
rtb-0446b0cfad48c9634
                    "DestinationCidrBlock": "10.0.0.1/32",
                    "DestinationCidrBlock": "100.65.0.0/16",

rtb-061bbb47b6e9db5bc
                    "DestinationCidrBlock": "10.0.0.1/32",
                    "DestinationCidrBlock": "100.65.0.0/16",



VIPへのルートを削除します。


for i in $(aws ec2 describe-route-tables --region ${AWS_DEFAULT_REGION} --filters "Name=tag:${TAG_NAME},Values=true" | grep '"RouteTableId":' | awk -F\" '{print $4}' | sort | uniq)
> do
>   aws ec2 delete-route --route-table-id $i --destination-cidr-block 10.0.0.1/32
> done


リソースエージェントは以下のようになります。


cat << 'EOF' | sudo tee /usr/lib/ocf/resource.d/heartbeat/ART
#!/bin/bash
#
#     ART OCF RA. adjust AWS route table so that VIP works.
#
#   (c) 2009-2010 Florian Haas, Dejan Muhamedagic,
#                 and Linux-HA contributors
#
#      modified by Katsuaki Hamada (hamada@pc-office.net), 1 July 2018
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it would be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Further, this software is distributed without any warranty that it is
# free of the rightful claim of any third person regarding infringement
# or the like.  Any license provided herein, whether implied or
# otherwise, applies only to this software file.  Patent licenses, if
# any, provided herein do not apply to combinations of this program with
# other software, or any other product whatsoever.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
#

#######################################################################
# Initialization:
: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs

# Lockfile, used for selecting a target ID
LOCKFILE=${HA_RSCTMP}/art.lock
#######################################################################

meta_data() {
  cat <<END
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="ART" version="1.0">
<version>1.0</version>

<longdesc lang="en">
Adjust AWS route table so that VIP works.
</longdesc>
<shortdesc lang="en">ART agent</shortdesc>

<parameters>
<parameter name="vip" required="1" unique="1">
<longdesc lang="en">
VIP.
</longdesc>
<shortdesc lang="en">VIP</shortdesc>
<content type="string" />
</parameter>
</parameters>

<actions>
<action name="start" timeout="10" />
<action name="stop" timeout="10" />
<action name="status" timeout="10" interval="30" depth="0" />
<action name="monitor" timeout="10" interval="30" depth="0" />
<action name="meta-data" timeout="5" />
<action name="validate-all" timeout="10" />
</actions>
</resource-agent>
END
}

#######################################################################

export AWS_DEFAULT_REGION=$(curl http://169.254.169.254/latest/dynamic/instance-identity/document 2> /dev/null|grep region|awk -F\" '{print $4}')

ART_usage() {
  cat <<END
usage: $0 {start|stop|status|monitor|validate-all|meta-data}

Expects to have a fully populated OCF RA-compliant environment set.
END
}

ART_start() {
  IDs=$(aws ec2 describe-route-tables --filters "Name=tag:VIP${OCF_RESKEY_vip},Values=true" | grep '"RouteTableId":' | awk -F\" '{print $4}' | sort | uniq)
  if [ "$IDs" = "" ]; then
    IDs=$(aws ec2 describe-route-tables --filters Name=vpc-id,Values=$(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/vpc-id 2> /dev/null) | grep RouteTableId | awk -F\" '{print $4}' | sort | uniq)
  fi
  rc=$OCF_SUCCESS
  for i in $IDs
  do
    aws ec2 create-route --route-table-id $i --destination-cidr-block ${OCF_RESKEY_vip}/32 --instance-id $(curl http://169.254.169.254/latest/meta-data/instance-id 2> /dev/null)
    if [ $? -ne 0 ]; then
      rc=$OCF_ERR_GENERIC
    fi
  done
  return $rc
}

ART_stop() {
  IDs=$(aws ec2 describe-route-tables --filters Name=vpc-id,Values=$(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/vpc-id 2> /dev/null) | grep RouteTableId | awk -F\" '{print $4}' | sort | uniq)
  for i in $IDs
  do
    aws ec2 delete-route --route-table-id $i --destination-cidr-block ${OCF_RESKEY_vip}/32
  done
  return $OCF_SUCCESS
}

ART_monitor() {
  if aws ec2 describe-route-tables --filters Name=vpc-id,Values=$(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/vpc-id 2> /dev/null) | grep -q "DestinationCidrBlock.*[^0-9]$(echo ${OCF_RESKEY_vip} | sed -e 's/\./\\./g')/32"; then
    return $OCF_SUCCESS
  else
    return $OCF_NOT_RUNNING
  fi
}

ART_validate() {
  if ! ocf_is_probe; then
    # Do we have all required binaries?
    check_binary aws
  fi
  return $OCF_SUCCESS
}

case $1 in
  meta-data)  meta_data; exit $OCF_SUCCESS;;
  usage|help) ART_usage; exit $OCF_SUCCESS;;
esac

# Everything except usage and meta-data must pass the validate test
ART_validate

case $__OCF_ACTION in
  start)          ART_start;;
  stop)           ART_stop;;
  monitor|status) ART_monitor;;
  reload)         ocf_log err "Reloading..."; ART_start;;
  validate-all)   ;;
  *)              ART_usage; exit $OCF_ERR_UNIMPLEMENTED;;
esac
rc=$?
ocf_log debug "${OCF_RESOURCE_INSTANCE} $__OCF_ACTION : $rc"
exit $rc
EOF
sudo chmod 755 /usr/lib/ocf/resource.d/heartbeat/ART


(c) セカンダリプライベートIPアドレスを移動する

EC2に紐づいているENIは1つしかない前提で話を進めます。1つのEC2に多くのIPアドレスをアサインしたい場合には、複数のENIを持つ必要が出てくる場合がありますが、本稿では対応しておりません。

セカンダリプライベートIPアドレスを付与します。


VIP=100.65.50.220
aws ec2 assign-private-ip-addresses --network-interface-id $(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/interface-id 2> /dev/null|head -1) --private-ip-addresses $VIP --allow-reassignment


セカンダリプライベートIPアドレスが付与されていることを確認します。


if aws ec2 describe-network-interfaces --network-interface-ids $(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/interface-id 2> /dev/null|head -1) | grep -q $(echo $VIP | sed -e 's/\./\\./g'); then
>   echo active
> else
>   echo not active
> fi
active


セカンダリプライベートIPアドレスを削除します。


aws ec2 unassign-private-ip-addresses --network-interface-id $(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/interface-id 2> /dev/null|head -1) --private-ip-addresses $VIP


リソースエージェントは以下のようになります。


cat << 'EOF' | sudo tee /usr/lib/ocf/resource.d/heartbeat/SIP
#!/bin/bash
#
#     SIP OCF RA. compliant AWS secondary private ip address script.
#
#   (c) 2009-2010 Florian Haas, Dejan Muhamedagic,
#                 and Linux-HA contributors
#
#      modified by Katsuaki Hamada (hamada@pc-office.net), 1 July 2018
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it would be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Further, this software is distributed without any warranty that it is
# free of the rightful claim of any third person regarding infringement
# or the like.  Any license provided herein, whether implied or
# otherwise, applies only to this software file.  Patent licenses, if
# any, provided herein do not apply to combinations of this program with
# other software, or any other product whatsoever.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
#

#######################################################################
# Initialization:
: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs

# Lockfile, used for selecting a target ID
LOCKFILE=${HA_RSCTMP}/sip.lock
#######################################################################

meta_data() {
  cat <<END
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="SIP" version="1.0">
<version>1.0</version>

<longdesc lang="en">
Compliant AWS secondary private ip address script.
</longdesc>
<shortdesc lang="en">SIP agent</shortdesc>

<parameters>
<parameter name="vip" required="1" unique="1">
<longdesc lang="en">
VIP.
</longdesc>
<shortdesc lang="en">VIP</shortdesc>
<content type="string" />
</parameter>
</parameters>

<actions>
<action name="start" timeout="10" />
<action name="stop" timeout="10" />
<action name="status" timeout="10" interval="30" depth="0" />
<action name="monitor" timeout="10" interval="30" depth="0" />
<action name="meta-data" timeout="5" />
<action name="validate-all" timeout="10" />
</actions>
</resource-agent>
END
}

#######################################################################

export AWS_DEFAULT_REGION=$(curl http://169.254.169.254/latest/dynamic/instance-identity/document 2> /dev/null|grep region|awk -F\" '{print $4}')

SIP_usage() {
  cat <<END
usage: $0 {start|stop|status|monitor|validate-all|meta-data}

Expects to have a fully populated OCF RA-compliant environment set.
END
}

SIP_start() {
  if aws ec2 assign-private-ip-addresses --network-interface-id $(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/interface-id 2> /dev/null|head -1) --private-ip-addresses $OCF_RESKEY_vip --allow-reassignment; then
    return $OCF_SUCCESS
  else
    return $OCF_ERR_GENERIC
  fi
}

SIP_stop() {
  if aws ec2 unassign-private-ip-addresses --network-interface-id $(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/interface-id 2> /dev/null|head -1) --private-ip-addresses $OCF_RESKEY_vip; then
    return $OCF_SUCCESS
  else
    return $OCF_ERR_GENERIC
  fi
}

SIP_monitor() {
  if aws ec2 describe-network-interfaces --network-interface-ids $(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/interface-id 2> /dev/null|head -1) | grep -q $(echo $OCF_RESKEY_vip | sed -e 's/\./\\./g'); then
    return $OCF_SUCCESS
  else
    return $OCF_NOT_RUNNING
  fi
}

SIP_validate() {
  if ! ocf_is_probe; then
    # Do we have all required binaries?
    check_binary aws
  fi
  return $OCF_SUCCESS
}

case $1 in
  meta-data)  meta_data; exit $OCF_SUCCESS;;
  usage|help) SIP_usage; exit $OCF_SUCCESS;;
esac

# Everything except usage and meta-data must pass the validate test
SIP_validate

case $__OCF_ACTION in
  start)          SIP_start;;
  stop)           SIP_stop;;
  monitor|status) SIP_monitor;;
  reload)         ocf_log err "Reloading..."; SIP_start;;
  validate-all)   ;;
  *)              SIP_usage; exit $OCF_ERR_UNIMPLEMENTED;;
esac
rc=$?
ocf_log debug "${OCF_RESOURCE_INSTANCE} $__OCF_ACTION : $rc"
exit $rc
EOF
sudo chmod 755 /usr/lib/ocf/resource.d/heartbeat/SIP



【ロール】

EC2に紐づけるロールの作成例は以下の通りです。Actionは3つのリソースエージェントのどれを選んでも問題ないようにリストアップしているので、必要に応じて削っていただければと思います。


cat << 'EOF' > vip-role.json
{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

cat << 'EOF' >vip-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "ec2:DescribeAddresses",
        "ec2:AssociateAddress",
        "ec2:DisassociateAddress",
        "ec2:DescribeRouteTables",
        "ec2:CreateRoute",
        "ec2:DeleteRoute",
        "ec2:DescribeNetworkInterfaces",
        "ec2:AssignPrivateIpAddresses",
        "ec2:UnassignPrivateIpAddresses"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}
EOF

aws iam create-role --role-name vip-role --assume-role-policy-document file://vip-role.json
{
    "Role": {
        "AssumeRolePolicyDocument": {
            "Version": "2008-10-17",
            "Statement": [
                {
                    "Action": "sts:AssumeRole",
                    "Principal": {
                        "Service": "ec2.amazonaws.com"
                    },
                    "Effect": "Allow",
                    "Sid": ""
                }
            ]
        },
        "RoleId": "AROAIUYAU2DBXLFEDX3WG",
        "CreateDate": "2018-07-01T03:39:06.243Z",
        "RoleName": "vip-role",
        "Path": "/",
        "Arn": "arn:aws:iam::999999999999:role/vip-role"
    }
}

ret=$(aws iam create-policy --policy-name vip-policy --policy-document file://vip-policy.json)
echo "$ret"
{
    "Policy": {
        "PolicyName": "vip-policy",
        "CreateDate": "2018-07-01T03:39:27.195Z",
        "AttachmentCount": 0,
        "IsAttachable": true,
        "PolicyId": "ANPAJ2TASVJGLJF3EOLBI",
        "DefaultVersionId": "v1",
        "Path": "/",
        "Arn": "arn:aws:iam::999999999999:policy/vip-policy",
        "UpdateDate": "2018-07-01T03:39:27.195Z"
    }
}

aws iam attach-role-policy --role-name vip-role --policy-arn $(echo "$ret" | grep arn:aws:iam | awk -F\" '{print $4}')

aws iam create-instance-profile --instance-profile-name vip-role --path /
{
    "InstanceProfile": {
        "InstanceProfileId": "AIPAII33IEQ6L5DRU7K6W",
        "Roles": [],
        "CreateDate": "2018-07-01T03:40:22.450Z",
        "InstanceProfileName": "vip-role",
        "Path": "/",
        "Arn": "arn:aws:iam::999999999999:instance-profile/vip-role"
    }
}

aws iam add-role-to-instance-profile --instance-profile-name vip-role --role-name vip-role


0 件のコメント:

コメントを投稿