AWS Lambdaから実行したEC2上のPythonでモジュールがimportできない

S3に動画ファイルが配置されたことを契機にLambdaでEC2上のPythonをキックし、
ffmpy経由でffmpegを起動し動画を処理するようなプログラムを作っていたのですが、
どうもffmpyが正常にimportできないので、現状と解決した際の情報共有に書き残します。

環境

実行用コードは下記のようにec2-userのホームディレクトリ配下に配置されています。

/home/ec2-user/hoge/
├── MAIN1_KICKED_BY_LAMBDA.py # メインメソッド
├── fuga
│   └── hogefuga.py           # 動画編集用モジュール
...

下記の設定をrootユーザに対して行いました。

  • pyenv -> Anaconda3.4.1 (Python 3.6)
  • pip install ffmpy
  • ffmpegをインストール (下記スクリプト)
ffmpegcpl.sh
#!/bin/sh

sudo yum -y install autoconf automake cmake freetype-devel gcc gcc-c++ git libtool make mercurial nasm pkgconfig zlib-devel

mkdir ~/ffmpeg_sources

#Yasm
cd ~/ffmpeg_sources
git clone --depth 1 git://github.com/yasm/yasm.git
cd yasm
autoreconf -fiv
./configure --prefix="$HOME/ffmpeg_build" --bindir="$HOME/bin"
make
make install
make distclean

#libx264
cd ~/ffmpeg_sources
git clone --depth 1 git://git.videolan.org/x264
cd x264
PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" ./configure --prefix="$HOME/ffmpeg_build" --bindir="$HOME/bin" --enable-static
make
make install
make distclean

#libx265
cd ~/ffmpeg_sources
hg clone https://bitbucket.org/multicoreware/x265
cd ~/ffmpeg_sources/x265/build/linux
cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="$HOME/ffmpeg_build" -DENABLE_SHARED:bool=off ../../source
make
make install

#libfdk_aac
cd ~/ffmpeg_sources
git clone --depth 1 git://git.code.sf.net/p/opencore-amr/fdk-aac
cd fdk-aac
autoreconf -fiv
./configure --prefix="$HOME/ffmpeg_build" --disable-shared
make
make install
make distclean

#libmp3lame
cd ~/ffmpeg_sources
curl -L -O http://downloads.sourceforge.net/project/lame/lame/3.99/lame-3.99.5.tar.gz
tar xzvf lame-3.99.5.tar.gz
cd lame-3.99.5
./configure --prefix="$HOME/ffmpeg_build" --bindir="$HOME/bin" --disable-shared --enable-nasm
make
make install
make distclean

#libopus
cd ~/ffmpeg_sources
git clone http://git.opus-codec.org/opus.git
cd opus
autoreconf -fiv
./configure --prefix="$HOME/ffmpeg_build" --disable-shared
make
make install
make distclean

#libogg
cd ~/ffmpeg_sources
curl -O http://downloads.xiph.org/releases/ogg/libogg-1.3.2.tar.gz
tar xzvf libogg-1.3.2.tar.gz
cd libogg-1.3.2
./configure --prefix="$HOME/ffmpeg_build" --disable-shared
make
make install
make distclean

#libvorbis
cd ~/ffmpeg_sources
curl -O http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.4.tar.gz
tar xzvf libvorbis-1.3.4.tar.gz
cd libvorbis-1.3.4
LDFLAGS="-L$HOME/ffmeg_build/lib" CPPFLAGS="-I$HOME/ffmpeg_build/include" ./configure --prefix="$HOME/ffmpeg_build" --with-ogg="$HOME/ffmpeg_build" --disable-shared
make
make install
make distclean

#libvpx
cd ~/ffmpeg_sources
git clone --depth 1 https://chromium.googlesource.com/webm/libvpx.git
cd libvpx
./configure --prefix="$HOME/ffmpeg_build" --disable-examples
make
make install
make clean

#FFmpeg
cd ~/ffmpeg_sources
git clone http://source.ffmpeg.org/git/ffmpeg.git
cd ffmpeg
PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" ./configure --prefix="$HOME/ffmpeg_build" --extra-cflags="-I$HOME/ffmpeg_build/include" --extra-ldflags="-L$HOME/ffmpeg_build/lib" --bindir="$HOME/bin" --pkg-config-flags="--static" --enable-gpl --enable-nonfree --enable-libfdk-aac --enable-libfreetype --enable-libmp3lame --enable-libopus --enable-libvorbis --enable-libvpx --enable-libx264 --enable-libx265
make
make install
make distclean
hash -r

現状・試したこと

実行するコードについては、公開を控えさせてください。。。
Lambdaから実行したときのログは下記のとおりで、どうやらec2-userのpyenv(存在しない)を見にいって落ちているようです。

Traceback (most recent call last):
  File "/home/ec2-user/hoge/MAIN1_KICKED_BY_LAMBDA.py", line 88, in <module>
    hogefuga.exeExtractWav(TMP_DIRECTORY2 + '/', nameonly)
  File "/home/ec2-user/hoge/fuga/hogefuga.py", line 175, in exeExtractWav
    extractWav(filename)
  File "/home/ec2-user/hoge/fuga/hogefuga.py", line 162, in extractWav
    ff.run()
  File "/home/ec2-user/.pyenv/versions/anaconda3-4.3.1/lib/python3.6/site-packages/ffmpy.py", line 99, in run
    raise FFExecutableNotFoundError("Executable '{0}' not found".format(self.executable))
