あずんひの日

あずんひの色々を書き留めるブログ

極小TVチューナーマシンを組む、あるいはpodman play kube周りについて

PT3を使ってmirakcでテレビ放送をLAN配信する極小サーバーマシンを組んだので紹介します。 mirakcを動かすにあたってpodman play kubeを頑張って活用してみたのでその記録でもあります。

今回組んだマシン
今回組んだマシン

ハードウェア構成

DualSenseと大きさ比較
DualSenseと大きさ比較
筐体内部(表側)
筐体内部(表側)
筐体内部(裏側)
筐体内部(裏側)
種類 型番(リンクは買ったとこ) 価格(購入時点)
マザーボード(CPU) BIOSTAR J4125NHU(Celeron J4125) ¥16,080
メモリ CFD D4U2400PS-4GC17 (DDR4 PC4-19200 4GB) ¥2,060
SSD Western Digital WD Blue SN570 WDS250G3B0C (M.2 2280 250GB) ¥5,820
チューナー アースソフト PT3 プライスレス
電源 Mini-Box picoPSU-90 ¥6,160
ACアダプタ AD-A120P500DC変換プラグ ¥2,480
ケース A07 Mini ITX Computer Case(買っちゃダメ) $30.14(含返金)
ライザーケーブル ADT-Link R33UF-TU 180mm $26.96
合計 - 約4万円

今回のテーマは「低消費電力で出来るだけ小さいPT3を積んだmirakc専用マシン」でした。 いわゆるロケフリのように遠隔地のテレビを見られるようにするためのものです。 現在はすぐ近くの別のマシンにEPGStationを入れてHDDに録画していますが、 将来的にはmirakc専用マシンを東京の実家に放置して自分はどこに引っ越しても東京のテレビが見られるようにする予定です。

最初に消費電力ですが、チューニング等は特にしていないもののアイドル時は約8W全4チューナー配信中でも13W前後と想定よりも電力消費の少ないマシンとなりました (測定にはRS-BTWATTCH2を使用)。

構成について、ここしばらく録画鯖と言えばRaspberry PiとUSB接続タイプのチューナーを使うのがトレンドですが、 ずっと死蔵していたPT3があったのと(起動用USBメモリ等を含むシステムとしての)ラズパイの信頼性に不安があったこと、 また1つの筐体に(見た目含め)いい感じに詰め込みたかったというのもありケースがある程度豊富なMini-ITXで組みました。

ケースはPCIeが挿せる出来るだけ小さいケースを探したところAliExpressで見つけました。寸法は223x184x104mmです。 ただ後述するように販売業者(Pamela digital Store)の対応が酷かったので商品へのリンクは張りません。 AliExpressで「mini-itx case pcie」とかで検索すると同じもので業者違いのものが出てくると思います。

このケースは最近流行っているらしいDan Cases A4-SFXと同じ仕組みのもので、 ライザーケーブルを使って拡張カードマザボと並行に立てることでスペースを稼いでいます。 より小さい同じタイプのケースとしてGEEEK CASE A31 V2というのがあるんですが、 サイドパネルがアクリル板というのが微妙だったのとそもそも在庫がなかったので採用を見送りました。

なお、ケース付属のライザーケーブルが物故割れていたので別でライザーケーブルを購入しています。 これはA4-SFXと同じものが使えそうだったのでADT-Link R33UF-TUの180mm版を買ったところ無事ぴったり合うものでした。 これもAliExpressで売っています(eBayでも売ってますが長さのバリエーションが少ない)。

ケースの販売業者が酷かったという愚痴

