この記事は、nginx Advent Calendar 2015の8日目の記事です。
nginx-build
nginx-buildはnginxをビルドするためのコマンドラインツールです。
使い方については過去にQiitaに書いた記事やnginx-buildのREADME.mdにそれなりに詳しく書いてあるのでそちらを参照して下さい。
- cubicdaiya/nginx-build/README.md
- nginx-buildでnginxをビルドする
- nginx-buildでnginxにサードパーティモジュールを組み込む
- nginx-buildでnginxの依存ライブラリを静的に組み込む
今回はnginx-buildの内部実装について少し解説します。わりと泥臭いこともやってます。
nginx-build internals
nginx-buildが行っているのは大きく分けて以下の3つです。
- nginxや依存ライブラリ、拡張モジュールのソースコードのダウンロード
- configureのオプション文字列を自動生成
- makeを実行
nginx-buildはGoで書かれていますが、中でもGoの特徴を最も活かしているのが最初のダウンロード処理部分です。
ゴルーチンを利用した並行ダウンロード
nginx-buildでは指定したオプションにもよりますが、実行すると複数のソースコードをダウンロードしてワーキングディレクトリに展開します。
- nginx
- PCRE
- OpenSSL
- 拡張モジュール群
各々のダウンロード処理はwget
、git
、hg
のいずれかによって行われます。実際の処理はこんな感じです。(ちょっと前までカウンターとチャネル使って実装してたけどsync.WaitGroup
使って書き直した)
wg := new(sync.WaitGroup)
if *pcreStatic {
wg.Add(1)
go downloadAndExtractParallel(&pcreBuilder, wg)
}
if *openSSLStatic {
wg.Add(1)
go downloadAndExtractParallel(&openSSLBuilder, wg)
}
if *zlibStatic {
wg.Add(1)
go downloadAndExtractParallel(&zlibBuilder, wg)
}
wg.Add(1)
go downloadAndExtractParallel(&nginxBuilder, wg)
if len(modules3rd) > 0 {
wg.Add(len(modules3rd))
for _, m := range modules3rd {
go downloadAndExtractModule3rdParallel(m, wg)
}
}
// wait until all downloading processes by goroutine finish
wg.Wait()
シェルスクリプトだとこの手の処理をうまくやるのはしんどいですが、Goだととても楽です。
ビルドエラーになりやすいポイントを自動的にカバー
nginxに限った話ではありませんが、プログラムをソースコードからビルドしようとするとハマリどころと言いますか、ビルドの実行環境やconfigureオプション、依存ライブラリなどの外的要因によってプログラムがうまくビルドできないことがあります。
nginxでもそういうのがいくつかあって、あまりにもハマリやすそうなものについてはnginx-build側でカバーするようにしています。(大体MacかOpenSSLで起きる)
OpenSSLとngx_http_ssl_module
例えば--with-openssl
オプションを利用してOpenSSLを静的リンクしてnginxをビルドする場合を考えてみましょう。
./configure --with-openssl={OpenSSLのソースコードディレクトリへのパス}
実は上記のやり方だとnginxはOpenSSLを静的リンクしません。というかオプション自体を無視します。(でもnginx -V
のconfigure arguments
の出力にはでるからハマる)
OpenSSLを静的リンクするには--with-http_ssl_module
も合わせて必要になります。そもそもOpenSSLはこのモジュールのためにあるようなものなのである意味当然と言えるかもしれません。
./configure --with-openssl={OpenSSLのソースコードディレクトリへのパス} --with-http_ssl_module
nginx-buildであれば以下のように実行するだけでOpenSSLを静的リンクしてnginxをビルドできます。
nginx-build -d work -openssl
nginx-buildでは上記のようにOpenSSLは静的リンクするんだけども--with-http_ssl_module
が指定されていない場合は--with-http_ssl_module
を自動的に付加する処理が入っています。
if openSSLStatic && !strings.Contains(configure, "--with-http_ssl_module") {
configure += "--with-http_ssl_module \\\n"
}
OpenSSLの静的リンクと並列ビルド
nginx-buildはnginxをビルドする際にmakeを実行します。また、コマンドラインオプション(-j
)でジョブ数を指定することができてデフォルトはCPUのコア数です。
jobs := flag.Int("j", runtime.NumCPU(), "jobs to build nginx")
このジョブ数はnginxだけでなくPCREやzlib、OpenSSLといった外部ライブラリのコンパイルにも適用されます。もちろん静的リンクする場合のみの話です。そしてOpenSSLはmakeのジョブ数を1より大きい値にするとコケます。これはログをパッと見ただけでは原因がわかりづらいので一時期相当ハマりました。
なので今はこんな感じのコードが入っています。
if *openSSLStatic {
// Workarounds for protecting a failure of building nginx with static-linked OpenSSL.
// Unfortunately a build of OpenSSL fails when multi-CPUs are used.
*jobs = 1
OpenSSLのコンパイルはとても時間がかかるのでこれなんとかしたい。
Mac上でOpenSSLを静的リンク
またOpenSSLかよ、と思ったあなた、気が合いますね。私も正直ちょっとうんざりしています。でも、もうちょっとだけ続くんじゃよ。
さっきも書きましたが、nginxでOpenSSLを静的リンクするにはconfigureに以下のオプションが必要になります。
./configure --with-openssl={OpenSSLのソースコードディレクトリへのパス} --with-http_ssl_module
しかし、これだけだと以下の警告メッセージが出てしまいます。(そしてビルドは止まります)
WARNING! If you wish to build 64-bit library, then you have to
invoke './Configure darwin64-x86_64-cc' *manually*.
You have about 5 seconds to press Ctrl-C to abort.
Macはあまり詳しくないのですが、どうも自分のMacだとuname -m
の出力はx86_64
なのにuname -p
の出力がi386
なっているせいで64bit環境用のライブラリとしてビルドできないのでこのエラーが出るようです。このエラーは環境変数でKERNEL_BITS=64
ってやれば回避できます。
// Sometimes machine hardware name('uname -m') is different
// from machine processor architecture name('uname -p') on Mac.
// Specifically, `uname -p` is 'i386' and `uname -m` is 'x86_64'.
// In this case, a build of OpenSSL fails.
// So it needs to convince OpenSSL with KERNEL_BITS.
if runtime.GOOS == "darwin" && runtime.GOARCH == "amd64" {
os.Setenv("KERNEL_BITS", "64")
}