2020/03/02(Mon)Vagrant / CentOS 8 / gcc 9 (Visual Studio からの remote build/debug) / apache / ffmpeg / nginx-rtmp-module 環境構築メモ

はてブ数 2020/02/29 10:05 つーさ

自分のためのメモ

「ffmpegを呼び出してライブストリームをごにょごにょして独自形式のコンテナにMuxしてhttpでストリーミングするアプリケーション」を作りたくなったので、 Visual Studio で、C++ コンソールアプリケーション(Linux) としてプロジェクトを作って、CentOS 8上でリモートビルドしてApache に載せてCGIとして動かしてみるために、 vagrant で VMを用意して、CentOS 8 上に、g++ と apache と ffmpeg を準備します。

WSLは、そのうち……(ずっと、そのうち……って言ってる気がする)

install

  • VirtualBox 入れる VirtualBox 6系はHyper-V上でも動くとか?
  • VirtualBox The Extension Pack 入れる (注意: VirtualBox Personal Use and Evaluation License)
  • Vagrant 入れる
  • Vagrant のプラグインを入れる
vagrant plugin install vagrant-proxyconf # いろんなコマンドのproxy設定を横断的にやってくれるやつ。 proxy環境下では便利。
vagrant plugin install vagrant-vbguest #guest additions を自動で更新してくれるやつ

ゲストOSを準備する

mkdir vagrant/centos8
cd vagrant/centos8
vagrant init

Vagrantfile を適当に編集する。

#Vagrantfile
Vagrant.configure("2") do |config|

  config.vm.box = "centos/8"

  config.vm.network "forwarded_port", guest: 80, host: 8080
  config.vm.network "forwarded_port", guest: 443, host: 8443

  config.vm.provider "virtualbox" do |vb|
    vb.cpus = 12 #morimori
    vb.memory = 4096
  end

  # Enable provisioning with a shell script.
  config.vm.provision "shell", inline: <<-SHELL
    echo ---- start provisioning ----
    dnf -y update
    timedatectl set-timezone Asia/Tokyo   # Timezone を Asia/Tokyo に
    echo ----  end  provisioning ----
  SHELL
 
  if Vagrant.has_plugin?("vagrant-proxyconf")
    config.proxy.enabled  = false
    config.proxy.http     = "http://hogehoge:8080"
    config.proxy.https    = "http://hogehoge:8080"
    config.proxy.no_proxy = "localhost,127.0.0.1"
  end
end

vagrant upすると、仮想マシンインスタンスが作成され立ち上がる。 up 中に Guest Additions が入らないとかいうエラーが出るのは Kernelが古いかららしい。

一度 dnf -y update すればいいらしいので、Vagrantfile の provision に書いてみたけど実行される前にエラーで止まるせい(?)で実行されてないっぽい。
マシンがあがった状態でもう一回 vagrant up すると provision されてカーネル他が更新される。
この状態で vagrant reload で、マシンを再起動すれば、先の vagrant-vbguest が Guest Additions を入れてくれる。

マシンの電源を切るには、vagrant halt

vagrant up
vagrant up
vagrant reload

gcc 9 を公式リポから入れる。

Visual Studio からのリモートビルドするには、ssh できて g++ gdb gdbserver が動けばいいんだとか。

gcc 8系とgcc 9系が利用できるらしいので、今回は 9系を準備してみる。

  sudo su # rootになる。
  dnf -y install gcc-toolset-9 gcc-toolset-9-gdb-gdbserver
  echo -e '#!/bin/bash\\nsource scl_source enable gcc-toolset-9' > /etc/profile.d/enable-gcc-toolset-9.sh #デフォルトで gcc 9 が使われるようにする

ログインしなおして、 which gcc/opt/rh/gcc-toolset-9/root/usr/bin/gcc に向いてれば成功。

vagrant up でエラーが出たり、vagrant halt でシャットダウンできなくなったりする。

ログイン時に自動的に scl_source するようにすると、sudoが壊れる(語弊がある)せい。

結論から言えば、

sudo mv /opt/rh/gcc-toolset-9/root/usr/bin/sudo{,.bkup}

で解決する。

トラブルシュート

$ vagrant up

... (略) ...

==> default: Checking for guest additions in VM...
The following SSH command responded with a non-zero exit status.
Vagrant assumes that this means the command failed!

 setup

Stdout from the command:

Stderr from the command:

$ vagrant halt
==> default: Attempting graceful shutdown of VM...
The following SSH command responded with a non-zero exit status.
Vagrant assumes that this means the command failed!

shutdown -h now

Stdout from the command:

Stderr from the command:

デバッグレベルのログを見る。参考: vagrant upでエラー 「command failed! setup」

VAGRANT_LOG=debug vagrant up 2> >(tee up.log)

流れる文字を眺めてると command not found なんて文字が見えた気がしたので、less して検索する。

