6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

nginxを使ってRTMPSとHLS再生を1コンテナで実現する(nginx + stream + rtmp)

Last updated at Posted at 2023-07-27

ある事情でRTMPによる映像配信基盤が必要になりそうになったため nginx-rtmp-module を使って自前で組んでいる際中のメモ書き的な記事になります。

実現内容

この記事で作る構成は以下の通りです。

nginx-rtmp.drawio (2).png

記事のタイトルの通り、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 を追加しました。

Dockerfile
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 を載せておきます。

docker-compose-certbot.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-challengesdns に設定しています。
また、ローカルでテストを行ったため名前の向き先は内部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 はこちら。

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"

ポート番号 801935 を公開していますが、テスト用なので本番デプロイ時は不要です。

nginx.conf

今回使用したnginxの設定ファイルはこちら。

nginx.conf
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の名前に基づいて 801935 ポートに振り分ける
  • rtmp
    • 1935 ポートで待ち受け
    • 映像を受信するとHLSファイルを保存
  • http
    • 80 ポートで待ち受け
    • HLSファイルとビデオプレイヤーを公開

ビデオプレイヤーHTML

HLSを再生するビデオプレイヤーのHTMLは以下の通りです。今回は Video.js を使用しました。

index.html
<!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の配信設定を行います。

image.png

名前
サービス カスタム
サーバー rtmps://rtmp.local.live.example.com/live
ストリームキー hogehoge

OBSで配信を開始したら、最後にビデオプレイヤーを https://www.local.live.example.com/ から表示して、映像が再生されていればOKです。

まとめ

nginx + stream + rtmp の組み合わせを説明した記事が無さそうだなぁ、ということで今回まとめてみました。
nginx-rtmp-module を使うと「Microsoft 365 の認証基盤で認証したユーザのみが配信、視聴できる」みたいなことも簡単にできそうなので、いずれ試してみるかもしれません。

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?