AWS LambdaからEC2上のPythonで実行したffmpegが正常に起動しない

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

環境

実行用コードは下記のように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実行でパスが変わる原因を明らかにしないとすっきりしませんが・・・。

続きを読む

Amazon ECSを用いたDocker本番運用の実現

はじめに

現在お手伝いしているアカウンティング・サース・ジャパンにて、ECSを使ったDockerの本番運用を始めたので、その一連の流れについてまとめました。

税理士向け会計システムを扱うアカウンティング・サース・ジャパンでは最近Scalaでの新規プロジェクトが立ち上がってきており、既存のプロジェクトはJavaであったり、Erlangであったりと様々な言語が用いられていますが、インフラ人員が少ないということもあり、なるべくシンプルなインフラ構成を実現する必要がありました。

そういった中、各アプリケーションをDocker化することでインフラとしては共通基盤としてのDockerクラスタのみの管理になり、運用コストが下がるのではないかという仮説からDocker化を進めることになりました。クラスタを実現するに辺りKubenatesなどの選択肢もありましたが、今回はECSを選択し、下記のようにAWSのマネージドサービスを最大限に活用しています。

  • オーケストレーションツール: Amazon EC2 Container Service (ECS)
  • サービスディスカバリ: Application Load Balancer (ALB)
  • Dockerレジストリ: Amazon ECR
  • ログ、メトリクス収集: CloudWatch, CloudWatch Logs
  • 監視: CloudWatch Alarms
  • Infrastructure as Code: CloudFormation
  • CIツール: Jenkins

各技術の選定理由

今回Docker化を行うに辺り、下記を優先的に技術選定を行いました。

  • 運用が楽であること
  • 構成がシンプルで、技術の学習コストが低いこと

まずは、オーケストレーションツールの選定です。候補に上がったのは、Docker Swarm、Kubernetes、ECSです。

DockerのSwarm modeは本番での運用例が技術選定時点であまり見当たらなかったので候補から落としました。次にKubernetesとECSですが、海外の事例などではどちらも多く使われているようです。

今回は多機能さよりも運用に手間がかからない方が良いと考え、マネージドサービスであるECSが第一候補にあがりました。ここは詳細に調査したというよりも、ある種勢いで決めています。その上でやりたいことが実現できるかどうか一つ一つ技術検証を行った上で導入判断を行いました。

同じようにマネージドサービスを優先的に使ったほうが良いという考えで、ログなどでもCloudWatchを使っています。

AWSインフラをコードで記述するものとしてはTerraformが良く取り上げられている気がしますが、個人的にはいくつかの理由でCloudFormationを推しているのでこちらを使っています。

CIツールですが、社内の標準であるJenkinsをそのまま使うことにしました。

全体構成

下記のような構成になっています。

スクリーンショット 2017-05-21 12.46.39.png

ざっくりと説明すると、developmentブランチにプッシュするとGithub HookでJenkinsがDockerイメージをビルドして、ECRにPushします。ユーザはJenkinsでDeployジョブを実行(あるいはBuildの後続ジョブとして自動実行)し、CloudFormationにyamlファイルを適用することでTask, Service, ALB, Route53設定, CloudWatch設定を一通り実行します。またECSのClusterはあらかじめCloudFormationテンプレートを作成して作っておきます。

Task/Serviceの更新についてはCloudFormationを経由しない方がシンプルかとは思いまいしたが、Service毎に管理するRoute53やCloudWatchと合わせて一つのテンプレートにしてしまうのが良いと判断しました。

ここまでやるなら専用のデプロイ管理ツールを作った方がとも思ったのですが、業務委託という立場で自分しかメンテができないものを残すものは躊躇されたため、あくまでAWSとJenkinsの標準的な機能を組み合わせて実現しています。

CloudFormationテンプレートの解説

上記の流れが全てなので理解は難しくないと思いますが、一連の処理で重要なポイントとなるのはCloudFormationテンプレートなのでこれについてだけ触れておきます。長いテンプレートなのでざっくりとだけ雰囲気を掴んでもらえればと思います。

ECSクラスタのテンプレート

cluster作成用のCloudFormationテンプレートは下記のようになっています。

gist:cluster.yaml

一見複雑に見えますが、Amazon EC2 Container Service テンプレートスニペットを参考に作ると簡単に作成できると思います。

(あまりそのまま書くと会社に怒られそうなため)省略していますが、実際にはここにECSクラスタの監視を行うCloudWatch Alarmなどを設定することで、監視設定までこのテンプレートだけで完了します。

