[UPDATE] AWS Elemental MediaLiveでVPC経由での出力ができるようになりました!

AWS Elemental MediaLiveでこれまでのVPC経由での入力に加えて、新たにVPC経由での出力が行えるようになりました。WebDAVが稼働しているEC2にプライベートIPでHLS出力を書き込み、動作を確認してみます。
2021.01.31

はじめに

清水です。本日お届けするアップデート情報はこちら、ブロードキャストグレードのライブ動画処理サービスであるAWS Elemental MediaLiveでVPC経由での出力ができるようになりました。(AWS Elemental MediaLive User GuideのDocument historyを確認すると2021/01/27付で更新がありました。)

AWS Elemental MediaLiveでは2019/03のアップデートでVPC経由での入力に対応していましたが、出力についてはVPCに未対応となっていました。

今回のアップデートにてVPC経由での出力に対応したことにより、例えばAmazon EC2インスタンスにプライベートIPを介して出力するといったことが可能になります。そのほかにも、VPCエンドポイント経由でアクセス元を限定したAmazon S3に出力をしたり、AWS Direct Connectを経由して接続先のオンプレミス環境に出力する、といったことなども想定されているようです。このVPC Outputを利用することで、VPC内にMediaLiveのChannelが使用するENIが作成されます。実際に使用するには、このENIに対し適切なネットワーク環境を設定する必要があります。(例えば、パブリックなのかプライベートなのか、NATゲートウェイやVPCエンドポイントなどが必要か否か、などなど。)User Guideには図解での解説もありますので、利用の際はこちらでまず詳細を確認すると良いかと思います。

本エントリではこのMediaLiveのVPC Output機能を使って、EC2上のプライベートIPにWebDAVとしてHLS出力を行います。確認のため、EC2インスタンにローカルからパブリックIPで接続し、HLS配信を視聴しました。

EC2上にMediaLiveが出力するWebDAVサーバを準備する

まず事前準備として、EC2上でWebDAVサーバを準備します。今回はNginxを使用しました。EC2はAmazon Linux 2のArm版、インスタンスタイプはt4g.largeとし、パブリックサブネットで起動します。NginxでWebDAVを動かすためのngx_http_dav_moduleモジュール、そしてnginx-dav-ext-moduleモジュールを組み込む必要があるため、Nginxをソースコードからビルドします。

Nginxのビルド

まずは事前に、ビルドの際に必要となりそうなモジュールをyumでインストールしておきます。

$ sudo yum install -y gcc pcre-devel zlib-devel openssl openssl-devel libxml2-devel libxslt-devel

作業ディレクトリ(/home/ec2-user/build/nginx)に移動して、Nginxのソースコードとnginx-dav-ext-moduleのコードをダウンロード、展開しておきます。

$ cd /home/ec2-user/build/nginx
$ wget https://nginx.org/download/nginx-1.19.6.tar.gz
$ tar xvf nginx-1.19.6.tar.gz
$ wget https://github.com/arut/nginx-dav-ext-module/archive/master.zip
$ unzip master.zip

nginx-dav-ext-moduleについては、master.zipをunzipすることでnginx-dav-ext-module-masterというディレクトリが作成されます(/home/ec2-user/build/nginx/nginx-dav-ext-module-master)。これをconfigureの際に--add-moduleで組み込みます。

$ cd /home/ec2-user/build/nginx/nginx-1.19.6
$ ./configure --with-http_dav_module --add-module=/home/ec2-user/build/nginx/nginx-dav-ext-module-master 2>&1 | tee log_configure.log

configureを実行すると最後にsummaryが出力されます。設定ファイルやバイナリファイルへのパスがまとまっているのでこちらは控えておくと良いでしょう。

Configuration summary
  + using system PCRE library
  + OpenSSL library is not used
  + using system zlib library

  nginx path prefix: "/usr/local/nginx"
  nginx binary file: "/usr/local/nginx/sbin/nginx"
  nginx modules path: "/usr/local/nginx/modules"
  nginx configuration prefix: "/usr/local/nginx/conf"
  nginx configuration file: "/usr/local/nginx/conf/nginx.conf"
  nginx pid file: "/usr/local/nginx/logs/nginx.pid"
  nginx error log file: "/usr/local/nginx/logs/error.log"
  nginx http access log file: "/usr/local/nginx/logs/access.log"
  nginx http client request body temporary files: "client_body_temp"
  nginx http proxy temporary files: "proxy_temp"
  nginx http fastcgi temporary files: "fastcgi_temp"
  nginx http uwsgi temporary files: "uwsgi_temp"
  nginx http scgi temporary files: "scgi_temp"

続いてmake、make installします。

