NLB が NLB とターゲット間の通信を HTTP/2 から HTTP/1.1 に変換するのか確認してみた

2022.11.15

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

NLBでHTTP/2からHTTP/1.1に変換してくれるのかな

こんにちは、のんピ(@non____97)です。

皆さんは「NLB のターゲットで HTTP/2 を無効化している時に ALPN ポリシーをHTTP2Onlyにした場合の挙動」が気になったことはありますか? 私はあります。

以下記事で紹介している通り、ALPN ポリシーを使う場合でも NLB で TLS を終端できるようになりました。

これにより、ターゲットとなるEC2インスタンスに証明書を入れる必要がなくなりました。

HTTP/2のRFCであるRFC 9113を見ると、HTTP/2にはプロトコル識別子h2h2cがあり、HTTP/2 over TLSh2を使用し、TLSを使用しない場合はh2cを使うと記載がありました。

3.2. Starting HTTP/2 for "https" URIs

A client that makes a request to an "https" URI uses TLS [TLS13] with the ALPN extension [TLS-ALPN].

HTTP/2 over TLS uses the "h2" protocol identifier. The "h2c" protocol identifier MUST NOT be sent by a client or selected by a server; the "h2c" protocol identifier describes a protocol that does not use TLS.

RFC 9113: HTTP/2

要するに、NLBはh2だけでなくh2cもサポートするようになったということですね。

余談ですが、HTTP/2 のホームページを確認すると、「現在、暗号化されていない HTTP/2 をサポートするブラウザーはない」とあるとも記載ありました。そのため、HTTP/2を使用するためにはTLSを必ず使用する必要があるという認識が広まっているかもしれません。

Does HTTP/2 require encryption?

No. After extensive discussion, the Working Group did not have consensus to require the use of encryption (e.g., TLS) for the new protocol.

However, some implementations have stated that they will only support HTTP/2 when it is used over an encrypted connection, and currently no browser supports HTTP/2 unencrypted.

HTTP/2 Frequently Asked Questions

話を元に戻します。

ALBの場合HTTP/2の通信をする際、ALBからターゲットへの通信はHTTP1HTTP2gRPCから選択することが可能です。

特にHTTP1を選択した場合は、クライアントとALB間はHTTP/2、ALBとターゲット間はHTTP/1.1の通信になります。

NLBはこのようにNLBとターゲット間で使用するHTTPのプロトコルを選択できません。

もしかして、NLBでHTTP/1.1に変換されるのでしょうか。それともエンドツーエンドでHTTP/2通信を行うことになるのでしょうか。

気になったので検証してみました。

いきなりまとめ

  • NLBでHTTP/2を使用する場合は、エンドツーエンドでHTTP/2通信を行うことになる
    • NLBからターゲットへの通信はHTTP/1.1に変換されない
  • ALPNポリシーがNoneの場合、ダイレクトでHTTP/2を指定しなければHTTP/2通信は行われない
  • ALPNポリシーがNone以外の場合、クライアントからリクエストされたプロトコルがALPNのリストに含まれない場合は、クライアントからリクエストされたプロトコルを使用する

検証環境

検証環境は以下の通りです。

NLBのALPNポリシーテストの検証環境構成図

ターゲットで稼働しているWebサーバーはApache HTTP Serverです。

せっかくなのでALPNポリシーに何を選択したか、NLBのターゲットとなるWebサーバーがサポートしているプロトコルは何か、リクエストプロトコルは何かで30パターン洗い出しました。