ECSクラスタはインフラチーム側であらかじめ用意しておき、リソースが足りなくなったときなどには適宜インスタンス数を変更したりクラスタ自体を別途作ったりしていきます。オートスケーリングを導入すればそれすら必要なくなります(今回はDocker運用が初めてだったので知見がたまるまで手動での対応にしています)。

インフラ側としての責務はここまでで、下記のテンプレートで定義される個別のサービスについてはアプリ開発者側の責務として明確に責任境界を分けました。(もちろん実際にはサポートはかなりの部分でしています。)

これにより全員が今までよりインフラに近い領域まで意識するように個人の意識が変わっていくことを期待しています。

個別サービス用テンプレート

開発環境、ステージング環境、プロダクション環境などそれぞれで同一のテンプレートを使うようにし、パラメータを使用します。そのパラメータをJenkinsのジョブ内で注入することで実現します。VPCなどの環境で決まる値はJenkinsジョブで実行するスクリプト内で定義し、アプリケーションごとの値は environment.yaml というファイルを用意してスクリプトから読み込みます。

environment.yamlは例えば下記のようになっています。アプリケーション開発者は、特殊なことをしない限りは service.yaml をインフラチームが用意したservice.yamlをコピーして、environment.yamlだけ編集すれば良い形になっています。DSLですら無いのでアプリ側のメンバーも心理的な抵抗が少ないようで良かったです。

environment.yaml
images:
- xxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/hoge-image
parameters:
  default:
    TaskMemory: 512
    TaskMaxMemory: 990
    ImageRepositoryUrl: xxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/hoge-image
    ServiceDesiredCount: 1
  dev:
    ClusterName: dev-default
    JavaOpts: "-Xmx256MB"
  stg:
    ClusterName: stg-default
    JavaOpts: "-Xmx256MB"
  prod:
    ClusterName: default
    JavaOpts: "-Xmx1500MB -Xms1500MB"
    TaskMemory: 1990
    TaskMaxMemory: 1990
    ServiceDesiredCount: 2

そして service.yaml は下記のようなファイルです。

gist:service.yaml

これもAmazon EC2 Container Service テンプレートスニペットから作ればすぐにできるのではないかと思います。(もちろん全てのパラメータは一つ一つ値を検討します。)

こちらもCloudWatch周りや重要でないところは削除しています。色々と手で削ってるのでコピペだと動かない可能性大ですが雰囲気だけ掴んで貰えればと思います。

このファイルは全アプリケーションで同一ファイルを使うのではなく、アプリケーションごとにコピー/編集して利用します。全体の変更を行うときには全プロジェクトのファイルを更新しなければいけませんが、共通基盤がアプリケーション側を制約しないように、プロジェクト毎のyamlファイル管理としています。ファイルの配置場所は各Gitリポジトリに配置するのが理想ですが、現状ではDocker運用になれてくるまで全てのyamlファイルを管理するリポジトリを作成してインフラチーム側が主に編集する形を取っています。

デプロイ

あとは、このservice.yamlとenvironment.yamlを組み合わせてデプロイするRubyスクリプトでもJenkinsのPipelineのコードでも適当に書いてJenkinsのJobを登録すれば完了です。(environment.yamlファイルを読み込んで aws cloudformation create-stack でservice.yamlと共にパラメータとして渡すだけなので簡単です!)

新規アプリ開発時も社内標準のservice.yamlとenvironment.yamlをファイルを持ってきて、environment.yamlを修正した上で、Jenkinsにジョブを登録すればすぐにDockerクラスタへのデプロイ準備が整います。しかも、上記のテンプレート例では割愛していますが、テンプレートには監視項目/通知設定まで書かれているので、インフラ側で設定を行う必要もなく監視が開始されます。CloudFormation最高ですね。

おわりに

実際の運用ではミッションクリティカルなアプリケーションならではの品質管理のために、JenkinsのPipeline機能を利用して開発→検証→リリースまでのデプロイメントパイプラインを実現しています。

アプリケーションのSECRETなどコミットしない情報をどう管理するかも検討する必要がありますが、これは管理の仕方はチームによって異なると思ったため割愛しています。

