砂漠の旅人(たびと)

UNIX / MS-DOS 時代から電脳砂漠を旅しています

【Docker編】.NET 6 gRPC アプリを Nginx 経由で HTTP/2 通信する

こんにちは、たびとです。

前回、WSL2/Ubuntu で作った gRPC サーバアプリを Docker コンテナで構築しましたが、 gRPC クライアントと gRPC サーバの間に Nginx サーバをバースプロキシとして構築します。

今回の記事は「前回の続き」となっています。 前回の記事はこちらを参照してください。

sabakunotabito.hatenablog.com

この記事の対象者

  • gRPC により HTTP/2 通信 (現在 HTTP/3 はプレビュー機能) アプリを作ってみたい。
  • gRPC アプリを Nginx をリバースプロキシとして HTTP/2 通信で実装したい。

docker の準備

ここでは、WSL2/Ubuntu 上にインストールした docker を用いています。 詳細は、以前の記事を参考にしてください。

sabakunotabito.hatenablog.com

gRPC サーバ

前回の gRPC の資産に Nginx のサーバ分を追加します。 web ディレクトリを作成し、Nginx 用のDockerfile と default.conf を追加します。

docker に web ディレクトリを追加する

$ cd ~/greeter
$ mkdir web
$ touch ./web/Dockerfile
$ touch ./web/default.conf

ソースコード

docker-compose.yml に Nginx 用の定義を追加します。 http は Nginx の Welcome ページ表示用、https(ssl) は gRPC 用のリバースプロキシとして使います。

version: '3.8'
services:
  #gRPC (ASP.NET Core)
  app:
    container_name: 'greeter'
    build:
      context: ./src
      dockerfile: Dockerfile
    ports:
      - "5094:80"
      - "7094:443"
    environment:
      - TZ=Asia/Tokyo
    volumes:
      - ./ssl_greeter.crt:/etc/ssl/certs/ssl_greeter.crt
      - ./ssl_greeter.key:/etc/ssl/private/ssl_greeter.key
    tty: true
    restart: always
    stdin_open: true

  # Web
  web:
    container_name: 'nginx'
    build: ./web
    ports:
      - "5011:80"
      - "7011:443"
    environment:
      - TZ=Asia/Tokyo
    volumes:
      - ./web/default.conf:/etc/nginx/conf.d/default.conf
      - ./ssl_greeter.crt:/etc/ssl/certs/ssl_greeter.crt
      - ./ssl_greeter.key:/etc/ssl/private/ssl_greeter.key
    tty: true
    restart: always
    depends_on:
      - app

web フォルダ内の Dockerfile を編集します。 今回追加する Nginx サーバは軽量な Alpine 版を使います。 ただし、現時点の Alpine は apk update で固る障害があるため、 apk コマンドでパッケージを追加する必要がある場合、避けた方がいいと思います。

イメージ OS 容量
nginx:latest Debian 11 142MB
nginx:alpine Alpine 3.16.2 23.5MB

環境変数 TZ は、docker-compose.yml で指定しているため、コメントにしています。 どちらで指定しても構いません。

FROM nginx:alpine
# ENV TZ Asia/Tokyo

web フォルダ内の default.conf を編集します。 upstream の項目は、 gRPC サーバを定義します。 サーバ名は docker-compose.yml に指定した container_name: 'greeter' を使います。 docker ネットワーク内なので、80 と 443 ポートでアクセスすることができます。 ここでは、どちらのパターンも掲載しておきますが、どちから片方で大丈夫です。

次に、Nginx サーバへのアクセスを定義します。 ポート 80 (クライアントからは 5011)は、Nginx の確認用として、Welcome ページを表示させます。 ポート 443(クライアントからは 7011)は、前回作った証明書を使います。 後は、URL 指定で、gRPC サーバと通信するように設定します。

今回はあまり関係ありませんが、client_max_body_size 0; とゼロを設定して、 クライアントのリクエストのサイズ制限を無効化しておきます。

# gRPC server
upstream grpc_service_servers {
    server greeter:80;
}
# gRPC server (SSL)
upstream grpcs_service_servers {
    server greeter:443;
}

# Default server configuration
server {
    # Welcome(HTML)表示用
    listen 80;
    listen [::]:80;

    # gRPC用
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name _;
    client_max_body_size 0;

    # 証明書
    ssl_certificate     /etc/ssl/certs/ssl_greeter.crt;
    ssl_certificate_key /etc/ssl/private/ssl_greeter.key;

    # デフォルト
    location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
    }

    # gRPCサーバにhttpで接続する
    location /greet.Greeter {
        grpc_pass grpc://grpc_service_servers;
        error_page 502 = /error502grpc;
    }

    # gRPCサーバにhttps(SSL)で接続する
    location /greet.Greeter.SSL {
        grpc_pass grpcs://grpcs_service_servers;
        error_page 502 = /error502grpc;
    }

    # エラー定義
    location /error502grpc {
        internal;
        default_type application/grpc;
        add_header grpc-status 14;
        add_header grpc-message "unavailable";
        return 204;
    }
}

ビルド&実行

Docker 資材をビルドします。

$ docker-compose build

ビルドの正常終了後、コンテナを起動します。

