LambdaでElasticsearch Serviceの手動スナップショットを取得する仕組みをCFnで一括デプロイ

Amazon Elasticsearch Service (AES) で手動スナップショット取得の仕組みをCloudformationで一気にデプロイします。かつCurator使って古いスナップショットは自動的に削除する設定になっています。スナップショット取るだけで意外に大変です。


PythonスクリプトはこちらのAWSのドキュメントにあるものベースにしてます。

https://docs.aws.amazon.com/ja_jp/elasticsearch-service/latest/developerguide/curator.html



# やりたいこと
これを作ります。スクリプトはAWSのドキュメントにあるものを、ほぼそのまま使っているので、読めばすぐわかるレベルだと思います。コメントも残ってます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116068/fd25ed74-896a-60f6-77fb-fc1ebf2bd53f.png)



# 手動スナップショットの必要性
2019年7月に以下のように自動スナップショットが強化され、1時間毎のスナップショットが14日間 S3に保管されるようになりました。

https://aws.amazon.com/jp/about-aws/whats-new/2019/07/amazon-elasticsearch-service-increases-data-protection-with-automated-hourly-snapshots-at-no-extra-charge/


これだけでも、複数ノード同時障害によるデータロスト時にも障害1時間前の状況までもどせる状態になりとても便利です。

ただ、自動スナップショットはマネージドのS3バケットに取得されるため、ドメインが削除されると一緒に全てが削除されてしまいます。

また、ドメインの複製を作成したい場合など、スナップショットを別ドメインにリストアはできないなどの制約もあります。

Kibanaの定義やIndexのマップ等を別ドメインで使いたいというニーズは結構あると思います。そのためには自動スナップショットに合わせて手動のスナップショットも並列で取得すると良いです。


# 参考URLはあるが
実績あるサービスだけあって、この辺りは公式でも情報があります。


https://docs.aws.amazon.com/ja_jp/elasticsearch-service/latest/developerguide/curator.html


ただ、これをそのままLambdaにコピペしても動きません。Pythonのサンプルスクリプトに使われているライブラリは別途Lambdaでインポートできるように設定が必要です。

また、手動スナップショットを行うにはIAM等の設定が必要です。ここにマニュアルありますが、慣れてないと何やっていいかわからないと思います。

https://docs.aws.amazon.com/ja_jp/elasticsearch-service/latest/developerguide/es-managedomains-snapshots.html


この記事では、このプログラム(を少し調整したもの)が動く環境作るのを目標にします。


# 作業前提
この記事で実施している操作の前提です。

## 作業端末側
最低、以下がインストールされているLinuxやMacマシン。（ちなみに、私はwsl2で作業してます）

- Python3
- Docker
- Git
- AWS CLI

## AWS側

- Amazon Elasticsearch ServiceをVPCアクセスタイプでデプロイ済み

# 実行方法

## Githubからダウンロード
Githubから以下のリポジトリをダウンロードします。

```sh
$ git clone git@github.com:yomon8/aes-snapshot-sample.git
$ cd aes-snapshot-sample/
```

https://github.com/yomon8/aes-snapshot-sample



## 設定ファイル調整
設定ファイルに変数を入力します。

```sh
$ cp settings.sh{.sample,}
$ vim settings.sh
```

以下のような項目があるので、環境に合わせて設定入れます。

```sh
# CloudformationのStack名
readonly STACK_NAME=es-snapshot-sample

# Cloudformationのパッケージをアップロードするバケット
readonly STACK_S3_BUCKET=bucket-name
readonly STACK_S3_PREFIX=es-snapshot-sample

# AESのホスト名（AESはVPCアクセスを前提に作ってます)
readonly AES_HOST=vpc-xxxxxxxxxxxxxxxxxxxxxx.ap-northeast-1.es.amazonaws.com

# AESにアクセス可能なSubnetを指定
readonly LAMBDA_SUBNET_ID=subnet-xxxxxxxxxxxxxxxxxxxxxx

# AESにアクセス可能なSGを指定
readonly LAMBDA_SECURITY_GROUP_ID=sg-xxxxxxxxxxxxxxxxxxx

# AESのスナップショットの保管先の名前
readonly SNAPSHOT_REPOSITORY_NAME=your-repo

# AESのスナップショットの名前のPrefix
readonly SNAPSHOT_PREFIX=your-snapshot
```

## Cloudformationデプロイ
後はデプロイ用のスクリプト叩いたら終わりです。初回はLayerのソフトウェアダウンロードするのに結構時間かかります。私の環境で10分かからないくらいです。

```sh
$ ./deploy.sh
～省略～
```


## Snapshotリポジトリの登録

`deploy.sh` を実行すると以下のようなメッセージが最後に表示されます。

```sh
1. 最初に一回、以下のコマンドを実行します.
aws lambda invoke --function-name aes-snapshot-sample-AESRegistSnapshotRepositoryFun-1SHIY9NWSSH8B:live /dev/null
```

メッセージの通り、コマンドを実行するとリポジトリ登録用のLambdaが実行されます。

```sh
$ aws lambda invoke --function-name aes-snapshot-sample-AESRegistSnapshotRepositoryFun-1SHIY9NWSSH8B:live /dev/null
{
    "StatusCode": 200,
    "ExecutedVersion": "1"
}
```


