この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
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スタックに渡すためのものです。
package.json
{
"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をカスタマイズしています。
cdk.json
{
"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インターフェースを定義しています。
index.ts
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
> 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 (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
> aws-cdk-elasticsearch@0.1.0 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クラスタを構築する情報が少なかったので、誰かの参考になればと思います。