CloudFrontの署名付きURLをRubyで発行する

概要

画像コンテンツを扱うアプリのバックエンドをRails/EC2/S3/CloudFrontで作った。
コンテンツ保護のためCloudFrontの署名付きURLを使ったが、何やらAWSのSDKとやらが必要だったのでそのメモ。

前提

S3/CloudFrontの設定と署名用のキーペアの作成が終わっていること。
下記を参考にさせてもらったらバッチリできた。

CloudFront+S3で署名付きURLでプライベートコンテンツを配信する
https://dev.classmethod.jp/cloud/aws/cf-s3-deliveries-use-signurl/

しかし、肝心の署名付きURLの発行しようとしたらRubyのサンプルがなくて詰んだ。
泣きながら初心者なりに試行錯誤して見ると一応動いた。

やったこと

AWS SDK をGemで追加して

Gemfile
gem 'aws-sdk-cloudfront'

キーペアのIDとダウンロードした秘密鍵を指定し、署名付きURLを発行する。

url_signer.rb
signer = Aws::CloudFront::UrlSigner.new
  key_pair_id:      "APKA9ONS7QCOWEXAMPLE", # 鍵ID
  private_key_path: "tmp/pk-cloudfront.pem" # 鍵ファイル
)
# 5分間有効な署名付きURLを発行
signed_url = signer.signed_url(
  "https://d111111abcdef8.cloudfront.net/images/image.jpg",
  expires: Time.now.getutc + 5.minute
)

# 発行された署名付きURL
# signed_url = https://d111111abcdef8.cloudfront.net/images/image.jpg?Expires=123456789&Signature=nitfHRCrtziwO2HwPfWw~yYDhUF5EwRunQA-j19DzZrvDh6hQ73lDx~-ar3UocvvRQVw6EkC~GdpGQyyOSKQim-TxAnW7d8F5Kkai9HVx0FIu-5jcQb0UEmatEXAMPLE3ReXySpLSMj0yCd3ZAB4UcBCAqEijkytL6f3fVYNGQI6&Key-Pair-Id=APKA9ONS7QCOWEXAMPLE

これだけだ。

発行したURLでアクセスするとちゃんとコンテンツにアクセスできたし、有効期間(5分)が過ぎた後はちゃんとアクセスが拒否された。
ちなみにexpiresではなくpolicyも指定できるので、もっと複雑な条件も指定できるらしい。

参考

続きを読む

AWSのECSでGPUを使う方法

はじめに

AWSのECSでGPUを使う場合、ECS側ではGPU搭載のインスタンスはデフォルトでは選べません。今のところ自分自身でどうにかするしかありません。その方法を記載します。

1. ECSでクラスターを作成

ECSで新規にクラスターを作成します。ここで設定するクラスター名は後ほど使います。今回の説明ではecs-clusterという名前で説明します。

2. ECSで動かせるGPUのAMIを作成する

AWSの公式ページにあるので、こちらの手順に沿ってAMIの作成まで行います。
GPUワークロードのAMIの作成
こちらの手順の最後にec2-userでsudoをつけずにDockerを叩けるように設定しておくと構築時に便利になります。

$ sudo usermod -g docker ec2-user
$ sudo /etc/init.d/docker restart

上記のコマンド入力後は一度ターミナルから抜けて再接続する必要があります。

3. 作成したGPUのAMIを使ってインスタンス作成

インスタンスの設定

ここではp2.xlargeを使って、インスタンスを立ち上げます。

スクリーンショット 2017-11-17 17.04.37.png

以下の点に注意する必要があります

  • 作成するインスタンスのロール設定はECSインスタンスロールを与えてください。
    作成方法はこちらを参照ください:Amazon ECSインスタンスロール

  • インスタンスの設定タブの下部の高度な詳細でユーザデータを設定する(下記)

#!/bin/bash
echo ECS_CLUSTER=ecs-cluster >> /etc/ecs/ecs.config

4. Dockerfileの構築

ここではGPUを使うためのDockerfileを作り、ECRへプッシュします。
今回はtensorflow-gpu 1.4.0を使います。TensorFlowのバージョンに合わせて、nvidiaのイメージを使いますので、下記のURLからご確認ください。
https://www.tensorflow.org/install/install_sources#common_installation_problems