前述の通りPCケースはAliExpressで買ったんですが、 このケースの販売業者(Pamela digital Store)の対応がキングオブウンコだったという愚痴のため経緯を書き残しておきます。

  1. ケース付属のPCIeライザーケーブルが動作しなかったので業者に問い合わせた。

  2. ボットから「24~48時間待ってね」と返ってきたが、48時間以上経過しても返答がないので催促。 すると4時間程度で「技術スタッフに伝えるので問題を示す写真か動画を送ってくれ」という返答が返ってくる。 その日のうちに言われたとおりに写真を送ると翌日「技術スタッフに伝えたのでちょっと待って」との返答。 しかしそれから1週間経過するも返答がなかったため再度催促。

  3. 業者から5ドル返金の提案があったものの、ライザーケーブルは大抵20ドル~30ドルはするもの。 流石にそれは受け入れられないので「せめて20ドルだろう」と返答するものの、ケチな業者からはたった1ドルアップの6ドルで返答。

  4. この時点で問い合わせ開始から13日も経過しており埒が明かないので20ドル返金の紛争を開始。 ケチな業者は今さら「もらった画像では商品が正しく動作しているか分からないので更なる情報が必要」 として返金無しの逆提案をしてくるがもちろんこちらは却下。 紛争開始から6日経過したことでAliExpressが介入し「商品返送+全額返金」か「返送無し+20ドル返金」との提案がなされる。 筆者はもちろん後者を選択するが業者はそれを却下。 その後紛争開始から8日経過したことで時間切れのためAliExpressからの提案が自動承認。20ドルの返金が決まる。

  5. 購入から29日、問い合わせ開始から21日経過してからようやく返金が決定され、時間と精神力を無駄にしたやり取りがついに決着。

販売業者の店名はPamela digital Storeです。みなさんはこの販売業者を利用しないよう気を付けてください。

環境のセットアップ

私はLinuxと言えばRHEL派なので最新版であるRHEL 9.1を使ってセットアップしました。 個人利用なら16台まで無償で提供されています。 互換OSでも良いと思いますが、その場合はバージョンが古いかもしれないので注意してください。 Ubuntuとかならできるmirakcに従えば良いと思います。

ドライバのインストール

今回はPT3を使っているのでPT3用のドライバを入れます。 なお「PT3のドライバがLinuxに組み込まれたので標準でDVBデバイスとして使えるようになった」という話が 転がってたりしますがRHEL 9でDVBへのサポートが削除されたので使えないようです。 カーネルのバージョンを考えるとRHEL系でPT3を使えるのはRHEL 8系だけということになります。 そんなわけでRHEL 8やRHEL以外のOSではドライバのインストールは不要かもしれないので注意してください。

PT3用ドライバを入れる前に、ドライバはdkmsで管理したいのでdkmsの入っているレポジトリであるEPELを入れておきます。 RHEL 9以外では手順が違うのでFedora Docsに従ってください。

$ sudo subscription-manager repos --enable codeready-builder-for-rhel-9-$(arch)-rpms
$ sudo dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm

そうしたらビルドに必要なgccとmake、そしてdkmsをインストールし、 m-tsudo/pt3からソースコードを落としてきてビルドします。 gitなどサーバーに不要なコマンドは入れたくないので、rsyncとかでサーバーに飛ばしておいてください (飛ばす側で先にmake version.hとしておくとバージョンが設定されるので便利・・・?)。

$ sudo dnf install gcc make dkms
$ cd pt3
$ make && sudo make install
$ sudo sh ./dkms.install

そうしたら再起動し、デバイスファイルが見えるか確認します。

$ lsmod | grep pt3
pt3_drv                53248  2
(何も出なかったらおかしい)
$ ls /dev/pt3video*
/dev/pt3video0  /dev/pt3video1  /dev/pt3video2  /dev/pt3video3
(No such file or directoryとか出たら何かおかしい)

podmanでmirakcを実行

RHELでDockerを使うのは面倒なだけでメリットがほとんど無いのでpodmanを使います。 podmanはDockerと違いコンテナをユーザー権限で実行できるのでセキュリティ的に優れています。 sudo dockerと打つ度にrootの恐ろしさにそわそわする日々とはもうお別れです(rootless Dockerもあるが普通のDocker以上に面倒くさい)。

