NGINX Full Version

Kernel TLSとSSL_sendfileによるパフォーマンス向上

TLS(Transport Layer Security)は、非常に有名な暗号プロトコルです。TLSをkernelに実装する(kTLS)ことで、ユーザー空間とkerne l空間のコピー動作の必要性を大幅に減らし、パフォーマンスを向上させることができます。

kTLSとsendfile()を組み合わせることで、データ転送でネットワークスタックに渡される前に、kernel空間で直接暗号化されます。これにより、データをユーザー空間にコピーしTLS ライブラリで暗号化し、その後kernel空間に戻す必要が無くなります。又、kTLS は TLS 処理をハードウェアにオフロードすることができ、TLS暗号処理をネットワークデバイスにオフロードすることもできます。

最近のLinuxとFreeBSDのカーネルはTLSをカーネルにオフロードすることをサポートしていますが、NGINXオープンソースもサポートしています。NGINX 1.21.4では、SSL_sendfile()で静的ファイルを配信する際にkTLSのサポートを導入しており、パフォーマンスを大幅に向上させることができます。以下に詳述するように、NGINXがSSL_sendfile()を使用するには、カーネルとOpenSSLの両方がkTLSでビルドされていることが必要です。

このブログでは、どのオペレーティングシステムとOpenSSLのバージョンがkTLSをサポートしているかを詳しく説明し、kTLS用にカーネルとNGINXを構築し設定する方法を紹介します。また、kTLSによって期待できるパフォーマンスの向上について、FreeBSDとUbuntuで行ったテストの仕様と結果も紹介します。

注意:kTLSの実装は非常に新しく、急速に進化しています。本ブログでは2021年11月現在のkTLSのサポートについて説明していますが、ここで提供する情報や手順の変更については、nginx.orgNGINXブログでのアナウンスを確認してください。

一般的な要件

対応オペレーティングシステム

kTLSをサポートするOS

2021年11月現在、NGINX Open SourceがサポートするOSのうち、以下はkTLSと表記されている暗号をサポートしています。暗号のサポートの詳細については、「TLS プロトコルと暗号のサポート」を参照してください。

  TLSv1.2 ciphers TLSv1.3
cipher suites
TLS_CHACHA20_POLY1305_SHA256 cipher Linux kernel version
Amazon Linux 2* 5.10
CentOS 8** 4.18
FreeBSD 13.0 ❌ *** N/A
RHEL 8 4.18
SLES 15 SP2 5.3
Ubuntu 20.04 LTS ❌​​ 5.4
Ubuntu 21.04 5.11
Ubuntu 21.10 5.13

  * カーネルのバージョンは4.14ではなく5.10である必要があります。kTLSをサポートしないOS及びAmazon Linux 2 FAQを参照してください。
** kTLSのサポート状況は、アップストリームのRHEL 8から継承しています。
*** FreeBSD のコミットログを参照してください。

kTLSをサポートしないOS

以下のOSは、以下の理由により、kTLSをサポートしていません。

TLS プロトコルと暗号のサポート

上記のように、kTLSをサポートしているOSは、TLSのプロトコルや暗号のサポートに違いがあります。

TLSv1.2において、kTLSモジュールは以下の暗号をサポートしています。

TLSv1.3では、kTLSモジュールは以下の暗号スイートをサポートしています。

OpenSSLによってサポートされているTLS暗号がNGINXバイナリで有効になっているか確認するには、NGINXを構築したディレクトリ(たとえば、ホームディレクトリ)でopenssl-3.0.0/.openssl/bin/openssl ciphersコマンドを実行してください。

NGINXでのkTLSの有効化

冒頭で述べたように、kTLSはすべての暗号化と復号化がカーネルで行われるため、NGINXのパフォーマンスを向上させます。データ転送の為にネットワークスタックに渡される前に、カーネル空間で直接暗号化されます。これにより、データをユーザー空間にコピーしてTLSライブラリで暗号化し、転送の為にカーネル空間に戻す必要がなくなります。

カーネルでのkTLSのロード