リポジトリが登録されたか確認してみます。 `my-repo` というリポジトリが登録されています。

```
GET _cat/repositories?v
id           type
cs-automated   s3
my-repo        s3
```



# バックアップ取ってみる
この時点ではリポジトリ登録しただけなのでスナップショットはありません。以下のAPI叩いてもスナップショット表示されないはずです。

```
GET _cat/snapshots/my-repo
```


スナップショットを取得するため `deploy.sh` でもう一つメッセージが表示されると思います。


```sh
2. スナップショットを手動で取得するには、以下のコマンドを実行します.   
   なお、こちらのLambdaはスケジュール実行も設定されています.
aws lambda invoke --function-name aes-snapshot-sample-AESRotateSnapshotFunction-1GB4JKXMNZ8KQ:live /dev/null
```

こちらで表示されているコマンドを実行するとバックアップが取得できます。


```sh
$ aws lambda invoke --function-name aes-snapshot-sample-AESRotateSnapshotFunction-1GB4JKXMNZ8KQ:live /dev/null
{
    "StatusCode": 200,
    "ExecutedVersion": "1"
}
```

```
GET _cat/snapshots/my-repo?v
id                            status start_epoch start_time end_epoch  end_time duration indices successful_shards failed_shards total_shards
my-snapshot-2019-11-13-12-35 SUCCESS 1573647456  12:17:36   1573647456 12:17:36    459ms       1                 1             0            1
```

S3にもスナップショットのデータ入ったこと確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116068/ffd8d31e-15f1-72e0-3d54-dca0f4345f22.png)


なお、バックアップの方はCloudWatch Eventで時間起動するように設定されていますので、上記のようにコマンド実行しなくても1日一回動きます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116068/6afbd6bb-3368-324d-e56c-a19d197c55ea.png)


`template.yaml` の以下の部分でスケジュール調整できます。スケジュール以外もスクリプトはありもの持ってきただけなので調整して使ってください。

```yaml
      Events:
        Rule:
          Type: Schedule
          Properties:
            Schedule: cron(5 16 * * ? *)
```


# やっていること
やっていることは `deploy.sh` の中身見てもらえばわかると思います。


elasticsearchやCuratorはDockerでダウンロード、ビルドしてLambda Layerとしてアップロードしています。

```sh
#!/bin/bash

# 設定ファイルの読み込み
source ./settings.sh

# Lambda Layerとして必要なPythonライブラリをダウンロード
docker run --rm -v $(pwd)/layer/python:/python python:3.7.5-alpine pip install -t /python requests-aws4auth elasticsearch elasticsearch-curator

# Cloudformation でデプロイ
aws cloudformation package --template-file ./template.yaml --output-template-file ./package.yaml \
    --s3-bucket ${STACK_S3_BUCKET} \
    --s3-prefix ${STACK_S3_PREFIX} 
aws cloudformation deploy --template-file ./package.yaml --capabilities CAPABILITY_IAM \
    --stack-name ${STACK_NAME} \
    --parameter-overrides \
    AESHost=${AES_HOST} \
    LambdaSubnetId=${LAMBDA_SUBNET_ID} \
    LambdaSecurityGroupId=${LAMBDA_SECURITY_GROUP_ID} \
    SnapshotRepositoryName=${SNAPSHOT_REPOSITORY_NAME} \
    SnapshotPrefix=${SNAPSHOT_PREFIX} 

# メッセージ表示
echo ""
echo "1. 最初に一回、以下のコマンドを実行します."
echo aws lambda invoke --function-name $(aws cloudformation describe-stacks --stack-name ${STACK_NAME} --query 'Stacks[].Outputs[?OutputKey == `RegistSnapshotFunctionName`].OutputValue' --output text):live /dev/null
echo ""
echo "2. スナップショットを手動で取得するには、以下のコマンドを実行します."
echo "   なお、こちらのLambdaはスケジュール実行も設定されています."
echo aws lambda invoke --function-name $(aws cloudformation describe-stacks --stack-name ${STACK_NAME} --query 'Stacks[].Outputs[?OutputKey == `RotateSnapshotFunctionName`].OutputValue' --output text):live /dev/null
echo ""
```


# スナップショットの復元
同じようにスナップショットが保管されているS3をリポジトリとして登録すれば、後は以下のリンクの手順で簡単にリストアできます。

https://docs.aws.amazon.com/ja_jp/elasticsearch-service/latest/developerguide/es-managedomains-snapshots.html#es-managedomains-snapshot-restore




# 参考

https://github.com/elastic/curator


# [Tipsメモ] Kibanaのインデックス以外をリストアする
よくあるのが、Kibana以外をリストアしたい場合。以下のように実行するとKibana以外のインデックスをリストアできます。indicesのパラメータ変更すれば特定のindexのみリストアしたり色々できます。

```
POST _snapshot/リポジトリ名/スナップショット名/_restore
{"indices": "*,-.kibana_1"}
```



https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshots.html



https://docs.aws.amazon.com/ja_jp/elasticsearch-service/latest/developerguide/es-managedomains-snapshots.html#es-managedomains-snapshot-restore