Passion make things more better

Ruby on Rails / React.js / Swift / AWS / Docker

S3 + Lambda + Transcoderを使用したサーバーレスな動画変換

AWSのS3, Lambda, Transcoderを使用したサーバーレスな動画変換の仕組みの作り方を紹介します。 処理の流れとしては以下のようになります。

  1. S3のBucketに動画ファイルをアップロード
  2. S3に設定したイベントからLambda関数を実行する
  3. Lambda関数内でTranscoderが実行され、動画をMP4形式に変換およびサムネイル画像の作成
  4. 変換した動画および作成されたサムネイル画像を別のS3のBucketにアップロード

Transcoderの設定

基本情報

  • Pipeline Name
    • XXXXXVideoTranscoding
  • Input Bucket
    • 元ファイルをアップする用のS3 Bucketを選択
  • IAM Role
    • Create console default roleを選択
      • すでにElastic_Transcoder_Default_Roleがあればそれを選択

Configuration for Amazon S3 Bucket for Transcoded Files and Playlists

  • Bucket
    • 変換したファイルをアップする用のS3 Bucketを選択
  • Storage Class
    • Standardを選択

Configuration for Amazon S3 Bucket for Thumbnails

  • Bucket
    • 変換したファイルをアップする用のS3 Bucketを選択
  • Storage Class
    • Standardを選択

Lambdaの設定

関数の作成を押して、「一から作成」を選択してください。

ポリシーおよびロールの作成

Lambda関数を作成する際に、Roleを指定する必要があります。事前に必要なポリシーおよびロールを作成しておきます。

ポリシーの作成

以下のJSONを入力して「LambdaElasticTranscoder」という名前でポリシーを作成します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "elastictranscoder:Read*",
                "elastictranscoder:List*",
                "elastictranscoder:*Job",
                "elastictranscoder:*Preset"
            ],
            "Resource": [
                "arn:aws:elastictranscoder:*:*:*"
            ]
        }
    ]
}

ロールの作成

先ほど作成したLambdaElasticTranscoderのポリシーを選択し、「lambda_elastictranscoder_exec_role」という名前のロールを作成します。

基本的な情報

  • 関数名
    • XXXXXVideoTranscoding
  • ランタイム
    • Node.js 12.x
  • 実行ロール
    • 既存のロールより「lambda_elastictranscoder_exec_role」を選択

これらを入力して、「関数の作成」をクリックしてください。そうすると関数が作成されます。

コード

実行するコードは以下を参考にしてください。以下を実行すると、指定したS3 Bucketに「MP4形式に変換された動画」と「サムネイル画像」がアップされます。

'use strict';

var AWS = require('aws-sdk');
var s3 = new AWS.S3({
    apiVersion: '2012-09-25'
});

var eltr = new AWS.ElasticTranscoder({
    apiVersion: '2012-09-25',
    region: 'ap-northeast-1'
});

exports.handler = function(event, context) {
    console.log('Executing Elastic Transcoder Orchestrator');
    var bucket = event.Records[0].s3.bucket.name;
    var key = event.Records[0].s3.object.key;
    var pipelineId = '<PipelineのIDを入力してください>';
    if (bucket !== '<元のファイルをアップするS3のBucket名を入力してください>') {
        context.fail('Incorrect Video Input Bucket');
        return;
    }
    var srcKey =  decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, " ")); //the object may have spaces
    var newKey = key.split('.')[0];
    var params = {
        PipelineId: pipelineId,
        Input: {
            Key: srcKey,
            FrameRate: 'auto',
            Resolution: 'auto',
            AspectRatio: 'auto',
            Interlaced: 'auto',
            Container: 'auto'
        },
        Outputs: [{
            Key: newKey + '.mp4',
            ThumbnailPattern: newKey + '-{count}',
            PresetId: '1351620000001-000010',
            Watermarks: [{
                InputKey: 'watermarks/logo.png',
                PresetWatermarkId: 'BottomRight'
            }],
        }]
    };

    console.log('Starting Job');
    eltr.createJob(params, function(err, data){
        if (err){
            console.log(err);
        } else {
            console.log(data);
        }
        context.succeed('Job well done');
    });
};

