nginx mirror module の罠?の話
注意
- nginx v1.24.0で確認しています。
- ソースコードを呼んだとかではないので、利用する際には十分検証を行ってください。
内容
Nginx には、ミラーモジュール ( ngx_http_mirror_module ) というモジュールがあり、
server {
listen :80;
location /mirror {
internal;
proxy_pass http://origin2;
}
location / {
mirror /mirror
proxy_pass http://origin1;
}
}
このような ForwardProxy の設定を行うことで
[Client] -> [nginx] -+-------------------> [origin1]
`- - -(mirror)- - -> [origin2]
このように origin1 にリクエストを行いつつ、同様のリクエストを origin2 にも行い、origin2 のレスポンスは無視することができるというものです。
しかし、実際に使ってみると罠?と思われる挙動が合ったので、ここにメモしておきます。
🪤1:ミラー側が遅延するとレスポンスが遅延する
以下のように origin1 と origin2 のレイテンシが異なる場合、遅い方に引きずられます。
[Client] -> [nginx] <---+--(100ms)--> [origin1]
+--(300ms)--------------> [origin2]
負荷試験など、遅いレイテンシに引きずられたくない場合は、以下のように、mirror側にタイムアウト設定を実施すると回避できます。
server {
listen :80;
location /mirror {
internal;
proxy_pass http://origin2;
proxy_connect_timeout 100ms;
proxy_read_timeout 100ms;
proxy_send_timeout 100ms;
}
location / {
mirror /mirror
proxy_pass http://origin1;
}
}
🪤2:ミラー側のKeepAliveが有効にならない
以下のように、origin1 / origin2ともにKeepAliveの設定を設定しているにも関わらず、
server {
listen :80;
location /mirror {
internal;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://origin2;
}
location / {
mirror /mirror
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://origin1;
}
}
以下のように、mirror側のKeepAliveが有効にならず都度接続増えてしまう場合、
[Client] ---> [nginx] <-+--(KeepAlive有効)----> [origin1]
+--(KeepAlive無効)----> [origin2]
mirrorの中ではKeepAliveが有効にならないようなので、別サーバとしてKeepAliveを有効にして、proxy_passの先に指定することで回避することが可能です。
upstream nginx_proxy_socket {
unix:/var/run/nginx-proxy.sock
}
server {
listen unix:/var/run/nginx-proxy.sock
location / {
internal;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://origin2;
}
}
server {
listen :80;
location /mirror {
internal;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://nginx_proxy_socket$request_uri;
}
location / {
mirror /mirror
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://origin1;
}
}
HTTP/gRPCハイブリッドなアプリ
gRPCはHTTP/1.1で受けれないのでHTTP/2としてリクエストをハンドリングできるようにしてやって、HTTP/2かつapplication/grpcの場合にgrpc側で処理してやるようにすればOK
addr := ":8080" if v, ok := os.LookupEnv("BIND"); ok { addr = v } grpcServer = grpc.NewServer() reflection.Register(grpcServer) mux := http.NewServeMux() mux.Handle("/live", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte{'o', 'k'}) })) httpServer = &http.Server{ Addr: addr, Handler: h2c.NewHandler( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") { grpcServer.ServeHTTP(w, r) } else { mux.ServeHTTP(w, r) } }), &http2.Server{}), } httpServer.ListenAndServe()
M1Mac で gvm+arm版goを利用する
ローカル環境のGoバージョン管理方法としてgvmを利用しているがM1 Macに変えたタイミングでgvm経由でインストールしたGoのdelveがうまく動かなかったりが発生していたので、rosetta2経由でintelバイナリを利用するのではなく、arm版バイナリを利用することで問題が解消した。
以下にgvm経由でにarm版goを利用する方法をメモしておく
$ cd /tmp
$ wget https://go.dev/dl/go1.18beta1.darwin-arm64.tar.gz
$ tar zxvf /tmp/go1.18beta1.darwin-arm64.tar.gz
$ mv go ~/.gvm/gos/go1.18beta1-arm
$ cp ~/.gvm/environments/go1.17.2-arm ~/.gvm/environments/go1.18beta1-arm
$ vim ~/.gvm/environments/go1.18beta1-arm
:%s/1.17.2-arm/1.18beta1-arm/g
$ mkdir ~/.gvm/pkgsets/go1.18beta1-arm
自分用 PC環境メモ
ゲーミングPC(自作)
- Motherboard
- B550M Pro4
- CPU
- Ryzen5 5600x (3.7-4.6GHz / 6C/12T/ Zen3 / AM4 / 65W)
- Memory
- CFD CX1 (DDR4-3200 16G x2)
- VGA
- Inno3D GEFORCE RTX 3070 ICHILL X3
- Storage
- SDSSDHII-480G-J26C
- CT525MX300SSD1
- SP240GBSS3S70S25
- Keyboard
- Mouse
- Case
- CoolerMaster MASTERBOX E500L
- PSU
事務処理&開発用
- Motherboard
- ASRock Z370M Pro4
- CPU
- Memory
- Crucial CT8G4DFS824A (DDR4-2400 8GB)x2
- Storage
- OCZ VTX4-25SAT3-256G
- Keyboard
- TK-P05FBK
- Mouse
- Logicool G700s
- Case
- IN WIN BK623
- PSU
KRPW-SXP400W(400W / 420W Peak)
\ Line +3.3V +5V +12V -12V +5Vsb MaxA 17A 14A 33A 0.3A 2.5A MaxW > 90w 396W 3.6W 12.5W
ラップトップ
MacBookPro 15inch 2018
その他
- Monitor
- MonitorArm
- ヘッドフォン
Spring + Webauthn4jでWebAuthnやってみる
年末年始の時間を使ってYubicoのWebAuthnServer使ってWebAuthnのサンプルアプリを作ろうといろいろ試行錯誤してたら、
Web+DB PRESS Vol.114でWebAuthnの特集やってたので、成果物としてはただ写経しただけになってしまった。
今度Yubico/WebAuthnServer使ってリベンジしてみる
【メモ】SpringOne2019報告会スライドまとめ
SpringOne Platform 2019概要 + Resilience4j + LTした話
Spring Initializrをハックする
// todo
Let's イベント駆動 on Spring Cloud Stream
// todo
www.slideshare.netSpring 18年の歴史
www.slideshare.net
Spring HATEOAS
Pack to the future
// スライド見つけれないマン
GKEからPubsubへの接続でタイムアウトが発生する件
このアプリをビルド&AlpineのDockerイメージ化したものをGKEで動かして、Topicを取得するときに(内部的にはPubSubに接続する時)タイムアウトエラーで落ちてしまう現象が発生した
$ cat Dockerfile
FROM alpine:3.8
RUN apk add bash tree pstree
ENTRYPOINT [ "/opt/pubsub" ]
$ cat pubsub.go
package main
import (
"cloud.google.com/go/pubsub"
"context"
"fmt"
"os"
"time"
)
func main() {
projectName := os.Getenv("PROJECT")
topicName := os.Getenv("TOPIC")
subscriptionName := os.Getenv("SUBSCRIPTION")
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, 5*time.Second)
client, err := pubsub.NewClient(ctx, projectName)
if err != nil {
panic(err)
}
topicExists, err := client.Topic(topicName).Exists(ctx)
if err != nil {
panic(err)
}
fmt.Println(topicName, topicExists)
subscription := client.Subscription(subscriptionName)
subscriptionExists, err := subscription.Exists(ctx)
if err != nil {
panic(err)
}
fmt.Println(subscriptionName, subscriptionExists)
go func() {
err = subscription.Receive(ctx, func(rcvCtx context.Context, message *pubsub.Message) {
fmt.Println("RCV", message.ID, string(message.Data), message.Attributes)
})
}()
time.Sleep(500 * time.Millisecond)
if err != nil {
panic(err)
}
fmt.Println("Wait for signal...")
sigCh := make(chan os.Signal, 1)
signal.Notify(
sigCh,
syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT)
fmt.Println("SignalRcv ->", <-sigCh)
}結論結論としては、Alpineに ca-certificates がなかったため、Pubsubのエンドポイント ( https://pubsub.googleapis.com )に接続しようとして接続できなかった模様。
なので apk add ca-certificates してパッケージをインストールすることでタイムアウト(に見えるhttps接続エラー)は出なくなった。