🧪

GCP+DockerでOBSを動かしYouTube Liveに配信をする

2024/03/06に公開

はじめに

クラウド環境でOBSを使ったYouTube Live配信がやりたくて、GCP上でOBS Studio on Dockerな環境を作って配信が出来るかをテストしてみました。
何故そんな謎の構成を? ローカルでOBS動かせば良いのでは? というのがもっともな疑問だと思うのですが、開発中のAITuberの基盤を配信含めてクラウド化しておくと、ポータブルだし出先でも色々触れて何かと便利そうだったので。
他にも、自宅のPCがそこまで強力じゃなくてもGCPのVMという比較的安定して帯域的にもしっかりしたところからYouTubeに配信出来るのは色々と通信の安定性の観点ではメリットがありそうです。

という分けで、やり方を備忘録としてまとめておきます。

構成

今回作成した構成は以下の通りです。

GCE上で直接コンテナを動かしています。k8sでも良いのですがGCEではVMとコンテナをシームレスに統合するオプションがあるので、高度な運用をしないならこうした単純な構成の方が管理面でも費用面でもコスパが良いですよね。
その上でGUI環境としてTightVNCを動作させています。X11系でも良いのですが、ブラウザからの操作等を考えるとVNCの方が楽なのでこちらを採用。VNCクライアントは同じ環境にnoVNCをインストールし、ブラウザ経由でアクセス出来るようにしています。デスクトップ環境は軽量なfluxboxを採用しました。
その環境でOBS Studioをインストールしてブラウザ経由、またはobsws_pythonというライブラリを利用してOBSを操作してYouTubeに配信を行います。

なお、ALSAPulseAudioといったLinuxのサウンド周りのライブラリもインストールしているので、コンテナ環境であっても問題無くYouTubeに音を配信できます。

GUI環境 on コンテナの構築

まずは、VNCを利用したGUI環境を構築します。今回のベースのイメージはDebianなので以下のようにDockerfile上で必要なライブラリをインストールします。

FROM debian:12-slim

RUN apt-get -y update && apt install -y \
    websockify novnc tightvncserver \
    fluxbox

そのうえで、以下のようにTightVNC, noVNCの順で起動していきます。

vncserver -localhost :1
websockify --web=/usr/share/novnc 6080 localhost:5901

まずはvncserverを画面ID1で起動させます。後述しますが、OBSも起動したいのでDISPLAYを:1を指定してバックグラウンド起動。そして、novncをvncserverのlocalhost:5901に接続し、ブラウザからアクセスするためのポートとして6080を指定します。

これで6080ポートにアクセスすれば良いわけですが、このポートをグローバルにさらすのは危険なのでSSHポートフォワーディングで繋ぎます。

gcloud compute ssh vm-obs -- -L 6080:localhost:6080

これでローカルPCのブラウザからhttp://localhost:6080でアクセス出来るようになりました!

私はアドホックにVMを作成する運用を想定しているのでこれで十分ですが、常時起動するような環境の場合はIAPなどの導入をするとセキュリティと利便性を両立出来ると思います。

最後に実はVNCは起動時にパスワードが要求されます。vncpasswdで以下のように作れます。

RUN echo foobar | vncpasswd -f > /root/.vnc/passwd
RUN chmod 600 /root/.vnc/passwd

権限は600じゃないと正常に動作しないので注意をしてください。このパスワードはインターネット等にさらされる事は原則無いので、いったんDockerfileにハードコーディングする方針にしました。
また初回起動時に.vncにはxstartupなどのファイルも生成されるので、こちらを事前に保存しておきビルド時にAddしています。

以上を踏まえた全体のDockerfileは以下のようになります。

FROM debian:12-slim
EXPOSE 6080

# INSTALL
RUN apt-get -y update && apt install -y \
    websockify novnc tightvncserver \
    fluxbox

ADD ./resources/vnc /root/.vnc
ADD ./resources/start-vnc_obs.sh /root/start-vnc_obs.sh

# SET VNC PASSWORD
RUN echo foobar | vncpasswd -f > /root/.vnc/passwd
RUN chmod 600 /root/.vnc/passwd

# START
WORKDIR /root
RUN chmod a+x /root/start-vnc_obs.sh
CMD ["/root/start-vnc_obs.sh"]