シェルスクリプトの実行時に line 2: -E: command not found とか出ているので、vagrant "-E: command not found"でググると Vagrant halt and up produces errors on Centos 6.7がヒットして、Centos 6.7 broken Sudoへのリンクが張ってあって、はー、なるほどという感じ。

$ which sudo
/opt/rh/gcc-toolset-9/root/usr/bin/sudo

/opt/rh/gcc-toolset-9/root/usr/bin/sudo/usr/bin/sudo への wrapper script で、wrapするとき自動的に -E をつけてくれちゃうせいで、むしろ明示的に -E をつけた sudo -E whoami などというコマンドが sudo -E ... -E whoami に展開されて、 -E なんてコマンドないよ というエラーが出てると。

うーん、個人的には、このような sudo は要らないので、剥がす。

sudo mv /opt/rh/gcc-toolset-9/root/usr/bin/sudo{,.bkup} # 先のコマンド

と、uphalt でエラーが出なくなる。

Visual Studio で、コンソールアプリケーション(Linux) を作って、リモートビルド・リモートでバッグができるか確認する。

Visual Studio からは、vagrant@localhost:2222 に 秘密鍵 .vagrant/machines/default/virtualbox/private_key で接続する。

一回ビルドすれば、ヘッダファイルがVisual Studio側にコピーされてIntelliSenseが効くようになるし、リモートデバッグ時にはブレークポイント張ればブレークするし、ウォッチはSTLコンテナの中身までちゃんと見える。おお、これは結構いいのでは。

Apache httpd を 公式リポから入れる

dnfで入れます。最新版を使うならソースからだけど、さしあたり手元でCGIを動かしてみるだけなので

sudo dnf install httpd httpd-devel httpd-filesystem httpd-tools
  • httpd: ほんたい
  • httpd-devel: モジュールをソースからビルドするのとかに使う。
  • httpd-filesytem: 基本的なApacheのディレクトリ構造を提供してくれる。
  • httpd-tools: ab とか htdbm とか htpasswd とかが入ってる。

自動的に、 apacheユーザ と apacheグループ が作られる。

sudo systemctl start httpd
sudo systemctl status httpd

Active: active (running) になってれば。 Vagrantfile で、 Host 8080 -> Guest 80 の設定をしてあるので、ホストOSから http://localhost:8080/ にアクセスすれば、スタートページが見えるはず……。

ファイヤーウォールはデフォルトはOFFっぽい。使う場合に設定すればいいだろう。

  sudo systemctl status firewalld

httpdの設定は /etc/httpd/conf/httpd.conf にあるので、これを書き換えていく。

ここでは開発環境が欲しいだけなので、/var/www/html 下を AllowOverride All して、.htaccess おくでもいいかな。

  sudo systemctl reload httpd

CGI の動作テスト

testを掘って、 .htaccess と index.cgi を置く。

sudo mkdir /var/www/html/test #testを掘る
sudo chown vagrant /var/www/html/test
cd /var/www/html/test

cat << __HTACCESS__ > .htaccess
DirectoryIndex index.cgi
Options +ExecCGI
AddHandler cgi-script cgi
__HTACCESS__

cat << __INDEXCGI__ > index.cgi
#!/usr/bin/bash
echo 'Status: 200 OK'
echo 'Content-Type: text/plain'
echo ''
cat << '__HTML__'
It wooooks.
__INDEXCGI__

chmod 755 index.cgi

ブラウザからアクセスしてみると、Internal Server Error. が出る。

sudo tail /var/log/httpd/error_log すると、Permission deniedが出ている。あ、SELinuxか。

sudo setenforce 0

で、It wooooks が出れば、CGIの実行もOK。

C++で作ったCGIも、一応試しておく。

cat << __INDEXCGI__ > index.cpp

// index.cpp
#include <cstdio>
int main()
{
  puts("Status: 200 OK");
  puts("Content-Type: text/plain");
  puts("");
  puts("C++ CGI also wooooks.");
  return 0;
}

__INDEXCGI__
g++ -o index.cgi index.cpp

ffmpeg をビルドする

ffmpeg は、 Nux Dextop のリポジトリから入れればいいよという記事がいっぱいあるけど、 ffmpeg 2系が降ってきちゃう? 最新版を使いたいのでソースから入れる。

https://trac.ffmpeg.org/wiki/CompilationGuide/Centosにインストールマニュアルがあるので、これに従えばよい。

けど、マニュアルに書いてあるのは、ユーザのホームディレクトリ以下にインストールする方法(root権限不要)であって(まぁこっちの方が需要あるのはわかるけど)、今回やりたいのは素直な /usr/local へのインストールなので、コマンドを書き換えていく。

# 作業ディレクトリ。
mkdir ~/ffmpeg_sources

Get the Dependencies