また、ログ解析としてはS3に出されたALBのログをRedash+Amazon Athenaでエラー率やアクセス数を分析できるようにし、CPU使用率やメモリ使用率などのパフォーマンス状況をCloudWatchの内容をGrafanaで可視化しています。これによりログ収集の基盤などを作らずに必要な可視化を実現することができました。ベンチャーでは分析基盤の運用も大きなコストになってしまうため、こういった工夫も必要です。(もちろん重要なKPIについては別途分析する仕組みが整っています。)

今回の構成が最高とは思いませんが、ある程度満足行くところまではできたかなと思います。もっとよくできるよ!とか一緒にやりたいな!とかもっと詳細聞きたいな!いう方はぜひ @miyasakura_ までご一報ください。

続きを読む

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"
            }
          ]
        }
    ]
}

続きを読む

〇FLAGの中の人に憧れてMastodon×AWSでモンストドン作ってみた

Mastodon立ち上げたらいい会社に入れると聞いて、邪な気持ちで。。。いや、Mastodonとモンストって相性よさそうだなぁと思いたち、少し乗り遅れた感をかもしだしながら、フルにAWSを使って規模拡大しても大丈夫な構成で作ってみた。

モンストドン (https://monstdn.com)

構成

monstdn.png

最小構成のざっくり料金($1=113円、1ヶ月30日計算)

サービス 単価 月額料金
ALB 1台 × $0.0243/1H + データ転送的なの  約2000円 + α
EC2 2台(t2.nano) × $0.008/1H + データ転送的なの 約1300円 + α
RDS 1台(db.t2.micro シングルAZ) $0.028/1H + データ転送的なの 約2300円 + α
ElasticCache 1台(cache.t2.micro) $0.026/1H + データ転送的なの 約2100円 + α
S3Bucket $0.025/GB + リクエスト数的なの + α
SES $0.10/1,000通あたり + データ転送的なの + α
合計     ( 約7700円 + α なので ) ざっくり1万ぐらい

※無料枠があるので1年目はもう少しやすくできそう

やったこと

  • AWSのアカウント作成
  • IAMの作成とアカウントの初期設定(二段階認証とか、パスワードポリシーとか)
  • Route53でドメインを買う
  • SESでメール設定と制限解除申請
  • ACMの取得(無料でHTTPS通信)
  • S3バケット作成(画像とかのアップロードファイルの配信用)
  • VPCとセキュリティグループの作成
  • SES、S3へアクセスする為のIAMユーザの作成
  • ElasticCacheでRedisの作成
  • RDSでPostgreSQLの作成
  • EC2でCentOSを使ってMastodonの構築(下に詳細)とイメージ(AMI)の作成
  • AutoScallingの設定
  • ALB(ApplicationLoadBalancer)の作成(ACMをつける)
  • Route53でHostZoneのレコード設定

CentOSでのMastdon構築(20170517現在)

sudo su -
yum -y update
yum -y install vim

localectl set-locale LANG=ja_JP.utf8
localectl set-keymap jp106
localectl status

timedatectl set-timezone Asia/Tokyo
timedatectl status

dd if=/dev/zero of=/mnt/swapfile bs=1M count=2560
mkswap /mnt/swapfile
swapon /mnt/swapfile
chmod 0644 /mnt/swapfile
echo "/mnt/swapfile                             swap                    swap    defaults                0 0" >> /etc/fstab
free

vim /etc/sysconfig/selinux
 SELINUX=enforcing
 ↓
 SELINUX=disabled

systemctl disable postfix
systemctl disable auditd.service

yum -y install libxml2-devel ImageMagick libxslt-devel git curl nodejs file
yum -y install epel-release
rpm --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro
rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm
yum -y install ffmpeg ffmpeg-devel

yum -y group install "Development tools"
curl -sL https://rpm.nodesource.com/setup_4.x | sudo bash -

yum -y install nodejs
npm -g install yarn

yum -y install postgresql postgresql-contrib postgresql-devel
yum install -y openssl-devel readline-devel

useradd mastodon
passwd mastodon
su - mastodon
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
cd ~/.rbenv && src/configure && make -C src && cd ~
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile && source ~/.bash_profile
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
rbenv install 2.4.1 && rbenv global $_ && rbenv rehash

# 確認
ruby -v

cd ~
git clone https://github.com/tootsuite/mastodon.git live
cd live
git checkout $(git tag | tail -n 1)

gem install bundler
bundle install --deployment --without development test
yarn install --pure-lockfile

cp .env.production.sample .env.production
sed -i "/^PAPERCLIP_SECRET=$/ s/$/`rake secret`/" .env.production
sed -i "/^SECRET_KEY_BASE=$/ s/$/`rake secret`/" .env.production
sed -i "/^OTP_SECRET=$/ s/$/`rake secret`/" .env.production

vim .env.production
#Redis,Postgresql,言語,SMTP,S3の設定

RAILS_ENV=production bundle exec rails db:setup
RAILS_ENV=production bundle exec rails assets:precompile

exit

cat << "_EOF_" > /etc/systemd/system/mastodon-web.service
[Unit]
Description=mastodon-web
After=network.target

[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/live
Environment="RAILS_ENV=production"
Environment="PORT=3000"
ExecStart=/home/mastodon/.rbenv/shims/bundle exec puma -C config/puma.rb
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target
_EOF_


cat << "_EOF_" > /etc/systemd/system/mastodon-sidekiq.service
[Unit]
Description=mastodon-sidekiq
After=network.target

[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/live
Environment="RAILS_ENV=production"
Environment="DB_POOL=5"
ExecStart=/home/mastodon/.rbenv/shims/bundle exec sidekiq -c 5 -q default -q mailers -q pull -q push
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target
_EOF_

cat << "_EOF_" > /etc/systemd/system/mastodon-streaming.service
[Unit]
Description=mastodon-streaming
After=network.target

[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/live
Environment="NODE_ENV=production"
Environment="PORT=4000"
ExecStart=/usr/bin/npm run start
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target
_EOF_

systemctl enable mastodon-{web,sidekiq,streaming}
systemctl start mastodon-{web,sidekiq,streaming}

cat << "_EOF_" | crontab -
RAILS_ENV=production
@daily cd /home/mastodon/live && /home/mastodon/.rbenv/shims/bundle exec rake mastodon:daily > /dev/null
_EOF_

yum -y install nginx

cat << "_EOF_" > /etc/nginx/conf.d/mastodon.conf
map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

server {
  listen 80;
  listen [::]:80;
  server_name {domainName};

  keepalive_timeout    70;
  sendfile             on;
  client_max_body_size 0;

  root /home/mastodon/live/public;

  gzip on;
  gzip_disable "msie6";
  gzip_vary on;
  gzip_proxied any;
  gzip_comp_level 6;
  gzip_buffers 16 8k;
  gzip_http_version 1.1;
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

  add_header Strict-Transport-Security "max-age=31536000";

  location / {
    try_files $uri @proxy;
  }

  location @proxy {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";
    proxy_pass_header Server;

    proxy_pass http://127.0.0.1:3000;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    tcp_nodelay on;
  }

  location /api/v1/streaming {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";

    proxy_pass http://localhost:4000;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    tcp_nodelay on;
  }

  error_page 500 501 502 503 504 /500.html;
}
_EOF_

systemctl enable nginx
systemctl start nginx

# ユーザ登録後 admin設定
RAILS_ENV=production bundle exec rails mastodon:make_admin USERNAME={UserName}

メモ

EC2のDiskはSSDで(swapで使う)
ロードバランサーはApplicationの方じゃないとwebSocketがうまくいかない
コミュニティベースのシステムだからCloudFrontはあまり必要性感じなかったので使わなかった
(日本向けだしS3のバケット東京リージョンにあるし、S3もかなり性能いいし)
もしCloudFrontを使うなら、websocketできないからS3の前に置く感じ
今回CloudFrontの利点があるとすれば”ドメイン”が自分の使えることぐらいかな
CentOSじゃなくてAmazonLinux使いたかったけど、ffmpeg入れるのにやたら時間かかったからやめた。一応動いたけど(純正AWSが。。。)
DockerはDeployまで楽そうだけど、効率よくなさそうだったのでやめた
AWSでDocker使うならECSでやってみたいけど、Mastodonはすんなりできるのかなー
セキュリティ的にはロードバランサーからの80番ポートしか受け付けないように制御してるから大丈夫かな。
sshでのログインは同じVPC内に踏み台サーバ立ててと。

最後に

ここまで読んで頂きありがとうございます。
技術的なことを投稿するのはこれが初めてですが、だれかのお役にたてれたら嬉しいです。
普段はPHPとAWS少しいじる程度なのでいい勉強になりました。
ほとんど公開されている文献をもとにプラモデル感覚で作りましたので、ご指摘等あればコメント頂ければと思います。
個人でのサイト運用となりますので、落ちたらごめんなさい。

続きを読む

AWS-EC2のp2インスタンスでchainer-goghの画風変換をGPU実行してみた

はじめに

DeepLearningの実践入門としてAWS-EC2のp2インスタンスを使用して、chainer-goghの画風変換をGPUで実行してみました。
結論としてp2.xlargeを使用してサンプル通りの画風変換を実行すると、5分程度で処理が完了します。
参考にさせていただいたchainer-goghのGitHub

環境

クライアントPC : macOS Sierra 10.12.4
サーバ : EC2 p2.xlarge (時間課金なので注意してください。)

事前準備

GPUコンピューティングのインスタンスを立ち上げる場合、デフォルトでは作成できないためAWSの担当者に問い合わせを行い、領域を拡張してもらってください。
領域の拡張はEC2ダッシュボードを開いて、左側のメニューにある「制限」から p2.xlarge の規制緩和の依頼ができます。

p2インスタンスの作成

今回はディープラーニングで使用する諸々のツールがインストール済みのAMIを使用します。
※私は”バージニア北部”リージョンを使用しています。
EC2ダッシュボードから「インスタンスの作成」 → 「AWS Marketplace」の順にクリック。
検索窓に「Deep Learning AMI」を入力して検索し、「Deep Learning AMI Amazon Linux Version」のAMIを使用してください。
その後のインスタンスの作成作業は通常のEC2と同じですので、ここでは割愛させていただきます。

環境構築

立ち上げたp2インスタンスにsshで接続して環境構築を実施します。

Pythonのインストール

「Deep Learning AMI Amazon Linux Version」を使用すると最初からPythonの2系と3系がインストールされているので、インストール作業は不要です。
※今回はPython3系を使用します。

Gitのインストール

こちらも最初から入っているので不要です。

Chainerのインストール

下記のコマンドでインストールできます。

sudo pip3 install chainer

Chainer-goghのクローン

必要であれば予めGitHub上で自分のリポジトリにChainer-goghをforkしておいてください。
chainer-goghをサーバにcloneします。

git clone https://github.com/xxxxxxxxx/chainer-gogh.git

モデルのダウンロード

NINモデルをダウンロードしてください。(下記リンク先のDropBoxのURLからダウンロードできます。)
https://gist.github.com/mavenlin/d802a5849de39225bcc6

ダウンロードしたファイルをクライアントマシンからサーバに送信してください。

scp -i xxxxxxx.pem xxxxxxxx/nin_imagenet.caffemodel ec2-user@host:~/chainer-gogh

出力用ディレクトリを作成

cd chainer-gogh; mkdir output

画風変換の実行

コマンドで実行

実行には5分程度かかります。

python3 chainer-gogh.py -m nin -i sample_images/cat.png -s sample_images/style_1.png -o output -g 0

実行結果をダウンロード

ローカルのターミナルでコマンドを実行。
このコマンドではim_04950.pngをDownloadディレクトリ配下にダウンロードします。
このファイルが画風変換した最終結果になります。
100イテレーションずつ途中結果も出力されますので、outputディレクトリを確認してください。

scp -i xxxxxxx.pem ec2-user@xxxxxxxxxx:~/chainer-gogh/output/im_04950.png ~/Download/im_04950.png

後処理

EC2は時間課金になります。
確認が終わったらインスタンスを削除するか停止してください。

まとめ

今回はchainer-goghを使用した画風変換の環境をp2インスタンスで作成し、GPU実行できるところまで確認しました。
AWSを使用することで手元にGPUマシンを用意しなくても手軽に使用できるので大変便利です!

続きを読む

AWS Lambdaデプロイ方法を求めて: Codeship

このドキュメントレベル:初めて学ぶ人向け

Lambdaの良いデプロイフローはないかと思って調べた記録です。
Lambdaのデプロイにはいくつか種類があるようです。

Serveless
Apex
Lamvery
LambCI
CodeShip

前回は「Serveless」AWS Lambdaデプロイ方法を求めて:Serverlessフレームワーク – Qiitaについて調べましたが、
今回は「CodeShip」について、どんなものかを触りだけ調べています。

Codeship

stackshareのDevOpsでなかなか人気もののようです

Codeship

無料枠で使いましょう。

スクリーンショット 2017-05-11 7.16.13.png

■ 注意しないといけない点

Codeshipでは Lambda関数を事前に作っておかなくてはいけません!
あと、エイリアス「PROD」も作っておかないと、後半のデプロイで失敗してしまいます。

■ AWSコンソールで、lambda関数を作成

ここで、コミットするハンドラー名称と関数名と合わせておくようにしましょう

lambda関数

  • sample-lambda関数
  • handler.jsのhelloを呼び出します、なのでハンドラ名は「handler.hello」にします
  • エイリアス「PROD」を作成
  • テスト実行

use strict';

module.exports.hello = (event, context, callback) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Good Good Morning CodeShip!!Your function executed successfully!',
      input: event,
    }),
  };

  callback(null, response);

};