S3の設定

  1. 元ファイル
  2. 変換したファイル

を保存するために、2つのBucketを作成します。作成時に特殊な設定は必要ありません。

ウォーターマーク用の画像

動画を変換する際に、動画自体にウォーターマークを入れることができます。 ウォーターマークに使用したい画像を、元ファイルのBucket内にwatermarks/logo.pngという名前でアップをしてください。

イベントの追加

元ファイルをアップするS3のBucketのプロパティからイベントの設定を行います。

  • 名前
    • VideoTranscoding
  • イベント
    • 「すべてのオブジェクト作成イベント」を選択
  • 送信先
    • 「Lambda関数」を選択
  • Lambda
    • 先ほど作成したLambda関数名を選択

以上の設定を終えて、S3のBucketに動画ファイルをアップロードすると処理が実行され、変換された動画およびサムネイル画像が作成されるのが確認できるようになります。


参考

RailsをAWS Fargateにデプロイする (AWS CLI / ECS CLIを使用)

AWS CLIとECS CLIを用いてRailsをFargateにデプロイする方法について書きます。 イメージのビルドや自動デプロイの仕組みはまた別で書こうと思っています。今回は、MacOSで実行することを想定しています。

目次

  • CLIツールのインストール
  • IAMアカウントおよびロールの作成
  • アカウント設定 / ESC設定
  • ECRの作成 & Push
  • クラスタの作成
  • デプロイについて
  • スケールについて
  • DB Migration等のコマンドについて

CLIツールのインストール

ターミナルでAWSを操作できるように、CLIツールをインストールします。今回はbrewを使ってインストールします。 他の方法は以下を参照してください。

# インストール
$ brew install awscli

# 確認
$ aws --version

# インストール
$ brew install amazon-ecs-cli

# 確認
$ ecs-cli --version

IAMアカウントおよびロールの作成

ターミナルでAWSを操作するためのアカウントを作成します。 こちらは、画面上で行います。IAMのページにアクセスして行ってください。

グループの作成

初めにポリシー(アカウントがどの操作を行えるかを制限するもの)を紐づけたグループを作成します。

今回の要件で必要となるポリシーは以下です。

  • AmazonEC2ContainerRegistryFullAccess
  • AmazonEC2ContainerServiceFullAccess
  • AmazonECSTaskExecutionRolePolicy
  • CloudWatchLogsFullAccess

ユーザーの作成

続いてIAMユーザーを作成します。先ほど作ったグループを紐づけてユーザーアカウントの作成を完了させてください。完了したら、以下を必ず控えておいてください。

  • アクセスキーID
  • シークレットアクセスキー

ロールの作成

最後にロールの作成を行います。Fargateでは、ecsTaskExecutionRoleが必要となります。以下を参考にして、作成を行ってください。

Amazon ECS タスク実行 IAM ロール

アカウント設定 / ESC設定

ターミナル上で操作するための情報が出揃ったので、続いてAWSのアカウントの設定およびESCを操作するための設定を行います。

AWS CLIの設定

先ほど作成したIAMの認証情報を設定します。PROFILE_NAMEのところには、サービス等の名前を入れておくと管理しやすいと思います。

$ aws configure --profile PROFILE_NAME

AWS Access Key ID [None]: ACCESS_KEY_ID
AWS Secret Access Key [None]: SECRET_ACCESS_KEY
Default region name [None]: ap-northeast-1
Default output format [None]: json

これを実行すると、~/.aws以下にconfigcredentialの2つのファイルが作成されます。

aws configureだけを実行して設定をするパターンがありますが、それだと複数のアカウントで運用する場合に切り替えが面倒になるので、私はprofile名を設定して運用しています。

ECS CLIの設定

