tumblr

tumblr(タンブラー)は、メディアミックスブログサービス。ブログとミニブログ、そしてソーシャルブックマークを統合したマイクロブログサービスである。アメリカのDavidville.inc(現: Tumblr, Inc.)により2007年3月1日にサービスが開始された。

オレオレ証明書を使ってwebrickを起動する

railsでdevelopment環境でオレオレ証明書を使いたい場合のメモ。 thinを使っている場合が多いみたいだけど、webrickでも出来ないこともない。

underthehood.carwow.co.uk

このページのコードに従っていけばちゃんと動いた。

環境

rails 4.2.2 ruby 2.1.4p265

STEP1. オレオレ証明書の作成
openssl req -new -newkey rsa:2048 -sha1 -days 365 -nodes -x509 -keyout localhost.key -out localhost.crt  

このコマンドでオレオレ証明書用の秘密鍵サーバ証明書を作成。 opensslのオプションやオレオレ証明書の仕組みなどについては以下を参考にした。

d.hatena.ne.jp

STEP2. 証明書をwebrickに読み込ませる

以下のファイルを何処か適当に置く。元リンクではRAILS_ROOT/config/ssl.rbとしておいてるのでそれに従っておく。

if ENV['SSL'] == 'true'  
  require 'rubygems'
  require 'rails/commands/server'
  require 'rack'
  require 'webrick'
  require 'webrick/https'

  module Rails
    class Server < ::Rack::Server
      def default_options 
        super.merge({ SSLEnable: true,
                      SSLVerifyClient: OpenSSL::SSL::VERIFY_NONE,
                      SSLPrivateKey: OpenSSL::PKey::RSA.new(File.open("/path/to/localhost.key").read),
                      SSLCertificate: OpenSSL::X509::Certificate.new(File.open("/path/to/localhost.crt").read),
                      SSLCertName: [["CN", WEBrick::Utils::getservername]],
        })
      end
    end
  end
end
STEP3. config/ssl.rbを読み込む

bin/rails.rbを変更する。具体的にはconfig/ssl.rbを読み込ませるだけ。 コメントで囲まれている部分が追加部分。

#!/usr/bin/env ruby

#------- added by carwow --------
require_relative '../config/ssl'  
#--------------------------------

begin  
  load File.expand_path("../spring", __FILE__)
rescue LoadError  
end  
APP_PATH = File.expand_path('../../config/application',  __FILE__)  
require_relative '../config/boot'  
require 'rails/commands'
STEP4. webrickの起動

config/ssl.rbでif ENV['SSL'] == 'true' という分岐があるのでそれの通りにENVを入れたうえでrails serverすればいい

SSL=true rails s
問題点
1. Rails.env == "development" で分岐できない

わざわざwebrickを起動する時にSSL=trueとかそういう分岐を入れるのも嫌なのでRails.env == "development"のときだけconfig/ssl.rbを読み込めないかと試してみたけど、無理っぽかった。出来るのかもしれないけどそこまで調べるほどのものでもないかとおもってこのままにしている。

そもそもステージングや本番環境なら多くはnginxなどのwebサーバを前段に置いて、アプリケーションサーバunicornとかpuma使うという構成になると思う。webrickの出番は開発環境でしかないことが多い。ので、まぁそこはそんなに気にしなくてもいいかなと思う。

2. bin/railsを書き換えなきゃいけない

ここが一番嫌。config/initializer/の中でなんとか出来ないかと思っていたけど、そこに来る時点でもうwebrickは立ち上がってるので無理。bin/rails変えるといろいろ面倒事が起こりそうな予感がしないでもないので、できるだけここには手を突っ込みたくなかったけど、他にやる方法がわからなかった。

vagrantで一度に複数台起動したいし、vagrant upしたときにchef-soloも流したい

どちらもVagrantfileにその設定を書けばいい

vagrantで複数台起動

Vagrantfileの中でconfig.vm.defineメソッドを呼ぶ。そしてその引数ブロックにそのマシンに設定したいことを記述する。 通常はconfig.vm.box = "CentOS6.4"みたいになってるけど、このconfigの部分をブロックの引数にすればいい。どういうことかというと具体的には以下

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.define :web1 do |web|
    web.vm.box = "CentOS6.4"
    web.vm.network :private_network, ip:"192.168.33.100"
  end

  config.vm.define :web2 do |web|
    web.vm.box = "CentOS6.4"
    web.vm.network :private_network, ip:"192.168.33.101"
  end
end

http://docs.vagrantup.com/v2/multi-machine/

vagrantでchef-solo

