Technofara

Golangエンジニア!仕事で必要になって勉強した事とか、新しい事とか色々まとめたりを緩くやります。技術系と思考系だけにしておきます、

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が立ち上がり、ブラウザで確認できるようになります。 f:id:shinofara:20160505123806g:plain

実際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

f:id:shinofara:20160317014231p:plain:w600

2 守るBranchを選択

f:id:shinofara:20160317014243p:plain:w600

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

f:id:shinofara:20160317014248p:plain:w600

4 設定後pushした直後

f:id:shinofara:20160317014254p:plain:w600

5 テストがコケた場合

f:id:shinofara:20160317014259p:plain:w600

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

f:id:shinofara:20160317014303p:plain:w600

これでみんなも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 

http://localhost:3000/

f:id:shinofara:20160316014703p:plain

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以下に取得する方法は、 Godepsgodo,gb などを使ってね。って事らしいです。

まだ試験的だからってのはあるので今後のバージョンでは
取得とymlなどでのファイル管理とかそういうのが標準搭載されればいいなーと思いました。

配布用、CI用としては1.5のvendoringがあるだけでかなり嬉しいので満足です!

と、適当に調べて適当にまとめた、1.5 vendoringレポートでした。