No ALPNポリシー ターゲットのプロトコル リクエストするプロトコル
1 None HTTP/1.1 HTTP/1.1
2 None HTTP/1.1 HTTP/2, HTTP/1.1
3 None HTTP/1.1 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)
4 None HTTP/1.1, HTTP/2 HTTP/1.1
5 None HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1
6 None HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)
7 HTTP1Only HTTP/1.1 HTTP/1.1
8 HTTP1Only HTTP/1.1 HTTP/2, HTTP/1.1
9 HTTP1Only HTTP/1.1 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)
10 HTTP1Only HTTP/1.1, HTTP/2 HTTP/1.1
11 HTTP1Only HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1
12 HTTP1Only HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)
13 HTTP2Only HTTP/1.1 HTTP/1.1
14 HTTP2Only HTTP/1.1 HTTP/2, HTTP/1.1
15 HTTP2Only HTTP/1.1 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)
16 HTTP2Only HTTP/1.1, HTTP/2 HTTP/1.1
17 HTTP2Only HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1
18 HTTP2Only HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)
19 HTTP2Optional HTTP/1.1 HTTP/1.1
20 HTTP2Optional HTTP/1.1 HTTP/2, HTTP/1.1
21 HTTP2Optional HTTP/1.1 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)
22 HTTP2Optional HTTP/1.1, HTTP/2 HTTP/1.1
23 HTTP2Optional HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1
24 HTTP2Optional HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)
25 HTTP2Preferred HTTP/1.1 HTTP/1.1
26 HTTP2Preferred HTTP/1.1 HTTP/2, HTTP/1.1
27 HTTP2Preferred HTTP/1.1 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)
28 HTTP2Preferred HTTP/1.1, HTTP/2 HTTP/1.1
29 HTTP2Preferred HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1
30 HTTP2Preferred HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)

各パターンがどのようなプロトコルを使おうとするのか、通信はできるのかを確認します。

リクエストする際は、以下のように行っています。

  • HTTP/1.1 : `curl --http1.1 -v https://nlb.non-97.net`
  • HTTP/2, HTTP/1.1 : `curl --http2 -v https://nlb.non-97.net`
  • HTTP/2, HTTP/1.1 (ダイレクトHTTP/2) : `curl --http2-prior-knowledge -v https://nlb.non-97.net`

検証環境はAWS CDKでデプロイします。使用したコードは以下リポジトリに保存しています。

npx cdk deployで各種リソースをデプロイします。

途中、Public Hosted Zoneの作成が完了したら、作成したPublic Hosted ZoneのNSレコードを上位のDNSサーバーに登録します。

なお、最新のAmazon Linux 2でインストールできるhttpdのバージョンである2.4.54http2_moduleがインストールされています。

# バージョンの確認
$ httpd -v
Server version: Apache/2.4.54 ()
Server built:   Jun 30 2022 11:02:23

$ httpd -M | grep http2
 http2_module (shared)
 proxy_http2_module (shared)

http2_moduleのディレクトティブで使用するHTTP/2をサポートするかどうか切り替えます。

デフォルトでは以下のようにHTTP/2をサポートしています。

$ cat /etc/httpd/conf/httpd.conf
.
.
(中略)
.
.
# Enable HTTP/2 by default
#
# https://httpd.apache.org/docs/2.4/mod/core.html#protocols

<IfModule mod_http2.c>
    Protocols h2 h2c http/1.1
</IfModule>