OBSのインストールと配信設定

OBSはLinuxでも動作するのでインストールは簡単です。単純に

apt intall obs

をするだけです。コンテナやクラウドインフラ上で音周りをきちんと扱えるのか心配でしたが、ALSAやPluseAudioなどのサウンド周りのパッケージが入っていれば少なくともYouTubeの配信は支障なく行えました。まだ試してないですが、VB-CABLEとかYAMAHA SYNCROOMを仮想マイク的に使ってる動きもPluseAudioで問題無くできそうです。

インストールして前述のDockerfileでイメージを作ってコンテナを起動すれば、以下のようにブラウザから見慣れたOBSを起動させることが出来ます。

OBSをGUIで操作して、いつも通りにシーンの作成や配信の設定を行います。この辺りは変わりないので詳細は割愛しますが、後程PythonからのAPI操作で使うのでWebSocketを有効にするのは忘れないでおきましょう。

重要なのは、こうした設定のあとでその内容をコンテナに組み込む事です。毎回毎回起動時に設定をするのはあまりにも面倒なので、設定した内容をコピーしてビルドスクリプトの中に組み込んでしまいます。OBSの設定ファイルは以下のようにホームディレクトリに保存されます。

root@f4ea294c8be7:~# ls -l /root/.config/obs-studio/
total 28
drwxr-xr-x 1 root root 4096 Mar  2 12:21 basic
-rwxr-xr-x 1 root root 1239 Mar  3 04:14 global.ini
drwxr-xr-x 2 root root 4096 Mar  5 22:31 logs
drwxr-xr-x 1 root root 4096 Mar  2 12:21 plugin_config
drwxr-xr-x 2 root root 4096 Mar  2 12:29 profiler_data

これをコンテナから以下のようにコピーしてローカルに保存します。

mkdir ./resources/config
docker cp f4ea294c8be7:/root/.config/obs-studio ./resources/config/obs-studio

そして、Dockerfileの中で以下のようにconfigを追加します。

ADD ./resources/config /root/.config

これでコンテナを起動した瞬間から設定を反映できます。なお基本的には中身は単なるJSONなどのテキストファイルなので、ベースが出来たら直接エディタで編集するのも手軽で良い気はします。ただ秘密情報をコミットしないように注意をしてください。例えばOBSのWebSocketのパスワードのように原則コンテナの中からしか利用できないものは、たとえパスワードであっても秘匿する必要が無い情報ですが、例えば YouTubeの配信キー のように秘匿するべきものも存在します。今のところ、こうした配信のキー以外は外に出して困る情報は無い認識ですが、GUI等でそうした情報を入力した際は注意をしてください。

こうした情報はイメージの中に含めずコンテナ実行時に具体的な値を注入するのが効果的です。例えばYouTubeの配信キーはconfig\obs-studio\basic\profiles\Untitled\service.jsonの中で管理されています。Untitledは適宜置き換えてください。

{
    "settings": {
        "key": "secret",
        "server": "rtmp://a.rtmp.youtube.com/live2",
        "service": "YouTube - RTMPS"
    },
    "type": "rtmp_common"
}

"key": "secret"の部分に本来であればストリームのキーが入っています。これをsecretなど適当な値に書き換えた上で、service.json.tempalteという名前にリネームします。

これをinit-yt-key.pyなどの名前で保存した以下のPythonスクリプトを使って環境変数から受け取った値でキーを置き換えます。

import json
import os

wk_dir = '/root/.config/obs-studio/basic/profiles/Untitled/'
template_file = "service.json.template"
output_file = "service.json"
stream_key = os.environ.get("YT_LIVE_KEY")

with open(wk_dir + template_file, "r") as f:
    template = json.load(f)

template["settings"]["key"] = stream_key

with open(wk_dir + output_file, "w") as f:
    json.dump(template, f, indent=4)

そして、Docker内の実行スクリプトの中に以下のように組み込みます。

#!/bin/sh

## Setup Config
python3 /root/init-yt-key.py

## Run VNC & OBS
vncserver -localhost :1
DISPLAY=:1 obs &
websockify --web=/usr/share/novnc 6080 localhost:5901

