こしごぇ(B)

旧:http://d.hatena.ne.jp/koshigoeb/

シェルスクリプトの trap でシグナルを拾う場合の実行中コマンド

例えば、以下の様にして TERM を拾う様にしてみる。

#!/bin/bash -x

trap "echo trap TERM; exit 1" TERM
trap "echo EXIT" EXIT

sleep 5

echo ok

以下は sleep 5 で待っている間に pkill -f trap-signal.sh で TERM シグナルを送った時の結果。

$ ./trap-signal.sh
+ trap 'echo trap TERM; exit 1' TERM
+ trap 'echo EXIT' EXIT
+ sleep 5
++ echo trap TERM
trap TERM
++ exit 1
+ echo EXIT
EXIT

この時、 sleep 5 が完了するまでは待たされる。

シグナルを trap した時に、そのシェルスクリプト内で実行中のプロセスに対してシグナルを伝播させる簡単な方法は何だろう?

Heroku で運用するプレビュー環境に自動デプロイする試み(0)

中途半端だけど、せっかくやってみたので晒してみる。一番の収穫は、シェルスクリプトの trap を覚えた事だったりするのは内緒。

やっていること

  • CI で CircleCI を使用している
  • Git を使用している
  • GitHub を使用している
  • PR をレビューして master ブランチにマージする運用
  • 開発時のプレビュー環境として Heroku を使用している

やりたいこと

  • master ブランチの最新版を、労力少なくプレビュー環境に反映させたい

手作業

  • 適宜 Heroku にデプロイする運用
  • めんどくさい

Heroku の Automatic deploys

  • Heroku の GitHub 連携機能のひとつ
  • ブランチを指定しておくと、自動的にデプロイしてくれる機能
  • Rails 固有の rake db:migrate みたいな後処理は実行させられない
    • ビルドパック作ればいいのかもしれないけど、試してない
  • 残念

余談: Heroku の Pull request apps

  • 存在する PR から Heroku アプリの複製を作れる機能
  • PR が作られたら自動で複製を作ってくれる機能もある
  • app.json を使う
    • 環境変数の継承が使えて便利
    • PostgreSQL 9.4 を使う場合、今は --version=9.4 を付ける必要があり、それを app.json の addons で記述する方法が分からない
  • 便利 and 残念

CircleCI Heroku Deployment

  • Continuous Deployment with Heroku - CircleCI
  • CircleCI のアカウント設定で Heroku の API キーを登録する
  • CircleCI のプロジェクト設定の Heroku Deployment で、使用する SSH キーのユーザをセットする
    • ボタンを押すと Heroku と GitHub に公開鍵が登録される
  • circle.yml の deployment でデプロイ設定書いたら ok
  • 簡単 and 便利

雑感

  • koshigoe/rails-example-circleci-heroku-deployment
  • 既存環境の上書きは余計な心配を抱える事になって上手くないかもしれない
  • Blue-Gree Deployment 的な考えで、毎回アプリを作成する方向で考えた方が健全かもしれない
    • URL を変えずに運用する工夫の手間次第
  • master ブランチの自動デプロイはやりすぎかもしれない
    • 同一 Heroku アプリに対する並行デプロイが発生するはず
  • 動作確認用の初期データを db:seed で冪等対策せず入れる事を考えているため、毎回 db:setup してしまっている
    • プレビュー環境で確認中にデプロイされてデータが消えるとか、余計にストレスたるはず
  • データベースのロールバックってどうするんだっけ
    • 今回のリリースで変更したスキーマとデータを元に戻す、という作業になるんだっけ
    • 今回は無理矢理 db:setup できるから気にしなくてもいいんだけど
  • CircleCI の出力無しタイムアウトの上限はどれだけなのか
    • コマンド全体のタイムアウトは別にあるのか、あるとして上限はどれだけなのか
    • timeout: 3 して while で sleep 1 をすると延々続けられるみたいだけど…
machine:
  ruby:
    version: 2.2.1
dependencies:
  cache_directories:
    - "vendor/bundle"
deployment:
  staging:
    branch: master
    commands:
      - ./deploy.sh:
          timeout: 600 # default
#!/bin/sh -ex
# -e を使って途中でエラーが発生したら即終了へ