また、docker-composeの代わりにはKubernetesマニフェストを使ってPod(≓コンテナ)を実行できるpodman play kubeを使います。 docker-composeと違って設定ファイルも含めて1ファイルで管理できるので便利です。

まずはpodmanをインストールします。RHEL系ならパッケージマネージャ以外に操作は不要です。 他のディストロでは設定が必要だと思うのでPodman - ArchWiki辺りを参照して頑張ってください。

$ sudo dnf install podman

そしたらコンテナにデバイスファイルをマウント出来るようにSELinuxの設定を変更します。

$ sudo setsebool -P container_use_devices=true

これでちゃんと動くか確認しておきます。

$ podman run -it --rm --device /dev/pt3video0 alpine:latest ls /dev/pt3video0
/dev/pt3video0
(No such file or directoryとか出たら何かおかしい)

コンテナの実行にはroot権限は不要なのでwheelに属さないユーザーを作ると操作ミスやセキュリティ的に安心です。

$ sudo useradd -G video mirakc
$ sudo -u mirakc -s
(ここからはmirakcユーザーのシェル)

あとはKubernetesマニフェストファイルを書きます。 以降はルート直下に~/mirakc.yamlとして保存する前提で話を進めます。これ以外のパスに保存する場合は適宜読み替えてください。

クリックして~/mirakc.yamlを見る

---
kind: Pod
metadata:
  name: mirakc
spec:
  containers:
    # バージョンは書き換えること
    - image: mirakc:1.X.Y-debian
      name: mirakc
      ports:
        - containerPort: 40772
          hostPort: 40772
      env:
        - name: TZ
          value: "Asia/Tokyo"
        - name: RUST_LOG
          value: "info"
      volumeMounts:
        # mirakcの設定用ボリュームをマウント
        - name: mirakc-config
          mountPath: "/etc/mirakc"
          readOnly: true
        # EPGデータのキャッシュ用ボリュームをマウント
        - name: epg-cache-pvc
          mountPath: "/var/lib/mirakc/epg"
        # チューナーをマウント。デバイスのパスは適宜変更すること
        - name: tuner0
          mountPath: "/dev/pt3video0"
        - name: tuner1
          mountPath: "/dev/pt3video1"
        - name: tuner2
          mountPath: "/dev/pt3video2"
        - name: tuner3
          mountPath: "/dev/pt3video3"
  # volumeMounts.subPathに非対応なのでinitContainersでコンテナのファイルをボリュームにコピー
  # https://github.com/containers/podman/issues/12929
  initContainers:
    - name: init-config
      image: mirakc:1.0.76-debian
      command: ["sh", "-c", "cp -rT /etc/mirakc/ /config"]
      volumeMounts:
        - name: mirakc-config
          mountPath: "/config"
  # initContainersが再起動し続けるのを防止
  # https://github.com/containers/podman/issues/16343
  restartPolicy: OnFailure
  # 各ボリュームを定義
  volumes:
    - name: mirakc-config
      configMap:
        name: mirakc-config
    - name: epg-cache-pvc
      persistentVolumeClaim:
        claimName: mirakc-epg-cache
    # デバイスのパスは適宜変更すること
    - name: tuner0
      hostPath:
        path: "/dev/pt3video0"
        type: CharDevice
    - name: tuner1
      hostPath:
        path: "/dev/pt3video1"
        type: CharDevice
    - name: tuner2
      hostPath:
        path: "/dev/pt3video2"
        type: CharDevice
    - name: tuner3
      hostPath:
        path: "/dev/pt3video3"
        type: CharDevice
---
kind: ConfigMap
apiVersion: v1
metadata:
  name: mirakc-config
