2020年11月24日火曜日

Dockerコンテナ化させたHLSのwebストリーミング配信環境構築(さくらのクラウド,Centos, Nginx,FFmpeg,docker) | ライタス株式会社

こんにちわ。
10月より新入社員として入社しました田所です。

今回もストリーミングサーバーネタです!
今回は前回構築したffmpegとnginxを使ったHLSでwebストリーミング配信環境をDockerでコンテナにまとめて、 どこでも簡単に環境を構築できるようにしちゃいます(^^♪
前回の記事↓
https://blog.litus.co.jp/2020/11/hlsflashplayerwebcentos-nginxffmpeg.html

さて、今回もコンテナ化するにあたってアーキテクト図を書きます!
参考記事↓
https://blog.soushi.me/entry/2017/02/17/135834/

基本は前回のcentos上で構築したもののと同じにするのですが、下記の通りコンテナならではの構成に若干変更しました。
  • RTMP→HLS変換するコンテナとweb配信するコンテナで分割
  • 二つのコンテナをdocker-composeでまとめて起動、停止可能
  • hls配信に必要なm3u8ファイル等はdockerボリューム上に置き、コンテナ間で共有させる

なんだかモダンでかっこいい!!(笑)



今回はローカル環境にDockerを入れてローカル上で構築を確認できたら、まるっと本番に移します。
というわけで本日の作業は環境構築含め以下の内容で進めていきます
  • Docker for windowsをインストール(起動確認まで)
  • Dockerfile、docker-compose.yml等必要なファイルを作成
  • ローカルでイメージをビルド→コンテナ起動→確認
  • さくらのクラウド(CentOS)上で起動→確認


1.Docker for windowsをインストール

※Windows10 Pro 64bitでのDockerインストールした手順について記述しています。
参考記事↓
https://www.public.ne.jp/2020/06/02/%E3%80%90docker%E3%80%91%E7%AC%AC9%E5%9B%9E%E3%80%80windows-10-pro-%E3%81%B8-docker-desktop-for-windows-%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8B/
https://docs.docker.jp/docker-for-windows/install.html


Hyper-Vを有効化


[コントロール パネル] > [プログラムと機能] > [Windowsの機能の有効化または無効化] > [Hyper-V]にチェック
変更後に再起動が必要

Docker Desktop for Windowsをインストール


↓Docker Hubにインストーラーがあるので「Get Docker」をクリックしてダウンロード
https://hub.docker.com/editions/community/docker-ce-desktop-windows/
ダウンロードが完了したら実行し、ポチポチと押して進めていきます(※基本OKを押してインストールまで終わらして問題ないので、割愛)
完了したら再起動します
再起動後は自動でDockerが起動する(はず)のでタスクトレイからDockerが起動していることを確認する。

起動確認


コマンドプロンプト(またはpowershell)でDockerとdocker-composeバージョン確認
> docker version
Client: Docker Engine - Community
 Cloud integration: 1.0.2
 Version:           19.03.13
 API version:       1.40
 Go version:        go1.13.15
 Git commit:        4484c46d9d
 Built:             Wed Sep 16 17:00:27 2020
 OS/Arch:           windows/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.13
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       4484c46d9d
  Built:            Wed Sep 16 17:07:04 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.3.7
  GitCommit:        8fba4e9a7d01810a393d5d25a3621dc101981175
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683
docker-compose の確認
> docker-compose --version
 docker-compose version 1.27.4, build 40524192
完璧です!
お試しでHello Worldを出力するだけのコンテナを実行してみます!
> docker run --rm hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/
※--rm オプションで実行後にコンテナを自動で削除するようにしています。
上記のようなメッセージが出力されたら成功です。
ついでに「docker run -it ubuntu bashでubuntuコンテナ起動してみなはれ。」って言っているのでやってやりましょう(笑)
> docker run -it --rm ubuntu bash
root@703f8d0cae30:/#    
※ここも--rmつけて停止後にコンテナを破棄するようにしています。
起動したubuntuコンテナ内にbashで操作できるようになりました。
一応OSの確認。
root@703f8d0cae30:/# cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.1 LTS"
「exit」コマンド or 「Ctrl + D」でコンテナから抜け出せます。
セットアップ完了です!
超簡単にDockerを構築できました!



2.Dockerfile、docker-compose.yml等必要なファイルを作成

ファイル構成は以下の通りとしました。
streamserver/
├─ rtmp/
|  ├─ Dockerfile
|  └─ nginx.conf
├─ web/
|  ├─ Dockerfile
|  ├─ index.html
|  └─ nginx.conf
└─ docker-compose.yml

各ファイル内容は以下の通りです。
  • streamserver/docker-compose.yml
  • version: "3"
    services:
      rtmp_ffmpg:
        build:
          context: ./rtmp
        volumes:
          - hls:/var/www/vhosts/live_stream
        ports:
          - 1935:1935
      web:
        build:
          context: ./web
        volumes:
          - hls:/var/www/vhosts/live_stream
          - ./web/index.html:/usr/share/nginx/html/index.html
        depends_on:
          - rtmp_ffmpg
        ports:
          - 80:80
    volumes:
      hls:
    
    ポイントはvolumesでhlsというdockerボリュームを作成し、rtmp_ffmpgとwebコンテナで共有している点です。
    このhlsボリュームにffmpegがコンバートしたhlsファイルが置かれるようにします

  • streamserver/rtmp/Dockerfile
  • FROM tiangolo/nginx-rtmp:latest
    COPY nginx.conf /etc/nginx/nginx.conf
    RUN apt-get -y update \
      && apt install -y ffmpeg \
      && mkdir -p /var/www/vhosts/live_stream \
      && chmod -R o+rwx /var
    
    tiangolo/nginx-rtmpというイメージを拝借しています。
    このイメージ単体で初回構築したときのような RTMPでのストリーミング配信が可能です。
    これに更にffmpegを追加インストールし、rtmp/nginx.confをコンテナ内にコピーしています。
    ちみなに
    && mkdir -p /var/www/vhosts/live_stream \
    && chmod -R o+rwx /var
    
    この部分はffmpegがhlsファイルを吐き出すディレクトリを作成しています。更にパーミッションを変更しています。
    ※パーミッションが変更されていないとコンテナ起動時にffmpegにPermission Deniedと怒られました。
    docker-compose.ymlにコンテナ間で共有するディレクトリとして指定しているのが原因と思われます。

  • streamserver/rtmp/nginx.conf
  • worker_processes auto;
    rtmp_auto_push on;
    events {}
    rtmp {
       server {
         listen 1935;
         allow play all;
         access_log /var/log/nginx/rtmp_access.log;
         application live1 {
         live on;
         exec ffmpeg -i rtmp://localhost/live1/$name -async 1 -vsync cfr -acodec copy -c:v libx264 -b:v 128K -f flv rtmp://localhost/live2/$name_low;
       }
       application live2 {
         live on;
         hls on;
         hls_path /var/www/vhosts/live_stream;
         hls_variant _low  BANDWIDTH=300000;
       }
      }
    }    
    
    1935ポートでRTMPを受け付けて、ffmpegが/var/www/vhosts/live_streamにhlsファイルを吐き出す旨を書いています。


  • streamserver/web/Dockerfile
  • FROM nginx:latest
    
    RUN apt-get -y update \
      && mkdir -p /var/www/vhosts/live_stream \
      && chmod -R 777 /var
    COPY nginx.conf /etc/nginx/nginx.conf
    COPY index.html /usr/share/nginx/html/index.html  
    
    webサーバーのnginxコンテナイメージを使用しています
    コンテナ間の共有ディレクトリとして/var/www/vhosts/live_streamを作成し、
    nginx.confとindex.htmlをコンテナ内にコピーさせています。


  • streamserver/web/index.html

  • <html>
      <head>
        <title>HLS Test</title>
        <link href="https://vjs.zencdn.net/7.4.1/video-js.css" rel="stylesheet">
      </head>

      <body>
        <video-js id=example-video width=640 height=360 class="vjs-default-skin" controls>
          <source src="http://「IPアドレス」/live_stream/「OBSストリームキー」_low.m3u8" type="application/x-mpegURL">
        </video-js>
        <script src="https://vjs.zencdn.net/7.4.1/video.js"></script>
        <script>
          var player = videojs('example-video');
        </script>
      </body>
    </html>

    「IPアドレス」と「OBSストリームキー」は各自で書き換えてください。
    ローカル上で動きを確認する場合はIPアドレスは「localhost」でよろしいかと思います。


  • streamserver/web/nginx.conf
  • user  nginx;
    worker_processes  2;
    
    error_log  /var/log/nginx/error.log warn;
    pid        /var/run/nginx.pid;
    
    
    events {
        worker_connections  1024;
    }
    
    http {
      include mime.types;
      default_type application/octet-stream;
      sendfile on;
      server {
        listen 80;
        server_name  localhost;
        location / {
        root /usr/share/nginx/html/;
        index index.html index.htm;
        }
        location /live_stream {
        types {
            application/vnd.apple.mpegurl m3u8;
        }
        root /var/www/vhosts/;
        }
      }
    }
    
    webサーバーとして80番ポートで受け付けています。

    準備が整いました!!


    3.ローカルでイメージをビルド→コンテナ起動→確認

    とりあえずローカル上での挙動確認のため、obsの配信設定はlocalhostに配信するように設定を変更しておきます。
    さっそくイメージをビルドします。
    docker-compose.ymlがあるstreamserverディレクトリまでpowershellやコマンドプロンプトで移動し、以下のコマンドを実行します。
    > docker-compose build
    
    時間がかかるのでしばらく待ちます。
    終了したらイメージ一覧を確認します
     > docker-compose images
    REPOSITORY                     TAG                 IMAGE ID            CREATED             SIZE
    streamserver_nginx             latest              ef56cc44a48e        34 minutes ago      156MB
    streamserver_rtmp_ffmpg        latest              f373c5e5f450        47 minutes ago      1.06GB
    nginx                          latest              daee903b4e43        5 days ago          133MB
    tiangolo/nginx-rtmp            latest              e2efd1d48936        3 months ago        850MB
    
    4つイメージがあれば成功です!!
    ではビルドしたイメージを起動します!
    docker-compose.ymlがあるstreamserverディレクトリで以下を実行
    > docker-compose up -d
    Creating network "streamserver_default" with the default driver
    Creating streamserver_rtmp_ffmpg_1 ... done
    Creating streamserver_nginx_1      ... done 
    
    起動しているか確認します
    > docker ps
    CONTAINER ID        IMAGE                     COMMAND                  CREATED              STATUS              PORTS                    NAMES
    741416b21ea2        streamserver_nginx        "/docker-entrypoint.…"   About a minute ago   Up About a minute   0.0.0.0:80->80/tcp       streamserver_nginx_1
    83d8673c3580        streamserver_rtmp_ffmpg   "nginx -g 'daemon of…"   About a minute ago   Up About a minute   0.0.0.0:1935->1935/tcp   streamserver_rtmp_ffmpg_1
    
    STATUSが2つともUPとなっているので問題なさそうです!
    まずはwebサーバーが起動しているか確認します
    ブラウザからlocalhostにアクセスします。
    問題なさそうです!
    続いてOBSでlocalhostに向けて配信を開始します!
    上手くいってそうです!!
    再度localhostにアクセスすると...

    きたー!!!!(^_-)-☆

    問題なく見れています!
    コンテナをまとめて終了させる際は
    > docker-compose down
    
    でコンテナが破棄されます。


    4.さくらのクラウド(CentOS)上で起動→確認

    環境は前回までで構築したインスタンスをそのまま使います。
    作業としてはsshでサーバーへ接続してdockerとdocker-composeをインストールするだけですね
    sshで接続するまでの工程は割愛します(詳細は前々回の記事参照)
    参考記事↓
    https://qiita.com/subretu/items/549bc720165004bca3c3
    以下のコマンドを実行し、インストールする。
    #dockerインストール
    yum install -y yum-utils device-mapper-persistent-data lvm2
    yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    yum -y install docker-ce
    #Docker Composeインストール
    curl -L https://github.com/docker/compose/releases/download/1.27.4/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
    chmod +x /usr/local/bin/docker-compose
    
    これでエラーがなければバージョンが表示されるはずです
    docker --version
    docker-compose --version
    
    続いてローカルで作ったstreamserverディレクトリをサーバーの中へコピーします
    teratermであればフォルダをzipとかに圧縮してドラッグ&ドロップします
    SCPを選んでコピー先を指定します。(今回はホームディレクトリ(~))
    解凍
    > cd ~
    > ls
    streamServer.zip
    > unzip streamServer.zip
    > ls
    streamServer streamServer.zip
    
    ※web/index.htmlの「IPアドレス」、「OBSストリームキー」は適宜vimやnanoを使って変更してください
    ※OBS設定で配信先をサーバーURLに変更しておいてください
    では、本題!!
    解凍したディレクトリへ入って起動させます!
    > cd ~/streamServer
    > docker-compose up -d
    Creating network "streamserver_default" with the default driver
    Creating streamserver_rtmp_ffmpg_1 ... done
    Creating streamserver_nginx_1      ... done 
    
    2つのコンテナが起動し、Doneと出力されたら完了です

    ブラウザでサーバーへアクセス
    webサーバーコンテナは無事動いています。
    ではOBSで配信を行います!
    やったーーーーー!!!
    これで無事Dockerで環境構築できました!



    今後の課題、目標等

    • 前回に引き続き遅延はかなりのあるので最小限にできるよう改善する
    • docker-composeではなく、ECS、EKS、kubernetes等のコンテナオーケストレーションツールで実装してみる

    これからも精進いたします!!😉
    それでは。

    2020年11月9日月曜日

    HLSでFlashPlayerを使用しないwebストリーミング配信環境の構築(さくらのクラウド,Centos, Nginx,FFmpeg) | ライタス株式会社

    こんにちわ。
    10月より新入社員として入社しました田所です。

    前回記事にしました、さくらのクラウド上で構築したストリーミングサーバーを更に進化させていきたい思います!!
    前回のストリーミングサーバーの構築記事↓
    https://blog.litus.co.jp/2020/10/centosnginx.html


    前回構築したストーリングサーバーでは再生するにはFlashPlayerが必要でした。
    FlashPlayerは2020年にサポート終了することもありますし、Chromeでは再生するにはいちいち許可したりする必要があり、なるだけ使用は避けたい、というところで終了しました。
    なので今回はこのRTMPを使用したストリーミングサーバーを生かしつつ、FlashPlayerを使用しないでストリーミング配信できる環境に進化させます!
    具体的にはHLS (HTTP Live Streaming)というプロトコルを使用します。
    ↓参考記事
    https://engineer.dena.com/posts/2018.12/knowledge-for-livestreaming
    .m3u8っていう拡張子のデータに映像を記録したプレイリストがはいってるわけですね。
    なるほど、なるほど。この.m3u8の配信映像データがあれば、いい感じにwebで動画を再生できるというわけ
    例のごとく前例はもちろんたくさんありました。
    FFmpegというライブラリを使います。
    こいつをNginxと組み合わせることで PCからRTMP配信→Nginx経由でFFmpegに.m3u8のデータを生成してもらう→.m3u8データを再生するJavaScriptコードが入ったHTMLを配置(Nginxのwebサーバー)
    って感じです。
    というわけで今回もイメージを作成↓
    基本は前回のさくらのクラウドの環境をそのまま使って
    • FFmpegをインストール
    • nginx.conf書き換え(FFmpegを実行する旨等記載)
    • htmlファイルの書き換え
    これらの追加作業をするだけです。

    今回の参考にさせていただいた記事
    https://qiita.com/khagi/items/b99f5a36846d9ab65daa
    https://qiita.com/nabeyaki/items/9ddece8231af8e691c3a

    さっそく行きましょう!!


    1.FFmpegをインストール

    前回の記事の通りさくらクラウドのサーバーにSSHで入った所から始めます


    FFmpegをダウンロード
    FFmpeg Static Builds
    cd /usr/local/share/
    wget https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-arm64-static.tar.xz
    tar Jxf ffmpeg-git-arm64-static.tar.xz
    

    ffmpegをインストール
    ln -s /usr/local/share/ffmpeg-git-arm64-static.tar.xz/ffmpeg /usr/bin/ffmpeg
    
    シンボリックリンクを作成して、コマンドで実行できるようにしている

    ffmpeg確認
    ffmpeg -version
    
    正常にインストールされていればバージョン情報がみれる
    これでFFmpegのインストールは終了です

    2.nginx.conf書き換え

    vimで編集
    vim /etc/nginx/nginx.conf
    
    下記のように設定しました。
    rtmp {
       server {
         listen 1935;
         allow play all;
         access_log /var/log/nginx/rtmp_access.log;
         application live1 {
         live on;
         exec ffmpeg -i rtmp://localhost/live1/$name -async 1 -vsync cfr -acodec copy -c:v libx264 -b:v 128K -f flv rtmp://localhost/live2/$name_low;
       }
       application live2 {
         live on;
         hls on;
         hls_path /var/www/vhosts/live_stream;
         hls_variant _low  BANDWIDTH=300000;
       }
    }
    
    http {
        include mime.types;
        default_type application/octet-stream;
        sendfile on;
        server {
            listen 80;
            server_name  localhost;
            location / {
            root html;
            index index.html index.htm;
            }
            location /live_stream {
            types {
                application/vnd.apple.mpegurl m3u8;
            }
            root /var/www/vhosts/;
            }
        }
    }
    
    
    rtmpのlive2内の
    hls_path /var/www/vhosts/live_stream;
    
    このディレクトリ内にFFmpegが頑張ってコンバートしたデータが入ってくれます。 なのでこのディレクトリがない場合は作成しておきます。
    mkdir -p /var/www/vhosts/live_stream
    
    これでnginxの設定は終了です。

    Nginx起動
    /usr/sbin/nginx
    
    この時点でOBSで配信を開始すれば/var/www/vhosts/live_streamにFFmpegが作った.m3u8等が生成されていることを確認します。
    OBSの設定は前回の記事のままで問題ありません。
    「配信開始」を押します。
    エラー警告もなく、右下が緑になっていればサーバーに接続ができています。
    ではサーバー側で/var/www/vhosts/live_streamをのぞいてみます。
    [root@ホスト名 ~]# ls /var/www/vhosts/live_stream/
    test.m3u8  test_low-28.ts  test_low-29.ts  test_low-30.ts  test_low-31.ts  test_low.m3u8
    
    無事、hls配信に必要なデータが生成されています。
    ※ちなみにファイル名の「test」の部分はOBSの配信設定のストリームキーの値です。

    3.htmlファイルの書き換え

    headタグ

    <link href="http s://vjs.zencdn.net/7.4.1/video-js.css" rel="stylesheet"></link>

    bodyタグ

    <source src="http://サーバーグローバルIPアドレス/live_stream/test_low.m3u8" type="application/x-mpegURL"></source>
    </video-js>
    <script src="https://vjs.zencdn.net/7.4.1/video.js"></script>
    <script>
      var player = videojs('example-video');
    </script>



    ※「サーバーグローバルIPアドレス」、「test_low.m3u8」の部分は自身の環境に沿って書き換えてください
    これでwebからアクセスすればみれるはずです。

    完璧!!!

    再生ボタンをワンポチで動画が見れます(^^



    が、遅延がヤバイ(笑)

    30秒くらいの遅延が確認できました(汗)


    症状の原因に下記の可能性があるかと思います。
    • FFmpeg自体が重たい
      topコマンド実行したら
      FFmpegさんのCPU使用率120%越えでむせび泣いてました(笑)
      →他に軽いソフトがあれば検討
    • ひとつのサーバーでNginxがRTMPとWeb両方を担っているのがキツい

      →RTMPとWebの役割を分けて行うと改善するかも??
    • サーバースペック的にストリーミング配信自体がきつい

      現在のさくらのクラウドサーバーのスペックは 仮想コア: 2, メモリ: 4GB
      →スケールアップで改善するかも??
    • 設定が悪い(OBS,Nginx,FFmpeg)
      →OBSのビットレートやキーフレーム,FFmpegの実行コマンドのパラメーター等を変えると改善するかも??
    等が考えられそうです。

    次回は色んな方法を模索し負荷を減らして遅延の少ない配信を目指します


    後Dockerコンテナ化してどこでも簡単に使いまわせるようにしてみたいと思います
    これからも精進いたします!!😉
    それでは。