# Supplemental configuration
#
# Load config files in the "/etc/httpd/conf.d" directory, if any.
IncludeOptional conf.d/*.conf

HTTP/1.1しかサポートしたくない場合は、Protocols http/1.1に変更してsudo systemctl reload httpdでリロードします。また、今回ターゲットには証明書をインストールしていないため、Protocols h2にするとNLBとターゲット間でHTTP/2を使用できません。

ちなみにhttp2_modulepreforkをサポートしていません。

# MPMの確認
$ httpd -V
Server version: Apache/2.4.54 ()
Server built:   Jun 30 2022 11:02:23
Server\'s Module Magic Number: 20120211:124
Server loaded:  APR 1.7.0, APR-UTIL 1.6.1, PCRE 8.32 2012-11-30
Compiled using: APR 1.7.0, APR-UTIL 1.6.1, PCRE 8.32 2012-11-30
Architecture:   64-bit
Server MPM:     prefork
  threaded:     no
    forked:     yes (variable process count)
Server compiled with....
 -D APR_HAS_SENDFILE
 -D APR_HAS_MMAP
 -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
 -D APR_USE_PROC_PTHREAD_SERIALIZE
 -D APR_USE_PTHREAD_SERIALIZE
 -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 -D APR_HAS_OTHER_CHILD
 -D AP_HAVE_RELIABLE_PIPED_LOGS
 -D DYNAMIC_MODULE_LIMIT=256
 -D HTTPD_ROOT="/etc/httpd"
 -D SUEXEC_BIN="/usr/sbin/suexec"
 -D DEFAULT_PIDLOG="/run/httpd/httpd.pid"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_ERRORLOG="logs/error_log"
 -D AP_TYPES_CONFIG_FILE="conf/mime.types"
 -D SERVER_CONFIG_FILE="conf/httpd.conf"

# エラーログの確認
$ sudo tail /var/log/httpd/error_log
[Mon Nov 14 10:12:14.417459 2022] [mpm_prefork:notice] [pid 2023] AH00171: Graceful restart requested, doing restart
[Mon Nov 14 10:12:14.435468 2022] [lbmethod_heartbeat:notice] [pid 2023] AH02282: No slotmem from mod_heartmonitor
[Mon Nov 14 10:12:14.435513 2022] [http2:warn] [pid 2023] AH10034: The mpm module (prefork.c) is not supported by mod_http2. The mpm determines how things are processed in your server. HTTP/2 has more demands in this regard and the currently selected mpm will just not do. This is an advisory warning. Your server will continue to work, but the HTTP/2 protocol will be inactive.
[Mon Nov 14 10:12:14.435635 2022] [mpm_prefork:notice] [pid 2023] AH00163: Apache/2.4.54 () configured -- resuming normal operations
[Mon Nov 14 10:12:14.435641 2022] [core:notice] [pid 2023] AH00094: Command line: '/usr/sbin/httpd -D FOREGROUND'

しかし、検証の結論を先にお伝えするとbut the HTTP/2 protocol will be inactive.とありますが、この状態でもNLBを介したHTTP/2の通信はできました。

Apache HTTP Serverの変更履歴を見ると以下のような記載がありました。

*) COMPATIBILITY: mod_http2: Disable and give warning when using Prefork.

The server will continue to run, but HTTP/2 will no longer be negotiated.

[Stefan Eissing]

https://downloads.apache.org/httpd/CHANGES_2.4

HTTP/2が動作しないというよりは、HTTP/2を使用するというネゴシエートをしないだけかもしれません。クライアントとNLB間で使用するプロトコルをそのまま、NLBとターゲット間の通信でも使用するのであればネゴシエートしなくても通信できるのではと考えます。

本番環境でHTTP/2のWebサーバーを実装する場合は、MPMをeventもしくはworkerに変更してください。

やってみた

結果のまとめ

それでは、検証をしてみます。

と言っても全ての検証結果のログを書くととんでもない長さになるので、検証結果を以下にまとめます。

No ALPNポリシー ターゲットのプロトコル リクエストするプロトコル 使用したプロトコル 通信できるか
1 None HTTP/1.1 HTTP/1.1 HTTP/1.1 o
2 None HTTP/1.1 HTTP/2, HTTP/1.1 HTTP/1.1 o
3 None HTTP/1.1 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)
HTTP/2 x
4 None HTTP/1.1, HTTP/2 HTTP/1.1 HTTP/1.1 o
5 None HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1 HTTP/1.1 o
6 None HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)
HTTP/2 o
7 HTTP1Only HTTP/1.1 HTTP/1.1 HTTP/1.1 o
8 HTTP1Only HTTP/1.1 HTTP/2, HTTP/1.1 HTTP/1.1 o
9 HTTP1Only HTTP/1.1 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)
HTTP/1.1 o
10 HTTP1Only HTTP/1.1, HTTP/2 HTTP/1.1 HTTP/1.1 o
11 HTTP1Only HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1 HTTP/1.1 o
12 HTTP1Only HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)
HTTP/1.1 o
13 HTTP2Only HTTP/1.1 HTTP/1.1 HTTP/1.1 o
14 HTTP2Only HTTP/1.1 HTTP/2, HTTP/1.1 HTTP/2 x
15 HTTP2Only HTTP/1.1 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)
HTTP/2 x
16 HTTP2Only HTTP/1.1, HTTP/2 HTTP/1.1 HTTP/1.1 o
17 HTTP2Only HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1 HTTP/2 o
18 HTTP2Only HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)
HTTP/2 o
19 HTTP2Optional HTTP/1.1 HTTP/1.1 HTTP/1.1 o
20 HTTP2Optional HTTP/1.1 HTTP/2, HTTP/1.1 HTTP/1.1 o
21 HTTP2Optional HTTP/1.1 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)
HTTP/1.1 o
22 HTTP2Optional HTTP/1.1, HTTP/2 HTTP/1.1 HTTP/1.1 o
23 HTTP2Optional HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1 HTTP/1.1 o
24 HTTP2Optional HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)
HTTP/1.1 o
25 HTTP2Preferred HTTP/1.1 HTTP/1.1 HTTP/1.1 o
26 HTTP2Preferred HTTP/1.1 HTTP/2, HTTP/1.1 HTTP/2 x
27 HTTP2Preferred HTTP/1.1 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)
HTTP/2 x
28 HTTP2Preferred HTTP/1.1, HTTP/2 HTTP/1.1 HTTP/1.1 o
29 HTTP2Preferred HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1 HTTP/2 o
30 HTTP2Preferred HTTP/1.1, HTTP/2 HTTP/2, HTTP/1.1
(ダイレクトHTTP/2)
HTTP/2 o

いずれのALPNポリシーでもターゲットがHTTP/2をサポートしていない場合、HTTP/2で通信しようとすると失敗していますね。NLBでHTTP/2からHTTP/1.1に変換してくれるなんてことはないようです。

ALPNポリシーが None の場合

ALPNポリシーがNoneの場合は、リクエストする際にダイレクトでHTTP/2を指定しなければ、HTTP/2で通信はしようとしないようです。ALPNでネゴシエートしないので(ALPN, server did not agree to a protocol)、ターゲットがHTTP/2をサポートしていない状態で、ダイレクトでHTTP/2を指定すると以下のようにエラーになります。

> curl --http2-prior-knowledge -v https://nlb.non-97.net
*   Trying 52.72.95.30:443...
* Connected to nlb.non-97.net (52.72.95.30) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES128-GCM-SHA256
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: CN=nlb.non-97.net
*  start date: Nov 14 00:00:00 2022 GMT
*  expire date: Dec 14 23:59:59 2023 GMT
*  subjectAltName: host "nlb.non-97.net" matched cert's "nlb.non-97.net"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M02
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x13f80b600)
> GET / HTTP/2
> Host: nlb.non-97.net
> user-agent: curl/7.79.1
> accept: */*
>
* Empty reply from server
* Closing connection 0
curl: (52) Empty reply from server

