t.marcusの外部記憶装置

忘備録とかちょっとした考えとかをつらつらと...

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

github.com

自分用 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
  • Storage
    • SDSSDHII-480G-J26C
    • CT525MX300SSD1
    • SP240GBSS3S70S25
  • Keyboard
  • Mouse
  • Case
    • CoolerMaster MASTERBOX E500L
  • PSU
    • KT-AP1000-AKG

      \ Line +3.3V +5V +12V1 +12V2 -12V +5Vsb
      MaxA 25A 25A 40A 60A 0.5A 3A
      MaxW > 150W > 835W 6W 15W

事務処理&開発用

  • Motherboard
    • ASRock Z370M Pro4
  • CPU
  • Memory
    • Crucial CT8G4DFS824A (DDR4-2400 8GB)x2
  • Storage
    • OCZ VTX4-25SAT3-256G
  • Keyboard
    • TK-P05FBK
  • Mouse
  • 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

その他

Spring + Webauthn4jでWebAuthnやってみる

年末年始の時間を使ってYubicoのWebAuthnServer使ってWebAuthnのサンプルアプリを作ろうといろいろ試行錯誤してたら、
Web+DB PRESS Vol.114でWebAuthnの特集やってたので、成果物としてはただ写経しただけになってしまった。
今度Yubico/WebAuthnServer使ってリベンジしてみる

github.com

github.com

【メモ】SpringOne2019報告会スライドまとめ

SpringOne Platform 2019概要 + Resilience4j + LTした話

speakerdeck.com

Spring Initializrをハックする

// todo

Let's イベント駆動 on Spring Cloud Stream

// todo

www.slideshare.net


Spring 18年の歴史

www.slideshare.net

Spring HATEOAS

speakerdeck.com

Pack to the future

docs.google.com



// スライド見つけれないマン

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接続エラー)は出なくなった。