# HEROKU_APP_NAME が未定義なら -u で拾って即終了
# HEROKU_APP_NAME が空なら if -z で拾って即終了
if [ -z "$HEROKU_APP_NAME" ]
then
    echo "\$HEROKU_APP_NAME required."
    exit 1
fi

# 1) まずはメンテナンスモードへ、失敗したら即終了
heroku maintenance:on -a $HEROKU_APP_NAME

# よくあるシグナルを受け取ったら異常終了
trap "echo trap SIGHUP;  exit 1" HUP
trap "echo trap SIGINT;  exit 1" INT
trap "echo trap SIGQUIT; exit 1" QUIT
trap "echo trap SIGTERM; exit 1" TERM
# CircleCI の timeout は拾えない様なので諦める

# ここ以降での終了を拾って後処理する
#
# 正常終了時)
#   1) メンテナンスモード終了
#
# 異常終了時)
#   1) ロールバック
#   2) DB セットアップ(リセット)
#   3) メンテナンスモード終了
trap "
case \$? in
  0 )
    echo Auto deploy successful.
    ;;
  * )
    heroku rollback -a $HEROKU_APP_NAME
    heroku run rake db:setup -a $HEROKU_APP_NAME
    echo Auto deploy failed.
    ;;
esac

heroku maintenance:off -a \$HEROKU_APP_NAME
" EXIT

# 2) デプロイ
git push -f git@heroku.com:$HEROKU_APP_NAME.git $CIRCLE_SHA1:refs/heads/master

# 3) DB セットアップ(毎回リセット)
heroku run rake db:setup -a $HEROKU_APP_NAME

delayed_job の plugin

知っていたかもしれないけど、記憶に無かったので調べてまとめてみる。

tl;dr

こんな感じかな?

Delayed::Worker.plugins

Delayed::Worker.pluginsプラグインクラスの配列で、初期値は [Delayed::Plugins::ClearLocks] となっている。

Delayed::Worker.setup_lifecycle

プラグインDelayed::Worker.setup_lifecycle メソッドの中で plugins.each { |klass| klass.new } のようにして初期化されている。

Delayed::Plugin

プラグインクラスは Delayed::Plugin を継承する前提となっている。

Delayed::Plugin#initialize の中で self.class.callback_block.call(Delayed::Worker.lifecycle) が実行されている。

callback_blockclass_attribute :callback_block の様に宣言されている。

Declare a class-level attribute whose value is inheritable by subclasses. Subclasses can change their own value and it will not impact parent class. http://apidock.com/rails/Class/class_attribute

Delayed::Lifecycle

Delayed::Lifecycle#initialize は以下の様に定義されている。

module Delayed
  class InvalidCallback < Exception; end

  class Lifecycle
    EVENTS = {
      :enqueue    => [:job],
      :execute    => [:worker],
      :loop       => [:worker],
      :perform    => [:worker, :job],
      :error      => [:worker, :job],
      :failure    => [:worker, :job],
      :invoke_job => [:job]
    }

    def initialize
      @callbacks = EVENTS.keys.each_with_object({}) do |e, hash|
        hash[e] = Callback.new
      end
    end

Delayed::Lifecycle クラスには以下の様なメソッドでコールバックを登録する事ができる。

  • Delayed::Lifecycle#before
  • Delayed::Lifecycle#after
  • Delayed::Lifecycle#around

プラグインでコールバックを登録することで、delayed_job のワーカーライフサイクルを拡張する、という感じ。

Delayed::Plugins::ClearLocks

delayed_job 組み込みのプラグイン Delayed::Plugins::ClearLocks を見てみる。

module Delayed
  module Plugins
    class ClearLocks < Plugin
      callbacks do |lifecycle|
        lifecycle.around(:execute) do |worker, &block|
          begin
            block.call(worker)
          ensure
            Delayed::Job.clear_locks!(worker.name)
          end
        end
      end
    end
  end
end

callbacksDelayed::Plugin.callbacks で、以下の様な実装になっている。

module Delayed
  class Plugin
    class_attribute :callback_block

    def self.callbacks(&block)
      self.callback_block = block
    end

プラグインはワーカーのライフサイクルを拡張する処理を実装し、その実装がライフサイクルのコールバックで実行される様に登録するのが仕事。

きっかけ

delayed_job のプラグインについて調べようと思ったきっかけは以下の gem。

S3 を使った実装のテストを fake-s3 を使って書いてみる

