[Amazon FSx for NetApp ONTAP] SnapMirror relationshipのHealthをCloudWatchメトリクスにPUTしてみた
SnapMirrorの転送エラーを検知したい
以下記事でも触れているとおり、SnapMirror relationshipのHealthはCloudWatchメトリクスは存在しません。
そのため、以下のいずれかの対応が必要になります。
- NetAppのData infrastructure InsightでSnapMirror relationshipのHealthを監視する
- SnapMirror relationshipの状態をPutMetricDataするVPC Lambdaを定期実行し、そのカスタムメトリクスをベースにCloudWatchアラームを設定する
前者のData infrastructure Insight(以降DII)はNetAppが提供しているSaaSです。
FSxNのデータをDIIへ連携するにあたってAUという役割をするサーバーが必要になります。EC2インスタンスにインストールすることも可能です。
ただし、EC2インスタンスを用意するとなると、どうしても運用コストや金銭的コストが気になります。
そのため、SnapMirrorの転送エラーを検知したいがためだけにDIIを導入するのは少しコスト的に気になるのではないでしょうか。
DIIをその他の用途を利用する想定がない場合は、後者の方法を採用したいところです。
ということで実際に後者の対応をしてみました。
使用するコードの構成
Lambda関数で実行する処理
Lambda関数で実行する処理は以下のとおりです。
- CloudWatchクライアントの初期化
- AWS Parameter and Secrets Lambda extension で SSM Parameter Storeに保存している認証情報を取得
- FSxNファイルシステムに接続し、SnapMirror relationshipの情報を取得
- SnapMirror relationship個別のHealthをカスタムメトリクスとしてPUT
- HealthがTrueの場合は1
- HealthがFalseの場合は0
- DestinationのSVM単位でSnapMirror relationshipのHealthをカスタムメトリクスとしてPUT
- Destination SVM 単位で、全てのSnapMirror relationshipのHealthがTrueの場合は1
- Destination SVM 単位で、いずれかのSnapMirror relationshipのHealthがFalseの場合は0
AWS Parameter and Secrets Lambda extensionでSSM Parameter Storeにアクセスする際に、not ready to serve traffic, please wait
と400エラーが返ってくる時があります。緩和策としてExponential Backoffで複数回リトライするような処理を組み込んでいます。
実際のログは以下のとおりです。
{
"time": "2024-11-17T01:52:37.563Z",
"type": "platform.initStart",
"record": {
"initializationType": "on-demand",
"phase": "init",
"runtimeVersion": "python:3.12.v38",
"runtimeVersionArn": "arn:aws:lambda:us-east-1::runtime:6ea5f72a8a27124ba0bc2bb6d2d4094f8560fac75ead0a3d8434a209061a5566",
"functionName": "MonitoringSnapmirrorRelati-LambdaConstruct4CD6E168-VaruG0t6vFp6",
"functionVersion": "$LATEST"
}
}
[AWS Parameters and Secrets Lambda Extension] 2024/11/17 01:52:37 PARAMETERS_SECRETS_EXTENSION_LOG_LEVEL is info. Log level set to info.
[AWS Parameters and Secrets Lambda Extension] 2024/11/17 01:52:37 INFO Systems Manager Parameter Store and Secrets Manager Lambda Extension 1.0.103
[AWS Parameters and Secrets Lambda Extension] 2024/11/17 01:52:37 INFO Serving on port 2773
{
"timestamp": "2024-11-17T01:52:42Z",
"level": "INFO",
"message": "successfully patched module httplib",
"logger": "aws_xray_sdk.core.patcher",
"requestId": ""
}
{
"timestamp": "2024-11-17T01:52:42Z",
"level": "INFO",
"message": "successfully patched module sqlite3",
"logger": "aws_xray_sdk.core.patcher",
"requestId": ""
}
{
"timestamp": "2024-11-17T01:52:42Z",
"level": "INFO",
"message": "successfully patched module requests",
"logger": "aws_xray_sdk.core.patcher",
"requestId": ""
}
{
"timestamp": "2024-11-17T01:52:42Z",
"level": "INFO",
"message": "successfully patched module botocore",
"logger": "aws_xray_sdk.core.patcher",
"requestId": ""
}
{
"timestamp": "2024-11-17T01:52:42Z",
"level": "INFO",
"message": "Found credentials in environment variables.",
"logger": "botocore.credentials",
"requestId": ""
}
{
"time": "2024-11-17T01:52:43.044Z",
"type": "platform.extension",
"record": {
"name": "AWSParametersAndSecretsLambdaExtension",
"state": "Ready",
"events": [
"INVOKE",
"SHUTDOWN"
]
}
}
{
"time": "2024-11-17T01:52:43.046Z",
"type": "platform.start",
"record": {
"requestId": "88eeef8e-4c34-4175-bcb0-f3392150e694",
"version": "$LATEST",
"tracing": {
"spanId": "52abbf594f8c3bb0",
"type": "X-Amzn-Trace-Id",
"value": "Root=1-67394c65-6fef1dac569797352a348fc4;Parent=73517fbe3d0fdcff;Sampled=1"
}
}
}
{
"timestamp": "2024-11-17T01:52:43Z",
"level": "INFO",
"message": "Requesting SSM parameter from: http://localhost:2773/systemsmanager/parameters/get/?name=/fsxn/non-97-fsxn/fsxadmin-readonly/password&withDecryption=true",
"logger": "index",
"requestId": "88eeef8e-4c34-4175-bcb0-f3392150e694"
}
[AWS Parameters and Secrets Lambda Extension] 2024/11/17 01:52:44 INFO ready to serve traffic
{
"timestamp": "2024-11-17T01:52:44Z",
"level": "WARNING",
"message": "Extension not ready. Retrying in 1 seconds. Attempt 1/4",
"logger": "index",
"requestId": "88eeef8e-4c34-4175-bcb0-f3392150e694"
}
{
"timestamp": "2024-11-17T01:52:49Z",
"level": "INFO",
"message": "Successfully put metric data: SnapMirrorRelationshipHealth",
"logger": "index",
"requestId": "88eeef8e-4c34-4175-bcb0-f3392150e694"
}
{
"timestamp": "2024-11-17T01:52:49Z",
"level": "INFO",
"message": "Successfully put metric data: SnapMirrorRelationshipHealth",
"logger": "index",
"requestId": "88eeef8e-4c34-4175-bcb0-f3392150e694"
}
{
"timestamp": "2024-11-17T01:52:49Z",
"level": "INFO",
"message": "Unhealthy SnapMirror Relationship detected Unhealthy Reason: [{'code': '13303943', 'message': 'SnapMirror relationship is unhealthy. Reason: Scheduled update failed to start. (Destination svm:vol_ntfs_dst must be a data-protection volume.).'}], Relationship UUID: 8a6d0455-9b21-11ef-accd-b31c82a68aa5, Source Path: svm:vol_ntfs, Destination Path: svm:vol_ntfs_dst",
"logger": "index",
"requestId": "88eeef8e-4c34-4175-bcb0-f3392150e694"
}
{
"timestamp": "2024-11-17T01:52:49Z",
"level": "INFO",
"message": "Successfully put metric data: SnapMirrorRelationshipHealth",
"logger": "index",
"requestId": "88eeef8e-4c34-4175-bcb0-f3392150e694"
}
{
"time": "2024-11-17T01:52:49.531Z",
"type": "platform.report",
"record": {
"requestId": "88eeef8e-4c34-4175-bcb0-f3392150e694",
"metrics": {
"durationMs": 6483.897,
"billedDurationMs": 6484,
"memorySizeMB": 128,
"maxMemoryUsedMB": 122,
"initDurationMs": 5481.946
},
"tracing": {
"spanId": "52abbf594f8c3bb0",
"type": "X-Amzn-Trace-Id",
"value": "Root=1-67394c65-6fef1dac569797352a348fc4;Parent=73517fbe3d0fdcff;Sampled=1"
},
"status": "success"
}
}
この時のX-Rayのトレース結果は以下のとおりです。
http://localhost:2773/systemsmanager/parameters/get
が400エラーになっていることがわかります。
キャッシュが効いている場合は以下のようなログが出力されます。
{
"time": "2024-11-17T01:52:58.916Z",
"type": "platform.start",
"record": {
"requestId": "c28c37b6-2510-4faa-b7a6-88c69d2faf0e",
"version": "$LATEST",
"tracing": {
"spanId": "40be7a26287f3d90",
"type": "X-Amzn-Trace-Id",
"value": "Root=1-67394c7a-6ba543397d04c1e75aa2612e;Parent=3f9f22cae872f208;Sampled=1"
}
}
}
[AWS Parameters and Secrets Lambda Extension] 2024/11/17 01:52:58 INFO ready to serve traffic
{
"timestamp": "2024-11-17T01:52:58Z",
"level": "INFO",
"message": "Requesting SSM parameter from: http://localhost:2773/systemsmanager/parameters/get/?name=/fsxn/non-97-fsxn/fsxadmin-readonly/password&withDecryption=true",
"logger": "index",
"requestId": "c28c37b6-2510-4faa-b7a6-88c69d2faf0e"
}
{
"timestamp": "2024-11-17T01:53:00Z",
"level": "INFO",
"message": "Successfully put metric data: SnapMirrorRelationshipHealth",
"logger": "index",
"requestId": "c28c37b6-2510-4faa-b7a6-88c69d2faf0e"
}
{
"timestamp": "2024-11-17T01:53:00Z",
"level": "INFO",
"message": "Successfully put metric data: SnapMirrorRelationshipHealth",
"logger": "index",
"requestId": "c28c37b6-2510-4faa-b7a6-88c69d2faf0e"
}
{
"timestamp": "2024-11-17T01:53:00Z",
"level": "INFO",
"message": "Unhealthy SnapMirror Relationship detected Unhealthy Reason: [{'code': '13303943', 'message': 'SnapMirror relationship is unhealthy. Reason: Scheduled update failed to start. (Destination svm:vol_ntfs_dst must be a data-protection volume.).'}], Relationship UUID: 8a6d0455-9b21-11ef-accd-b31c82a68aa5, Source Path: svm:vol_ntfs, Destination Path: svm:vol_ntfs_dst",
"logger": "index",
"requestId": "c28c37b6-2510-4faa-b7a6-88c69d2faf0e"
}
{
"timestamp": "2024-11-17T01:53:00Z",
"level": "INFO",
"message": "Successfully put metric data: SnapMirrorRelationshipHealth",
"logger": "index",
"requestId": "c28c37b6-2510-4faa-b7a6-88c69d2faf0e"
}
{
"time": "2024-11-17T01:53:00.771Z",
"type": "platform.report",
"record": {
"requestId": "c28c37b6-2510-4faa-b7a6-88c69d2faf0e",
"metrics": {
"durationMs": 1854.397,
"billedDurationMs": 1855,
"memorySizeMB": 128,
"maxMemoryUsedMB": 122
},
"tracing": {
"spanId": "40be7a26287f3d90",
"type": "X-Amzn-Trace-Id",
"value": "Root=1-67394c7a-6ba543397d04c1e75aa2612e;Parent=3f9f22cae872f208;Sampled=1"
},
"status": "success"
}
}
この時のX-Rayのトレース結果は以下のとおりです。
実行時間はおおよそ2秒ほどです。コールドスタートの場合は14秒ほどかかることもあるのでかなり速くなりました。
とはいえ、SnapMirror relationshipの情報を取得する処理で1秒弱、カスタムメトリクスをPUTする処理でも1秒弱時間がかかっていそうですね。SnapMirror relationship数が多い場合はタイムアウト値に注意が必要そうです。
また、2分間隔でしばらく実行しましたが、実行時間はおおよそ1~2秒ほどでした。
コールドスタートとなる明確な条件は明らかになっていませんが、体感的には5分以上間隔が開くとコールドスタートになりそうです。
SnapMirror relationshipの情報の取得はONTAP REST APIのPythonクライアントライブラリを使用しました。
Pythonクライアントライブラリのリファレンスは以下にまとまっています。
SnapMirror relationship関連の操作は以下ドキュメントです。
細かいパラメーターを知りたい場合はONTAP REST APIのドキュメントも確認しておくと良いでしょう。
Pythonクライアントライブラリのファーストステップは以下サイトが参考になります。
実際のLambda関数のコードは以下のとおりです。
import os
import logging
import json
import urllib.parse
import urllib.request
import urllib.error
import boto3
import sys
import time
from typing import Dict, List, Any
from collections import defaultdict
from botocore.exceptions import ClientError, BotoCoreError
from netapp_ontap import config, HostConnection
from netapp_ontap.resources import SnapmirrorRelationship
from netapp_ontap.error import NetAppRestError
from aws_xray_sdk.core import patch_all
# 各種定義
NAMESPACE = os.environ.get('NAMESPACE', 'ONTAP/SnapMirror')
PARAMETERS_SECRETS_EXTENSION_HTTP_PORT = os.environ.get('PARAMETERS_SECRETS_EXTENSION_HTTP_PORT', '2773').upper()
SSM_ENDPOINT = 'http://localhost:' + PARAMETERS_SECRETS_EXTENSION_HTTP_PORT
SSM_PATH = '/systemsmanager/parameters/get/'
MAX_RETRIES = 4
INITIAL_DELAY = 1
MAX_DELAY = 4
# Loggerの設定
def setup_logger() -> logging.Logger:
log_level = os.environ.get('LOG_LEVEL', 'INFO').upper()
logging.basicConfig(
level=getattr(logging, log_level),
format='%(asctime)s [%(levelname)s] %(name)s %(message)s'
)
return logging.getLogger(__name__)
logger = setup_logger()
# urllib にパッチ適用するには 二重パッチ適用が必要
patch_all(double_patch=True)
# CloudWatch クライアントの初期化
try:
cloudwatch = boto3.client('cloudwatch')
except (ClientError, BotoCoreError) as e:
logger.error(f"Failed to initialize CloudWatch client: {e}")
sys.exit(1)
# AWS Parameter and Secrets Lambda extension で SSM Parameter StoreのSecure Stringを取得
def get_ssm_parameter(parameter_name: str) -> str:
encoded_name = urllib.parse.quote(parameter_name)
url = f"{SSM_ENDPOINT}{SSM_PATH}?name={encoded_name}&withDecryption=true"
headers = {'X-Aws-Parameters-Secrets-Token': os.environ['AWS_SESSION_TOKEN']}
logger.info(f"Requesting SSM parameter from: {url}")
# "not ready to serve traffic, please wait" とエラーになることがあるため、その場合はExponential Backoffしながらリトライ
for attempt in range(MAX_RETRIES):
try:
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req) as response:
response_data = response.read().decode('utf-8')
parameter = json.loads(response_data)
return parameter['Parameter']['Value']
except urllib.error.HTTPError as e:
if e.code == 400 and "not ready to serve traffic, please wait" in e.read().decode('utf-8'):
delay = min(INITIAL_DELAY * (2 ** attempt), MAX_DELAY)
logger.warning(f"Extension not ready. Retrying in {delay} seconds. Attempt {attempt + 1}/{MAX_RETRIES}")
time.sleep(delay)
else:
logger.error(f"HTTP Error {e.code}: {e.reason}")
logger.error(f"Error response body: {e.read().decode('utf-8')}")
raise
except (urllib.error.URLError, TimeoutError) as e:
logger.error(f"Error fetching SSM parameter: {e}")
raise
except (json.JSONDecodeError, KeyError) as e:
logger.error(f"Error parsing SSM parameter response: {e}")
raise
logger.error("Failed to retrieve SSM parameter after all retries.")
raise Exception("Max retries reached for SSM parameter retrieval")
# FSxNへの接続
def get_ontap_connection() -> HostConnection:
try:
password = get_ssm_parameter(os.environ['FSXN_USER_CREDENTIAL_SSM_PARAMETER_STORE_NAME'])
return HostConnection(
os.environ['FSXN_DNS_NAME'],
username=os.environ['FSXN_USER_NAME'],
password=password,
verify=False
)
except KeyError as e:
logger.error(f"Missing environment variable: {e}")
raise
except Exception as e:
logger.error(f"Error setting up ONTAP connection: {e}")
raise
# SnapMirror relationshipの取得
def get_snapmirror_relationships() -> List[SnapmirrorRelationship]:
# SnapMirror relationshipの取得
try:
return SnapmirrorRelationship.get_collection(fields='*')
except NetAppRestError as error:
logger.error(f"Error fetching SnapMirror relationships: {error}")
raise
# CloudWatchのメトリクスデータのPUT
def put_metric_data(metric_name: str, value: float, dimensions: List[Dict[str, str]]) -> None:
try:
cloudwatch.put_metric_data(
Namespace=NAMESPACE,
MetricData=[
{
'MetricName': metric_name,
'Value': value,
'Dimensions': dimensions
}
]
)
logger.info(f"Successfully put metric data: {metric_name}")
except ClientError as e:
error_code = e.response['Error']['Code']
error_message = e.response['Error']['Message']
logger.error(f"ClientError putting metric data: {error_code} - {error_message}")
except BotoCoreError as e:
logger.error(f"BotoCoreError putting metric data: {e}")
except Exception as e:
logger.error(f"Unexpected error putting metric data: {e}")
# 取得したSnapMirror relationshipの評価とレポーティング
def evaluate_and_report_snapmirror_health(relationships: List[SnapmirrorRelationship]) -> None:
if not relationships:
logger.info("No SnapMirror relationships found.")
return
# Destination の SVM 単位の SnapMirror relationship の Health を確認するための変数を宣言
svm_health = defaultdict(lambda: {'healthy': True, 'name': '', 'uuid': ''})
for relationship in relationships:
rel_dict = relationship.to_dict()
logger.debug(f"SnapMirror Relationship: {rel_dict}")
# SnapMirror relationship個別の状態のレポート
report_individual_relationship_health(rel_dict)
# Destination SVM単位SnapMirror relationshipの状態の評価
update_svm_health(rel_dict, svm_health)
# Destination SVM単位SnapMirror relationshipの状態のレポート
report_svm_level_health(svm_health)
# SnapMirror relationshipの個別のHealthをメトリクスとしてPUT
def report_individual_relationship_health(rel_dict: Dict[str, Any]) -> None:
# HealthがTrueの場合は1
# HealthがFalseの場合は0
health_value = 1 if rel_dict.get('healthy', False) else 0
dimensions = [
{'Name': 'SourcePath', 'Value': rel_dict.get('source', {}).get('path', 'Unknown')},
{'Name': 'DestinationPath', 'Value': rel_dict.get('destination', {}).get('path', 'Unknown')},
{'Name': 'RelationshipUUID', 'Value': rel_dict.get('uuid', 'Unknown')}
]
put_metric_data('SnapMirrorRelationshipHealth', health_value, dimensions)
# Destination SVM単位SnapMirror relationshipの状態の評価
def update_svm_health(rel_dict: Dict[str, Any], svm_health: Dict[str, Dict[str, Any]]) -> None:
destination_svm = rel_dict.get('destination', {}).get('svm', {})
destination_svm_uuid = destination_svm.get('uuid', 'Unknown')
# SnapMirror relationshipがHealthではない場合、該当SnapMirror relationshipの詳細をログに記録
if not rel_dict.get('healthy', False):
svm_health[destination_svm_uuid]['healthy'] = False
log_unhealthy_relationship(rel_dict)
# UnhealthyなSnapMirror relationshipが存在するとして整理
svm_health[destination_svm_uuid]['name'] = destination_svm.get('name', 'Unknown')
svm_health[destination_svm_uuid]['uuid'] = destination_svm_uuid
# Destination SVM 単位でメトリクスをPUT
def report_svm_level_health(svm_health: Dict[str, Dict[str, Any]]) -> None:
# Destination SVM 単位で、全てのSnapMirror relationshipのHealthがTrueの場合は1
# Destination SVM 単位で、いずれかのSnapMirror relationshipのHealthがFalseの場合は0
for svm_uuid, svm_info in svm_health.items():
health_value = 1 if svm_info['healthy'] else 0
dimensions = [
{'Name': 'DestinationStorageVirtualMachineName', 'Value': svm_info['name']},
{'Name': 'DestinationStorageVirtualMachineUUID', 'Value': svm_uuid}
]
put_metric_data('SnapMirrorRelationshipHealth', health_value, dimensions)
# Unhealthy な SnapMirror relationshipの情報をロギング
def log_unhealthy_relationship(rel_dict: Dict[str, Any]) -> None:
unhealthy_reason = rel_dict.get('unhealthy_reason', 'Unknown reason')
relationship_uuid = rel_dict.get('uuid', 'Unknown')
source_path = rel_dict.get('source', {}).get('path', 'Unknown')
destination_path = rel_dict.get('destination', {}).get('path', 'Unknown')
logger.info(
f"Unhealthy SnapMirror Relationship detected "
f"Unhealthy Reason: {unhealthy_reason}, "
f"Relationship UUID: {relationship_uuid}, "
f"Source Path: {source_path}, "
f"Destination Path: {destination_path}"
)
def main():
try:
config.CONNECTION = get_ontap_connection()
relationships = get_snapmirror_relationships()
evaluate_and_report_snapmirror_health(relationships)
except Exception as e:
logger.exception(f"An unexpected error occurred: {e}")
sys.exit(1)
def lambda_handler(event, context):
main()
AWS CDKの構成
リソースはAWS CDKでデプロイします。
使用したコードは以下リポジトリに保存しています。
デプロイするリソースは以下のとおりです。
- Lambda周り
- Lambda関数
- Lambda関数にアタッチするIAMロール
- Lambda関数にアタッチするSecurity Group (指定したSecurity Groupを使用することも可)
- EventBridge Scheduler周り (オプション)
- EventBridge Scheduler
- EventBridge Scheduler Group
- EventBridge SchedulerにアタッチするIAMロール
- VPCエンドポイント (オプション)
- CloudWatch
- SSM
ディレクトリツリーは以下のとおりです。
> tree
.
├── .gitignore
├── .npmignore
├── .python-version
├── README.md
├── bin
│ └── monitoring-snapmirror-relationship-health.ts
├── cdk.context.json
├── cdk.json
├── jest.config.js
├── lib
│ ├── construct
│ │ ├── lambda-construct.ts
│ │ ├── scheduler-construct.ts
│ │ └── vpc-endpoint-construct.ts
│ ├── monitoring-snapmirror-relationship-health-stack.ts
│ └── src
│ └── lambda
│ ├── index.py
│ └── requirements.txt
├── package-lock.json
├── package.json
├── parameter
│ ├── config
│ │ ├── index.ts
│ │ ├── lambda-config.ts
│ │ ├── scheduler-config.ts
│ │ └── vpc-endpoint-config.ts
│ ├── index.ts
│ └── types
│ └── index.ts
├── pyproject.toml
├── test
│ └── monitoring-snapmirror-relationship-health.test.ts
├── tsconfig.json
└── uv.lock
10 directories, 26 files
Stackに渡すパラメーターの型定義は以下のとおりです。
import * as cdk from "aws-cdk-lib";
export interface VpcEndpointProperty {
vpcId: string; // VPCエンドポイントを作成するVPCのID
vpcEndpointSubnetSelection: cdk.aws_ec2.SubnetSelection; // VPCエンドポイントを作成するサブネットの検索条件
shouldCreateSsmVpcEndpoint?: boolean; // SSMのVPCエンドポイントを作成するかどうか
shouldCreateCloudWatchVpcEndpoint?: boolean; // CloudWatchのVPCエンドポイントを作成するかどうか
}
export interface LambdaProperty {
vpcId: string; // Lambda関数が接続するVPCのID
functionSubnetSelection: cdk.aws_ec2.SubnetSelection; // Lambda関数が接続するサブネットの検索条件
functionSecurityGroupId?: string; // Lambda関数のENIにアタッチするSecurity Group
functionApplicationLogLevel?: cdk.aws_lambda.ApplicationLogLevel; // Lambda関数のアプリケーションログレベル
paramsAndSecretsLogLevel?: cdk.aws_lambda.ParamsAndSecretsLogLevel; // AWS Parameter and Secrets Lambda extensionのログレベル
fsxnDnsName: string; // FSxNファイルシステム または SVMのDNS名
fsxnUserName: string; // FSxNファイルシステム または SVMのユーザー名
fsxnUserCredentialSsmParameterStoreName: string; // FSxNファイルシステム または SVMのユーザーの認証情報を保存しているSSM Parameter Store
fsxnUserCredentialSsmParameterStoreKmsKeyId?: string; // FSxNファイルシステム または SVMのユーザーの認証情報を保存しているSSM Parameter StoreのKMSキーID (デフォルトキーの場合は指定不要)
}
export interface SchedulerProperty {
scheduleExpression: string; // Lambda関数の実行間隔
}
export interface MonitoringSnapMirrorRelationshipHealthProperty {
vpcEndpointProperty?: VpcEndpointProperty;
lambdaProperty: LambdaProperty;
schedulerProperty?: SchedulerProperty;
}
export interface MonitoringSnapMirrorRelationshipHealthStackProperty {
env?: cdk.Environment;
props: MonitoringSnapMirrorRelationshipHealthProperty;
}
Lambda関数で使用するライブラリはrequirements.txt
で定義して、バンドルします。
netapp-ontap==9.16.1.0
boto3==1.34.63
aws-xray-sdk==2.14.0
ライブラリの依存関係は以下のとおりです。
> uv tree
Resolved 18 packages in 1ms
monitoring-snapmirror-relationship-health v0.1.0
├── aws-xray-sdk v2.14.0
│ ├── botocore v1.35.63
│ │ ├── jmespath v1.0.1
│ │ ├── python-dateutil v2.9.0.post0
│ │ │ └── six v1.16.0
│ │ └── urllib3 v2.2.3
│ └── wrapt v1.16.0
├── boto3 v1.35.63
│ ├── botocore v1.35.63 (*)
│ ├── jmespath v1.0.1
│ └── s3transfer v0.10.3
│ └── botocore v1.35.63 (*)
└── netapp-ontap v9.16.1.0
├── certifi v2024.8.30
├── marshmallow v3.23.1
│ └── packaging v24.2
├── requests v2.32.3
│ ├── certifi v2024.8.30
│ ├── charset-normalizer v3.4.0
│ ├── idna v3.10
│ └── urllib3 v2.2.3
├── requests-toolbelt v1.0.0
│ └── requests v2.32.3 (*)
└── urllib3 v2.2.3
(*) Package tree already displayed
なお、デプロイされたLambda関数のパッケージサイズは47.2 MBです。少しでもパッケージサイズを小さくして、コールドスタートの時間を短くしたい場合はバンドルするライブラリを削りましょう。
以下ライブラリを削ると33.2MBほどになります。
aws-xray-sdk
- X-Rayでトレースしない場合は不要
boto3
- Lambdaのランタイムに含まれるため、バージョン指定をする要件がない場合はバンドル不要
Lambda関数デプロイ周りのコードは以下のとおりです。
// Lambda Function
const lambdaFunction = new cdk.aws_lambda.Function(this, "Default", {
runtime: cdk.aws_lambda.Runtime.PYTHON_3_13,
handler: "index.lambda_handler",
code: cdk.aws_lambda.Code.fromAsset(
path.join(__dirname, "../src/lambda/"),
{
bundling: {
image: cdk.aws_lambda.Runtime.PYTHON_3_13.bundlingImage,
command: [
"bash",
"-c",
"pip install -r requirements.txt -t /asset-output && cp -au . /asset-output",
],
},
}
),
role,
vpc,
vpcSubnets: vpc.selectSubnets(props.functionSubnetSelection),
securityGroups: [securityGroup],
paramsAndSecrets: cdk.aws_lambda.ParamsAndSecretsLayerVersion.fromVersion(
cdk.aws_lambda.ParamsAndSecretsVersions.V1_0_103,
{
cacheEnabled: true,
cacheSize: 10,
logLevel: props.paramsAndSecretsLogLevel,
parameterStoreTimeout: cdk.Duration.seconds(10),
parameterStoreTtl: cdk.Duration.minutes(5),
}
),
architecture: cdk.aws_lambda.Architecture.ARM_64,
timeout: cdk.Duration.seconds(30),
tracing: cdk.aws_lambda.Tracing.ACTIVE,
logRetention: cdk.aws_logs.RetentionDays.ONE_MONTH,
loggingFormat: cdk.aws_lambda.LoggingFormat.JSON,
applicationLogLevelV2: props.functionApplicationLogLevel,
environment: {
LOG_LEVEL: props.paramsAndSecretsLogLevel || "INFO",
FSXN_DNS_NAME: props.fsxnDnsName,
FSXN_USER_NAME: props.fsxnUserName,
FSXN_USER_CREDENTIAL_SSM_PARAMETER_STORE_NAME:
props.fsxnUserCredentialSsmParameterStoreName,
},
});
やってみた
検証環境
実際にやってみましょう。
検証環境は以下のとおりです。
以下記事の検証環境の続きから行います。
後ほどSVMを追加して、複数SVMのSnapMirrorの状態を確認できるかも試します。
FSxNファイルシステムにアクセスする際に使用するユーザーの作成
各種リソースをデプロイする前にFSxNファイルシステムにアクセスする際に使用するユーザーを作成します。
SVMユーザーを作成しても良いですが、その場合、FSxNファイルシステム内のSVMごとにユーザーを作成することになります。
ユーザー管理が手間なので、ファイルシステムユーザーでアクセスしたいところです。
ファイルシステムのロールはfsxadmin
とfsxadmin-readonly
の2つがあります。
FSxNの制約事項として、追加でファイルシステムロールを作成することはできません。
追加のファイルシステムユーザーを作成し、 fsxadminまたは fsxadmin-readonlyロールを割り当てることができます。新しいロールを作成したり、既存のロールを変更したりすることはできません。詳細については、「ファイルシステムとSVM管理用の新しいONTAPユーザーの作成」を参照してください。
SnapMirror relationshipの情報のみ取得できれば良いため、fsxadmin-readonly
でも権限的に過剰ではありますが、fsxadmin-readonly
ロールを割り当てたユーザーfsxadmin-readonly
を用意します。
::> security login show
Vserver: FsxId0e64a4f5386f74c87
Second
User/Group Authentication Acct Authentication
Name Application Method Role Name Locked Method
-------------- ----------- ------------- ---------------- ------ --------------
autosupport console password autosupport no none
fsxadmin http password fsxadmin no none
fsxadmin ontapi password fsxadmin no none
fsxadmin ssh password fsxadmin no none
fsxadmin ssh publickey fsxadmin - none
Vserver: svm
Second
User/Group Authentication Acct Authentication
Name Application Method Role Name Locked Method
-------------- ----------- ------------- ---------------- ------ --------------
vsadmin http password vsadmin no none
vsadmin ontapi password vsadmin no none
vsadmin ssh password vsadmin no none
8 entries were displayed.
::> security login create -user-or-group-name fsxadmin-readonly -application http -authentication-method password -role fsxadmin-readonly
Please enter a password for user 'fsxadmin-readonly':
Please enter it again:
::> security login show
Vserver: FsxId0e64a4f5386f74c87
Second
User/Group Authentication Acct Authentication
Name Application Method Role Name Locked Method
-------------- ----------- ------------- ---------------- ------ --------------
autosupport console password autosupport no none
fsxadmin http password fsxadmin no none
fsxadmin ontapi password fsxadmin no none
fsxadmin ssh password fsxadmin no none
fsxadmin ssh publickey fsxadmin - none
fsxadmin-readonly
http password fsxadmin-readonly
no none
Vserver: svm
Second
User/Group Authentication Acct Authentication
Name Application Method Role Name Locked Method
-------------- ----------- ------------- ---------------- ------ --------------
vsadmin http password vsadmin no none
vsadmin ontapi password vsadmin no none
vsadmin ssh password vsadmin no none
9 entries were displayed.
デプロイ
リソースをデプロイします。
デプロイ時のパラメーターは以下のとおりです。
import * as cdk from "aws-cdk-lib";
import { VpcEndpointProperty } from "../types";
export const vpcEndpointConfig: VpcEndpointProperty = {
vpcId: "vpc-043c0858ea33e8ec2",
vpcEndpointSubnetSelection: {
subnetType: cdk.aws_ec2.SubnetType.PRIVATE_ISOLATED,
availabilityZones: ["us-east-1a"],
subnetFilters: [
cdk.aws_ec2.SubnetFilter.byIds(["subnet-0ddc1cafa116ba0dd"]),
],
},
shouldCreateSsmVpcEndpoint: true, // SSMのVPCエンドポイントを作成
shouldCreateCloudWatchVpcEndpoint: true, // CloudWatchのVPCエンドポイントを作成
};
import * as cdk from "aws-cdk-lib";
import { LambdaProperty } from "../types";
export const lambdaConfig: LambdaProperty = {
vpcId: "vpc-043c0858ea33e8ec2",
functionSubnetSelection: {
subnetType: cdk.aws_ec2.SubnetType.PRIVATE_ISOLATED,
availabilityZones: ["us-east-1a"],
subnetFilters: [
cdk.aws_ec2.SubnetFilter.byIds(["subnet-0ddc1cafa116ba0dd"]),
],
},
functionSecurityGroupId: "sg-03730d9e2b49e7cbc", // FSxNファイルシステムにTCP/443で接続を許可しているSecurity Groupを指定
functionApplicationLogLevel: cdk.aws_lambda.ApplicationLogLevel.INFO,
paramsAndSecretsLogLevel: cdk.aws_lambda.ParamsAndSecretsLogLevel.INFO,
fsxnDnsName: "management.fs-0e64a4f5386f74c87.fsx.us-east-1.amazonaws.com", // FSxNファイルシステム内のSVM全てのSnapMirrorの情報を取得したいため、FSxNファイルシステムのDNS名を指定
fsxnUserName: "fsxadmin-readonly", // FSxNファイルシステム全体のRead Only権限を持ったユーザーwー指定
fsxnUserCredentialSsmParameterStoreName:
"/fsxn/non-97-fsxn/fsxadmin-readonly/password2", // fsxadmin-readonly の認証情報を保存したSSM Parameter Storeを指定
fsxnUserCredentialSsmParameterStoreKmsKeyId:
"6233b0b8-a26b-4f0d-8589-ac35b7152932", // SSM Parameter StoreのKMSキーのIDを指定
};
import { SchedulerProperty } from "../types";
export const schedulerConfig: SchedulerProperty = {
scheduleExpression: "cron(0/2 * * * ? *)", // 2分間隔でLambda関数を実行するように指定
};
デプロイ後のCloudWatchメトリクスの確認
現在のSnapMirror relationshipの状態は以下のとおりです。
::> snapmirror show
Progress
Source Destination Mirror Relationship Total Last
Path Type Path State Status Progress Healthy Updated
----------- ---- ------------ ------- -------------- --------- ------- --------
svm:vol_ntfs
XDP svm:vol_ntfs_dst
Broken-off
Idle - false -
svm:vol_ntfs_dst
XDP svm:vol_ntfs Snapmirrored
Idle - true -
2 entries were displayed.
この状態をCloudWatchメトリクスとして反映できているのか確認します。
まず、SnapMirror relationship単位のHealthを確認します。
ONTAP CLIで確認したとおり、転送元がsvm:vol_ntfs
で転送先がsvm:vol_ntfs_dst
は0
、転送元がsvm:vol_ntfs_dst
で転送先がsvm:vol_ntfs
は1
になっていることが分かりますね。
2分間隔でカスタムメトリクスがPUTされていることも分かります。
Destination SVM単位のHealthも確認します。
0
となっていますね。これは転送元がsvm:vol_ntfs
で転送先がsvm:vol_ntfs_dst
のHealthがfalseであるため、意図したとおりです。
ちなみに、原因を調査するためにFSxNファイルシステムにSSHで接続するのも面倒そうだったので、Unhealthyとなっている原因はLambda関数のログに出力させています。
{
"timestamp": "2024-11-18T02:12:35Z",
"level": "INFO",
"message": "Unhealthy SnapMirror Relationship detected Unhealthy Reason: [{'code': '13303943', 'message': 'SnapMirror relationship is unhealthy. Reason: Scheduled update failed to start. (Destination svm:vol_ntfs_dst must be a data-protection volume.).'}], Relationship UUID: 8a6d0455-9b21-11ef-accd-b31c82a68aa5, Source Path: svm:vol_ntfs, Destination Path: svm:vol_ntfs_dst",
"logger": "index",
"requestId": "53673aa2-7019-4a68-b2e2-97835223899e"
}
SVM間のSnapMirror relationshipの場合
SVMピアリング
SVM間のSnapMirror relationshipの場合も試したいところです。
svm2
というSVMを追加して、svm
とSnapMirror relationshipを組ませます。
SVM一覧は以下のとおりです。
::> vserver show
Admin Operational Root
Vserver Type Subtype State State Volume Aggregate
----------- ------- ---------- ---------- ----------- ---------- ----------
svm data default running running svm_root aggr1
svm2 data default running running svm2_root aggr1
2 entries were displayed.
::> cifs show
Server Status Domain/Workgroup Authentication
Vserver Name Admin Name Style
----------- --------------- --------- ---------------- --------------
svm SMB-SERVER up CORP domain
svm2 SVM2 up CORP domain
2 entries were displayed.
SVMピアリングを作成します。
::> vserver peer create -vserver svm -peer-vserver svm2 -applications snapmirror
Info: 'vserver peer create' command is successful.
::> vserver peer show
Peer Peer Peering Remote
Vserver Vserver State Peer Cluster Applications Vserver
----------- ----------- ------------ ----------------- -------------- ---------
svm svm2 peered FsxId0e64a4f5386f74c87
snapmirror svm2
svm2 svm peered FsxId0e64a4f5386f74c87
snapmirror svm
2 entries were displayed.
SnapMirror relationshipの作成
snapMirror protect
でSnapMirror relationshipを作成します。
::> snapmirror protect -destination-vserver svm2 -path-list svm:vol_ntfs_dst -auto-initialize false -policy MirrorAllSnapshots -schedule 5min -support-tiering true
[Job 1169] Job is queued: snapmirror protect for list of source endpoints beginning with "svm:vol_ntfs_dst".
::> snapmirror show
Progress
Source Destination Mirror Relationship Total Last
Path Type Path State Status Progress Healthy Updated
----------- ---- ------------ ------- -------------- --------- ------- --------
svm:vol_ntfs
XDP svm:vol_ntfs_dst
Broken-off
Idle - false -
svm:vol_ntfs_dst
XDP svm2:vol_ntfs_dst_dst
- - - - -
svm:vol_ntfs Snapmirrored
Idle - true -
3 entries were displayed.
作成後にLambda関数を手動で実行します。
実行後のCloudWatchメトリクスは以下のとおりです。
新しくPUTされたメトリクスは1
のようですね。
このまま放置すると、メトリクスが0
になりました。
これは作成したSnapMirror relationshipが初期化されていない状態で、設定したスケジュール実行を行おうとしたためです。
::> snapmirror show
Progress
Source Destination Mirror Relationship Total Last
Path Type Path State Status Progress Healthy Updated
----------- ---- ------------ ------- -------------- --------- ------- --------
svm:vol_ntfs
XDP svm:vol_ntfs_dst
Broken-off
Idle - false -
svm:vol_ntfs_dst
XDP svm2:vol_ntfs_dst_dst
Uninitialized
Idle - false -
svm:vol_ntfs Snapmirrored
Idle - true -
3 entries were displayed.
::> snapmirror show -fields unhealthy-reason
source-path destination-path unhealthy-reason
------------ ---------------- --------------------------------------------------------------------------------------------------
svm:vol_ntfs svm:vol_ntfs_dst Scheduled update failed to start. (Destination svm:vol_ntfs_dst must be a data-protection volume.)
svm:vol_ntfs_dst
svm2:vol_ntfs_dst_dst
Scheduled update failed to start. (Volume svm2:vol_ntfs_dst_dst is not initialized.)
svm:vol_ntfs_dst
svm:vol_ntfs -
3 entries were displayed.
SnapMirror relationshipの初期化
SnapMirror relationshipの初期化を行います。
::> snapmirror initialize -destination-path svm2:vol_ntfs_dst_dst -source-path svm:vol_ntfs_dst
Operation is queued: snapmirror initialize of destination "svm2:vol_ntfs_dst_dst".
::> snapmirror show
Progress
Source Destination Mirror Relationship Total Last
Path Type Path State Status Progress Healthy Updated
----------- ---- ------------ ------- -------------- --------- ------- --------
svm:vol_ntfs
XDP svm:vol_ntfs_dst
Broken-off
Idle - false -
svm:vol_ntfs_dst
XDP svm2:vol_ntfs_dst_dst
Snapmirrored
Transferring 0B true 11/18 02:49:58
svm:vol_ntfs Snapmirrored
Idle - true -
3 entries were displayed.
::> snapmirror show
Progress
Source Destination Mirror Relationship Total Last
Path Type Path State Status Progress Healthy Updated
----------- ---- ------------ ------- -------------- --------- ------- --------
svm:vol_ntfs
XDP svm:vol_ntfs_dst
Broken-off
Idle - false -
svm:vol_ntfs_dst
XDP svm2:vol_ntfs_dst_dst
Snapmirrored
Transferring 1.59GB false 11/18 02:50:14
svm:vol_ntfs Snapmirrored
Idle - true -
3 entries were displayed.
::> snapmirror show
Progress
Source Destination Mirror Relationship Total Last
Path Type Path State Status Progress Healthy Updated
----------- ---- ------------ ------- -------------- --------- ------- --------
svm:vol_ntfs
XDP svm:vol_ntfs_dst
Broken-off
Idle - false -
svm:vol_ntfs_dst
XDP svm2:vol_ntfs_dst_dst
Snapmirrored
Finalizing 41.38GB false 11/18 02:58:00
svm:vol_ntfs Snapmirrored
Idle - true -
3 entries were displayed.
::> snapmirror show
Progress
Source Destination Mirror Relationship Total Last
Path Type Path State Status Progress Healthy Updated
----------- ---- ------------ ------- -------------- --------- ------- --------
svm:vol_ntfs
XDP svm:vol_ntfs_dst
Broken-off
Idle - false -
svm:vol_ntfs_dst
XDP svm2:vol_ntfs_dst_dst
Snapmirrored
Idle - true -
svm:vol_ntfs Snapmirrored
Idle - true -
3 entries were displayed.
完了しました。
完了後、CloudWatchメトリクスを確認します。
すると、0
になっていたメトリクスが1
に変わりました。意図したとおり動作していますね。
SnapMirror relationshipのHealthがFSxNのサービスとして提供するCloudWatchメトリクスに追加されることを願う
SnapMirror relationshipのHealthをCloudWatchメトリクスにPUTしてみました。
今回は行いませんでしたが、あとはPUTされたメトリクスに対してCloudWatchアラームを設定するだけです。
SnapMirror relationship個別に設定しても良いですし、Destination SVM単位で設定して、通知設定数を減らすのもアリです。
なお、今回の構成の主な課金要素は以下のとおりです。
- Lambda関数の実行時間
- カスタムメトリクス数
- VPCエンドポイント数
- KMSキー
- CloudWatch Logs出力ログ量
VPCエンドポイントを作成しない場合、SnapMirror relationshipの数が10個ほどであれば、5 USDほどで実装可能です。
VPCエンドポイントを作成する場合でも1AZのみであれば、25 USDほどです。
DIIの場合は、AUのEC2インスタンスを以下が要求されます。
- vCPU : 2個
- RAM : 8 GiB
- ディスク : 50 GB
抜粋 : Acquisition Unit の要件
要件に当てはまるように、RHELのインスタンスをm6i.largeで動作させる場合、オンデマンドで116.34 USD、3年EC2 Instance Savings Plans前払いなしで66.82 USDかかります。
加えて、AUからDIIのサービスエンドポイントまでのアウトバウンド通信の経路としてNAT Gatewayも必要になります。NAT Gatewayの料金として毎月45 USDほどかかります。
DIIが運用要件的にオーバースペックなのであれば、今回紹介した方法で十分な場合もあるでしょう。
最後に、SnapMirror relationshipのHealthがFSxNのサービスとして提供するCloudWatchメトリクスに追加されることを願って締めます。
以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!