config.vm.provision "chef_solo" に設定行書いたブロックを渡す。 ブロック内でchefのレシピやdatabagsを指定する。cookbookの場所はVagrantfileの場所から相対パスで指定できる。また、配列で複数のcookbookを指定することもできる。以下の例ではberkshelfで入れたcookbookと自分で作ったsite-cookbooksを指定してる。 また、add_recipeメソッドでrunlistもこのブロックの中で指定する。 具体的には以下。

config.vm.provision :chef_solo do |chef|
  chef.cookbooks_path = ["chef/cookbooks", "chef/site-cookbooks"]
  chef.data_bags_path = "chef/data_bags"
  chef.add_recipe "yum"
  chef.add_recipe "yum::epel"
  chef.add_recipe "server_conf"
  chef.add_recipe "common_packages"
end

http://docs.vagrantup.com/v2/provisioning/chef_solo.html

複数台起動してそれぞれにchefを流し込む

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.define :web1 do |web1|
    web1.vm.box = "CentOS6.4"
    web1.omnibus.chef_version = :latest

    web1.vm.network :private_network, ip: "192.168.33.102"

    web1.vm.provision :chef_solo do |chef|
      chef.cookbooks_path = ["chef/cookbooks", "chef/site-cookbooks"]
      chef.data_bags_path = "chef/data_bags"
      chef.add_recipe "yum"
      chef.add_recipe "yum::epel"
      chef.add_recipe "server_conf"
      chef.add_recipe "common_packages"

    end
  end

  config.vm.define :web2 do |web2|
    web2.vm.box = "CentOS6.4"
    web2.omnibus.chef_version = :latest

    web2.vm.network :private_network, ip: "192.168.33.103"

    web2.vm.provision :chef_solo do |chef|
      chef.cookbooks_path = ["chef/cookbooks", "chef/site-cookbooks"]
      chef.data_bags_path = "chef/data_bags"
      chef.add_recipe "yum"
      chef.add_recipe "yum::epel"
      chef.add_recipe "server_conf"
      chef.add_recipe "common_packages"

    end
  end
end

こんな感じでいけた。 web1とweb2で設定を分離してるので、別々のrunlist流すとかも出来るんだと思う。

githubのプライベートリポジトリのbasic認証をtokenで置き換え

githubのプライベートリポジトリにあるコードをcapistranoでデプロイしようとした時、httpsでコードをcloneしようとしたのだけど、その時にユーザー名とパスワードをプロンプトで入力しても以下の様なエラーが出た。

fatal: could not read Username for 'https://github.com': No such device or address

結局これがどういうことなのかはわからないし、https://shim0mura@github.comというようにリポジトリのurlに自分のユーザー名を入れてみても同じようなエラーが出た。

で、色々調べてみるとurlにユーザー名の代わりにtokenを入れればいいらしいということが書いてあった。githubのユーザーアカウントページのApplication=>Personal Access Tokensからtokenを取得できる。そこにも以下の様なことが書いてある。

Personal Access Tokens function like ordinary OAuth access tokens. They can be used instead of a password for git over HTTPS, or can be used to authenticate to the API over Basic Authentication.

というわけでやはりhttps上でbasic認証を通す代わりにことPersonalAccessTokenというのが必要なようだ。capistranoのdeployファイルのrepository指定部分を以下の形式に書き換えた。

set :repository, "https://@github.com/groupname/application.git"

そしたらすんなりうまくいった。特にパスワードなどのプロンプトも出ずに終了

参考: http://stackoverflow.com/questions/18886729/fatal-could-not-read-username-for-https-github-com-no-such-device-or-addre

キー一覧を見るためにmemcached入門

memcachedを使う場合、多くはアプリケーションの言語ごとにあるクライアントを使ってgetとかsetをすると思う。telnetで直接memcachedプロトコル使うのはキーの一覧が見たいとかデバッグ時くらいだと思う。そのデバッグ時も言語ごとのrepl使うほうが早かったりするけど。

まぁそんなわけでmemcachedtelnetから叩くことは殆どないのではないかと思う。自分が単純に経験不足なのも在るだろうけど。で、この間直接覗きたい機会があったのでどういうkeyが入ってるのか調べるためにキー一覧を見ようとしてみたのだけど見方が全くわからない。『memcached key 一覧』でググって一番最初に出てきたこのページを参考にしてstats itemsコマンドを打ってみたけど何が何だか...

> set hoge 0 0 4
> hage
STORED

> stats items
STAT items:1:number 1
STAT items:1:age 929
STAT items:1:evicted 0
STAT items:1:evicted_nonzero 0
STAT items:1:evicted_time 0
STAT items:1:outofmemory 0
STAT items:1:tailrepairs 0

空のmemcachedhogeというkeyをsetしてもこうなってしまう。
コマンド1つでkeyを一覧で表示させたいのだけなのに...
というかkeyが1つしか入ってないはずなのに何で色々出てくるの...
cache dumpコマンドを打てば一覧見れそうだけど、何を指定すればいいの...
色々よくわからない。