■ Codeshipにサインアップでプロジェクトを作成します

プロジェクトタイプはBasicを選択

プロジェクトタイプの洗濯

今回はGitHubと連携してデプロイします
GitLabとも連携できますね!

GitHub連携

テストのパイプラインも設定できます。
今回は、「npm test」だけ実行します

なので、「package.json」側にとりあえず通るダミーを設定しています。

テストパイプライン

package.json


{
  "name": "sample-lambda",
  "version": "1.0.0",
  "description": "",
  "main": "handler.js",
  "scripts": {
    "test": "echo "OK" && exit 0" ★無理やりテスト通していますw
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/bohebohe/sample-lambda.git"
  },
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/bohebohe/sample-lambda/issues"
  },
  "homepage": "https://github.com/bohebohe/sample-lambda#readme"
}

gitHubのsample-lambdaリポジトリのマスターと連携します

gitHubのマスターと連携します

連携するgitHub
今回はserverless.ymlは不要です・・・。前回の実験の名残です。

連携するgitHub

準備できました!

sample-lambda

■ Codeshipでのデプロイ設定

デプロイできるIAMユーザーを作成します。
そのユーザーに対して、以下のポリシーをアタッチしてください。
ARNの部分を書き換えてください

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "lambda:UpdateFunctionCode",
                "lambda:UpdateFunctionConfiguration",
                "lambda:InvokeFunction",
                "lambda:GetFunction",
                "lambda:PublishVersion",
                "lambda:UpdateAlias"
            ],
            "Resource": [
                "arn:aws:lambda:YOUR_AWS_REGION:YOUR_AWS_ACCOUNT_ID:function:YOUR_FUNCTION_NAME"
            ]
        }
    ]
}

