Shoryuken を導入しようとして諦めた話

Rails で非同期処理といえば、 Sidekiq, Resque, DelayedJob あたりが有名かと思います。
DelayedJob は RDS をジョブキューとして利用できる1ため、インフラの準備が不要で比較的ライトに導入できますが、数万件のジョブを登録しようと思うと RDS にかなりの負荷がかかりますし、Insert もそれほど早くないため、ジョブキューとしてはあまり適しているとは言えません。
Redis などのインメモリーデータベースを用いれば、RDS に負荷をかけず、Insert の高速化も見込めますが、万が一 Redis がクラッシュした際には登録されていたキューは全て無くなりますし、そういった障害に対応するためには幾らかの運用コストが発生します。

ところで、 AWS には SQS というキューイングサービスが提供されています。
安価で堅牢な造りになっており、運用コストも低そうだったのでジョブキューとしては最適なように感じましたが、SQS をジョブキューとして利用している非同期処理と言えば Shoryuken くらい2です。

今回、DelayedJob からの移行先として、Shoryuken が使えないか検討したのですが、いくつかの理由により結局諦めた、という話を書いてみます。

環境

  • Ruby 2.4.1
  • Ruby on Rails 5.0.2
  • Shoryuken 3.0.6

Shoryuken とは

SQS を使った Job queue worker。ActiveJob にも対応。
Sidekiq を意識して作られており、スレッドベース。

しょーりゅーけん!
GitHub Shoryuken

なぜ Shoryuken が良いと思ったのか?

  • SQS は安い

    • 100万 requests / month まで無料
    • 1億 requests / month で $39.60 (およそ 4,300円)
  • SQS だとインスタンスタイプの変更などの運用が発生しない
  • SQS 側でリトライをサポートしている
    • 一定時間内に処理が正常に完了しなかった場合、再度 Queue に入る
    • 一定回数失敗した場合、Dead letter queue に移動される
    • Redis の場合、 Sidekiq の worker プロセスが吹っ飛んだ場合にジョブが失われる恐れがある
      • Redis 本体が死んでもデータは吹っ飛ぶ
      • Sidekiq Pro ($950 / year) ならリカバリ可能らしい
  • 昇龍拳だから

SQS の仕組み

キューの種類

標準キュー

Amazon SQS のデフォルトのキューの種類は標準です。標準キューでは、1 秒あたりのトランザクション数はほぼ無制限です。標準キューではメッセージが少なくとも 1 回配信されることが保証されます。ただし (高いスループットを可能にする、徹底して分散化されたアーキテクチャであるために) ときとして、 メッセージのコピーが乱れた順序で複数配信されることがあります 。標準キューでは、メッセージが通常は送信されたのと同じ順序で配信されるようにする、ベストエフォート型の順序付けが行われます。
SQS よくある質問 – 標準キューと FIFO キューの違いは何ですか?

標準キュー

乱れた順序で複数配信されることがあります。

つまり、稀に複数の worker が同じ Job を実行してまう可能性があるということ。
また、Queue と言ってるけど、順番は保証しない(ベストエフォートなので一応は考慮してる)ということ。

複数回同じジョブが実行されてしまうのは困るけど、ジョブの処理の中で RDS などに処理の進行状態を保存して、複数回実行されないような実装をすれば回避可能。というか、 そういう実装が必須 だと思います。

一番の強みはトランザクション数がほぼ無制限なので、複数の worker から同時にアクセスしまくっても全然大丈夫です。

FIFO キュー

FIFO キューは、標準キューを補完するものです。このキュータイプの最も重要な特徴は、FIFO (先入れ先出し方式) 配信と 1 回限りの処理です。メッセージが送受信される順序は厳密に保たれます。メッセージは 1 回配信されると、消費者がそれを処理して削除するまでは使用できるままとなり、キューで重複が起きることはありません。FIFO キューはメッセージグループもサポートしているため、1 つのキュー内に複数の順序付けられたメッセージグループが可能です。FIFO キューは、1 秒あたり 300 トランザクション (TPS) に制限されていますが、標準キューの機能をすべて備えます。
SQS よくある質問 – 標準キューと FIFO キューの違いは何ですか?

FIFOキュー

キューの順序と1回限りであることを保証する代わりにトランザクション数が 300 TPS に制限されたバージョン。 EC サイトの決済処理とかで使う、みたいな例が書かれていました。

1 秒あたり 300 トランザクション = 1 分あたり 18000 トランザクション。
現在開発中のシステムでは要件として5分で 10000 件の処理を目標にしているジョブだったので、これでは物足りない感じ。

可視性タイムアウト (Visibility Timeout)

コンシューマーがキューからメッセージを受信して処理しても、そのメッセージはキューに残ったままです。Amazon SQS では、メッセージは自動的に削除されません。これは分散システムであるため、コンポーネントがそのメッセージを実際に受信するという保証がないからです (接続が切断されたり、コンポーネントでメッセージの受信に失敗する可能性があります)。そのため、コンシューマーはメッセージを受信して処理した後、キューからメッセージを削除する必要があります。
SQS 開発者ガイド – 可視性タイムアウト

可視性タイムアウト

