CFNでElastiCache入門 – Redis クラスター無効版

目次

CloudFormationでElastiCache(Redis クラスター無効)入門

ElastiCacheはAWSが提供するインメモリキャッシングサービスです。

今回は入門編ということで、ElastiCache for Redis(クラスター無効)を作成します。

構築する環境

Diagram of introduction to ElastiCache with CFN - Redis(Cluster Disabled)

主に3つのリソースを作成します。

1つ目はElastiCacheです。
2つのノードを作成し、それぞれプライマリおよびレプリカとして使用します。

2つ目はEC2インスタンスです。
ElastiCacheに接続するクライアントとして使用します。
OSは最新版のAmazon Linux 2とします。

3つ目はLambda関数です。
こちらもElastiCacheに接続するクライアントとして使用します。
ランタイム環境はPython3.8とします。

EC2インスタンス用に2種類のVPCエンドポイントを作成します。
1種類目はS3用です。インスタンスにredis-cliをインストールするために使用します。
2種類目はSSM用です。インスタンスにSSM Session Manager経由で接続するために使用します。

CloudFormationカスタムリソースを使用して、Lambdaレイヤー作成を自動化します。
カスタムリソースにLambda関数を関連づけて、CloudFormationスタック作成時に、レイヤーパッケージを作成し、S3バケットに設置するように設定します。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

https://github.com/awstut-an-r/awstut-fa/tree/main/063

テンプレートファイルのポイント解説

ElastiCache for Redis(クラスター無効化)

ElastiCacheを作成するためには、少なくとも以下の2つのリソースを作成する必要があります。

  • サブネットグループ
  • クラスターグループ or レプリケーショングループ

サブネットグループ

Resources:
  SubnetGroup:
    Type: AWS::ElastiCache::SubnetGroup
    Properties:
      CacheSubnetGroupName: !Sub "${Prefix}-subnetgroup"
      Description: !Sub "${Prefix}-SubnetGroup"
      SubnetIds:
        - !Ref CacheSubnet1
        - !Ref CacheSubnet2
Code language: YAML (yaml)

SubnetIdsプロパティで2つ以上のサブネットを指定します。
ElastiCacheをマルチAZとする場合、ここで指定するサブネットのAZを分ける必要がある点にご注意ください。

レプリケーショングループ or キャッシュクラスター

CloudFormationでElastiCache本体を作成する場合、以下のどちらかのリソースを作成する必要があります。

  • レプリケーショングループ:AWS::ElastiCache::ReplicationGroup
  • キャッシュクラスター:AWS::ElastiCache::CacheCluster

ElastiCache for Redisを作成する場合の使い分けですが、リードレプリカを含む2つ以上のノードを作成する場合は前者、ノードを1つだけ作成する場合は後者を選択することになります。
今回はプライマリノードに加えて、リードレプリカノードを1つ作成しますので、レプリケーショングループを作成します。

  ReplicationGroup:
    Type: AWS::ElastiCache::ReplicationGroup
    Properties:
      AutoMinorVersionUpgrade: true
      CacheNodeType: !Ref CacheNodeType
      CacheParameterGroupName: !Ref CacheParameterGroupName
      CacheSubnetGroupName: !Ref SubnetGroup
      Engine: !Ref CacheEngine
      EngineVersion: !Ref CacheEngineVersion
      MultiAZEnabled: true
      NodeGroupConfiguration:
        - PrimaryAvailabilityZone: !Sub "${AWS::Region}${AvailabilityZone1}"
          ReplicaAvailabilityZones:
            - !Sub "${AWS::Region}${AvailabilityZone2}"
          ReplicaCount: !Ref ReplicaCount
      Port: !Ref RedisPort
      ReplicationGroupDescription: !Sub "${Prefix}-ReplicationGroup"
      ReplicationGroupId: !Sub "${Prefix}-replicationgroup"
      SecurityGroupIds:
        - !Ref CacheSecurityGroup
Code language: YAML (yaml)

