Fargateでradikoを録音してS3に保存するCloudFormation

前にdockerでradikoを録音するようにした。
これを定期実行するためにVPSを借りているのだが、録音は1日のごくわずかな時間しか行わないので、録音していない時間は余計なコストがかかってしまう。また低スペックのVPSのためか、複数番組を同時に録音すると失敗することがある(まれに)。

Fargateでは実行している間だけしか金がかからないので安いはず。それに同時録音の問題も起きないだろう。というわけでFargateに乗り換えてみることにした。

やりたいこと

  • Fargateを使ってradikoを録音するスクリプトを定期実行する
  • 音声ファイルはS3に保存する
  • 一連の構成はCloudFormationによって管理する

前提条件

  • dockerコマンドが入っている
  • AWSアカウントを持っている
    CloudFormationでIAMの設定をしたりするので、強い権限が必要。
  • AWS CLIが入っている
  • S3の保存先バケットが作成されている
  • 録音したい番組は関東エリアで放送されているもの
    Fargateが東京にしかないので。(radikoプレミアムに対応したスクリプト使えばいけるはず。)

できたもの

docker_rec_radiko

前に作ったやつにFargate版を追加した。CloudFormationはcfディレクトリ下にある。

作業手順

ECRにDockerイメージを登録

  1. リポジトリ作成
    aws ecr create-repository --repository-name <REPOSITORY_NAME>
    

    <REPOSITORY_NAME> には好きなリポジトリ名を入れる(たとえば docker_rec_radiko)。
    結果としてjsonが返る。その中の repositoryUri をあとで使う。

  2. dockerイメージをビルド・タグ付け

    docker build . -t <REPOSITORY_URI>
    

    <REPOSITORY_URI> には上で取得した repositoryUri を入れる。

  3. docker login認証コマンド文字列を取得

    aws ecr get-login --no-include-email
    
  4. ログイン
    上で取得したdocker login認証コマンド文字列をそのまま実行。
    これでECRにpushできるようになる。

  5. dockerイメージをECRにpush

    docker push <REPOSITORY_URI>
    
  6. ECRに登録されたことを確認
    AWSのコンソールを見てもいいが、コマンドでもわかる。

    aws ecr describe-images --repository-name <REPOSITORY_NAME>
    

CloudFormationの設定

各yamlファイルには Parameters セクションがあり、ここで名前などを設定している。基本的に何も変えなくてもいけるはずだが、問題がある場合は変更する。ただしタスク・スケジュールのyamlは修正必要。
また、yamlファイルが4つあるが、ここまで分ける必要はないかもしれない。

  1. ネットワーク(network.yaml)
    VPC、サブネット、セキュリティグループを作成。さらにゲートウェイやルートテーブルなどなども作成した。あれこれ設定しているが、たぶんこれが必要最小限のはず…。CIDRとかは適当に設定しているので問題があれば変える。

  2. ロール(roles.yaml)
    IAM関連はCloudFormationでなく、コンソールで管理すれば十分な気もする。今回はECSタスクを実行するロールなどの作成で試行錯誤していたらカオスになったのでCloudFormationで管理するようにした。

  3. ECSクラスタとLambda(cluster.yaml)
    ここで作成するLambdaは、ECSタスクを実行するだけのもの。本当はCloudWatchのイベントでECSタスクを実行したいのだが、現在はまだCloudFormationでその設定ができないようなので、間にLambdaを挟んでいる。

  4. ECSタスクとスケジュール(task.template.yamlをtasks/mytask.yamlとしてコピーして修正)
    Parametersセクションを必ず修正する。ECRのイメージ、cronパターン、放送局と録音時間、S3バケットなどを指定する。このyamlファイル1つがcronの1行に対応するイメージ。なので番組をいくつも録音したいときはその数だけyamlファイルを作る。
    CPUやメモリは現在最小限にしているが、番組によっては足りないかも。

    • ECSCommand
      <放送局ID>, <録音時間(分)>, <ファイルプレフィクス> の形式。放送局IDはここ参照。
    • ECSTaskSchedulerPattern
      cronパターンを書く。癖があるのでドキュメント参照。
      絶対にUTCで書く! 日本時間での記述には対応していない(めんどくせえ)。
    • ECSTaskName
      タスク名を書く。なんでもいいが、わかりやすく短い名前にした方がいい。
    • ImageName
      ECRで登録したイメージ名。
    • S3BucketName
      あらかじめ作成したS3バケット名を書く。

CloudFormationでリソース作成

ここではAWS CLIで実行する場合を書く。コンソール画面からそのままやっても問題ない。
が、いずれにせよ作成する順番はこの通りにすること(ネットワークとロールは入れ替え可能)。ネットワークとロールが作成完了したあとでないとECSクラスタなどを作成できない。
以下ではスタック名は適当につけている。

  1. ネットワーク
    aws cloudformation create-stack --stack-name docker-rec-radiko-network-stack --template-body file://./cf/network.yaml
    
  2. ロール
    IAM関連なので、コンソール画面で作成するときにはチェックボックスに確認のチェックをする作業がある。AWS CLIでもそれに対応するオプションをつける。

    aws cloudformation create-stack --stack-name docker-rec-radiko-roles-stack --template-body file://./cf/roles.yaml --capabilities CAPABILITY_NAMED_IAM
    
  3. ECSクラスタとLambda
    aws cloudformation create-stack --stack-name docker-rec-radiko-cluster-stack --template-body file://./cf/cluster.yaml
    
  4. ECSタスクとスケジューラ
    aws cloudformation create-stack --stack-name docker-rec-radiko-task-stack --template-body file://./cf/tasks/mytask.yaml
    
  5. 確認
    ECSタスクまで作成成功すれば録音できるようになっているはず。指定した時間にECSでタスクが実行され、録音終了すればS3に保存される。ログはCloudWatchで見れる。