引用の通りですが、SQS には可視性タイムアウトという設定値があって、Shoryuken の worker が SQS からメッセージを受け取ると、SQS 側はメッセージを一定時間、論理削除します。
worker はジョブが完了すると SQS 側にメッセージの削除要求を投げて、論理削除されていたメッセージを物理削除します。
もし、 worker 側で処理に失敗、または可視性タイムアウトの設定時間を経過しても処理が終わらなかった場合、SQS 側で論理削除を解除して、次のリクエストでメッセージを再配信します。
この仕組みがあるため、 Shoryuken 側でリトライを設定していなくとも、失敗した処理は再度実行されるようになります。
万が一 worker のプロセスが死んでしまっても、 SQS 上でメッセージはちゃんと生きている訳です。

worker の処理時間が長すぎて可視性タイムアウトの時間を過ぎてしまうのは問題なので、 2 分かかる処理なら可視性タイムアウトを4分に設定する、などがセオリーのようです。

なお、Shoryuken には auto_visibility_timeout というオプションがあって、これを true にしておくと、可視性タイムアウトに達する 5 秒前に可視性タイムアウトの延長をしてくれるようです。

遅延キュー

遅延キューを使用すると、キューにある新しいメッセージの配信を指定の秒数延期できます。遅延キューを作成した場合、そのキューに送信したすべてのメッセージが遅延期間の間コンシューマーに表示されなくなります。DelaySeconds 属性を 0 ~ 900 (15 分) の任意の値に設定することで、CreateQueue アクションを使用して遅延キューを作成できます。
(中略)
遅延キューは、メッセージを一定の時間コンシューマーが使用できなくするため、可視性タイムアウトと似ています。遅延キューと可視性タイムアウトの違いは、遅延キューの場合、メッセージが最初にキューに追加されたときに非表示になるのに対して、可視性タイムアウトの場合、メッセージがキューから取得された後のみ非表示になるという点です。
SQS 開発者ガイド – 遅延キュー

遅延キュー

ActiveJob ではオプションとして #set(wait: 1.minute) などが使用できますが、 Shoryuken でこの実現に用いられるのが遅延キューです。SQS 側で遅延時間に 0 ~ 15 分という制約があるので、 Shoryuken では 15 分以上の遅延実行はサポートされていません。

デッドレターキュー (Dead letter queue)

Amazon SQS では、デッドレターキューがサポートされます。デッドレターキューは、正常に処理できないメッセージの送信先として他の (送信元) キューが使用できるキューです。これらのメッセージは、処理が成功しなかった理由を判断するためにデッドレターキューに分離できます。
SQS 開発者ガイド – デッドレターキュー

これも引用の通りですが、 SQS 側で設定しておけば一定回数失敗したメッセージが別のキューに自動的に移動されるようになります。失敗回数は 1~1000 で設定でき、これで失敗時のリトライ回数の上限が設けられるようです。

おそらく前述の可視性タイムアウトで失敗した回数だと思うのですが、 SQS のドキュメントでは記述が見つからなかったので未確認です。

結局使うのは諦めました

SQS の堅牢さには心惹かれるものがあったので、ぜひ利用したかったのですが、いくつかの理由により見送ることにしました。

Shoryuken での設定値の共有がイマイチ

config/shoryuken.yml に AWS の設定などを記述するのですが、どうやらこれは worker が参照するための設定らしく、Rails アプリ側ではこの値を読み込んではくれないようです。なのでジョブの登録時に AWS の設定が反映されておらず、エラーになりました。

Shoryuken の wiki によると、AWS クライアントのインスタンス作成処理を Shoryuken に任せずに自分で作るなどすれば良いようですが、せっかく config/shoryuken.yml が存在しているのだから、 Shoryuke 側で反映させて欲しいな、と思いました。
なお、参考先のサイト では、Rails の config/initializersShoryuken::EnvironmentLoader.load(config_file: "config/shoryuken.yml") を実行して Rails 側に設定を読み込ませる方法が紹介されていましたが、僕の環境ではキュー名の Prefix が二重に付いてしまったりして、うまく動作してくれませんでした。

また、 Rails.env によって設定を変える、といったことが Shoryuke 側ではサポートされていないので、 staging 環境では少し worker の thread 数減らそう、とかそういった設定がいちいち面倒なのも残念でした。

100万トランザクションは簡単に突破しそう

100万トランザクションまで無料、という SQS ですが、Queue が空でメッセージが配信されない場合もトランザクションとして計上されているようです(料金に計上されるかは未確認なので、もし違ったら教えてください。)
Shoryuken は 1 プロセスで標準 25 スレッド並列に処理が走るので、ローカルで1時間弱動かしただけでも 40000トランザクションを超えました。

ただし、Shoryuken 側のオプションで delay という項目があり、メッセージが空の場合は delay で指定した期間はリクエストをストップするので、この値を調整すればなんとかなりそうです。
しかし、あまり長い秒数 delay してしまうと Job の即時実行ができないので、要件に合わせた慎重な検討が必要です。

個人で開発された gem である

一番大きな点はここです。
現在も頻繁に開発されているのですが、割と作者本人が自分で PR 出して自分で merge という流れが多いようです。
あくまで作者が個人的に開発している gem であり、商用利用を考慮した慎重な開発はされていないような印象を持ちました。

ただ、ローカルで動かしてみただけですが、非常に軽快に動作しており、worker としての核はよくできている gem だと感じました。SQS についても Redis より堅牢なように感じたので、かなり本腰を入れて検討したのですが、ジョブで実行している処理は開発中のアプリの核になる機能なので、ちょっとここはリスクと考え、慎重になりました。

最後に