最近のFreeBSDやLinuxのディストリビューションでは、kTLSは通常モジュールとして(CONFIG_TLS=mオプションで)ビルドされています。NGINXを起動する前に、明示的にkTLSモジュールをカーネルにロードする必要があります。

FreeBSDでNGINXをkTLSで使用可能にする

FreeBSDのNGINXでkTLSサポートを有効にするには、Linuxディストリビューションと同じ手順を使用することができます。ただし、FreeBSD Ports Collectionsecurity/openssl-develポートにあるkTLS付きNGINXのビルドを活用するために、以下の手順を実行することをお勧めします。kTLSの概要を含む詳細については、FreeBSDウェブサイトの「TLS Offload in the Kernel」を参照してください。

  1. kTLSをサポートするOpenSSL 3.0を、configメニューで適切なオプションを選択しながらビルドします。

    # cd /usr/ports/security/openssl-devel && make config && make install
  2. openssl-devel をデフォルトのSSLライブラリとして使用するように /etc/make.conf を変更します。

    # echo "DEFAULT_VERSIONS+=ssl=openssl-devel >> /etc/make.conf
  3. NGINXをビルドします。

    # cd /usr/ports/www/nginx-devel && make install

ディストリビューションでkTLSを使用したNGINXの構築

現在ほとんどのLinuxディストリビューションには、3.0.0より前のバージョンのOpenSSL(一般的には、バージョン1.1)が含まれています。そのため、NGINXはOpenSSL 3.0.0を使用してソースからビルドする必要があります。

kTLSサポートを有効にするconfigureコマンドの重要なオプションは、以下の2つです。

その他のconfigureオプションは、nginx.orgで公開されているNGINXの公式バイナリパッケージに含まれるモジュールに対応したものです。代わりにカスタムモジュールのセットを指定することもできます。現在使用しているNGINXバイナリに使用されているビルドオプションを確認するには、nginx -Vを実行してください。

OpenSSL 3.0.0を使用してNGINXをビルドするには、以下のコマンドを実行します。

$ wget https://nginx.org/download/nginx-1.21.4.tar.gz
$ wget https://www.openssl.org/source/openssl-3.0.0.tar.gz
$ tar xzf openssl-3.0.0.tar.gz
$ cd nginx-1.21.4
$ ./configure \
--with-debug \
--prefix=/usr/local \
--conf-path=/usr/local/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/run/nginx.lock \
--http-client-body-temp-path=/var/cache/nginx/client_temp \
--http-proxy-temp-path=/var/cache/nginx/proxy_temp \
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp \
--user=nginx \
--group=nginx \
--with-compat \
--with-file-aio \
--with-threads \
--with-http_addition_module \
--with-http_auth_request_module \
--with-http_dav_module \
--with-http_flv_module \
--with-http_gunzip_module \
--with-http_gzip_static_module \
--with-http_mp4_module \
--with-http_random_index_module \
--with-http_realip_module \
--with-http_secure_link_module \
--with-http_slice_module \
--with-http_ssl_module \
--with-http_stub_status_module \
--with-http_sub_module \
--with-http_v2_module \
--with-mail \
--with-mail_ssl_module \
--with-stream \
--with-stream_realip_module \
--with-stream_ssl_module \
--with-stream_ssl_preread_module \
--with-openssl=../openssl-3.0.0 \
--with-openssl-opt=enable-ktls \
--with-cc-opt='-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' \
-with-ld-opt='-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie'
$ make –j4
$ make install

注:NGINXバイナリは、OpenSSL 3.0.0ライブラリと静的にリンクされています。OpenSSLにパッチを適用する必要がある場合は、新しいOpenSSLソースアーカイブをダウンロードして解凍し、上記のコマンドを実行してNGINXバイナリをリビルドする必要があります。

NGINXの設定

kTLSを有効にするには、テストに使用したこのサンプル構成のように、server{}コンテキストにOptions KTLSパラメータを持つssl_conf_commandディレクティブを含めます。

worker_processes auto;
error_log /var/log/nginx/error.log debug;

events {}

