Passion make things more better

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

小〜中規模程度の Flutter プロジェクトにおけるフォルダ構成について

フォルダ構成について

プロジェクトの初期やグロースするタイミングでは結構大きめの変更なども多いので、「極力シンプルに、かつ最低限のルールは守られるように。」という観点で、以下のようなフォルダ構成で開発を始めます。

lib
- ui
  - component/
  - page_name/
    - hoge_page.dart(ページそのもの)
    - hoge_view_model.dart(ファイル名の通り。providerのChangeNotifierを継承したクラス)
    - fuga.dart(hoge_page.dartで使用するviewなど)
- model/
- utils/
- constants/

もっときっちりやるのであれば

開発の人数が増えてきたり、プロダクトとして安定してきたタイミングなどで、よりきっちりやるのであれば、wasabeef/flutter-architecture-blueprints を参考にしてやろうと思っています(まだ私のプロダクトは検証が多く、頻繁に大きめの変更が入ったりしているのでいつになるのかはわかりませんが....)。

参考

Nuxt.jsで作成したSPAをS3 + CloudFrontにデプロイする

環境

  • Node.js 12.18.0
  • Nuxt.js 2.14.5

手順

基本的にはNuxt.jsの公式ドキュメント通り進めてください。 しかし、そのままだとエラーが発生してしまいます。以下でその解消方法について説明します。

ポリシーの変更

AWS上で作成するポリシーを以下のように変更してください。 example.comのところはS3のバケット名に変更してください。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": ["arn:aws:s3:::example.com"]
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:PutObjectAcl",
        "s3:GetObject",
        "s3:GetObjectAcl",
        "s3:DeleteObject",
        "s3:ListMultipartUploadParts",
        "s3:AbortMultipartUpload"
      ],
      "Resource": ["arn:aws:s3:::example.com/*"]
    },
    {
      "Effect": "Allow",
      "Action": [
        "cloudfront:CreateInvalidation",
        "cloudfront:GetInvalidation",
        "cloudfront:ListInvalidations"
      ],
      "Resource": "*"
    }
  ]
}

gulpfile.jsの変更

gulpfile.jsheadersの部分を以下のように変更してください。

  headers: {
    'x-amz-acl': 'private',
    /* 'Cache-Control': 'max-age=315360000, no-transform, public', */
  },

以上の変更を加えることで、適切にデプロイができるようになります。

直接アクセスするとエラーが発生する

Nuxt.jsで作ったSPAをS3 + CloudFrontの環境にデプロイすると、トップ以外に直接アクセスをした場合にエラーが発生してしまいます。 それを回避する方法としては、CloudFrontのカスタムエラーレスポンスの設定を行います。

CloudFrontのErrorPagesのタブにて以下の情報でカスタムエラーレスポンスを作成してください。

  • HTTP Error Code -> 403
  • Error Caching Minimum TTL -> 0
  • Response Page Path -> /
  • HTTP Response Code -> 200

参考

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

AWSVPC アドレスでよく使用されるのは以下の範囲となります。

  • 10.0.0.0 ~ 10.255.255.255 (10.0.0.0/8)
  • 172.16.0.0 ~ 172.31.255.255 (172.16.0.0/12)
  • 192.168.0.0 ~ 192.168.255.255 (192.168.0.0/16)

今回は VPC アドレスを172.16.0.0/16に設定して解説をしてきます。

Subnet

Subnet に関しては、奇数を Private(ネットワーク内部との通信のみ)、隅数を Public(ネットワーク外部との通信あり) にするなどのルールを設定しておくとわかりやすくなります。今回は Private な Subnet を奇数に、Public な Subnet を隅数にします。 数字以外にも AWS のネットワークには Availability Zone が存在するので、a と c の2つをそれぞれ作成します。

例として以下の Subnet を作成します。

  • {サービス名}-private-a-{環境(prod や dev など)}
    • 172.16.1.0/24
  • {サービス名}-private-c-{環境(prod や dev など)}
    • 172.16.3.0/24
  • {サービス名}-public-a-{環境(prod や dev など)}
    • 172.16.2.0/24
  • {サービス名}-public-c-{環境(prod や dev など)}
    • 172.16.4.0/24

Internet Gateway

インターネットゲートウェイは、xxxxxx

{サービス名}-{環境(prodやdevなど)}の名前で新たに Internet Gateway を作成してください。 その後、作成したインターネットゲートウェイを使用したい VPC にアタッチしてください。

Route Table

ルートテーブルは、サブネット内の通信のルールを設定するものです。

  • {サービス名}-public
  • {サービス名}-private

今回は Public と Private の 2 つを作成します。

Subnet の紐付け

作成したルートテーブルに、利用したいサブネットを紐付けます。 今回は、外部との通信を行うものを Public、そうでないものを Private に割り振ってください。

Internet Gateway の紐付け

外部に公開するルートテーブルにインターネットゲートウェイを紐付けてください。 ルートの編集より、以下の情報にて新しいルートを作成してください。

参考