amazonicaとAWS Lambdaで定期的にEBSのスナップショットを作成する

この記事は[Clojure Advent Calendar 2015](http://qiita.com/advent-calendar/2015/clojure)の21日目の記事です。

ClojureとAWS Lambdaを使って何かできないかを考えるのがマイブームなのですが、[amazonica](https://github.com/mcohen01/amazonica)を使うとAWSのリソースに簡単にアクセスできるのが分かったので、amazonicaを使って定期的にEBSスナップショットを作成するLambda Functionを作ってみます。

## EBSスナップショット関連のamazonicaの関数
[amazonicaのgithubには代表的な関数の呼び出し方の例](https://github.com/mcohen01/amazonica/blob/master/README.md#examples)は記載されていますが、包括的なリファレンスがどこにあるのか分からない！ということで、Lambda Functionを作る前に、replであたりをつけます。

EBSスナップショットを作成。

```clj
(def cred {:access-key "YOUR_ACCESS_KEY"
           :secret-key "YOUR_SECRET_KEY"
           :endpoint   "ap-northeast-1"})

(amazonica.aws.ec2/create-snapshot cred :volume-id "vol-a1b2c3d4" 
                                        :description "vol-a1b2c3d4-snapshot-2015-12-21-16-50")
```

スナップショットにタグをつける。

```clj
(amazonica.aws.ec2/create-tags cred :resources ["snap-a1b2c3d4"]
                                    :tags [{:key "Name"
                                            :value "vol-a1b2c3d4-snapshot-2015-12-21-16-50"}])
```

スナップショットを削除。

```clj
(amazonica.aws.ec2/delete-snapshot cred :snapshot-id "snap-a1b2c3d4")
```

## Lambda Functionを登録してみる。
とりあえず意図したとおりにLambda Functionの定期実行でスナップショットをとれるかどうかを確認するため、AWSのアクセスキーや対象のVolume IDはハードコードしてしまってLambda Functionを登録してみます。

```clj
; src/lambdabackup/ec2.clj
(ns lambdabackup.ec2
  (:gen-class
   :implements [com.amazonaws.services.lambda.runtime.RequestStreamHandler])
  (:require [clojure.java.io :as io]
            [amazonica.aws.ec2 :as ec2]))

(def cred {:access-key "YOUR_ACCESS_KEY"
           :secret-key "YOUR_SECRET_KEY"
           :endpoint   "ap-northeast-1"})

(def volume-id "vol-a1b2c3d4")

(defn -handleRequest [this is os context]
  (let [name "test-backup"
        snapshot (ec2/create-snapshot cred
                                      :volume-id volume-id
                                      :description name)
        snapshot-id (get-in snapshot [:snapshot :snapshot-id])]
    (ec2/create-tags cred
                     :resources [snapshot-id]
                     :tags [{:key "Name" :value name}])
    (let [w (io/writer os)]
      (.write w snapshot-id)
      (.flush w))))
```

`project.clj`はこんな感じ。

```clj
(defproject lambdabackup "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.7.0"]
                 [com.amazonaws/aws-lambda-java-core "1.0.0"]
                 [amazonica "0.3.41"]]
  :aot :all)
```

standaloneなjarを作成。

```
$ lein ubarjar
```

jarをアップロードし、Lambda Functionを新規作成。`--role`は[この記事](http://qiita.com/csakatoku/items/27bfed3cc87901fa24c7#%E4%B8%8B%E6%BA%96%E5%82%99)を書いたときに作ったRoleを再利用しました。

```
$ aws lambda create-function --function-name ebs-create-snapshot --handler lambdabackup.ec2::handleRequest --runtime java8 --memory 512 --timeout 60 --role arn:aws:iam::AWS_ACCOUNT_ID:sandbox-lambda-exec-role --zip-file fileb://./target/lambdabackup-0.1.0-SNAPSHOT-standalone.jar
```

jarを更新する場合には、

```
$ lein ubarjar
$ aws lambda update-function-code --function-name ebs-create-snapshot --zip-file fileb://./target/lambdabackup-0.1.0-SNAPSHOT-standalone.jar
```

AWS LambdaのWebコンソールでテストを実行したところ、スナップショットを作成することはできているようなので、同じくWebコンソールのLambda > Functions > FUNCTION_NAME > Scheduled Eventを登録しました。(本当はCLIでやりたかったが方法がぱっと分からず。)

## TODO
Clojure力とAWS Lambda力が低すぎて、アドベントカレンダーの期日までには解決できなかったことがたくさん残っています。

- AWSのアクセストークンをハードコードしているのは何とかならないか？
  - Lambdaを実行しているRoleに権限を付与すれば消せるだろうか？
- Volume IDをハードコードしているのは何とかならないか？
  - LambdaのScheduled Eventに任意の引数を含められれば消せそうだが、そういう機能はなさそう。
- 毎回`com.amazonaws.services.lambda.runtime.RequestStreamHandler`を実装するは無駄なので、もう少し省エネでハンドラーを書きたい。
- スナップショット作成に失敗した場合の処理をなんとかしたい。
- 保持するスナップショット数を一定の世代数に制限して、不要になったスナップショットを削除するLambda Functionもamazonicaで実装したい。

## 参考URL

- https://github.com/mcohen01/amazonica
- [ClojureとAWS LambdaでHello World](http://qiita.com/csakatoku/items/27bfed3cc87901fa24c7)
- [AWS LambdaからPythonでEC2のインスタンスを起動するスケジュールを設定してみた #reinvent](h ttp://dev.classmethod.jp/cloud/scheduled_to_start_instance_from_lambda/)