ジョブのキューに SQS を利用する、という観点は非常に良いように思いますし、他に SQS に対応した Job queue worker は存在しなさそうなので、個人的には Shoryuken を応援しています。
今回は商用利用のアプリ用だったので見送りましたが、個人で作成するアプリではぜひ使ってみたいと思います。

参考


  1. DelayedJob は RDS 以外のバックエンドも サポートしています 

  2. 他にもDelayedJobSQSがありますが、4年ほど開発が止まっているようです。  

続きを読む

JAWS DAYS 2017 ワークショップ Docker on Elastic Beanstalk 〜Days after tomorrow〜 (2)

この記事では、複数のDockerコンテナで構成される環境を構築するための設定例を具体的に解説します。ワークショップのフォローアップではありますが、一般的な事柄を扱いますので、ワークショップに参加されていない方にもお読み頂ける内容です。

前回の記事: JAWS DAYS 2017 ワークショップ Docker on Elastic Beanstalk 〜Days after tomorrow〜 (1)

春ですね

こんにちは。Emotion Techの子安です。ようやく暖かくなってきましたね。やっぱり春といえば桜ですかね!

sakura.jpg

恐縮です。

前回のつづき

さて、前回はテーマとする環境の構成を説明し、利用するコンテナ定義ファイル docker-compose.yml の枠組みを解説しました。更に web app db コンテナのうち、 web db コンテナの設定を見ました。今回はアプリケーションそのものである app コンテナを見ていきます。

各コンテナの設定 2

appコンテナ

app:
  image: phusion/passenger-ruby23:0.9.20
  environment:
    APPLICATION_ENV: development
    APPLICATION_ROLE: app
    PASSENGER_APP_ENV: development
  labels:
    eb.workshop.role: app
  networks:
    - eb.workshop
  depends_on:
    - db
  volumes:
    - ./app/init/40_setup.sh:/etc/my_init.d/40_setup.sh:ro
    - ./app/passenger/passenger.conf:/etc/nginx/sites-available/default:ro
    - ./app/rails-app:/var/www/rails-app

Ruby on Railsのアプリを実行するappコンテナです。phusion/passenger-dockerイメージを利用しています。このイメージはさまざまな機能を持っていますが、まずは設定内容を見ましょう。

  • image: phusion/passenger-dockerイメージを指定しています。
  • environment: イメージ側で PASSENGER_APP_ENV 環境変数を読んでRailsの環境を切り替えてくれる機能があります。
  • volumes: ここで3つのマウントを指定しています。コンテナ側のパスを見てください。

    • /etc/my_init.d/40_setup.sh:ro: /etc/my_init.d 配下に置いたスクリプトを初期化時に実行してくれる機能があります。実行権限を忘れずにつけるようにします。 :ro は読み取り専用の意味です。
    • /etc/nginx/sites-available/default:ro: ここにnginxの設定ファイルが置かれています。
    • /var/www/rails-app: Railsのアプリケーションをここに置きます。これは任意の場所で構いません。上記のnginx設定ファイルに記述しています。

phusion/passenger-docker

phusion/passenger-dockerは、Phusion社がメンテナンスしているDockerイメージです。このイメージは、さらに別のイメージ phusion/baseimage-docker をベースとして作られています。このphusion/baseimage-dockerには、いくつかの有用な機能が含まれています。

  • 正しいinitプロセス: /sbin/my_init にinitプロセスを内蔵しています。本来initプロセスには、親プロセスのいなくなった子プロセス(孤児)を里親として引き受ける役割があります。加えて SIGTERM を受け取った際に、他のサービスを終了させる機能を持ちます。
  • スーパーバイザ: 軽量なスーパーバイザ runit を内蔵しています。
  • ログマネージャ: syslogデーモン syslog-ng を内蔵しています。logrotateも設定済みです。
  • cron: cronデーモンを内蔵しています。

重要なことは、これらの機能が協調動作するように設定されていることです。 /sbin/my_init がrunitを立ち上げ、runitがsyslog-ngやcronを管理します。デフォルトのコンテナ起動コマンドが /sbin/my_init に設定されていますので、

docker run phusion/baseimage

として起動することで、最小限のコンテナ化されたLinux(Ubuntu)環境を手に入れることができます。他にも、今回も利用している /etc/my_init.d 配下のスクリプトを初期化時に実行する機能、root以外でコマンドを実行するための setuser コマンドなど、様々な機能や仕組みを備えています。詳しくはWebページを参照ください。

phusion/passenger-dockerは、このphusion/baseimage-dockerの上に、nginxやrubyのアプリケーションサーバである Phusion Passenger をインストールしてあります。nginxはrunitの配下に設定されていますので、すぐにスーパーバイズされたデーモンとして稼働させることができます。

コンテナ初期化スクリプト

可能なことはコンテナ自身に判断させるのが良い設計です。そのためにコンテナ起動時に実行するスクリプトを組み込むことがよく行われます。今回 app コンテナで設定している初期化スクリプトの内容を見てみましょう。 /app/init/40_setup.sh を参照ください。

このスクリプトの主な役割は4つです。

  • nginxを起動可能にする
  • Railsアプリのセットアップ
  • 環境変数の引き渡し
  • DBマイグレーション

ハンズオンのため、本来はコンテナ初期化スクリプトに記述すべきでない処理もここに入っていますが、続けて詳しく説明します。

/app/init/40_setup.sh
rm -f /etc/service/nginx/down

