Amazon Elasticsearch serviceのsnapshot取得してリストア（復元）するまで

Amazon Elasticsearch Service（以後esと記載）のsnapshotを取得する際、権限まわりで詰まったので、まとめてみる。
利用環境はMacです。

## 【背景】
- esのsnapshotを手動で取得するのってどんな方法でできるんだろう。
- なんかちょっと権限まわりややこしそうだから一回触ってみよう
- 取得と復元でどれぐらい時間がかかるかもできたら測ってみよう

こんな課題感で手を動かし始めました。

## 【実際にやったこと】
- [公式ドキュメント](https://docs.aws.amazon.com/ja_jp/elasticsearch-service/latest/developerguide/es-managedomains-snapshots.html)を参考にする
- [【Amazon Elasticsearch Service】 手動スナップショットからリストアする方法](https://dev.classmethod.jp/articles/restore-from-amazon-es-manual-snapshot-ja/)を参考にする（この記事を本当に参考にさせていただきました）
- Kibanaでアクセス許可を与える

このような手順で実現させて行きました！！！

### 手動でS3にsnapshotを取得していく
手動でsnapshotを作成するためには、前提として、以下の対応が必要になります。

- s3のバケットの作成
- IAMロール、ポリシーを整理する（←ここで苦労しました）
    - Kibanaでの認証
- 手動snapshotのリポジトリを登録する
- 手動snapshotを取得する
- snapshotを取得したリポジトリを確認する
- snapshotの取得状況を確認する
- スナップショットのリストア
    - 一度インデックスを削除する
    - 指定したインデックスをリストアする


今回記述しているscriptはpythonで書いていますが、他にもJava, Ruby, Node, Goでも記述できます。こちらの[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/elasticsearch-service/latest/developerguide/es-request-signing.html)を参考にしてください

それでは順番に説明していきたいと思います。
## S3バケットの作成
手動でsnapshotを保存するためのバケットを作成する。
今回はS3コンソールで```arn:aws:s3:::test-es-snapshot```を作成した。これは以下の二つの箇所で必要になる

- IAMロールにアタッチされたIAMポリシーのResource Statement内
- snapshotリポジトリ登録に使用するpythonのpayload内

## IAMロール、ポリシーを整理する
今回確認すべき箇所は三箇所

#### 1.ESのサービスロール(arn:aws:iam::ユーザ名:role/es_s3_role)を作成する。

信頼関係を以下のように編集する
principalで指定するのは"誰がこのロールを引き受けるか”ということ
以下のServiceはesを示しており、「作成したサービスロールはesを引き受ける」という意味（だと思う。）

```JSON:サービスロールの信頼関係に記載
{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "",
    "Effect": "Allow",
    "Principal": {
      "Service": "es.amazonaws.com"
    },
    "Action": "sts:AssumeRole"
  }]
}
```
#### 2.S3に対してアクセスし、データを取得するためのポリシーをサービスロールに対してアタッチする

create-es-backup-policyという管理ポリシーを以下の内容で作成し、上で作成したサービスロールに対してアタッチする

```json:create-es-backup-policy(作成したサービスロールに対してアタッチする)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "s3:ListBucket"
      ],
      "Effect": "Allow",
      "Resource": [
        "arn:aws:s3:::test-es-snapshot"
      ]
    }, 
    {
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Effect": "Allow",
      "Resource": [
        "arn:aws:s3:::test-es-snapshot/*"
      ]
    }
  ]
}
```
#### 3.Kibana側でIAMロールのアクセスを許可する
Kibana で、**[Security (セキュリティ)]**、**[Role Mappings (ロールのマッピング)]**、**[Add (追加)]** の順に選択します。**[Role (ロール)]**で、**[manage_snapshots]** を選択します。次に、IAM ユーザーまたは IAM ロールの ARN を該当するフィールドに指定します。ユーザーの ARN は、**[Users (ユーザー)]**セクションに入力します。ロールの ARN は、**[Backend roles (バックエンドロール)]** セクションに入力します。（※これで、スナップショットを使用するためのロール情報のアクセス許可を付与する）
今回はKibanaのRole MappingsというページのBackend Rolesというところに、作成したサービスロールのARN情報を入力した。
（IAMのポリシーの操作でも認証情報は設定できるはずだが、エラーで詰まったのでこの手法で回避した）

以上で、IAMロール周りの整理は終わりです。

## 手動snapshotのリポジトリを登録する
以下のpythonスクリプトを実行する(参考資料では認証をアクセスキーで行っていたが、現在公式からアクセスキーでの認証は、推奨されていない)

```python:register-repositry.py
import boto3
import requests
from requests_aws4auth import AWS4Auth

region = 'リージョン名' # e.g. us-west-1
service = 'es'
credentials = boto3.Session().get_credentials()
awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token)
host = 'esドメインのエンドポイント /' # /を忘れないように 

# Register repository
path = '_snapshot/test-es-snapshot' # the Elasticsearch API endpoint
url = host + path
payload = {
  "type": "s3",
  "settings": {
    "bucket": "バケット名",
    "region": "リージョン名",
    "role_arn": "作成したサービスロール名"
  }
}

headers = {"Content-Type": "application/json"}

r = requests.put(url, auth=awsauth, json=payload, headers=headers)

print(r.status_code)
print(r.text)
```
responseが以下のようになっていることを確認する

```
200
{"acknowledged":true}
```

## 手動snapshotを取得する

```python:snapshot.py
import boto3
import requests
from requests_aws4auth import AWS4Auth

region = 'リージョン名' # e.g. us-west-1
service = 'es'
credentials = boto3.Session().get_credentials()
awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token)
host = 'esドメインのエンドポイント /' # /を忘れないように 

path = '_snapshot/test-es-snapshot/my-snapshot-1' # the Elasticsearch API endpoint
url = host + path

r = requests.put(url, auth=awsauth)

print(r.text)
```

```:実行例
-> % python snapshot.py
Enter MFA code for :
{"accepted":true}
```

## snapshotを取得したリポジトリを確認する

```python:check_repository.py
import boto3
import requests
from requests_aws4auth import AWS4Auth

region = 'リージョン名' # e.g. us-west-1
service = 'es'
credentials = boto3.Session().get_credentials()
awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token)
host = 'esドメインのエンドポイント /' # /を忘れないように 

path = '_snapshot/?pretty'
url = host + path

r = requests.get(url, auth=awsauth)

print(r.text)
```
```:実行例
-> % python check_repository.py
Enter MFA code for　:
{
  "cs-automated-enc" : {
    "type" : "s3"
  },
  "test-es-snapshot" : {
    "type" : "s3",
    "settings" : {
      "bucket" : "test-es-snapshot",
      "region" : "リージョン名",
      "role_arn" : "作成したサービスロール名"
    }
  }
}
```

## snapshotの取得状況を確認する

```python:check_snapshot.py
import boto3
import requests
from requests_aws4auth import AWS4Auth

region = 'リージョン名' # e.g. us-west-1
service = 'es'
credentials = boto3.Session().get_credentials()
awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token)
host = 'esドメインのエンドポイント /' # /を忘れないように 

path = '_snapshot/test-es-snapshot/_all?pretty'
url = host + path

r = requests.get(url, auth=awsauth)

print(r.text)
```

```:実行例
-> % python check_snapshot.py
Enter MFA code for :
{
  "snapshots" : [ {
    "snapshot" : "my-snapshot-1",
    "uuid" : "*************",
    "version_id" : ******,
    "version" : "7.8.0",
    "indices" : [ "index名①", "index名②", ・・・・・・ ],
    "include_global_state" : true,
    "state" : "SUCCESS",
    "start_time" : "2020-11-17T07:20:17.265Z",
    "start_time_in_millis" : 1605597617265,
    "end_time" : "2020-11-17T07:21:21.901Z",
    "end_time_in_millis" : 1605597681901,
    "duration_in_millis" : 64636,
    "failures" : [ ],
    "shards" : {
      "total" : 38,
      "failed" : 0,
      "successful" : 38
    }
  } ]
}
```

## スナップショットのリストア
リストアの際に同じ名前のインデックスがある場合、リストアできないので、リストアしたいインデックスを一度削除する必要があります。
指定のインデックスを一度削除して、リストアするまでの手順を示します。

### 一度インデックスを削除する
```python:delete_index.py
import boto3
import requests
from requests_aws4auth import AWS4Auth

region = 'リージョン名' # e.g. us-west-1
service = 'es'
credentials = boto3.Session().get_credentials()
awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token)
host = 'esドメインのエンドポイント /' # /を忘れないように 

# DELETE INDEX
path = 'インデックス名'
url = host + path

r = requests.delete(url, auth=awsauth)

print(r.text)
```

```:実行例
-> % python delete_index.py
Enter MFA code for :
{"acknowledged":true}
```
esのコンソールで、ドメインを選択し、インデックスタブに行くと、インデックスが削除できていることを確認できます。
また、ダッシュボードで、検索可能なドキュメントが少なくなっていることも確認できます。

### 指定したインデックスをリストアする
```python:restore_one.py
import boto3
import requests
from requests_aws4auth import AWS4Auth

region = 'リージョン名' # e.g. us-west-1
service = 'es'
credentials = boto3.Session().get_credentials()
awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token)
host = 'esドメインのエンドポイント /' # /を忘れないように 

path = '_snapshot/test-es-snapshot/my-snapshot-1/_restore'
url = host + path
payload = {"indices": "インデックス名"}
headers = {"Content-Type": "application/json"}

r = requests.post(url, auth=awsauth, json=payload, headers=headers)

print(r.text)
```

```:実行例
-> % python restore_one.py
Enter MFA code for :
{"accepted":true}
```

## まとめ
snapshotの取得やリストアは参考資料がとても役に立ちました。（ほとんど同じです。）
IAMのロールやポリシーの整理をするときに、結構苦労しました。。。
どのロールにどのポリシーをアタッチしたとかをしっかり整理できてないと厳しかったです。。

どなたかの参考になれば幸いです。。