ffmpy.FFExecutableNotFoundError: Executable 'ffmpeg' not found

SSH接続しrootで直接実行した際はroot側のpyenvを見にいき問題なく実行されるのですが、解決策としては以下の2つでしょうか。

  • ec2-user環境にも同様にpyenv, ffmpeg環境を構築する
  • 実行ファイル類をrootのホーム配下へ移動する

そもそも直接実行とLambda実行でパスが変わる原因を明らかにしないとすっきりしませんが・・・。

(2017.05.23 17:12追記)

  • ec2-user環境にも同様にpyenv, ffmpeg環境を構築する
  • 実行ファイル類をrootのホーム配下へ移動する

両方試したものの、下記のエラーが出て終了。

----------ERROR-------
failed to run commands: exit status 1
Traceback (most recent call last):
  File "MAIN1_KICKED_BY_LAMBDA.py", line 17, in <module>
    import formatchg
  File "/root/fuga/hoge/fugahoge.py", line 10, in <module>
    from ffmpy import FFmpeg
ImportError: No module named ffmpy

続きを読む

ALB(Application Load Balancer)でWebサービスを冗長化する

概要

ALBを使ってアプリケーションを冗長化する手順です。

HTTPS接続でアプリケーションにアクセス出来るところまでをこの記事で紹介します。

前提条件

以下の事前条件が必要です。

  • VPCの作成を行っておく
  • 最低でも2台のWebサーバインスタンスを起動させておく事
  • ロードバランサー用サブネットの作成が行われている事(後で説明します。)

事前準備その1(ロードバランサー用サブネットの作成)

以下は公式サイトに書かれている内容です。

ロードバランサーのアベイラビリティーゾーンを指定します。ロードバランサーは、これらのアベイラビリティーゾーンにのみトラフィックをルーティングします。アベイラビリティーゾーンごとに 1 つだけサブネットを指定できます。ロードバランサーの可用性を高めるには、2 つ以上のアベイラビリティーゾーンからサブネットを指定する必要があります。

今回検証で利用している東京リージョンには ap-northeast-1aap-northeast-1c の2つのアベイラビリティーゾーンが存在するので、それぞれでサブネットの作成を行います。

サービス → VPC → サブネット → 「サブネットの作成」より作成を行います。

ap-northeast-1a で サブネットを作成します。
以下のように入力を行います。

  • ネームタグ

    • account_api_alb_1a
    • 開発環境アカウント用APIのALB用と分かる名前を付けています。分かりやすい名前であれば何でも構いません。
  • VPC

    • 利用対象となるVPCを選択します。
  • IPv4 CIRD block

    • 192.0.30.0/24
    • ネットワークの設計方針にもよりますが今回は 192.0.30.0/24 を割り当てます。

alb_subnet_step1.png

続いて ap-northeast-1c でも同じ要領でサブネットを作成します。
※先程とほとんど同じなので、入力内容に関しての詳細は省略します。

alb_subnet_step2.png

事前準備その2(SSLの証明書の用意)

SSLで接続を可能にするのでSSL証明書の用意が必要です。

今回は検証なので自己証明書を利用する事にします。

以前、LAMP 環境構築 PHP 7 MySQL 5.7(前編) という記事を書きました。

こちらに載っている手順を参考に自己証明書を用意します。

ALB(Application Load Balancer)の新規作成

ここからが本題になります。
サービス → EC2 → ロードバランサー → ロードバランサーの作成 を選択します。

alb_step1.png

Step1 ロードバランサーの設定

基本的な設定を行っていきます。
名前を入力します。(今回はaccount-api-alb)という名前を付けました。

インターネットに公開するサービスを想定しているので、スキーマは「インターネット向け」を選択します。

ロードバランサーのプロトコルにHTTPSを追加します。

alb_step2-1.png

アベイラビリティーゾーンに先程作成したサブネットを割り当てます。

alb_step2-2.png

Step2 セキュリティ設定の構成

SSL証明書の設定を行います。

alb_step2-3.png

証明書の名前は分かりやすい名前でOKです。

プライベートキーには事前準備で作成した、プライベートキーを入れます。
-----BEGIN RSA PRIVATE KEY----- から -----END RSA PRIVATE KEY----- までを全てコピーして下さい。

パブリックキー証明書には -----BEGIN CERTIFICATE----- から -----END CERTIFICATE----- までの内容を全てコピーして下さい。

セキュリティポリシーは ELBSecurityPolicy-2016-08 を選択します。

※2017-05-22 現在、この手順で問題なく証明書の追加が出来るハズなのですが Certificate not found というエラーが発生しロードバランサーの作成に失敗してしまいます。

証明書のアップロードを aws-cli を使って事前に実施するようにしたら上手く行きました。

証明書のアップロード
aws iam upload-server-certificate --server-certificate-name self-certificate --certificate-body file://crt.crt --private-key file://private.key

file:// を付けるのがポイントです。これがないと上手くアップロード出来ませんでした。

--server-certificate-name には任意の名前を入力して下さい。

上手く行くと下記のようなレスポンスが返ってきます。

