AWS CDKでAmazon Elasticsearch Serviceのドメイン(クラスタ)を作ってみた
はじめに
AWS CDKで、Amazon Elasticsearch Serviceのドメイン(クラスタ)を作ってみました。 シンプルにシングルノードでElasticsearchクラスタを作っていますが、設定値を少し変えれば、複数台の専用マスターノードとデータノードからなるクラスタを構築できます。
今回のソースコードはこちらで公開しています。 https://github.com/shoito/aws-cdk-elasticsearch
バージョン情報
$ sw_vers ProductName: Mac OS X ProductVersion: 10.14.6 BuildVersion: 18G103 $ cdk --version 1.14.0 (build 261a1bf) $ node --version v10.15.3
プロジェクトの設定
プロジェクトのファイルは以下のようになります。
$ tree -L 1 . ├── cdk.json ├── index.ts ├── package-lock.json ├── package.json └── tsconfig.json
package.jsonのハイライト部分ですが、synth, deploy, destroyを簡単に実行できるようにしています。 -c sourceIp=`curl -s https://checkip.amazonaws.com` の部分は、手元のPCからElasticsearchやKibanaのエンドポイントにアクセスするために、CDKのContextを利用して、ローカルPCのIPアドレスをCDKスタックに渡すためのものです。
{ "name": "aws-cdk-elasticsearch", "version": "0.1.0", "license": "MIT", "scripts": { "build": "tsc", "watch": "tsc -w", "cdk": "cdk", "synth": "cdk synth --no-staging -c stage=dev -c sourceIp=`curl -s https://checkip.amazonaws.com` > template.yaml", "deploy": "cdk deploy -c stage=dev -c sourceIp=`curl -s https://checkip.amazonaws.com`", "destroy": "cdk destroy -c stage=dev" }, ...省略 }
次にcdk.jsonですが、コンテキスト変数を用いて、Elasticsearch Serviceのノード数などのパラメータを定義しています。 後ほど紹介するindex.tsのコード内から、こちらで定義されている値を利用します。 devやprodなど、ステージ毎に、Elasticsearch Serviceをカスタマイズしています。
{ "app": "npx ts-node index.ts", "context": { "es": { "version": "7.1" }, "dev": { "es": { "domainName": "test-dev-domain", "instanceType": "t2.small.elasticsearch", "instanceCount": 1, "volumeSize": 10 } }, "prod": { "es": { "domainName": "test-prod-domain", ...省略 } } }
クラスタの作成
今回は検証のため、Elasticsearchクラスタはシングルノードで構築します。 なお、マルチノード構成に必要な設定はコードコメントで残してますので、何か参考になればと。
Elasticsearch Serviceは CfnDomainクラス
を用いて作成します。
Amazon Elasticsearch Service Construct Library https://docs.aws.amazon.com/cdk/api/latest/typescript/api/aws-elasticsearch.html
以下では、ESDomainStackクラスと上記cdk.jsonからのコンテキスト変数esの型として、ESContextインターフェースを定義しています。
import cdk = require('@aws-cdk/core'); import { CfnDomain } from '@aws-cdk/aws-elasticsearch'; // ステージ毎に、ノード数やデータの暗号化有無などを可変にできるように、cdk.jsonのContextを用いる。 // cdk.jsonのcontextで定義しているes contextの型定義。 interface ESContext { // Elasticsearchのバージョン readonly version: string; // Elasticsearch Serviceのドメイン名(クラスタ名) readonly domainName: string; // 専用マスターノードのインスタンスタイプ readonly masterInstanceType: string; // データノードのインスタンスタイプ readonly instanceType: string; // データノードのノード数 readonly instanceCount: number; // ボリュームサイズ readonly volumeSize: number; // アベイラビリティゾーン数 readonly availabilityZoneCount: 1 | 2 | 3; // AZ分散有無 readonly zoneAwareness: boolean; // 専用マスタノードの有無 readonly dedicatedMaster: boolean; // データ暗号化の有無 readonly encryption: boolean; } class ESDomainStack extends cdk.Stack { // LambdaなどからElasticsearch Serviceエンドポイントの参照用 public endpoint: string; constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // cdkコマンドで、-c stage=devのように、devやprodを指定する const stage: string = this.node.tryGetContext('stage'); // Elasticsearchのバージョン const esVersion: string = this.node.tryGetContext('es').version; // cdk.jsonからesコンテキストのオブジェクトを取得する const esContext: ESContext = this.node.tryGetContext(stage).es; // アクセスポリシー設定のため、cdk deployコマンド実行時にパラメーターで自身のIPを渡す -c sourceIp=`curl -s https://checkip.amazonaws.com` const sourceIp: string = this.node.tryGetContext('sourceIp'); const domain = new CfnDomain(this, esContext.domainName || 'domain', { // アクセスポリシー設定 accessPolicies: { Version: '2012-10-17', Statement: [ { Effect: 'Allow', Principal: { AWS: [ '*' ] }, Action: [ 'es:*' ], Resource: `arn:aws:es:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:domain/${esContext.domainName}/*`, Condition: { IpAddress: { // 自身のIPからElasticsearchやKibanaのエンドポイントにアクセスできるようにする 'aws:SourceIp': `${sourceIp || '127.0.0.1'}` } } }, { Effect: 'Allow', Principal: { AWS: [ // 自身のAWSアカウント環境からのElasticsearchへの操作を許可する cdk.Stack.of(this).account ] }, Action: [ 'es:*' ], Resource: `arn:aws:es:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:domain/${esContext.domainName}/*` } ] }, domainName: esContext.domainName, ebsOptions: { ebsEnabled: true, volumeSize: esContext.volumeSize, volumeType: 'gp2', }, elasticsearchClusterConfig: { instanceCount: esContext.instanceCount, // T2 インスタンスタイプは、保管時のデータの暗号化をサポートしていない // https://docs.aws.amazon.com/ja_jp/elasticsearch-service/latest/developerguide/aes-supported-instance-types.html instanceType: esContext.instanceType, // 専用マスターノードを使う構成にしたい場合は以下のオプションを設定する // dedicatedMasterEnabled: true, // dedicatedMasterCount: 3, // dedicatedMasterType: esContext.masterInstanceType, // zoneAwarenessEnabled: true, // zoneAwarenessConfig: { // availabilityZoneCount: esContext.availabilityZoneCount // } }, elasticsearchVersion: esVersion, encryptionAtRestOptions: { enabled: esContext.encryption // kmsKeyId: '' }, nodeToNodeEncryptionOptions: { enabled: false }, snapshotOptions: { automatedSnapshotStartHour: 0 }, // tags: [], // vpcOptions: { // subnetIds: [], // securityGroupIds: [] // }, }); this.endpoint = domain.attrDomainEndpoint; } } const app = new cdk.App(); new ESDomainStack(app, "ESDomainStack");
デプロイ
CDKスタックをビルド・デプロイします。 なお、私の環境ではデプロイに11分程度かかりました。
$ npm i $ npm run build > [email protected] build /Users/shoito/workspaces/aws-cdk-elasticsearch > tsc # cdk bootstrapを過去に実行していれば不要 $ cdk bootstrap -c stage=dev ⏳ Bootstrapping environment aws://123456789/ap-northeast-1... CDKToolkit: creating CloudFormation changeset... 0/1 | 17:16:36 | UPDATE_COMPLETE_CLEA | AWS::CloudFormation::Stack | CDKToolkit 1/1 | 17:16:36 | UPDATE_COMPLETE | AWS::CloudFormation::Stack | CDKToolkit ✅ Environment aws://123456789/ap-northeast-1 bootstrapped. $ npm run deploy > [email protected] deploy /Users/shoito/workspaces/aws-cdk-elasticsearch > cdk deploy -c stage=dev -c sourceIp=`curl -s https://checkip.amazonaws.com` ESDomainStack: deploying... ESDomainStack: creating CloudFormation changeset... 0/3 | 17:25:17 | CREATE_IN_PROGRESS | AWS::CDK::Metadata | CDKMetadata 0/3 | 17:25:17 | CREATE_IN_PROGRESS | AWS::Elasticsearch::Domain | test-dev-domain (testdevdo main) 0/3 | 17:25:19 | CREATE_IN_PROGRESS | AWS::CDK::Metadata | CDKMetadata Resource creat ion Initiated 1/3 | 17:25:19 | CREATE_COMPLETE | AWS::CDK::Metadata | CDKMetadata 1/3 | 17:25:19 | CREATE_IN_PROGRESS | AWS::Elasticsearch::Domain | test-dev-domain (testdevdomain) Resource creation Initiated 1/3 Currently in progress: testdevdomain 2/3 | 17:36:23 | CREATE_COMPLETE | AWS::Elasticsearch::Domain | test-dev-domain (testdevdomain) 3/3 | 17:36:25 | CREATE_COMPLETE | AWS::CloudFormation::Stack | ESDomainStack ✅ ESDomainStack Stack ARN: arn:aws:cloudformation:ap-northeast-1:123456789:stack/ESDomainStack/f479bb70-f700-11e9-b8c5-0e36924addac
デプロイ確認
AWSマネジメントコンソールからAmazon Elasticsearch Serviceにアクセスします。 するとこのようにドメインやElasticsearch、Kibanaのエンドポイントなどが作られていることが確認できます。
Kibanaのエンドポイントにアクセスし、サンプルデータを読み込ませたUI
Elasticsearch Serviceドメイン設定確認画面
削除
不要になった場合は、cdk destroyコマンドでスタックを削除します。 ここでは、npm run destroyで間接的に上記コマンドを呼んでいます。
$ npm run destroy > [email protected] destroy /Users/shoito/workspaces/aws-cdk-elasticsearch > cdk destroy -c stage=dev Are you sure you want to delete: ESDomainStack (y/n)? y ESDomainStack: destroying... 0 | 18:27:58 | DELETE_IN_PROGRESS | AWS::CloudFormation::Stack | ESDomainStack User Initiated 0 | 18:27:59 | DELETE_IN_PROGRESS | AWS::Elasticsearch::Domain | test-dev-domain (testdevdomain) 0 | 18:27:59 | DELETE_IN_PROGRESS | AWS::CDK::Metadata | CDKMetadata 1 | 18:28:01 | DELETE_COMPLETE | AWS::CDK::Metadata | CDKMetadata 1 Currently in progress: ESDomainStack, testdevdomain ✅ ESDomainStack: destroyed
さいごに
CDKで、Amazon Elasticsearch Serviceのクラスタを構築してみました。 クラスタを構築するだけならそこまで手間取らなかったのですが、手元のPCから接続できるようにアクセスポリシーを設定する部分で悩みました。 今回のようにCDKでElasticsearchクラスタを構築する情報が少なかったので、誰かの参考になればと思います。