AWS CLIと同様に、ECS CLIの設定も行います。CLUSTER_NAMECONFIG_NAMEには、PROFILE_NAMEと同様にサービス等の名前を入れておくと管理しやすいと思います。

※もし本番環境とテスト環境を分ける場合などはそれも含めておくと管理しやすくなります。

# 認証情報の設定
$ ecs-cli configure profile --profile-name PROFILE_NAME --access-key ACCESS_KEY_ID --secret-key SECRET_ACCESS_KEY

# Fargateに関する設定
$ ecs-cli configure --cluster CLUSTER_NAME --default-launch-type FARGATE --region ap-northeast-1 --config-name CONFIG_NAME

ECRの作成 & Push

ビルドしたDockerイメージはECRにPushするようにします。 ECRでリポジトリを作成し、URLを控えてください。

docker-compose.ymlの作成

最新のECS CLIであれば、Docker ComposeのVersion3の記述を使うことができます。

以下のように作成することができます。

version: '3'
services:
  app:
    build:
      context: ./
      dockerfile: Dockerfile
    image: ECR_REPOSITORY_URL
    ports:
      - "3000:3000"
    environment:
      RAILS_ENV: production
    logging:
      driver: awslogs
      options:
        awslogs-group: SERVICE_NAME
        awslogs-region: ap-northeast-1
        awslogs-stream-prefix: PREFIX

この場合、ECS上ではbuildは無視されます。ローカルでのbuildのために定義しています。

イメージの作成 ~ ECRへのPush

以下のコマンドを実行してECRへのPushを行うことができます。

# イメージビルド
$ docker-compose -f docker-compose.yml build

# AWS ECRへのログイン
$ $(aws ecr get-login --no-include-email --region ap-northeast-1 --profile PROFILE_NAME)

# ECRへのPush
$ docker push ECR_REPOSITORY_URL:latest

クラスタの作成

VPC / Subnet / Security Groupの作成

クラスタを作成するにあたり、VPC、Subnet、Security Groupを事前に作成しておく必要があります。 クラスタを作成する際に指定をしなければ自動で作成されるのですが、自分で作成しておいた方が管理しやすいので、私は事前に作っておくようにしています。

VPCとSubnetについては、AWSネットワークルールを参考にして作ってみてください。 Fargateで配置するコンテナはPublicなSubnet(インターネットゲートウェイでインターネットとつながっている状態)に配置する必要があるので、そこだけ注意してください。

コマンド実行

クラスタを作る際には、ecs-cli upコマンドを使用します。PROFILE_NAMECONFIG_NAMEに関しては、「アカウント設定 / ESC設定」で指定したものと同じにしてください。

$ ecs-cli up \
    --security-group SECURITY_GROUP_ID \
    --vpc VPC_ID \
    --subnets SUBNET_ID_1,SUBNET_ID_2 \
    --aws-profile PROFILE_NAME \
    --ecs-profile PROFILE_NAME \
    --cluster-config CONFIG_NAME

デプロイについて

ロードバランサーとターゲットグループの作成

サービスを起動する際に、ロードバランサーまたはターゲットグループを指定する必要があるので、事前に作成しておきます。作成時の注意としては、ターゲットグループを作成する際に、「ターゲットの種類」の項目で「IP」を指定するようにしてください。

ecs-params.ymlの作成

デプロイをする際に、どのVPC, Subnetに配置するか等を指定する必要があります。それらを指定するためにecs-params.ymlというファイルを作成します。

subnet IDとsecurity Group IDを自分で作成したものに置き換えてください。

version: 1
task_definition:
  task_execution_role: ecsTaskExecutionRole
  ecs_network_mode: awsvpc
  task_size:
    mem_limit: 1.0GB
    cpu_limit: 512
run_params:
  network_configuration:
    awsvpc_configuration:
      subnets:
        - "subnet ID 1"
        - "subnet ID 2"
      security_groups:
        - "security group ID"
      assign_public_ip: ENABLED

デプロイコマンド

デプロイをするために、ecs-cli compose service upコマンドを使用します。