その際のWebサーバーのログは以下の通りです。

$ sudo tail -n 1 /var/log/httpd/access_log
<クライアントのIPアドレス> - - [14/Nov/2022:04:59:11 +0000] "PRI * HTTP/2.0" 400 226 "-" "-"

NLBのアクセスログを確認すると、以下のようになっていました。

tls 2.0 2022-11-14T04:59:11 net/NlbSt-NLB55-CAEURZ3ZW0D7/19d9630377043ec7 469d019e6174afc6 <クライアントのIPアドレス>:37464 10.0.0.6:443 588 391 104 402 - arn:aws:acm:us-east-1:<AWSアカウントID>:certificate/791c852f-af15-41cd-84b1-0df0481c7460 - TLS_AES_128_GCM_SHA256 tlsv13 - nlb.non-97.net - - -

末尾の3つのフィールドがALPN関連のフィールドです。

フィールド 説明
alpn_fe_protocol クライアントとネゴシエートされたアプリケーションプロトコル (文字列形式)。指定できる値は、h2、http/1.1、および http/1.0 です。TLS リスナーで ALPN ポリシーが設定されていない場合、一致するプロトコルが見つからない場合、または有効なプロトコルリストが送信されない場合、この値は - に設定されます。
alpn_be_protocol ターゲットとネゴシエートされたアプリケーションプロトコル (文字列形式)。指定できる値は、h2、http/1.1、および http/1.0 です。TLS リスナーで ALPN ポリシーが設定されていない場合、一致するプロトコルが見つからない場合、または有効なプロトコルリストが送信されない場合、この値は - に設定されます。
alpn_client_preference_list クライアントの hello メッセージ内の application_layer_protocol_negotiation 拡張機能の値。この値は URL でエンコードされます。各プロトコルは二重引用符で囲まれ、プロトコルはカンマで区切られます。TLS リスナーで ALPN ポリシーが設定されていない場合、有効なクライアント hello メッセージが送信されない場合、または内線番号が存在しない場合、この値は - に設定されます。文字列は、256 バイトを超える場合は切り捨てられます。

