kWatanabe 記事一覧へ

kWatanabe の 技術帖

某企業でOSや仮想化の研究をやっているインフラ系エンジニア。オンプレとクラウドのコラボレーションなど、興味ある技術を綴る。

Majestouch MINILA のキートップを新品に交換する

  • 在宅勤務で持ち帰っていた Majestouch MINILA のキートップを新品にしてみた。
  • 既に8年選手のキーボードが、新品のようによみがえった。

Majestouch MINILA Air専用 Keycap Set 英語配列

今回使ったのは、メーカーが展開している交換用キートップ。カラフルなカスタム向けの商品もありますが、今回購入したのはノーマルの新品キャップ。

商品名には「MINILA Air 専用」とあるが、USB版の「MINILA」にも使える。

f:id:kWatanabe:20210503104257j:plain

www.diatec.co.jp

交換中の様子

f:id:kWatanabe:20210503104736j:plain

キャップを交換した MINILA は、2013年の新発売当時に会社にお願いして買って貰った*1もので、もう8年選手になる。購入後、しばらく別の職場に異動して不在の期間もあったけども、その間もちゃんと保管していてくれた今の職場には感謝している。

f:id:kWatanabe:20210503105049j:plain
交換前

f:id:kWatanabe:20210503105111j:plain
交換後

新品のように綺麗になった。このモデルは、既に絶版なので大切に使っていきたい。

思ったよりよかったので、プライベートで使っている MINILA も交換しようかしら。でも3台あるから、全部交換するとキャップだけで1万円近くになるという・・・。

*1:なお、プライベートでは予約して購入して既に使っていたので、使い勝手の良さはもう分かっており、会社のカネで冒険したわけではない。

LXC非特権コンテナでIntel CPU内蔵GPUを使ってGPGPUする

以前 LXCの非特権コンテナで CUDA する記事を書いた

以前、こんな記事を書いた。

kwatanabe.hatenablog.jp

kWatanabeは、主に waifu2x と ffmpeg を使って動画ファイルを 4K 化するために、LXCゲストでCUDAを動かしている。

github.com

github.com

waifu2xは動画を扱えないので、一度 ffmpegbmp にバラしてから1フレームずつ処理させてるんだけども、そうなるとフレーム同士に依存関係がないので、複数のGPUやCPUを混ぜて並列処理させることができる。

これまで、GeForceとCPUで処理をしていたわけだけど、ふとCPU内蔵GPUも同じように使えるのではと思ったので、試してみた。

検証環境

ホストの設定

GPUの特定と有効化

IntelGPU ドライバは Proxmox VE のカーネルにマージされているので、GeForce のようにドライバを導入する必要はない。

確認する場合は、lspci-v オプションで確認すればよい。まず、PCIのバス番号を調べておいて。

$ lspci | grep VGA
00:02.0 VGA compatible controller: Intel Corporation Xeon E3-1200 v3/4th Gen Core Processor Integrated Graphics Controller (rev 06)

バス番号を指定して詳細を確認する。

$ lspci -v -s 00:02.0
00:02.0 VGA compatible controller: Intel Corporation Xeon E3-1200 v3/4th Gen Core Processor Integrated Graphics Controller (rev 06) (prog-if 00 [VGA controller])
        Subsystem: Intel Corporation Xeon E3-1200 v3/4th Gen Core Processor Integrated Graphics Controller
        Flags: bus master, fast devsel, latency 0, IRQ 44
        Memory at f7400000 (64-bit, non-prefetchable) [size=4M]
        Memory at e8000000 (64-bit, prefetchable) [size=128M]
        I/O ports at f000 [size=64]
        [virtual] Expansion ROM at 000c0000 [disabled] [size=128K]
        Capabilities: <access denied>
        Kernel driver in use: i915
        Kernel modules: i915

見つからないようなら、GeForce のような dGPU のために iGPU が無効化されていないか、BIOS/UEFI などで確認する。

なお、ここで調べたPCIバス番号 ( 上記の場合は 00:02.0 ) は後で必要なのでメモしておく。

GPUをパススルー

GeForce の時と同様に、/dev/dri 配下をゲストに bind マウントする。素のLXC の場合は config ファイル、Proxmox VE の場合は /etc/pve/lxc 配下の設定ファイルに以下を書き加える。

lxc.cgroup.devices.allow: c 226:* rwm
lxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dir

そして、次がハマりポイント

/dev/dri のファイルは root ユーザ、video グループ、render グループが所有者となっているが、LXCの非特権コンテナの中からは nobodynogroup にリマップされるため、このままで権限不足でアクセスできない。

これを解決するためには以下の2通りの方法がある。

  • LXC がゲストに bind するのをフックして所有者を変更する
  • アクセス権を 660 から 666 に変えてしまう

セキュリティ的にはよくないが、後者の方が簡単。メモしておいた PCI バス番号 ( 今回は 00:02.0 ) を頼りにデバイスファイルのアタリをつける。

$ ls -l /dev/dri/by-path
lrwxrwxrwx 1 root root  8  2月  7 01:06 pci-0000:00:02.0-card -> ../card0
lrwxrwxrwx 1 root root 13  2月  7 01:06 pci-0000:00:02.0-render -> ../renderD128
...

アタリをつけた GPUパーミッションを変更する。

$ sudo chmod 666 /dev/dri/card0 /dev/dri/renderD128

ゲストの設定

まずは、デバイスファイルが bind マウントされており、アクセス権があることを確認する。

$ ls -l /dev/dri        
合計 0
drwxr-xr-x 2 root root         80  2月  7 01:06 by-path/
crw-rw-rw- 1 nobody nogroup 226,   0  2月  7 01:06 card0
crw-rw-rw- 1 nobody nogroup 226, 128  2月  7 01:06 renderD128

問題なければ、実機と同じようにセットアップする。Intel CPU 内蔵 GPU 用の OpenCL ドライバは beignetになる。

$ sudo apt install beignet beignet-opencl-icd opencl-headers clinfo

clinfoOpenCLバイスとして認識できていることを確認する。