http {
    sendfile on;

    server {
        listen 443 ssl;
        ssl_certificate ssl/example.crt;
        ssl_certificate_key ssl/example.key;
        ssl_conf_command Options KTLS;
        ssl_protocols TLSv1.3;

        location / {
            root /data;
    	}
    }
}

kTLSが有効であることを確認する

NGINXがkTLSを使用していることを確認するには、デバッグモードを有効にして、エラーログにBIO_get_ktls_send()SSL_sendfile()があるかどうかを確認します。

$ grep BIO /var/log/nginx/error.log
2021/11/10 16:02:46 [debug] 274550#274550: *2 BIO_get_ktls_send(): 1
2021/11/10 16:02:49 [debug] 274550#274550: *3 BIO_get_ktls_send(): 1

$ grep SSL_sendfile /var/log/nginx/error.log
2021/11/10 16:02:46 [debug] 274550#274550: *2 SSL_sendfile: 1048576
2021/11/10 16:02:49 [debug] 274550#274550: *3 SSL_sendfile: 1048576

注:これらのチェックを行った後、特に実稼働環境では、デバッグモードを無効にすることをお勧めします。デバッグ・ログは、大量の書き込み操作を行うため、パフォーマンス・ペナルティが発生します。また、デバッグ・ログは巨大になりやすく、ディスク・パーティションの空き容量をすぐに使い果たしてしまいます。

kTLSによるパフォーマンス向上

高負荷下で静的ファイルを提供する場合、SSL_sendfile()はユーザー空間のTLSと比較して最大2倍までスループットを向上させることができますが、性能向上の大きさは様々な要因(ディスク性能、システム負荷など)に大きく依存します。また、ネットワークカードがTLSオフロードをサポートしていれば、CPU使用率を下げることが可能です。

性能テスト

以下の手順で1スレッドテストを実施することで、お使いのセットアップでのパフォーマンス向上を測定することができます。以下のテスト結果によると、特別なチューニングを行わなくても、最大で約30%の性能向上が確認されています。

使用したハードウェアとソフトウェア

テストを実行するには

  1. ディスクキャッシュに完全に収まるような大きなファイルを作成します。

    # truncate -s 1g /data/1G
  2. このコマンドを実行してスループットを確認します。より正確な結果を得るために、ベースとなるコマンドを複数回繰り返し実行します。出力をministatユーティリティ[FreeBSD][Ubuntu]にパイプし、基本的な統計分析に使用します。

    # for i in 'seq 1 100'; do curl -k -s -o /dev/null -w '%{speed_download}\n' https://localhost/1G | ministat

パフォーマンステスト結果

以下のテスト結果は、ministatの出力として表示され、各値はkBytes/secondでのダウンロード速度です。

FreeBSD 13.0、kTLS使用しない場合のスループット。

    N           Min           Max        Median           Avg        Stddev
x  10        532225        573348        555616      555155.6     10239.137

FreeBSD 13.0、kTLS使用した場合のスループット。

    N           Min           Max        Median           Avg        Stddev
x  10        629379        723164        717349      708600.4     28304.766

Ubuntu 21.10でkTLSを使用しない場合のスループット。

    N           Min           Max        Median           Avg        Stddev
x  10        529199        705720        662354      654321.6     48025.103

Ubuntu 21.10でkTLSを使用した場合のスループット。

    N           Min           Max        Median           Avg        Stddev
x  10        619105        760208        756278      741848.3     43255.246

我々のテストでは、kTLSはUbuntuよりもFreeBSDでより性能を向上させました。改善率は以下の通りです。

  Min Max Median Avg
FreeBSD 13.0 18% 26% 29% 28%
Ubuntu 21.10 16% 8% 14% 13%

概要

NGINX 1.21.4では、SSL_sendfile()を使用して静的ファイルを配信する際のkTLSのサポートが導入されました。当社のテストでは、オペレーティングシステムによって8%から29%、パフォーマンスが向上することが確認されています。

kTLSとNGINXの使用経験、特に他のOSでのテスト結果についてお聞かせください。特に他のOSでのテスト結果をお待ちしています!