作成したユーザーのAWSのアクセスキーをCodeShipの環境変数に設定します

CodeShipの環境変数

デプロイのためのCustomScriptを書きます

CustomScript

CustomScript

■ gitHubにpush

gitHubのpushをトリガーにして、テストが実行され、OKであればデプロイされて、結果が表示されます。

ダッシュボード

感想

お手軽感はありますね。ただ、事前にlambda関数を作成したりと一手間かかるので、全てyamlでというserverless方式とはちょっと違います。
コードを苦手な人向け?なのかな。

参考サイト

続きを読む

AWSが不正利用され300万円の請求が届いてから免除までの一部始終

前置き

情けないことだが、自身の過失により、GitHubで長年Privateリポジトリで運用していたリポジトリを、とある事情でpublicに変更したのだが、その中にAWSのS3のアクセスキーとシークレットキーがファイルに直接ハードコーディングされているのにすっかり気づかず、自身のAWSのアクセスキーとシークレットキーが流出してしまうという失態を起こしてしまった。

その不正利用により届いた請求金額は約300万円。請求を見た時は頭が真っ白になり冷や汗ものだったが、過去に同様のミスとその状況と対応をまとめてくださっていた方々のおかげで、なんとか深呼吸して対応することができたので、自分も少しでも今後起きうる同様の状況の方に対する助けになればと、一部始終を共有しておくことにしようと思う。