抜粋 : Network Load Balancer のアクセスログ - Elastic Load Balancing

いずれのフィールドも-であることからALPNを使ってネゴシエートしていないことが分かります。

ターゲットでHTTP/2をサポートしていれば以下のようにHTTP/2で通信することが可能です。

> curl --http2-prior-knowledge -v https://nlb.non-97.net
*   Trying 52.72.95.30:443...
* Connected to nlb.non-97.net (52.72.95.30) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES128-GCM-SHA256
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: CN=nlb.non-97.net
*  start date: Nov 14 00:00:00 2022 GMT
*  expire date: Dec 14 23:59:59 2023 GMT
*  subjectAltName: host "nlb.non-97.net" matched cert's "nlb.non-97.net"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M02
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x14700ec00)
> GET / HTTP/2
> Host: nlb.non-97.net
> user-agent: curl/7.79.1
> accept: */*
>
< HTTP/2 200
< date: Mon, 14 Nov 2022 05:04:47 GMT
< server: Apache/2.4.54 ()
< last-modified: Mon, 14 Nov 2022 04:24:17 GMT
< etag: "a-5ed669cdddfe4"
< accept-ranges: bytes
< content-length: 10
< content-type: text/html; charset=UTF-8
<
test text
* Connection #0 to host nlb.non-97.net left intact

その際のWebサーバーのログは以下の通りです。

$ sudo tail -n 1 /var/log/httpd/access_log
<クライアントのIPアドレス> - - [14/Nov/2022:05:04:47 +0000] "GET / HTTP/2.0" 200 10 "-" "curl/7.79.1"

この場合もALPNは使用していないため、NLBのアクセスログを確認するとネゴシエートしていないことが分かります。

tls 2.0 2022-11-14T05:04:46 net/NlbSt-NLB55-CAEURZ3ZW0D7/19d9630377043ec7 469d019e6174afc6 <クライアントのIPアドレス>:37480 10.0.0.6:443 608 403 113 177 - arn:aws:acm:us-east-1:<AWSアカウントID>:certificate/791c852f-af15-41cd-84b1-0df0481c7460 - TLS_AES_128_GCM_SHA256 tlsv13 - nlb.non-97.net - - -

ALPNポリシー HTTP1Only の場合

ALPNポリシーHTTP1Onlyの場合は、HTTP/1.* のみをネゴシエートするため、ターゲットでHTTP/2をサポートしていてもHTTP/1.1を使っていますね。

ダイレクトでHTTP/2を指定した場合のcurlの結果は以下の通りです。ALPN, offering h2とHTTP/2を使用するようにリクエストしていますが、ALPN, server accepted to use http/1.1とHTTP/1.1を使うとレスポンスがありました。

> curl --http2-prior-knowledge -v https://nlb.non-97.net
*   Trying 52.72.95.30:443...
* Connected to nlb.non-97.net (52.72.95.30) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES128-GCM-SHA256
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: CN=nlb.non-97.net
*  start date: Nov 14 00:00:00 2022 GMT
*  expire date: Dec 14 23:59:59 2023 GMT
*  subjectAltName: host "nlb.non-97.net" matched cert's "nlb.non-97.net"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M02
*  SSL certificate verify ok.
> GET / HTTP/1.1
> Host: nlb.non-97.net
> User-Agent: curl/7.79.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Mon, 14 Nov 2022 05:15:22 GMT
< Server: Apache/2.4.54 ()
< Upgrade: h2,h2c
< Connection: Upgrade
< Last-Modified: Mon, 14 Nov 2022 04:24:17 GMT
< ETag: "a-5ed669cdddfe4"
< Accept-Ranges: bytes
< Content-Length: 10
< Content-Type: text/html; charset=UTF-8
<
test text
* Connection #0 to host nlb.non-97.net left intact

Webサーバーのログを確認すると確かにHTTP/1.1で通信していますね。

$ sudo tail -n 1 /var/log/httpd/access_log
<クライアントのIPアドレス> - - [14/Nov/2022:05:15:22 +0000] "GET / HTTP/1.1" 200 10 "-" "curl/7.79.1"

NLBのアクセスログは以下の通りです。クライアントとネゴシエートされたプロトコル(alpn_fe_protocol)はHTTP/1.1ですが、ターゲットとネゴシエートされたプロトコル(alpn_be_protocol)は-となっていますね。

tls 2.0 2022-11-14T05:15:22 net/NlbSt-NLB55-CAEURZ3ZW0D7/19d9630377043ec7 469d019e6174afc6 <クライアントのIPアドレス>:37742 10.0.0.6:443 605 213 78 283 - arn:aws:acm:us-east-1:<AWSアカウントID>:certificate/791c852f-af15-41cd-84b1-0df0481c7460 - TLS_AES_128_GCM_SHA256 tlsv13 - nlb.non-97.net http/1.1 - "h2","http/1.1"

ALPNポリシーが HTTP2Only の場合

ALPNポリシーがHTTP2Onlyの場合は、ターゲットがHTTP/2をサポートされていれば優先的にHTTP/2を使用して通信していることが分かります。

> curl --http2 -v https://nlb.non-97.net
*   Trying 52.72.95.30:443...
* Connected to nlb.non-97.net (52.72.95.30) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=nlb.non-97.net
*  start date: Nov 14 00:00:00 2022 GMT
*  expire date: Dec 14 23:59:59 2023 GMT
*  subjectAltName: host "nlb.non-97.net" matched cert's "nlb.non-97.net"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M02
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x142812200)
> GET / HTTP/2
> Host: nlb.non-97.net
> user-agent: curl/7.79.1
> accept: */*
>
< HTTP/2 200
< date: Mon, 14 Nov 2022 05:22:04 GMT
< server: Apache/2.4.54 ()
< last-modified: Mon, 14 Nov 2022 04:24:17 GMT
< etag: "a-5ed669cdddfe4"
< accept-ranges: bytes
< content-length: 10
< content-type: text/html; charset=UTF-8
<
test text
* Connection #0 to host nlb.non-97.net left intact