fakes3 (fake-s3) という gem を使うと、S3 のダミーサーバ(webrick)が使えるようになるとのこと。

サーバプロセスの管理をどうするのが今っぽいのか分からず、ググって得られた情報を元に、glint という gem を使ってみることに。

before(:suite) で glint に fakes3 の面倒を見てもらう。 after(:suite) は、fakes3 に渡した一時ディレクトリの後始末を行うのみ。

# spec/support/fakes3.rb
require 'tmpdir'
require 'glint'

RSpec.configure do |config|
  config.before :suite do
    rootdir = Dir.mktmpdir
    server = Glint::Server.new(nil, signals: [:INT]) do |port|
      exec "bundle exec fakes3 -p #{port} -r #{rootdir}", err: '/dev/null'
      exit 0
    end
    server.start

    Glint::Server.info[:fakes3] = {
      address: "127.0.0.1:#{server.port}",
      root: rootdir
    }
  end

  config.after :suite do
    if Dir.exists? Glint::Server.info[:fakes3][:root]
      FileUtils.remove_entry_secure(Glint::Server.info[:fakes3][:root])
    end
  end
end

ついでに、テスト実行中に万が一誤って本当の S3 に繋がらない様に webmock も入れてみる。

# spec/support/webmock.rb
require 'webmock/rspec'

WebMock.disable_net_connect!(:allow_localhost => true)

適当に example を書いて動作確認してみたけど、本当にこんな感じで良いのかはよく分かってない。

# spec/lib/example_spec.rb
require 'spec_helper'
require 'aws-sdk'

RSpec.describe Aws::S3 do
  let(:s3_client) do
    Aws::S3::Client.new(
      :access_key_id => 'YOUR_ACCESS_KEY_ID',
      :secret_access_key => 'YOUR_SECRET_ACCESS_KEY',
      :region => 'ap-northeast-1',
      :endpoint => "http://#{Glint::Server.info[:fakes3][:address]}/",
      :force_path_style => true)
  end

  before do
    Aws::S3::Resource.new(client: s3_client)
      .create_bucket(bucket: 'mybucket')
      .put_object(key: 'myobject', body: 'this is a pen.')
  end

  it 'バケットを作れること' do
    expect(Aws::S3::Resource.new(client: s3_client).bucket('mybucket'))
      .to be_an_instance_of Aws::S3::Bucket
  end

  it 'オブジェクトを作れること' do
    expect(s3_client.get_object(bucket: 'mybucket', key: 'myobject').body.read).to eq 'this is a pen.'
  end
end

S3 の API リクエストをスタブする方が良かったりするのだろうか?

Rails + MySQL で BIGINT で AUTO_INCREMENT な id カラムを PRIMARY KEY にしたい

もちろん、schema.rb 対応で。

リポジトリの master では t.primary_key :id, :bigint と書ける様だけど、4.2.1.rc4 には見当たらず。いつ頃使える様になるのか。

この際、PostgreSQL に改宗するべきなのか。。。

という所まで考えて、手を出せずにいた activerecord-mysql-awesome の事を思い出した。

無事、 t.primary_key :id, :bigint で BIGINT, AUTO_INCREMENT, PRIMARY KEY な id カラムが作れ、 rake db:reset しても同じ様に作れる様になった。

追記

create_table :hoge, id: :bigint で良かった。

『Webエンジニアが知っておきたいインフラの基本』を(流し)読んだ

ss -lnp 覚えた。

自分が今まで経験してきた事を(濃さを増し増しにしてたくさん付け足して)丁寧に一冊の本にまとめるとこんな感じになるのかなと勝手に思ってみたり、みなかったり。 未だに浅いところをうろちょろしてる自分に焦りを感じつつ、割とまっとうなコースど真ん中を歩けてきたかなと言う自信も沸きつつ、一通り目を通して読了。

Webサービスの開発に自信はあるけど、運用周りやチューニングについては自信がない状況にある場合、この本があれば何が分かったら良いのか分かって(何が分からないのかが分かって)色々はかどる、のかな? 分からない事は近くに居るインフラエンジニアにさっと教えてもらえるだろうし。

本書には関係無いけど、どうも流し読みのクセがついてしまってしょうがない。感性が老化してるのかな。

追記

ss -lnp 覚えた。

あれ。