初心者がAWSでミスって不正利用されて$6,000請求、泣きそうになったお話。
AWS で不正アクセスされて凄い額の請求が来ていた件

流出発覚〜色々と対応

あるPrivateリポジトリをpublicリポジトリに変更してから1週間ほど経った頃、AWSより英語で

AWSアカウントで、不正アクセスのパターンに一致する不規則なアクティビティが検出されました。アカウントにログインして、すべてのリソースが正当なものであることを確認してください。

なるメールが届いた。自身のAWSコンソールを確認すると、見知らぬEC2インスタンスが大量に稼働し、約300万円分もの請求が自分のアカウントへ記載されており、直ぐに何が起きたかを悟った(同時にそれを引き起こした原因の自分の失態も)。

この辺りは、直ぐに起こした行動は半分テンパりながら作業していたこともあり、記憶がやや曖昧で的を得ないことを書いているかもしれないが、ご了承を願いたい。(因みにこの時点では筆者のAWSに対する知見はあまり深くない。)

まず自分の場合は確認した請求額に東京リージョン以外の全てのリージョンでc4.8xlargeのインスタンスの稼働によるサービス料金がプラスで請求に記載されていた。その額およそ300万円。通常なら毎月1万円以下の請求金額だったので、さすがにこの数字を見た時は、頭を突然殴られたかのような衝撃だった。
スクリーンショット 2017-05-04 2.27.39.png