証明書アップロードのレスポンス
{
    "ServerCertificateMetadata": {
        "ServerCertificateId": "XXXXXXXXXXXXXXXXXXXXX",
        "ServerCertificateName": "self-certificate",
        "Expiration": "2018-05-22T04:14:02Z",
        "Path": "/",
        "Arn": "arn:aws:iam::999999999999:server-certificate/self-certificate",
        "UploadDate": "2017-05-22T05:58:44.754Z"
    }
}

アップロード完了後に「AWS Identity and Access Management(IAM)から、既存の証明書を選択する」を選んで先程アップロードした証明書を選択して下さい。

alb_step2-3.1.png

この問題については 既存の ELB に SSL 証明書を追加しようとすると Server Certificate not found for the key というエラーになる件の解決方法 を参考にさせて頂きました。

Step3 セキュリティグループの設定

セキュリティグループの設定を行います。

alb_step2-4.png

Step4 ルーティングの設定

ターゲットグループの新規作成を行います。

alb_step2-5.png

名前、プロトコル、ヘルスチェック用のURLの設定等を行います。

Step5 ターゲットの登録

ロードバランサーの配下で起動するインスタンスを選択します。

alb_step2-6.png

作成に必要な情報入力は以上となります。

確認画面に進み作成を行いしばらくすると、ロードバランサーが作成され利用可能な状態となります。

※サービス → EC2 → ロードバランサー より確認が出来ます。

alb_step3.png

動作確認

サービス → EC2 → ロードバランサー よりDNSが確認出来るので、動作確認を行います。

