Posted at
nginxDay 8

nginx-build internals

More than 3 years have passed since last update.

この記事は、nginx Advent Calendar 2015の8日目の記事です。


nginx-build

nginx-buildはnginxをビルドするためのコマンドラインツールです。

使い方については過去にQiitaに書いた記事やnginx-buildのREADME.mdにそれなりに詳しく書いてあるのでそちらを参照して下さい。

今回はnginx-buildの内部実装について少し解説します。わりと泥臭いこともやってます。


nginx-build internals

nginx-buildが行っているのは大きく分けて以下の3つです。


  • nginxや依存ライブラリ、拡張モジュールのソースコードのダウンロード

  • configureのオプション文字列を自動生成

  • makeを実行

nginx-buildはGoで書かれていますが、中でもGoの特徴を最も活かしているのが最初のダウンロード処理部分です。


ゴルーチンを利用した並行ダウンロード

nginx-buildでは指定したオプションにもよりますが、実行すると複数のソースコードをダウンロードしてワーキングディレクトリに展開します。


  • nginx

  • PCRE

  • OpenSSL

  • 拡張モジュール群

各々のダウンロード処理はwgetgithgのいずれかによって行われます。実際の処理はこんな感じです。(ちょっと前までカウンターとチャネル使って実装してたけどsync.WaitGroup使って書き直した)


nginx-build.go

        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 -Vconfigure 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を自動的に付加する処理が入っています。


configure.go

        if openSSLStatic && !strings.Contains(configure, "--with-http_ssl_module") {

configure += "--with-http_ssl_module \\\n"
}


OpenSSLの静的リンクと並列ビルド

nginx-buildはnginxをビルドする際にmakeを実行します。また、コマンドラインオプション(-j)でジョブ数を指定することができてデフォルトはCPUのコア数です。


nginx-build.go

        jobs := flag.Int("j", runtime.NumCPU(), "jobs to build nginx")


このジョブ数はnginxだけでなくPCREやzlib、OpenSSLといった外部ライブラリのコンパイルにも適用されます。もちろん静的リンクする場合のみの話です。そしてOpenSSLはmakeのジョブ数を1より大きい値にするとコケます。これはログをパッと見ただけでは原因がわかりづらいので一時期相当ハマりました。

なので今はこんな感じのコードが入っています。


nginx-build.go

        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ってやれば回避できます。


nginx-build.go

                // 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")
}