まずは、直ちにGithubの該当リポジトリをprivateに戻し、AWSのインスタンスの状況を見ようと、EC2のコンソールから各リージョンを変えてみて、インスタンス稼働状況を見てみるも、問題のc4.8xlargeのインスタンスたちが稼働している様子は見受けられない。いつも通り東京リージョンに自分の作成したインスタンス以外は稼働していないように見えた。(ここに関しては今も謎で、なぜそうなのかわかっていない。とにかく焦っていたので、自分が何か勘違いしている?)

ただ、各リージョンで発覚の数日前に作られた不審なセキュリティグループがあるのが確認できたので、そのセキュリティグループを消していった。その後、色々と見ているとAWS Identity and Access Management(IAM)より、同じく発覚の数日前に作られた不審なアクセスキーを発見する。これも直ちに削除した。因みに他の自分が作ったアクセスキーはこの不審なアクセスキーが作られた日に勝手に削除されていた。(流出させてしまったキーも削除され無効になっていた)

恐らくこの発覚からの初動でやったのはこの動作であったはずである。そんなこんなで色々と自身のAWSをパトロールしていたら、AWSより再び英語にて、

公開されたアクセスキーを削除するための迅速な対応をしていただきありがとうございます。すべての不正なAWSの使用が削除されているように見えるので、私はあなたに代わって払い戻しリクエストを提出しました。リクエストの状況をお知らせするため、7-10営業日後に再度ご連絡いたします。

とのメールが追伸で届いた。ここまでは発覚から1時間くらいの出来事である。このメールをもらって「一旦は安心なのか? 払わなくていい可能性があるのか?」とちょっと安心し、落ち着いて物事を考えられるようになってきた。

過去の事例を調べる〜日本語で問い合わせ

何とか応急処置は出来たっぽいものの、あまりに不安なので、過去の同じような事例を調べていて、下記の2つにたどり着いた。

初心者がAWSでミスって不正利用されて$6,000請求、泣きそうになったお話。
AWS で不正アクセスされて凄い額の請求が来ていた件

こちらはどちらも同じ状況になり、結果的に不正利用分の請求は免除となっているので、少し心が救われた。300万の支払いが回避できる希望が見えつつもやはり、まだかなり不安。(こちらのお二方には本当に心が救われました。発覚当時の不安を和らげてくれて、大変ありがとうございますm(_ _)m)

AWSに問い合わせてみようにも、何と発覚時はGW初日。平日のみの問い合わせ対応で後5日間待たなければならない。しかし、月1万円のビジネスサポートプランだと年中無休で1時間以内二返信をくれるらしいので、300万支払いのリスクを少しでも減らせるなら、と即時プランを購入して問い合わせてみた。結果的に1時間以内に返信をもらえるのはアカウントサポートではなく、技術サポートだけだったが、しっかりと無駄に動いてるEC2インスタンスはないことなどを確認してもらえたり、不正使用の際に行うべきマニュアル日本語版をもらえたり(英語版は前の英語メールに付属されてきていた)とサポートプランを買ってよかった。

日本での同様の事例は先述の2件だけだったが、色々不安をぬぐい去るために、海外事例も調べてみた。

My AWS account was hacked and I have a $50,000 bill, how can I reduce the amount I need to pay?
Dev put AWS keys on Github. Then BAD THINGS happened
Ryan Hellyer’s AWS Nightmare: Leaked Access Keys Result in a $6,000 Bill Overnight

「全部、無事に支払いを免除されている!まだ、不正利用の金額を支払うことになったという記事はまだ見ていない」 だいぶ希望が湧いてきた。(まぁでも支払うことになったらわざわざブログとかには書かないか…)
どうやらどこも共通して免除の審査は1~2週間ほどかかるそうだ。もう審査の結果が出る1~2週間の間まで、本件に関してはなるべく考えないようにしよう。
(因みにここを執筆の時点でまだ結果は出ていないが、これで免除にされなかったらどう生きるか。。そもそも全貯金出しても300万は支払えない。。。)

