DataPipelineでバッチアプリケーションを運用してみた

業務でDataPipelineを使ってバッチアプリケーションの運用を経験したので、その感想と、一番悩んだamiのとりなおしとDataPipelineへの反映に関して書きます。

# DataPipelineの使い方について
DataPipelineの真髄にはまったく触れず、ただのcronの代替として使いました。
cronの代替としての使い方については、<a href="http://qiita.com/imaifactory/items/6dc16820c4bec67a7d05" target="_blank">こちらの記事</a>が参考になります。

使い方は大体上記の通りで、1日1回特定のShellScriptActivityを実行するといったものが主になります。

# DataPipeline使ってみて良かったとこ、微妙だったとこ
#### 良かったとこ
* cronサーバー不要
* バッチ実行時だけインスタンス立てる、みたいなエコな運用が手軽に実現可能
* 1バッチ1pipelineな運用になると思うので、バッチ間の依存がなくなりやすい
* GUIに慣れれば、ポチポチして簡単に再実行

#### 微妙だったとこ
* GUIに絶対に苦戦する
* スケジュールは曜日や特定時間帯のみの実行など、cronほど細かい指定はできない
* ActivtyとかEC2の状態によって再実行できたり、できなかったりするのがわかりずらい（GUI）
* サーバーの設定変更を行いたい場合に、amiとりなおしたりすると反映が手間
* GUI

# amiのとりなおしとDataPipelineへの反映
微妙だったとこに書いた、amiのとりなおしとその反映についてです。
運用方針として、バッチアプリケーションで使うamiは事前に作成しておき、そのimage_idをDataPiplineに指定するという方針をとっていました。

この運用方法の場合、サーバーに新しいライブラリ等をインストールした場合等、都度amiのとりなおしが発生し、またそのimage_idをDataPipelineに反映するという作業が発生します。

最初はポチポチ手作業でやってたのですが、すぐにやっていられなくなり、ある程度の自動化をするために下記のような仕組みで運用するようにしました。

* サーバー構成はcloud_configにすべて記述する
* 上記の構成を検証するためのserverspecを書く
* 上記2つをリポジトリ管理する
* 変更してpushしたタイミングで、amiの作成とserverspecの実行をjenkins氏におねがい
* 上記で作成したimageを運用してるDataPipelineすべてに反映するスクリプトを書く

バッチで利用するサーバー構成はすべてcloud_configで定義することにより、設定の追加を容易にしました。
また、その妥当性の検証を簡単に行えるようにserverspecを書きました。

上記を元にしたamiの作成と、その妥当性の検証はjenkins氏にお願いしました。
jenkins氏が行う内容としては、記述したcloud_configを元にしたインスタンスを立ち上げ、serverspecを実行、その後ami取得し、インスタンスをterminate、という感じです。

```
#!/bin/bash
user_data=`cat ./cloud_config.yml | base64`

# run instance
instance_info=`aws ec2 run-instances --image-id [ベースとなるimageのid] --iam-instance-profile Arn="arn:aws:iam::[アカウントID]:instance-profile/[ロール名]" --key-name [pem] --security-group-ids [security_group_id] --instance-type t2.micro --subnet-id [subnet_id] --region ap-northeast-1 --user-data "${user_data}"`

# check running
instance_id=`echo $instance_info | jq '.Instances[0].InstanceId' | sed -e s/\"//g`
# インスタンス起動するまでwait
while true
do
  enable_instance_id=`aws ec2 describe-instance-status --instance-ids $instance_id --filters "Name=instance-status.status,Values=ok" | jq '.InstanceStatuses[0].InstanceId' | sed -e s/\"//g`
  if [ ${enable_instance_id} = ${instance_id} ];then
    break
  fi
  sleep 10s
done

# 10minutes wait
# server specが正常に動かなかったのでwaitしてるんだけど、↑のインスタンス起動判定見直せば改善すると思う
sleep 10m

# server spec
# コードは仮。起動したインスタンスのprivate ipに対してremoteでspec流す感じがいいと思います
ip_address=`echo $instance_info | jq '.Instances[0].PrivateIpAddress' | sed -e s/\"//g`
cd ./server_spec/
bundle exec rake || exit_code=$? 

# stop instance
stop_info=`aws ec2 stop-instances --region ap-northeast-1 --instance-ids $instance_id`
# インスタンス停止するまでwait
while true
do
  stopped_instance_id=`aws ec2 describe-instances --instance-ids $instance_id --filters "Name=instance-state-name,Values=stopped" | jq '.Reservations[0].Instances[0].InstanceId' | sed -e s/\"//g`
  if [ ${stopped_instance_id} = ${instance_id} ];then
    break
  fi
  sleep 10s
done

# create image
today=`date '+%Y%m%d%H%M%S'`
image_id=`aws ec2 create-image --region ap-northeast-1 --instance-id $instance_id --name "ami-${today}" --no-reboot | jq '.ImageId' | sed -e s/\"//g`
# amiとれるまでwait
while true
do
  available_image_id=`aws ec2 describe-images --image-ids $image_id --filters "Name=state,Values=available" | jq '.Images[0].ImageId' | sed -e s/\"//g`
  if [ ${available_image_id} = ${image_id} ];then
    break
  fi
  sleep 10s
done

# terminate instance
terminate_info=`aws ec2 terminate-instances --instance-ids $instance_id`

# exit with serverspec result
exit $exit_code
```
そのまま使ったりはできないと思うんですが、何かの参考になればと。コードはごめんなさい。ちなみにjqとserverspec使うならruby入ってる前提です。

作成したamiのDataPipelineへの反映については、管理しているDataPipelineのIDを設定ファイルに定義し、そのIDすべてにaws cliでimage_idを更新するスクリプトを書きました。

```
for pipeline_id in $pipeline_ids
do
  pipeline_id=`echo $pipeline_id | sed 's/"//g'`
  pipeline_info=`aws datapipeline get-pipeline-definition --pipeline-id $pipeline_id`
  old_image_id=`echo $pipeline_info | sed -E 's/^.*"imageId": "([^"]*)".*$/\1/g'`
  `echo $pipeline_info | sed "s/${old_image_id}/${image_id}/g" > datapipeline_tmp.json`
  aws datapipeline put-pipeline-definition  --pipeline-id $pipeline_id --pipeline-definition "file://datapipeline_tmp.json"
  if [ -n "${is_activate}" ];then
    aws datapipeline activate-pipeline --pipeline-id $pipeline_id
  fi
done
```
指定のpipeline_idの定義を取得して、image_idを置換して反映する。
is_activateという変数があったらactivateも行う、という感じですね。

# 運用方法について考察
という感じで、私は構成変更する度にamiをいちいち変更していますが、おそらく毎回バッチ実行時にDataPipeline上で必要なサービスをインストールするという手もあると思います。

とはいえ、毎回全部作り直すのもちょっととも思うので、一番バランスがいいのはある程度ベースとなるamiは取っておいて、あとはバッチごとに必要なソフトウェアを都度インストールしたりする運用なのかなと思いました。