増殖Lightsailで動画納品のデータ転送費を無料にする話

増える、そして食べられる

この記事は [Wano Group Advent Calendar 2019](https://qiita.com/advent-calendar/2019/wano-group) の6日目の記事となります。

## Video Kicks

<img width="240" alt="viodekicks" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85375/c18d822b-1de7-e2d1-1c84-b11f720f9a7f.png">

Wanoグループでは　[VideoKicks](https://www.tunecore.co.jp/video_kicks?via=115&gclid=Cj0KCQiAz53vBRCpARIsAPPsz8Wwlt6fw3jIeMJ_MlLxP1ah7jhLrouUwOuM21Fnsn_zQ2rNLAD6ppYaAst-EALw_wcB)というミュージックビデオ納品代行サービスを展開しています。
現在は、グループ会社である [TuneCore Japan](https://www.tunecore.co.jp)経由でログインでき、Apple , Line Music , Gyao , MusicJp, Recochoku 等のストアにミュージックビデオを一括配信できます。

今回はそのサービスでの動画納品事例周りについて書きます。

## 最近のストア動画納品マスタにありがちなこと

**とにかくサイズが大きい。**

特に**Prores**というフォーマットでは5-10分で 1920x1080 でも**5-20GB**の動画になります。
さらに、4Kでは場合によっては**60GB**を超えたりします。
このProresですが、マスタとしての品質は、OTT側の配信用であるmp4などと比べてかなり安定しているため、動画プロダクションのファイルのやりとりや、配信業者さんへの納品マスタとしては好まれ、受け付けているストアも多いです。
弊社のVideo Kicksでも、変換時に安定しているという理由でストア向けの納品物のトランスコード出力としてProresを好んで使っています。

ただ、やはりサイズは大きい........


## ストアへの納品動画のサイズが大きくて困ること  

**転送料金の増大です。**
現在,VideoKicksのアーキテクチャは全てAWS (東京リージョン)上にあります。
AWSではデータのINは無料なのですが、リージョン間を跨いだデータ転送やインターネットへのOUTは料金がかかります。
東京リージョンでは現在は 1GBあたり$0.114ほどでしょうか。

動画を納品するストアが同リージョンのEC2やS3にでもない限り、納品時にこの転送料金が発生します。こちらがサービスをスケールしていく上で無視できないコストになる可能性があったため、策を練ることになりました。  

ここで出てくるのがAWSで提供されているVPSの[Lightsail](https://aws.amazon.com/jp/lightsail/) です。

## 「定額」 というLightsailの特性を掘る

<img width="180" alt="Lightsail" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85375/be911208-86a6-2750-8d23-4f06fc34c03a.png">


Lightsailには以下の特性があります。  

* 各種プランごとに**月毎のデータ転送量無料枠がある**。
* 無料データ転送量を超過すると、$0.14/GBのデータ転送量が追加でかかります。(Tokyoリージョン)
* 停止しただけではLightsailの月額プラン/稼働時間分の料金がかかる
* **インスタンスを削除するとその時点で課金されないようになる**

「この特性を用いて、各種プランを上手く使えば1TB + 2TB + ... + 7TB で1アカウントあたり毎月30TB転送料なんとかなるんじゃない?」という上司の着想があり、ちょっと考え始めてみました。
調査として色んなプランのLightsailをいくつも立ち上げた後、停止/削除し、請求への反映を確認したところ、実際に実行時間分のみの課金となっており、Outの転送料金もかかっていませんでした。

ちょっと裏技感があってやっていいものか不安でしたが、アマゾン東京本社に行ってこのあたりを何人かのエンジニアさんにお聞きしたところ、「特に問題なさそう」とのことでした。


## Lightsail経由でデータをOUTするには

「ストアへの転送をLightsail経由にする方策」を考えるにあたって、TCPやUDPのプロキシツールをLightsail上に立ち上げ、AWS Batch上の納品アプリケーション群からプロキシ経由で納品する、ということをまず考えました。
しかし、ストアによっては動画納品ツールを自前で配布しており、そこにプロキシオプションがなかったり、独自の転送プルトコルを用いたものを提供しているところもあって、そこを吸収していくのはなかなか難しそう、という判断になりました。

そこで結局は、納品アプリケーション自体をスケールするLightsailインスタンスそのものの上で実行することにしました。
要するに、AWS Batch登場以前にEC2で自前で行われていたであろうジョブキュー付きバッチ実行システムと同様のものをLightsailで作ることになったわけです。

## LightsailとSQSでオレオレAWS Batchを作った

ちなみにシステム名は **daisendan(大船団)** となっております。
(作った時点でLightsailをシンプルに船のことだと勝手に解釈していたので、いっぱいお船が立つという意味でつけました)


### 事前設定

* dockerとaws-sdk, cloudwatch-agent だけ入ってるくらいのLightsail snapshotを作っておく
* **スケーリング設定ファイル** をS3(どこでもいいけど)に格納

```json:daisendan_config.json
{
    "vm_setting_map": {
        "store1-delivery": {
            "vm_tag": "store1-delivery", // lighsailのタグやinstance名のprefixに使う
            "vm_count_max": 3, // 立ち上げるインスタンス数のMax
            "worker_count_max_per_vm": 1, // インスタンスあたりの同時実行数上限
            "lightsail_option": {
                "min_required_lightsail_bundle_id": "medium_2_0" // Lightsailプラン
            },
            "is_maintenance": false, // メンテナンス(いったんそのSQSキューのコンシューマ起動をやめる)
            "sqs_fifo_queue_name": "store1_delivery_queue.fifo" // ジョブキューとして認知するSQSキュー
        },
        "store2-delivery": {
            "vm_tag": "store2-delivery",
            "vm_count_max": 1,
            "worker_count_max_per_vm": 1,
            "lightsail_option": {
                "min_required_lightsail_bundle_id": "medium_2_0"
            },
            ....
```
設定ファイルは以上の感じになっています。
AWS Batchのコンピューティング環境設定やジョブ設定を簡単にしたイメージです。

### スケーリング設定ファイル管理コンソール

サービスの開発者向け管理画面には以下のような SQSとLightsailのモニタリング & 設定ファイルエディタがあります。

<img width="520" alt="lightsail" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85375/132ec958-c460-2490-1d5f-0c2b36179525.png">

この画面から設定ファイルを編集し、　　

* ジョブキューとして認知するSQSキュー
* Lightsailプラン、
* 立ち上げるインスタンス数
* インスタンスあたりの同時実行数上限
* メンテナンス(いったんそのSQSキューのコンシューマ起動をやめる)

を決定しています。
もちろんjson直接編集でもOK.


### 実行の流れ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85375/cba0fdf2-f622-c5a8-d538-4d54f9e4776a.png)



* (1) 管理daemonは、以下の要素を鑑みて、インスタンス立ち上げとインスタンスに与える命令の方針を決定する。
  * 現在、納品系SQSキューに格納されているメッセージの数
  * 前述したスケーリング設定ファイルによって定められた価格プランや同時実行最大数
  * 現在立ち上がっているLightsailインスタンスの数   
  
* (2) daemonがsnapshotからLightsailを立ち上げる。Lightsailのタグに```State:Initialize```を設定。インスタンスが担当するキューや並列数の情報もここに格納してしまう。
* (3) Lightsailインスタンスは起動時にdocker daemonを立ち上げ、自身のタグを```State:Ready```に書き換える
* (4) daemonは```State:Ready```なタグのインスタンスを検知すると、インスタンスのタグを```State:Running```に書き換えつつ、環境ファイル等をscpで送信する。 また、sshで対象Lightsailに規定のdocker imageをpullさせ、アプリケーションを実行する。
* (5) Lightsail/Dokcer上の納品アプリケーションは命令で与えられた同時実行数に応じてgoroutineの並列数を調整し、SQSのコンシューマ群を立ち上げ、**ストアへの動画納品を実行する**。
  (6) 自身が担当するSQSにメッセージがなくなったアプリケーションは、自身の動くインスタンスのタグを```State:ShutDown```に書き換え、インスタンスをshutdownする。
* (7) daemon側は```State:ShutDown```かつ「停止」状態にあるインスタンスを検知すると、**インスタンスを「削除」する。**


このように、インスタンスごとの固有ステートをざっくりLightsailのタグに格納しています。管理デーモンはそこから「次に移すべき状態」を判断しているわけです。
なぜ(3)-(6)を全てLightsailの起動コマンドでやらずに管理デーモンアプリやらscpやらsshやらが出てきたのかというと、**一度にやるとあまりにもデバッグしづらかった**というのが理由です。結果的には複数段階に分けてデーモンから順次命令を送るスタイルは落とし所だったかな、と考えています。

## 結果/請求

1年ほど動かしていますが、数TBの動画群をAWS外の環境へ納品した月も、請求上、**data transfer が $0で**計上されております。インスタンス自体の料金も立ち上げから削除時点までのみ課金されていました。
Lightsail全プランを適切に組み合わせるとまだまだ転送量無料上限が使える...!
枯渇するくらい納品したい。


## 課題

### Docker
* 図ではLightsail上でDockerを動かしていましたが、特定のストアさんが提供している納品クライアントツールがなぜか**docker環境だとうまいこといかないケース**があり、、結局そこは各種ツール同梱済みのLightsailのsnapshotを作っておき、普通にホスト側で実行する...というスタイルになってしまいました。無念。逆に言えばAWS Batch +Lightsail Proxy方面を模索してなくて助かったのかも。
* 自前Batchもどきを作ってしまいましたが、Lighsailをk8sのworker node として動かす、みたいなことも頑張れば可能らいしいので、そのうち検証してみたいと思います。

### スナップショットの治安
* Lightsailの**snapshotがアカウントやリージョンをまたぐことが現時点でできない**ようなので、今後いろんな地域で展開していくときに適切に同じものを作らないといけません。それなりにIaC頑張らないとなあ、と考えています。

### プラン選定の最適化
* まだまだ今は転送料無料枠があるので、Lightsailプランごとの現在のデータout量をしっかり取得/保存していないのですが、**今後は自動で無料枠のあるプランを選択していく機構**が欲しいかな、と考えています。
* たまたま引っかかっていないだけで、プランごとのインスタンス同時実行上限もあるらしい?のでその辺も。