審査待ちの間に反省、セキュリティの強化、対策の模索

二度と本件のような失態を犯さないため、なるべく対策を立てたい。あまり詳しくなかったAWSのセキュリティなども少し調べてみた。

・まずコードレベルで秘密鍵などの情報はファイルにハードコーディングで直接書かない(当然)。環境変数などに入れる。
・AWSのMulti-Factor Authentication(MFA)の二段階認証を利用する
・全権限を持つルートユーザーを使わず、IAMユーザーを作成し、そのIAMユーザーにて、AWSを使用する。
「git secret」と言うパスワードなどの機密情報がコミットされることを防ぐライブラリの使用(Amazonがメールで教えてくれましたm(_ _)m)
・あとは一定コストを超えると通知が来るような設定もAWSはできるようなので、それを設定するなど。

今回の失態で、今まで軽視していたことを否めないセキュリティに関しては痛い目を見て、懲りたので、AWS(や、その他セキュリティ)に関してはまだまだ未熟な部分も多く、もっとこれから真剣に調べていかねば。。

また、数日後に日米ともにAWSサポートセンターからメールが届き、「申請の結果が出るまでサポートいたしますので、なんでも聞いてください」と非常に丁寧に対応いただけました。ありがとうございます。

免除審査結果の通知

発覚から8日ほど経った日、米国AWSから通知が。書き出しは「Great News!! 免除申請が許可されました(英語)」という内容のメールが届く。その後、日本のAWSからも(日本にも問い合わせていたので、)「無事に申請が許可されました。」とのメールが届きました。

日本のメールからには、非常に丁寧にどの部分が免除されたのかなどの情報が記載されており、1$にも満たないデータ転送料のような箇所まで丁寧に金額免除していただけました。そして、最後に「免除は今回限りになります」との注意書き。それはそうである。。今回免除していただいたことだけでも本当に感謝しかないです。

今回、AWSさんには自分のミスにより、損害を発生させてしまったことは重々反省し、今後AWSさんへ恩返しできるように努めていきたいと思います。

続きを読む

G Suiteの認証を介してAWSの一時認証クレデンシャルを取得する

説明

AWSコンソールへのログインがG SuiteのSAML2.0フェデレーションを介して行えるようになり、コンソールへのログインは
ユーザ管理の手間が減りました。が、個々のユーザがAWS CLIなどでAPIを呼出す時に認証にはまだ固定のキーが必要な状況でした。
これをやめてキー管理からも開放されたいと思います。
ここでは、AWSブログにあったsamlapi_formauthの内容を参考にG Suiteのログインをスクリプトで実行して、SAMLレスポンスを
受け取る用にしました。

準備

  • python2.7系が動く環境
  • jsを動かすためのブラウザとしてPhantomJSをインストール

    $ brew install phantomjs
    
  • その他以下のライブラリをインストール

    • selenium
    • requests
    • lxml
    • beautifulsoup4
    $ pip install selenium
    $ pip install requests
    $ pip install lxml
    $ pip install beautifulsoup4
    

    ※pipに関しては各自使えるようにしておいてください。

  • githubからクローンする

    $ git clone .......
    
  • credentialsの確認
    現状、~/.aws/credentialsの状態が以下の状態であることを前提とします

    [deafult]
    access_key_id =       # ここは何も設定されていなくてもOK
    secret_access_key =     # ここは何も設定されていなくてもOK
    
    [some_profile]
    access_key_id = xxxxxx
    secret_access_key = xxxxx
    

使い方

$ python gsuite_samlapi_formauth.py
:
username: xxxxxxxx
password: 
:
:

[0] xxxxxxx/xxxxxxxx

select hogehoge
0

:

スクリプトを実行するとユーザ名/パスワードの入力を求められます。こちらは、ログイン情報を入力してください。
その後、ロールの選択を行います。番号を入力してください。
一時認証の取得に成功した場合は、該当のアカウントのS3のバケット一覧が表示されます。

取得した結果は、~/.aws/credentialsのセクション[saml]へ書き出されます。
以降、1時間以内は下記のようにコマンドを実行可能となります。

$ aws --profile saml s3 ls

残念ながら現在のAWSの仕様上一時認証情報の有効期間は、1時間となっています。:cry:
あと、スクリプトの実行は遅いです…。なんとかなんないかな?

続きを読む