data:
  # 設定ファイルを定義
  config.yml: |
    epg:
      cache-dir: /var/lib/mirakc/epg

    server:
      addrs:
        - http: 0.0.0.0:40772

    # デバイスのパスやコマンドは適宜変更すること
    # https://mirakc.github.io/dekiru-mirakc/latest/config/tuners.html#%E8%A8%AD%E5%AE%9A%E4%BE%8B
    tuners:
      - name: PT3-S1
        types: [BS, CS]
        command: recpt1 --device /dev/pt3video0 {{{channel}}} - -
      - name: PT3-S2
        types: [BS, CS]
        command: recpt1 --device /dev/pt3video1 {{{channel}}} - -
      - name: PT3-T1
        types: [GR]
        command: recpt1 --device /dev/pt3video2 {{{channel}}} - -
      - name: PT3-T2
        types: [GR]
        command: recpt1 --device /dev/pt3video3 {{{channel}}} - -

    channels:
      # チャンネルを定義
      # https://mirakc.github.io/dekiru-mirakc/latest/config/channels.html
      ...

あとはこのマニフェストファイルを使ってPodを実行します。

$ podman play kube ~/mirakc.yaml

ログやAPIを覗いてみます。動作していれば成功です。

$ podman pod logs -f mirakc
(ログがだーっと出てくる)
$ curl http://localhost:40772/api/channels
{"current":"1.X.Y","latest":"1.X.Y"}

ただし、このままではマシン再起動時にmirakcが実行されません。なので、まずはこのマニフェストファイルをサービスとして登録します。 なお、上記で起動済みのPodはマニフェストファイルが同じならサービス内で勝手に置き換えてくれるので手動で停止する必要はありません。

$ systemctl --user enable --now podman-kube@$(systemd-escape ~/mirakc.yaml).service
$ systemctl --user status podman-kube@$(systemd-escape ~/mirakc.yaml).service
active (running)とか出てたらOK

これでサービスとしては登録されましたが、このサービスはユーザーのログイン中のみ、 つまりSSHなどのセッションを張っている間にしか実行されません。 これでは困るのでlingerを有効化し、マシン起動時にサービスが起動するようにします。

root権限が必要なので制限ユーザー(mirakcユーザー)からは抜けておいてください。

$ sudo loginctl enable-linger mirakc

同時に、外からアクセス出来るようにfirewalldの設定をします。

$ sudoedit /etc/firewalld/services/mirakc.xml
<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>mirakc</short>
  <description>mirakc</description>
  <port protocol="tcp" port="40772"/>
</service>
$ sudo firewall-cmd --add-service=mirakc --zone=public --permanent
$ sudo firewall-cmd --reload
$ sudo firewall-cmd --list-all
(servicesの中にmirakcがあればOK)

この状態でhttp://<SERVER>:40772/api/tunersなどにアクセスできれば大丈夫です。 あとは念のため一通り設定が適用できているか確認するため再起動してからの動作確認もしておきましょう。

あとがき:podmanのk8s機能について

今回のセットアップでpodmanに搭載されているKubernetesマニフェストを使ったPod生成機能を色々触ってみましたが、 現時点(2023年1月時点)ではまだまだ完成度が足りないように感じました。

podman 4.3.1時点ではvolumeMounts.subPathに非対応なため、 コンテナ内のディレクトリにファイルを追加したり一部ファイルだけ差し替えるといったことはできません。 そのため今回はinitContainersを使ってこれを実現しています。

ただこのinitContainersにも処理が成功しても再起動を続けるという 正直ちゃんとテストしてるんか?と思わざるを得ないバグもあります。 今回はPod全体にrestartPolicyにOnFailureを設定することで問題を回避しています。

とは言え上記の問題はどちらもmainブランチでは修正済みで互換性は日々向上中のようですし、目くじらを立てるほどではないかもしれません。

また、podmanはpodman-kube@.serviceや(今回は使わなかった)podman generate systemdなどによって コンテナをsystemdサービスとして使えるなどsystemdとの親和性が高く、rootlessでの運用がしやすいのはとてもありがたいです。

皆さんもdocker-composeばかり使わずKubernetesへの移行がしやすいpodmanを使い、 Kubernetesマニフェストと戯れることも検討してみてはいかがでしょうか。