curl -kv https://account-api-alb-000000000.ap-northeast-1.elb.amazonaws.com/
*   Trying 0.0.0.0...
* TCP_NODELAY set
* Connected to account-api-alb-000000000.ap-northeast-1.elb.amazonaws.com (0.0.0.0) port 443 (#0)
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
* Server certificate: system
> GET / HTTP/1.1
> Host: account-api-alb-000000000.ap-northeast-1.elb.amazonaws.com
> User-Agent: curl/7.51.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Date: Mon, 22 May 2017 07:26:02 GMT
< Content-Type: application/json
< Transfer-Encoding: chunked
< Connection: keep-alive
< Server: nginx/1.12.0
< X-Request-Id: 76c7e41f-1a4e-4328-972c-b98055e84395
< Cache-Control: no-cache, private
<
* Curl_http_done: called premature == 0
* Connection #0 to host account-api-alb-000000000.ap-northeast-1.elb.amazonaws.com left intact
{"code":404,"message":"Not Found"}

各Webサーバのログを確認すると、処理が振り分けられているのが、確認出来ます。

本番環境での運用に向けて

ここまで簡単に作成が出来ましたが実環境で運用を行うにはまだまだ考慮が必要な点が多いです。

  • SSL証明書を正式な物にする(自己証明書で運用とかはさすがに厳しいと思います)
  • 独自ドメインでのアクセスを可能にする
  • 各EC2のログに記載されているIPがロードバランサーの物になっている

※これらの手順は順次行っていく予定ですので、準備が出来次第記事を書く予定です。

最後まで読んで頂きありがとうございました。

続きを読む

DynamoDB は空文字を登録できない

DynamoDB は空文字を登録できないため、空文字チェックしてエラー回避する実装にする必要があります。

が、JSONとかの関係でどうしても登録したい場合は、以下のように単純にスペースで登録するとよいかもしれません。

ちなみに Lambda – Node.js 6.10 での例です。

'use strict';

const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient();

exports.handler = (event, context, callback) => {

    // 空文字チェック
    // ※DynamoDBの仕様で空文字を登録できないため、半角スペースで登録
    let resourcesPara = ' ';
    if (event.resources !== undefined && event.resources.length > 0) {
        resourcesPara = event.resources;
    }

    const params = {
        TableName: "tableName",
        Item: {
            "id": "newId",
            "resources": resourcesPara
        }
    };

    docClient.put(params, function(err, data) {
        if (err) {
            console.error('Unable to add item. Error JSON:', JSON.stringify(err, null, 2));
            // エラー
            callback(null, err);
        } else {
            console.log('Added item:', JSON.stringify(data, null, 2));
            callback(null, {"id": "newId"});
        }
    });
};

取得するときも単純に半角スペースを空文字にして返却。

'use strict';

const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient();

exports.handler = (event, context, callback) => {

    const params = {
        TableName: "tableName"
    };

    docClient.scan(params, function(err, data) {
        if (err) {
            console.error('Unable to get item. Error JSON:', JSON.stringify(err, null, 2));
            // エラー
            callback(null, err);
        } else {
            if (data.Count > 0) {
                // データあり
                for (let i = 0; i < data.Count; i++) {
                    // スペースのみの場合空文字にする
                    // ※DynamoDBの仕様で空文字を登録できないため、半角スペースで登録している
                    if (data.Items[i].resources === ' ') {
                        data.Items[i].resources = '';
                    }
                }
                callback(null, data.Items);
            } else {
                // データなし
                callback(null, '404 Not Found. Data is nothing.');
            }
        }
    });
};

続きを読む

IPv6でアクセスすると"via IPv6"って出るやつ

IPv6でアクセスすると”via IPv6″って出る例のやつ作りました。
(HTMLタグ貼るだけのやつが見つからなかったので)

表示してみる

IPv6から繋ぐと
Screen Shot 2017-05-22 at 3.19.22.png
が表示されます。

IPv4から繋ぐと
Screen Shot 2017-05-22 at 3.19.41.png
が表示されます。

使い方

<span id="kibousoft-viav6"></span>
<script type="text/javascript">
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://viav6.kibousoft.co.jp/', true);
xhr.onreadystatechange = function(){
if (xhr.readyState === 4 && xhr.status === 200){
   var dom = document.getElementById('kibousoft-viav6');
   dom.innerHTML = xhr.responseText;
 }
};
xhr.send(null);
</script>

ソースコード

汚いですが直書きです。大したことしてない。

index.php
<a href="https://github.com/kibousoft/viav6_web/" style="text-decoration: none; color: white;">
<?php
$ip = $_SERVER['REMOTE_ADDR'];
$headers = apache_request_headers();
if ($headers['X-Forwarded-For']) {
    $ip = $headers['X-Forwarded-For'];
}

if (preg_match('/^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/', $ip)) {
    echo '<div style="background: linear-gradient(#FF0000, #FF99CC); padding: 5px; border: 1px solid #333333; border-radius: 3px; font-size: 13px; width: 50px; text-align: center; font-family: sans-serif;">via IPv4</div>';
} else {
    echo '<div style="background: linear-gradient(#0000FF, #99CCFF); padding: 5px; border: 1px solid #333333; border-radius: 3px; font-size: 13px; width: 50px; text-align: center; font-family: sans-serif;">via IPv6</div>';
}
?>
</a>

CORSの話

外部からXHRで取得される可能性のあるサイトでは、
Access-Control-Allow-Origin , Access-Control-Allow-Methods ヘッダーを返す必要があります。
.htaccessで以下を設定しました。

.htaccess
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Methods "GET"

インフラの話

最初Amazon API Gatewayでやろうとしたんですが、API GatewayはIPv6対応していませんでした。
なので、OpsWorksでPHP App Serverを立てて動かしています。
OpsWorksにも以下の問題がありました。

  • Application Load Balancer(IPv6対応)には対応していない
  • EC2へのIPv6アドレスのアタッチには対応していない
  • セキュリティグループでIPv6のTCP 80番が許可されていない

そのため、上記の設定は手動で行いました。

備考

  • Happy Eyeballsの関係で、サイトにはIPv4で繋がって、XHRはIPv6で繋がるケースもあるよねとか細かい話はなしで。

続きを読む

【AWS】Amazon Lexのカスタムボットできんにくフレンズを作ろう[Part1]

タダです。

Amazon Lex(以下、Lex)と戯れてみたレポートを何回か書いてみたいと思います。
今回は、定型文の会話を行うカスタムボットを作ってみた時の紹介記事です。
社内のLT大会で話してみたので、その模様は以下からどうぞ。

社内ブログとか動画

[contents]

Lexの概要

さて、改めてLexの概要をまとめてみると、次のような特徴があります

  • Lexはアプリケーションの対話型インタフェースを作るのをサポートするサービス

    • チャットボット開発に最適
  • Lambda,Cognito,Polly,MobileHub 等とも連携可能
  • チャットのプラットフォームとして、Facebook Messanger,Twilio SMS,Slackと連携可能
  • 従量課金制

Lexの専門用語

Lexで扱う専門用語は次のものです

  • Bot : ボットそのもの
  • Intents : ユーザが入力した自然言語に応答する、ボットのアクションの固まり
  • Utterances : Intentsを発動する口頭もしくは入力されるフレーズ
  • Slot : Intentsを満たすための要求される入力データ
  • Prompt : Slotを引き出すためのフレーズ
  • Fulfillment : Intentsを実現するビジネスロジック

公式資料

やってみたこと

  • Slackにきんにくフレンズを召喚

    • 普段から使っているし、馴染みがある
  • きんにくフレンズBot(@musclechang)に話しかけて自分のトレーニングメニューを管理するものを作ってみたい
    • トレーニングしたいと話しかけると、トレーニングメニュー、回数、セット数を聞いてくれて、最後は励ますようなボットです

実現するためにやったこと

AWS側

Utterancesと、Slots、Promptはこんな感じです

Screen Shot 2017-05-21 at 10.25.05 PM.png

Slots typesは3つ作りました

  • Set(トレーニングのセット数)
  • Training Menu(トレーニングメニュー)
  • Count(何回やるかのカウント数)

Fulfillmentは、Lambdaでやらないので、「Return parameters to client」を選択しています
Screen Shot 2017-05-21 at 10.27.11 PM.png

Slack側

Slackの連携部分は、以下のドキュメントを参考にしました

結果

こんな感じの対話ができるフレンズができました!

Screen Shot 2017-05-21 at 10.06.30 PM.png

まとめ

  • ノンプラグラミングでボットを作れました

    • 定型文しか返せないので、AIぽく入力した情報を貯めて、より良いトレーニングメニューを提案するような感じにしていきたい
    • 次回に他のAWSリソースと連携してみたいです(Lambda,Pollyとの連携とか)

続きを読む

Serverless Step FunctionsにてLambdaの環境変数にStatemachineのARNを指定する方法

結論

以下のようにserverless.ymlを記述することで、Lambdaの環境変数にstatemachineのARNを記述することが出来ます。

serverless.yml
functions:
  hello:
    handler: handler.hello
    environment:
        statemachine_arn: ${self:resources.Outputs.HelloStepfunc.Value}

stepFunctions:
  stateMachines:
    hellostepfunc:
      definition:
        <your definition>

resources:
  Outputs:
    HelloStepfunc:
      Description: The ARN of the example state machine
      Value:
        Ref: HellostepfuncStepFunctionsStateMachine

plugins:
  - serverless-step-functions

これでstatemachineを実行するだけのLambdaファンクションを作って、Lambdaのイベントからstatemachineを開始することが可能となります。

続きを読む

AWS ECSにてカスタムしたredmineのdockerイメージを動かすまでのメモ(その1)

redmineの構築、プラグインの導入をふつーにやると面倒くさい。
あと、一旦構築した後redmineのバージョンをあげるのもやっぱり面倒くさい。

→ので、dockerにてプラグインのインストールやらなにやらを手順をコード化して簡単にRedmineの導入が
できるようにしました。
なお動作環境はAWSのECS(Elastic Container Service)を使います。

大きな流れ

1.Dockerfileを用意
2.AWSにてElastic Container Service(ECS)のタスクを定義
3.ECSのインスタンスを用意

今回はまず1を用意します。

1.Dockerfileを用意

redmineの公式イメージがdockerhubにあるのでこれをもとにpluginを導入する手順を
dockerfile化していきます。

ポイントは2つです。
1.ベースのイメージはredmine:x.x.x-passengerを利用する
2.DBのマイグレーションが必要ないものはpluginsフォルダに配置し、マイグレーションが必要なものは別フォルダ(install_plugins)に配置。
→コンテナ起動時にマイグレーションを行うようdocker-entrypoint.shに記載しておきます。

インストールするプラグイン一覧

独断と偏見で入れたプラグインです。

No プラグインの名前 概要
1 gitmike githubの雰囲気のデザインテーマ
2 backlogs スクラム開発でおなじみ。ストーリーボード
3 redmine_github_hook redmineとgitを連動
4 redmine Information Plugin redmineの情報を表示可能
5 redmine Good Job plugin チケット完了したら「Good Job」が表示
6 redmine_local_avatars アイコンのアバター
7 redmine_startpage plugin 初期ページをカスタマイズ
8 clipboard_image_paste クリップボードから画像を添付できる 
9 Google Analytics Plugin 閲覧PV測定用
10 redmine_absolute_dates Plugin 日付を「XX日前」 ではなくyyyy/mm/ddで表示してくれる
11 sidebar_hide Plugin サイドバーを隠せる
12 redmine_pivot_table ピボットテーブルできる画面追加
13 redmine-slack 指定したslack channnelに通知可能
14 redmine Issue Templates チケットのテンプレート
15 redmine Default Custom Query 一覧表示時のデフォルト絞り込みが可能に
16 redmine Lightbox Plugin 2 添付画像をプレビューできる
17 redmine_banner Plugin 画面上にお知らせを出せます
18 redmine_dmsf Plugin フォルダで文書管理できる
19 redmine_omniauth_google Plugin googleアカウントで認証可能
20 redmine view customize Plugin 画面カスタマイズがコードで可能

Dockerfile

上記のプラグインをインストール済みとするためのDockerfileです。
なお、ベースイメージは最新(2017/05/19時点)の3.3.3を使用しています。

Dockerfile
FROM redmine:3.3.3-passenger
MAINTAINER xxxxxxxxxxxxxxxxx

#必要コマンドのインストール
RUN apt-get update -y 
 && apt-get install -y curl unzip ruby ruby-dev cpp gcc libxml2 libxml2-dev 
  libxslt1-dev g++ git make xz-utils xapian-omega libxapian-dev xpdf 
  xpdf-utils antiword catdoc libwpd-tools libwps-tools gzip unrtf 
  catdvi djview djview3 uuid uuid-dev 
 && apt-get clean

#timezoneの変更(日本時間)
RUN cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
ENV RAILS_ENV production

#gitmakeテーマのインストール
RUN cd /usr/src/redmine/public/themes 
 && git clone https://github.com/makotokw/redmine-theme-gitmike.git gitmike 
 && chown -R redmine.redmine /usr/src/redmine/public/themes/gitmike



#redmine_github_hookのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/koppen/redmine_github_hook.git

#Redmine Information Pluginのインストール
RUN curl http://iij.dl.osdn.jp/rp-information/57155/rp-information-1.0.2.zip > /usr/src/redmine/plugins/rp-information-1.0.2.zip 
 && unzip /usr/src/redmine/plugins/rp-information-1.0.2.zip -d /usr/src/redmine/plugins/ 
 && rm -f /usr/src/redmine/plugins/rp-information-1.0.2.zip

#Redmine Good Job pluginのインストール
RUN curl -L https://bitbucket.org/changeworld/redmine_good_job/downloads/redmine_good_job-0.0.1.1.zip > /usr/src/redmine/plugins/redmine_good_job-0.0.1.1.zip 
 && unzip /usr/src/redmine/plugins/redmine_good_job-0.0.1.1.zip -d /usr/src/redmine/plugins/redmine_good_job 
 && rm -rf /usr/src/redmine/plugins/redmine_good_job-0.0.1.1.zip

#redmine_startpage pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/txinto/redmine_startpage.git

#Redmine Lightbox Plugin 2 Pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/peclik/clipboard_image_paste.git

#Google Analytics Pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/paginagmbh/redmine-google-analytics-plugin.git google_analytics_plugin

#redmine_absolute_dates Pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/suer/redmine_absolute_dates

#sidebar_hide Pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/bdemirkir/sidebar_hide.git

#redmine_pivot_tableのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/deecay/redmine_pivot_table.git

#redmine-slackのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/sciyoshi/redmine-slack.git redmine_slack

#Redmine Issue Templates Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/akiko-pusu/redmine_issue_templates.git redmine_issue_templates

#Redmine Default Custom Query Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/hidakatsuya/redmine_default_custom_query.git redmine_default_custom_query

#Redmine Lightbox Plugin 2 Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/paginagmbh/redmine_lightbox2.git redmine_lightbox2

#redmine_banner Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/akiko-pusu/redmine_banner.git redmine_banner

#redmine_dmsf Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/danmunn/redmine_dmsf.git redmine_dmsf

#redmine_omniauth_google Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/yamamanx/redmine_omniauth_google.git redmine_omniauth_google

#redmine_omniauth_google Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/onozaty/redmine-view-customize.git view_customize

#redmine_local_avatars用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/ncoders/redmine_local_avatars.git

#backlogsのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN mkdir /usr/src/redmine/install_plugins 
 && cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/AlexDAlexeev/redmine_backlogs.git 
 && cd /usr/src/redmine/install_plugins/redmine_backlogs/ 
 && sed -i -e '11,17d' Gemfile

#database.ymlファイルを置くフォルダを用意
RUN mkdir /config
COPY docker-entrypoint.sh /
RUN chmod +x /docker-entrypoint.sh

ENTRYPOINT ["/docker-entrypoint.sh"]

EXPOSE 3000
CMD ["passenger", "start"]

docker-entrypoint.sh

次にdocker-entrypoint.shファイルです。
githubに公開されているファイル
(https://github.com/docker-library/redmine/blob/41c44367d9c1996a587e2bcc9462e4794f533c15/3.3/docker-entrypoint.sh)
を元にプラグインのインストールを行うコードを記載していきます。

docker-entrypoint.sh
#!/bin/bash
set -e

case "$1" in
    rails|rake|passenger)
        if [ -e '/config/database.yml' ]; then
                    if [ ! -f './config/database.yml' ]; then
                echo "use external database.uml file"
                ln -s /config/database.yml /usr/src/redmine/config/database.yml
            fi
        fi
                if [ -e '/config/configuration.yml' ]; then
                        if [ ! -f './config/configuration.yml' ]; then
                                echo "use external configuration.uml file"
                                ln -s /config/configuration.yml /usr/src/redmine/config/configuration.yml
                        fi
                fi
        if [ ! -f './config/database.yml' ]; then
            if [ "$MYSQL_PORT_3306_TCP" ]; then
                adapter='mysql2'
                host='mysql'
                port="${MYSQL_PORT_3306_TCP_PORT:-3306}"
                username="${MYSQL_ENV_MYSQL_USER:-root}"
                password="${MYSQL_ENV_MYSQL_PASSWORD:-$MYSQL_ENV_MYSQL_ROOT_PASSWORD}"
                database="${MYSQL_ENV_MYSQL_DATABASE:-${MYSQL_ENV_MYSQL_USER:-redmine}}"
                encoding=
            elif [ "$POSTGRES_PORT_5432_TCP" ]; then
                adapter='postgresql'
                host='postgres'
                port="${POSTGRES_PORT_5432_TCP_PORT:-5432}"
                username="${POSTGRES_ENV_POSTGRES_USER:-postgres}"
                password="${POSTGRES_ENV_POSTGRES_PASSWORD}"
                database="${POSTGRES_ENV_POSTGRES_DB:-$username}"
                encoding=utf8
            else
                echo >&2 'warning: missing MYSQL_PORT_3306_TCP or POSTGRES_PORT_5432_TCP environment variables'
                echo >&2 '  Did you forget to --link some_mysql_container:mysql or some-postgres:postgres?'
                echo >&2
                echo >&2 '*** Using sqlite3 as fallback. ***'

                adapter='sqlite3'
                host='localhost'
                username='redmine'
                database='sqlite/redmine.db'
                encoding=utf8

                mkdir -p "$(dirname "$database")"
                chown -R redmine:redmine "$(dirname "$database")"
            fi

            cat > './config/database.yml' <<-YML
                $RAILS_ENV:
                  adapter: $adapter
                  database: $database
                  host: $host
                  username: $username
                  password: "$password"
                  encoding: $encoding
                  port: $port
            YML
        fi

        # ensure the right database adapter is active in the Gemfile.lock
        bundle install --without development test
        if [ ! -s config/secrets.yml ]; then
            if [ "$REDMINE_SECRET_KEY_BASE" ]; then
                cat > 'config/secrets.yml' <<-YML
                    $RAILS_ENV:
                      secret_key_base: "$REDMINE_SECRET_KEY_BASE"
                YML
            elif [ ! -f /usr/src/redmine/config/initializers/secret_token.rb ]; then
                rake generate_secret_token
            fi
        fi
        if [ "$1" != 'rake' -a -z "$REDMINE_NO_DB_MIGRATE" ]; then
            gosu redmine rake db:migrate
        fi

        chown -R redmine:redmine files log public/plugin_assets

        if [ "$1" = 'passenger' ]; then
            # Don't fear the reaper.
            set -- tini -- "$@"
        fi
                if [ -e /usr/src/redmine/install_plugins/redmine_backlogs ]; then
                        mv -f /usr/src/redmine/install_plugins/redmine_backlogs /usr/src/redmine/plugins/
                        bundle update nokogiri
                        bundle install
                        bundle exec rake db:migrate
                        bundle exec rake tmp:cache:clear
                        bundle exec rake tmp:sessions:clear
            set +e
                        bundle exec rake redmine:backlogs:install RAILS_ENV="production"
                        if [ $? -eq 0 ]; then
                echo "installed backlogs"
                                touch /usr/src/redmine/plugins/redmine_backlogs/installed
            else
                echo "can't install backlogs"
                        fi
            set -e
            touch /usr/src/redmine/plugins/redmine_backlogs/installed
        fi
                if [ -e /usr/src/redmine/install_plugins/redmine_local_avatars ]; then
                        mv -f /usr/src/redmine/install_plugins/redmine_local_avatars /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
                fi
        if [ -e /usr/src/redmine/install_plugins/redmine_slack ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_slack /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_issue_templates ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_issue_templates /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_default_custom_query ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_default_custom_query /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_lightbox2 ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_lightbox2 /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_banner ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_banner /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_dmsf ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_dmsf /usr/src/redmine/plugins/
            bundle install --without development test xapian
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_omniauth_google ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_omniauth_google /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/view_customize ]; then
            mv -f /usr/src/redmine/install_plugins/view_customize /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ ! -f '/usr/src/redmine/plugins/redmine_backlogs/installed' ]; then
            set +e
            bundle exec rake redmine:backlogs:install RAILS_ENV="production"
            if [ $? -eq 0 ]; then
                echo "installed backlogs"
                touch /usr/src/redmine/plugins/redmine_backlogs/installed
            else
                echo "can't install backlogs"
            fi
            set -e
        fi
        set -- gosu redmine "$@"
                ;;
