Gem in a Boxサーバーのデータをrsyncで同期する

More than 3 years have passed since last update.


TL;DR


  • Gem in a BoxのGemを別のGem in a Boxサーバーに同期する


    • 新しくpushされたとき

    • 削除されたとき



  • Rack Middlewareでrsyncを使う


はじめに

Gem in a BoxのデータをRackのMiddlewareを使用して同期する方法を紹介します。

プライベートなGemサーバーをCIに組み込む時にGemデータを同期して冗長化する必要があったのでこの方法をとりました。

あくまでもデータ同期だけです。Active-Standbyを想定してます。


方針


やりたいこと


  • Gem in a BoxにpushしたGemをpushされた時に同期する

  • 削除された時も同期削除する


どうするか


  • RackでPOSTリクエストまたはDELTEリクエストが来た時にrsyncでデータディレクトリを同期する

  • 同期してからレスポンスを返す

  • RackのMiddlewareで実現する

  • Proxyしてるrubygems.orgのGemなんかはどうでもいい


そのために


  • rsyncするRack Middlewareを使う

  • rsyncするため Primary -> Secondary へSSH接続できるようにする


    • Gem in a Box用のユーザを作る

    • 秘密鍵・公開鍵のペアを適切に設定する




その他


  • Unicornを使う

  • systemdでUnicornを起動する

  • systemdの環境ファイルにお互いのIPを持つ

  • Rackは環境ファイルからの環境変数で自分がPrimaryかSecondaryか判断する


注意点


  • rsyncなのでGem数が増えたとき時間かかるかも


    • rubygems.orgをProxyしなければいい



  • GETに比べてPOST/DELETEが少ない前提

  • Unicornが落ちたらデータが同期できない


やってみる

Vagrantでやってみます。一式をGithubに用意しました。

https://github.com/nownabe/example-geminabox-replication

まずこのレポジトリをクローンしてください。

git clone https://github.com/nownabe/example-geminabox-replication

cd example-geminabox-replication

VagrantでGem ServerのPrimary/Secondaryとなる2台のVMを起動してプロビジョンします。

Rubyをビルドするので時間かかります。

vagrant up

このVagrantfileを使うと、次のIPアドレスで起動されます。


  • Primary: 192.168.33.29:8080

  • Secondary: 192.168.33.30:8080

まずブラウザで確認してみます。どちらのサーバーもGem in a Boxの画面が見れると思います。

スクリーンショット_2015-08-31_0_05_53.png

適当なGemをPushしてみましょう。

$ gem push pkg/ringc-0.1.2.gem --host http://192.168.33.29:8080

Pushing gem to http://192.168.33.29:8080...
Gem ringc-0.1.2.gem received and indexed.

ブラウザを更新するとGemがpushされたことが確認できます。

スクリーンショット_2015-08-31_0_23_53.png

Secondaryもrsyncによって同期されています。

スクリーンショット_2015-08-31_0_23_59.png

削除も同期されます。

スクリーンショット_2015-08-31_0_24_33.png

SecondaryからGemをインストールできることを確認してみます。

$ gem install ringc -s http://192.168.33.30:8080

Fetching: ringc-0.1.2.gem (100%)
Successfully installed ringc-0.1.2
Parsing documentation for ringc-0.1.2
Installing ri documentation for ringc-0.1.2
Done installing documentation for ringc after 0 seconds
1 gem installed

ばっちりですね!!:laughing:


解説

具体的な方法はGithubのコードを読んでいただくのが一番だと思います。

重要な一部だけ説明します。


SSH



  • gem_serverユーザを作成している


  • gem_serverユーザのホームディレクトリは/opt/gem_server


  • /opt/gem_server/.ssh以下をごにょごにょやって Primary -> Secondary にSSHできるようにしている


config.ru

Gem in a Boxを起動するconfig.ruは次のようになってます。


config.ru

require "geminabox"

require "rack/rsync"

if ENV["GEMINABOX_PRIMARY"] == ENV["GEMINABOX_MYADDRESS"]
src = "/opt/gem_server/data/"
dst = "#{ENV['GEMINABOX_SECONDARY']}:/opt/gem_server/data/"

use Rack::Rsync, src, dst, "-a", "--delete" do |env|
env["REQUEST_METHOD"] == "POST" || env["REQUEST_METHOD"] == "DELETE"
end
end

Geminabox.data = "/opt/gem_server/data"
Geminabox.rubygems_proxy = true
run Geminabox



  • systemdが読む環境ファイルから自分のIPがPrimaryのIPと一致するかチェックする

  • 一致するときだけRack::Rsyncというミドルウェアを使うようにする


  • Rack::RsyncにリクエストメソッドがPOSTかDELETEのときだけrsyncを実行するように条件を与えている


systemd

systemdのunitファイルは次のようになってます。

[Unit]

Description=Gem Server

[Service]
WorkingDirectory=/opt/gem_server/geminabox
ExecStart=/usr/local/bin/unicorn -p 8080
EnvironmentFile=/opt/gem_server/geminabox/environments
User=gem_server
Group=gem_server

[Install]
WantedBy=multi-user.target

なんの変哲もないですね。起動ユーザをgem_serverユーザにして、環境ファイルを読むようにしてます。

Unicornが落ちたとき自動で復旧するようにRestart=alwaysとかしといてもいいかもしれません。


おわりに

タイミングシビアに同期したい場合にいかがでしょうか。

これだと不安だって場合はもっと下のレイヤーでやったほうがよさそうです。