memcachedがkey-value形式で値を保存するのは知っていたので、keyを一覧するなんて簡単に出来そうだと安易に思っていたけどそうでもなさそうだ。色々調べてみると、telnetから直接keyを一覧するというのは無理そうだと分かる。というかそもそもkey-valueを見るためにmemcachedが内部でどのようにデータを保存しているかを知らなければならない。という訳で簡単にまとめてみた。

SlabAllocator

memcachedはkey-value型のストレージ。RDBと違う部分の1つに、データをメモリに保存する。RDBはデータをHDDなどの補助記憶に保存するため、速度抜きにして保存という観点で見ればそこまでメモリは必要ない。が、memcachedの場合は全てのデータをメモリに載せる。ということはデータサイズの増減に合わせて行うメモリ管理(mallocとかfree)が重要になるのだが、この管理が面倒らしくフラグメンテーション起こしたり色々面倒なことが起こっていたようだ。それを克服するためにmemcachedはSlabAllocatorという構造を取り入れた。
ざっくり言えばSlabAllocatorはメモリを最初からある程度の量確保しておき、その確保したメモリを一定サイズの塊に分割してフラグメンテーションが起こらないようにした。そしてこの構造を理解する事でtelnetからmemcachedに入っているkey-valueを取得できるようになる。
memcachedはchunkという小さなメモリ領域に値を保存する。このchunkは決まったサイズのものだ。chunkのサイズは複数あるが、同一サイズのchunkをまとめたものがSlabと呼ばれる。memcachedはまずメモリをPageという単位で分割する。このPageはデフォルトで1MB。そのPageのなかにそれぞれのSlabが入り、Slabの中にはSlab固有のchunksizeのchunkたちが入っているという構造だ。*1
データを入れる場合、memcachedはそのデータサイズに一番近いchunkにそのデータを格納する。0.5KB、1KB、2KBのSlabがあり、今0.8KBのデータを格納しようとした場合は1KBのchunkを選択する。Slabのサイズは最小で96バイトで、デフォルトでは1.25倍のサイズのSlabが用意される。前述の例の場合、1.1KBのデータは2KBのchunkに入ることになるが、その場合0.9KBが無駄になる。Slab間のサイズが大きすぎると無駄が発生してしまう。

stats itemsの意味

ともかく、slabとchunkという単位が分かった。ここでmemcachedプロトコルについてのドキュメントを見てみる。
https://github.com/memcached/memcached/blob/master/doc/protocol.txt

最初に打ったstats itemsというのは実際の所なにを表しているのか調べてみると、以下のように書いてあった。

The "stats" command with the argument of "items" returns information about
item storage per slab class. The data is returned in the format:
STAT items:: \r\n

どうやらstats itemsで見られるのはslabごとに入っているkeyの情報だったようだ。今回はhogeというkeyを1つしか入れていないため、slabclass = 1のものしかmemcached内で生成されず、そのslabclass = 1のslabに関する情報が出てきていたのだ。STAT items:1:number 1 というのはどうもslabclass1には1つのkeyしかはいっていないという情報が出ているらしい。ちなみにstats slabsでSlab自体の一覧情報が表示される。

> stats slabs 
STAT 1:chunk_size 96
STAT 1:chunks_per_page 10922
STAT 1:total_pages 1
STAT 1:total_chunks 10922
STAT 1:used_chunks 2
STAT 1:free_chunks 0
STAT 1:free_chunks_end 10920
STAT 1:mem_requested 144
STAT 1:get_hits 1
STAT 1:cmd_set 1
STAT 1:delete_hits 0
STAT 1:incr_hits 0
STAT 1:decr_hits 0
STAT 1:cas_hits 0
STAT 1:cas_badval 0
STAT active_slabs 1
STAT total_malloced 1048512
END

stats cachedump

ここでやっと本題のkey-valueはどうやって見るのか?というところに来る。見たい値はstats cachedumpコマンドを使えばいい。stats cachedumpは以下の形式で打てばいいようだ*2
stats cachedump
slab-idについてはもう分かるだろう。stats itemsまたはstats slabsで取得したidを使えばいい。stats slabsからそのchunksizeがわかるので、データサイズからslab-idの値を推測できそうだ。limitは表示する値のlimitだ。

ということで

>stats cachedump 1 1
ITEM hoge [4 b; 1390641291 s]
END

できた!

*1:slabとpageが常に1:1で割り当てられるのか、多:1なのか、多:多なのかはよく分からない...

*2:cachedumpについてのドキュメントがどこにもなくて無いんだけど、みんなどうやって見つけたの…