Amazon Web Services ブログ

オンプレミスの利用者がAmazon Route 53経由でSUSE HAEで保護されたSAP HANAインスタンスに接続する方法

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ドキュメントを参照してください。

以下の値が必要です。(ここでは値の例を示しています)

  • hostedzoneidZ22XDORCGO4P3T
  • fullnamesuse-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 河原が担当しました。原文はこちらです。