マイクロサービスとSOA
似ているよう異なる二つの概念をまとめる。
SOA(サービス指向アーキテクチャ)
- いくつかのサービスのコレクションによってシステムを実現する構造
- ソフトウェアを繋ぐミドルウェアであるESB(エンタープライズサービスバス)によって、サービス間の結合を実現する
- サービス間で同じリソース、ストレージを共有する
マイクロサービス
- いくつかのサービスのコレクションによってシステムを実現する構造
- REST API のようなシンプルで軽い手段でサービス間の結合を実現する
- サービスごとにリソース、ストレージが分離されている
ポイントは通信手段、リソースやストレージを共有するか否かというところかな。
マイクロサービスの方は、それぞれのサービスがより独立しているという認識。
Elasticsearch のCRUD
いつでもElasticsearchでCRUDが実行できるように自分用のメモです。
ElasticsearchはREST APIを提供しているので、馴染みのある感覚でデータの操作を実行できます。
かといってもCurlで実行するのは辛いのでKibanaのコンソールを使います。
使用バージョン
- Elasticsearch 7.7.1
- Kibana 7.7.1
Create
- Documentの新規作成はPUTもしくはPOSTを利用します。
PUTの場合
PUT /member/_doc/1 { "name": "Beats", "age" : 26, "birthday": "1994-10-29", "blog_url": "https://beatsbeats.hatenablog.com/", "job": ["monk","developer"], "favorite_song": {"title":"fav_song","created_at":"2000-01-01"} }
{ "_index" : "member", "_type" : "_doc", "_id" : "1", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 0, "_primary_term" : 1 }
またこっちの形式でも結果は同じ
PUT /member/_create/1 { "name": "Beats", "age" : 26, "birthday": "1994-10-29", "blog_url": "https://beatsbeats.hatenablog.com/", "job": ["monk","developer"], "favorite_song": {"title":"fav_song","created_at":"2000-01-01"} }
POSTの場合
POSTを用いる場合はIDを指定しなくても良いです。指定がない場合は自動採番されます。
POST /member/_doc/ { "name": "Beats", "age" : 26, "birthday": "1994-10-29", "blog_url": "https://beatsbeats.hatenablog.com/", "job": ["monk","developer"], "favorite_song": {"title":"fav_song","created_at":"2000-01-01"} }
{ "_index" : "member", "_type" : "_doc", "_id" : "4_1FfHcBun3pb7RqaiAK", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 2, "_primary_term" : 1 }
またはこっちの形式でも結果は同じ
POST /member/_create/1 { "name": "Beats", "age" : 26, "birthday": "1994-10-29", "blog_url": "https://beatsbeats.hatenablog.com/", "job": ["monk","developer"], "favorite_song": {"title":"fav_song","created_at":"2000-01-01"} }
READ
GETの場合
GET /member/_doc/1
{ "_index" : "member", "_type" : "_doc", "_id" : "1", "_version" : 1, "_seq_no" : 0, "_primary_term" : 1, "found" : true, "_source" : { "name" : "Beats", "age" : 26, "birthday" : "1994-10-29", "blog_url" : "https://beatsbeats.hatenablog.com/", "job" : [ "monk", "developer" ], "favorite_song" : { "title" : "fav_song", "created_at" : "2000-01-01" } } }
こっちの形式を使えばインデックスの名前などのメタデータを除いた形で取得可能です。
GET /member/_source/1
{ "name" : "Beats", "age" : 26, "birthday" : "1994-10-29", "blog_url" : "https://beatsbeats.hatenablog.com/", "job" : [ "monk", "developer" ], "favorite_song" : { "title" : "fav_song", "created_at" : "2000-01-01" } }
UPDATE
フィールドを指定して更新する場合は以下の形式のリクエストを実行します。
POST /member/_update/1 { "doc": { "name": "Beeeats", "age" : 27, "birthday": "1995-10-29", "blog_url": "", "job": ["developer"], "favorite_song": {"title":"fav_song","created_at":"2000-01-03"} } }
{ "_index" : "member", "_type" : "_doc", "_id" : "1", "_version" : 2, "result" : "updated", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 1, "_primary_term" : 1 }
Document全体を新たなDocumentに更新する場合は以下の形式のリクエストを実行します。
PUT /member/_doc/1 { "name": "Beats", "age" : 27, "birthday": "1994-10-29", "blog_url": "https://beatsbeats.hatenablog.com/", "job": ["monk","developer"], "favorite_song": {"title":"fav_song","created_at":"2000-01-01"} }
POST /member/_doc/1 { "name": "Beats", "age" : 27, "birthday": "1994-10-29", "blog_url": "https://beatsbeats.hatenablog.com/", "job": ["monk","developer"], "favorite_song": {"title":"fav_song","created_at":"2000-01-01"} }
DELETE
DELETE /member/_doc/1
{ "_index" : "membe", "_type" : "_doc", "_id" : "1", "_version" : 3, "result" : "deleted", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 2, "_primary_term" : 1 }
Elasticsearch の基本用語
Elasticsearch に入門したということで基本の用語についてまとめました。
Document
- Elasticsearch に格納するデータの一つの単位を指します。
- RDS でいうレコードに相当します。
- JSON形式のオブジェクトです。
- Documentは一つ以上のFieldを持ちます。
- IDは指定がなければ自動採番されます。
Field
以下に基本的な型のみ列挙しておきます。
text
- 文字列を表す型です。
- text型は格納される際に単語ごとに分割され、転置インデックスが構成されます。
keyword
- 文字列を表す型です。
- text型と異なる点として、単語が分割されません。
- メールアドレスやURLなどの完全一致時のみヒットして欲しいような値を格納します。
- keyword型で
https://beatsbeats.hatenablog.com/
を格納した場合、beats
やhatenablog
で検索しても検索でヒットしません。
long,short,integer,float...etc
- 数値を表す型です。(公式ページに詳しく記載があります。)
date
- 日付を表す型です。
- 下記のいずれかの形式を指定できます。
- UNIXエポック(1970/1/1 0:00:00からのミリ秒表記):
1420070400001
- 日付のみ:
"2015-01-01"
- 日付+時刻:
"2015-01-01T12:10:30Z"
boolean
- 真偽を表す型です。
object
- JSONオブジェクトを表す型です。(ネスト構造)
array
- 配列を表す型です。
- 配列の各要素は同じ型になります。
また、一つのFiledが複数のデータ型を持つことも可能です。(マルチフィールド型) 明示的に型を定義していない状態で文字列を格納した場合には、自動的にtextとkeywordが定義されます。 こちらは一見便利ですが、インデックスが増えて容量の増加に繋がります。
Documentの簡単な例
{ "name": "Beats", "age" : 26, "birthday": "1994-10-29", "blog_url": "https://beatsbeats.hatenablog.com/", "job": ["monk","developer"], "favorite_song": {"title":"fav_song","created_at":"2000-01-01"} }
Index
- Documentを格納する場所を表します。
- RDSでいうテーブルに相当します。
- 元々はスキーマかデータベースに相当すると言われていましたが、後述のDocument typeが廃止になったためインデックス単位でデータの分別する他、データ格納場所を分ける方法がないためIndexがテーブルに相当すると言えます。
- Documentがそのまま格納されているのではなく、検索しやすいように様々な形式で格納されている。
Document type
- Documentの種別を表します。
- 廃止となった概念です。詳細は下記の記事にあります。
Mapping
- Documentの各Fieldのデータ型を定義した情報を表します。
- Mappingを事前に定義していなくても、ドキュメント格納時に自動的にElasticsearch側でMappingの定義をしてくれます。
以下は実際にドキュメントをどのように格納しているのかを示す用語なので図も交えてまとめました。
Node
- Elasticsearch が動作するプロセスを指します。
- 実体は一つのJVMインスタンスです。
- 一つのサーバー上で一つのNodeが動作するのが基本ですが、複数のNodeを一つのサーバー上で動作させることも可能です。
- 一意の名(node.name)を持ちます。
- Nodeは役割ごとに4つの属性を持ちます。(詳細は別記事にて)
Cluster
- 互いに通信し合う複数のNodeの集合を指します。
- 一意のCluster名(cluster.name)を持ちます。
Shard
- Elasticsearchはデータを複数のノードで分散保持することがですが、分散した際のそれぞれのパートを指します。
- Shardの数は事前(Index作成時)に設定しておく必要があります。(作成後に増やせないので注意)
Replica
- Shardの複製を指します。
- 複製元のShardをPrimary Shardと呼び、複製されたShardをReplica Shardと呼びます。
- Replica Shardは自動的にPrimary Shardとは別のNodeに作成されます。
- Primary Shardが配置されているNodeの障害時には、ReplicaがPrimaryに昇格します。
- Replica Shardの数はいつでも変更することが可能です。
参考
開発途中でプロジェクト名を変更した時は一括置換すること
僕の場合 Go を使用することが多いのですが、ディレクトリ名変更語にプロジェクト直下でgo build
をした時に古いプロジェクト名でバイナリファイルが作られ続け割と長い時間ハマりました。
これはgo.mod
のモジュール名を変え忘れたままだったことが原因でした。
かなり基本的なことですが二度と忘れないようにと記事にしました。
初歩的なミスに限ってなかなか原因に気づけないですね。。。
CDK で DynamoDB Stream を Lambdaのトリガーにする時の注意点
タイトルを実現するために、半日近く溶かしたので備忘録として残しておきます。
CDKの細かい説明は省きます。
正常に動作するコードがこちら
import { DynamoEventSource } from '@aws-cdk/aws-lambda-event-sources'; import * as dynamodb from '@aws-cdk/aws-dynamodb'; import * as lambda from '@aws-cdk/aws-lambda'; . . . const Table = dynamodb.Table.fromTableAttributes(...,{ // tableArn: でもOK // tableArn tablaName のどちらか片方必須 tableName: {table名}, tableStreamArn: {tableのStreamArn}' }); const Event = new DynamoEventSource(Table, { startingPosition: lambda.StartingPosition.LATEST, }); const fn = new lambda.Function(...,{ . . . events:[Event] });
別のコンストラクタのスタックとして作成したDynamoDBを取得してStreamArnを使ってイベントソースを作成するには、fromTableAttributes()
でテーブルを取得しないといけないようです。
fromTableArn()
、fromTableName()
で取得するとストリームが設定されていないですよと怒られます。
ドキュメント内を解決策は探しても見つからず結局GitHubのIssueが解決の糸口となりました...
1/26 追記
あとは// tableArn tablaName のどちらか片方必須
というのも味噌です。
公式→issue→ブログくらいの順番で探すのが良さそうですね。
VPC内にElasticsearchを置く時の注意点
VPC内のプライベートサブネットにElasticsearchを置いたはいいけど、LambdaからAPI叩けないしKibanaもアクセスできなくて困ったときのメモです。
解決策
- BastionServerとして同VPC内にEC2(パブリックサブネット)を置く
- EC2にSSH接続してダイナミックフォワードを行う(ダイナミックフォワードあまりわかっていない...)
- Lambdaを同じVPC内に置く
- 仕様によってプライベートかパブリックかを選択する
- それぞれのリソースに適切なセキュリティグループ(後述)を設定する
セキュリティグループ設定
EC2
Lambda
- 今回はインバウンドの設定は不要(仕様による)
Elasticsearch
ハマりポイント
ElasticsearchのアクセスコントロールでEC2のプライベートIPを許可しようとしたが以下のエラーが出ました。
UpdateElasticsearchDomainConfig: {"message":"You can’t attach an IP-based policy to a domain that has a VPC endpoint. Instead, use a security group to control IP-based access."}
解決策が見つからず2時間くらいはまりましたが、ちゃんと公式に書いてありました。
VPCs ではセキュリティグループを通じてドメインへのアクセスを管理できます。多くのユースケースでは、このセキュリティ機能の組み合わせで十分となり、ドメインにオープンなアクセスポリシーを安心して適用できます。
Amazon Elasticsearch Service ドメインの VPC サポート - Amazon Elasticsearch Service
どうやらセキュリティグループでの制限で十分のようです。
つまりVPC内にElasticsearchを置くケースだとオープンアクセスを選択しておいて、アタッチしているセキュリティグループのみでアクセスの制限を行う形になります。
追記(2021/1/16)
CDKでElasticsearchを作成する時は明示的にオープンアクセスを設定しなければいけないみたいです。
以下がオープンアクセスのポリシーステートメントです。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "*" }, "Action": "es:*", "Resource": "{domain ARN}/*" } ] }
またKibanaにアクセスするためにSOCKSプロキシを用いたダイナミックフォワード方式ではなく、ローカルフォワード方式でSSHトンネルを掘るときには、EC2にアタッチするセキュリティグループにローカルマシンからの443ポート
アクセスを許可し、8157ポート
を閉じておきましょう。
コマンドもこちらに残しておきます。
$ ssh -i xxxxxxxx.pem ec2-user@{dns} -N -L 9200:{elasticsearch-domain}:443
https://localhost:9200/_plugin/_kibana
へアクセスすると、Kibanaが表示されるはずです。
参考
VPC内にLambdaを置く時の注意点
LambdaをVPC内に置こうとしてエラーが発生した時のメモです。
マネコンからVPC、サブネット、セキュリティグループを指定して保存しようとすると以下のエラーが出ました。
The provided execution role does not have permissions to call CreateNetworkInterface on EC2
ネットワークインターフェイスを作る権限がないみたい。
Lambda VPC ネットワークインターフェース でググると公式がヒット。
Lambda は、関数のアクセス許可を使用してネットワークインターフェイスを作成および管理します。VPC に接続するには、関数の実行ロールに次のアクセス許可が必要です。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-vpc.html
AWS管理ポリシーであるAWSLambdaVPCAccessExecutionRole
をLambdaの実行ロールにアタッチしてあげなければいけません。
公式を参照する癖をつけたいですね。