その際のWebサーバーのログは以下の通りです。

$ sudo tail -n 1 /var/log/httpd/access_log
<クライアントのIPアドレス> - - [14/Nov/2022:05:22:04 +0000] "GET / HTTP/2.0" 200 10 "-" "curl/7.79.1"

この場合もALPNは使用していないため、NLBのアクセスログを確認するとネゴシエートしていないことが分かります。

tls 2.0 2022-11-14T05:04:46 net/NlbSt-NLB55-CAEURZ3ZW0D7/19d9630377043ec7 469d019e6174afc6 <クライアントのIPアドレス>:37480 10.0.0.6:443 608 403 113 177 - arn:aws:acm:us-east-1:<AWSアカウントID>:certificate/791c852f-af15-41cd-84b1-0df0481c7460 - TLS_AES_128_GCM_SHA256 tlsv13 - nlb.non-97.net - - -

NLBのアクセスログは以下の通りです。

tls 2.0 2022-11-14T05:22:03 net/NlbSt-NLB55-CAEURZ3ZW0D7/19d9630377043ec7 469d019e6174afc6 <クライアントのIPアドレス>:37484 10.0.0.6:443 590 383 113 177 - arn:aws:acm:us-east-1:<AWSアカウントID>:certificate/791c852f-af15-41cd-84b1-0df0481c7460 - TLS_AES_128_GCM_SHA256 tlsv13 - nlb.non-97.net h2 - "h2","http/1.1"

