読者です 読者をやめる 読者になる 読者になる

はてなブログからgithub.ioに移転します

移転先:

俺とお前とlaysakura

OSS版droneで快適オンプレCI

OSS版drone をソースからビルドして自前サーバで稼働し、GitHub Enterprise (バージョン11) のprivate repogitoryでCIしました。

CentOS 6.5上に構築してます。現在のdroneはCentOS 6.xはサポート対象外という噂ですが、以下の手順でとりあえず問題なく動作します。

droneのバージョンは、2015/12/02 時点でのmasterブランチのHEADのもの使ってます。

セットアップの手順と、GH:E 11 x private repoならではのハマりどころに対するworkaroundを書きます。

droneを使った感想は、オンプレ版Travis CIみたいで控えめに言って最高といったところです。

dockerインストールと動作確認

http://qiita.com/kajitack/items/776437138630d2f4d763 通り。

sudo rpm -ivh http://ftp.riken.jp/Linux/fedora/epel/6/x86_64/epel-release-6-8.noarch.rpm

sudo yum update
sudo yum -y remove docker
sudo yum -y install docker-io
sudo service docker start
sudo chkconfig docker on

sudo docker pull centos
sudo docker images centos
sudo docker run -i -t centos /bin/bash  # ゲストOSへ入ってみる
exit

Go 1.5 のセットアップ

droneはGo製で、1.5からのvendoring使ってるので。

wget https://storage.googleapis.com/golang/go1.5.1.linux-amd64.tar.gz

tar xvf go1.5.1.linux-amd64.tar.gz

mv go goroot

mikdir gopath

# ~/.bashrcに追記
# Go
export GOROOT=$HOME/goroot
export GOPATH=$HOME/gopath
export PATH=$GOPATH/bin:$GOROOT/bin:$PATH

droneのビルド

gitインストール

yum install -y git

gccのアップグレード

CentOS 6.5の gcc 4.4.7だと、droneの依存ライブラリのlibsassがコンパイルエラーになる。 http://abyssluke.hatenablog.com/entry/2014/07/07/202702 を参考にgccをアップグレード。

wget http://people.centos.org/tru/devtools-2/devtools-2.repo -O /etc/yum.repos.d/devtools-2.repo
sudo yum install devtoolset-2-gcc devtoolset-2-binutils
sudo yum install devtoolset-2-gcc-c++ devtoolset-2-gcc-gfortran

/opt/rh/devtoolset-2/root/usr/bin/gcc --version

droneビルド

git clone git://github.com/drone/drone.git $GOPATH/src/github.com/drone/drone
cd $GOPATH/src/github.com/drone/drone

git log |head -n1
commit 69f5d90fd3076fe0d3db0d342e818a0800477fcd

make deps    # Download required dependencies

scl enable devtoolset-2 bash  # 新しいg++じゃないとsassのビルドに失敗する
sudo ./contrib/setup-sassc.sh
sudo ./contrib/setup-sqlite.sh

export GO15VENDOREXPERIMENT=1
make gen     # Generate code
make build   # Build the binary

dronercを設定してブラウザでアクセス

http://qiita.com/sonots/items/71da7797139aab7c28d1 を参考に。

https://github.dena.jp/settings/applications/new

で、CI回したい対象のレポジトリにアクセス権のあるuser/organizationでdroneアプリを登録しておく。

dronerc を編集。 (ネットには drone.toml での設定情報が多いが、最近は dronerc に移行中らしい)

vim /etc/drone/dronerc


#!/bin/bash

# server configuration

SERVER_ADDR=":80"
#SERVER_CERT=""
#SERVER_KEY=""

# database configuration

DATABASE_DRIVER="sqlite3"
DATABASE_CONFIG="/var/lib/drone/drone.sqlite"

# remote configuration
REMOTE_DRIVER="github"
REMOTE_CONFIG="https://github.dena.jp?client_id=xxxxxxxxxxxxx&client_secret=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&private_mode=true&open=true&ssh=true"
## private_mode=true : private repo を扱う
## ssh=true : `git fetch` するときに、private repoのhttpsだとUsernameやPassword入力を求められてしまうので、それを回避

# docker configuration

DOCKER_HOST="unix:///var/run/docker.sock"
#DOCKER_CERT=""
#DOCKER_KEY=""
#DOCKER_CA=""

# plugin configuration

PLUGIN_FILTER="plugins/*"

droneを立ち上げてみる。

sudo $GOPATH/src/github.com/drone/drone/drone --config=/etc/drone/dronerc

localhostから http://DRONE_HOST:80 へアクセスし、ログインしてOAuthも突破。

注意: REMOTE_CONFIGssh=true は、GH:E 11を使用しているなら本質的に重要です。GH:E 11 だと、初回起動より前にこのオプションをつけていなければ git fetch に失敗してテストが進行しません。もしも先に起動してしまったら、 /var/lib/drone/drone.sqlite を一度消しましょう。

という話をbradrydzewski氏に教えていただいた

droneをデーモンとして動かす