sudo dnf install autoconf automake bzip2 bzip2-devel cmake freetype-devel gcc gcc-c++ git libtool make mercurial pkgconfig zlib-devel

g++ とか libtool は、gcc-toolset-9 入れてるので要らないんだけど。

NASM

cd ~/ffmpeg_sources &&
curl -O -L https://www.nasm.us/pub/nasm/releasebuilds/2.14.02/nasm-2.14.02.tar.bz2 &&
tar xjvf nasm-2.14.02.tar.bz2 &&
cd nasm-2.14.02 &&
./autogen.sh &&
./configure &&
make -j &&
sudo make install

Yasm

cd ~/ffmpeg_sources &&
curl -O -L https://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz &&
tar xzvf yasm-1.3.0.tar.gz &&
cd yasm-1.3.0 &&
./configure &&
make -j &&
sudo make install

libx264

cd ~/ffmpeg_sources &&
git clone --depth 1 https://code.videolan.org/videolan/x264.git &&
cd x264 &&
./configure --enable-static &&
make -j &&
sudo make install

libx265

cd ~/ffmpeg_sources &&
hg clone https://bitbucket.org/multicoreware/x265 &&
cd ~/ffmpeg_sources/x265/build/linux &&
cmake -G "Unix Makefiles" ../../source &&
make -j &&
sudo make install

libfdk_aac

cd ~/ffmpeg_sources &&
git clone --depth 1 https://github.com/mstorsjo/fdk-aac &&
cd fdk-aac &&
autoreconf -fiv &&
./configure &&
make -j &&
sudo make install

libmp3lame

cd ~/ffmpeg_sources &&
curl -O -L https://downloads.sourceforge.net/project/lame/lame/3.100/lame-3.100.tar.gz &&
tar xzvf lame-3.100.tar.gz &&
cd lame-3.100 &&
./configure --enable-nasm &&
make -j &&
sudo make install

libopus

cd ~/ffmpeg_sources &&
curl -O -L https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz &&
tar xzvf opus-1.3.1.tar.gz &&
cd opus-1.3.1 &&
./configure &&
make -j &&
sudo make install

libvpx

cd ~/ffmpeg_sources &&
git clone --depth 1 https://chromium.googlesource.com/webm/libvpx.git &&
cd libvpx &&
./configure --disable-examples --disable-unit-tests --enable-vp9-highbitdepth --as=yasm --enable-pic &&
make -j &&
sudo make install

FFmpeg

cd ~/ffmpeg_sources &&
curl -O -L https://ffmpeg.org/releases/ffmpeg-snapshot.tar.bz2 &&
tar xjvf ffmpeg-snapshot.tar.bz2 &&
cd ffmpeg &&
PKG_CONFIG_PATH="/usr/local/lib/pkgconfig" ./configure \\
  --pkg-config-flags="--static" \\
  --extra-libs=-lpthread \\
  --extra-libs=-lm \\
  --enable-gpl \\
  --enable-libfdk_aac \\
  --enable-libfreetype \\
  --enable-libmp3lame \\
  --enable-libopus \\
  --enable-libvpx \\
  --enable-libx264 \\
  --enable-libx265 \\
  --enable-nonfree &&
make -j &&
sudo make install

このままだと、ffmpeg: error while loading shared libraries: ... と言われる。
/usr/local/lib にも .so を探しに行く設定をする。

sudo echo '/usr/local/lib' | sudo tee /etc/ld.so.conf.d/usr-local-lib.conf
sudo ldconfig
ffmpeg -codecs # 動くか確認

nginx

http://nginx.org/en/linux_packages.html に書いてあるとおりにやる。

# nginx.repo を追加する。
sudo tee  /etc/yum.repos.d/nginx.repo << '__NGINX_REPO__'

