ある事情でRTMPによる映像配信基盤が必要になりそうになったため nginx-rtmp-module を使って自前で組んでいる際中のメモ書き的な記事になります。
実現内容
この記事で作る構成は以下の通りです。
記事のタイトルの通り、1コンテナで映像伝送と映像配信を両方行えるようにしています。
まずは映像の送信側です。
OBSやMicrosoft Teamsから443ポートに向けて映像伝送を行います。その際、RTMPSで盗聴・改ざんを防止します。映像はまずnginxのsteramモジュールで受信し、SSL通信を終端します。次にSNIで転送先のバックエンドを選択し、最終的にnginxのRTMPモジュールに送られ、HLSファイル(*.m3u8
, *.ts
)に保存されます。
次に映像の受信側です。
Webブラウザで、同じく443ポートにHTTPSでアクセスします。nginxのstreamモジュールでSSL通信を終端し、SNIでHTTPのバックエンドへ転送します。HTTPのバックエンドからHLSファイル群やHTMLプレイヤーを読み込むことで映像を再生します。
この構成により、OBSやMicrosoft Teamsのような、RTMPによる映像伝送機能を持ったクライアントを使って、遠隔地へ映像を届けることができます。
stunnel について
nginxとRTMPSのキーワードで調べると、stunnelを使ってOBSやMicrosoft TeamsからのSSLを終端する構成が見受けられましたが、nginxのstreamモジュールを使えば同じくSSL終端できる上に、nginxのアクセス制御の恩恵にあずかれることと設定を集約できることから、最終的にはstunnelを使用していません。
nginx の Dockerfile
Dockerfileはtiangolo/nginx-rtmpのイメージを少し修正しています。こちらのイメージにはnginxのstreamモジュールが含まれていなかったため configure
のオプションに --with-stream
と --with-stream_ssl_module
を追加しました。
FROM buildpack-deps:bullseye
# Versions of nginx and nginx-rtmp-module to use
ENV NGINX_VERSION nginx-1.23.2
ENV NGINX_RTMP_MODULE_VERSION 1.2.2
# Install dependencies
RUN apt-get update && \
apt-get install -y ca-certificates openssl libssl-dev && \
rm -rf /var/lib/apt/lists/*
# Download and decompress nginx
RUN mkdir -p /tmp/build/nginx && \
cd /tmp/build/nginx && \
wget -O ${NGINX_VERSION}.tar.gz https://nginx.org/download/${NGINX_VERSION}.tar.gz && \
tar -zxf ${NGINX_VERSION}.tar.gz
# Download and decompress RTMP module
RUN mkdir -p /tmp/build/nginx-rtmp-module && \
cd /tmp/build/nginx-rtmp-module && \
wget -O nginx-rtmp-module-${NGINX_RTMP_MODULE_VERSION}.tar.gz https://github.com/arut/nginx-rtmp-module/archive/v${NGINX_RTMP_MODULE_VERSION}.tar.gz && \
tar -zxf nginx-rtmp-module-${NGINX_RTMP_MODULE_VERSION}.tar.gz && \
cd nginx-rtmp-module-${NGINX_RTMP_MODULE_VERSION}
# Build and install nginx
# The default puts everything under /usr/local/nginx, so it's needed to change
# it explicitly. Not just for order but to have it in the PATH
RUN cd /tmp/build/nginx/${NGINX_VERSION} && \
./configure \
--sbin-path=/usr/local/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--pid-path=/var/run/nginx/nginx.pid \
--lock-path=/var/lock/nginx/nginx.lock \
--http-log-path=/var/log/nginx/access.log \
--http-client-body-temp-path=/tmp/nginx-client-body \
--with-http_ssl_module \
--with-threads \
--with-ipv6 \
--with-stream \
--with-stream_ssl_module \
--add-module=/tmp/build/nginx-rtmp-module/nginx-rtmp-module-${NGINX_RTMP_MODULE_VERSION} --with-debug && \
make -j $(getconf _NPROCESSORS_ONLN) && \
make install && \
mkdir /var/lock/nginx && \
rm -rf /tmp/build
# Forward logs to Docker
RUN ln -sf /dev/stdout /var/log/nginx/access.log && \
ln -sf /dev/stderr /var/log/nginx/error.log
# Set up config file
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 1935
CMD ["nginx", "-g", "daemon off;"]
証明書の取得
今回はLet's Encryptから証明書を取得しました。
細かい手順は省きますが、備忘録も兼ねて証明書を取得した時に使用した docker-compose.yml
を載せておきます。
version: "3"
services:
certbot:
image: certbot/certbot:latest
volumes:
- type: bind
source: ./data/certbot/etc/letsencrypt
target: /etc/letsencrypt
- type: bind
source: ./data/certbot/var/lib/letsencrypt
target: /var/lib/letsencrypt
command: ["certonly", "--manual", "--agree-tos", "--no-eff-email", "--preferred-challenges", "dns"]
実行コマンドは以下の通りです。
docker compose run -f docker-compose-certbot.yml --rm certbot
今回はワイルドカード証明書を取得するため --preferred-challenges
は dns
に設定しています。
また、ローカルでテストを行ったため名前の向き先は内部IPアドレスになります。
パブリックDNSに登録するレコードのサンプルは以下の通りです。example.com
部分はダミーですので、取得したドメインに合わせて変更します。
名前 | タイプ | 値 |
---|---|---|
*.local.live.example.com | A | 127.0.0.1 |
_acme-challenge.local.live.example.com | TXT | certbotから指示される文字列 |
構築
では、構築手順について説明します。今回は、GitHub等でファイルを公開していないので、この記事に書いてあることが今お見せできる内容の全てになります。
使用環境
- Windows 10
- Docker Desktop for Windows
- WSL2
- OBS 29.1.2
ファイル構成
Docker を実行する端末の中に以下の様なファイル構成を作ります。
- data/
- nginx/
- hls/ # HLSファイル保存場所
- deployments/
- nginx/
- certs/
- certificate.crt # Let's Encrypt 証明書
- private.key # Let's Encrypt 秘密鍵
- etc/
- nginx/
- nginx.conf
- www/
- index.html # ビデオプレイヤー
- images/
- nginx/
- Dockerfile
- nginx.conf # イメージビルド用nginx設定
- docker-compose-certbot.yml
- docker-compose.yml
docker-compose.yml
今回テストで使用した docker-compose.yml
はこちら。
version: "3"
services:
nginx:
build:
context: ./images/nginx
volumes:
- type: bind
source: ./deployments/nginx/certs
target: /certs
- type: bind
source: ./deployments/nginx/etc/nginx/nginx.conf
target: /etc/nginx/nginx.conf
- type: bind
source: ./deployments/nginx/www
target: /www
- type: bind
source: ./data/nginx/hls
target: /hls
ports:
- "80:80"
- "443:443"
- "1935:1935"
ポート番号 80
と 1935
を公開していますが、テスト用なので本番デプロイ時は不要です。
nginx.conf
今回使用したnginxの設定ファイルはこちら。
worker_processes auto;
error_log /var/log/nginx/error.log info;
pid /var/run/nginx.pid;
rtmp_auto_push on;
events {
worker_connections 1024;
}
rtmp {
server {
listen 1935;
listen [::]:1935 ipv6only=on;
application live {
live on;
record off;
hls on;
hls_path /hls;
hls_fragment 10s;
}
}
}
stream {
map $ssl_server_name $backend {
www.local.live.example.com 127.0.0.1:80;
rtmp.local.live.example.com 127.0.0.1:1935;
}
server {
listen 443 ssl;
listen [::]:443 ssl ipv6only=on;
ssl_prefer_server_ciphers on;
ssl_certificate /certs/certificate.crt;
ssl_certificate_key /certs/private.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE:HIGH:!aNULL:!MD5;
proxy_pass $backend;
}
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
gzip off;
keepalive_timeout 65;
server {
listen 80;
location /hls/ {
alias /hls/;
}
location / {
alias /www/;
}
}
}
例によって example.com
の部分はダミーですので、自身の持つドメインに合わせて変える必要があります。
この設定の全体像を要約すると以下のようになります。
- stream
-
443
ポートで待ち受け - SSLを終端
- SNIの名前に基づいて
80
か1935
ポートに振り分ける
-
- rtmp
-
1935
ポートで待ち受け - 映像を受信するとHLSファイルを保存
-
- http
-
80
ポートで待ち受け - HLSファイルとビデオプレイヤーを公開
-
ビデオプレイヤーHTML
HLSを再生するビデオプレイヤーのHTMLは以下の通りです。今回は Video.js を使用しました。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Live</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="https://vjs.zencdn.net/8.3.0/video-js.css" rel="stylesheet" />
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div style="height: 100vh">
<video
autoplay
id="player"
class="video-js"
style="width: 100%; height: 100%;"
controls
muted
preload="auto"
width="640"
height="360"
data-setup="{}">
<source src="/hls/hogehoge.m3u8" type="application/vnd.apple.mpegurl" />
</video>
</div>
<script src="https://vjs.zencdn.net/8.3.0/video.min.js"></script>
</body>
</html>
hogehoge.m3u8
というファイル名が書かれていますが、この中の hogehoge
という部分がOBSやMicrosoft Teamsへ入力するストリームキーにあたります。
実行
諸々を構成したらサーバを実行します。
$ docker compose up
rtmp-server-nginx-1 | 2023/07/27 04:58:21 [notice] 1#1: using the "epoll" event method
rtmp-server-nginx-1 | 2023/07/27 04:58:21 [notice] 1#1: nginx/1.23.2
rtmp-server-nginx-1 | 2023/07/27 04:58:21 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
rtmp-server-nginx-1 | 2023/07/27 04:58:21 [notice] 1#1: OS: Linux 5.10.102.1-microsoft-standard-WSL2
rtmp-server-nginx-1 | 2023/07/27 04:58:21 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
rtmp-server-nginx-1 | 2023/07/27 04:58:21 [notice] 1#1: start worker processes
rtmp-server-nginx-1 | 2023/07/27 04:58:21 [notice] 1#1: start worker process 7
rtmp-server-nginx-1 | 2023/07/27 04:58:21 [notice] 1#1: start worker process 8
rtmp-server-nginx-1 | 2023/07/27 04:58:21 [notice] 1#1: start worker process 9
rtmp-server-nginx-1 | 2023/07/27 04:58:21 [notice] 1#1: start worker process 10
rtmp-server-nginx-1 | 2023/07/27 04:58:21 [notice] 1#1: start worker process 11
rtmp-server-nginx-1 | 2023/07/27 04:58:21 [notice] 1#1: start worker process 12
rtmp-server-nginx-1 | 2023/07/27 04:58:21 [notice] 1#1: start cache manager process 13
次にOBSの配信設定を行います。
名前 | 値 |
---|---|
サービス | カスタム |
サーバー | rtmps://rtmp.local.live.example.com/live |
ストリームキー | hogehoge |
OBSで配信を開始したら、最後にビデオプレイヤーを https://www.local.live.example.com/
から表示して、映像が再生されていればOKです。
まとめ
nginx + stream + rtmp の組み合わせを説明した記事が無さそうだなぁ、ということで今回まとめてみました。
nginx-rtmp-module を使うと「Microsoft 365 の認証基盤で認証したユーザのみが配信、視聴できる」みたいなことも簡単にできそうなので、いずれ試してみるかもしれません。