$ ecs-cli compose \
    --project-name PROJECT_NAME \
    --file docker-compose.yml \
    --ecs-params ecs-params.yml \
    service up \
    --target-group-arn TARGET_GROUP_ARN \
    --container-name app \
    --container-port 3000 \
    --cluster CLUSTER_NAME \
    --aws-profile PROFILE_NAME \
    --ecs-profile PROFILE_NAME \
    --cluster-config CONFIG_NAME \
    --create-log-groups \
    --force-deployment

これでクラスター内にサービスと1つのタスクが起動するようになります。 もしサービスがすでに作成されていた場合は、タスクのみが新規で作成され、立ち上がり次第古いものと入れ替わります。

スケールについて

先ほどのコマンドでデプロイを行なった場合に、タスクの数は1となります。ここからタスクの数をさらに増やしたいという場合は、スケールさせるためのコマンドを実行する必要があります。

$ ecs-cli compose \
    --project-name PROJECT_NAME \
    service scale 2 \
    --cluster CLUSTER_NAME \
    --aws-profile PROFILE_NAME \
    --ecs-profile PROFILE_NAME \
    --cluster-config CONFIG_NAME

こちらを実行するとタスクが2つに増え、自動で不足している分のタスクが起動します。

DB Migration等のコマンドについて

Docker Composeファイルの作成

Dockerは1コンテナ1プロセスが原則ですので、この状態だとrake db:migrate等のコマンドを実行することができません。なので、そういったタスクを実行するためのDocker Composeファイルを作成します。

例として、rake db:migrateを実行するためのDocker Composeファイル(db-migration.yml)を作成します。 app用のRailsイメージのコマンドを上書きすることで同じものを利用することができます。

version: '3'
services:
  app:
    image: ECR_REPOSITORY_URL
    command: ["bundle", "exec", "rails", "db:migrate"]
    environment:
      RAILS_ENV: production
    logging:
      driver: awslogs
      options:
        awslogs-group: SERVICE_NAME
        awslogs-region: ap-northeast-1
        awslogs-stream-prefix: PREFIX

コマンド

先ほど作成したdb-migration.ymlを指定して、タスクを実行します。ここで、PROJECT_NAMEの部分は通常のappとは別の名前にしてください。もし同様の名前で実行してしまった場合に、app用のタスク定義が変わってしまい、アプリケーションが起動しなくなってしまいます。

$ ecs-cli compose \
    --project-name PROJECT_NAME \
    --file db-migration.yml \
    --ecs-params ecs-params.yml \
    up \
    --cluster CLUSTER_NAME \
    --aws-profile PROFILE_NAME \
    --ecs-profile PROFILE_NAME \
    --cluster-config CONFIG_NAME

参考

AWSネットワークルール

個人メモ。

VPCについて

各環境毎(Prodiction / Stating / Development)にVPCを作成する。 /16が推奨。

10.0.0.0/16をVPCのアドレスとして指定した場合。

  • Production -> 10.0.0.0/16
  • Staging -> 10.1.0.0/16
  • Development -> 10.2.0.0/16

サブネットについて

以下の条件の掛け合わせで分割する。 /24が推奨。

  • 運営するサービス
  • Public or Private
  • Availablity Zone

10.0.0.0/16をVPCのアドレスとして指定した場合。

  • NAME Production Public A -> 10.0.1.0/24
  • NAME Production Public C -> 10.0.2.0/24
  • NAME Production Private A -> 10.0.3.0/24
  • NAME Production Private C -> 10.0.4.0/24

ルートテーブル

  • NAME Production Private -> Private通信のみのもの
  • Name Production Public -> Internet Gatewayを割り当てたもの

2つを用意してそれぞれをサブネットにアタッチする。

参考

RailsでReact.jsを使う時のミニマムの設定

RailsでReact.jsを使う時に使用するミニマムの設定を紹介します。

構成

  • Railsのプロジェクト直下にfrontというディレクトリを用意する
  • webpackでコンパイルした際に/assets/javascripts/webpack以下にエントリファイル毎に吐き出されるようにする