$ make 2>&1 | tee log_make.log
$ sudo make install 2>&1 | tee log_make_install.log

Nginxの設定

Nginxのビルドとインストールが完了したら、続いて設定ファイルを編集します。個人的にファイル編集しやすいようにEmacsをyumでインストールしておきます。

$ sudo yum install -y emacs

ルートディレクトリは/var/www/dataとし、事前に作成しておきます。dataディレクトリは検証目的ということでパーミッションを777にしています。(実際には環境などにあわせて適切に設定が必要となる認識です。)

$ sudo mkdir /var/www
$ sudo mkdir /var/www/data
$ sudo chmod 777 /var/www/data

設定ファイル(nginx.conf)を編集します。ポート8080でWebDAVを稼働させるほか、同じディレクトリをポート80の通常のHTTPアクセスで参照できるようにしておきます。

$ sudo emacs /usr/local/nginx/conf/nginx.conf

編集前後を比較すると以下のようになります。実際の設定ファイル(nginx.conf)全体は本エントリの末尾に補足として記載しておきます。

$ diff /usr/local/nginx/conf/nginx.conf /usr/local/nginx/conf/nginx.conf.default
44,45c44
<             # root   html;
<           root /var/www/data;
---
>             root   html;
80,93d78
<     }
<
<     # WebDAV
<     server {
<         listen 8080;
<         client_max_body_size 1g;
<         location / {
<             root /var/www/data;
<             client_body_temp_path /var/www/client_temp;
<             dav_methods PUT DELETE MKCOL COPY MOVE;
<             create_full_put_path on;
<             dav_access group:rw all:r;
<           dav_ext_methods PROPFIND OPTIONS;
<         }

設定ファイル編集後、構文チェックをします。

$ sudo /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful

問題ないようですので、Nginxを起動します。

$ sudo /usr/local/nginx/sbin/nginx

Nginxの動作確認

動作確認として、ローカル環境からWebDAV経由での書き込み、HTTP GETでの参照を確認しておきます。事前にEC2にアタッチしているセキュリティーグループで、ローカル環境からの80、8080のポートを許可しておきます。

まずはWebDAV経由での書き込みです。

% curl -v -X PUT http://18.XXX.XXX.XXX:8080/test.txt --data-ascii "Hello, WebDAV"
*   Trying 18.XXX.XXX.XXX...
* TCP_NODELAY set
* Connected to 18.XXX.XXX.XXX (18.XXX.XXX.XXX) port 8080 (#0)
> PUT /test.txt HTTP/1.1
> Host: 18.XXX.XXX.XXX:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Length: 13
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 13 out of 13 bytes
< HTTP/1.1 201 Created
< Server: nginx/1.19.6
< Date: Sun, 31 Jan 2021 07:16:03 GMT
< Content-Length: 0
< Location: http://18.XXX.XXX.XXX:8080/test.txt
< Connection: keep-alive
<
* Connection #0 to host 18.XXX.XXX.XXX left intact
* Closing connection 0

続いて、WebDAVで書き込んだ内容が参照できるかの確認です。こちらは80番ポートへのアクセスになります。

% curl http://18.XXX.XXX.XXX/test.txt
Hello, WebDAV%

WebDAVでの書き込み、また80番ポートからの読み込みアクセスも問題なさそうです。続いて、本題のMediaLiveでのVPC Outputの確認に移ります。

AWS Elemental MediaLiveでVPC Outputを試してみる

VPC Outputの出力先となるEC2上のWebDAV環境が準備できました。それでは本題のMediaLiveのVPC Outputリソースを作成していきます。

MediaLive Inputリソースの作成

まずはInputの作成です。Input typeはRTMP (push)とします。Network modeでPublic/VPCの選択がありますが、ここはPublicとします。ここで選択するVPCはVPC Inputの機能で2019/03にアップデートされたものですね。([アップデート] AWS Elemental MediaLiveでVPCからの入力がサポートされていました | Developers.IO

Input security group、Input destinationsも設定します。Input classは今回は検証目的のためSINGLE_INPUTとしました。最後の[Create]ボタンでInputを作成します。

MediaLive Channelリソースの作成

続いてChannelを作成します。Channel nameを入力してIAMロールを選択、ここでIAMロールのアップデートが促されたら実施しておきます。(EC2に関する権限が増えていました、今回のUPDATEに関連するのかも、と推測しています。)

今回はChannel TemplateでLive event / HLSを選択しました。Channel classはSINGLE_PIPELINE、Input specificationsはデフォルトのまま、CDI input specificationsもデフォルト状態としておきます。

続いて、本題となるOutput deliveryの選択です。今回のアップデートで新たに追加された設定項目ですね。デフォルト状態では以下のようにPublicが選択されています。

ここでVPCを選択します。するとVPC settingsの項目が現れますので、VPCサブネット、セキュリティグループをそれぞれ選択します。サブネットはプライベートサブネットとなっているものを選択しました。またセキュリティグループについては、先ほどWebDAV機能を有効にしたNginxが稼働するEC2にポート8080でのアクセスができるよう設定したものを選択しています。EC2でのセキュリティグループ設定と同様ですが、サブネットの属するVPCに紐づいているセキュリティグループを選択する必要があります。EIPについても設定が可能なようですが、今回は未指定としました。

Inputについては先ほど作成したものをアタッチします。Output groupsの設定にて、HLS group destinationのURLにWebDAVの宛先を入力します。具体的には http://10.82.21.196:8080/hls としました。プライベートIPアドレスであることと、ポートを8080で指定している点がポイントです。またHLS settingsの項目、CDN Settingsは(デフォルトですが)HLS webdavとなっていることを確認しておきます。

なお、Channel TemplateでLive eventを選択しており、HLS outputsは4つのABRから構成されるのがデフォルトですが、検証目的ですので、1つのOutputのみ残し、ほか3つは削除しました。これで[Create channel]ボタンからChannelを作成します。

なお、本エントリでは検証のためChannel classをSINGLE_PIPELINEとしていますが、STANDARDとした場合は以下のようにサブネットを2つ、異なるAZで選択する必要があります。(Select two subnet in two different Availability Zones)

VPC OutputのENIの確認

Channel作成後にリソースについて確認してみると、Destinationsの項目、Egress endpointsがENIとなっていることが確認できます。

EC2のマネジメントコンソールでNetwork Interfaceの項目についても確認してみましょう。medialiveで検索するとENIが確認できます。プライベートIPとして10.82.31.146が割り当てられていること、パブリックIPは割り当てられていないことなどがわかりますね。

実際のライブ配信を視聴しての確認

それでは実際に映像を打ち上げ、VPC Output経由のライブ配信を視聴してみます。今回映像打ち上げにはiPhone6s上のZixi ONAIRを使用しました。

MediaLiveのChannelをスタートさせ、ライブエンコーダから映像を打ち上げたら、まずはEC2側で確認してみます。WebDAVのデータ書き込み先となるディレクトリを参照してみましょう。以下のようにm3u8ファイル、tsファイルがそれぞれ書き込まれているのがわかります。

$ ls -l /var/www/data/
合計 22072
-rw-rw-r-- 1 nobody nobody     200  1月 31 09:51 hls.m3u8
-rw-rw-r-- 1 nobody nobody     352  1月 31 09:52 hls_720p30.m3u8
-rw-rw-r-- 1 nobody nobody 2775444  1月 31 09:51 hls_720p30_00001.ts
-rw-rw-r-- 1 nobody nobody 2569772  1月 31 09:51 hls_720p30_00002.ts
-rw-rw-r-- 1 nobody nobody 2418996  1月 31 09:51 hls_720p30_00003.ts
-rw-rw-r-- 1 nobody nobody 2669036  1月 31 09:51 hls_720p30_00004.ts
-rw-rw-r-- 1 nobody nobody 2223664  1月 31 09:52 hls_720p30_00005.ts
-rw-rw-r-- 1 nobody nobody 2482540  1月 31 09:52 hls_720p30_00006.ts
-rw-rw-r-- 1 nobody nobody 2488180  1月 31 09:52 hls_720p30_00007.ts
-rw-rw-r-- 1 nobody nobody 2465244  1月 31 09:52 hls_720p30_00008.ts
-rw-rw-r-- 1 nobody nobody 2484984  1月 31 09:52 hls_720p30_00009.ts

Nginxのアクセスログも確認してみます。以下のようにMediaLiveのENIのプライベートIPである10.82.31.146から書き込みがあるのが確認できます。

$ cat /usr/local/nginx/logs/access.log
10.82.31.146 - - [31/Jan/2021:09:51:36 +0000] "PROPFIND / HTTP/1.1" 207 424 "-" "Elemental 2.18.3.706629"
10.82.31.146 - - [31/Jan/2021:09:51:36 +0000] "PUT /hls_720p30_00001.ts HTTP/1.1" 201 25 "-" "Elemental 2.18.3.706629"
10.82.31.146 - - [31/Jan/2021:09:51:42 +0000] "PUT /hls_720p30_00002.ts HTTP/1.1" 201 25 "-" "Elemental 2.18.3.706629"
10.82.31.146 - - [31/Jan/2021:09:51:48 +0000] "PUT /hls_720p30_00003.ts HTTP/1.1" 201 25 "-" "Elemental 2.18.3.706629"
10.82.31.146 - - [31/Jan/2021:09:51:48 +0000] "PUT /hls_720p30.m3u8 HTTP/1.1" 201 25 "-" "Elemental 2.18.3.706629"
10.82.31.146 - - [31/Jan/2021:09:51:49 +0000] "PROPFIND / HTTP/1.1" 207 424 "-" "Elemental 2.18.3.706629"
10.82.31.146 - - [31/Jan/2021:09:51:49 +0000] "PUT /hls.m3u8 HTTP/1.1" 201 25 "-" "Elemental 2.18.3.706629"
10.82.31.146 - - [31/Jan/2021:09:51:54 +0000] "PUT /hls_720p30_00004.ts HTTP/1.1" 201 25 "-" "Elemental 2.18.3.706629"
10.82.31.146 - - [31/Jan/2021:09:51:54 +0000] "PUT /hls_720p30.m3u8 HTTP/1.1" 204 25 "-" "Elemental 2.18.3.706629"
10.82.31.146 - - [31/Jan/2021:09:52:00 +0000] "PUT /hls_720p30_00005.ts HTTP/1.1" 201 25 "-" "Elemental 2.18.3.706629"
10.82.31.146 - - [31/Jan/2021:09:52:00 +0000] "PUT /hls_720p30.m3u8 HTTP/1.1" 204 25 "-" "Elemental 2.18.3.706629"
10.82.31.146 - - [31/Jan/2021:09:52:06 +0000] "PUT /hls_720p30_00006.ts HTTP/1.1" 201 25 "-" "Elemental 2.18.3.706629"
10.82.31.146 - - [31/Jan/2021:09:52:06 +0000] "PUT /hls_720p30.m3u8 HTTP/1.1" 204 25 "-" "Elemental 2.18.3.706629"
10.82.31.146 - - [31/Jan/2021:09:52:12 +0000] "PUT /hls_720p30_00007.ts HTTP/1.1" 201 25 "-" "Elemental 2.18.3.706629"
10.82.31.146 - - [31/Jan/2021:09:52:12 +0000] "PUT /hls_720p30.m3u8 HTTP/1.1" 204 25 "-" "Elemental 2.18.3.706629"
10.82.31.146 - - [31/Jan/2021:09:52:18 +0000] "PUT /hls_720p30_00008.ts HTTP/1.1" 201 25 "-" "Elemental 2.18.3.706629"
10.82.31.146 - - [31/Jan/2021:09:52:18 +0000] "PUT /hls_720p30.m3u8 HTTP/1.1" 204 25 "-" "Elemental 2.18.3.706629"
10.82.31.146 - - [31/Jan/2021:09:52:24 +0000] "PUT /hls_720p30_00009.ts HTTP/1.1" 201 25 "-" "Elemental 2.18.3.706629"
10.82.31.146 - - [31/Jan/2021:09:52:24 +0000] "PUT /hls_720p30.m3u8 HTTP/1.1" 204 25 "-" "Elemental 2.18.3.706629"

続いて、EC2に対してパブリックIPからのアクセスになりますが、視聴についても確認してみます。macOS上のSafariブラウザに、http://[EC2のパブリックIP]/hls.m3u8を入力します。

視聴できました!

まとめ

AWS Elemental MediaLiveの新機能、VPC Outputについて、WebDAVが稼働しているEC2にプライベートIPでHLS出力を書き込むことにより動作を確認してみました。MediaLiveではこれまでもInputについてはVPCに対応していましたが、今回OutputもVPC対応したことで、より柔軟なネットワーク構成がとれるようになったかと思います。プライベートIPでEC2やDirect Connect接続先に出力したい、という場合に活躍しそうですね。ただし柔軟になった反面、ネットワーク構成をきちんと把握しておく必要もあります。User GuideにもVPCなどAWSネットワークまわりに精通していること、と前提がありますね。計画的に利用していきたい機能かなと思いました。

補足

nginx.conf

本エントリの動作確認にあたり、実際に使用したNginx設定ファイル(nginx.conf)が下記になります。デフォルト状態の設定(nginx.conf.default)から書き換えた部分をハイライト表示しています。

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       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  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            # root   html;
	    root /var/www/data;
            index  index.html index.htm;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }

    # WebDAV
    server {
        listen 8080;
        client_max_body_size 1g;
        location / {
            root /var/www/data;
            client_body_temp_path /var/www/client_temp;
            dav_methods PUT DELETE MKCOL COPY MOVE;
            create_full_put_path on;
            dav_access group:rw all:r;
	    dav_ext_methods PROPFIND OPTIONS;
        }
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

}