esac

exec "$@"

次はこのイメージをAWSのECSにて動作させます。
(次回に続く)

続きを読む

EC2 instance起動時にtagをつけるTagSpecifications

AWSCLIでEC2 instance起動時に同時にタグをつける方法としては、instance起動してinstance-idを取得しておいて、パイプでつないでtagをつけたり、スクリプトの中で後でタグ付けする方法があったと思います。
http://kurochan-note.hatenablog.jp/entry/2017/01/08/220155

AWSCLI EC2 Run-Instanceのなかに–tag-specificationsというoptionが入って、run-instancesの中でタグが作成できるようになりました。地味なアップデートかもしれませんが、結構うれしいです。

instanceの詳細はjsonに記述して、下記のように指定して実行します。

aws ec2 run-instances --cli-input-json file://instance.json

EC2は山ほど設定項目があるので、generate-cli-skeltonでフォーマットを出力して、必要な項目だけ入力して、不必要なものは消すとinstanceの詳細を記述したjsonの完成です。Gitにでも入れておきましょう。
http://docs.aws.amazon.com/cli/latest/userguide/generate-cli-skeleton.html

aws ec2 run-instances --generate-cli-skeleton

Instanceの設定詳細を記述したjsonサンプル

instance.json
{
    "ImageId": "<image-id>",
    "KeyName": "<my-key>",
    "SecurityGroupIds": [
        "<my-sgid>"
    ],
    "InstanceType": "<instance-type>",
    "BlockDeviceMappings": [
        {
            "VirtualName": "Root",
            "DeviceName": "/dev/sda1",
            "Ebs": {
                "VolumeSize": 100,
                "DeleteOnTermination": true,
                "VolumeType": "gp2"
            }
        }
    ],
    "Monitoring": {
        "Enabled": false
    },
    "SubnetId": "<subnet-id>",
    "DisableApiTermination": false,
    "IamInstanceProfile": {
        "Name": "<instance-iam-role>"
    },
    "TagSpecifications":[
        {
            "ResourceType": "instance",
            "Tags": [
              {
                "Key": "Name",
                "Value": "<server-name>"
              },
              {
                "Key": "ClusterName",
                "Value": "<cluster-name>"
              },
              {
                "Key": "Application",
                "Value": "<myapp>"
              },
              {
                "Key": "CostCenter",
                "Value": "<my-cost-center>"
              },
              {
                "Key": "Environment",
                "Value": "Test"
              },
              {
                "Key": "User",
                "Value": "<user-name>"
              }
            ]
        },
        {
          "ResourceType": "volume",
          "Tags": [
            {
              "Key": "Device",
              "Value": "<device-name>"
            },
{
              "Key": "CostCenter",
              "Value": "<my-cost-center>"
            },
            {
              "Key": "backup_key",
              "Value": "true"
            }
          ]
        }
    ]
}