$ clinfo -l
Platform #0: Intel Gen OCL Driver
 `-- Device #0: Intel(R) HD Graphics Haswell GT2 Desktop

以上で、LXCの非特権ゲストに Intel CPU 内蔵 GPU をパススルーして、OpenCL できるようになる。

おわりに

  • GeForceをパススルーしてCUDAした以前の記事に引き続き、LXCゲストに Intel CPU 内蔵 GPU をパススルーして OpenCL する方法を整理した
  • バイスファイルのアクセス権の設定を忘れると、デバイスファイルは見えているのに、clinfoからはデバイスが見えないといったことになるので注意
  • waifu2xのように工夫次第で複数、かつ、異種のGPUを使い分けられるワークロードであれば、少しの性能の足しにはなるかもしれない

Azure Spot VM の強制削除イベントをハンドルして安全に終了する

  • Azure や AWS には大幅なディスカウントのある Spot VM インスタンスがある
  • Azure の Spot VM は、インフラの負荷が高まった際、事前通知のうえ強制終了される
  • 事前通知されるイベントを検知する仕組みを作り、安全に終了できるようにした

Azure Spot VM

Azure Virtual Machine には、インフラに余力がある場合にのみ利用でき、従量課金制と比べて大幅にディスカウントされる「Azure Spot VM」というサービスがある。

azure.microsoft.com

ディスカウント幅は、インフラ側の都合で動的に決定される。Microsoft は最大で90% ディスカウントすると主張している。 一方、インフラの余力が無くなった場合には、30秒前に事前通知したうえで、強制的にシャットダウンするとしている。

また、ユーザから「ここまでディスカウントしてくれるなら使ってもいい。そうでないなら、使うのを止めさせてもらう。」といった宣言も可能で、その場合、インフラの余力がまだ残っていても、価格がユーザ指定値以上になった際にシャットダウンされるようになる。

Azure Metadata Service の Scheduled Events

Spot VM に限らず Azure Virtual Machine には、保守のための再デプロイ(ライブマイグレーション)や再起動など、データがロストする可能性があるイベントが予定された際に、それを VM の内部から検知できる仕組みがある。

docs.microsoft.com

以下の様に VM の内部からエンドポイントにアクセスすると、予定されているイベントがあれば JSON で取得できる。

$ curl -H "MetaData:true" "http://169.254.169.254/metadata/scheduledevents?api-version=2019-08-01"
{
    "DocumentIncarnation": {IncarnationID},
    "Events": [
        {
            "EventId": ce5f797a-7369-11eb-ba65-a706ed727702,
            "EventType": "Preempt",
            "ResourceType": "VirtualMachine",
            "Resources": ["SpotVM_TEST"],
            "EventStatus": "Scheduled",
            "NotBefore": Mon, 15 Feb, 2021 18:29:47 GMT,
            "Description": "",
            "EventSource" : "Platform",
        }
    ]
}

もし、Spot VM がインフラ側の余力不足などで強制削除される場合、その30秒前に EventTypePreempt なイベントが通知される。また、Preempt 以外にも以下のイベントが発生する可能性がある。

EventType 概要 ロストするデータ
Freeze 一時停止が予定されている。約15分前に発生 特になし
Reboot 再起動が予定されている。約15分前に発生 オンメモリ
Redeploy ライブマイグレーションが予定されている。約10分前に発生 一時ストレージ
Preempt Spot VMの削除が予定されている。約30秒前に発生 すべて
Terminate ユーザによる削除が予定されている。(発生タイミングは設定による) すべて

エンドポイントをポーリングして、もし、これらのイベントを検知すれば、動かしているプログラムを安全に終了させたり、必要なデータを退避するなどの対応が採れる。

検証環境

検証

Azure Spot VM を作る

具体的な作り方は割愛。閾値を現在価格以下にしてしまうと、そもそも起動できないので、1円上で指定してみた。

検知スクリプトを書く

やっつけだけど、こういうのを書いてみた。とりあえずイベントを検知したら、wall で警告を挙げて、プロセスを pkill するだけのシンプルなもの。

MITライセンスで。

github.com

結果

まだ、発動していないので、絶賛放置中。かなり渋い価格にしたので直ぐ発動すると思ったんだけど。それとも何かミスったか。

まとめ

  • Azure Spot VM の強制終了前に、それを検知し後片付けするスクリプトを書いてみた。
  • これで、Spot VM を安全に使えるはず・・・だが、まだ結果は分からない。

nftablesでシンプルなステートフルファイアウォールを作る

iptables から nftables への移行

Debian 11 (bullseye) の Freeze がはじまったので、自宅サーバ移行検証をしていたところ、ちょっとファイアウォールが気になった。

Debian では、10 (buster) で既に nftables がデフォルトになっており、iptables は nftables の互換レイヤーとして機能している。

wiki.debian.org

wiki.debian.org

今のところ、bullseye でも iptables が無くなるという話はないので、もう1回引っ張っても問題は無いのだが、nftables がデフォルトになった次のバージョンで移行というのはタイミングとしても良いので、ここで移行することにした。

検証環境

想定する構成

目指すはシンプルな2ファイアウォール型のDMZを構成すること。抽象化したモデルとしては以下のような形になる。

f:id:kWatanabe:20210213122100p:plain
シンプルな2ファイアウォールDMZ

なお、我が家はいわゆる「逸般の誤家庭」ではないので、Y社 や C社 のエンタープライズ向けルーターなんてものはない*1。 そのため、実態としては以下のような形になる。

f:id:kWatanabe:20210213123055p:plain
本記事の想定環境

この図の「Linux server」が今回のターゲットになる。 簡単のため、以下 DMZ 側の NICeth-dmz、Trust Zone 側の NICeth-trust と記載する。目指すルールは以下。

  • Trust Zone から DMZ への通信は全て許可
  • DMZ から Trust Zone へのセッション確立済みの通信を許可
  • DMZ から Trust Zone への特定のマシンから、特定のマシンの特定のポートへの通信を許可

nftables で作るステートフルファイアウォール

IP forward の有効化

まずは、eth-dmzeth-trust 間のパケット転送を有効にする。ここは nftables に限った話じゃないので詳細は触れない。

$ sudo vim /etc/sysctl.conf
#net.ipv4.ip_forward=1
↓
net.ipv4.ip_forward=1

適用。

$ sudo sysctl -p

nftables の導入

Debian buster および bullseye では、iptables と nftables の両方が提供されているため、明示的に nftables を導入する。

なお、パッケージは nftables のみでよい。iptablesiptables-persistent のように、管理インタフェースと永続化のための仕組みで分かれていたりはしない。

$ sudo apt install nftables

nftables のルールを書く

nftables は、全ての操作が nft コマンドで完結する。

iptables と同様、テーブル、チェイン、ルールの概念がある。ただし、デフォルトのテーブルやチェインは無い。そのため「nftables はなにもしない」状態から設定を作り込んでいく*2

大まかな流れは以下のようになる。 なお、<> はパラメータ、[]は省略可能な引数を指す。

1. 設定済みのルールをクリア
$ sudo nft flush ruleset
2. テーブルを作成
$ sudo nft add table [<アドレスファミリ>] <テーブル名>

アドレスファミリには以下が設定できる。

  • ip : IPv4
  • ip6 : IPv6
  • inet : IPv4IPv6
  • arp : ARP
  • bridge : ネットワークブリッジ
  • netdev : ネットワークインタフェース

IP層でのステートフルファイアウォールでは inet を使えば事足りる。具体例は以下。

$ sudo nft add table inet FIREWALL
3. テーブルにベースチェインを登録
$ sudo nft add chain [アドレスファミリ] <テーブル名> <ベースチェイン名> \
      { type <タイプ> hook <フック> [device <デバイス>] priority <優先度>; [policy <ポリシ> ;] }

ベースチェインは、初めに呼び出されるチェイン。Cでいう main() みたいなもの*3

「タイプ」は、チェインの用途を指定する。

タイプ 用途とアドレスファミリ 利用できるフック
filter 汎用 (全ファミリ) 全て
nat NAT (ip, ip6) prerouting, input, output, postrouting
route ルーティング (ip, ip6) output

「フック」は、チェインの実行タイミングを指定する。

フック タイミング 利用できるアドレスファミリ
ingress バイスがパケットを受信した際 netdev
prerouting パケット受信後、ルーティング判定の前 ip, ip6, inet, bridge
input ルーティング判定後、宛先が自分自身と判断した際 ip, ip6, inet, bridge, arp
forward ルーティング判定後、宛先が他ホストと判断した際 ip, ip6, inet, bridge
output 自分自身が他ホスト宛のパケットを生成した際 ip, ip6, inet, bridge
postrouting 他ホストへパケットを送信する直前 ip, ip6, inet, bridge

「デバイス」は、NIC 名を指定する。

「プライオリティ」は、同じフックが指定されたベースチェインが複数ある場合の優先度を指定する。値が小さいほど、先に実行される。

「ポリシ」は、チェインの中で取り扱いが決まらなかった際の取り扱いを acceptdrop で指定する。未指定だと accept になる。

具体例は以下の通り。

$ sudo nft add chain inet FIREWALL FILTER { type filter hook prerouting priority 0 \; }
  • type filter (汎用) で
  • hook prerouting (ルーティング判定前) に
  • priority 0 (最優先) で処理するベースチェインで、
  • 合致するルールが無い場合は受け入れる

policy を accept もしくは省略した場合は、確実に不要なパケットを落とすルールを入れることを忘れてはいけない。

4. テーブルにレギュラーチェインを登録
$ sudo nft add chain [アドレスファミリ] <テーブル名> <レギュラーチェイン名>

レギュラーチェインは、他のチェインから明示的に呼び出されるチェイン。呼び出し元のチェインの設定を引き継ぐため、チェイン名の登録のみ。具体例は割愛。

5. チェインにルールを登録
$ sudo nft add rule [<アドレスファミリ>] <テーブル名> <チェイン名> <ステートメント>

当該チェインが呼び出された際に、適用するルールを実行順に登録する。

ステートメント」は、具体的な制御内容を記載するが、網羅すると膨大な数になるため参考ページを記載し、ここでは具体例の記述にとどめる。

wiki.nftables.org

「確立済みの通信を許可する」ための設定例は以下になる。

$ sudo nft add rule inet FIREWALL FILTER \
    ct state related,established accept
  • ct (コネクション) の
  • state (状態) が
  • relatedestablished なものを
  • accept (許可) する。

「特定のマシンから、特定のマシンの特定のポートへのアクセスを許可する」ための設定例は以下になる。

$ sudo nft add rule inet FIREWALL FILTER \
    ip saddr 10.0.0.100 ip daddr 172.16.0.100 tcp dport 443 accept
  • ip (IPv4ヘッダ) の
  • saddr (送信元IPアドレス) が
  • 10.0.0.100 であって
  • ip (IPv4ヘッダ) の
  • daddr (送信先IPアドレス) が
  • 172.16.0.100 であって
  • tcp (TCPヘッダ) の
  • dport (接続先ポート番号) が
  • 443 のものを
  • accept (許可) する。

「(先のステートメントから漏れたパケットで、)特定のNICから入力された全てのパケットを破棄する」ための設定例は以下になる。

$ sudo nft add rule inet FIREWALL FILTER \
    meta iifname eth-dmz drop
  • meta (その他のメタデータ) の
  • iifname (入力インターフェース名) が
  • eth-dmz のものを
  • drop (破棄) する

設定済みのルールを確認

$ sudo nft list ruleset

上記のコマンドにて、現在の設定を出力できる。出力結果の具体例は以下になる。

table inet FIREWALL {
        chain FILTER {
                type filter hook prerouting priority 0; policy accept;
                ct state established,related accept
                ip saddr 10.0.0.100 ip daddr 172.16.0.100 tcp dport https accept
                iifname "eth-dmz" drop
        }
}

ルールの永続化

list ruleset の出力は、そのまま nftables の設定ファイルとして用いることができる。

Debian の場合、nftables サービスが起動する際に /etc/nftables.conf があればそれをロードする。そのため、以下のようにすることで設定を永続化できる。

$ sudo nft list ruleset | sudo tee /etc/nftables.conf
$ sudo systemctl enable nftables
$ sudo systemctl restart nftables

なお、手動で設定ファイルをロードしたい場合は以下のようにする。

$ sudo nft -f <ファイル名>

まとめ

最後に、上記のような設定を一発で行うためのスクリプトを作ったので公開しておく。

ライセンスは、MITライセンスで。

github.com

所感

  • nftables で単純なステートフルファイアウォールを作るための最低限の設定を整理した
  • iptables と比べて、設定ファイルや構文も構造化されており、人間様にわかりやすい
  • なにより、ツールが nft 1本に統一されているのがよい

*1:10GbE を敷くのが精々である。

*2:全て許可でも、全て禁止でも、全て転送でも無い。nftables 以外の仕組みが動いていれば、その設定が有効になる。本文に「nftablesは」とあえて書いた理由はここにある。

*3:_start()じゃないのか、とか面倒なコメントは受け付けない。

RHEL互換ディストリの作り方を手探ってみる2(大晦日ハッカソン)

  • 2020年の大晦日に大晦日ハッカソンなるイベントに参加した
  • CentOS のようなRHEL互換ディストリの作り方を1日で出来る範囲で手探ってみた
  • 1日でできる限られた範囲だけど、得られた知見をここで整理する

はじめに

「大晦日ハッカソンとは何ぞや」については、過去記事を参照。

今回は、そのRHEL*1互換ディストリビューションを自製するためには、どうすればいいのかを手探ってみることにしました。

kwatanabe.hatenablog.jp

RHEL互換ディストリビューションとは

過去記事にも書きましたが、RHEL互換ディストリビューションとは、商用ディストリビューションである RHEL から、RedHatの商標や独占的な権限を有するものを削除し、オープンソースライセンスの下に公開されているもののみで再構成したものです。*2

RHEL互換ディストリビューションを作成するための大まかな手順は以下になります。

  1. RHELサブスクリプションを得る
  2. RHEL のリビルド環境を用意する
  3. RHELソースコード (SRPM) を入手
  4. ソースコードの全てから、RedHat に関わる商標やその他権利を有するモノを仕分け・排除
  5. 上記の結果、RHELとの互換性が崩れてしまうものがあれば、それを補填するモノを開発
  6. リビルドする

以下、実際に大晦日ハッカソンで、kWatanabe が取り組んだ内容を整理していきます。

てさぐれ!RHELもの

RHELサブスクリプションを得る

RHELサブスクリプションを得るには以下の方法があります。

  • RedHatから購入する
  • リセラーを介して調達する
  • 評価版を入手する
  • Red Hat Developers に登録して入手する

また、RHELサブスクリプションは、用途や導入するマシンの種別に応じて、多種多様なものがあります。詳細はサブスクリプションガイドに記載があります。

www.redhat.com

Red Hat Enterprise Linux Developer Suite 以外のサブスクリプションは、RedHatもしくはリセラーから調達する必要があります。Red Hat Enterprise Linux Developer Suiteは、開発用途専用で、サポートもありませんが、Red Hat Developers にエントリすることで無償で入手できます。今回はこのサブスクリプションを利用しました*3

なお、Red Hat Developers には以下からエントリできます。

developers.redhat.com

RHEL のリビルド環境を用意する

バージョン齟齬や変な依存関係で悩みたくなかったため、リビルド対象の RHEL 8.3 をそのまま実機にインストールして準備しました。

RedHatカスタマーポータル*4にログインすれば、RHEL のインストールISOを入手できます。9GBを超え、DVD-R DLでも焼けないサイズのため、BalenaEtcher *5 を使って16GBの USB メモリに焼きました。

インストールが終われば、サブスクリプションを認証し、リビルドに必要なパッケージを導入します。この時、CodeReady Linux Builder リポジトリ*6を一緒に有効化しました。

$ sudo subscription-manager register

$ sudo subscription-manager attach

$ sudo subscription-manager repos --enable codeready-builder-for-rhel-8-x86_64-rpms

$ sudo dnf update

$ sudo dnf groupinstall "Development Tools"

$ sudo dnf install rpm-utils rpm-build texinfo

RHELソースコード (SRPM) を入手

ソースコードもまた、RedHatカスタマーポータルから ISO で入手できます。ISO に含まれるものは BaseOSリポジトリとAppStreamリポジトリのみですが、それでも 20GB ほどあります。

これをマシンにコピーし、loop マウントしておきます。

$ sudo mkdir /media/rhel-source

$ sudo vim /etc/fstab
/home/kwatanabe/rhel-8.3-source-dvd.iso /media/rhel-source iso9660 ro,nofail,loop 0 0

$ sudo mount -a

なお、通常は不要ですが、手探るうえで必要になり CentOS のソースも入手しました。 CentOS は ソース ISOを配布していないため、rsyncリポジトリをミラーしました。

$ sudo mkdir -p /media/centos-source/

$ sudo rsync -avzP rsync://vault.centos.org/8.3.2011/BaseOS /media/centos-source/

RedHat に関わる商標やその他権利を有するモノを仕分け・排除

RHELCentOSを比較

環境が整えば、実際に手探っていきます。全パッケージを対象にすると、プロジェクトが立ち上がる位の作業量になってしまうので、BaseOS リポジトリに絞って作業します。

まずは RHEL にあって、CentOS にないものを探しました。もちろん、これはチートです。本来は、削除しないとダメなモノを全部自力で調べなければなりません。

f:id:kWatanabe:20210107120056p:plain
RHELCentOSSRPMを比較

CentOSリポジトリには過去バージョンも含まれていますが、それ以外で RHEL にあって、CentOS にない SRPM は以下の通りでした。

  • libica
  • libzfcphbaapi
  • memstrack
  • openssl-ibmca
  • qclib
  • redhat-indexhtml
  • redhat-logos
  • redhat-release
  • s390utils
諸元から削除された理由を調べる

アタリが付いたら、rpm コマンドを使って中身を調べていきます。まずは、rpm -p -qi を使って諸元を調べました。例えば、libicaだと以下の結果となります。

$ rpm -p -qi libica-3.7.0-2.el8.src.rpm
Name        : libica
Version     : 3.7.0
Release     : 2.el8
Architecture: s390x
Install Date: (not installed)
Group       : System Environment/Libraries
Size        : 552759
License     : CPL
Signature   : RSA/SHA256, 2020年07月21日 17時52分31秒, Key ID 199e2f91fd431d51
Source RPM  : (none)
Build Date  : 2020年07月20日 21時56分29秒
Build Host  : s390-046.build.eng.bos.redhat.com
Relocations : (not relocatable)
Packager    : Red Hat, Inc. <http://bugzilla.redhat.com/bugzilla>
Vendor      : Red Hat, Inc.
URL         : https://github.com/opencryptoki/
Summary     : Library for accessing ICA hardware crypto on IBM z Systems
Description :
A library of functions and utilities for accessing ICA hardware crypto on
IBM z Systems.

Architectureや、Description の内容から、これは IBM z 向けのパッケージと分かります。CentOSIBM z をサポートしないため、含まれていないようです。同様に、libzfcphbaapi、openssl-ibmca、qclib、s390utilsもアーキテクチャ違いによるものでした。

中身からCentOSで削除された理由を調べる

x86_64向けやアーキテクチャ非依存にもかかわらず CentOS に含まれていないものは、RedHatの商用や権利関係で削除された可能性があります。rpm -ivhSRPMを展開し、rpmbuild -bpでパッチ群をマージして、実物を確認します。

$ rpm -ivh redhat-indexhtml

$ rpmbuild -bp ~/rpmbuild/SPECS/redhat-indexhtml.spec

$ ls ~/rpmbuild/BUILD/redhat-indexhtml

確認の結果、redhat-indexhtml、redhat-logos は RedHat の商標やロゴが含まれており、NGなものでした。redhat-release は、RHELリポジトリ登録のためのパッケージで RHEL互換ディストリビューション では置き換えが必要なものです。

一方、memstrack は問題があるパッケージには見えませんでした。また、大元のソースコードGitHub で公開されています。

github.com

CentOSリポジトリを見直すと、 BaseOS リポジトリから AppStream リポジトリに移動されていました。理由は不明ですが、削除しなければならないパッケージではなさそうです。

これらの結果から、RHEL 8.3 の BaseOS リポジトリでは以下のものを削除し、オリジナルのものに置き換える必要があると分かりました。

パッケージ 概要
redhat-indexhtml RHELのオフラインマニュアル
redhat-logos RHELのロゴ・商標の画像集
redhat-release RHELリポジトリ情報

互換性が崩れてしまうものがあれば補填するモノを開発

抽出したモノのうち、redhat-logosとredhat-releaseは代替となる画像データや、リポジトリを用意しないとディストリビューションとして完結しなくなりそうです。

redhat-indexhtmlは、無くても良い気がしますが、未確認です。

リビルド

だいたいの流れ

リビルド’作業そのものは、通常のRPMのリビルドと大きな差はありません。dnf builddepで依存パッケージを導入して、RPMをビルドしていきます。

しかし、大きな注意点があります。RHELとの互換性を担保するために、RedHatがビルドに使ったライブラリと同じものを使わなければなりません。これはバージョンや適用されているパッチも完全に一致しなければダメです。

REHLのリポジトリに依存パッケージがあればいいですが、無い場合何が使われているのかを調査し、環境を整える必要があります。ここが、RHEL互換ディストリビューション構築で重要かつ困難な所になります。

依存パッケージを調べる

私は以下の流れで作業を進めました。

  1. dnf builddepを試す → 何事も無く完了したら、rpmbuild -baでビルドする
  2. RedHatのマニュアル「RHEL 8 の導入における検討事項」で、RHEL8.0~8.3で統廃合されたパッケージに該当しないか調べる
  3. FedoraやEPELなど、RHELの upstream に含まれていないか調べる
  4. RPM pbone.net などの RPM 検索サイトなどから調べる
  5. Google 検索する

結果としては、大きく以下の4種類(AND/OR含む)となりました。

もしかしたら、RedHat はビルド環境を RHEL ではなく、upstream の Fedora をベースに構築しているのかもしれません。なお、具体的なパッケージ名は当日ツイートした以下の通りです。

なお、調査に使ったリソースは以下からたどれます。

access.redhat.com

getfedora.org

fedoraproject.org

rpm.pbone.net

ここで終了・おわりに

ここまで絞り込めれば、ビルドが通らなかったパッケージで使われているライブラリのバージョンを調べ、ビルドできるように進めていくところですが、ここで2020年が終わりました。

真に難しいところの1歩手前で時間切れとなりましたが、それでも CentOS という先人の成果をフル活用してもかなりパワーを使ったので、世のRHEL互換ディストリビューションの構築がいかに大変かがうかがい知れます。

それでは。

*1:RedHat Enterprise Linux

*2:単なる再構成ではなく、RHEL とソフトウェア的な互換性が担保されている必要があります。RHELエンタープライズディストリビューションとしてデファクトに近い存在であるので、RHEL と同じ機能・品質を持ち、かつ特定の組織にロックインされないディストリビューションを求める人が大勢いるのです。

*3:RHEL互換ディストリビューションの作成が「開発用途」に含まれるかどうかは不明。限りなく黒に近いグレーな気がしますので、このライセンスで用達したソースを元に再構築したディストリビューションの再配布は止めた方がいいとおもいます。

*4:https://access.redhat.com

*5:https://www.balena.io/etcher/

*6:ドライバやパッケージのビルド環境など、開発用途のツールキットを収録した追加リポジトリCentOSでいうところの PowerTools に相当。

RHEL互換ディストリの作り方を手探ってみる1(大晦日ハッカソン)

  • 2020年はSTAY HOMEのため、帰省できないため何かやりたかった
  • ひとりでもくもく会をやってもいいのだが、折角だから大晦日ハッカソンに参加してみる
  • 自己啓発として、RHEL互換(かも知れない)ディストリの作り方を手探ってみることにした

晦日ハッカソン

2012年より、毎年大晦日に開催されているイベントっぽい何かです。

omisoka-hackathon.connpass.com

内容としては、

以上。

2020年は、コロナ渦対策のSTAY HOMEで、旅行も帰省もできないので。ちょっと参加してみようと思ったのです。

何をやるか

とはいいつつも、何かをこれまで作ってきたわけでもなく、何か作りたいものがあるわけでもない。1日で材料から集めてモノを作っても、中途半端になるに決まってる。

そこで、「何かモノを作るのではなく、知見をためよう。知見であれば中間報告であっても、ブログで公開するなり、会社の勉強会で語るなりすれば、それはれっきとした成果物になるだろう。」と言い訳をして、手を動かして何かを手探っていこうと思った次第です。

ネタ探し

1ヶ月前のとある出来事

ところで、1月ほど前にある出来事があり、業界でちょっとした騒動になりました。

blog.centos.org

超意訳すると、RHEL*1という商用Linuxディストリビューションのクローンとして無償公開されていた CentOS が、突如2021年に開発終了するとアナウンスされたものです*2

元々、CentOS はクローン元の RHEL と同様に2028年まで開発されると予想されていたので、これに期待して構築されたシステムは RHEL の購入、 Oracle Linux などの他のクローンへの移行、Debian などの他のディストリへの移行のいずれかを強いられることになりました。

RHELクローン(互換ディストリビューション)とは

そもそも、RHELクローンとは何でしょうか。

RHELは商用のLinuxディストリビューションですが、製品の多くはオープンソースプロジェクトの成果物のうえに成り立っています。そのため、RHEL はそれ自身もオープンソースとして公開されています*3

そして、RHELEULA*4には、以下の記載があります。

1. License Grant. Subject to the following terms, Red Hat, Inc. ("Red
   Hat") grants to you a perpetual, worldwide license to the Programs
   (most of which include multiple software components) pursuant to
   the GNU General Public License v.2. The license agreement for each
   software component is located in the software component's source
   code and permits you to run, copy, modify, and redistribute the
   software component (subject to certain obligations in some cases),
   both in source code and binary code forms, with the exception of
   (a) certain binary only firmware components and (b) the images
   identified in Section 2 below. The license rights for the binary
   only firmware components are located with the components
   themselves. This EULA pertains solely to the Programs and does not
   limit your rights under, or grant you rights that supersede, the
   license terms of any particular component.
 
2. Intellectual Property Rights. The Programs and each of their
   components are owned by Red Hat and other licensors and are
   protected under copyright law and under other laws as
   applicable. Title to the Programs and any component, or to any
   copy, modification, or merged portion shall remain with Red Hat and
   other licensors, subject to the applicable license. The "Red Hat"
   trademark and the "Shadowman" logo are registered trademarks of Red
   Hat in the U.S. and other countries. This EULA does not permit you
   to distribute the Programs or their components using Red Hat's
   trademarks, regardless of whether the copy has been modified. You
   may make a commercial redistribution of the Programs only if (a)
   permitted under a separate written agreement with Red Hat
   authorizing such commercial redistribution, or (b) you remove and
   replace all occurrences of Red Hat trademarks. Modifications to the
   software may corrupt the Programs. You should read the information
   found at http://www.redhat.com/about/corporate/trademark/ before
   distributing a copy of the Programs.

要するに、オープンソースライセンスの下に公開されているソースコードは、RedHatの商標やロゴ、その他独占的な権限を有するものを全て排除した場合に限り、そのライセンスに抵触しない範囲で再利用して良いことになっています。

そこで、RHELソースコードから RedHat が権利を有するものを削除し、再配布されている Linux ディストリビューションがあります。それが、RHEL クローンと呼ばれるものです。

過去には様々な RHEL クローンがありましたが、2020年現在、プロダクトに耐えうるものは Oracle LinuxAsianux です*5Asianux は有償のため、無償のものは Oracle Linux のみです。なお、このアナウンスを受けて、CentOS に代わる新たな RHEL クローンを作ろうというプロジェクトがいくつか立ち上がっています。

rockylinux.org

www.projectlenix.org

RHELクローンの作り方

さて、やっと本題になりました。大晦日ハッカソンでは、この RHELクローンの作り方を手探っていくことにします。

大まかには以下の流れになるはずです。

  1. RHELサブスクリプションを得る
  2. RHELソースコード (SRPM) を入手
  3. ソースコードの全てから、RedHat に関わる商標やその他権利を有するモノを仕分け・排除
  4. 上記の結果、動かなくなってしまうものがあれば、それを補填するモノを開発
  5. リビルド

SRPMを入手してビルドするだけなら、RHEL を開発で使ったことがある人なら難なくできる作業です。しかし、RedHat が権利を持つものを削除したうえで、RedHatの権利を侵害すること無く同等の機能を補填し、RHEL と同じ動きをするものを作るとなると、簡単にはいきません。

RHEL クローンは、莫大な資金と人的リソースを費やして開発されているものなので、大晦日にちょっと手を出したくらいで何か形になるとは思いません。ひとつでも知見を得られれば、それで成功だと思っています*6

おわりに

  • 2020年の大晦日ハッカソンでは、RHEL クローン(RHEL 互換ディストリビューション)の作り方を手探っていく
  • 目標は、何らかの知見を得ることで、RHEL クローンを作ることでは無い(そもそも無理)
  • 得られた知見は、別途整理して、何らかの形でまとめる

*1:Red Hat Enterprise Linux

*2:より正確には、RHEL のクローンとしての CentOS が開発終了。RHEL のアップストリームとしての CentOS Stream は継続される。

*3: RHELサブスクリプションを持っている人には全てのソースコードを詰め込んだ 20GB強もの ISO ファイルがダウンロードできる

*4:End User License Agreement。https://www.redhat.com/ja/about/agreements

*5: ただし、汎用品として。他にも Red Hawk Linux などの特定用途に特化した製品はあります。

*6:そもそも環境の構築方法を調べるだけで1日終わるかもしれないし…

Proxmox VEのLXCでホストにあるISOをマウントする

  • Proxmox VEのLXCゲストは、ホストに保存されているISOファイルをマウントできない
  • メディアからaptやyumをしたい場合など、ニッチだけど地味に困る
  • ホストに保存されているISOの中身を、LXCゲストに見せる方法を整理する

検証環境

  • Proxmox VE 6.3

Proxmox VEでは、LXCゲストでISOをマウントできない

Proxmox VEは、KVMゲストにISOファイルをマウントさせることはできるが、LXCゲストではWebインタフェースに項目が無く、マウントさせることはできない。

LXCでは、ISOからOSをインストールする必要が無いため、割り切った仕様にしているのだとは思うが、DVDメディアやISOファイルをリポジトリとして yum や apt を使いたい場合に困る*1

ダメな例1

Webインタフェースで設定する代わりに、以下のように/etc/pve/lxc/<VMID>.confに設定を書き込んでも、エラーになりゲストが起動しなくなる。

mp0: /path/to/isofile,mp=/path/to/mountpoint,ro=1 ★ダメな例1★

ダメな例2

Porxmox VE のLXCゲストの設定ファイルには、通常のLXCのオプションをそのまま書くこともできるので、以下のようにloopマウントをやりたくなるが、これもうまくいかない。なお、bindマウントはできる。

lxc.mount.entry = /path/to/isofile path/to/mountpoint iso9660 loop,nofail,ro 0 0 ★ダメな例2★

ISOファイルをLXCゲストに送り込んで、ゲストの中からloopマウントする方法も考えられるが、これは Permission Denied になる。回避するためには、apparmorの設定を緩めたうえで、特権コンテナにし、さらにケーパビリティを調整しないといけない(ケーパビリティに関しては過去記事参照)。

kwatanabe.hatenablog.jp

素のLXCならともかく、Proxmox VE のようなプラットフォームでホストの設定を変えると、不具合の原因になりかねないので避けたい。

解決策

「ダメな例2」でしれっと書いたが、bindマウントはできる。そこで、力業だけどもホストでloopマウントして、そのディレクトリをbindマウントして投影すればよい。

なお、loopマウントのマウントポイントをbindマウントのマウント元に指定しないと、ISOファイルの中身は見えない。横着して、複数のマウントポイントを集めた親ディレクトリをbindマウントしてもISOの中は見えない。

最終的にはホストで以下のようにする。

  • ISOファイルをloopマウント
$ sudo vim /etc/fstab
/path/to/isofile /path/to/mountpoint-host iso9660 ro,loop,nofail 0 0
$ sudo vim /etc/pve/lxc/<VMID>.conf
lxc.mount.entry = /path/to/mountpoint-host path/to/mountpoint-guest none ro,bind,nofail 0 0

これで、ISOファイルの中身がゲストでも中身が見えるようになる。なお、Readonlyとはいえど、ホストとゲストの間で穴を開けることになるので、セキュリティ面では大変よろしくない。使い終わったら、さっさと元に戻すのが吉。

おわりに

  • Proxmox VEでは、LXCゲストにISOファイルをマウントする手段を提供していない
  • ゲストの中からマウントするには、セキュリティ設定をかなり調整しないとダメ
  • ホストでloopマウントしてから、bindマウントすれば間接的にISOファイルをマウントできる
  • セキュリティ面では大変宜しくないため、使い終わったら元に戻すこと

*1:CentOS 7のEverything ISOを使う時とか、ISOの中身をHTTPで公開してPXEサーバを作りたい時とか、ニッチだけど地味に困る

aptリポジトリにおける優先度のルールと確認方法

  • apt には、リポジトリやパッケージが競合する場合に、自動選択する仕組みがある
  • これは優先度に基づいて行われるが、デフォルトの優先度は状況によって異なり「リポジトリを登録したのに使われていない」なんてことがまれに生じる
  • apt のリポジトリ優先度の考え方と、設定の確認・変更方法をまとめる

検証環境

apt における優先度付けのルール

apt の優先度は静的な順序付けではない

実は apt リポジトリの優先度は、静的に順序付けされるものではない。ユーザが設定ファイルで指定しない限り、優先度は動的に設定される。また、優先度の絶対値にも意味がある。

apt_preferencesのmanページを見れば、具体的なルールが記載されている。

manpages.debian.org

デフォルトの優先度

特に指定のない場合、apt は以下の優先度を付与する。

優先度 諸元
1 Releaseファイルに NotAutomatic: yes が指定されているが、ButAutomaticUpgrades: yes が指定されていないリポジトリのパッケージ
100 (1) Releaseファイルに NotAutomatic: yesButAutomaticUpgrades: yes が指定されているリポジトリのパッケージ
(2) 既にシステムにインストール済みのパッケージ
500 他の条件に合致していないパッケージ
990 ターゲットリリースとして指定されたリポジトリのパッケージ

なお、ターゲットリリースとは、/etc/apt/apt.confAPT::Default-Releaseに指定しているリリースか、コマンド実行時に -t オプションで指定したリリースを指す。

絶対値に応じた制御

apt は、リポジトリ同士の相対的な比較とは独立して、絶対値による評価も行う。

  • 優先度が負値のリポジトリからは、パッケージをインストールしない
  • 優先度が1000以上のリポジトリは、たとえパッケージをダウングレードすることになってもインストールする
  • 優先度0は未定義動作

ただし、デフォルトの優先度でこれらの値になることはない。ユーザが /etc/apt/preferences で明示的に優先度を設定して、パッケージやリポジトリブラックリスト化や優先付けができるようになっている。

優先度を手動で設定する方法

リポジトリの優先度をaptに決めさせるのでなく、手動で設定したい場合は /etc/apt/preferencesに設定を追記する。

なお、優先度はリポジトリではなくパッケージごとに設定できる。

Package: <パッケージ>
Pin: release a=<リリース名>
Pin-Priority: <優先度の値>

例えば、testing リポジトリexperimental リポジトリと同様に、「明示的にターゲットリリースにしない限り、インストールもアップグレードもされないようにする」には、以下のようにする。

Package: *
Pin: release a=testing
Pin-Priority: 1

また、一時的にターゲットリリースとして設定するには、-t オプションを指定する。

$ sudo apt install -t testing ...

恒久的にターゲットリリースとして設定するには、/etc/apt/apt.confAPT::Default-Releaseを設定する。

APT::Default-Release "testing";

要するに

これらのルールに基づいて整理すると、以下のようになる。

リポジトリの優先度 指定方法 挙動
1000~ /etc/apt/preferences ダウングレードであっても採用する
991~999 /etc/apt/preferences ダウングレードで無い限り、ターゲットリリースにパッケージがあっても、こちらを採用する
990 -tオプション
/etc/apt/apt.conf
ターゲットリリース。明示的に指定した際に採用する
501~989 /etc/apt/preferences ターゲットリリースを指定したものの、目的のパッケージが無い場合に採用する
500 自動 一般的なリポジトリのデフォルト値
101~499 /etc/apt/preferences 他のリポジトリに目的のパッケージが無い場合に採用する
100 自動 インストール済みのパッケージ
1~99 /etc/apt/preferences
1は自動
他のリポジトリに存在せず、また、まだインストールされていない場合(アップグレードでない場合)に限り採用する
0 使用禁止 未定義動作
負値 /etc/apt/preferences いかなる場合でも採用しない

設定の確認方法

特に何も指定しない場合

現状の設定は apt-cache policyコマンドで確認できる。

$ apt policy
Package files:
 100 /var/lib/dpkg/status
     release a=now
   1 http://deb.debian.org/debian experimental/main amd64 Packages
     release o=Debian,a=experimental,n=experimental,l=Debian,c=main,b=amd64
     origin deb.debian.org
 100 http://deb.debian.org/debian buster-backports/main amd64 Packages
     release o=Debian Backports,a=buster-backports,n=buster-backports,l=Debian Backports,c=main,b=amd64
     origin deb.debian.org
 500 http://ftp.debian.org/debian buster/main amd64 Packages
     release v=10.6,o=Debian,a=stable,n=buster,l=Debian,c=main,b=amd64
     origin ftp.debian.org
...(略)...

この環境の例では、以下の設定となっていることが分かる。

  • 不安定なexplerimentalリポジトリは、インストール時に-t experimentalと明示的に指定しない限り、過去に導入済みであったとしても、自動では導入・更新されない。
  • 将来バージョンからのバックポートであるbuster-backportsは、安定版リポジトリと競合するため、インストール時に-t buster-backportsと明示的に指定しないと導入されないが、一度導入した後は自動更新の対象とされる。
  • 標準の安定版リポジトリは、他のリポジトリがターゲットリリースとして指定されない限り、これを使う。

なお、experimentalリポジトリについては以下の過去記事参照。 kwatanabe.hatenablog.jp

ターゲットリリースを明示的に指定した場合

ここで、-t experimentalを付けて、明示的にexperimentalリポジトリをターゲットリリースにしてみる。

$ apt policy -t experimental
Package files:
 100 /var/lib/dpkg/status
     release a=now
 990 http://deb.debian.org/debian experimental/main amd64 Packages
     release o=Debian,a=experimental,n=experimental,l=Debian,c=main,b=amd64
     origin deb.debian.org
 100 http://deb.debian.org/debian buster-backports/main amd64 Packages
     release o=Debian Backports,a=buster-backports,n=buster-backports,l=Debian Backports,c=main,b=amd64
     origin deb.debian.org
 500 http://ftp.debian.org/debian buster/main amd64 Packages
     release v=10.6,o=Debian,a=stable,n=buster,l=Debian,c=main,b=amd64
     origin ftp.debian.org
...(略)...

experimentalリポジトリの優先度が990になっていることが分かる。

まとめ

  • apt リポジトリの優先度は、インストール状況やパッケージの品質に応じて動的に調整されるようになっている
  • 一時的にリポジトリの優先度を高めたい場合は、-tオプションでターゲットリリースとして指定する
  • リポジトリによっては、インストール済みのパッケージのアップグレードでも、都度ターゲットリリースと指定しないと更新対象とならない場合もある
  • 現状の設定状況は apt-cache policy コマンドで確認できる

LXCコンテナの中から、chronydでホストの時刻を補正・配信する

  • LXCの標準設定ではセキュリティ上の理由から chrony が動かせないようになっている
  • コンテナはホストの時刻を使うため、NTPクライアント(chronyc)は必要はないが、NTPサーバ(chronyd)をコンテナで運用したいことはままある*1
  • そこで、セキュリティリスクを承知のうえで、コンテナの中で chronyd を動かして、ホストの時刻を修正したり、時刻を配信したり、できるようにする

検証環境

  • Debian 10.6 (buster) amd64 (ホスト、コンテナ共に)
  • LXC 3.1.0
  • 特権コンテナ

LXCコンテナ内でのchronyの挙動

とりあえず動かして見る

普通にLXCでコンテナを作った場合、たとえそれが特権コンテナでも chronyd は動作しない。

$ sudo apt install chrony
$ sudo systemctl restart chronyd
$ sudo systemctl status chronyd
● chrony.service - chrony, an NTP client/server
   Loaded: loaded (/lib/systemd/system/chrony.service; enabled; vendor preset: enabled)
   Active: inactive (dead)
Condition: start condition failed at Wed 2020-11-18 04:18:48 UTC; 2min 14s ago
           └─ ConditionCapability=CAP_SYS_TIME was not met
     Docs: man:chronyd(8)
           man:chronyc(1)
           man:chrony.conf(5)

Nov 18 04:18:48 tim systemd[1]: Condition check resulted in chrony, an NTP client/server being skipped.

どうして?

キモになるのはここ。

ConditionCapability=CAP_SYS_TIME was not met

LXCの特権コンテナでは、init が root 権限で実行されている。そのため、何も制限をしないとコンテナの root はホストの root と同等の権限になる*2

そこでセキュリティ担保のため、コンテナ内の root からは特にセキュリティリスクの高い権限が剥奪されている。CAP_SYS_TIMEはシステムタイマやタイマデバイスを操作するための権限。他にどのような権限があるかは以下の man ページを参照。

linuxjm.osdn.jp

自分の環境では、全てのコンテナで共通的に読み込まれる config ファイルで以下のようにいくつかの権限が剥奪されていた。

$ cat /usr/share/lxc/config/common.conf
...
# Drop some harmful capabilities
lxc.cap.drop = mac_admin mac_override sys_time sys_module sys_rawio
...

lxc.cap.dropは、ここに定義した権限を剥奪するパラメータ。ここで sys_time が設定されているので、CAP_SYS_TIMEが剥奪されている。これが、本事象の原因となる。

対策

上記のパラメータから sys_time を外してしまいたいけども、それをすると全てのコンテナに権限が付与されてしまう。また、困った事にLXCに権限を与えるパラメータはない*3

仕方がないので、lxc.cap.dropを再定義する。基本的に lxc.cap.drop の設定は重ねがけだが、空欄を指定するとそれまで指定した全ての設定がクリアされるという仕様になっている。

linuxcontainers.org

そこで、共有の config は手を付けずに、chronyd を導入したいコンテナの config の一番末尾で空欄指定で一度パラメータをクリアしてから、sys_time を除いた権限を指定する。

$ sudo vim /var/lib/lxc/【コンテナ名】/config
...
# Drop some harmful capabilities
lxc.cap.drop = 
lxc.cap.drop = mac_admin mac_override sys_module sys_rawio
...

動作確認

権限を付与するためにコンテナを再起動する。

$ sudo lxc-stop -n 【コンテナ名】
$ sudo lxc-start -n 【コンテナ名】

確認してみる。

$ sudo systemctl status chronyd
● chrony.service - chrony, an NTP client/server
   Loaded: loaded (/lib/systemd/system/chrony.service; enabled; vendor preset: enabled)
   Active: active (running) since Wed 2020-11-18 07:04:41 UTC; 10s ago
     Docs: man:chronyd(8)
           man:chronyc(1)
           man:chrony.conf(5)
  Process: 112 ExecStart=/usr/sbin/chronyd $DAEMON_OPTS (code=exited, status=0/SUCCESS)
  Process: 124 ExecStartPost=/usr/lib/chrony/chrony-helper update-daemon (code=exited, status=0/SUCCESS)
 Main PID: 122 (chronyd)
    Tasks: 2 (limit: 4915)
   Memory: 1.3M
   CGroup: /system.slice/chrony.service
           ├─122 /usr/sbin/chronyd -F -1
           └─123 /usr/sbin/chronyd -F -1

Nov 18 07:04:41 tim systemd[1]: Starting chrony, an NTP client/server...
Nov 18 07:04:41 tim chronyd[122]: chronyd version 3.4 starting (+CMDMON +NTP +REFCLOCK +RTC +PRIVDROP +SCFILTER +SIGND +ASY
NCDNS +SECHASH +IPV6 -DEBUG)
Nov 18 07:04:41 tim chronyd[122]: Initial frequency -10.270 ppm
Nov 18 07:04:41 tim chronyd[122]: Loaded seccomp filter
Nov 18 07:04:41 tim systemd[1]: Started chrony, an NTP client/server.
Nov 18 07:04:47 tim chronyd[122]: Selected source 23.131.160.7

これで、コンテナの中から、ホストのシステムタイマーを制御できるようになる。

余談だが、ホストで既に chronyd や systemd-timesyncd など、タイマを制御するデーモンが動いているなら、止めておかないと大変なことになる。

おわりに

  • LXCでは、セキュリティ上の理由から、標準でタイマを制御する権限が剥奪されている
  • コンテナ内で chronyd を動かすために、特定のコンテナからタイマを制御する権限を復活させるための手順を整理した
  • タイマの制御は、かなりプリミティブな権限なのでくれぐれも自己責任

*1:少なくとも kWatanabe は

*2:root以外のユーザはuser namespaceによりremapされるので、コンテナ内のユーザとホストのユーザは、同じユーザ名でも異なるユーザとして扱われる。

*3:lxc.cap.keep というパラメータがあるが、これは「指定した権限以外を全て剥奪する」効果なので、今回の用途に適さない。

systemdで多段プロキシ・リバースプロキシを作る

  • systemd だけを使って、多段プロキシやリバースプロキシを作る
  • 用途はいろいろあるけど、深く突っ込んじゃイケない

背景

ユースケースはあえて挙げないけど、多段プロキシできるフォワードプロキシとか、一時的なリバースプロキシなど、便利なプロキシサーバが時たま必要になる。

Squidやnginxなどの汎用のものから、Poundやdelegateなどの専用のものまでいろいろな方法があるけども、諸事情*1でこれらが使えない時もある。

そんな時には systemd が使える。systemd は Linux の比較的新しい*2 init システムだが、 init とは思えないほど多機能*3で、実はプロキシサーバが作れてしまう。その手順をまとめる。

検証環境

systemd でプロキシサーバを作る

socket ファイル

まず、リクエストを受け付けるための socket のユニットファイルを用意する。

ListenStreamにリクエストを待ち受けるインタフェースのIPアドレスとポート番号を指定する。

$ sudo vim /etc/systemd/system/proxy.socket
[Unit]
Description=HTTP Proxy Socket

[Socket]
ListenStream=0.0.0.0:80

service ファイル

次に、実際にリクエストを経由する service のユニットファイルを用意する。

ExecStartsystemd-socket-proxydのパスを指定し、第一引数に転送先(多段プロキシなら次のプロキシサーバ、リバースプロキシなら内側のWebサーバ)のIPアドレスとポート番号を指定する。

注意点としては、2つのユニットファイル名はそろえておかないとダメ。(今回は proxy

$ sudo vim /etc/systemd/system/proxy.service
[Unit]
Description=HTTP Proxy Service

[Service]
ExecStart=/usr/lib/systemd/systemd-socket-proxyd 192.168.1.1:5963
PrivateTmp=yes

起動

後はお約束。

systemdをリフレッシュして、

$ sudo systemctl daemon-reload

socketを起動して、

$ sudo systemctl start proxy.socket

うまくいっているようなら、永続化する。

$ sudo systemctl enable proxy.socket

まとめ

  • systemd 単体で多段プロキシもしくはリバースプロキシとして使える、プロキシサーバを構築した

*1:手間をかけたくない場合や、顧客の環境で余計なソフトを入れられない場合、など、いろいろと。

*2:sysvinitやupstartと比べて

*3:プロキシのほかに systemd 単体で Docker みたいなコンテナを作れたりもする。systemd-nspawn とかでググるとよろしい。

ローカルのGitBucketをGitHubに移行する(その3)

  • ローカルサーバのGitBucketで管理していたリポジトリGitHubに移行したい
  • 過去記事では、リポジトリの移行と、Issue と Pull Request のダンプを行った
  • 今回は、GitBacket からダンプした Issue と Pull Request を GitHub に投稿する
  • なお、既に close され branch が消えていた Pull Requset は登録できないので、他のIssueやPull Requestの番号に齟齬が生じないように調整するにとどめる

その1とその2はこちら。

kwatanabe.hatenablog.jp

kwatanabe.hatenablog.jp

背景

過去記事参照。

GitHubAPI

Issue と Pull Request を作って閉じるために必要なAPIは以下の通り。

Issue の投稿

GitHub で Issue を作るときは、 Create an issueAPI をコールする。

developer.github.com

パラメータは以下の通り。残念ながら、MileStone との対応づけはない。

コール先:POST /repos/:owner/:repo/issues

パラメータ 概要 GitBucketからダンプしたモノとの対応
title string Issueのタイトル issue['titile']
body string 本文 issue['body']
milestone integer マイルストン番号 なし
labels array of strings ラベル名 issue['labels'][N]['name']
assignees array of strings アサイン先 issue['assignees'][N]['login']

Issue へのコメントの投稿

作った Issue にコメントする場合は、 Create an issue commentAPI をコールする。

developer.github.com

コール先:POST /repos/:owner/:repo/issues/:issue_number/comments

パラメータは以下の通り。issue_comments は、issuecomment_url に格納されているURLから、更にAPIをコールして取得したもの。その2 では、ダンプした issue の中に comments という key を追加して埋め込んでいる。

パラメータ 概要 GitBucketからダンプしたモノとの対応
body string コメント本文 issue_comments[N]['body']

Issue のクローズ

Issue をクローズする場合は、Update an issueAPI をコールする。

developer.github.com

コール先:PATCH /repos/:owner/:repo/issues/:issue_number

パラメータは以下の通り。クローズしたいだけなら、state だけ送ればいい。

パラメータ 概要 GitBucketからダンプしたモノとの対応
title string Issueのタイトル issue['titile']
body string 本文 issue['body']
milestone integer マイルストン番号 なし
labels array of strings ラベル名 issue['labels'][N]['name']
assignees array of strings アサイン先 issue['assignees'][N]['login']
state string openclosed issue['state']

Pull Request の投稿

Pull Request を作るときは、 Create a pull requestAPI をコールする。

コール先:POST /repos/:owner/:repo/pulls

developer.github.com

パラメータは以下の通り。

パラメータ 概要 GitBucketからダンプしたモノとの対応
title string タイトル pull['title']
body string 本文 pull['body']
head string 改修後のブランチ名 pull['head']['ref']
base string 改修前のブランチ名 pull['base']['ref']
draft boolean Draft Pull Requestか pull['draft']

Pull Request へのコメントの投稿

Pull Request にコメントを投稿するときは、Issue の時と同様に Create an issue commentAPI をコールする。

Pull Request のマージ

Pull Request をマージするときは、 Merge a pull requestAPI をコールする。

コール先:PUT /repos/:owner/:repo/pulls/:pull_number/merge

developer.github.com

パラメータは以下の通り。ただマージしたいだけなら sha だけ合わせておけばいい。

パラメータ 概要 GitBucketからダンプしたモノとの対応
commit_title string コミットメッセージのタイトル -
commit_message string コミットメッセージ -
sha string Pull RequestのheadのSHA pull['head']['sha']
merge_method string マージする方法 -

Pull Request のクローズ

Pull Request をクローズする場合は、Update a pull requestAPI をコールする。

developer.github.com

コール先:PATCH /repos/:owner/:repo/pulls/:pull_number

パラメータは以下の通り。クローズしたいだけなら、state だけ送ればいい。

パラメータ 概要 GitBucketからダンプしたモノとの対応
title string タイトル pull['title']
body string 本文 pull['body']
base string 改修前のブランチ名 pull['base']['ref']
draft boolean Draft Pull Requestか pull['draft']
state string openclosed pull['state']

手順

これらAPIを用いてGitHubに Issue と Pull Request を移行する。

アクセストークンを発行する

その1で移行したリポジトリに、その2でダンプした Issue を投稿する。

GitHubでもGitBucketの時と同じように、アクセストークンを発行する。

  1. GitHubにログインする
  2. アカウントのマイページにアクセスして
  3. Settings を選択
  4. Personal access tokens を選択
  5. Generate new token を選択
  6. Note にテキトーな名前を入れて
  7. repo にチェックをして
  8. Generation Token を選択する

するとアクセストークンが発行されるので、メモしておく。

f:id:kWatanabe:20201011220840p:plain
GitHubのアクセストーク

ダンプした Issue と Pull Request を登録する

注意点は以下の通り。

  • Issue 番号 と Pull Request 番号は同じ採番体系になっており、これらは投稿した順に採番される。そのため、元のIssue番号、Pull Request番号の通りに投稿する。
  • GitBucket で使っていたユーザ名が、GitHub でも使えるとは限らない。Issue の assignees には GitHub に存在するユーザ名に置換した上で投稿する。
  • クローズ済みの Pull Request について、Marge した後、リポジトリから head となる Branch が削除されていたりすると投稿できなくなる。私のリポジトリの場合、クローズ済みの Pull Request にあまり価値はない*1ので、ダミーの Issue を投稿して番号をずらして回避する。

特に工夫点はなく、愚直に API をコールし続ける。その2で作ったコードとマージして完成。

実際のコード

長くなったので、GitHubに公開します。

github.com

結果

  • GitBacket 上の Issue と open な Pull Request は Issue 番号やコメントの内容も含めて移行できた。
  • クローズした Pull Request はダミーIssue として移行した。
  • これで、ローカルのGitBucketのGitHubへの移行は完了となった。

*1:一方で、クローズされていても検討の課程が記録されている Issue は、ある意味リポジトリ本体よりも大切。

Debian10でGeForce RTX 3080を使ってCUDAするための準備

先日、激戦を勝ち抜き GeForce RTX 3080 を買いました。

とはいいつつも、私はPCでゲームを殆どしないので、メインマシンは75W級グラボの最高傑作だと思っている Quadro P2200 をそのまま使います。

では、なぜ GeForce RTX 3080 を調達したかなんですが、自宅のなんちゃって IaaS の GPUコンテナ で CUDA するためです。しかし、GeForce RTX 3080 はリリースされたばかり。debian-backportsでもSidでもドライバが古く、3080 を動かせません。

なので、3080 を動かせるドライバに入れ替えることにしました。

2021/02/03 追記

  • 半年の歳月を経て、buster-backports にも、GeForce RTX 3080 対応のドライバがリリースされました。現在は、以下の記事に頼らなくても、debian-backports リポジトリで対応できます。

GeForce RTX 3080 を動かせるLinuxドライバ

GeForce RTX 3080 を使うためには、455.23 以上でなければなりません。2020/10/15現在、Debian の各リポジトリnvidia-driver のバージョンは以下の通りです。

リポジトリ バージョン
stable (buster) 418.152.00-1
backports (buster-backports) 450.66-1~bpo10+1
testing (bullseye) 450.66-1
unstable (sid) 450.66-1

不安定版のSidを以てしても、まだ足りません。NVIDIAから公式バイナリを入手する手もあるのですが、できれば apt で一元管理したい。そこで experimental リポジトリを使うことにしました。

experimental リポジトリ

要するに、Sid (不安定版)に取り込む前の開発用リポジトリです。ページには「一般ユーザはこのリポジトリのパッケージを使ってくれるな」(意訳)と書いてあります。

また、各種パッケージのページにも以下の警告が記述されています。

  • 警告: このパッケージは experimental ディストリビューションのものです。つまり、おそらく不安定でバグがあり、それどころかデータの損失を起こすかもしれません。使用前には、変更履歴やその他の参照可能なドキュメントを必ず調べてください。

wiki.debian.org

2020/10/15現在、experimental リポジトリnvidia-driver は455.23です。ちょうど GeForce RTX 3000 をサポートし始めたバージョンです。

導入

手順自体は、debian-backports や Sid を使う時と変わりません。/etc/apt/sources.listにexperimentalリポジトリを追加して、apt するだけです。

$ sudo vim /etc/apt/sources.list
deb http://deb.debian.org/debian/ experimental main non-free contrib

$ sudo apt update

$ sudo apt -t experimental install nvidia-driver

運が良ければ、ちゃんと稼働してくれることでしょう。

2020/11/28 追記

  • もし依存パッケージが解決できないようなら testingbuster-backports リポジトリも追加してください。
  • testing を登録した際、システム全体 testing にアップグレードされることを防ぐために /etc/apt/apt.confAPT::Default-Release "stable"; を付けると良いと思います。

2021/01/20 追記

  • ついに、testing に 460 のドライバが降りてきました。
  • なので、危ない橋を渡って experimental リポジトリを使わなくても、testing リポジトリを足すだけで大丈夫になりました。
  • また、今年の夏頃にリリース予定の Debian 11 では、何もしなくても RTX 3080 が使えるようになります。

2021/02/03 追記

  • 半年の歳月を経て、buster-backports にも、460 のドライバが降りてきました。
  • なので、今は Debian10 でも、buster-backports リポジトリを足すだけで、RTX 3080 が使えるようになりました。

結果

まだ、GeForce RTX 3080 は組み込んでいませんが、ドライバだけ入れ替えました。GPUをパススルーしたコンテナからも、ちゃんと新しいドライバが動いています。

$ sudo nvidia-smi
Thu Oct 15 00:43:32 2020       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 455.23.04    Driver Version: 455.23.04    CUDA Version: 11.1     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  GeForce GTX 105...  On   | 00000000:0A:00.0 Off |                  N/A |
| 29%   31C    P8    N/A /  75W |      1MiB /  4040MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

あとは、GeForce RTX 3080 を組み込んで CUDA が動けば問題なしですね。

2020/10/16追記

GeForce RTX 3080 組み込み完了しました。 コンテナの中から、ちゃんと GeForce RTX 3080 を認識できています。バッチリですね。

f:id:kWatanabe:20201016214235p:plain
nvidia-smiにてGeForce RTX 3080を認識している様子

参考

大元のGPUコンテナを作った時の記事はこちら。 kwatanabe.hatenablog.jp

ローカルのGitBucketをGitHubに移行する(その2)

  • ローカルサーバのGitBucketで管理していたリポジトリGitHubに移行したい
  • その1では、リポジトリ本体とユーザ情報、タグなどの移行を行った
  • 今回は、データベースで管理されている Issue を GitBacket からダンプする

その1はこちら。

kwatanabe.hatenablog.jp

その3はこちら。

kwatanabe.hatenablog.jp

背景

過去記事参照。

GitBucket から Issue を取り出す

アプリケーショントークンの取得

GitBucket は GitHub REST API v3 のサブセット*1を実装しているので、直接 H2 や PostgreSQL の中身を覗かなくてもある程度はなんとかなる。

REST API なので python なり perl なりでスクリプトでサクッとおわらせたい。そのためにまずはアプリケーショントークンを取得する。

  1. GitBucket にログイン
  2. Account settings を選択
  3. Applications を選択
  4. Generate new token の Token description にテキトーな説明を入れる
  5. Generate token を選択

するとトークンが表示されるのでメモっておく。

f:id:kWatanabe:20201011014344p:plain
アプリケーショントークンの取得

Issue と Pull Request の取得

アプリケーショントークンを使って、http://GITBACKET_SERVER_URL/api/v3/repos/USERNAME/REPONAME/issues に対して、APIをコールしてやれば以下のような JSON で降ってくる。

{
    'number': 2,
    'title': 'Issueのタイトル',
    'user': {
        'login': 'USERNAME',
        'email': 'USERNAME@example.com',
        'type': 'User',
        'site_admin': True,
        'created_at': '2018-08-08T12: 51: 17Z',
        'id': 0,
        'url': 'http://GITBACKET_SERVER_URL/api/v3/users/USERNAME',
        'html_url': 'http://GITBACKET_SERVER_URL/USERNAME',
        'avatar_url': 'http://GITBACKET_SERVER_URL/USERNAME/_avatar'
    }, 
    'assignee': {
        userと同じ key の集まり (valueは異なる)
    },
    'labels': [
        {
            'name': 'enhancement',
            'color': '84b6eb',
            'url': 'http://GITBACKET_SERVER_URL/api/v3/repos/USERNAME/REPONAME/labels/enhancement'
        }
    ],
    'state': 'closed',
    'created_at': '2018-08-13T14:33:25Z',
    'updated_at': '2020-10-10T06:34:36Z',
    'body': 'Issueのメッセージ本文',
    'id': 0,
    'assignees': [
        {
            user や assignee 同じ key の集まり (valueは異なる)
        }
    ],
    'comments_url': 'http://GITBACKET_SERVER_URL/api/v3/repos/USERNAME/REPONAME/issues/2/comments',
    'html_url': 'http://GITBACKET_SERVER_URL/USERNAME/REPONAME/issues/2'
}

さらに、Issue 内のコメントは、降ってきた comments_url に対してAPIをコールしてやれば、これまた JSON で手に入る。

[
    {
        "id": 3,
        "user": {
            "login": "USERNAME",
            "email": "USERNAME@excample.com",
            "type": "User",
            "site_admin": true,
            "created_at": "2018-08-08T12:51:17Z",
            "id": 0,
            "url": "http://GITBACKET_SERVER_URL/api/v3/users/USERNAME",
            "html_url": "http://GITBACKET_SERVER_URL/USERNAME",
            "avatar_url": "http://GITBACKET_SERVER_URL/USERNAME/_avatar"
        },
        "body":"Issueのコメント本文"
    }
]

Pull Request と Issue はAPIのエントリポイントが異なるだけで、全く同じ手順でダンプできる。なので、Issue と Pull Request はメソッドを共通化できる。やっつけだが、概ね以下の通り。

【2020/10/20修正】ちょっとやっつけすぎたので、少しリファクタリング

import os
import requests

GITBUCKET_API_BASEURL='http://gitbucket.example.com/api/v3/repos/USERNAME/REPONAME'
GITBUCKET_APP_TOKEN='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

# GitBucket から issue を取得する
def get_issues (base_url, token):
    headers = {'Authorization': 'token ' + token}
    issues = []

    # issue
    for issue_type in ('issues', 'pulls'):
        url = '%s/%s' % (base_url, issue_type)
        for state in ('open', 'closed'):
            for page in range(1, 10000):
                payload = {'state': state, 'page':page}
                response = requests.get(url, headers=headers, params=payload)
                new_issues = response.json()
                if not len(new_issues):
                    break
                issues.extend(new_issues)
    issues.sort(key=lambda x: x['number'])

    # issue comment
    for issue in issues:
        response = requests.get(issue['comments_url'], headers=headers)
        issue['comments'] = response.json()

    return issues

if __name__ == '__main__':
    issues = get_issues(GITBUCKET_API_BASEURL, GITBUCKET_APP_TOKEN)
    print(issues)

ハマりポイントとしては2点。

  • GitBucket の issues APIGitHub では使える state=all が使えないので、state=openとstate=closedを使って結果を結合しないとダメ。
  • またper_page=N も使えず、25個ずつしか取ってこれないので、page=N を順繰りに増やし、空配列しか降ってこなくなるまで取得し続けないとダメ。

これで、issue と Pull Request を一通りぶっこ抜けるようになったので、今度はこれを同じく GitHub API を使って GitHub に突っ込んでいく。

次回予告

  • GitBucket からぶっこ抜いた Issue と Issue comment を GitHub に入れていきます。

*1:サブセットなので、いろいろと実装が足りなくてハマりポイント多数。ちゃんとしたドキュメントが欲しい

ローカルのGitBucketをGitHubに移行する(その1)

  • ローカルサーバのGitBucketで管理していたリポジトリGitHubに移行したい
  • リポジトリ本体は clone して push すれば事が済むが、ユーザ名やメールアドレスが同じとは限らない
  • また、データベースで管理されている Issue などはそう簡単にはいかない
  • あんまり頑張りすぎずに、できる範囲で移行した際の作業記録(その1)

その2とその3はこちら。

kwatanabe.hatenablog.jp

kwatanabe.hatenablog.jp

背景

とある事情にて、ローカルのGitBucketをGitHubのプライベートリポジトリに移行することになった

GitHubリポジトリの移行機能を持っていたり、極端な話 clone して push したら済むので、そう難しくない。でも、PostgreSQL に入っている Issue とか、 pull request とかはどうすりゃいいか分からん。

移行ツールとか無いか、いろいろ漁ってみたもののコレといったものが出てこない。なんとかならんものか。

最低限守りたいモノ

とりあえず、あんまり頑張りたくないので、最低限守りたいものを整理する。

プルリクは、たまたまオープンのものが無かったので、特に意識はしていない。

【2020/10/11訂正】 Issue だけだと、Issue 番号がずれてしまうので、プルリクも入れてやらないとダメだった。

リポジトリ

フルミラーの取得

リポジトリの完全なミラーを取得する。

まずは単純に clone して。

$ git clone <リポジトリのURL>

ブランチを確認して。

$ cd <cloneしたディレクトリ>
$ git branch -r | grep -v "\->" | grep -v master
  origin/issue103

見つけたブランチも引っ張ってくる。

$ git branch --track issue103 origin/issue103
$ git branch
  issue103
* master

多分大丈夫だと思うけど、fetch と pull もしておく。

$ git fetch --all
$ git pull --all

おっけー。

ユーザ情報を GitHub のものに移行

ユーザ情報(AuthorとCommitter)が、ローカルとGitHubアカウントで齟齬があるので、コレを修正する。

齟齬とはいったものの、実態としてローカルのリポジトリでは、好き勝手にでっち上げたユーザ名とメールアドレスを使ってコミットしていたのでもうグチャグチャ。まずは、こいつを GitHub のそれに合わせて整える。

現状を確認する。

$ git log --pretty=full
…(略)…
Author:  XXXX <XXXX@XXXX.com>
Commit: XXXX <XXXX@XXXX.com>
…(略)…

もし、GitHubのユーザ名が kwatanabe、 メールアドレスが kwatanabe@example.com だとすると、こうする。

$ git filter-branch -f --env-filter "GIT_AUTHOR_NAME='kwatanabe'; GIT_AUTHOR_EMAIL='kwatanabe@example.com'; GIT_COMMITTER_NAME='kwatanabe'; GIT_COMMITTER_EMAIL='kwatanabe@example.com';" HEAD 
Ref 'refs/heads/master' was rewritten

もし、ブランチがあるならそれぞれで同じ事をやっておく。

$ git checkout BRANCH
Switched to branch 'BRANCH'
Your branch is up to date with 'origin/BRANCH'.

$ git filter-branch -f … (省略)

修正されたことを確認。

$ git log --pretty=full
…(略)…
Author:  kwatanabe <kwatanabe@example.com>
Commit: kwatanabe <kwatanabe@example.com>
…(略)…

試しに push してみる

とりあえずこれでリポジトリは整った。試しにGitHubにプッシュしてみる。

GitHubでプラベートリポジトリを作る。このとき、「Add a README file」と「Add .gitignore」のチェックを外しておく。

f:id:kWatanabe:20201010180849p:plain
プライベートリポジトリの作成

リポジトリのPUSH先URLを変更する。

$ git remote set-url origin https://github.com/<ユーザ名>/<リポジトリ>

あと、GitHubのデフォルトブランチ名は master ではなく main に変わった*1ので、メインブランチ名を変更する。

$ git branch -M main
$ git branch
  issue103
* main

PUSHする。

$ git push --all origin
$ git push --tags

GitHubのコンソールに入って、リポジトリのブランチとタグがきちんと反映されてることを確認しておく。

f:id:kWatanabe:20201010184333p:plain
ブランチ

あと、きちんとコミットとユーザが紐付き、いわゆる"草原"に反映されていることを確認する。

f:id:kWatanabe:20201010183038p:plain
いわゆる”草原”を確認する

次回予告

単なるリポジトリの移行だけであれば、コレで完了。

Issue やら Release やらをコピーしたい場合は、GitBucket から どうにかしてぶっこ抜く必要がある。

これは以下の記事参照です。

kwatanabe.hatenablog.jp

*1:完全なる独り言なんですが、blacklistとwhitelistとか、masterとslaveとか、言いたいことは分かるのですが、専門用語ってお互いに誤解を生まないように作られているものなので、そう簡単に変えちゃイカンと思うのです…。

LXCの非特権コンテナにホストのGPUをパススルーする

  • 2020/11/08 一部のtypoを修正

大要

  • Proxmox VEのLXCの非特権コンテナにGPUをパススルーし、CUDAを使う
  • KVMVGAパススルーと違って、ホストに搭載するGPUは1つでよい
  • GPUは(性能を考慮しないなら)そのままホストでも利用できる
  • (たぶん、)生のLXCでも同等の手順で実現できる

背景

  • 我が家では Ryzen マシンに Proxmox VE を入れて、IaaS モドキを運用している(以前は CloudStack + XenServer だったが、XenServer 無料版 の制限強化の際にやめた)
  • RyzenGPUを持たないので、コンソール用に GeForce GTX 1050Ti を挿しているが、非常時以外は使わないので勿体ない
  • LXCの非特権コンテナ上で 1050Ti を使って CUDA ができれば、追加費用なしで手軽にGPUインスタンスが手に入るのではと思った

ターゲット環境

手順

大まかな手順は以下の通り。

  1. ホストでGPUのドライバをビルド
  2. ホストでGPUのデバイスファイルを有効化
  3. ゲストにGPUのデバイスファイルをリマップ
  4. ゲストでGPUのドライバをビルド
  5. ゲストでCUDAのライブラリをインストール(割愛)

ホストでGPUドライバをビルド

サブスクリプションを持たない人向けの Proxmox リポジトリを有効にする。もちろん、サブスクリプションをお持ちの場合は不要。

# vim /etc/apt/sources.list
deb http://download.proxmox.com/debian/pve buster pve-no-subscription

Proxmox VE のリポジトリには nvidia-driver がないので、Proxmox VE 6.x のベースである Debian 10 のリポジトリを有効にする。なお、stable リポジトリnvidia-driver は古いので、backports リポジトリを用いる。

# vim /etc/apt/sources.list
deb http://deb.debian.org/debian buster-backports main contrib non-free
deb-src http://deb.debian.org/debian buster-backports main contrib non-free

nvidia-driver を導入する。nvidia-driver はバイナリで提供されていないので、DKMS*1 でその場でビルドされる。そのため、カーネルヘッダやビルドツールも一式必要になる。

# apt update
# apt upgrade
# apt install pve-headers build-essentials
# apt install -t buster-backports nvidia-driver nvidia-smi

ホストでGPUバイスファイルを有効化

DKMSでビルドされたドライバモジュールを有効にする。

# vim /etc/modules-load.d/nvidia-gpu.conf
nvidia-drm
nvidia
nvidia_uvm

udevルールを作成して、ドライバがロードされた時に非特権コンテナからでもデバイスファイルを扱えるように、アクセス権が変更されるようにする。(特権コンテナの場合は不要)

余談だが、非特権コンテナから扱えるようにアクセス権を緩めると、ホストの一般ユーザ(Proxmox VEに一般ユーザはないが)からでもデバイスファイルが叩けるようになってしまう。でも、コンテナを特権コンテナにするよりはまだマシか…?

# vim /etc/udev/rules.d/70-nvidia-gpu.rules
KERNEL=="nvidia", RUN+="/bin/bash -c '/usr/bin/nvidia-smi -L && /bin/chmod 666 /dev/nvidia*'"
KERNEL=="nvidia_uvm", RUN+="/bin/bash -c '/usr/bin/nvidia-modprobe -c0 -u && /bin/chmod 0666 /dev/nvidia-uvm*'"

ホストを一度再起動する。

# reboot

起動したら、GPUが正しくホストで認識されているか確認する。

# nvidia-smi
Sun Oct  4 15:42:59 2020       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.100      Driver Version: 440.100      CUDA Version: N/A      |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 105...  On   | 00000000:0A:00.0 Off |                  N/A |
| 29%   29C    P8    N/A /  75W |      1MiB /  4040MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

ゲストにGPUバイスファイルをリマップ

コンテナの設定ファイルを開いて、GPUのデバイスファイルをリマップする。

例えば、対象のコンテナの vmid が 101 だった場合は、以下の通り。

# vim /etc/pve/lxc/101.conf 
lxc.cgroup.devices.allow: c 195:* rwm
lxc.cgroup.devices.allow: c 236:* rwm
lxc.cgroup.devices.allow: c 226:* rwm

lxc.mount.entry: /dev/nvidia0 dev/nvidia0 none bind,optional,create=file
lxc.mount.entry: /dev/nvidiactl dev/nvidiactl none bind,optional,create=file
lxc.mount.entry: /dev/nvidia-uvm dev/nvidia-uvm none bind,optional,create=file
lxc.mount.entry: /dev/nvidia-modeset dev/nvidia-modeset none bind,optional,create=file
lxc.mount.entry: /dev/nvidia-uvm-tools dev/nvidia-uvm-tools none bind,optional,create=file
lxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dir

コンテナを起動して、コンテナの中からもデバイスファイルが見えている事を確認する。

$ ls /dev/nvidia* /dev/dri/*
/dev/nvidia0
/dev/nvidiactl
/dev/nvidia-modeset
/dev/nvidia-uvm
/dev/nvidia-uvm-tools
/dev/dri/card0
…(省略)…

ゲストでGPUドライバをビルド

ホストと同様にコンテナでGPUのドライバをビルドする。今回は Proxmox VE のベースである Debian 10 のコンテナなので、カーネルヘッダが異なる以外は同じ。

まずは、backports リポジトリを有効にして…。

$ sudo vim /etc/apt/sources.list
deb http://deb.debian.org/debian buster-backports main contrib non-free
deb-src http://deb.debian.org/debian buster-backports main contrib non-free

次に、DKMS でドライバをビルドする。

$ sudo apt update
$ sudo apt upgrade
$ sudo apt install build-essential
$ sudo apt install -t buster-backports nvidia-driver nvidia-smi

コンテナの内部からGPUにアクセスできるか確認する。

$ nvidia-smi
…(省略)…

あとは、実機でCUDAを使うときと同様に、コンテナにCUDAドライバをインストールする。ここでは割愛する。

備考

  • ホストとゲストのドライバは同じバージョンが望ましい。
  • Debian 10 以外のコンテナにパススルーする場合は、ディストリビューションのドライバのバージョンを確認して、少しでも違ったら公式ドライバでバージョン合わせをするのが無難
  • Proxmox VE 固有の手順は殆どないので、多分、生のLXCでも同じ手順で使えると思う

まとめ

  • Proxmox VE 6.2 上に構築した、LXCの非特権コンテナに GeForce GTX 1050Ti をパススルーして、お手軽GPUインスタンスを作った
  • ホストでも引き続きGPUを使えるので、RyzenLinuxなをお持ちの方は、とりあえずGPUを握ったコンテナを用意しておくと何かと捗るかもしれない

*1:Dynamic Kernel Module Support。ライセンスがポリシに合わなかったり、そもそもフリーじゃなかったりの理由で、Linuxカーネルのソースツリーの外にソースが存在するカーネルモジュールを、利用者の責任の下でその場でビルドさせる仕組み。