AWS CDKでリソース作成 第7回: OpenSearch編

こんにちは。 エキサイト株式会社で内定者アルバイトをしています。

今回も、実際のリソースの作成を行いたいと思います。第7回は、OpenSearchです。

前の記事

tech.excite.co.jp

OpenSearchとは?

OpenSearchは、高速検索が可能な全文検索・分析エンジンです。
大量のデータへの高速なアクセスと応答が提供され、高度なスケール化も容易に行えます。

また、検索機能に加え、データ蓄積や分析環境の構築も容易なため、ビッグデータを利用する場合などの大規模なシステムに向いています。

OpenSearchは(これまで作成したRDSやRedisのように)クラスタ化が可能で、そのクラスタドメインと呼ばれます。
ドメインは指定した設定、インスタンスタイプ、インスタンス数、ストレージリソースを持ちます。

実際に作成してみる

今回作成するのは以下のような設定を持つドメインOpenSearchクラスタ)です。

項目 設定
バージョン OpenSearch 1.0
EBS ボリュームタイプ gp2
EBS ボリュームサイズ 150GiB
暗号化:HTTPS 要求
暗号化:ノード間 有効
暗号化:保管時 有効
アベイラビリティゾーン数 2
データノードの数 2
データノードのインスタンスタイプ t3.medium.search
マスターノードの数 3
マスターノードのインスタンスタイプ m6g.large.search

前回までで作成したVPCおよびサブネット上に作成していきます。

なお、マスターノードはクラスタ管理タスクを行い、データの保持やデータのアップロードリクエストには応答しません。 マスターノードを存在させることによってクラスタの安定性が向上するようです。

検索およびクエリリクエストを処理するのはデータノードです。

補足

前回までのコード

前回はElastiCacheを作成しましたが、今回はそれらを参照しませんので、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();
    }
}

OpenSearchドメインを作成するコード

        // OpenSearch
        // OpenSearch用のセキュリティグループ ①
        SecurityGroup openSearchSecurityGroup = SecurityGroup.Builder.create(this, "OpenSearchSG")
                .securityGroupName("sample-open-search-sg")
                .description("security-group-for-sample-open-search")
                .vpc(vpc)
                .allowAllOutbound(true)
                .build();

        openSearchSecurityGroup.addIngressRule(Peer.ipv4(privateSubnetA.getIpv4CidrBlock()), Port.tcp(80), "private-subnet-a");
        openSearchSecurityGroup.addIngressRule(Peer.ipv4(privateSubnetA.getIpv4CidrBlock()), Port.tcp(443), "private-subnet-a");
        openSearchSecurityGroup.addIngressRule(Peer.ipv4(privateSubnetB.getIpv4CidrBlock()), Port.tcp(80), "private-subnet-c");
        openSearchSecurityGroup.addIngressRule(Peer.ipv4(privateSubnetB.getIpv4CidrBlock()), Port.tcp(443), "private-subnet-c");

        // ドメイン ②
        Domain domain = Domain.Builder.create(this, "SampleOpenSearch")
                .domainName("sample-open-search")
                .version(EngineVersion.OPENSEARCH_1_0)
                .vpc(vpc)
                .securityGroups(List.of(openSearchSecurityGroup))
                .enforceHttps(true)
                .nodeToNodeEncryption(true)
                .encryptionAtRest(EncryptionAtRestOptions.builder()
                        .enabled(true)
                        .build())
                .vpcSubnets(List.of(SubnetSelection.builder()
                        .subnets(List.of(privateSubnetA, privateSubnetB))
                        .build()))
                .capacity(CapacityConfig.builder()
                        .masterNodes(3)
                        .masterNodeInstanceType("m6g.large.search")
                        .dataNodes(2)
                        .dataNodeInstanceType("t3.medium.search")
                        .build())
                .ebs(EbsOptions.builder()
                        .enabled(true)
                        .volumeType(EbsDeviceVolumeType.GP2)
                        .volumeSize(150)
                        .build())
                .zoneAwareness(ZoneAwarenessConfig.builder()
                        .enabled(true)
                        .availabilityZoneCount(2)
                        .build())
                .build();

        // アクセスポリシーの追加 ③
        domain.addAccessPolicies(PolicyStatement.Builder.create()
                .effect(Effect.ALLOW)
                .principals(List.of(new AnyPrincipal()))
                .actions(List.of("es:*"))
                .resources(List.of(domain.getDomainArn() + "/*"))
                .build());

少し長くなりましたので以下で少し説明を加えます。

まず、①ではOpenSearchドメイン用のセキュリティグループを作成しています。
セキュリティグループについては以下で解説しているので、興味がある方はこちらをご覧ください。

tech.excite.co.jp

ちなみに、addIngressRuleの第3引数はdescriptionですので、特別な設定をしているわけではありません。ただの説明です。

次に、②では実際にドメインを作成しています。
設定項目はほとんどメソッド名通りですので、細かい説明は省きます。

zoneAwarenessについて、zone awareness(ゾーン認識)という機能を有効にすると、同じリージョン内の2つまたは3つのアベイラビリティゾーンにわたって(データ)ノードとレプリカを割り当てるようで、ダウンタイムやデータ損失を防ぐのに役立つそうです。 (ノード数はアベイラビリティゾーンの数の倍数でなければなりません。これはマネジメントコンソール上から作成する際にもエラーメッセージとして出ますよね。)
今回は、2つのアベイラビリティゾーンにそれぞれ一つずつのノードを設置しています。
ただし、耐障害性を高めるためには、少なくとも3つのノードを作成することが推奨されているようです。

最後に、③ではドメインのアクセスポリシーを追加しています。 ここでは、以下のようなアクセスポリシーとなります。

このアクセスポリシーは、任意のAWSアカウント、ユーザまたはロールがこのドメイン内の任意のノードに任意のアクション("ESHttpDelete", "es:ESHttpGet", "es:ESHttpHead", "es:ESHttpPost", "es:ESHttpPut", "es:ESHttpPatch")を実行できることを表しています(ほぼ何も設定していないのと同じです)。
特にこの部分は必要に応じてカスタマイズしてください。

アクセスポリシーについては以下のドキュメントを参考にしてください。

Identity and Access Management in Amazon OpenSearch Service - Amazon OpenSearch Service

デプロイ

ここまで、記述できたらいつものようにデプロイします。

cdk deploy

エラーなくデプロイが完了したらOKです(作成には少し時間がかかります)。

終わりに

今回は、AWS CDKを用いてOpenSearchドメインを作成しました。

前回のElastiCacheのようなややこしい設定はないので作成自体は比較的簡単にできました。

では、また次回。

次の記事

tech.excite.co.jp

参考文献