続きを読む

Amazon Elasticsearch Service へ Embulk を使ってデータロード

はじめに

AWS ElasticSearch Serviceはフルマネージドで運用要らず、今のところ(2017/5/18)他の追随を許さ無い魅力があります(筆者はElastic社のElastic Cloudは使った事がありません)
Azureの場合、MarketPlaceからElastic-Stackが提供されて完全占有クラスターが自動構築可能です。GCPもblogにある通り、Azureとほぼ同じ。完全占有クラスターのメリット?がありそうな反面、ElasticClusterの運用/管理がtrade-offとしてつきまといます。

当初はAzureで個別クラスターを使っていましたがトラブルが続き、運用などはしたく無いのでAWSに浮気してみましたのでその記録とTipsをまとめます

環境

  • 既存の物を使い回しでAzureのVMにembulkインストール、データロード等の制御に使います
  • データは Azure Blobに保存
  • AWS ElasticSearch Service (ver5.1が選択できたのでこれ)
  • 図解
    Load

セットアップ

実行

以下の手順で実施

  • BlobにElasticSearchに投入したいログをUploadしておく
  • embulkから吸い出し、ElasticSearch Serviceへ投げ込み用のconfig.yml作成
in:
  type: azure_blob_storage
  account_name: <BLOB NAME>
  account_key: <BLOB KEY>
  container: <CONTAINER NAME>
  path_prefix: <PREFIX as you like>
  decoders:
    - {type: gzip}
  parser:
    charset: UTF-8
    newline: CRLF
    type: jsonl
    schema:

out:
  type: elasticsearch_using_url
  mode: normal
  nodes:
    - url: "<YOUR ElasticSearch Domain>.us-east-2.es.amazonaws.com:80"
  index: "sample"
  index_type: "sample"
  • 実行
$ embulk preview config.yml
$ embulk run config.yml -l warn -r resume_state_aws.yml &>> embulk_awses.log

確認

  • AWS consoleで確認
    Load OK

まとめ

  • ElasticSearchへの大量のデータロードにはembulkは最適な手段
  • AWS ElasticSearch Serviceでは 9200 portが使えない為、embulk pluginの選択に注意、http経由で実施するのがおすすめ

余談

  • およその費用比較
AWS Azure GCP
費用 \$250 – $300 程度 \$530 未確認
  • VirtulMachineのサイズ、データ量は1TB程度

    • AWS

      • EC2: t2.midium x3 , \$150
      • EBS: 1TB, \$100
      • データ通信料
    • Azure
      • VM: D2v2 , \$84.82 x6 = $505.44
      • Elastic-Stack構築時、VMサイズを選択可能だが、D1v2(default)では使い物にならなかった
      • Blob: 1TB , \$24
      • データ通信料
    • GCP
      • 未検証

続きを読む

EC2のボリューム(EBS)容量拡張方法検証 (AmazonLinux)