これはrunitの機能です。サービスを定義するディレクトリ(今回は /etc/service/nginx )の直下に down という名前のファイルがあると、自動起動しません。phusion/passenger-dockerイメージ側で配置されているものですが、自動起動してほしいのでファイルを削除しています。

/app/init/40_setup.sh
chown -R app:app /var/www
cd /var/www/rails-app
setuser app bundle install --path=../vendor/bundle
if [ "$PASSENGER_APP_ENV" = 'production' ]; then
  setuser app bin/rails assets:precompile
  setuser app bundle install --path=../vendor/bundle --without test development --deployment --clean
fi

このあたりは実環境であればCIで、ビルドの一処理として適切に実施すべきものです。アプリケーションのファイルパーミッションを適切に設定し、ライブラリのインストールを行います。更にproductionモードであれば、アセットのプリコンパイルと不要なライブラリの削除を行います。

/app/init/40_setup.sh
echo "passenger_env_var 'SECRET_KEY_BASE' '$SECRET_KEY_BASE';" >> /etc/nginx/conf.d/10_setenv.conf
echo "passenger_env_var 'RDS_HOSTNAME' '$RDS_HOSTNAME';" >> /etc/nginx/conf.d/10_setenv.conf

ここは環境変数をpassengerへ引き渡すための処理です。コンテナでは環境変数を扱う機会が多くなりますので、利用するミドルウェアに合わせて設定が必要です。

/app/init/40_setup.sh
RAILS_ENV=$PASSENGER_APP_ENV setuser app bin/rails db:create db:migrate

こちらはDBのマイグレーションです。この処理も実環境であればコンテナ初期化スクリプトではなく、デプロイの手順の一貫として実施すべきものです。

次回へ

ここまでが app コンテナの設定でした。長くなりましたので、つづきは次回にしたいと思います。次回はElastic Beanstalkの設定ファイル、Dockerrun.aws.jsonを解説する予定です。

続きを読む

Mastodonでgit pullしたらビルドエラー

概要

EC2で稼働させているMastodongit pullした後にdocker-compose buildしたら下記エラーが出てハマった。

環境

  • AWS EC2(t2.medium)
  • Ubuntu Server 16.04 LTS

エラー内容

ubuntu@ip-***-***-**-**:~/mastodon$ docker-compose build
redis uses an image, skipping
db uses an image, skipping
Building streaming
Step 1/9 : FROM ruby:2.4.1-alpine
 ---> 5eadd5d1419a
Step 2/9 : LABEL maintainer "https://github.com/tootsuite/mastodon" description "A GNU Social-compatible microblogging server"
 ---> Using cache
 ---> 95a4b711ef32
Step 3/9 : ENV RAILS_ENV production NODE_ENV production
 ---> Using cache
 ---> 499e95f00e13
Step 4/9 : EXPOSE 3000 4000
 ---> Using cache
 ---> 167a91f421f4
Step 5/9 : WORKDIR /mastodon
 ---> Using cache
 ---> e185ae07f027
Step 6/9 : COPY Gemfile Gemfile.lock package.json yarn.lock /mastodon/
 ---> Using cache
 ---> 460d49537428