上記のURLで確認するとcuDNN:6 CUDA:8なのでnvidiaのimageはこのバージョンに沿ったものを使います。

下記のDockerfileは一例なので、各自行いたい構築を設定してください。

Dockerfile
FROM nvidia/cuda:8.0-cudnn6-devel
RUN apt-get update
RUN apt-get install -y  git 
                        vim 
                        build-essential 
                        libncursesw5-dev 
                        libgdbm-dev 
                        libc6-dev 
                        zlib1g-dev 
                        libsqlite3-dev 
                        tk-dev 
                        libssl-dev 
                        openssl 
                        libbz2-dev 
                        libreadline-dev 
                        checkinstall 
                        wget 
                        jq

RUN git clone https://github.com/yyuu/pyenv.git /root/.pyenv

ENV PYENV_ROOT "/root/.pyenv"
ENV PATH "$PYENV_ROOT/bin:$PATH"
ENV PATH "$PYENV_ROOT/shims:$PATH"
RUN pyenv install 3.6.2
RUN pyenv global 3.6.2
RUN pip install tensorflow-gpu==1.4.0

DockerFileを作成したら、ECRへプッシュします。
ECSのリポジトリで新規リポジトリを作成するとプッシュまでの流れが記載されているのでその通りに行います。

個人的にハマった点
tensorflowとtensorflow-gpuは一緒に入れないでください、tensorflowを入れるとCPUで動きます。必ずtensorflow-gpuだけにしてください。

5. タスク定義

ここで重要な部分は下記です。

  • 特権付与をつける
  • ボリュームマウントでホスト側のnvidia関連をマウントする
    ホスト側の「/var/lib/nvidia-docker/volumes/nvidia_driver/latest」とコンテナ側の「/usr/local/nvidia」

以上のことを行えば、ECSでGPUインスタンスを使えて、かつTensorFlowが回せると思います。

続きを読む

[アップデート] Amazn EC2のサービスレベル契約が変更されました

AWS will use commercially reasonable efforts to make Amazon EC2 and Amazon EBS each available with a Monthly Uptime Percentage (defined below) of at least 99.99%, in each case during any monthly billing cycle (the “Service Commitment”). 続きを読む

アクセスログをシンプルにDB出力する

サーバー構成

AWSのELBに複数台EC2がぶら下がっており、そのEC2上でApache+PHP環境のアプリケーションが動作している

アクセスログ用テーブルの作成

