こんにちは。 エキサイト株式会社で内定者アルバイトをしています。
今回も、実際のリソースの作成を行いたいと思います。第6回は、ElastiCacheです。
前の記事 tech.excite.co.jp
ElastiCacheとは?
名前にもある通り、インメモリキャッシングのためのリソースです。
アプリケーションとデータベースパフォーマンスを高速化するキャッシングに使ったり、セッションストア、ゲーミングリーダーボード、ストリーミング、および分析などの耐久性を必要としないユースケースのデータストアとして使用できます。
RedisとMemcachedと互換性があります。
AWSにおけるRedis
今回はRedisのキャッシュを作成するので、AWSにおけるRedisについてもう少し詳しく説明します。
ノード(Node)
ElastiCacheにおけるRedisの最小単位で、EC2インスタンスを使用して動作します。
ノードは後ほど紹介するシャードに属しています。
各ノードはクラスタを作成した時に設定したバージョンで動作しており、クラスタ内のノードは全て同じノードタイプを持ちます。
シャード(Shard)
シャードは1~6個のノードをまとめたもので、そのうち1個が書き込みノードであるprimaryで、残りの最大5個のノードは読み込み専用のreplicaとなります。
シャードはクラスタに属しており、一つのクラスタは最大500個のシャードを持つことができます。(どれだけの数のシャードを作成し、そのシャードにどれだけのノードを割り当てるかは任意に決定できます。)
クラスタ
クラスタはシャードをまとめており、複数のシャードを作成することでシャーディング(データをシャード間で分割する。読み書きの負荷分散やコストパフォーマンス向上の効果があるよう。)が可能です。(ただし、クラスタモードというものを有効にする必要があります。クラスタモードが無効の場合はシャードは1つです。)
クラスタモードについては以下のページ内の図が参考になるかと思います。
レプリケーション: Redis (クラスターモードが無効) 対 Redis (クラスターモードが有効) - Amazon ElastiCache for Redis
実際に作成してみる
では、実際にElastiCacheのリソースを作成します。
今回は、前回までで作成しているVPC上にリソースを作成します。
作成するリソースの設定は以下の通りです。
項目 | 設定 |
---|---|
モード | Redis |
エンジンバージョン | 5.0.6 |
キャッシュノードタイプ | cache.t2.micro |
メンテナンスウィンドウ | 土曜日 16:30 - 土曜日 17:30 UTC |
また、ノードグループ(シャードのこと)に対する設定は以下の通りです。
項目 | 設定 |
---|---|
primaryのアベイラビリティゾーン | ap-northeast-1a |
replicaの数 | 2 |
replica1のアベイラビリティゾーン | ap-northeast-1c |
replica2のアベイラビリティゾーン | ap-northeast-1c |
前回までのコード
前回はRoute 53を作成しましたが、今回は必要ないためVPCとサブネットを作成する部分のみ再掲します。
package com.myorg; import software.amazon.awscdk.CfnTag; import software.amazon.awscdk.services.ec2.CfnInternetGateway; import software.amazon.awscdk.services.ec2.CfnVPCGatewayAttachment; import software.amazon.awscdk.services.ec2.PrivateSubnet; import software.amazon.awscdk.services.ec2.PublicSubnet; import software.amazon.awscdk.services.ec2.Vpc; import software.constructs.Construct; import software.amazon.awscdk.Stack; import software.amazon.awscdk.StackProps; import java.util.List; public class MyProjectStack extends Stack { public MyProjectStack(final Construct scope, final String id) { this(scope, id, null); } public MyProjectStack(final Construct scope, final String id, final StackProps props) { super(scope, id, props); Vpc vpc = Vpc.Builder.create(this, "SampleVPC") .vpcName("sample-vpc") .cidr("cidr") .subnetConfiguration(List.of()) .build(); // Availability zone: ap-northeast-1a PrivateSubnet privateSubnetA = PrivateSubnet.Builder.create(this, "PrivateSubnetA") .availabilityZone("ap-northeast-1a") .vpcId(vpc.getVpcId()) .cidrBlock("cidr") .build(); PublicSubnet publicSubnetA = PublicSubnet.Builder.create(this, "PublicSubnetA") .availabilityZone("ap-northeast-1a") .vpcId(vpc.getVpcId()) .cidrBlock("cidr") .build(); // Availability zone: ap-northeast-1c PrivateSubnet privateSubnetB = PrivateSubnet.Builder.create(this, "PrivateSubnetB") .availabilityZone("ap-northeast-1c") .vpcId(vpc.getVpcId()) .cidrBlock("cidr") .build(); PublicSubnet publicSubnetB = PublicSubnet.Builder.create(this, "PublicSubnetB") .availabilityZone("ap-northeast-1c") .vpcId(vpc.getVpcId()) .cidrBlock("cidr") .build(); // NAT gatewayの追加 publicSubnetA.addNatGateway(); publicSubnetB.addNatGateway(); // Internet Gateway CfnInternetGateway internetGateway = CfnInternetGateway.Builder.create(this, "InternetGateway") .tags(List.of(CfnTag.builder() .key("Name") .value("sample-internet-gateway") .build())) .build(); // Internet gatewayをVPCにattachする CfnVPCGatewayAttachment.Builder.create(this, "VpcGateAwayAttachment") .vpcId(vpc.getVpcId()) .internetGatewayId(internetGateway.getAttrInternetGatewayId()) .build(); } }
ElastiCacheを作成するコード
// ElastiCache // ElastiCache用のセキュリティグループ SecurityGroup sampleCacheRedisSecurityGroup = SecurityGroup.Builder.create(this, "SampleCacheRedisSecurityGroup") .securityGroupName("sample-cache-redis-security-group") .vpc(vpc) .allowAllOutbound(true) .description("security-group-for-sample-cache-redis") .build(); // ElasticCache用のサブネットグループ CfnSubnetGroup cacheSubnetGroup = CfnSubnetGroup.Builder.create(this, "CacheSubnetGroup") .cacheSubnetGroupName("sample-cache-subnet-group") .description("subnet-group-for-cache") .subnetIds(List.of(privateSubnetA.getSubnetId(), privateSubnetB.getSubnetId())) .build(); sampleCacheRedisSecurityGroup.addIngressRule(Peer.ipv4("0.0.0.0/0"), Port.tcp(6379)); CfnParameterGroup parameterGroup = CfnParameterGroup.Builder.create(this, "RedisCacheParameterGroup") .cacheParameterGroupFamily("redis5.0") .description("parameter-group-for-redis-cache") .build(); CfnReplicationGroup redisReplicationGroup = CfnReplicationGroup.Builder.create(this, "RedisReplicationGroup") .replicationGroupDescription("") .engine("redis") .engineVersion("5.0.6") .autoMinorVersionUpgrade(true) .cacheNodeType("cache.t2.micro") .cacheParameterGroupName(parameterGroup.getRef()) .replicationGroupId("sample-redis-cache") .preferredMaintenanceWindow("sat:16:30-sat:17:30") .securityGroupIds(List.of(sampleCacheRedisSecurityGroup.getSecurityGroupId())) .cacheSubnetGroupName(cacheSubnetGroup.getCacheSubnetGroupName()) .nodeGroupConfiguration(List.of(CfnReplicationGroup.NodeGroupConfigurationProperty.builder() // 4文字より多いとダメらしい .nodeGroupId("1") .primaryAvailabilityZone("ap-northeast-1a") .replicaAvailabilityZones(List.of("ap-northeast-1c", "ap-northeast-1c")) .replicaCount(2) .build())) .atRestEncryptionEnabled(false) .build(); redisReplicationGroup.addDependsOn(cacheSubnetGroup); redisReplicationGroup.addDependsOn(parameterGroup);
長くなりましたので、以下でこのコードについて少し説明します。
前半部分はElastiCache用のセキュリティグループとサブネットグループを作成しています。
後半は、ElastiCacheに直接関係する部分になります。CfnParameterGroup
はElastiCacheのためのパラメータグループです。今回はRedis5.0系のクラスタを作成しますのでcacheParameterGroupFamily
でredis5.0
を指定しています。
ちなみにElastiCacheは現段階ではL2コンストラクトに対応していないリソースの一つで、CDKで作成するためにはCloudFormationから直接生成されたより低レベルなコンストラクトであるL1コンストラクトを使用する必要があります。
最後に、Redisクラスタを作成しています。
この部分は非常にややこしいです。
実は、CfnCacheCluster
というコンストラクトがあります。
名前の感じではこれでもRedisクラスタを作成できそうですが、実はできません。
正確には、できなくはないが制約が大きすぎるためお薦めしません。その制約はengine
がredis
の場合はノードの数numNodeCache
を1にしなければならない。というものです。
実際、numNodeCache
を1以外にして無理矢理Redisのクラスタを作成しようとすると、
NumCacheNodes should be 1 if engine is redis
(エンジンがRedisなんだったらNumCacheNodesは1にしろよ)と怒られます。
これでは、あまりにも使いにくいですね....
そこで、代わりに使用するのは上のコードの通りCfnReplicationGroup
です。
これを使えば、Redisでも自由にノード数を設定することができます。
ただし、このコンストラクトにも落とし穴があります。
それは以下の5つのプロパティの相互関係です。
automaticFailoverEnabled numNodeGroups numCacheClusters replicasPerNodeGroup nodeGroupConfiguration: replicaCount
それぞれについて、説明します。
- automaticFailoverEnabled(boolean)
- primaryが機能しなくなった時にreplicaのうちの一つが自動的にprimaryとなること(フェイルオーバー)を許可するかどうか
- numNodeGroups(Number)
- ノードグループ(すなわちシャード)の数
- クラスタモード無効のRedisでは設定しないか、1にする
- numCacheClusters(Number)
- replicasPerNodeGroup(Number)
- 各ノードグループ(シャード)内のreplicaの数
nodeGroupConfiguration: replicaCount
と同時に指定することができない
- nodeGroupConfiguration: replicaCount
- このノードグループ内のreplicaの数(クラスタモードが有効の場合、
nodeGroupConfiguration
はノードグループ数だけ指定する)
- このノードグループ内のreplicaの数(クラスタモードが有効の場合、
まとめると、
クラスタモード無効かつフェイルオーバー無効 →
numCacheClusters
で1〜6の値を設定することでノードの数を指定するか(デフォルト1で省略可)、replicaPerNodeGroup
またはnodeGroupConfiguration: replicaCount
でシャード内のノードの数(クラスタ内のノードの数と一致)を指定する。クラスタモード無効かつフェイルオーバー有効 →
numCacheClusters
で2〜6の値を設定することでクラスタの数を指定し(省略不可)、replicaPerNodeGroup()
またはnodeGroupConfiguration: replicaCount
でシャード内のノードの数(クラスタ内のノードの数と一致)を指定する。クラスタモード有効 →
numNodeGroups
でシャードの数を指定し、replicasPerNodeGroup
で全部のシャードについてまとめてノード数を指定するか、nodeGroupConfiguration: replicaCount
で各シャードのノードの数を指定する。
よくわからなければ、nodeGroupConfiguration: replicaCount
を使用すればnodeGroupConfiguration
の他のプロパティで詳細な設定もできますし、クラスタモードの有効無効に関わらず使えるので、大抵の場合このプロパティで統一してしまっても問題ないと思います。
このブログのコードでも、nodeGroupConfiguration: replicaCount
を使用しています。
最後に、CfnSubnetGroup
、CfnParameterGroup
、CfnReplicationGroup
はL1コンストラクトでリソース作成時の順番がコードに書いた順と必ずしも一致しませんので、最後の2行でリソース作成の際の依存関係を指定しています。
詳細は、以下の記事で解説していますので、興味がある方はご覧ください。
終わりに
今回は、AWS CDKを用いてRedisキャッシュを作成しました。
ElastiCache自体がまだL2コンストラクトに対応しておらず、ノード数の設定回りが非常にややこしくなっており、かなりわかりづらいです。
今後、この辺りが改善されるといいですね...。
では、また次回。
次の記事