[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

__NGINX_REPO__

# 新しいバージョンを使いたいので mainline repository を有効にする。
sudo yum-config-manager --enable nginx-mainline

# インストール
sudo dnf install nginx

# 起動
sudo systemctl starat nginx

例によって http://localhost:8080/ で見えればOK。設定ファイルは /etc/nginx/nginx.conf

RTMPモジュールを入れてみる

モジュールを入れるにはソースからコンパイルする必要がある? nginx -V すると configure のときのオプションが見える。

nginx -V 

自分でコンパイルするなら、/usr/local に入れた方がいい気もするし、path系のオプションを除いてコンパイルしてみよ。

mkdir nginx-source
cd nginx-source

sudo dnf install openssl-devel pcre-devel

curl -O -L https://nginx.org/download/nginx-1.17.8.tar.gz && tar xf nginx-1.17.8.tar.gz
git clone https://github.com/sergey-dryabzhinsky/nginx-rtmp-module.git
cd nginx-1.17.8

./configure --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-cc-opt='-O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector-strong -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -pie' \\
--add-module=../nginx-rtmp-module &&
make -j &&
sudo make install

もともとの ./configure オプションにあったパス回りの設定

--prefix=/etc/nginx
--sbin-path=/usr/sbin/nginx
--modules-path=/usr/lib64/nginx/modules
--conf-path=/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 

デフォルトでは↓こうなるみたい。

  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"

そのまま systemctl を使うには、 /lib/systemd/system/nginx.service をカスタムしてどうか。ユニットファイルのカスタムは /etc/systemd/system/ にコピーして行うのがお作法

sudo cp /lib/systemd/system/nginx.service /etc/systemd/system/

書き換え後:

PIDFile=/usr/local/nginx/logs/nginx.pid
ExecStart=/usr/local/nginx/sbin/nginx
sudo systemctl start nginx
sudo systemctl status nginx

ブラウザからアクセスして見えてればOKっと。

# パッケージの方の nginx は紛らわしいので消しておく
sudo dnf remove nginx

RTMP

Vagrantfile に RTMP の port-forwarding を書き足す。vagrant reloadで再起動。

  config.vm.network "forwarded_port", guest: 1935, host: 1935

/usr/local/nginx/conf/nginx.conf に RTMP モジュールの設定をする。

rtmp {
    server {
        listen 1935;
        chunk_size 8192;
        application live {
           live on;
           record off;
           meta copy;
        }
    }
}

ついでにリバプロも設定してみる。

http {
    server {
        ... (略) ...

        location /proxy/ {
            proxy_pass   http://127.0.0.1:8080/proxy/;
        }
}

apache 側 /etc/httpd/conf/httpd.conf の Listen port を変える。

Listen 8080
sudo systemctl reload httpd
sudo systemctl reload nginx
  • ホストOSから、ブラウザで http://localhost:8080/proxy/ にアクセスして Apacheの Not Found が出る。
  • ホストOSから、OBSで rtmp://localhost/live/abc に 配信して、 VLCで rtmp://localhost/live/abc が再生できる。

を確認して、OK。っと、ここまできてしまったけど、 SRPM というものを使ってビルドする方法もあるらしい。モジュールの git clone とかの手順も spec ファイルに書いておけば、やってくれるみたい。便利そう。

SRPMでもやってみる。

参考: nginxでsrpmからのconfigure変更 参考: nginx のモジュール追加リビルド (CentOS 7 EPEL 版) | 技術メモの壁

rpmbuildコマンドが必要

# 環境
sudo dnf install rpm-build rpmdevtools 

# 作業ユーザ
sudo su - 
useradd builder
cd ~builder
su - builder

# 作業ディレクトリ
mkdir -p ~/nginx/rpmbuild &&
cd ~/nginx/rpmbuild

# ダウンロードと展開
echo '%_topdir %(echo $PWD)' > ~/.rpmmacros &&
rpmdev-setuptree &&
curl -O http://nginx.org/packages/mainline/centos/8/SRPMS/nginx-1.17.8-1.el8.ngx.src.rpm &&
rpm -i nginx-1.17.8-1.el8.ngx.src.rpm

# SPEC書き換え (後述)
vim SPECS/nginx.spec

BuildRequires に git を足して、%prepgit clone しつつ BASE_CONFIGURE_ARGS マクロに --add-module を足す。%defineでやると、lazyに展開されるせいで、無限再帰マクロになって死ぬので %global にする。

BuildRequires: git
 ... (略) ...

%prep
 ... (略) ...
git clone https://github.com/sergey-dryabzhinsky/nginx-rtmp-module.git
%global BASE_CONFIGURE_ARGS %{BASE_CONFIGURE_ARGS} --add-module=./nginx-rtmp-module

# ビルド
# RPMS/ にバイナリパッケージ、 SRPMS/ に ソースパッケージ がビルドできる
rpmbuild -ba SPECS/nginx.spec

# builder は sudoer ではないので、 logout して root に戻ってインストール
logout
dnf localinstall nginx/rpmbuild/RPMS/x86_64/nginx-1*

この場合は設定は /etc/nginx/nginx.conf に書く。

# boot
sudo systemctl start nginx
sudo systemctl status nginx

うーん。

バージョンアップするごとに結局新しいパッケージのSRPMとってきて SPEC書き換えるとかやってると、ソースから入れるのとあんま変わらないかも。

モジュールを更新するためには、リビルドすればいいだけではあるけど……。

複数台サーバセットアップするときはRPMにして持っていけはするけど、それ以前に、ディスクイメージごと固めたり、VMのプロビジョニングレベルでスクリプト書くんでいい気がするしなぁ。

まぁ、パッケージ管理でインストールしたものと近しい構造でインストールされるというのが、一番大きなメリットかもしらん(?)

名前変えとかないと dnf update で上書きされたりするんかな……?

これでひととおり

いかがでしたか?