$ docker-compose up -d

コンテナが正常に起動していることを確認します。

$ docker ps
CONTAINER ID   IMAGE         COMMAND                  CREATED       STATUS       PORTS                                                                            NAMES
b79b5edfb252   greeter-web   "/docker-entrypoint.…"   2 hours ago   Up 2 hours   0.0.0.0:5011->80/tcp, :::5011->80/tcp, 0.0.0.0:7011->443/tcp, :::7011->443/tcp   nginx
87c0bb4a8d40   greeter-app   "dotnet GrpcGreeter.…"   2 hours ago   Up 2 hours   0.0.0.0:5094->80/tcp, :::5094->80/tcp, 0.0.0.0:7094->443/tcp, :::7094->443/tcp   greeter

docker-compose logs コマンドで Nginx と gRPC のログを確認します。 Log2Console を起動している場合、前回同様に gRPC に関するログが出力されています。

$ docker-compose logs
greeter  | DEBUG: init main
greeter  | TRACE: Discovering gRPC methods for GrpcGreeter.Services.GreeterService.
greeter  | TRACE: Added gRPC method 'SayHello' to service 'greet.Greeter'. Method type: 'Unary', route pattern: '/greet.Greeter/SayHello'.
greeter  | DEBUG: Hosting starting
greeter  | INFO : Now listening on: http://[::]:80
greeter  | INFO : Now listening on: https://[::]:443
greeter  | DEBUG: Loaded hosting startup assembly GrpcGreeter
greeter  | INFO : Application started. Press Ctrl+C to shut down.
greeter  | INFO : Hosting environment: Production
greeter  | INFO : Content root path: /app/
greeter  | DEBUG: Hosting started
nginx    | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
nginx    | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
nginx    | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
nginx    | 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
nginx    | 10-listen-on-ipv6-by-default.sh: info: /etc/nginx/conf.d/default.conf differs from the packaged version
nginx    | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
nginx    | /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
nginx    | /docker-entrypoint.sh: Configuration complete; ready for start up
nginx    | 2022/09/04 14:38:53 [notice] 1#1: using the "epoll" event method
nginx    | 2022/09/04 14:38:53 [notice] 1#1: nginx/1.23.1
nginx    | 2022/09/04 14:38:53 [notice] 1#1: built by gcc 11.2.1 20220219 (Alpine 11.2.1_git20220219)
nginx    | 2022/09/04 14:38:53 [notice] 1#1: OS: Linux 5.10.102.1-microsoft-standard-WSL2
nginx    | 2022/09/04 14:38:53 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
nginx    | 2022/09/04 14:38:53 [notice] 1#1: start worker processes
nginx    | 2022/09/04 14:38:53 [notice] 1#1: start worker process 31
nginx    | 2022/09/04 14:38:53 [notice] 1#1: start worker process 32

ブラウザで http://localhost:5011 に接続し、Nginx のホームページが表示されることを確認します。

Nginx のホームページ

Web サーバへの接続

Alpine なので、ash を指定します。

$ docker-compose exec web /bin/ash
または
$ docker-compose exec web ash

gRPC サーバへの接続

Ubuntu なので、bash を指定します。

$ docker-compose exec app /bin/bash
または
$ docker-compose exec app bash

gRPC クライアント

クライアントは、URL を変更する以外、全て同じなので割愛します。

gRPC クライアントからは、今回の作り方では 4通りのアクセスができます。 大きくは Nginx サーバ経由、または gRPC サーバ経由になります。 ただし、Nginx サーバ経由の場合、Nginx までは SSL が必須ですが、 gRPC サーバへは SSL を選択する必要はありません。

サーバ アクセス方法 補足
Nginx https://localhost:7011/greet.Greeter gRPC サーバへ HTTP/2 (SSLなし)
Nginx https://localhost:5011/greet.Greeter.SSL gRPC サーバへ HTTP/2 + SSL
gRPC https://localhost:7094 直接 gRPC サーバへ HTTP/2 + SSL
gRPC http://localhost:5094 直接 gRPC サーバへ HTTP/2

IPv4 アドレスを指定する場合、localhost の箇所を IPv4 アドレスに変更してください。 このとき、docker を起動させた WSL2/Ubuntu の IP アドレスを指定してください。

実行結果は、前回同様なので省略します。

まとめ

今回は前回の docker 版 gRPC サーバに Nginx のリバースプロキシを追加しました。 Nginx の追加に関しては、特に難しことはなかったと思います。

今後は、gRPC の通信にファイルやバイナリデータを送受信するとか、 それらを DB サーバを作って、そこに保存できるようにするアプリを掲載しようと思います。 このときは、ネットワークもフロントエンドとバックエンドに分離していきましょう。 それぐらいの規模になったら、GitHub に掲載しようと思います。

上記のように Docker で、Web-AP-DB の 3 層構造を作るぐらいになると、 やはり書籍で一通り勉強しておくと良いと思います。 私が読んだ中で、お勧めなのは以下の本です。 ただし、CentOS のため、WSL2/AlmaLinux を使うといいかもしれません。

最後に参考サイトを掲載しておきます。 では、皆さん、よい旅を。

参考サイト