ポイントとなるパラメータを取り上げます。

CacheNodeTypeプロパティで作成するノードのタイプを設定します。
ミニマム構成(vCPU: 2, メモリ: 0.5GiB)の「cache.t4g.micro」を選択します。

CacheParameterGroupNameプロパティでグループに適用するパラメータグループを指定します。
パラメータグループはRedisやMemcachedのパラメータを指定するためのリソースです。
自作したパラメータグループを指定することもできますし、AWS側で作成したデフォルトのものを使用することもできます。
今回はデフォルトの「default.redis6.x」を指定します。

CacheSubnetGroupNameプロパティでグループを配置するサブネットを指定します。
先述のサブネットグループを指定します。

EngineおよびEngineVersionプロパティでElastiCacheのモードやバージョンを指定します。
Redisの最新バージョンを作成するように、前者に「redis」、後者に「6.2」を設定します。

MultiAZEnabledプロパティを有効化すると、システムの可用性を向上させることができます。

マルチ AZ が有効になっている場合、ダウンタイムは最小限に抑えられます。プライマリノードのロールは、いずれかのリードレプリカに自動的にフェイルオーバーされます。

マルチ AZ による ElastiCache for Redis のダウンタイムの最小化

NodeGroupConfigurationプロパティで、グループ内に作成するノードの詳細設定を行います。
PrimaryAvailabilityZoneプロパティでプライマリノードが起動するAZを指定できます。
一方ReplicaAvailabilityZonesプロパティでは、リードレプリカノードが起動するAZを指定できます。
ReplicaCountプロパティで起動させるリードレプリカノードの数を指定できます。今回は1つだけ起動させるように設定します。

PortプロパティでElastiCacheに接続するための通信ポートを指定できます。
通常のRedis用ポート(6379/tcp)を指定します。

SecurityGroupIdsプロパティで、ElastiCacheに適用するセキュリティグループを指定します。
以下のセキュリティグループを作成し、本プロパティに指定します。

Resources:
  CacheSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub "${Prefix}-CacheSecurityGroup"
      GroupDescription: Allow Redis from Instance and Function.
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: !Ref RedisPort
          ToPort: !Ref RedisPort
          SourceSecurityGroupId: !Ref InstanceSecurityGroup
        - IpProtocol: tcp
          FromPort: !Ref RedisPort
          ToPort: !Ref RedisPort
          SourceSecurityGroupId: !Ref FunctionSecurityGroup
Code language: YAML (yaml)

今回の構成では、EC2インスタンスとLambda関数からElastiCacheに接続するため、送信元に両リソース用セキュリティグループを、ポートに先述のRedis用ポート(6379/tcp)を指定します。

(参考)EC2インスタンス

Resources:
  Instance:
    Type: AWS::EC2::Instance
    Properties:
      IamInstanceProfile: !Ref InstanceProfile
      ImageId: !Ref ImageId
      InstanceType: !Ref InstanceType
      NetworkInterfaces:
        - DeviceIndex: 0
          SubnetId: !Ref InstanceSubnet
          GroupSet:
            - !Ref InstanceSecurityGroup
      UserData: !Base64 |
        #!/bin/bash -xe
        yum update -y
        amazon-linux-extras install redis6 -y
Code language: YAML (yaml)

ElastiCache for Redisに接続するためのEC2インスタンスです。
ElastiCache for Redisに接続するためには、特別な設定は不要です。
ただしredis-cliを使用して接続する予定ですので、ユーザーデータを使用して、初期化処理時に本ツールをインストールするように設定します。

インスタンスの初期化処理に関する詳細は、以下のページをご確認ください。

あわせて読みたい
Linuxインスタンスの初期化方法4選 【Linuxインスタンスを初期化する4つの方法】 EC2インスタンスの起動時に初期化処理を実行する方法を考えます。 EC2インスタンスを構築時に初期化する以下の4つの手法を...

