AWS ECSでRDSにアクセスするアプリを動かすことはじめ


## tl;dr
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">自作のsinatraアプリがAWS ECSで動いた図<br>コンテナ考えることが多くて難しいな <a href="https://t.co/7Rkl5ZYHB1">pic.twitter.com/7Rkl5ZYHB1</a></p>&mdash; sasaki (@yysaki) <a href="https://twitter.com/yysaki/status/1107620831826317323?ref_src=twsrc%5Etfw">2019年3月18日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

図のように、AWS初心者がECSコンテナ上にRDBをRead/WriteするWebアプリを動かすまでの手順です。

TODO:

- 自作WebアプリのDockerイメージをFargateタスクとして起動させた
- WebアプリからRDSへのアクセス方法
- System ManagerのSecureStringを環境変数にとりコンテナからRDSにアクセス方法

## 前置き

### Sinatra
[未経験からRuby on Railsを学んで仕事につなげるまでの1000時間メニュー](https://qiita.com/saboyutaka/items/1a8c40e105e93ac6856a) をやっています。
未経験ではないので記載どおりの時間はかけていませんが自学するのにバランスがよいと思っています。
学習の過程でDBアクセスの伴うSinatraアプリケーションを作りました。

URL: https://github.com/yysaki/sinatra-workout-recording

### AWS
また並行してAWSについて手を動かしてます。
普段触らないモダンな環境がどんなものか知りたいというのがモチベーションです。
1月ごろから手を動かしていましたようです:

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">dotinstallの流れで設置したS3バケットにRoute 53でドメインひもづけたやつ<a href="https://t.co/YVJOR0WxIf">https://t.co/YVJOR0WxIf</a></p>&mdash; sasaki (@yysaki) <a href="https://twitter.com/yysaki/status/1085178933060894722?ref_src=twsrc%5Etfw">2019年1月15日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

今回Sinatraアプリの一環でこれのDockerイメージを作ったので、AWSのコンテナ上で動かしてみたくなりました。
[Amazon EKSが東京リージョンに対応した](https://aws.amazon.com/jp/blogs/news/amazon-eks-tokyo-region/) 話もありますが、ハマりどころが多そうなのでまずは手始めにECSから。

実際に手を動かしてみると、

* RDBとECSが接続するためファイアウォールをどう設定するか
* 秘密情報であるDBの接続情報をどうコンテナに渡すか

などが疑問になり、解決できたので書きます。

## AWSの下準備

これら本題ではありませんが、自分の理解度の確認のため書きます。

### AWSアカウントの初期設定

[最初の IAM 管理者のユーザーおよびグループの作成 - AWS Identity and Access Management](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/getting-started_create-admin-group.html)
アカウントを作成したらこの記事に従ってやります。
ルートユーザーを使わずにAdministrator用アカウントを使用するのが記事中でベストプラクティスとされているので、セキュリティステータスを埋めておきます:

<img width="1107" alt="スクリーンショット 2019-03-21 19.07.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/50400/1a7af74e-ae18-b10f-ccd4-a746c8a2f7d4.png">

まだある程度手を動かしてからこの記事に気づきました。
[AWSアカウントを取得したら速攻でやっておくべき初期設定まとめ - Qiita](https://qiita.com/tmknom/items/303db2d1d928db720888)
大変参考になりますのでおいおい追従していきたいです。

### AWS CLIのインストール
ECRのpushに使うのでAWS CLIをインストールしておきます。
[AWS CLI のインストールと設定 - Amazon Kinesis Data Streams](https://docs.aws.amazon.com/ja_jp/streams/latest/dev/kinesis-tutorial-cli-installation.html)

以下のようにCLIのインストール、初期設定をします。

``` bash
pip install awscli
aws configure
```

リージョンはap-northeast-1を指定します
成功すれば ~/.aws/ 配下にconifgおよびcredentialsが保存されます。

### ECSのチュートリアルをやっておく
[Fargate を使用した Amazon ECS の使用開始 - Amazon Elastic Container Service](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ECS_GetStarted.html)
この手順で一度サービスを稼働させて、Fargateのクラスターや ecsTaskExecutionRole IAMロールを用意しておきます。

### ECRにDockerイメージを仕込む
 [Amazon ECR における Docker の基本 - Amazon ECR](https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/docker-basics.html)
これはCLIで簡単に作業が完結できました。
「(オプション) イメージの Amazon Elastic Container Registry へのプッシュ」を参考にリポジトリへpushします:

``` bash
$(aws ecr get-login --no-include-email)
aws ecr create-repository --repository-name sinatra-workout-recording
docker pull yysaki/sinatra-workout-recording
docker tag yysaki/sinatra-workout-recording {aws_account_id}.dkr.ecr.ap-northeast-1.amazonaws.com/sinatra-workout-recording
docker push {aws_account_id}.dkr.ecr.ap-northeast-1.amazonaws.com/sinatra-workout-recording
```

## ECSタスクを起動してRDSに接続させる

ここからが本題です。

### まずRDSを立てる
[MySQL DB インスタンスを作成して MySQL DB インスタンス上のデータベースに接続する - Amazon Relational Database Service](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/CHAP_GettingStarted.CreatingConnecting.MySQL.html)
この手順の通り進めてMySQLインスタンスを作成します。
このとき、「開発/テスト - MySQL」のユースケースを選び、「RDS 無料利用枠の対象オプションのみを有効化」をチェックして作成すれば無料枠で済みます。

### RDSにアクセスできるようにする

通常はファイアウォールによりRDSに直接アクセスすることはできません。
そこで、「VPCセキュリティグループ」から図のようにローカルIPのCIDRを指定することでローカルからも接続できるようになります。

<img width="1099" alt="スクリーンショット 2019-03-21 17.25.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/50400/580c22c5-5b0e-ade9-644c-7650f3a8dd4f.png">

### 環境変数の保持

DBアカウント情報は秘匿すべき情報なので、ソースコードに記載せず環境変数で取り扱うやりかたを計画しました。
今回のsinatraのアプリではactiverecordを使いますが、環境変数DB_URLでRDSへの接続文字列を渡すことにします。

``` ruby
ActiveRecord::Base.establish_connection(ENV['DB_URL'])
```
書式は `mysql2://{DBユーザ名}:{DBパスワード}@{エンドポイント}/{DB名}` で、一つの環境変数で設定できるので便利でした。

ECSにサービスを作成する前にECSコンテナにDBアクセスの情報を安全に渡す仕組みが必要なのですが、以下の記事がためになりました。

[【祝！】FargateでもECSにごっつ簡単に環境変数に機密情報を渡せるようになりました！](https://dev.classmethod.jp/cloud/aws/fargarte-secrets/)
AWS Systems ManagerのパラメータストアにSecureStringとして保持し、これをECSタスクから読み出す仕組みです。
ごくごく最近導入された機能のようで手間が少なくて助かります。

図のように設定を行います:

<img width="1076" alt="スクリーンショット 2019-03-21 19.10.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/50400/3604a71b-1064-3090-78c7-d190b14425fa.png">

公式ドキュメントとしてはこれです:
[機密データの指定 - Amazon Elastic Container Service](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/specifying-sensitive-data.html)

### 環境変数を設定したECSタスクを立てる
ECSのタスク定義を行います。
先程のSecureStringを読めるよう、以下のjsonによる「ユーザによる管理」タイプのポリシーを定義し、 ecsTaskExecutionRole ロールに付与します:

``` yaml
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ssm:GetParameters",
        "secretsmanager:GetSecretValue",
        "kms:Decrypt"
      ],
      "Resource": "*"
    }
  ]
}
```

また、タスク定義を作成する際、「コンテナの定義」 > 「コンテナの編集」 > 「環境」の環境変数に対して図のように設定します。

<img width="922" alt="スクリーンショット 2019-03-18 20.35.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/50400/80bab465-a572-e320-7c1a-500a03be3117.png">

その他は先の「ECSのチュートリアルをやっておく」の要領でECSタスク定義して、サービスを稼働させればよいです。

### コンテナからRDSに接続できるようにして動作確認
VPCセキュリティグループの設定を行ってRDSに接続できるようにします。

* ECSタスクがアクティブになったら「詳細」タブにある「Public IP」を見て、RDSのVPCセキュリティグループのインバウンドのルールに追記する
    * ただしIPアドレスは可変なので、安定稼働させるうえで改善の余地が大いにあると感じます

以上で設定が完了しタスクが稼働していますので「Public IP」にブラウザからアクセスすることでアプリの動作が確認できました。
ECS -> RDSへの接続については課題を感じるので、どう自動化するかは今後の課題として調べていきたいとお思います。

## なお料金
RDBは無料枠がありますが、ECSでは起動タスクに対する課金を避けられない認識です。
しかしFargate起動タスクはvCPUを細かく刻めるので、お試しに安く使えて便利でした。
0.5GB 0.25vCPUのFargateを稼働させた課金額のslack通知の様子です:

<img width="481" alt="スクリーンショット 2019-03-21 17.59.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/50400/baa6917a-8498-f701-96f5-39fe20e8d409.png">

初めの1.08$はRoute53のドメイン管理で、残りがECSのぶんです。
丸一日稼働させても0.4１$程度で済みました。