httpコンテナが増えたらSerfを使って自動的にNginxが割り振るサーバを増やすというserfを使ってる感を出してみた
以前Qiitaに投稿したGWに一人さみしくDockerとSerfと戯れてみた(๑´ڡ`๑) の続き 今回は、下記構成をDockerを使って構築
- Nginx(LB) x 1
- python(http) x ∞
そして、Serfを使ってhttp serverが増えるたびに、nginxが通知する先を増やす。
というなんか、俺 Serf 使いになったんじゃね?と錯覚するような事をやります。
今回の全ソース類 https://github.com/shinofara/serf/tree/dbb46c60a24dede36b5546f6ff611131d9b4636a
コード量が多くなるので、抜粋して説明しよう
Docker
Dockerfiles
BuildのためのMakefile
別にMakefileでなくてもOK
default: build-nginx build-app
build-nginx:
@cd docker/nginx && docker build -t shinofara/serf_nginx .
build-app:
@cd docker/app && docker build -t shinofara/serf_app .
あとは$ make で作成するだけ
各コンテナの実行
Nginx Container(LB)
docker run -d -t \
--name proxy \
-p 80:80 \
shinofara/serf_nginx
proxyという名前で、80番ポートを開放して立ち上げます。 実際はproxy.shを実行
App Container(Http)
docker run -d -t \
--name node1 \
--link proxy:node \
-h node1 \
shinofara/serf_app
node1という名前で、proxyと内部リンクさせて立ち上げます。
実際はnode.shを実行
確認方法
上の2つを実行することで、Nginx(LB) + http serverが立ち上がり、ブラウザで確認できるようになります。

実際Serfはどのように動いているのか
ここから本題
SerfのRoleを2つ作成してます。
- web
- app
webはLBのroleの事で、appはhttp serverです。
といっても今回はwebrole以外特に何もしていないですが、名目上分けて管理しています。
LBとHTTPの内部で実行されているserf agent
LB
serf agent -bind 0.0.0.0:7946 -event-handler /usr/local/src/python/handler.py -log-level=debug -tag role=web
実際はsupervisorで管理しています。supervisor.conf
HTTP
serf agent -bind 0.0.0.0:7946 -join $NODE_PORT_7946_TCP_ADDR:7946 -tag role=app
実際はsupervisorで管理しています。supervisor.conf
LB側だけにhandlerを登録
Nginx(LB)では、httpサーバが増減したらそれらをupstream に追加・削除したいので、
event handlerを使って検知して、nginx.confを変更して再起動するという処理を書いています。
汚いけど、これ
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from serf_master import SerfHandler, SerfHandlerProxy
from jinja2 import Environment, FileSystemLoader
import logging
import sys
import bsddb
import json
import subprocess
import os
class AppHandler(SerfHandler):
def member_join(self):
# nginx.confのテンプレート取得
env = Environment(loader=FileSystemLoader('/usr/local/src/python/', encoding='utf8'))
tpl = env.get_template('nginx.tpl.conf')
# ip管理用配列
ips = []
db=bsddb.hashopen('hashtest.db','c')
try:
dbips = json.loads(db["ips"])
for ip in dbips:
ips.append(ip)
except:
print "no cache"
# 追加されたagent情報は標準入力で来るので(改行コード区切り)
for line in iter(sys.stdin.readline, ""):
print line
agent = line.split("\t")
node = agent[0] #node
ip = agent[1] #node
role = agent[2] #node
# この時app roleに追加された場合、追加
if role == "app":
ips.append(ip)
db["ips"] = json.dumps(ips)
db.sync()
# テンプレートから書き出すデータを生成
html = tpl.render({'backends':ips})
# nginxに読み込ませるconfファイルを作成
f = open("/etc/nginx/conf.d/proxy.conf", "w")
f.write(html)
f.close()
# Nginxを再起動
cmd = 'supervisorctl restart nginx'
ret = subprocess.check_output( cmd.split(" ") )
print ret
if __name__ == '__main__':
handler = SerfHandlerProxy()
# 存在するappとdefaultのみ許可される(Roleごとのハンドラを登録)
handler.register('default', AppHandler())
handler.run()
実際に書いたhandler.py
そして、この時のnginx.tpl.confはこちら
upstream backend {
{% for backend in backends %}
server {{ backend }}:9000;
{% endfor %}
}
server {
listen 80;
location / {
proxy_set_header Host $host;
proxy_pass http://backend/cgi-bin/index.py;
proxy_redirect default;
break;
}
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Real-IP $remote_addr;
}
html = tpl.render({'backends':ips}) で、upsteam の箇所が色々書き換わるイメージ
これで、app roleに追加があった場合、nginxはかってにupstreamを書き換えて再起動までやっちゃいます。
冒頭で増減と書きましたが、体力の関係で削除は飛ばしました。
最後に
serf は今まで触りくらいしか触ってこなかったけど、イベントまわりを理解しておけば
便利ツールが色々作れそうだなと思いました
そういえば
https://github.com/sorah/mamiya
はcookpadで使われているSerf+s3を使ったデプロイツールみたいですね。
※S3以外も可能
もっと色々やれそうなので、何か進展があれば追加します。
Rails5 + RspecをCircleCIで動かしてみた
http://shinofara.hateblo.jp/entry/2016/03/16/234010
前回Rspec導入までしたので、これはCIもやるっしょって事で、
CircleCI と連携させました。
CircleCI ではテスト結果を収集させるには、
JUnit Report形式 にしないといけないので、
rspec_junit_formatter を入れました。
https://github.com/shinofara/sample-rail5/pull/2/commits/e1ebee0c9d9fed591395251eb848a81e3e766e71#diff-8b7db4d5cc4b8f6dc8feb7030baa2478R39
こんな感じ
あとは実行時に、レポート形式を指定して実行させる為に
公式のドキュメントを参考にして
https://github.com/shinofara/sample-rail5/pull/2/commits/53d173f96311c62b18114db98d9fcf93eee1d784#diff-29944324a3cbf9f4bd0162dfe3975d88R14
こんな感じ
準備が整いましたので、 CircleCI と連携させて実行させてみます。
連携方法は割愛しますが、circle.yml の例を貼っておきます。
$ cat circle.yml
machine:
ruby:
version: 2.2.3
environment:
RAILS_ENV: test
dependencies:
pre:
- gem uninstall bundler
- gem install bundler
test:
override:
- bundle exec rspec -r rspec_junit_formatter --format RspecJunitFormatter -o $CIRCLE_TEST_REPORTS/rspec/junit.xml
テスト失敗時の表示確認
https://circleci.com/gh/shinofara/sample-rail5/9
おお、ちゃんとエラーがファーストビューにでておるではないか
あとは、正常に戻して終わり
https://circleci.com/gh/shinofara/sample-rail5/11
これで、RspecからのCircleCI連携が完了!!
と思ったけど、Githubには連携先のサービスから返却されるstatusに応じて、
PRをマージさせないとかできるので、その設定をしてみます。
1 まずは、Settings

2 守るBranchを選択

3 とりあえず全部チェック

4 設定後pushした直後

5 テストがコケた場合

6 テストが正常終了した場合

これでみんなもCIに支配された生活の始まりデスね。
今回の差分
せっかくなのでRails5でRspecした!
http://shinofara.hateblo.jp/entry/2016/03/16/014844
の続き
はじめてのbundler
$ gem install bundler $ bundle install --path vendor/bundle
spec/ dirをつくる
$ rails generate rspec:install
Running via Spring preloader in process 40013
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb
試しに実行
$ bundle exec rspec
No examples found.
Finished in 0.00022 seconds (files took 0.06497 seconds to load)
0 examples, 0 failures
テスト無いから当然やけど!
試しにモデルを作って、テストしてみよーっ
rails generate model user
Running via Spring preloader in process 40126
invoke active_record
create db/migrate/20160316141612_create_users.rb
create app/models/user.rb
invoke rspec
create spec/models/user_spec.rb
かってにusersにしてくれる可愛いやつ
$ bundle exec rspec /path/to/schema.rb doesn't exist yet. Run `rails db:migrate` to create it, then try again..........
怒られたし
ファイル無いから作れ
はい
$ rails db:migrate == 20160316141612 CreateUsers: migrating ====================================== -- create_table(:users) -> 0.0010s == 20160316141612 CreateUsers: migrated (0.0011s) =============================
users tableが出来たようだ!
どこに?
$ cat config/database.yml
を見る限り db/development.sqlite3 にあるようだ
defaultはとりあえずSqlLiteなのね。
はい勧めます。
$ bundle exec rspec
ending: (Failures listed here are expected and do not affect your suite's status)
1) User add some examples to (or delete) /path/to/<APPNAME>/spec/models/user_spec.rb
# Not yet implemented
# ./spec/models/user_spec.rb:4
inished in 0.00042 seconds (files took 2.35 seconds to load)
example, 0 failures, 1 pending
まだテストの中身ないからペンディング....
失敗するように書き換えてみた。
https://github.com/shinofara/sample-rail5/pull/1/commits/f4fd4d1865556cfefdde75aac5be1ce61746eb49
こんな感じ
$ bundle exec rspec
F
Failures:
1) User 作成できるか
Failure/Error: expect(u).to be_valid
NoMethodError:
undefined method `valid?' for nil:NilClass
# ./spec/models/user_spec.rb:7:in `block (2 levels) in <top (required)>'
Finished in 0.00096 seconds (files took 1.1 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/models/user_spec.rb:5 # User 作成できるか
失敗
じゃ、最後は
https://github.com/shinofara/sample-rail5/pull/1/commits/fb0035b4e71dbfe6371b4215e1940f0b0728e5d6
こんな感じに書き換えて
$ bundle exec rspec
.
Finished in 0.00318 seconds (files took 1.1 seconds to load)
1 example, 0 failures
Greeeeeeeen とりあえずRspec動かす編終了
前回との差分 https://github.com/shinofara/sample-rail5/pull/1/files
新しいMacにxbuildでruby入れて、Rails5 beta3 を動かして見た。
xbuildというrvm,nvmをまとめてやれるような奴を入れてみた。
$ git clone https://github.com/tagomoris/xbuild.git
xbuildでruby2.3.0を入れてみるっす
$ ./xbuild/ruby-install 2.3.0 ~/local/ruby-2.3
入れたらPATHを通す
$ export PATH=/Users/shinofara/local/ruby-2.3/bin:$PATH
Rails5を入れて動かしてみよーっ
$ gem install rails -v 5.0.0.beta3 $ rails new sample $ cd sample $ rails server

Golangのテスト結果をJUnit Report形式で書き出してCircleCIに読み込ませた話
ふと、以前作成した、GolangのCircleciをみてみたら、
junit report形式のxmlもつくれや!(本当は出したらよしなしにしますよ。)って書いてあるのを見つけたので 対応してみた
対応後のcircle.ymlがこれ github.com
go test にはreport機能が無いので、今回は
jstemmer/go-junit-report を使うことにしました。
どう使うかというと
go test -v ./... | go-junit-report > junit.xml
といった感じ
ただこれの問題点が
テストがエラーの場合exit code が1になるはずなのですが、junit.xmlの生成に成功しているから
exit code が0になってしまう。
正常だけど正常じゃない。。。
このままだと、https://circleci.com/gh/shinofara/circle-go/31:エラーだけど成功 の様にテスト失敗してるけどCI的にはOKってなっちゃうので
{ go test -v ./...; echo $? > /tmp/status;} | go-junit-report > junit.xml;
exit `cat /tmp/status`
という感じで、一度ファイルに書き出して、それをexitで返すという方法を取ることにしました。
素直に exit $? とすればいいやん?でもできないやん?
ということでこうなりました。
あとは、テスト失敗時、成功時の確認をして問題なければこれにて終了
テスト失敗!そしてRED Continuous Integration and Deployment
テスト成功!そしてGREEEEEEEN Continuous Integration and Deployment
はい、今回は以上
訂正
go-junit-reportには、set-exit-code というオプションがあって、デフォルトではfalseとなっているため、
テスト結果が何であろうともexit 0 となってしまっていたようです。
go-junit-report -set-exit-code=true とすることで、失敗時は失敗となりました。
訂正後のcircle.yml の内容は以下
test:
override:
- mkdir -p $CIRCLE_TEST_REPORTS/golang
- go test -v $(go list ./...|grep -v vendor) | go-junit-report -set-exit-code=true > $CIRCLE_TEST_REPORTS/golang/junit.xml;
3度目の転職をして1年2ヶ月が過ぎました
新年のあいさつ
あけましておめでとうございます! 本日29歳となりました!! 年が明けてめでたいなかすみません!
2014年11月末に
blog.shinofara.xyz
を書いてから、1年と少し過ぎました。
2015年は本当に色々な方々にお世話になりました。
数年ぶりにお会いしたり、転職をキッカケに定期的に飲みに行くようになったり
変化ばかりの2015年でした。
関わってくださいました皆様、本当にありがとうございます。
少しでも頂いたお時間を有意義な物と感じてもらえていればと思います。
今回は転職してからの1年と称して2015年を振り返って、2016年どうしていきたいかをここに書き残したいと思います。
転職して丸一年何をしてきたかなーと
- 年末から年始にかけてCVE関連対応が多かったイメージ
ここで涙を流した人は多いはず - Go言語を使ったプロダクト開発をふたりでしてました
Golang(Go言語)を採用して、たった二人で基盤となるAPIゲートウェイを開発した話 - メドピア開発者ブログ - 第一期新卒の採用担当として、面接・逆求人参加など、社会人になって初めて学生を夢を語ったりしてました
- CircleCI、Container Registry、Terraform, Packerなど開発支援となるものの検証・導入・推進
- sake(日本酒)部部長や、技術部の部長としての生活が始まりました。 + まだまだひよっこなので悩みながら色々進んでいきたいと思っています。
プライベートでは
- 新卒時代から住んでいた練馬を脱出して大井町に浮気したり
- お酒で記憶を二回なくしたり
- 初めてセブ島でダイビングしたり
- 英会話が二年目になったり
- スノボ7回程いったり
- PHPconferenceで初めて当日staffをしたり
数年ぶりに少しリア充だったかも知れません。
今年一番色々考えさせられた出来事
「結局逃げてるようにしか聞こえないよね?やれない環境だからやれる環境に行くことは、やりたい事に対してのショートカットになるけど、やれない環境でやれるようにもって行く人の方が成長すると思うよ」
と言った感じの事を、言っていただく機会があり、振り返ってみると、
そう意識してなかっただけなのか、気づいたたけど気づかないふりをしていたのかもしれませんが
確かに「逃げ」を選んでしまっていたんだな...と思いました。
確かにそうやなとその日は色々考えてしまった。
なので2016年からこのことを心がけて、まず自分から変えていこうと思います。
出来ないと言う前に、やれるだけやって結果出来なかった。出来たといえるように、今年は一歩踏み出していきます。
2016年の
心構え
「感謝を伝える事」と「褒める事」を意識していきたいと思います。
この2つは今まであまりできてないなと....
29際のタイミングで改めて
目標
プライベート面
- 英語でナンパできるように...ではなく、TOEIC650以上
- LTや勉強会での発表数を増やす(主にGolang)
- スカイダイビングしたい(グアムとか、そういう海に囲まれたリゾート地で)
- iosアプリを個人的に開発して公開(案はこれから)
- 婚活(29歳なので)
各KPIは追って共有します。
仕事面
色々カオスな事が多いので、それを現在の環境に適当した形に落とし込んでいきたいです。
- インフラ
- エンジニア採用や新卒育成などのHRS面
- モノリシック(が悪ではないが、現状)
最後に
2016年も2015年に負けない位、挑戦したいと思います。(仕事もプライベートも)
矛盾するかも知れませんが、仕事の時間を減らして、質を更に上げていきたいと思います。とこのブログをたまたま見て思いました。
家族がいる起業家の働き方 | Like A Silicon Valley
ま、子供居ませんが
それどころか嫁なんて居ませんが
てかそれ以前に予定も.....
いうほど綺麗にまとめれませんでしたが
次の1年も、何が起きるかわかりませんが、結果楽しかったと思えるように歩み続けたいと思います。
最後になりましたが、本日1月2日が誕生日ですので、恒例のアレを貼っておきます。 www.amazon.co.jp |д゚)チラッ
これからもよろしくお願いします。 + 2016年もご迷惑をお掛けするかもしれませんが、どうぞよろしくお願いいたいます。 + また新年お会いさせて下さい。
shinofara(篠原)
Golang1.5のvendoringが期待と少し違ったという感想
サンプルでgojiを使って検証してみました。
今までだったら
$ go get github.com/zenazn/goji
とすると
$ ls -d $GOPATH/src/github.com/zenazn/goji /Users/shinofara/go/src/github.com/zenazn/goji
GOPATH以下にダウンロードされて、
こんなmain.go を用意して
package main
import (
"fmt"
"net/http"
"github.com/zenazn/goji"
"github.com/zenazn/goji/web"
)
func hello(c web.C, w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", c.URLParams["name"])
}
func main() {
goji.Get("/hello/:name", hello)
goji.Serve()
}
$ go run main.go
とかすればOKだったんですが、これだと複数のプロジェクトで開発する時困るんですよね。
例えば趣味は最新のバージョンつかうけど、とあるプロジェクトでは半年前のバージョンだったりとか
でもこの仕組だと上書きされるので、どちらか一つ。。あぁ....
コレは結構前から課題で、Godepsさんが登場したり、godoさんが...gbさんが
だったんですけど、まだコレがデファクト!!的なのはなかったんです。
で、golang1.5になるとvendoring登場!とみてo(´∀`)oワクワクしてたんですけど (ちゃんと嫁系オチですが)
実際golang1.5のvendoring機能の使いかたを書くと
$ tree -L 4 .
.
├── main.go
└── vendor
└── github.com
└── zenazn
└── goji
こんな感じで、プロジェクトルートにvendorを置いて、その下に$GOPATH/src以下と同じように置けばvendorを優先して読みまっせ!まかせてーな!という感じ
なので期待して
$ go run main.go
main.go:7:9: cannot find package "github.com/zenazn/goji" in any of:
/usr/local/go/src/github.com/zenazn/goji (from $GOROOT)
/Users/shinofara/go/src/github.com/zenazn/goji (from $GOPATH)
main.go:8:9: cannot find package "github.com/zenazn/goji/web" in any of:
/usr/local/go/src/github.com/zenazn/goji/web (from $GOROOT)
/Users/shinofara/go/src/github.com/zenazn/goji/web (from $GOPATH)
よまへんやないか!嘘つきか!
これはvendor以下を一切みないで、GOPATH/src以下を見に行っててないと怒ってるのです。
ではではどうすればいいのか?それは簡単で
$ GO15VENDOREXPERIMENT=1 go run main.go
go15のGO15VENDOREXPERIMENTのフラグを立てるとあら不思議
$ GO15VENDOREXPERIMENT=1 go run main.go 2015/08/22 03:38:36.965105 Starting Goji on [::]:8000
このようにvendor以下を読みにいってくれるのです。 便利ですね?
さて、ではvendor読んでるのだから問題ないのでは?と思うと思いますが、
確かに問題は無いのです、、、が
$ go get
をすると$GOPATH以下に行く.. どうすれば、vendor以下にダウンロードできるのか?
答えは簡単で、1.5の段階ではvendorを使えるcommandはrun, test, build, install, etcのみだそうです。
vendor以下に取得する方法は、 Godeps、godo,gb などを使ってね。って事らしいです。
まだ試験的だからってのはあるので今後のバージョンでは
取得とymlなどでのファイル管理とかそういうのが標準搭載されればいいなーと思いました。
配布用、CI用としては1.5のvendoringがあるだけでかなり嬉しいので満足です!
と、適当に調べて適当にまとめた、1.5 vendoringレポートでした。