ツールインストール時に、インスタンスの内部ではyumを実行します。
インスタンスはインターネットに接続不可のプライベートサブネットに配置されているため、通常、yumの実行に失敗します。
しかし今回のようなAmazon Linux 2の場合、S3バケット上に構築されたyumリポジトリにアクセスすることで、yumを実行することができます。
そのためS3用VPCエンドポイントを作成し、S3バケットにアクセスするための経路を用意します。
プライベートサブネットでyumを実行する方法につきましては、以下のページをご確認ください。

あわせて読みたい
プライベートサブネットのインスタンスでyum/dnfを実行する 【プライベートサブネット内のインスタンスでyum/dnfを実行する構成】 プライベートサブネット内のインスタンスで、yum/dnfを実行する方法を確認します。 今回は以下の2...

(参考)Lambda関数

Resources:
  Function:
    Type: AWS::Lambda::Function
    Properties:
      Architectures:
        - !Ref Architecture
      Environment:
        Variables:
          PRIMARY_ENDPOINT_ADDRESS: !Ref CachePrimaryEndpointAddress
          READER_ENDPOINT_ADDRESS: !Ref CacheReaderEndPointAddress
          REDIS_PORT: !Ref RedisPort
      Code:
        ZipFile: |
          import json
          import os
          import redis

          primary_endpoint_address = os.environ['PRIMARY_ENDPOINT_ADDRESS']
          reader_endpoint_address = os.environ['READER_ENDPOINT_ADDRESS']
          redis_port = os.environ['REDIS_PORT']

          def lambda_handler(event, context):
            primary_redis = redis.Redis(
              host=primary_endpoint_address,
              port=redis_port
              )
            primary_redis.set('hogehoge', 'fugafuga')

            reader_redis = redis.Redis(
              host=reader_endpoint_address,
              port=redis_port
              )

            return {
              'statusCode': 200,
              'body': json.dumps(
                {
                  'primary_redis': primary_redis.get('hogehoge').decode(),
                  'reader_redis': reader_redis.get('hogehoge').decode()
                },
                indent=2
                )
            }
      FunctionName: !Sub "${Prefix}-function"
      Handler: !Ref Handler
      Layers:
        - !Ref LambdaLayer
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole.Arn
      VpcConfig:
        SecurityGroupIds:
          - !Ref FunctionSecurityGroup
        SubnetIds:
          - !Ref LambdaSubnet
Code language: YAML (yaml)

ElastiCache for Redisに接続するためのLambda関数です。
ElastiCache for Redisに接続するためには、特別な設定は不要です。

Environmentプロパティで環境変数を定義できますが、変数としてElastiCache接続用のエンドポイントとポートを定義します。
エンドポイントの変数はプライマリノード接続用と、リードレプリカ接続用の2種類を定義します。

Redis接続用ライブラリですが、redis-pyを使用します。

https://github.com/redis/redis-py

コードの内容ですが、以下を実行します。

  • os.environにアクセスし、環境変数として定義したエンドポイントやポートを取得する。
  • redis-pyを使用してプライマリノードに接続後、試験用文字列を保存後、その文字列を取得する。
  • redis-pyを使用してリードレプリカノードに接続後、上記の文字列を取得する。

(参考)Lambdaレイヤーパッケージ作成

先述のredis-pyですが、サードパーティ製のライブラリですので、デフォルトのランタイム環境には用意されていません。
そこでこのモジュールが含まれているLambdaレイヤーを作成し、これをLambda関数に関連づけて対応します。
Lambdaレイヤーに関する詳細は、以下のページをご確認ください。

あわせて読みたい
CFNでLambdaレイヤー作成 【CloudFormationでLambdaレイヤー作成】 本ページでは、CloudFormationでLambdaレイヤーを作成する方法を確認します。 Lambda レイヤーは、Lambda 関数で使用できるラ...

今回はCloudFormationカスタムリソースを使って、redis-pyが含まれるLambdaレイヤーの作成を自動化します。
具体的には、Lambdaレイヤーパッケージを作成し、所定にS3バケットに設置する作業を自動化します。

