Introduction
概要
AWSのLambda上で、ElastiCache(Redis)サーバのうち、primaryとreplicaのそれぞれのIPアドレスを取得してみます。
前提
- Redis用ElastiCacheを使用する
- クラスターモードはOFFである(つまりクラスターは使用しない)
- Multi AZによる自動フェイルオーバーを有効にしている
- 負荷分散の目的で、primaryサーバと、複数のreplicaサーバが存在する
動機(一つ目)
primaryサーバに障害が発生したとき、自動フェイルオーバーでreplicaサーバが自動的にprimaryサーバに昇格するのはとても便利!
しかも、primaryエンドポイントの名前にアクセスする限りは、ユーザはフェイルオーバーを意識する必要が無い (自動的にprimaryサーバを見てくれる)。
でも、replicaサーバにはそんな仕組みが無いので、primaryに昇格したサーバや、障害で切り離されたサーバを意識する必要があります。
動機(二つ目)
EC2から短期間に大量の名前問い合わせを行うと、DNSがロックアウト状態になってエラーを返すようになってしまうようです
(条件等は未確認だが、事実、現象として発生しました)。
したがって、上記のような状態が発生する環境下では、クライアントが都度問い合わせを行うのではなく、ある程度まとめて問い合わせを行うことでDNSへのアクセスを減らす必要があります。
対応
上記の二つの動機を満たすため、ElastiCacheのprimaryとreplicaサーバの状態を随時更新し、また、名前解決を行うlambdaを用意することにしたのです。
Lambda
言語
今回はPython3系を使用しました (Python 3.6)。
ロール
ElastiCacheの情報を読み込む必要がありますので、Lambdaには、AmazonElastiCacheReadOnlyAccess権限を含むロールを持たせる必要があります。
プログラム
import boto3
import socket
# 対象となるElastiCacheのクラスター名
CLUSTER_NAME='your-redis-cluster-name'
def get_elasticache_info():
# boto3のElastiCacheクライアントを取得
cache_client = boto3.client('elasticache')
# describeReplicationGroupsコマンドの発行
info = cache_client.describe_replication_groups(ReplicationGroupId=CLUSTER_NAME)
if 'ReplicationGroups' in info:
if 'NodeGroups' in info['ReplicationGroups'][0]:
# コマンドの結果を解析
return parse_node_groups(info['ReplicationGroups'][0]['NodeGroups'][0])
raise Exception('Unexpected AWS result')
def parse_node_groups(group):
if 'NodeGroupMembers' not in group:
raise Exception('No Node Group Members')
primary_node_end_point = None
replica_node_end_point = []
# describeReplicationGroupsコマンド内のNodeGroupMembersを解析
for cluster in group['NodeGroupMembers']:
if 'ReadEndpoint' not in cluster or 'CurrentRole' not in cluster:
raise Exception('Unexpected node group member')
# クラスターのendpointを取得し、名前解決を行う
end_point = lookup(cluster['ReadEndpoint'])
current_role = cluster['CurrentRole']
# クラスターの種類(primary/replica)毎に情報を保存
if 'primary' == current_role:
primary_node_end_point = end_point
elif 'replica' == current_role:
replica_node_end_point.append(end_point)
else:
raise Exception('Unknown role')
if primary_node_end_point is None:
raise Exception('No primary node')
return [
primary_node_end_point,
replica_node_end_point
]
def lookup(endpoint):
if 'Address' not in endpoint:
raise Exception('No address in endpoint')
address = endpoint['Address']
# 名前解決
return socket.gethostbyname(address)
def lambda_handler(event, context):
servers = get_elasticache_info()
return servers
このlambdaを呼び出すと、[primaryサーバのIPアドレス, [replicaサーバのIPアドレス, ....]]の結果を返します。
利用方法
API Gatewayと結びつけて、定期的にプログラムから呼び出すとか、そんな感じで(雑)。
TO-DO
- フェイルオーバーの途中に呼び出したらどうなるか調べる
- 名前解決のretryとかいる?
- 直接関係ないけど、cloudwatchでフェイルオーバーやreplicaサーバの死亡を検知する方法も調べとく
- 例外のエラーメッセージが適当(ごめんなさい)