sudo vim /etc/init.d/drone


#!/bin/bash
#
# chkconfig: 2345 85 15
# description: drone - a CI service.

## http://qiita.com/volanja/items/383fcd6d2d2e14792723 を見て書いた

. /etc/rc.d/init.d/functions

prog="drone"
APP_ROOT=$HOME/gopath/src/github.com/drone/drone
APP_USER="root"
conf=/etc/drone/dronerc
lockfile="/var/lock/subsys/${prog}"
logfile="/var/log/${prog}.log"
CMD="${APP_ROOT}/drone --config=${conf} &"
action="$1"
RETVAL=0

cd ${APP_ROOT} || exit 1

start(){
  echo -n $"Starting $prog: "
  daemon --user=${APP_USER} "cd ${APP_ROOT} && ${CMD}" > $logfile 2>&1
  RETVAL=$?
  [ $RETVAL -eq 0 ] && touch $lockfile
  echo
}

stop(){
  echo -n $"Stopping $prog: "
  killproc ${prog}
  RETVAL=$?
  if [ $RETVAL = 0 ]; then
    rm -f ${lockfile}
    success $"$prog stop"
  else
    failure $"$prog stop"
  fi
  echo
}

rh_status() {
    status ${prog}
}

case $action in
start)
  start
  ;;
stop)
  stop
  ;;
status)
  rh_status
  ;;
restart)
  stop
  sleep 2
  start
  ;;
*)
  echo >&2 "Usage: $0 <start|stop|restart|status>"
  exit 1
  ;;
esac
exit 0
sudo chkconfig --add drone

あとは...

普通にレポジトリを有効化して、レポジトリ.drone.yml を追加して、ブランチpushすればテストが走る。 travisみたいな感じ。

おまけ: .drone.yml の例

C++製のライブラリと、そのGoバインディングのテストを走らす設定。

build:
  image: drone/golang:1.5
  commands:
    # https://github.com/google/googletest を引っ張ってくる
    - git submodule init
    - git submodule update

    # drone/golang:1.5 は割とミニマムなので、インクルードヘッダの入ったパッケージを取得可能にする
    - echo 'deb-src http://httpredir.debian.org/debian jessie main' >> /etc/apt/sources.list
    - apt-get update
    - apt-get install zlib1g-dev

    - wget -q https://cmake.org/files/v3.3/cmake-3.3.0-Linux-x86_64.tar.gz
    - tar xf cmake-3.3.0-Linux-x86_64.tar.gz
    - cmake-3.3.0-Linux-x86_64/bin/cmake -DCMAKE_BUILD_TYPE=Debug .
    - make -j4 VERBOSE=1 mytest
    - ./test/mytest

    - go test -x -v

git commit時の名前とemail切替をalias登録しておくと捗る

小ネタ

f:id:laysakura:20151031075959p:plain

↑恥ずかしいアレ↑

  • 会社のGitHub Enterprise用のユーザ情報が ~/.gitconfig に書かれている
  • プライベートで書いたコード github.com に上げようとしたけど git commit 時に会社ユーザ情報使われた
  • git config --local user.name 'プライベートユーザ名' などすれば良いんだけど、面倒でついつい忘れちゃう

そんなあなた(私)のために、

$ git laysakura  # プライベート用ユーザ情報へ切替

$ git kaisha       # 会社用ユーザ情報へ切替

とできるようにした話。

~/.gitconfig

...

[alias]
    laysakura = !git config --replace-all --local user.name 'Sho Nakatani' && git config --replace-all --local user.email 'laysakura@example.com'
    kaisha     = !git config --replace-all --local user.name 'nakatani.sho' && git config --replace-all --local user.email 'nakatani.sho@heisha.co.jp'

...

git commit とかの普通のサブコマンドに補完が聞くなら git laysakura とか git kaisha にも補完効くはずで、ユーザ情報切替が捗ります

速いしスケールする並列CSVパーサ作った紆余曲折話

laysakura/PartialCsvParser · GitHub

年の瀬にどうしてもCSVを並列にパースしたくなって、PartialCsvParserというC++のライブラリを作った。

という3拍子揃ったやつです。

細かいことはプロジェクトのページを見ていただくとして、ブログには頑張ってベンチマークとった話と、全然スケールしなかったのに2行追加しただけでグンと並列性能が上がった話を書く。

作る前の勝算

CSVの並列パーサライブラリって、ちゃんとスケールするものが割と簡単に作れると思って作り始めた。

例えば2並列にしたいなら、CSVファイルを前半と後半に2分割してあげて、それぞれのスレッドで別々にパースしてあげれば良いだけ。

なぜスケールさせられると思ったかというと、

  • 確かにCSVのパーズという(比較的)単純な処理は、ディスクIOが並列性能のボトルネックになりがち
  • とはいえこれからはSSDの時代だし、SSDなら並列読込はスケールするって色んなとこで見た気がする

という感じ。

最初からHDDでの並列性能は無視して、SSDで戦おうと思ってた。

作ったらスケールしなかった