結論を3行で

検証

【遂に来た!】EBS でボリュームサイズを変更できるようになりました(ボリュームタイプ変更も) | Developers.IO を参考に、稼働中のインスタンスにアタッチ済みのボリュームのサイズを増やしてみます。

今回ボリュームを増やしたいインスタンスはこのような感じです。

インスタンス的には/dev/xvdaというところに30GBのボリュームがあります。

ec2-user@ip-172-31-10-224 ~ $  df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/xvda1       30G  3.8G   26G  13% /
devtmpfs        490M   56K  490M   1% /dev
tmpfs           499M     0  499M   0% /dev/shm

ec2-user@ip-172-31-10-224 ~ $  lsblk
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0  30G  0 disk
└─xvda1 202:1    0  30G  0 part /

ほとんど元記事のとおりですが以下のような感じです。

スクリーンショット_2017-05-18_11_30_15.png

Modify Volumeを押します

スクリーンショット 2017-05-18 11.30.31.png

今回は 30GB -> 100GBにします

スクリーンショット 2017-05-18 11.32.51.png

パフォーマンスが変更されるまで時間がかかるとのこと。。yesを押す。

スクリーンショット 2017-05-18 11.33.00.png

完了。

スクリーンショット 2017-05-18 11.34.08.png

ボリュームの状態は optimizing...0% という表示になりますが、もうこの状態で ディスクの拡張は終わっています。

スクリーンショット 2017-05-18 11.34.46.png

awscli的には $ aws ec2 describe-volumes-modifications と叩くと進捗が表示されます(引数なしでOK)

$ aws ec2 describe-volumes-modifications
{
    "VolumesModifications": [
        {
            "TargetSize": 100,
            "TargetVolumeType": "gp2",
            "ModificationState": "optimizing",
            "VolumeId": "vol-0e92fb2e26dfd9687",
            "TargetIops": 300,
            "StartTime": "2017-05-18T02:34:07.151Z",
            "Progress": 0,
            "OriginalVolumeType": "gp2",
            "OriginalIops": 100,
            "OriginalSize": 30
        }
    ]
}

"Progress": 0 ですが、lsblk を叩くともう反映されていることがわかります。

ec2-user@ip-172-31-3-117 ~ $  lsblk
NAME    MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
xvda    202:0    0  100G  0 disk             # <- 100になってる
└─xvda1 202:1    0   30G  0 part /

次に resize2fs すればよいのですが、以下のような感じで怒られます。

resize2fs 1.42.12 (29-Aug-2014)
resize2fs: Device or resource busy while trying to open /dev/xvda
Couldn't find valid filesystem superblock.

今回はパーティションの設定がされているためと思われます。パーティションを利用している場合の設定はこちら。http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/storage_expand_partition.html
ただルートパーティションの場合は面倒そうなので、起動時に実行されるresize2fsに任せることにしました。

見たところAmazonLinuxの場合 /etc/cloud/cloud.cfg.d/00_defaults.cfg の中で resize2fs の記述があるので、再起動時に実行されるようです。

こうして、何も考えずにrebootすることにより、dfの結果が変わりました :tada:

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/xvda1       99G  9.1G   90G  10% /           # <- 99GBに増えてる
devtmpfs        490M   56K  490M   1% /dev
tmpfs           499M     0  499M   0% /dev/shm

続きを読む