Stefan Schneiderは、Amazon Web Services(AWS)のソリューション アーキテクトです。
このブログ記事では、AWSクラウド上のSUSE Linux Enterprise High Availability Extension(SLES HAE)によって保護されているSAP HANAデータベースに、オンプレミスの利用者が接続できるようになる、Amazon Route 53エージェントについて説明します。このエージェントは、Amazon Route 53を介して利用者を振り分ける機能を提供します。
このエージェントを利用するには、SAP Note 2309342 – SUSE Linux Enterprise High Availability Extension on AWS for SAP HANAに記載されているような、オーバーレイIPアドレスエージェントを使用して実装された高可用性フェイルオーバー用のSLES HAEの設定が必要です。(SAPノートを参照するにはSAP Support Portalの認証情報が必要です)
Route 53エージェントは、SAP HANAデータベースを保護するクラスタリソース管理フレームワークであるPacemakerを含むSLES HAEの機能を拡張します。このエージェントとオーバーレイIPアドレスエージェントを組み合せた利用方法は、SAPセントラルインスタンス(CI)を含む、AWSクラウド上のSLESでサポートされるすべての構成のSLES HAEに対して適用できます。
Route 53エージェントは、現時点では未サポートのオープンソースツールとして利用可能となっており、ソースコードはこのブログ記事の中で公開します。現在、AWSとSUSEが協力し、サポート済みツールとして本家リポジトリから利用できるように調整しています。お客様のAWSアカウントでSAP HANAを構築した後、このエージェントをインストールできます。
Route 53エージェントの仕組み
現在のオーバーレイIPアドレスエージェントでは、仮想プライベートクラウド(VPC)内のアプリケーションサーバーは、そのVPC内の保護されたSAP HANAサーバーに接続できますが、オンプレミスのアプリケーションからの接続はできません。
そのため、RDPや踏み台サーバーを経由したVPC内で管理されたSAP HANA Studioなどのアプリケーションが必要となり、オンプレミスの利用者にはいくつかの不便が生じます。Route 53エージェントは、名前ベースのアプローチを使用してオンプレミスの利用者がVPCに接続できるようにすることで、この制限を回避します。2つのエージェントは並行して動作します。オーバーレイIPエージェントは、オーバーレイIPアドレスからアクティブなノードにトラフィックをルーティングします。Route 53エージェントは、SAP HANAサーバーの名前と紐づく現在のIPアドレスを更新します。
私は、ウェブサイトScaling Bitsに、DNS Name Failover for Highly Available AWS Servicesという記事で、このエージェントの内部動作を掲載しました。この記事では、Route 53のホストゾーンの更新方法について説明しています。
Route 53エージェントは、SAPから独立しています。また、SLES HAEのSAP NetWeaverセントラルインスタンス(CI)コンポーネントとも連動します。
前提条件
この記事では、SLES Pacemaker クラスタを含む、オーバーレイIPアドレスエージェントを既にインストールしていることを前提としています。さらに、Route 53エージェントには以下が必要です。
- SLES HAE クラスタインスタンスがRoute 53レコードを更新するためのポリシー
- rootユーザー用のAWSプロファイル
- Route 53のプライベートホストゾーン
ポリシーの追加
以下のポリシーをSLES HAE クラスタインスタンスに追加して、Route 53のAレコードを更新できるようにします。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1471878724000",
"Effect": "Allow",
"Action": [
"route53:ChangeResourceRecordSets",
"route53:GetChange",
"route53:ListResourceRecordSets",
],
"Resource": [
"*"
]
}
]
}
rootユーザ用のAWSプロファイルの作成
エージェントは、AWSプロファイルを使用してAWS CLIコマンドを呼び出し、そしてオーバーレイIPエージェントでも同じプロファイルを使用します。ウェブサイトScaling Bitsで説明しているように、プロキシ設定が必要な場合もあります。
任意のプロファイル名を選択できます。エージェントはデフォルトの名前としてclusterを使用するため、必要に応じてリファレンスを修正する必要があります。
Route 53のプライベートホストゾーンの作成
エージェントは、Route 53 ホストゾーンのAレコードを更新します。つまり、お客様のAWSアカウント内に要求されるインフラストラクチャが必要です。プライベートホストゾーンの作成方法については、AWSドキュメントを参照してください。
以下の値が必要です。(ここでは値の例を示しています)
- hostedzoneid:
Z22XDORCGO4P3T
- fullname:
suse-service.awslab.cloud.mylab.corp.
(最後のドットは重要です!)
エージェントのインストール
このブログ記事の最後に掲載しているソースコードをテキストファイルにコピーし、ディレクトリ/usr/lib/ocf/resource.d/aws 配下に置きます。このソースコードは、MITライセンスのもとで利用できます。
クラスタの設定
Pacemakerで、以下のようにクラスタの構成を編集(crm configure editコマンド)します。
primitive res_AWS_ROUTE53 ocf:aws:aws-vpc-route53 \
params hostedzoneid=Z22XDORCGO4P3T ttl=10 fullname=suse-service5.awslab.cloud.mylab.corp. profile=cluster \
op start interval=0 timeout=180 \
op stop interval=0 timeout=180 \
op monitor interval=300 timeout=180 \
meta target-role=Started
以下の必須パラメータを適切な値に置き換えてください。
- hostedzoneid: Route 53のホストゾーンID。これは、Route 53のレコードセットです
- ttl: Route 53のAレコードの秒単位の有効期限(TTL)。(合理的なデフォルト値は10です)
- fullname: IPアドレスをホストするサービスのフルネーム。例えば、
suse-service.awslab.cloud.mylab.corp.
(最後のドットは重要です!)
- profile: rootアカウントのAWS CLIプロファイルの名前。/root/.aws/configファイルには、以下のようなエントリが必要です
[profile cluster]
– clusterの場所にお客様のプロファイル名
region = us-east-1
(お客様が利用するAWSリージョン)
output = text
(この設定も必要)
AWS固有の制約に関する設定
Route 53エージェントは、SAP HANAデータベースと同じノードで動作する必要があります。この制約により、必然的に同じノードに置かなければなりません。
以下の内容のaws-route53-constraint.txtというファイルを作成します。前述と同じリソース識別子を使用していることを確認してください。
colocation col_Route53 2000: res_AWS_ROUTE53:Started msl_SAPHana_SID_HDB00:Master
order ord_SAPHana 2000: cln_SAPHanaTopology_SID_HDB00 msl_SAPHana_SID_HDB00
この例では、SAP SIDがリソース名の一部としてコード化されています。この値は構成によって異なります。
このファイルを設定に追加するために、スーパーユーザーとして次のコマンドを実行します。ファイル名はaws-route53-constraint.txtです。
crm configure load update aws-route53-constraint.txt
まとめ
Route 53エージェントは、クラスタリソース管理フレームワークであるPacemakerとともに使用され、SAP HANAデータベースを保護するSLES HAEの機能を拡張します。また、アクティブなABAP SAPセントラルサービス(ASCS)サーバーを検出し、Route 53を介して利用者を振り分けることで、SAPセントラルインスタンスを保護することもできます。
エージェントは、SLES HAE SAPエージェントの依存エージェントとして実行されます。つまり、個々の管理を必要とはしません。
SLES HAEシステムにオンプレミスからの接続が必要な場合は、このエージェントをインストールすることをお勧めします。また、ご質問やご意見がある場合はお気軽にお問合せください。
Route 53エージェントのソースコード
#!/bin/bash
#
# Copyright 2017 Amazon.com, Inc. and its affiliates. All Rights Reserved.
# Licensed under the MIT License.
#
# Copyright 2017 Amazon.com, Inc. and its affiliates
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
#
#
# OCF resource agent to move an IP address within a VPC in the AWS
# Written by Stefan Schneider , Martin Tegmeier (AWS)
# Based on code of Markus Guertler#
#
#
# OCF resource agent to move an IP address within a VPC in the AWS
# Written by Stefan Schneider (AWS) , Martin Tegmeier (AWS)
# Based on code of Markus Guertler (SUSE)
#
# Mar. 15, 2017, vers 1.0.2
#######################################################################
# Initialization:
: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs
OCF_RESKEY_ttl_default=10
: ${OCF_RESKEY_ttl:=${OCF_RESKEY_ttl_default}}
#######################################################################
usage() {
cat <<-EOT
usage: $0 {start|stop|status|monitor|validate-all|meta-data}
EOT
}
metadata() {
cat <<END
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="aws-vpc-route53">
<version>1.0</version>
<longdesc lang="en">
Update Route53 record of Amazon Webservices EC2 by updating an entry in a
hosted zone ID table.
AWS instances will require policies which allow them to update Route53 ARecords:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1471878724000",
"Effect": "Allow",
"Action": [
"route53:ChangeResourceRecordSets",
"route53:GetChange",
"route53:ListResourceRecordSets",
],
"Resource": [
"*"
]
}
]
}
Example Cluster Configuration:
Use a configuration in "crm configure edit" which looks as follows. Replace
hostedzoneid, fullname and profile with the appropriate values:
primitive res_route53 ocf:heartbeat:aws-vpc-route53 \
params hostedzoneid=Z22XDORCGO4P3T fullname=suse-service5.awslab.cloud.sap.corp. profile=cluster \
op start interval=0 timeout=180 \
op stop interval=0 timeout=180 \
op monitor interval=300 timeout=180 \
meta target-role=Started
</longdesc>
<shortdesc lang="en">Update Route53 VPC record for AWS EC2</shortdesc>
<parameters>
<parameter name="hostedzoneid" required="1">
<longdesc lang="en">
Hosted zone ID of Route 53. This is the table of
the Route 53 record.
</longdesc>
<shortdesc lang="en">AWS hosted zone ID</shortdesc>
<content type="string" default="" />
</parameter>
<parameter name="fullname" required="1">
<longdesc lang="en">
The full name of the service which will host the IP address.
Example: suse-service5.awslab.cloud.sap.corp.
Note: The trailing dot is important to Route53!
</longdesc>
<shortdesc lang="en">Full service name</shortdesc>
<content type="string" default="" />
</parameter>
<parameter name="ttl" required="0">
<longdesc lang="en">
Time to live for Route53 ARECORD
</longdesc>
<shortdesc lang="en">ARECORD TTL</shortdesc>
<content type="string" default="${OCF_RESKEY_ttl_default}" />
</parameter>
<parameter name="profile" required="1">
<longdesc lang="en">
The name of the AWS CLI profile of the root account. This
profile will have to use the "text" format for CLI output.
The file /root/.aws/config should have an entry which looks
like:
[profile cluster]
region = us-east-1
output = text
"cluster" is the name which has to be used in the cluster
configuration. The region has to be the current one. The
output has to be "text".
</longdesc>
<shortdesc lang="en">AWS Profile Name</shortdesc>
<content type="string" default="" />
</parameter>
</parameters>
<actions>
<action name="start" timeout="180" />
<action name="stop" timeout="180" />
<action name="monitor" depth="0" timeout="180" interval="300" />
<action name="validate-all" timeout="5" />
<action name="meta-data" timeout="5" />
</actions>
</resource-agent>
END
}
debugger() {
ocf_log debug "DEBUG: $1"
}
ec2ip_validate() {
debugger "function: validate"
# Full name
[[ -z "$OCF_RESKEY_fullname" ]] && ocf_log error "Full name parameter not set $OCF_RESKEY_fullname!" && exit $OCF_ERR_CONFIGURED
# Hosted Zone ID
[[ -z "$OCF_RESKEY_hostedzoneid" ]] && ocf_log error "Hosted Zone ID parameter not set $OCF_RESKEY_hostedzoneid!" && exit $OCF_ERR_CONFIGURED
# profile
[[ -z "$OCF_RESKEY_profile" ]] && ocf_log error "AWS CLI profile not set $OCF_RESKEY_profile!" && exit $OCF_ERR_CONFIGURED
# TTL
[[ -z "$OCF_RESKEY_ttl" ]] && ocf_log error "TTL not set $OCF_RESKEY_ttl!" && exit $OCF_ERR_CONFIGURED
debugger "Testing aws command"
aws --version 2>&1
if [ "$?" -gt 0 ]; then
error "Error while executing aws command as user root! Please check if AWS CLI tools (Python flavor) are properly installed and configured." && exit $OCF_ERR_INSTALLED
fi
debugger "ok"
if [ -n "$OCF_RESKEY_profile" ]; then
AWS_PROFILE_OPT="--profile $OCF_RESKEY_profile"
else
AWS_PROFILE_OPT="--profile default"
fi
return $OCF_SUCCESS
}
ec2ip_monitor() {
ec2ip_validate
debugger "function: ec2ip_monitor: check Route53 record "
IPADDRESS="$(ec2metadata aws ip | grep local-ipv4 | /usr/bin/awk '{ print $2 }')"
ARECORD="$(aws $AWS_PROFILE_OPT route53 list-resource-record-sets --hosted-zone-id $OCF_RESKEY_hostedzoneid --query "ResourceRecordSets[?Name=='$OCF_RESKEY_fullname']" | grep RESOURCERECORDS | /usr/bin/awk '{ print $2 }' )"
debugger "function: ec2ip_monitor: found IP address: $ARECORD ."
if [ "${ARECORD}" == "${IPADDRESS}" ]; then
debugger "function: ec2ip_monitor: ARECORD $ARECORD found"
return $OCF_SUCCESS
else
debugger "function: ec2ip_monitor: no ARECORD found"
return $OCF_NOT_RUNNING
fi
return $OCF_SUCCESS
}
ec2ip_stop() {
ocf_log info "EC2: Bringing down Route53 agent. (Will remove ARECORD)"
IPADDRESS="$(ec2metadata aws ip | grep local-ipv4 | /usr/bin/awk '{ print $2 }')"
ARECORD="$(aws $AWS_PROFILE_OPT route53 list-resource-record-sets --hosted-zone-id $OCF_RESKEY_hostedzoneid --query "ResourceRecordSets[?Name=='$OCF_RESKEY_fullname']" | grep RESOURCERECORDS | /usr/bin/awk '{ print $2 }' )"
debugger "function: ec2ip_monitor: found IP address: $ARECORD ."
if [ ${ARECORD} != ${IPADDRESS} ]; then
debugger "function: ec2ip_monitor: no ARECORD found"
return $OCF_SUCCESS
else
debugger "function: ec2ip_monitor: ARECORD $ARECORD found"
# determine IP address
IPADDRESS="$(ec2metadata aws ip | grep local-ipv4 | /usr/bin/awk '{ print $2 }')"
# Patch file
debugger "function ec2ip_stop: will delete IP address to ${IPADDRESS}"
ocf_log info "EC2: Updating Route53 $OCF_RESKEY_hostedzoneid with $IPADDRESS for $OCF_RESKEY_fullname"
ROUTE53RECORD="/var/tmp/route53-${OCF_RESKEY_hostedzoneid}-${IPADDRESS}.json"
echo "{ " > ${ROUTE53RECORD}
echo " \"Comment\": \"Update record to reflect new IP address for a system \", " >> ${ROUTE53RECORD}
echo " \"Changes\": [ " >> ${ROUTE53RECORD}
echo " { " >> ${ROUTE53RECORD}
echo " \"Action\": \"DELETE\", " >> ${ROUTE53RECORD}
echo " \"ResourceRecordSet\": { " >> ${ROUTE53RECORD}
echo " \"Name\": \"${OCF_RESKEY_fullname}\", " >> ${ROUTE53RECORD}
echo " \"Type\": \"A\", " >> ${ROUTE53RECORD}
echo " \"TTL\": ${OCF_RESKEY_ttl}, " >> ${ROUTE53RECORD}
echo " \"ResourceRecords\": [ " >> ${ROUTE53RECORD}
echo " { " >> ${ROUTE53RECORD}
echo " \"Value\": \"${IPADDRESS}\" " >> ${ROUTE53RECORD}
echo " } " >> ${ROUTE53RECORD}
echo " ] " >> ${ROUTE53RECORD}
echo " } " >> ${ROUTE53RECORD}
echo " } " >> ${ROUTE53RECORD}
echo " ] " >> ${ROUTE53RECORD}
echo "}" >> ${ROUTE53RECORD}
cmd="aws --profile ${OCF_RESKEY_profile} route53 change-resource-record-sets --hosted-zone-id ${OCF_RESKEY_hostedzoneid} \
--change-batch file://${ROUTE53RECORD} "
debugger "function ec2ip_start: executing command: $cmd"
CHANGEID=$($cmd | grep CHANGEINFO | /usr/bin/awk -F'\t' '{ print $3 }' )
debugger "Change id: ${CHANGEID}"
rm ${ROUTE53RECORD}
CHANGEID=$(echo $CHANGEID |cut -d'/' -f 3 |cut -d'"' -f 1 )
debugger "Change id: ${CHANGEID}"
STATUS="PENDING"
MYSECONDS=2
while [ "$STATUS" = 'PENDING' ]; do
sleep ${MYSECONDS}
STATUS="$(aws --profile ${OCF_RESKEY_profile} route53 get-change --id $CHANGEID | grep CHANGEINFO | /usr/bin/awk -F'\t' '{ print $4 }' |cut -d'"' -f 2 )"
debugger "Waited for ${MYSECONDS} seconds and checked execution of Route 53 update status: ${STATUS} "
done
return $OCF_SUCCESS
fi
return $OCF_SUCCESS
}
ec2ip_start() {
# determine IP address
IPADDRESS="$(ec2metadata aws ip | grep local-ipv4 | /usr/bin/awk '{ print $2 }')"
# Patch file
debugger "function ec2ip_start: will update IP address to ${IPADDRESS}"
ocf_log info "EC2: Updating Route53 $OCF_RESKEY_hostedzoneid with $IPADDRESS for $OCF_RESKEY_fullname"
ROUTE53RECORD="/var/tmp/route53-${OCF_RESKEY_hostedzoneid}-${IPADDRESS}.json"
echo "{ " > ${ROUTE53RECORD}
echo " \"Comment\": \"Update record to reflect new IP address for a system \", " >> ${ROUTE53RECORD}
echo " \"Changes\": [ " >> ${ROUTE53RECORD}
echo " { " >> ${ROUTE53RECORD}
echo " \"Action\": \"UPSERT\", " >> ${ROUTE53RECORD}
echo " \"ResourceRecordSet\": { " >> ${ROUTE53RECORD}
echo " \"Name\": \"${OCF_RESKEY_fullname}\", " >> ${ROUTE53RECORD}
echo " \"Type\": \"A\", " >> ${ROUTE53RECORD}
echo " \"TTL\": ${OCF_RESKEY_ttl} , " >> ${ROUTE53RECORD}
echo " \"ResourceRecords\": [ " >> ${ROUTE53RECORD}
echo " { " >> ${ROUTE53RECORD}
echo " \"Value\": \"${IPADDRESS}\" " >> ${ROUTE53RECORD}
echo " } " >> ${ROUTE53RECORD}
echo " ] " >> ${ROUTE53RECORD}
echo " } " >> ${ROUTE53RECORD}
echo " } " >> ${ROUTE53RECORD}
echo " ] " >> ${ROUTE53RECORD}
echo "}" >> ${ROUTE53RECORD}
cmd="aws --profile ${OCF_RESKEY_profile} route53 change-resource-record-sets --hosted-zone-id ${OCF_RESKEY_hostedzoneid} \
--change-batch file://${ROUTE53RECORD} "
debugger "function ec2ip_start: executing command: $cmd"
CHANGEID=$($cmd | grep CHANGEINFO | /usr/bin/awk -F'\t' '{ print $3 }' )
debugger "Change id: ${CHANGEID}"
rm ${ROUTE53RECORD}
CHANGEID=$(echo $CHANGEID |cut -d'/' -f 3 |cut -d'"' -f 1 )
debugger "Change id: ${CHANGEID}"
STATUS="PENDING"
MYSECONDS=2
while [ "$STATUS" = 'PENDING' ]; do
sleep ${MYSECONDS}
STATUS="$(aws --profile ${OCF_RESKEY_profile} route53 get-change --id $CHANGEID | grep CHANGEINFO | /usr/bin/awk -F'\t' '{ print $4 }' |cut -d'"' -f 2 )"
debugger "Waited for ${MYSECONDS} seconds and checked execution of Route 53 update status: ${STATUS} "
done
return $OCF_SUCCESS
}
###############################################################################
case $__OCF_ACTION in
usage|help)
usage
exit $OCF_SUCCESS
;;
meta-data)
metadata
exit $OCF_SUCCESS
;;
monitor)
ec2ip_monitor
;;
stop)
ec2ip_stop
;;
validate-all)
ec2ip_validate
;;
start)
ec2ip_start
;;
*)
usage
exit $OCF_ERR_UNIMPLEMENTED
;;
esac
翻訳はPartner SA 河原が担当しました。原文はこちらです。