AWS CDKでAmazon Elasticsearch Serviceのドメイン(クラスタ)を作ってみた
はじめに
AWS CDKで、Amazon Elasticsearch Serviceのドメイン(クラスタ)を作ってみました。
シンプルにシングルノードでElasticsearchクラスタを作っていますが、設定値を少し変えれば、複数台の専用マスターノードとデータノードからなるクラスタを構築できます。
今回のソースコードはこちらで公開しています。
https://github.com/shoito/aws-cdk-elasticsearch
バージョン情報
1 2 3 4 5 6 7 8 9 10 | $ sw_versProductName: Mac OS XProductVersion: 10.14.6BuildVersion: 18G103$ cdk --version1.14.0 (build 261a1bf)$ node --versionv10.15.3 |
プロジェクトの設定
プロジェクトのファイルは以下のようになります。
1 2 3 4 5 6 7 | $ 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スタックに渡すためのものです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | { "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", "destroy": "cdk destroy -c stage=dev" }, ...省略} |
次にcdk.jsonですが、コンテキスト変数を用いて、Elasticsearch Serviceのノード数などのパラメータを定義しています。
後ほど紹介するindex.tsのコード内から、こちらで定義されている値を利用します。
devやprodなど、ステージ毎に、Elasticsearch Serviceをカスタマイズしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | { "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インターフェースを定義しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | 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 インスタンスタイプは、保管時のデータの暗号化をサポートしていない 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分程度かかりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | $ npm i$ npm run build> aws-cdk-elasticsearch@0.1.0 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> aws-cdk-elasticsearch@0.1.0 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 (testdevdomain) 0/3 | 17:25:19 | CREATE_IN_PROGRESS | AWS::CDK::Metadata | CDKMetadata Resource creation 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 Initiated1/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 ✅ ESDomainStackStack 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で間接的に上記コマンドを呼んでいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $ npm run destroy> aws-cdk-elasticsearch@0.1.0 destroy /Users/shoito/workspaces/aws-cdk-elasticsearch> cdk destroy -c stage=devAre you sure you want to delete: ESDomainStack (y/n)? yESDomainStack: 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クラスタを構築する情報が少なかったので、誰かの参考になればと思います。