Amazon Elasticsearch Serviceで手動スナップショットを作成する

**2017-9-16追記: AWSに連絡しなくても自動スナップショットから復元できるようになったようです。**

https://aws.amazon.com/jp/about-aws/whats-new/2017/09/elasticsearch-5_5-now-available-on-amazon-elasticsearch-service/
> Self-service restore from automated snapshots: Self-service restore of Amazon Elasticsearch Service domains is now available from the automated snapshots taken by the service. All automated snapshots are available for restore from the Elasticsearch API or Curator CLI. You no longer have to request support assistance for restore requests.

# 概要

Amazon Elasticsearch Serviceは毎日自動スナップショットを撮るがスナップショットから復元するのにAWSに連絡する必要があるので緊急に復元するのが難しい。

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

> Amazon Elasticsearch Service (Amazon ES) は毎日、Amazon ES ドメインでプライマリインデックスのシャードのスナップショットを撮ります。ただし、自動スナップショットを使用して Amazon ES ドメインを復元するには、AWS サポートチームに連絡する必要があります。


障害時にすぐ復元したい場合や、スナップショットを撮る回数や時間などを柔軟にする場合は手動スナップショットを取ったほうが良い。
S3にリポジトリを作成してスナップショットを保存する。
Amazon Elasticsearch Serviceの場合、通常のElasticsearchと違って認証がAWSの認証になっているためプログラム経由で実行する。

## Amazon Elasticsearch Serviceのアクセスポリシー

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": [
          "arn:aws:iam::xxxxxxxxxxxxx:user/api-user"
        ]
      },
      "Action": "es:*",
      "Resource": "arn:aws:es:ap-northeast-1:xxxxxxxxxxxxx:domain/docbase-staging/*"
    }
  ]
}
```

## スナップショットのリポジトリを作成

ロールタイプをAmazon EC2にしたIAM Roleを作成

es-index-backups

### インラインポリシー

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:ListBucket"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::es-index-backups"
            ]
        },
        {
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject",
                "iam:PassRole"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::es-index-backups/*"
            ]
        }
    ]
}
```

### 信頼関係の編集

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "es.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```

## IAM Userのポリシー

アクセスキーとアクセスシークレットを使うユーザーarn:aws:iam::xxxxxxxxxxxxx:user/api-userに設定する。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "iam:PassRole"
            ],                                                  
            "Resource": "arn:aws:iam::xxxxxxxxxxxxx:role/es-index-backups"
        }
    ]
}
```

# Rubyでの実装例

```rb:Gemfile
source 'https://rubygems.org'

gem 'aws-sdk'
gem 'elasticsearch'
gem 'faraday_middleware-aws-signers-v4'
```

```rb
require 'aws-sdk'
require 'elasticsearch'
require 'faraday_middleware/aws_signers_v4'

client = Elasticsearch::Client.new url: 'https://test-imq6pvhf84ujmtxigvc1rd5tna.ap-northeast-1.es.amazonaws.com' do |f|
  f.request :aws_signers_v4,
            credentials: Aws::Credentials.new(ENV['AWS_ACCESS_KEY'], ENV['AWS_SECRET_ACCESS_KEY']),
            service_name: 'es',
            region: 'ap-northeast-1'
end
```

## リポジトリの作成

```rb
body = {
  type: 's3',
  settings: {
    bucket: 'es-index-backups',
    region: 'ap-northeast-1',
    role_arn: 'arn:aws:iam::xxxxxxxxxxxxx:role/es-index-backups'
  }
}

puts client.snapshot.create_repository(repository: 'es-index-backups', body: body)
```

## スナップショットの作成

```rb
body = {
  indices: 'index_name',
  ignore_unavailable: true
}
puts client.snapshot.create(repository: 'es-index-backups', snapshot: '20170829', body: body)
```

## 動作確認テスト用のドキュメントを作成

```rb
1.upto(3) do |i|
  puts client.create(index: 'index_name', type: 'mytype', id: i, body: {title: 'title test'})
end
```

## スナップショットから復元

```rb
body = {
  rename_pattern: '^(.*)$',
  rename_replacement: "$1_20170829"
}
puts client.snapshot.restore(repository: 'es-index-backups', snapshot: '20170829', body: body)
```

## インデックスの削除

```rb
puts client.indices.delete(index: 'index_name')
```

## 参考

https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch-transport#transport-implementations

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