Resources:
  RequirementsParameter:
    Type: AWS::SSM::Parameter
    Properties:
      Name: !Ref Prefix
      Type: String
      Value: |
        redis

  CustomResource:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !GetAtt CustomResourceLambdaLayerFunction.Arn
Code language: YAML (yaml)

SSMパラメータストアでLambdaレイヤーに含めるライブラリのリストを準備します。
CloudFormationカスタムリソースを作成し、CloudFormationスタック作成時に、パッケージ作成用関数の実行を定義します。
関数の内容ですが、SSMパラメータストアを読み込み、pipでライブラリのダウンロードを実行後、ZIPでまとめてS3バケットに設置するというものです。
詳細につきましては、以下のページをご確認ください。

あわせて読みたい
CFNカスタムリソースでLambdaレイヤーパッケージを準備する – Python版 【CloudFormationカスタムリソースを使って、Python用のLambdaレイヤーパッケージを自動的に作成・配置する】 以下のページでLambdaレイヤーの作成方法について取り上げ...

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【CloudFormationのネストされたスタックで環境を構築する方法】 CloudFormationにおけるネストされたスタックを検証します。 CloudFormationでは、スタックをネストす...

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • ElastiCache:fa-063-replicationgroup
  • ElastiCacheのプライマリエンドポイント:fa-063-replicationgroup.ryxnym.ng.0001.apne1.cache.amazonaws.com
  • ElastiCacheのリーダーエンドポイント:fa-063-replicationgroup-ro.ryxnym.ng.0001.apne1.cache.amazonaws.com
  • EC2インスタンス:i-040ea797576760e6e
  • Lambda関数のFunction URL:https://74paff2horq7g3kjedafhr5pba0satxd.lambda-url.ap-northeast-1.on.aws/

AWS Management Consoleから各リソースを確認します。
まずElastiCache本体を確認します。

Detail of ElastiCache 1.
Detail of ElastiCache 2.

CloudFormationテンプレートで設定した通りに、ElastiCacheが作成されています。
このページでElastiCache接続用のエンドポイントが確認できます。
ノードが2台作成され、プライマリとリードレプリカが1台ずつです。

次にサブネットグループを確認します。

Detail of ElastiCache 3.

2つのサブネットが登録されています。
異なるAZを指定しており、「10.0.3.0/24」の方にプライマリノードが、「10.0.4.0/24」の方にリードレプリカノードが起動することになります。

動作確認

準備が整いましたので、実際の動作を確認します。

EC2インスタンスからElastiCache for Redisに接続する

EC2インスタンスにアクセス後、ElastiCacheに接続します。

インスタンスへのアクセスはSSM Session Managerを使用します。

% aws ssm start-session --target i-040ea797576760e6e

Starting session with SessionId: root-02f6df0f40f0a01e9

sh-4.2$
Code language: Bash (bash)

SSM Session Managerの詳細につきましては、以下のページをご確認ください。

あわせて読みたい
LinuxインスタンスにSSM Session Manager経由でアクセスする 【LinuxインスタンスにSSM Session Manager経由でアクセスする】 EC2インスタンスにSSM Session Manager経由でアクセスする構成を確認します。 Session Manager は完全...

ユーザーデータによるEC2インスタンスの初期化処理の実行状況を確認します。

[ssm-user@ip-10-0-1-17 bin]$ yum list installed | grep redis
redis.aarch64                         6.2.6-1.amzn2                  @amzn2extra-redis6
Code language: Bash (bash)

確かにredis-cliがインストールされています。

redis-cliを使用して、ElastiCacheに接続します。
まずプライマリエンドポイントを指定し、プライマリノードに接続します。

sh-4.2$ redis-cli -h fa-063-replicationgroup.ryxnym.ng.0001.apne1.cache.amazonaws.com -p 6379
fa-063-replicationgroup.ryxnym.ng.0001.apne1.cache.amazonaws.com:6379>
Code language: Bash (bash)

正常に接続することができました。

試験用の値を保存します。

