- 投稿日:2021-01-13T23:04:22+09:00
glibc, musl libc, go の resolver の違い
先日、resolv.conf で timeout を調整したいなと思うことがありました、しかし、Docker だの Kubernetes だのといった時代です。Linux しか使っていなかったとしても resolver が glibc のそれだとは限らないわけですね。そこで glibc, musl libc (alpine のやつです), go の resolver の違いを調べてみました。
環境
それぞれ docker container を使い、問い合わせの状況は tcpdump で確認しました。
macOS Catalina (10.15.7) docker desktop 3.0.3 (51017)container image は
alpine:3.12.3 / musl-1.1.24-r10 debian:buster-20201209 (10.7) / glibc 2.28-10Go は mac 上で 1.15.6 を使い、クロスコンパイルして上記の alpine 上で実行しました。
クロスコンパイルなのでデフォルトでCGO_ENABLED=0
のはずですが、明示してビルドしました。resolv.conf の options などの対応状況
resolv.confの例nameserver 8.8.8.8 nameserver 8.8.4.4 search default.svc.cluster.local svc.cluster.local cluster.local asia-northeast1-b.c.project-id.internal c.project-id.internal google.internal options ndots:5 timeout:5 attempts:2resolv.conf の options にはいろいろありますが、今回の 3 つの resolver 全てで対応されているのは
ndots
timeout
attempts
の3つでした。
nameserver
は勿論ですがsearch
にも対応しています。
昔の musl libc はsearch
(domain 補完) には対応していませんでしたが 1.1.13 から対応していました。
nameserver
は複数回指定可能ですが、使われるのは3つまでです。4つ以上指定しても3つまでしか使われません。それでは機能の違いを見ていきましょう
ndots
まずは
ndots
です、Kubernetes を使うようになってはじめて知りました。指定するのは問い合わせるドメインに含まれる dot (
.
) の数です、Kubernetes のデフォルト設定では 5 (ndots:5
) となっています。例えば
ping www.google.com
とした場合、dot の数は 2 です。dot の数が ndots 以上の場合
dot の数が
ndots
で指定した以上であればそれは FQDN であろうと判断し、search
で指定したドメインを補完せずに DNS サーバーに問い合わせます。ここで、NXDOMAIN
(そんなドメイン存在しないよ) が返ってきたら glibc と go はsearch
で指定したドメインを補完して問い合わせます。しかし、musl libc は補完した問い合わせを行わないでそんなドメインは存在しないよということで終了してしまいます。その昔、musl はそもそもドメイン補完に対応していなかったので当時から使っている人は常に FQDN で指定しているでしょうから問題ないでしょうが、知らないで
ndots
を小さくすると解決できるはずのドメインが解決できないかもしれません。dot の数が ndots 未満の場合
dot の数が
ndots
未満であった場合はまずsearch
で指定した複数のドメインを順番に補完して問い合わせます、見つかるまで繰り返すため(これの上限を確認するの忘れた)、順序は重要ですかね。ただ、順序以上にndots
で指定する数が重要です、5
というのは結構多いです。Kubernetes でデフォルト設定だとservice.namespace.svc.cluster.local
という FQDN を指定したとしても4
なわけです。search
に並んでいる全てのドメインの補完を試した後にservice.namespace.svc.cluster.local
をそのまま問い合わせてやっと欲しい結果が得られるわけです、DNS サーバー側にネガティブキャッシュがあるとはいえ、DNS サーバーとの通信はその回数行われるわけですから、かなりの無駄です。GKE のデフォルト設定では6つもドメインが列挙されています。EKS は4つ。ndots
を小さくして名前解決ができないドメインが発生してしまうよりも、無駄な問い合わせが増えたとしても名前解決できる値がデフォルトなっているわけですね。ndots
はチューニングの余地がありそうです。ちなみに、末尾に
.
をつけた場合はそれは FQDN だぞということを意味するのでping www.google.com.
などとした場合はどの resolver でもドメインの補完は行いません。nameserver
複数サーバーを指定した場合の扱いが glibc と musl で大きく異なるため、まず説明しておきます。
glibc と Go は一つ目のサーバーに問い合わせて、timeout 秒待っても応答がない場合に、次のサーバーに問い合わせます。
musl libc は複数の nameserver に同時にリクエストを送って最初に返ってきた応答を使います。3つのネームサーバーが指定されていたら3倍の問い合わせが発生してしまうわけですね。timeout と attempts
timeout
とattempts
は関連しているため一緒に扱います。また、nameserver
の数にもよるためそれごとにまとめます。nameserver が1つの場合
glibc と Go は問い合わせを投げては
timeout
秒待ち、応答がなければ再度問い合わせて、合計attempts
回問い合わせます。musl libc は
timeout
秒をattempts
で割った秒数をそれぞれの問い合わせで待ちます。つまり、リトライを含めた合計で最大timeout
秒待つということです。timeout:5 attempts:2
というデフォルト設定では 2.5 秒ずつ待ちます。nameserver が2つの場合
glibc と Go は1つ目の
nameserver
に問い合わせてtimeout
秒待ち、次は2つ目のnameserver
に問い合わせます。これがattempts
の1回分です。timeout:5 attempts:2
というデフォルト設定ではそれぞれのnameserver
に対して2回ずつ、合計4回問い合わせます。その都度5秒待ちます。musl libc は
nameserver
の項で書いた通り、複数のnameserver
に対して同時に問い合わせるため、nameserver
が1つの場合と同じです。nameserver が3つの場合
nameserver
が3つになると glibc のtimeout
の振る舞いが少し変わります。1つ目のnameserver
に対してはtimeout
で指定した秒数待ちますが、2つ目はtimeout
で指定したのよりも短くなり、3つ目はtimeout
で指定したものより長くなります。
timeout:5
の場合は5秒、3秒、6秒となります。このセットをattempts
回繰り返します。Go は常に
timeout
秒待ちます。musl libc はこれまでと同じです。
timeout 時のドメイン補完
何度問い合わせても timeout するような場合はもういろいろダメなのであまり重要ではないですが、動作に違いがあったので書いておきます。
timeout が続く状態で、2つ目以降の search ドメインを補完するのかどうか、glibc は2つ目以降のドメイン補完は行いませんが、補完なしの問い合わせを行います。Go は律儀に全てのドメイン補完を試しますし、補完なしでの問い合わせもします。musl libc は1つ目の補完の問い合わせは行いますが、それで終わりです。補完なしの問い合わせもありません。
Go の動作確認で使ったコード
package main import ( "context" "net" "os" "log" ) func main() { ctx := context.Background() resolver := &net.Resolver{} for _, v := range os.Args[1:] { log.Printf("Resolving %s\n", v) names, err := resolver.LookupHost(ctx, v) if err != nil { log.Fatal(err) } for _, name := range names { log.Printf("%s\n", name) } } }ndots:5 の凶悪さを可視化する
文章でつらつら書いても分かりづらいので、 GKE 環境で
storage.googleapis.com
にアクセスしようとした場合にどんな問い合わせが発生するかを見てみましょう。default
namespace に立てた Pod でping storage.googleapis.com
を実行した際の tcpdump の出力を加工しました。(横幅を抑えるために)Cloud Storage の API にアクセスしようとする度にこの数の問い合わせが発生すると思うとゾッとしますね。マイクロサービスで Keep-Alive などをしていない場合は大量の内部通信でもこれが発生しているかもしれません。JVM などのように DNS のキャッシュをしてくれれば良いですけどね。
ndots:2
として常に FQDN で指定するということが徹底できると良いのかな。14:53:34.706909 ▶︎ A? storage.googleapis.com.default.svc.cluster.local. (66) 14:53:34.707130 ▶︎ AAAA? storage.googleapis.com.default.svc.cluster.local. (66) 14:53:34.708881 ◁ NXDomain 0/1/0 (159) 14:53:34.708940 ◁ NXDomain 0/1/0 (159) 14:53:34.709051 ▶︎ A? storage.googleapis.com.svc.cluster.local. (58) 14:53:34.709133 ▶︎ AAAA? storage.googleapis.com.svc.cluster.local. (58) 14:53:34.709615 ◁ NXDomain 0/1/0 (151) 14:53:34.709689 ◁ NXDomain 0/1/0 (151) 14:53:34.709771 ▶︎ A? storage.googleapis.com.cluster.local. (54) 14:53:34.709816 ▶︎ AAAA? storage.googleapis.com.cluster.local. (54) 14:53:34.712211 ◁ NXDomain 0/1/0 (147) 14:53:34.712280 ◁ NXDomain 0/1/0 (147) 14:53:34.712387 ▶︎ A? storage.googleapis.com.asia-northeast1-b.c.my-project-id.internal. (83) 14:53:34.712479 ▶︎ AAAA? storage.googleapis.com.asia-northeast1-b.c.my-project-id.internal. (83) 14:53:34.716561 ◁ NXDomain 0/1/0 (189) 14:53:34.716623 ◁ NXDomain 0/1/0 (189) 14:53:34.716718 ▶︎ A? storage.googleapis.com.c.my-project-id.internal. (65) 14:53:34.716760 ▶︎ AAAA? storage.googleapis.com.c.my-project-id.internal. (65) 14:53:34.719891 ◁ NXDomain 0/1/0 (162) 14:53:34.720191 ◁ NXDomain 0/1/0 (162) 14:53:34.720304 ▶︎ A? storage.googleapis.com.google.internal. (56) 14:53:34.720390 ▶︎ AAAA? storage.googleapis.com.google.internal. (56) 14:53:34.724145 ◁ NXDomain 0/1/0 (145) 14:53:34.724352 ◁ NXDomain 0/1/0 (145) 14:53:34.724458 ▶︎ A? storage.googleapis.com. (40) 14:53:34.724500 ▶︎ AAAA? storage.googleapis.com. (40) 14:53:34.726930 ◁ 4/0/0 AAAA 2404:6800:4004:813::2010, AAAA 2404:6800:4004:81c::2010, AAAA 2404:6800:4004:81d::2010, AAAA 2404:6800:4004:81e::2010 (152) 14:53:34.726957 ◁ 16/0/0 A 172.217.161.80, A 172.217.175.16, A 172.217.175.48, A 172.217.175.80, A 172.217.175.112, A 216.58.197.144, A 172.217.25.208, A 172.217.25.240, A 172.217.26.48, A 172.217.31.176, A 172.217.161.48, A 172.217.174.112, A 172.217.175.240, A 216.58.220.112, A 216.58.197.208, A 216.58.197.240 (296)
- 投稿日:2021-01-13T22:44:11+09:00
Golang製PHPアプリケーションサーバRoadRunnerでLaravel-adminを動かす
アプリケーションサーバーの新潮流
昨今のPHP界隈では、Swooleに代表されるように、よくあるApache+mod_phpやPHP-FPMによる従来の構成と異なる実行方法を持つPHPのためのHTTPサーバまたはアプリケーションサーバが少しずつ注目を集めるようになっています。
RoadRunner
RoadRunnerもそんな新興のアプリケーションサーバの1つで、Go言語で書かれています。 HTTPのリクエストを前段のGoのHTTPハンドラがさばき、ロードバランサ/プロセスマネージャがPHPのWorkerにリクエストを割り振るという構成になっています。
通常のHTTPサーバだけでなく、RoadRunnerによるgRPCサーバの実装も公開されており、PHPの新しい可能性を見せてくれています。
RoadRunnerを試す
RoadRunnerはLaravelもサーポートしており、Laravel用のパッケージも開発されているので
今回はこれをベースにRoadRunner実装していきたいと思います。
RoadRunnerのインストール
まずは下記コマンドでパッケージのインストールを行います。
# roadrunner composer require spiral/roadrunner "^1.8" # roadrunner-laravel composer require spiral/roadrunner-laravel "^3.4"次に、下記コマンドを実行してパッケージ構成ファイル(./config/roadrunner.php)を作ります。
php artisan vendor:publish --provider='Spiral\RoadRunnerLaravel\ServiceProvider' --tag=config.rr.ymlの作成
roadrunnerの設定ymlファイルを書きます。
80番ポートで起動するように設定します。
env: APP_REFRESH: true http: address: 0.0.0.0:80 http2: enabled: true h2c: true maxConcurrentStreams: 128 workers: command: 'php ./vendor/bin/rr-worker' static: dir: public reload: interval: 1s patterns: [ '.php' ] services: http: dirs: [ '' ] recursive: true諸々の微修正
logging.php
'stdout' => [ 'driver' => 'monolog', 'handler' => StreamHandler::class, 'with' => [ //php://stdoutにするとエラーになってコケるのでphp://stderrに変える 'stream' => 'php://stderr', ], 'level' => 'debug', ],composer.jsonにscriptを追加する。
"scripts": { "roadrunner:dev": [ "composer dump-autoload", "./rr serve -v -d" ], "roadrunner:prod": [ "./rr serve" ], "roadrunner:setup": [ "rr get-binary" ], }startup.shに起動コマンドを乗せる
# roadrunner composer roadrunner:setup chmod +x rr composer roadrunner:devこれで大体の設定は完了しました。
起動してみる
以下の手順で起動させます。
cp docker-compose.example.yml docker-compose.yml docker-compose up -d無事に起動できました。
まとめ
今回はGolangで作られたPHPのweb・アプリケーションサーバーを使った。サンプルを作ってみました。
おもしろいですが、不安定なのでプロダクション環境ではあまり使うのはおすすめできないですね。参考文献
- 投稿日:2021-01-13T22:44:11+09:00
Golang製PHPアプリケーションサーバRoadRunnerを試す
アプリケーションサーバーの新潮流
昨今のPHP界隈では、Swooleに代表されるように、よくあるApache+mod_phpやPHP-FPMによる従来の構成と異なる実行方法を持つPHPのためのHTTPサーバまたはアプリケーションサーバが少しずつ注目を集めるようになっています。
RoadRunner
RoadRunnerもそんな新興のアプリケーションサーバの1つで、Go言語で書かれています。 HTTPのリクエストを前段のGoのHTTPハンドラがさばき、ロードバランサ/プロセスマネージャがPHPのWorkerにリクエストを割り振るという構成になっています。
通常のHTTPサーバだけでなく、RoadRunnerによるgRPCサーバの実装も公開されており、PHPの新しい可能性を見せてくれています。
RoadRunnerを試す
RoadRunnerはLaravelもサーポートしており、Laravel用のパッケージも開発されているので
今回はこれをベースにRoadRunner実装していきたいと思います。
RoadRunnerのインストール
まずは下記コマンドでパッケージのインストールを行います。
# roadrunner composer require spiral/roadrunner "^1.8" # roadrunner-laravel composer require spiral/roadrunner-laravel "^3.4"次に、下記コマンドを実行してパッケージ構成ファイル(./config/roadrunner.php)を作ります。
php artisan vendor:publish --provider='Spiral\RoadRunnerLaravel\ServiceProvider' --tag=config.rr.ymlの作成
roadrunnerの設定ymlファイルを書きます。
80番ポートで起動するように設定します。
env: APP_REFRESH: true http: address: 0.0.0.0:80 http2: enabled: true h2c: true maxConcurrentStreams: 128 workers: command: 'php ./vendor/bin/rr-worker' static: dir: public reload: interval: 1s patterns: [ '.php' ] services: http: dirs: [ '' ] recursive: true諸々の微修正
logging.php
'stdout' => [ 'driver' => 'monolog', 'handler' => StreamHandler::class, 'with' => [ //php://stdoutにするとエラーになってコケるのでphp://stderrに変える 'stream' => 'php://stderr', ], 'level' => 'debug', ],composer.jsonにscriptを追加する。
"scripts": { "roadrunner:dev": [ "composer dump-autoload", "./rr serve -v -d" ], "roadrunner:prod": [ "./rr serve" ], "roadrunner:setup": [ "rr get-binary" ], }startup.shに起動コマンドを乗せる
# roadrunner composer roadrunner:setup chmod +x rr composer roadrunner:devこれで大体の設定は完了しました。
起動してみる
以下の手順で起動させます。
cp docker-compose.example.yml docker-compose.yml docker-compose up -d無事に起動できました。
まとめ
今回はGolangで作られたPHPのweb・アプリケーションサーバーを使った。サンプルを作ってみました。
おもしろいですが、不安定なのでプロダクション環境ではあまり使うのはおすすめできないですね。
- 投稿日:2021-01-13T21:21:42+09:00
【gvm】Goのバージョン管理
はじめに
集団開発においてバージョンを一致させる必要があり、バージョン管理はマストの作業である.
本記事はGoにおけるバージョン管理について記録を残しておく.Goのバージョン管理ツール 【gvm】
Goのバージョン管理ツール go version manager (以降 gvm )を扱う
https://github.com/moovweb/gvmgvmの利用手順
gvm の利用手順は大きく以下の3つ
- gvmのインストール
- goのバージョンをインストール
- goの特定のバージョンを利用
1. gvmインストール
gvmのインストールはターミナル上で以下を実行する
$ bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer) $ source /Users/nancy/.gvm/scripts/gvmインストールが正しく実行されているかを確認
$ gvm version Go Version Manager v1.0.22 installed at hoge2. goのバージョンをインストール
gvm のインストールを確認したら開発で扱う go のバージョンを取り込む.
gvm install go1.15.6この際、下記のようなエラーが出る場合がある.
gvm install go1.15.6 Installing go1.15.6... * Compiling... ERROR: Failed to compile. Check the logs at /Users/ko-wa/.gvm/logs/go-go1.15.6-compile.log ERROR: Failed to use installed versionこのエラーが出た際の対処法としてはバイナリをインストールする.
$ gvm install go1.15.6 -B Installing go1.15.6 from binary source参考:https://akataworks.hatenadiary.jp/entry/2016/10/18/201025
3. goの特定のバージョンを利用
インストールしたバージョンを再度 gvm コマンドを使って切り替える.
$ gvm use go1.15.6 Now using version go1.15.6goのバージョンを確認する.
$ go version go version go1.15.6 darwin/amd64既定のバージョンになっていればオッケー!
gvm を常に利用可能にする
gvm はターミナルを開き直すと利用できなくなるので、bash_profile(ターミナルが開かれる際に最初に実行される) に設定が必要
$ cat $HOME/.gvm/scripts/gvm >> ~/.bash_profile書き込まれていれば、gvm が常に利用できる.
Go のバージョンデフォルトを変更する
最後に Go のバージョンの固定したい場合にデフォルトを変更する方法を書きます.
$ gvm use go1.15.6 --default Now using version go1.15.6以上で Go バージョン管理、gvm の使い方になります.
- 投稿日:2021-01-13T09:49:11+09:00
Golang入門4
Golangでgorilla/muxやgin, echo等フレームワークを使わずに一覧/詳細画面を作る。
本文はこちら: https://bizzare.me/post/golang4/