バージョン 0.1.0 を作ってみてSSD環境でベンチマークを取ってみたら全然スケールしなかった。寝た。

起きて悔しくなったのでもう一度色々測定してみると、 CSVファイルがページキャッシュに乗っている時、つまりオンメモリな処理をしているときは割とちゃんとスケールすることが分かった。

となるとやっぱりSSDアクセスがボトルネックになっていそう。 少し頭を巡らせパーサの気持ちになって考えてみた。

そもそもPartialCsvParserは、パース対象のCSVファイルをどかんと丸ごとmmap(2)している。 で、複数のスレッドが同じタイミングで離れた領域(ページ)を触りに行く。

このとき、基本的には要求したページがまだメモリに乗っていなくてページフォルトを起こし、ディスクまで取りに行くことになるのだけど、複数のスレッドから同タイミングで「そこそこ離れているけどそんなに離れてはいない」ページを取りに行くことになる。

SSDの気持ちになるとこれはつらい。そもそもSSDが並列性能出るのって、SSDフラッシュメモリ(板)の集合であって、別々の板のページを同時に要求されたら同時に返せるからってのが基本...なはず。

(SSDチップにもよるけど、)CSVファイルなんてものはサイズもたかが知れているし、同一の板にちょい離れたページを同タイミングで要求しているような気がして、そうするとSSDアクセスがボトルネックになっても仕方がないなという気持ちになった。

じゃあどうしたか

やっぱり複数スレッドから同時にCSVなんて小さいファイルにアクセスしに行くのは大変だろうと思ったので、CSVファイルをドカンと1スレッドでメモリに乗せたくなった。

1スレッドでドカンとやると、ランダムアクセスじゃなくてシーケンシャルアクセスになる。 いくらランダムアクセスに強いと言われるSSDといえど、シーケンシャルアクセスのほうが1桁は速い。

とはいえmmap(2)を捨ててread(2)使うのは、コーディング上面倒くさい。とても面倒くさい。

mmap(2)でシーケンシャルに読みたい、つまりプリフェッチできないもんかなぁ。 と思って探したらmadvise(2)が見つかった。

madvise(2)カーネルに「この領域はプリフェッチしておいてほしいなぁ(チラッチラッ」となんとなーくお願いするためのシステムコール、らしい。

ダメ元で使ってみたプルリクエストがこちら。

Prefetch pages by madvise(2) to prevent random accesses from threads. by laysakura · Pull Request #12 · laysakura/PartialCsvParser · GitHub

たった2行。コメント入れても3行。

これがうまくいって、グンとスケールするようになった。

結局のところ、まだスレッド作ってない段階でガツッとシーケンシャルアクセスしてメモリに乗せるので、 図らずもHDDでもちゃんとスケールするようになった。

ベンチマークを頑張ってとった話

年末で実家にこもれたので、まとまった時間とってそこそこしっかりベンチマークをとれた。

どんな風にベンチマークとったかなどは、ここ にかなりしっかり書いた(つもり)。

ここではすぐに役立ちそうなことを2点ほど。

Google SpreadSheetはデータ集計に超便利

学生時代はExcelとかGnuplot (ウッアタマガ) とか使ってベンチマーク結果を集計してたけど、会社でGoogle Drive使うこと増えてきたし、 試しにGoogle SpreadSheetでデータ集計してみた。

これが大当たりで、

  • 実験結果の集計に使うレベルならExcelと遜色なく使える
  • グラフウィザードがExcelよりなんとなく分かりやすい気がするし、デフォルトのデザインも悪くない
  • 生データやグラフを簡単に公開できる。特にグラフは自動で画像ファイルにしてくれたりする

と最高だった。

ディスクのパフォーマンス計測にはfioがよかった

hdparmとかbonnie++とか、世の中には色々とディスクのベンチマークをとるためのツールが存在している。

そんな中でfioはダントツで使いやすかった。 こんな感じで、短い設定ファイル作ってコマンドライン引数にそれだけ渡せば何かそれっぽい結果が出てくる。

$ vim random-read-mem.fio
[random-read]
rw=randread
size=512m
directory=/dev/shm

$ vim sequential-read-hdd.fio
[sequential-read]
rw=read
size=512m
directory=/dev/shm

$ fio random-read-mem.fio

root権限いらないのもポイント高い。

まとめ

2014年も終わろうとしてるのにCSVなんて使う人少ないでしょうけど、どうしてもCSVをパースしたくなったらPartialCsvParserのことを思い出してください。

MySQLiteストレージエンジンの発表をしました

第2回 MariaDB/MySQL コミュニティ イベントで、以前作っていたMySQLiteというMySQL/MariaDBのストレージエンジンの発表をしました。

スライドはこちらです。

MySQLite: SQLiteデータベースを読み書きするMySQLストレージエンジン

他の方の発表も楽しめました。 懇親会でMariaDB開発・セールスのおじさま方に「You look so cool!」って言われたのが一番嬉しかったです。

お話を持ちかけてくれたり会場を提供してくださったDeNA様、ありがとうございました。