Step 7/9 : RUN BUILD_DEPS="     postgresql-dev     libxml2-dev     libxslt-dev     build-base"  && apk -U upgrade && apk add     $BUILD_DEPS     nodejs     libpq     libxml2     libxslt     ffmpeg     file     imagemagick  && npm install -g npm@3 && npm install -g yarn  && bundle install --deployment --without test development  && yarn --ignore-optional  && yarn cache clean  && npm -g cache clean  && apk del $BUILD_DEPS  && rm -rf /tmp/* /var/cache/apk/*
 ---> Running in 68a8d45af6e8
fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/main/x86_64/APKINDEX.tar.gz
ERROR: http://dl-cdn.alpinelinux.org/alpine/v3.4/main: No such file or directory
WARNING: Ignoring APKINDEX.167438ca.tar.gz: No such file or directory
fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/community/x86_64/APKINDEX.tar.gz
ERROR: http://dl-cdn.alpinelinux.org/alpine/v3.4/community: No such file or directory
WARNING: Ignoring APKINDEX.a2e6dac0.tar.gz: No such file or directory
OK: 21 MiB in 29 packages
WARNING: Ignoring APKINDEX.167438ca.tar.gz: No such file or directory
WARNING: Ignoring APKINDEX.a2e6dac0.tar.gz: No such file or directory
ERROR: unsatisfiable constraints:
  build-base (missing):
    required by: world[build-base]
  ffmpeg (missing):
    required by: world[ffmpeg]
  file (missing):
    required by: world[file]
  imagemagick (missing):
    required by: world[imagemagick]
  libpq (missing):
    required by: world[libpq]
  libxml2 (missing):
    required by: world[libxml2]
  libxml2-dev (missing):
    required by: world[libxml2-dev]
  libxslt (missing):
    required by: world[libxslt]
  libxslt-dev (missing):
    required by: world[libxslt-dev]
  nodejs (missing):
    required by: world[nodejs]
  postgresql-dev (missing):
    required by: world[postgresql-dev]
ERROR: Service 'streaming' failed to build: The command '/bin/sh -c BUILD_DEPS="     postgresql-dev     libxml2-dev     libxslt-dev     build-base"  && apk -U upgrade && apk add     $BUILD_DEPS     nodejs     libpq     libxml2     libxslt     ffmpeg     file     imagemagick  && npm install -g npm@3 && npm install -g yarn  && bundle install --deployment --without test development  && yarn --ignore-optional  && yarn cache clean  && npm -g cache clean  && apk del $BUILD_DEPS  && rm -rf /tmp/* /var/cache/apk/*' returned a non-zero code: 11

解決方法

$ git checkout $(git describe --tags `git rev-list --tags --max-count=1`)

ハマってる最中にREADMEに安定バージョン使ってねって追記されてましたとさ。。

追記

v1.2にしたところ下記のエラーが再現。調査中。

ubuntu@ip-***-**-**-***:~/mastodon$ docker-compose build
redis uses an image, skipping
db uses an image, skipping
Building streaming
Step 1/9 : FROM ruby:2.4.1-alpine
 ---> 5eadd5d1419a
Step 2/9 : LABEL maintainer "https://github.com/tootsuite/mastodon" description "A GNU Social-compatible microblogging server"
 ---> Using cache
 ---> 95a4b711ef32
Step 3/9 : ENV RAILS_ENV production NODE_ENV production
 ---> Using cache
 ---> 499e95f00e13
Step 4/9 : EXPOSE 3000 4000
 ---> Using cache
 ---> 167a91f421f4
Step 5/9 : WORKDIR /mastodon
 ---> Using cache
 ---> e185ae07f027
Step 6/9 : COPY Gemfile Gemfile.lock package.json yarn.lock /mastodon/
 ---> Using cache
 ---> 06b33c54cfd4
Step 7/9 : RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories  && BUILD_DEPS="     postgresql-dev     libxml2-dev     libxslt-dev     build-base"  && apk -U upgrade && apk add     $BUILD_DEPS     nodejs@edge     nodejs-npm@edge     libpq     libxml2     libxslt     ffmpeg     file     imagemagick@edge  && npm install -g npm@3 && npm install -g yarn  && bundle install --deployment --without test development  && yarn --ignore-optional  && yarn cache clean  && npm -g cache clean  && apk del $BUILD_DEPS  && rm -rf /tmp/* /var/cache/apk/*
 ---> Running in 0b9005a839d9
fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/main/x86_64/APKINDEX.tar.gz
ERROR: http://dl-cdn.alpinelinux.org/alpine/v3.4/main: No such file or directory
WARNING: Ignoring APKINDEX.167438ca.tar.gz: No such file or directory
fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/community/x86_64/APKINDEX.tar.gz
ERROR: http://dl-cdn.alpinelinux.org/alpine/v3.4/community: No such file or directory
WARNING: Ignoring APKINDEX.a2e6dac0.tar.gz: No such file or directory
fetch https://nl.alpinelinux.org/alpine/edge/main/x86_64/APKINDEX.tar.gz
140162439957356:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:s23_clnt.c:794:
ERROR: https://nl.alpinelinux.org/alpine/edge/main: Permission denied
WARNING: Ignoring APKINDEX.65bdaf85.tar.gz: No such file or directory
OK: 21 MiB in 29 packages
WARNING: Ignoring APKINDEX.167438ca.tar.gz: No such file or directory
WARNING: Ignoring APKINDEX.a2e6dac0.tar.gz: No such file or directory
WARNING: Ignoring APKINDEX.65bdaf85.tar.gz: No such file or directory
WARNING: The repository tag for world dependency 'nodejs@edge' does not exist
WARNING: The repository tag for world dependency 'nodejs-npm@edge' does not exist
WARNING: The repository tag for world dependency 'imagemagick@edge' does not exist
ERROR: Not committing changes due to missing repository tags. Use --force to override.
ERROR: Service 'streaming' failed to build: The command '/bin/sh -c echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories  && BUILD_DEPS="     postgresql-dev     libxml2-dev     libxslt-dev     build-base"  && apk -U upgrade && apk add     $BUILD_DEPS     nodejs@edge     nodejs-npm@edge     libpq     libxml2     libxslt     ffmpeg     file     imagemagick@edge  && npm install -g npm@3 && npm install -g yarn  && bundle install --deployment --without test development  && yarn --ignore-optional  && yarn cache clean  && npm -g cache clean  && apk del $BUILD_DEPS  && rm -rf /tmp/* /var/cache/apk/*' returned a non-zero code: 255

続きを読む

ド素人がAWSでRuby on Railsを導入したメモ

はじめに

いい加減見るだけでなくなにかしら自分用にでもいいからアウトプットしてみようと思ったので書いてみました。
ほぼグラフィックデザイナーとしてしかやってこなかった自分が軽い気持ちで手を出しているので、そもそも理解できてない部分が多い上に無駄な工程、逆になぜそこを書かないんだ的なところもどっさりあるかと思います。
(一応、自分のWindows10PCに仮想環境あり、PHPとかjQueryとかMysqlとかほんの触り程度に知っているくらいですが超初級レベルです。)

参考にした記事とやったこと

導入できました!

その後、rubyは入ってるのに
$ ruby -v
が効かなくてrehashも結局試したけどだめだったので一度接続しなおしたらバージョンが表示されました。導入できたようです。ヤッタネ!

今後の課題とか所感

Railsをインストールするディレクトリ
何も考えずec2-user直下にインストールしたがこれで果たして良かったのか・・・
まだ何もいじっていないのでわからんですがちゃんと調べてから導入したほうがいいなと後から感じました。

開発環境に慣れる
これから仕事で使うことになるので否が応でも慣れるしかない・・・まずは何ができて何ができないのかを理解するところからですかね。

まとめるのって難しい!
今回こうして書いてみたら思っていた以上に書き起こすのが難しかったのと、Markdown方式できれいな記事に仕上げるのもなかなか至難の技だということがわかりました。
あとはいろんな人の記事をもっと読んで、また自分が書こうと思ったときに書いてぼちぼち慣れていければなと思います。

続きを読む

JenkinsおじさんのためのAWS Batch

はじめに

この記事の対象者

主にこんな感じの人をターゲットにしています。

  • Jenkinsでジョブを管理している
  • AWSをcliで触るのは実は大変…。GUIでやりたい
  • Dockerはインストールはしているけれど、あんまり触ったことが無い

また、本記事執筆時点では、us-east(virginia)でのみ利用可能なので、VPCでの利用はあまり想定していません。 (VPNを繋げば出来ると思いますが…)
本来はAuto ScalingやSPOTインスタンスと組み合わせたりといろいろあるのですが、私的事情により、志は低く、過疎っているJenkinsサーバを廃止(サーバレス化)することを目標にしています。

対象のJenkinsジョブ

今回ターゲットにするのは、Jenkinsだと一般的と思われる以下の処理とします。

  • JDKがバージョン指定で入っている
  • バッチ処理の入ったリポジトリをgit cloneしてくる
  • シェルスクリプトからごにょごにょして、リポジトリの中身を実行する

PHPやRubyなど別言語の人は、Javaな部分は脳内で別言語に置き換えてみてください。

AWS Batchでの流れ

Jenkinsでごく一般的なこの処理をAWS Batchでやろうとした場合、以下のような流れが一番シンプルになるかなと思います。

  1. JDK等、必要な実行環境を準備したDockerfileを作成する
  2. Jenkinsでやっていたシェル相当のシェルスクリプトを作成する
  3. リポジトリにDockerfileとシェルスクリプトを追加する
  4. Amazon ECRに設定を作成する
  5. Dockerfileでビルドし、JDK及びバッチ処理のリポジトリの入ったコンテナを作成し、ECRにプッシュする
  6. AWS Batchの設定(IAM Role, Compute Environment, Job Definition等)を作成する
  7. AWS Batchを実行する

というわけで、ハンズオン的に進めて行きたいと思います。

作業手順

1. Dockerfileの作成

古風なエンジニアにはこちらの記事が一番わかりやすいんじゃないかと思います。

効率的に安全な Dockerfile を作るには

今回のケースだとJavaのバッチ(Hello.class)を動かしたいので

$ ls
Hello.class

$ vim Dockerfile

として、以下のようなDockerfileを作成します。

Dockerfile
FROM centos:6

RUN yum install -y java-1.7.0-openjdk java-1.7.0-openjdk-devel
ENV JAVA_HOME /usr/lib/jvm/java-openjdk
RUN mkdir /opt/batch-directory
COPY . /opt/batch-directory/
WORKDIR /opt/batch-directory

Javaは諸々の事情からOpenJDKにしていますが、Oracle Javaが必要な場合は、少し手間はかかりますが、Oracle Javaでもコンテナを作成することはできます。また、本来だとベースイメージはAmazonLinuxだったりJavaが入ったコンテナの方が良いと思いますが、保守的な方のために、敢えてCentOSにしました。(あんまり意味ないかな…)

2. シェルスクリプトの作成

Jenkinsにバッチを実行するためのシェルスクリプト(Jenkinsでコマンド欄に入れていたようなもの)を作ります。

$ ls
Dockefile
Hello.class

$ vim batch.sh

今回は解説用なので雑なものを用意しています。

batch.sh
#!/bin/bash

java -version
java Hello

3. Dockerfileとシェルスクリプトをリポジトリに追加

せっかく作成したので、これらのファイルをGitリポジトリに追加しましょう。(これはこの後の工程上の必須項目ではないので、一連がテストされてからでも大丈夫です)

4. Amazon ECRにリポジトリを作成する

Amazon EC2 Container Registry(ECR)にリポジトリを作成します。ここではリポジトリ名を入れるだけです。
Amazon EC2 Container Service.png

5. Dockerfileでビルド

先ほど付けた名前を使って、Docker buildをします。

$ docker build -t unagi/jenkins-ojisan .

6. コンテナをECRに登録する

タグ付けして、先ほど作成したECRに登録してあげます。

$ aws ecr get-login --region us-east-1

# これでdocker loginに必要なコマンドがでてくるので、実際はそれを使う
$ docker login -u AWS -p xxxxxx -e none https://xxxx.dkr.ecr.us-east-1.amazonaws.com
$ docker tag unagi/jenkins-ojisan:latest xxxx.ecr.us-east-1.amazonaws.com/unagi/jenkins-ojisan:latest
$ docker push xxxx.ecr.us-east-1.amazonaws.com/unagi/jenkins-ojisan:latest

7. AWS Batchの設定をつくる

Job definitionとCompute environmentを設定します。

こちらがJob definitionで、コンテナを起動するときの設定になります。環境変数を設定できるので、パラメータを渡したい場合は使う事ができます。
AWS Batch (1).png

S3アクセス等でIAM Roleを使いたい場合は、ここで定義するJob Roleで定義する必要があります。そしてさらに分かりにくいことに、ここに表示されるIAM Roleを作るためには、信頼するエンティティがAmazon EC2 Container Service Task Role(ecs-tasks.amazonaws.com)のIAM Roleを作る必要があります。IAM Role作成時に似たようなのが沢山あるので、非常に解りづらいところです。

そして、次の画面はCompute environmentです。
AWS Batch (2).png
こちらはあまり見所は無く、淡々と設定していきます。ここで出てくるRoleは、ECSで動作するために必要なもので、コンテナで使うモノではありません。なので、適当に作成します…。

8. AWS Batchの実行

Job definitionsからSubmit jobして実行します。実行時に先ほど設定した環境変数を変更しながらの実行等もできます。
ちなみにこれも凄く分かりにくいのですが、Job definitionを編集したい場合はCreate new versionです。新しいバージョンの定義が出来てしまうので、古い方は不要になった段階で削除してしまいましょう。

9. 実行ログの確認

CloudWatchのログから見ます。Submitしてから起動までは少し時間がかかるので、少し待たないとログ出力はされません。

あとがき

Jenkinsおじさん的には、Dockerが出てくるため取っつき辛い印象を持つのかなと思います。
美しくDockerを使う場合はさておき、バッチ処理をやるだけであれば、Dockerfileに書くのはバッチサーバを作るときのセットアップコマンドで、一回やってしまえばあまり難しくないです。

続きを読む

AWS CodeDeploy 導入調査 アプリ側編

AWS CodeDeploy 導入調査 AWS 設定編 の続きになります

前回のあらすじ

  • EC2 インスタンスを作った
  • コードアップロード用の S3 Bucket を作った
  • デプロイ用の IAM ロールを作った
  • CodeDeploy アプリケーションを作った

今回はアプリ側の作業です.

デプロイ先アプリ構成

Nginx – uWsgi を sudpervisord でプロセス監視
Nginx とか supervisord とかは割とどうでもいいのですが
サーバー構成はあらかじめ一通り作成しておきます.

さらに CodeDeloy-Agent をインストールしておきます.
http://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/codedeploy-agent-operations-install.html

とりあえずデプロイしてみる

ローカル -> S3 のアップロードは AWS-CLI を使用します.
ローカルプロジェクトのトップディレクトリで,

$ aws deploy push --application-name <<app-name>> --s3-location s3://<<s3bucket-name>>/<<app-name>>.zip --source ./ --region <<region-name>>

を実行します.
<<app-name>> は CodeDeploy で作成したアップケーション名です.

  • –ignore-hidden-files オプションで隠しファイルを無視します.
  • S3 にアップロードするファイル名は本当はなんでもいいです.
  • 圧縮時の拡張子は tar.gz でもいけるはず.

アップロードに成功すると以下のように出力されます.

To deploy with this revision, run:
aws deploy create-deployment --application-name <<app-name>> --s3-location bucket=<<s3bucket-name>>,key=<<app-name>>.zip,bundleType=zip,eTag=<<eTag>>--deployment-group-name <deployment-group-name> --deployment-config-name <deployment-config-name> --description <description>

ただし今回は成功しないと思います.

appspec.yml の作成

appspec.yml は CodeDeploy によるデプロイ作業を記述したものになります.
まずローカルプロジェクトのトップディレクトリに appspec.yml ファイルを作成し,

version: 0.0
os: linux

まで記述します. ここで,
– version は appspec のシステムバージョンで2017/04現在 0.0 のみ有効です
– os はデプロイ先 EC2 インスタンスの OS 種類で windows か linux かを設定します. linux ディストリビューションまで指定する必要はありません.

次に,

files:
   - source: /
     destination: /usr/local/src

でファイル配置を設定します.
source はコードパスで appspec.yml があるディレクリの相対パスになります.
なので source: / とした場合はディレクリ構成そのままで EC2 インスタンスの
destination に配置されます.
特定のディレクリ/ファイルのみ配置先を変えたい場合は – source: destination の記述を追加することができます.

さらに, 今回は以下の設定を追加しました.

hooks:
  AfterInstall:
    - location: copy_settings.sh
      timeout: 180
      runas: root
    - location: pip_install.sh
      timeout: 180
      runas: root
  ApplicationStart:
    - location: reload_uwsgi.sh
      timeout: 180
      runas: root

hooks 以下では CodeDeploy ライフサイクルの各処理にフックしてスクリプトを実行させることができます.
http://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html

CodeDeploy-Agent は Ruby で記述されており, Ruby コード上で Shell スクリプトを実行します.
今回追加した処理は

  • コード配置後に EC2インスタンス内に仕込んでおいた追加設定をコピー
  • プロジェクト内の requirements.txt の内容で pip install
  • アプリスタート時に uwsgi のリスタート

です. runas は各スクリプトの実行ユーザーです.
今回は面倒だったので全て root にしました.

デプロイに再度チャレンジ

ふたたび ローカル -> S3 のアップロード をやってみると今度は成功するはずです.
アップロードが成功したら次は AWS コンソールの CodeDeploy に移動します.

  1. ダッシュボードでアプリケーションを選択して “アクション” -> “新しいリビジョンのデプロイ”
  2. “アプリケーション”, “デプロイグループ” はそのまま, “リビジョンタイプ” は Amazon S3
  3. “リビジョンの場所” でアップロードしたソースコードを指定します.
  4. その他はそのままで “デプロイ” でデプロイ開始します.

フックスクリプトでエラーにならなければデプロイは成功するはずです.

フックスクリプトでエラーになったので解決した内容は次回

続きを読む

lammaで始める等身大のAWS Lambda

(https://medium.com/@ayemos/lamma%E3%81%A7%E5%A7%8B%E3%82%81%E3%82%8B%E7%AD%89%E8%BA%AB%E5%A4%A7%E3%81%AEaws-lambda-d032addc5f4d より転載)

TL;DR

AWS Lambda

Amazon S3上にcsvファイルがアップロードされたら、その集計データをS3上の別のcsvファイルに保存したい

毎週金曜日の21:00に特定のタグがついたEc2インスタンスの過去数時間のCPUUtilization値をチェックして、アイドル状態にあるものは自動的にシャットダウンしたい

などなど、AWS上に構築されたインフラシステムにおいて、あるトリガーが発生したときに、ちょっとしたロジックと実装でもって処理を継続したい、というシチュエーションに最適なサービスがAWS Lambdaです。

AWS Lambdaで予め用意された計算環境(python, Node.jsなどの言語環境と、 AWS SDKの各言語実装などが含まれる)を利用することで、EC2上に計算環境をプロビジョンする手間が省けたり、実行回数とその長さに基づいた課金システムで、非常に安価に利用できるなどの利点があります。

しかし、計算機環境の構築と付き合わなくなった一方で、実装された関数のLambda上でのバージョンコントロールやデプロイ/ロールバックが面倒であるという現状があり、実際apexやlamveryなど、Lambdaのマネジメントツールが開発されています。

lamma

https://github.co/ayemos/lamma
ruby製のAWS Lambdaのマネジメントツール。

すでにあるツールとの差別化という意味で「IoT、ChatBotなどの目的でAWS Lambdaをなるべく手軽に今すぐ使いたい」という人をターゲットに作っているつもりです。

$ gem install lamma
$ lamma init my_function --runtime=python2.7
Looks like you didn't specified role arn for the function.
Do you want me to create default IAM role and configure it (my_function-lamma-role)? (y/n) y
I, [2017-04-06T22:28:53.952278 #5027]  INFO -- : Creating role my_function-lamma-role
I, [2017-04-06T22:28:54.863304 #5027]  INFO -- : Checking attached role policies for my_function-lamma-role
I, [2017-04-06T22:28:55.243400 #5027]  INFO -- : Could not find AWSLambdaBasicExecutionRole policy. Attatching.
I, [2017-04-06T22:28:55.243449 #5027]  INFO -- : Attaching minimal policy (AWSLambdaBasicExecutionRole) to my_function-lamma-role
I, [2017-04-06T22:28:55.466092 #5027]  INFO -- : Done
      create  /workspace/my_function/lambda_function.py
      create  /workspace/my_function/lamma.yml
I, [2017-04-06T22:28:55.468021 #5027]  INFO -- : Initializing git repo in /workspace/my_function
$ ls
total 16
drwxr-xr-x   5 yuichiro-someya  staff  170  4  6 22:28 ./
drwxr-xr-x   3 yuichiro-someya  staff  102  4  6 22:28 ../
drwxr-xr-x  10 yuichiro-someya  staff  340  4  6 22:28 .git/
-rw-r--r--   1 yuichiro-someya  staff  357  4  6 22:28 lambda_function.py
-rw-r--r--   1 yuichiro-someya  staff  207  4  6 22:28 lamma.yml
$ lamma deploy -a production
Function my_function doesn't seem to be exist on remote.
Do you want me to create it? (y/n) y
I, [2017-04-06T22:29:14.113561 #9209]  INFO -- : Creating new function my_function...
I, [2017-04-06T22:29:14.123244 #9209]  INFO -- : Saved the build: /var/folders/kd/_chs6sw13jvg01hlq6nd50k00000gp/T/lamma/05f92074cf833092343cd5414754efef
I, [2017-04-06T22:29:14.448724 #9209]  INFO -- : Created new function arn:aws:lambda:ap-northeast-1::function:my_function
I, [2017-04-06T22:29:14.448780 #9209]  INFO -- : Publishing...
I, [2017-04-06T22:29:14.605540 #9209]  INFO -- : Published $LATEST version as version 1 of funtion: arn:aws:lambda:ap-northeast-1::function:my_function:1
Function alias PRODUCTION doesn't seem to be exist on remote.
Do you want me to create it? (y/n) y

とまあこんな感じに、
shell-session
gem install lamma

して、

lamma init my_function --runtime=python2.7

して、 lambda_function.py 編集して

lamma deploy -a production

とすると、my_functionとPRODUCTIONエイリアスがクラウド上に生成されます。便利。

一方で、init時に生成されたlamma.ymlは次のように設定値がまるっとはいってます。

$ cat lamma.yml
function:
  name: my_function
  role_arn: arn:aws:iam:::role/my_function-lamma-role
  description: Hello, world.
  timeout: 3
  memory_size: 128
  runtime: python2.7
  region: ap-northeast-1

また、複数回デプロイした後は、

lamma rollback -a production

でエイリアスを指定して(デプロイごとに1回のみ)ロールバックすることが出来ます。
(deploy時にエイリアス毎に古いバージョンに向けたエイリアス(LAST)を用意することで実現している。)

lammaの紹介は以上。issueを上げてもらえると開発頑張れます。

続きを読む