fa-063-replicationgroup.ryxnym.ng.0001.apne1.cache.amazonaws.com:6379> set hoge foo
OK
Code language: Bash (bash)

正常に保存できました。

保存した値を取得します。

fa-063-replicationgroup.ryxnym.ng.0001.apne1.cache.amazonaws.com:6379> get hoge
"foo"
Code language: Bash (bash)

正常に取得することができました。

続いてリーダーエンドポイントを指定し、リードレプリカに接続します。

sh-4.2$ redis-cli -h fa-063-replicationgroup-ro.ryxnym.ng.0001.apne1.cache.amazonaws.com -p 6379
fa-063-replicationgroup-ro.ryxnym.ng.0001.apne1.cache.amazonaws.com:6379>
Code language: Bash (bash)

正常に接続することができました。

試験用の値を保存します。

fa-063-replicationgroup-ro.ryxnym.ng.0001.apne1.cache.amazonaws.com:6379> set aaa bbb
(error) READONLY You can't write against a read only replica.
Code language: Bash (bash)

失敗しました。
これはリードレプリカは読み込み専用であり、書き込みの権限がないためです。

保存されている値のキーのリストを確認します。

fa-063-replicationgroup-ro.ryxnym.ng.0001.apne1.cache.amazonaws.com:6379> keys *
1) "hoge"
Code language: Bash (bash)

先ほどプライマリノードで保存した値のキーが確認できます。

このキーの値を取得します。

fa-063-replicationgroup-ro.ryxnym.ng.0001.apne1.cache.amazonaws.com:6379> get hoge
"foo"
Code language: Bash (bash)

正常に値を取得できました。
このようにプライマリノードに保存した値を、リードレプリカで取得することができます。

参考として、各エンドポイントのIPアドレスを確認します。

sh-4.2$ nslookup fa-063-replicationgroup.ryxnym.ng.0001.apne1.cache.amazonaws.com
Server:		10.0.0.2
Address:	10.0.0.2#53

Non-authoritative answer:
fa-063-replicationgroup.ryxnym.ng.0001.apne1.cache.amazonaws.com	canonical name = fa-063-replicationgroup-001.ryxnym.0001.apne1.cache.amazonaws.com.
Name:	fa-063-replicationgroup-001.ryxnym.0001.apne1.cache.amazonaws.com
Address: 10.0.3.34
Code language: Bash (bash)
sh-4.2$ nslookup fa-063-replicationgroup-ro.ryxnym.ng.0001.apne1.cache.amazonaws.com
Server:		10.0.0.2
Address:	10.0.0.2#53

Non-authoritative answer:
fa-063-replicationgroup-ro.ryxnym.ng.0001.apne1.cache.amazonaws.com	canonical name = fa-063-replicationgroup-002.ryxnym.0001.apne1.cache.amazonaws.com.
Name:	fa-063-replicationgroup-002.ryxnym.0001.apne1.cache.amazonaws.com
Address: 10.0.4.55
Code language: Bash (bash)

それぞれプライマリノードとリードレプリカノードであることがわかります。
また異なるサブネットのアドレスが振られていることがわかります。

Lambda関数からElastiCache for Redisに接続する

Lambda関数からElastiCacheに接続します。

この関数のFunction URLにアクセスします。
Function URLに関する詳細は、以下のページをご確認ください。

あわせて読みたい
CFNでLambda Function URL – 認証方式: NONE 【CloudFormationでLambda Function URLを作成する(NONEバージョン)】 2022年4月22日にLambda Function URLがリリースされました。 この新機能は、AWS Lambda サービス...
Result of Lambda Function accessed to ElastiCache.

関数内で保存した試験用の値を、プライマリノード・リードレプリカで正常に取得することができました。
このようにLambda関数からもElastiCache for Redisに接続することができます。

まとめ

ElastiCache for Redis(クラスター無効)を作成する方法を確認しました。
作成したElastiCacheに、EC2インスタンスおよびLambda関数から接続できることを確認しました。

目次