課題とか

  • Lambdaを使わずに済ませたい
    これはAWSが対応してくれることを待つしかないようだ。
  • 日本時間でcronを書きたい
    これもAWSが対応するのを待つしかない(たぶんされない)。
    現在はUTCで書くしかない。計算が面倒。
  • CloudFormationスタック間の情報受け渡し
    今回はスタックで作成したリソース(たとえばサブネットやロール)をパラメータストアに入れることで、他のスタックから参照できるようにしている。
    だがこれだとスタックを作り直すと、それに依存するスタックも作り直さないといけない(パラメータストア内の値は、作り直す前のものなので)。
    たとえばネットワークやロールのスタックを作り直すと、クラスタやタスクのスタックも作り直さないといけない。
    何かうまい方法があればいいのだが…。
    あとProjectNameなどを複数のスタックでそれぞれ定義し直しているのも気持ち悪い。
  • CloudFormationスタック分割のベストプラクティスがわからない
    今回は練習の意味もあり、わりと細かく分割した。
    本来ならどのくらい粒度で分割するのがいいかよくわからない。
    たとえばECSタスクは1つ1つ別のスタックにしたが、タスクはすべて1つのスタックにまとめてしまうというのもありだと思う。

参考

ECR

CloudFormation

公式

各種記事

スケジュール関連

VPC

ローカルに作ったgitリポジトリをGithubに上げる

ローカルでgitリポジトリを作って何度かコミットし、それをGithubに上げた。
このときcontributionsがどうなるかわからなかったが、結局ちゃんとコミットの日が緑になった。

方法

  1. Githubに新しいリポジトリを作る
    普通に”New repository”で作成。
    このときREADMEや.gitignore、LICENSEも作れるが、既存のリポジトリと衝突するのが怖いので作らなかった。
  2. コマンドを打つ
    上でリポジトリ作ると、その次の画面で注意書きが出る。
    その中に既存リポジトリをpushする方法が書いてある。その通りやればいい。

    Quick_setup

    git remote add origin https://github.com/<USERNAME>/<REPOSITORY_NAME>.git
    git push -u origin master
    
  3. Githubを見て、うまくpushされたか確認する
    contributionsもちゃんと緑になっていた。実はこれが一番心配だったのでほっとした。
    “Created 1 repository”は今日の日付だが、過去のコミットもcontributionsとして扱われているようだ。

その他の方法

Githubの右上の「+」を押すと”Import repository”があり、ここから既存のリポジトリ(gitでもsvnでも)をインポートできるよう。
だがここではURLを指定しないといけない。今回はローカルのリポジトリだったのでできなかった。

Import repository

参考

radiko録音スクリプトのdocker化

radikoを録音するシェルスクリプトmatchy2/rec_radiko.shをdockerで動かすようにした。他の方のスクリプトを使うだけだが…。

できたもの

docker_rec_radiko

あとはCRONでdocker runすればいい(docker runはradiko_docker_run.shの中でやっている)。
録音時にdocker runするか、それともコンテナは起動しっぱなしにしてその中でCRONで録音するか迷った。(つまりCRONでコンテナを起動するか、コンテナの中でCRONするか。)だが録音しないときはこのコンテナは何もしないので、前者でいいということにした。

作った理由

環境構築が面倒だから。

関東を離れてもTBSラジオなどが聞きたいので、ConoHaで東京リージョンのVPSを借り、そこでこのシェルスクリプトを動かすようにした。スクリプトではffmpegなど必要となるツールがあるのでそれらをインストールしないといけない。VPSではいろいろなOSの中からCentOSを(サーバならCentOSだろという固定観念から)選んだが、CentOSだとこのインストール作業がやや面倒なのでdockerにした。

radikoのプレミアム会員になれば全国どこの局でも聞けるというのは知ってる。借りたサーバでは他にも何かするつもり。

もろもろ

  • 時刻設定が面倒だった
    はじめubuntu:16.04のdockerイメージを使っていたが環境変数 TZAsia/Tokyo にしてもJSTにならないようだった。他にも設定の方法はあるのだろうが、ubuntuはやめてdebianのイメージを使うことにした。debianだとこれでいける。
  • dockerでのマウント方法
    新しい指定方法ができていた(docker 17.06からなので新しくもない…知らなかっただけ)。
    ホストのディレクトリ[ファイル]をコンテナのディレクトリ[ファイル]にマウントさせたいとき、以下が同じ意味になる。

    # -v で指定する方法
    $ docker run -v <PATH_IN_HOST>:<PATH_IN_CONTAINER> <IMAGE_NAME>
    # --mount で指定する方法
    $ docker run --mount type=bind,source=<PATH_IN_HOST>,target=<PATH_IN_CONTAINER> <IMAGE_NAME>
    

    -v では、コロンの前と後どっちがホストのパスだっけと迷うことがよくあった。 --mount だといちいちsourceなどと書くのでわかりやすい。
    またvolumeのマウントでも、 -v だとホストディレクトリ[ファイル]のマウントと区別しにくかったが、 --mount ではtypeが変わるのでわかりやすい。

    # -v で指定する方法
    $ docker run -v <VOLUME_NAME>:<PATH_IN_CONTAINER> <IMAGE_NAME>
    # --mount で指定する方法
    $ docker run --mount type=volume,source=<VOLUME_NAME>,target=<PATH_IN_CONTAINER> <IMAGE_NAME>
    

参考