また、HTTP/2のみをネゴシエートするとのことでしたが、HTTP/1.1でリクエストしたらHTTP/1.1で通信できました。

ターゲットがサポートしているプロトコルがHTTP/1.1のみの状態で、HTTP/1.1のリクエストをした際のcurlの結果は以下の通りです。

> curl --http1.1 -v https://nlb.non-97.net
*   Trying 52.72.95.30:443...
* Connected to nlb.non-97.net (52.72.95.30) port 443 (#0)
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES128-GCM-SHA256
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: CN=nlb.non-97.net
*  start date: Nov 14 00:00:00 2022 GMT
*  expire date: Dec 14 23:59:59 2023 GMT
*  subjectAltName: host "nlb.non-97.net" matched cert's "nlb.non-97.net"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M02
*  SSL certificate verify ok.
> GET / HTTP/1.1
> Host: nlb.non-97.net
> User-Agent: curl/7.79.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Mon, 14 Nov 2022 05:19:04 GMT
< Server: Apache/2.4.54 ()
< Last-Modified: Mon, 14 Nov 2022 04:24:17 GMT
< ETag: "a-5ed669cdddfe4"
< Accept-Ranges: bytes
< Content-Length: 10
< Content-Type: text/html; charset=UTF-8
<
test text
* Connection #0 to host nlb.non-97.net left intact

ネゴシエートに失敗した結果、クライアントからのリクエスト通りHTTP/1.1を使用しているように見えますね。

NLBのアクセスログを確認しても、その様子がよく分かります。

tls 2.0 2022-11-14T05:19:04 net/NlbSt-NLB55-CAEURZ3ZW0D7/19d9630377043ec7 469d019e6174afc6 <クライアントのIPアドレス>:37419 10.0.0.6:443 417 214 78 245 - arn:aws:acm:us-east-1:<AWSアカウントID>:certificate/791c852f-af15-41cd-84b1-0df0481c7460 - TLS_AES_128_GCM_SHA256 tlsv13 - nlb.non-97.net - - "http/1.1"

HTTP2Onlyだからと言ってHTTP/1.1をブロックすることはないようです。

ALPNポリシーが HTTP2Optional の場合

ALPNポリシーがHTTP2Optionalの場合は、いずれのパターンもHTTP/1.1を使用しています。

> curl --http2-prior-knowledge -v https://nlb.non-97.net
*   Trying 52.72.95.30:443...
* Connected to nlb.non-97.net (52.72.95.30) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES128-GCM-SHA256
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: CN=nlb.non-97.net
*  start date: Nov 14 00:00:00 2022 GMT
*  expire date: Dec 14 23:59:59 2023 GMT
*  subjectAltName: host "nlb.non-97.net" matched cert's "nlb.non-97.net"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M02
*  SSL certificate verify ok.
> GET / HTTP/1.1
> Host: nlb.non-97.net
> User-Agent: curl/7.79.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Mon, 14 Nov 2022 05:29:36 GMT
< Server: Apache/2.4.54 ()
< Upgrade: h2,h2c
< Connection: Upgrade
< Last-Modified: Mon, 14 Nov 2022 04:24:17 GMT
< ETag: "a-5ed669cdddfe4"
< Accept-Ranges: bytes
< Content-Length: 10
< Content-Type: text/html; charset=UTF-8
<
test text
* Connection #0 to host nlb.non-97.net left intact

ダイレクトでHTTP/2を指定してもALPNでHTTP/1.1をネゴシエーションしていますね。

その際のNLBのアクセスログは以下の通りです。

tls 2.0 2022-11-14T05:29:36 net/NlbSt-NLB55-CAEURZ3ZW0D7/19d9630377043ec7 469d019e6174afc6 <クライアントのIPアドレス>:37740 10.0.0.6:443 615 216 78 283 - arn:aws:acm:us-east-1:<AWSアカウントID>:certificate/791c852f-af15-41cd-84b1-0df0481c7460 - TLS_AES_128_GCM_SHA256 tlsv13 - nlb.non-97.net http/1.1 - "h2","http/1.1"

ALPNポリシーが HTTP2Preferred の場合

ALPNポリシーがHTTP2Optionalの場合はHTTP/1.* よりも HTTP/2 を優先しているだけなので、HTTP/1.1でリクエストがあった場合もALPNでネゴシエートされた上でHTTP/1.1で通信します。

> curl --http1.1 -v https://nlb.non-97.net
*   Trying 52.72.95.30:443...
* Connected to nlb.non-97.net (52.72.95.30) port 443 (#0)
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES128-GCM-SHA256
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: CN=nlb.non-97.net
*  start date: Nov 14 00:00:00 2022 GMT
*  expire date: Dec 14 23:59:59 2023 GMT
*  subjectAltName: host "nlb.non-97.net" matched cert's "nlb.non-97.net"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M02
*  SSL certificate verify ok.
> GET / HTTP/1.1
> Host: nlb.non-97.net
> User-Agent: curl/7.79.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Mon, 14 Nov 2022 05:38:15 GMT
< Server: Apache/2.4.54 ()
< Upgrade: h2,h2c
< Connection: Upgrade
< Last-Modified: Mon, 14 Nov 2022 04:24:17 GMT
< ETag: "a-5ed669cdddfe4"
< Accept-Ranges: bytes
< Content-Length: 10
< Content-Type: text/html; charset=UTF-8
<
test text
* Connection #0 to host nlb.non-97.net left intact

その際のNLBのアクセスログは以下の通りです。

tls 2.0 2022-11-14T05:38:15 net/NlbSt-NLB55-CAEURZ3ZW0D7/19d9630377043ec7 469d019e6174afc6 <クライアントのIPアドレス>:37628 10.0.0.6:443 402 208 78 283 - arn:aws:acm:us-east-1:<AWSアカウントID>:certificate/791c852f-af15-41cd-84b1-0df0481c7460 - TLS_AES_128_GCM_SHA256 tlsv13 - nlb.non-97.net http/1.1 - "http/1.1"

もちろん、HTTP/2がALPNのリクエストに含まれていればHTTP/2で通信をします。

> curl --http2 -v https://nlb.non-97.net
*   Trying 52.72.95.30:443...
* Connected to nlb.non-97.net (52.72.95.30) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=nlb.non-97.net
*  start date: Nov 14 00:00:00 2022 GMT
*  expire date: Dec 14 23:59:59 2023 GMT
*  subjectAltName: host "nlb.non-97.net" matched cert's "nlb.non-97.net"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M02
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x123010a00)
> GET / HTTP/2
> Host: nlb.non-97.net
> user-agent: curl/7.79.1
> accept: */*
>
< HTTP/2 200
< date: Mon, 14 Nov 2022 05:38:50 GMT
< server: Apache/2.4.54 ()
< last-modified: Mon, 14 Nov 2022 04:24:17 GMT
< etag: "a-5ed669cdddfe4"
< accept-ranges: bytes
< content-length: 10
< content-type: text/html; charset=UTF-8
<
test text
* Connection #0 to host nlb.non-97.net left intact

その際のNLBのアクセスログは以下の通りです。

tls 2.0 2022-11-14T05:38:50 net/NlbSt-NLB55-CAEURZ3ZW0D7/19d9630377043ec7 469d019e6174afc6 <クライアントのIPアドレス>:37662 10.0.0.6:443 586 210 113 177 - arn:aws:acm:us-east-1:<AWSアカウントID>:certificate/791c852f-af15-41cd-84b1-0df0481c7460 - TLS_AES_128_GCM_SHA256 tlsv13 - nlb.non-97.net h2 - "h2","http/1.1"

NLBでHTTP/2を使用する場合は、エンドツーエンドでHTTP/2通信を行うことになる

NLB のターゲットで HTTP/2 を無効化している時に ALPN ポリシーをHTTP2Onlyにした場合などの挙動を確認してみました。

NLBでHTTP/2を使用する場合は、エンドツーエンドでHTTP/2通信を行うようですね。HTTP/2を使いたい場合は、ターゲット側がHTTP/2に対応するように設定しましょう。

この記事が誰かの助けになれば幸いです。

以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!