ライブラリのインストール

開発環境のみ入れるもの

# webpack
$ npm install -D webpack webpack-cli

# babel
$ npm install -D @babel/core @babel/preset-env @babel/preset-react 

# sass
$ npm install -D node-sass 

# loader
$ npm install -D babel-loader sass-loader css-loader file-loader style-loader url-loader 

本番環境で使用するもの

$ npm install react react-dom

webpackの設定

webpack.config.js

const path = require('path');

module.exports = [
  {
    entry: {
      'messageEdit': './src/javascripts/entries/articles/edit.js'
    },
    output: {
      path: path.resolve(__dirname, '../app/assets/javascripts/webpack'),
      filename: "[name].js"
    },
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env', '@babel/react']
            }
          }
        },
        {
          test: /\.(css|scss)$/,
          use: ['style-loader', 'css-loader?modules', 'sass-loader']
        },
        {
          test: /\.(png|jpg|svg)$/,
          loader: 'url-loader'
        }
      ]
    },
    resolve: {
      extensions: ['.js', '.css', '.scss']
    }
  }
]

package.json

以下の内容を追記する。

"scripts": {
    "watch": "webpack -w --mode development",
    "build": "webpack --mode production"
}

RailsのAssets構成(css / javascript)について

Railsでプロジェクトを作る際のassets周り(css / javascript)の構成等についてまとめました。 ※yarnを使ったパターンは、別途まとめたいと思います。

Boostrapの導入

bootstrap-sassというgemを利用します。Gemfileに以下を記述し、bundle installを行ってください。

gem 'bootstrap-sass', '~> 3.3.7'
gem 'jquery-rails'

stylesheetの構成に関して

├── application.scss
├── base.scss
├── mixin.scss
├── pages(特定のページのみに適用したいSCSSを配置するフォルダ)
│   └── sample.scss(ファイルが無いとエラーが起きるため、仮で配置しています)
├── partials(共通で使用するパーツを配置するフォルダ)
│   └── _texts.scss(テキストサイズ・色に関するCSS)
└── variables.scss

また、application.scssbase.scsspartials/_text.scssの内容は、以下を参考にしてください。

application.scss

@import "bootstrap-sprockets";
@import "bootstrap";

@import "variables";

@import "mixin";

@import "base";

@import "partials/*";
@import "pages/*";

base.scss

body {
  font-size: 14px;
  font-weight: 300;
  word-break: break-word;
  line-height: 1.6;
}

h1,h2,h3,h4,h5,h6 {
  line-height: 1.5;
  letter-spacing: 1.5px;
  margin: 0 0 20px 0;
}

h1 { font-size: 32px; }
h2 { font-size: 24px; }
h3 { font-size: 21px; }
h4, h5, h6{ font-size: 18px; }

.section {
  margin-bottom: 80px;
}

.item {
  margin-bottom: 60px;
}

.main {
  padding-top: 80px;
}

partials/_texts.scss

.text-ex-large {
  font-size: 18px;
}

.text-large {
  font-size: 16px;
}

.text-normal {
  font-size: 14px;
}

.text-small {
  font-size: 12px;
}

.text-ex-small {
  font-size: 10px;
}

.text-bold {
  font-weight: bold;
}

javascriptsの構成に関して

├── application.js
├── libs(ライブラリを配置するフォルダ)
│   └── sample.js
├── pages(特定のページのみに適用したいJSを配置するフォルダ)
│   └── sample.js.coffee
└── partials(共通で使用するパーツを配置するフォルダ)
    └── sample.js.coffee

ページ毎にjavascriptを呼び出せるように、config/initialiers/assets.rbに以下を追記してください。

Rails.application.config.assets.precompile += %w( pages/*.js )

また、application.jsの内容は以下を参考にしてください。

application.js

//= require jquery
//= require jquery_ujs
//= require bootstrap-sprockets
//= require_tree ./libs
//= require_tree ./partials
//= require_tree ./pages