sql
CREATE TABLE `access_logs` (
    `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
    `request_time` DATETIME NOT NULL COMMENT 'リクエスト日時',
    `session_id` VARCHAR(30) NOT NULL COMMENT 'セッションID',
    `request_method` VARCHAR(10) NOT NULL COMMENT 'リクエスト方式',
    `request_uri` VARCHAR(2083) NOT NULL COMMENT 'リクエストURI',
    `action_name` VARCHAR(50) NOT NULL COMMENT 'アクション名',
    `controller_name` VARCHAR(50) NOT NULL COMMENT 'コントローラ名',
    `x_forwarded_for` VARCHAR(255) NULL DEFAULT NULL COMMENT 'リクエスト元IP',
    `server_addr` VARCHAR(10) NULL DEFAULT NULL COMMENT 'サーバーIP',
    `user_agent` VARCHAR(255) NULL DEFAULT NULL COMMENT 'ユーザーエージェント',
    `request_query` TEXT NULL COMMENT 'リクエストクエリ',
    `request_data` TEXT NULL COMMENT 'リクエストデータ',
    `referer` VARCHAR(255) NULL DEFAULT NULL COMMENT 'リファラー',
    PRIMARY KEY (`id`)
)
COMMENT='アクセスログ'
COLLATE='utf8_general_ci'
ENGINE=InnoDB
;

Model

Bakeで
https://book.cakephp.org/3.0/ja/bake/usage.html

AppController

beforeFilter

リクエスト内容を簡潔に取りたいのでbeforeFilterだけにしています。
結果まで取るのでしたらafterFilterも併用するといいかもしれません。

AppControllerクラスbeforeFilterメソッド
use CakeChronosChronos;

public function beforeFilter(Event $event) {
    try{
        // リクエストクエリとデータを文字列に変換&password等は出力しない
        $requestQuery = $this->unsetSecret($this->request->query);
        $requestData = $this->unsetSecret($this->request->data);

        $accessLogParameters = [
            'request_time' => (Chronos::createFromTimestamp(time()))->toDateTimeString(),
            'session_id' => $this->request->session()->id(),
            'request_method' => $this->request->env('REQUEST_METHOD'),
            'request_uri' => $this->request->env('REQUEST_URI'),
            'action_name' => $this->request->action,
            'controller_name' => $this->name,
            'x_forwarded_for' => $this->request->env('HTTP_X_FORWARDED_FOR'),
            'server_addr' => $this->request->env('SERVER_ADDR'),
            'user_agent' => $this->request->env('HTTP_USER_AGENT'),
            'request_query' => json_encode($requestQuery),
            'request_data' => json_encode($requestData),
            'referer' => $this->request->env('HTTP_REFERER')
            ];

        $accessLogModel = TableRegistry::get('AccessLogs');
        $accessLogEntity = $accessLogModel->newEntity($accessLogParameters);
        $accessLogModel->save($accessLogEntity);
    }
    catch (Throwable $throwable){
    }
}

アクセスログに残すべきでない情報の消去

パスワード等取っておいてはいけない情報を消します。

AppController内privateメソッド
/**
 * パスワード等のログ出力してはいけない項目をマスクする。
 * @param Array $params
 * @return Array
 */
private function unsetSecret($params){

    // unset対象の文字列
    $keywords = [
        'password',
    ];

    // unset処理
    $paramKeys = array_keys($params);
    foreach($keywords as $maskKeyword){
        $masks = preg_grep("/{$maskKeyword}/i", $paramKeys);
        foreach($masks as $mask){
            $params[$mask] = "***";
        }
    }
    return $params;
}

参考

AWS ELBが付与するHTTPヘッダ
http://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/classic/x-forwarded-headers.html

続きを読む

AWSのメンテナンス[EC2編]

AWSからのメンテナンス通知

AWSからたまに送られてくる以下のような定期メンテナンス実施のアナウンスメール。

Dear Amazon EC2 Customer,

One or more of your Amazon EC2 instances is scheduled for maintenance on 2017-**-** for 2 hours starting at 2017-**-** 12:00:00 UTC UTC. During this time, the following instances in the ******* region will be unavailable and then rebooted:

************

Your instances will return to normal operations after maintenance is complete and all of your configuration settings will be retained. To continue normal operation and avoid any unavailability or reboots during this time, you can migrate the instances listed above to replacement instances. Replacement instances will not be affected by this scheduled maintenance. Otherwise, no action is required on your part.

以下省略

AWSではEC2群を頻繁にアップデートしており、多くのパッチやアップグレードの適用を行なっています。
今回も指定期間のうちにメンテナンスをするから、インスタンスが再起動されるよ。と言うお知らせでした。

再起動にかかる時間は通常、数分程度ですが少しでもサービスが止まってしまうと困る!と言う場合は指定のメンテナンス期間より前の任意の時間で再起動を行なってしまえばOKです。

メンテナンス対象はコンソールからも確認可能

コンソール>EC2>左側のメニュー上部の[イベント]

EC2メンテナンス01.png

現在予定されているメンテナンス対象が表示されます。

EC2メンテナンス02.png

予定の他に、進行中や完了ステータスになっているものも確認できます。
指定期間に実行されたインスタンスの状況も確認できるので安心です!

EC2メンテナンス03.png

システムリブートとインスタンスリブート

メンテナンスの内容によって、システムリブートとインスタンスリブート対応の仕方が少し異なります。
予定されたメンテナンスがどちらに該当するかはイベントの[イベントタイプ]から確認可能です。

システムリブートの場合は、stop → start
以下のように、インスタンスを完全に停止させてから開始します。

EC2メンテナンス04.png

インスタンスリブートの場合は、再起動でOKです。
が、stop → startの対応しておけばより確実。

続きを読む