DNSの送受信をtcpdumpで見る

 apt update && apt install -y tcpdump dnsutils
 tcpdump -X -n -vv dst port 53 or src port 53 &
# dig すればなんか出るはず
 dig yahoo.co.jp

tcpdumpで真面目に見たのは初めてなので ↓を見ながら調べた。

2019/07/26 KCL v2周りの話

KCLv2の話

aws sdk for java v2の話

  • 移行はこの辺読む。AWS SDK for Java 2.x 移行ガイドとは - AWS SDK for Java 移行ガイド
  • デフォルトではHTTPクライアントとしてnettyを使う模様
    • ServiceLoader経由で切り替えられるようになっている模様
  • nettyのunstable APIに依存していて互換性エラーが出ることがある
  • 現状メトリクス周りの実装は存在しない
  • HTTPクライアントで使われるスレッドプールは以下の感じになっていた sdk-core:2.5.10
    • スレッドプールがmax: 50

        ThreadPoolExecutor executor = new ThreadPoolExecutor(50, 50,
                                                             10, TimeUnit.SECONDS,
                                                             new LinkedBlockingQueue<>(10_000),
                                                             new ThreadFactoryBuilder()
                                                                 .threadNamePrefix("sdk-async-response").build());
      
        // Allow idle core threads to time out
        executor.allowCoreThreadTimeOut(true);
        return executor;
      

Armeriaで始めるDNSのSRV Recordを使った クライアントサイドロードバランシング

今回は、Armeriaを使って、DNSSRV Recordを使ったクライアントサイドロードバランシングをやってみました。

ソースコードこのリポジトリにおいています。

クライアントサイドロードバランシングの必要性については以下の記事を読むと良いかもしれません。 いくつか現実とそぐわない点があるかもしれませんが。

今回動かす構成としては以下の通りです。

  • server: Hello Worldを返すエンドポイントを持つHTTPサーバ
  • client: Armeriaの機能を使って、クライアントサイドロードバランシングしながらserverに接続する

serverは、シンプルなもgolangで書かれたサーバです。 clientは、クライアントサイドロードバランシングしながらserverに接続します。 マイクロサービスの文脈で使おうと思っているので、今回はclientもHTTPサーバとして起動し、APIを持っています。

また、動作確認には、AWSのECSで、Fargateタイプでコンテナを起動しながら、動作確認をしました。 ECSではService Discoveryの機能を使ってDNSSRVレコードを生成することが出来るので、便利です。 今回はECSコンテナインスタンスを立てるのが面倒なので、Fargateを使います。

Service Discoveryの設定等については以下のブログを参考にしました。 SRVレコードが登録されるように、設定してください。

server

golangで書きます。

package main

import (
    "log"
    "net/http"
    "time"
)

func main() {
    m := http.NewServeMux()
    m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello World!\n"))
    })
    srv := &http.Server{
        Addr:         ":8080",
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
        Handler:      m,
    }
    log.Println("started 8080")
    log.Println(srv.ListenAndServe())
}

Hello Worldを返すエンドポイントが生えてるだけですね。 今回のメインはこちらではないので、さっくり流します。

client

このclientでは、ArmeriaのService Discovery機能を使って クライアントサイドロードバランシングをします。 ドキュメントを見ると他の例も書かれているので 詳しいことが知りたければそちらを見ると良さそうです。

APIとクライアントの利用例を先に下に示します。

@RestController
public class RootController {

    private final ExternalService externalService;

    RootController(ExternalService externalService) {
        this.externalService = externalService;
    }

    @GetMapping("/")
    public CompletableFuture<String> index() {
        // serverに通信したレスポンスをそのまま返却する
        return externalService.get().thenApply(r -> r.content().toString(StandardCharsets.UTF_8));
    }

}

/**
 * 色々例としてはよくないけど、通信するためのService。 
 */
@Service
public class ExternalService {

    
    public CompletableFuture<AggregatedHttpResponse> get() {
        // 本当はフィールドに持っておいたほうが良さそう
        HttpClient httpClient = HttpClient.of("http://group:backend/"); // URLも外部化しておくべき。
        return httpClient.get("/").aggregate();
    }
}

ControllerとServiceをざっと書きましたがすごくシンプルなものです。

見てほしいのは下の部分です。

        HttpClient httpClient = HttpClient.of("http://group:backend/");

ここで、Armeriaに対して、backend という、EndpointGroupを解決してリクエストを送ってほしい旨を表しています。 では、backendというEndpointGroupはどこで登録しているかというと下の部分です。

@Configuration
public class ArmetriaConfig implements InitializingBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        // DNSのSRVレコードでService Discoveryするエンドポイントのグループ
        // backend.internal.localという名前でDNSに問い合わせる
        DnsServiceEndpointGroup group = new DnsServiceEndpointGroupBuilder("backend.internal.local")
                // Custom backoff strategy.
                .backoff(Backoff.exponential(1000, 16000).withJitter(0.3))
                .build();
        // Wait until the initial DNS queries are finished
        group.awaitInitialEndpoints();
        
        // backendという名前で エンドポイントのグループを登録
        EndpointGroupRegistry.register("backend", group, EndpointSelectionStrategy.WEIGHTED_ROUND_ROBIN);
    }
}

ドキュメントからほぼコピペです。

これだけで動きます。

Fargateにデプロイするくだりは省略します。 Docker Imageをいい感じに作っていい感じにService Discoveryの機能を有効にして Fargateでデプロイしてください。

(Fargateにデプロイ後) すごい!簡単に動きました!便利! 本当に使いやすくて楽だった。

まとめ

Armeriaで、DNSSRVレコードのサービスディスカバリを試しました。 spotify/dns-java などを自前で使って実装する場合、ラウンドロビンの実装などが必要になってしまうため もう少し簡単に利用できないものかな?と考えていました。 今回試した、Armeriaは、すごい簡単にService Discoveryを使ったクライアントサイドロードバランシングが可能で 本番に導入していきたいと思います。

今回はマイクロサービス間での利用を考えていますが、Androidでクライアントサイドロードバランシングしたい要求とかってあるのかな? あんまり聞いたことないんですが、Envoy Mobileみたいなのも出てきているので 需要はあるのかな?

今回の記事は、こんなところで。

Armeria、クライアントライブラリとして利用するだけでも非常に便利なので使っていきたい。 OAuth2周りの連携部分を組み込んで、本番導入を狙っていきたい。