OBSの実行より先であればいつでも良いのですが、上記のように同じスクリプトの中に混ぜてしまうのが簡単ですね。そして、以下のように起動時に環境変数YT_LIVE_KEYに値を入れます。

ocker run --rm -it -e YT_LIVE_KEY=xxxx-xxxx-xxxx-xxxx-xxxx -p 6080:6080 koduki/obs

これで秘密情報をコンテナの実行時に注入出来るようになりました。

PythonからOBSの配信を開始する

今回コンテナ化、クラウド化する目的として自動配信があります。特定の時間になったら自動的にVM/コンテナが起動して、その中でAI配信者とOBSが動く感じですね。となると手動でOBSの配信ボタンを押すと片手落ちなので、そのあたりもPythonで制御していきます。

OBS 28以降であればWebsocket v5 Pluginが入っているので、こちらから様々な言語でOBSを操作することが出来ます。仕様は以下にあります。
https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#obs-websocket-501-protocol

Pythonでこれを扱う場合はobsws-pythonが良そうなので、こちらを利用します。
https://pypi.org/project/obsws-python/

ただ、このライブラリはドキュメントが不十分というか上記のOBS Websoketプロトコルを一定のルールで透過的に実行できるよ! という書き方がされてるので、ちょっとサンプルが探しづらいのが難点。素のChatGPTに聞いても幻想にまみれているので、自力でマッピングしながらいくしかなさそう。。。
ちなみに変換ルールは、リクエストのメソッド名をスネークに変換する、です。例えばOBSのバージョンを取得するGetVersionの場合は以下のようにobs_versionになります。

print(f"OBS Version: {resp.obs_version}")

という分けで、配信の開始と終了は以下のようになります。

import obsws_python as obs

obs_password='j85l4lc0yoAlh7Ou'

client = obs.ReqClient(host='localhost', port=4455, password=obs_password, timeout=3)
resp = client.get_version()
print(f"OBS Version: {resp.obs_version}")

# 配信開始
client.start_stream()

# 配信終了
#client.stop_stream()

obs_passwordはOBSに接続するためのパスワードです。コンテナの外からアクセス出来るようには環境的にしていないので、ハードコーディングで問題無いかと思います。とはいえ気になる場合はGUIやconfig/obs-studio/global.iniを変更しましょう。

[OBSWebSocket]
FirstLoad=false
ServerEnabled=true
ServerPort=4455
AlertsEnabled=false
AuthRequired=true
ServerPassword=j85l4lc0yoAlh7Ou

GCPにデプロイする

コンテナにするところまで出来たので、せっかくなのでクラウド環境に乗せたいと思います。今回はGCPです。GCPではk8sやCloud Runなどコンテナをデプロイするための様々なオプションがありますが、実はGCEに直接デプロイも出来ます。Cloud Runでは用途的に実行時間が合わないですし、k8sでは大仰なので、今回はシンプルにGCEを利用します。

インスタンスはe2-highcpu-8を選びました。GPU無しです。

少なくともOBSでBGMを流すテストをした感じではこのくらいのリソースで問題は出ませんでした。ただ、配信の設定内容によってはGPU付きのインスタンスを選んだほうが良いかもしれません。この辺りは少し作業内容に依存するかと。コンテナのオプションは以下の通りで、環境変数で配信キーを指定しています。

これで、準備はOKです。ローカルから接続する場合は以下のようにgcloud経由でポートフォワーディングをするのが簡単です。

gcloud compute ssh vm-obs -- -L 6080:localhost:6080

また、以下のようにSSH+docker execでログインしてPython経由で配信を開始することも出来ます。

/app# poetry run python start-yt-live.py
OBS Version: 29.0.2

まとめ

配信環境をクラウドに構築することで、旅行先など何処からでも配信が出来るのは色々な使い方は出来そうです。GCP-> YouTubeだとGoogle同士だからきっと通信も安定するでしょうし。まあ、通常はさらにOBSに何かしらの配信コンテンツを流し込まないといけないのでAITuber以外ではあまり使い道が無いかもしれませんが...

とりあえず、ベースは出来たので毎朝のニュース的な配信をしてくれるシステムを構築していきたいと思います。

それではHappy Hacking!

Discussion