CUBE SUGAR CONTAINER

技術系のこと書きます。

Python: PyTorch で Apple Silicon GPU を使ってみる

PyTorch v1.12 以降では、macOS において Apple Silicon あるいは AMD の GPU を使ったアクセラレーションが可能になっているらしい。 バックエンドの名称は Metal Performance Shaders (MPS) という。 意外と簡単に使えるようなので、今回は手元の Mac で試してみた。

使った環境は次のとおり。 GPU が 19 コアの Apple M2 Pro を積んだ Mac mini を使用している。

$ sw_vers
ProductName:        macOS
ProductVersion:     14.4.1
BuildVersion:       23E224
$ sysctl machdep.cpu.brand_string     
machdep.cpu.brand_string: Apple M2 Pro
$ pip list | grep -i torch
torch                     2.2.1
$ python -V               
Python 3.10.14

もくじ

下準備

あらかじめ、必要なパッケージをインストールする。 特に意識しなくても MPS バックエンドが有効なバイナリが入る。

$ pip install torch tqdm numpy

インストールできたら Python のインタプリタを起動する。

$ python

そして、PyTorch のパッケージをインポートしておく。

>>> import torch

MPS バックエンドを使ってみる

MPS バックエンドが有効かどうかは以下のコードで確認できる。 True が返ってくれば利用できる状態にある。

>>> torch.backends.mps.is_available()
True

使い方は CUDA バックエンドと変わらない。 テンソルやモデルを .to() メソッドで転送するだけ。 このとき、引数に "mps" を指定すれば良い。

>>> x = torch.randn(2, 3, 4).to("mps")
>>> x.shape
torch.Size([2, 3, 4])
>>> x.device
device(type='mps', index=0)

ちゃんと転送できた。

簡単にベンチマークしてみる

続いては、どれくらいパフォーマンスが出るのか気になるので簡単にベンチマークしてみる。 PyTorch のベンチマークのページ 1 を参考に、以下のようなコードを用意した。 いくつかのサイズやスレッド数の組み合わせで、行列の積や和を計算している。

from itertools import product

from tqdm import tqdm
import torch
import torch.utils.benchmark as benchmark


def device():
    """環境毎に利用できるアクセラレータを返す"""
    if torch.backends.mps.is_available():
        # macOS w/ Apple Silicon or AMD GPU
        return "mps"
    if torch.cuda.is_available():
        # NVIDIA GPU
        return "cuda"
    return "cpu"


def batched_dot_mul_sum(a, b):
    """mul -> sum"""
    return a.mul(b).sum(-1)


def batched_dot_bmm(a, b):
    """bmm -> flatten"""
    a = a.reshape(-1, 1, a.shape[-1])
    b = b.reshape(-1, b.shape[-1], 1)
    return torch.bmm(a, b).flatten(-3)


DEVICE = device()
print(f"device: {DEVICE}")


results = []

# 行列サイズ x スレッド数の組み合わせでベンチマークする
sizes = [1, 64, 1024, 10000]
for b, n in tqdm(list(product(sizes, sizes))):
    label = "Batched dot"
    sub_label = f"[{b}, {n}]"
    x = torch.ones((b, n)).to(DEVICE)
    for num_threads in [1, 4, 16, 32]:
        results.append(benchmark.Timer(
            stmt="batched_dot_mul_sum(x, x)",
            setup="from __main__ import batched_dot_mul_sum",
            globals={"x": x},
            num_threads=num_threads,
            label=label,
            sub_label=sub_label,
            description="mul/sum",
        ).blocked_autorange(min_run_time=1))
        results.append(benchmark.Timer(
            stmt="batched_dot_bmm(x, x)",
            setup="from __main__ import batched_dot_bmm",
            globals={"x": x},
            num_threads=num_threads,
            label=label,
            sub_label=sub_label,
            description="bmm",
        ).blocked_autorange(min_run_time=1))

compare = benchmark.Compare(results)
compare.print()

Apple M2 Pro (GPU 19C)

実際に、上記を実行してみよう。 まずは Apple M2 Pro の環境から。

$ python bench.py 
device: mps
100%|███████████████████████████████████████████| 16/16 [02:12<00:00,  8.27s/it]
[-------------- Batched dot --------------]
                      |  mul/sum  |   bmm  
1 threads: --------------------------------
      [1, 1]          |     49.9  |    30.8
      [1, 64]         |     48.7  |    30.1
      [1, 1024]       |     48.8  |    30.0
      [1, 10000]      |     51.5  |    30.1
      [64, 1]         |     50.1  |    30.3
      [64, 64]        |     49.1  |    30.2
      [64, 1024]      |     54.9  |    30.1
      [64, 10000]     |     58.0  |    30.0
      [1024, 1]       |     49.9  |    30.0
      [1024, 64]      |     55.5  |    30.4
      [1024, 1024]    |     54.9  |    30.0
      [1024, 10000]   |    400.2  |    90.0
      [10000, 1]      |     53.7  |    30.5
      [10000, 64]     |     56.0  |    31.0
      [10000, 1024]   |    271.2  |   107.0
      [10000, 10000]  |   6594.7  |    31.2
4 threads: --------------------------------
      [1, 1]          |     52.1  |    31.5
      [1, 64]         |     50.6  |    31.3
      [1, 1024]       |     50.5  |    30.5
      [1, 10000]      |     53.2  |    31.3
      [64, 1]         |     52.7  |    31.3
      [64, 64]        |     51.2  |    30.3
      [64, 1024]      |     56.7  |    30.5
      [64, 10000]     |     59.6  |    30.7
      [1024, 1]       |     51.5  |    30.6
      [1024, 64]      |     56.6  |    30.7
      [1024, 1024]    |     57.1  |    30.7
      [1024, 10000]   |     64.5  |   204.3
      [10000, 1]      |     55.3  |    35.1
      [10000, 64]     |     58.0  |    34.4
      [10000, 1024]   |    590.8  |   223.3
      [10000, 10000]  |  32409.0  |  1498.3
16 threads: -------------------------------
      [1, 1]          |     51.6  |    30.8
      [1, 64]         |     51.1  |    30.4
      [1, 1024]       |     50.6  |    30.4
      [1, 10000]      |     53.7  |    30.7
      [64, 1]         |     51.7  |    30.6
      [64, 64]        |     50.4  |    30.4
      [64, 1024]      |     57.1  |    30.7
      [64, 10000]     |     59.5  |    30.5
      [1024, 1]       |     51.2  |    30.3
      [1024, 64]      |     56.3  |    30.8
      [1024, 1024]    |     57.3  |    31.0
      [1024, 10000]   |     60.3  |   106.8
      [10000, 1]      |     54.9  |    34.9
      [10000, 64]     |     57.2  |    34.5
      [10000, 1024]   |    400.3  |   220.7
      [10000, 10000]  |  32418.2  |  1503.2
32 threads: -------------------------------
      [1, 1]          |     51.1  |    30.6
      [1, 64]         |     50.4  |    30.6
      [1, 1024]       |     50.7  |    30.5
      [1, 10000]      |     53.0  |    30.5
      [64, 1]         |     51.8  |    30.7
      [64, 64]        |     50.4  |    30.2
      [64, 1024]      |     56.7  |    30.6
      [64, 10000]     |     59.3  |    30.5
      [1024, 1]       |     51.3  |    30.6
      [1024, 64]      |     56.6  |    34.5
      [1024, 1024]    |     57.8  |    33.5
      [1024, 10000]   |    447.3  |   202.6
      [10000, 1]      |     54.3  |    35.3
      [10000, 64]     |     57.0  |    34.5
      [10000, 1024]   |    591.2  |   219.7
      [10000, 10000]  |  32443.3  |  1493.3

Times are in microseconds (us).

NVIDIA GeForce RTX 3060

さきほどの結果は、もちろん CPU よりは全然速い。 とはいえ、他の GPU などに比べてどれくらい速いのかイメージしにくい。 そこで、厳密な比較にはならないものの RTX 3060 を積んだ Linux の環境でも実行してみる。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.6 LTS
Release:    20.04
Codename:   focal
$ pip list | grep -i torch
torch                    2.2.2
$ python -V
Python 3.10.14

先ほどのコードを実行する。

$ python bench.py 
device: cuda
100%|███████████████████████████████████████████| 16/16 [03:08<00:00, 11.81s/it]
[-------------- Batched dot --------------]
                      |  mul/sum  |   bmm  
1 threads: --------------------------------
      [1, 1]          |      6.5  |     6.8
      [1, 64]         |      6.5  |     6.8
      [1, 1024]       |      6.4  |     7.8
      [1, 10000]      |      6.3  |     7.8
      [64, 1]         |      6.3  |     6.6
      [64, 64]        |      6.4  |     6.8
      [64, 1024]      |      6.6  |     6.8
      [64, 10000]     |     25.1  |    10.2
      [1024, 1]       |      6.4  |     6.7
      [1024, 64]      |      6.3  |     6.7
      [1024, 1024]    |     40.2  |    15.6
      [1024, 10000]   |    375.3  |   179.1
      [10000, 1]      |      6.3  |    32.6
      [10000, 64]     |     29.2  |    34.9
      [10000, 1024]   |    374.7  |   123.5
      [10000, 10000]  |   3603.7  |  1672.6
4 threads: --------------------------------
      [1, 1]          |      6.5  |     6.9
      [1, 64]         |      6.5  |     6.9
      [1, 1024]       |      6.4  |     7.8
      [1, 10000]      |      6.4  |     7.8
      [64, 1]         |      6.4  |     6.6
      [64, 64]        |      6.5  |     6.8
      [64, 1024]      |      6.6  |     6.9
      [64, 10000]     |     25.1  |    10.2
      [1024, 1]       |      6.3  |     6.7
      [1024, 64]      |      6.4  |     6.7
      [1024, 1024]    |     40.2  |    15.6
      [1024, 10000]   |    375.3  |   179.1
      [10000, 1]      |      6.3  |    32.6
      [10000, 64]     |     29.2  |    34.9
      [10000, 1024]   |    374.9  |   123.5
      [10000, 10000]  |   3602.4  |  1672.5
16 threads: -------------------------------
      [1, 1]          |      6.5  |     6.9
      [1, 64]         |      6.5  |     6.7
      [1, 1024]       |      6.5  |     7.9
      [1, 10000]      |      6.3  |     7.8
      [64, 1]         |      6.3  |     6.6
      [64, 64]        |      6.4  |     6.8
      [64, 1024]      |      6.5  |     6.9
      [64, 10000]     |     25.1  |    10.2
      [1024, 1]       |      6.3  |     6.7
      [1024, 64]      |      6.4  |     6.7
      [1024, 1024]    |     40.3  |    15.6
      [1024, 10000]   |    375.3  |   179.1
      [10000, 1]      |      6.4  |    32.6
      [10000, 64]     |     29.2  |    34.9
      [10000, 1024]   |    374.9  |   123.5
      [10000, 10000]  |   3604.9  |  1672.4
32 threads: -------------------------------
      [1, 1]          |      6.6  |     6.9
      [1, 64]         |      6.4  |     6.8
      [1, 1024]       |      6.5  |     7.9
      [1, 10000]      |      6.4  |     7.8
      [64, 1]         |      6.3  |     6.7
      [64, 64]        |      6.6  |     6.8
      [64, 1024]      |      6.6  |     6.9
      [64, 10000]     |     25.1  |    10.3
      [1024, 1]       |      6.4  |     6.8
      [1024, 64]      |      6.3  |     6.8
      [1024, 1024]    |     40.2  |    15.6
      [1024, 10000]   |    375.1  |   179.2
      [10000, 1]      |      6.4  |    32.6
      [10000, 64]     |     29.2  |    34.9
      [10000, 1024]   |    374.9  |   123.5
      [10000, 10000]  |   3604.6  |  1672.4

Times are in microseconds (us).

こちらの環境の方が多くの場合に 2 ~ 10 倍程度速いことがわかる。 ただし、一部サイズの大きな bmm を使った演算に関しては、むしろ Apple Silicon の方が速いようだ。 また、消費電力は RTX 3060 の方が 20 倍近く大きい 2

まとめ

Apple Silicon の GPU は、そこまで速くないにしてもワットパフォーマンスには優れている。 また、CPU に比べればずっと速いので PyTorch で気軽に使えるのはありがたい。

参考

developer.apple.com

pytorch.org

pytorch.org



  1. https://pytorch.org/tutorials/recipes/recipes/benchmark.html
  2. Apple M2 Pro の GPU は実測で最大 10W 程度、RTX 3060 はカタログで 170W

Windows 11 に Chocolatey をインストールする

Windows の CLI で扱えるパッケージマネージャのひとつに Chocolatey がある。 今回は、そのインストール方法を確認したのでメモ的にまとめておく。

なお、インストール方法に関する公式のドキュメントは以下にある。 インストールに使う手順やコマンドがいつ変更されるとも限らない。 そのため、実際にインストールする場合は下記を確認した上で実施してもらいたい。

chocolatey.org

もくじ

ターミナルを管理者権限で起動する

まずはターミナルのアプリケーションを管理者権限で起動する必要がある。 検索ボックスに「ターミナル」を入力するなどでアプリケーションを見つける。 アプリケーションが見つかったら右クリックで「管理者として実行」を選択する。

ターミナルを管理者権限で起動する

ユーザーアカウント制御の確認ダイアログが出るので「はい」を選択する。

ユーザーアカウント制御の確認ダイアログで「はい」を選択する

すると、以下のようにターミナルが起動する。

起動した管理者権限で動作するターミナル

実行ポリシーを一時的に変更する

Chocolatey のインストールには PowerShell スクリプトを利用する。 ただし、スクリプトを実行するためには、あらかじめ実行ポリシーを Bypass に設定する必要がある。 このとき -Scope オプションに Process を指定することで、現在のプロセスだけに影響を与えるようにする。

実行ポリシーを一時的に変更する

> Set-ExecutionPolicy Bypass -Scope Process

Chocolatey をインストールする

あとは公式ドキュメントにある Chocolatey のインストール用のコマンドを入力するだけ。

Chocholatey をインストールするコマンドを入力する

現在のコマンドは次のとおり。 繰り返しになるけど、コマンドは変更される可能性があるので公式を確認することを忘れずに。

> Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))

動作を確認する

インストールが完了したら choco コマンドが実行できることを確認しておく。

chocoコマンドが利用できることを確認する

試しに何かパッケージをインストールする

あとは choco install コマンドでパッケージをインストールしていくだけ。 インストールの対象によっては、別の依存しているパッケージもインストールされる。

> choco install -y virtualbox

いじょう。

dbt を DuckDB で使う

OLAP (OnLine Analytical Processing) の用途に特化した組み込みの RDBMS に DuckDB がある。 そして、dbt には DuckDB 向けのアダプタがあるので、バックエンドのデータベースとして利用できる。 これは、ローカルのマシンでデータ分析をしたり、dbt の機能を試す際に有益と考えられる。 そこで、今回は環境をセットアップする流れをメモ的に書いておく。

使った環境は次のとおり。

$ sw_vers
ProductName:        macOS
ProductVersion:     14.3.1
BuildVersion:       23D60
$ python -V    
Python 3.11.7
$ dbt -v    
Core:
  - installed: 1.7.8
  - latest:    1.7.8 - Up to date!

Plugins:
  - duckdb: 1.7.2 - Up to date!

もくじ

下準備

まずは DuckDB 向けの dbt アダプタである dbt-duckdb をインストールしておく。 dbt の本体である dbt Core も依存関係として一緒に入る。

$ pip install dbt-duckdb

あとは DuckDB の CLI をインストールしておく。

$ brew install duckdb

プロジェクトをセットアップする

基本的には dbt init を使うことで、対話的にプロジェクトのひな形を作れる。

$ dbt init

ただし、手っ取り早くセットアップしたいときは、以下のように設定ファイルを直接作っても良い。

まず必要なのはプロジェクトの情報を記述した dbt_project.yml という YAML ファイル。

$ cat << 'EOF' > dbt_project.yml 
config-version: 2
name: 'dbt_duckdb_example'
version: '1.0.0'
profile: 'dbt_duckdb_example'
EOF

次に、データベースに接続するためのプロファイルの設定ファイルを用意する。 デフォルトでは $HOME/.dbt/profiles.yml の内容が使われる。

以下では dbt_duckdb_example という名前のプロファイルを定義している。 プロファイルの名前は先ほどの dbt_project.yml で記述した profile と揃える必要がある。 なお、以下のコマンドは既に dbt を使っていてプロファイルの設定ファイルがある場合には上書きされてしまう点に注意すること。

$ mkdir -p ~/.dbt
$ cat << 'EOF' > ~/.dbt/profiles.yml
config:
  send_anonymous_usage_stats: False

dbt_duckdb_example:
  outputs:
    dev:
      type: duckdb
      path: dev.duckdb
  target: dev
EOF

上記で dbt_duckdb_example.outputs.devdev というターゲット (環境)の設定をしている。 ターゲットは開発用、ステージング用、本番用などで複数作れる。 そして dbt_duckdb_example.outputs.dev.typeduckdb を指定することで、データベースへ接続する際のアダプタとして dbt-duckdb が使用される。 dbt_duckdb_example.outputs.dev.path は dbt-duckdb アダプタ固有の設定でデータベースのファイルパスを表す。 dbt_duckdb_example.target はデフォルトで実行するターゲットを指定している。

以上で必要な設定ファイルができた。 続いては dbt debug コマンドを実行してみよう。 このコマンドはデータベースへの接続を確認するためのもの。

$ dbt debug
10:46:05  Running with dbt=1.7.8
10:46:05  dbt version: 1.7.8
10:46:05  python version: 3.11.7
...
10:46:05  Connection:
10:46:05    database: dev
10:46:05    schema: main
10:46:05    path: dev.duckdb
10:46:05    config_options: None
10:46:05    extensions: None
10:46:05    settings: None
10:46:05    external_root: .
10:46:05    use_credential_provider: None
10:46:05    attach: None
10:46:05    filesystems: None
10:46:05    remote: None
10:46:05    plugins: None
10:46:05    disable_transactions: False
10:46:05  Registered adapter: duckdb=1.7.2
10:46:05    Connection test: [OK connection ok]

10:46:05  All checks passed!

問題なくデータベースに接続できればコマンドが正常終了する。

任意の場所にあるプロファイルの設定ファイルを使いたい場合

なお、$HOME/.dbt 以外の場所にあるプロファイルの設定ファイルを使用することもできる。 その場合は dbt コマンドに --profiles-dir オプションを指定すれば良い。

$ cat << 'EOF' > profiles.yml
config:
  send_anonymous_usage_stats: False

dbt_duckdb_exaple:
  outputs:
    dev:
      type: duckdb
      path: dev.duckdb
  target: dev
EOF
$ dbt debug --profiles-dir .

データベースの内容を確認する

dbt debug コマンドを実行すると、ひとまずデータベースのファイルができる。 このときファイル名は、先ほどプロファイルで指定した path に対応する。

$ ls -1                   
dbt_project.yml
dev.duckdb
logs
profiles.yml

DuckDB の CLI を使って接続してみよう。 中身は空っぽではあるものの、ちゃんと利用できることが確認できる。

$ duckdb dev.duckdb         
v0.10.0 20b1486d11
Enter ".help" for usage hints.
D

いじょう。

Network Namespace 内の Linux Bridge で STP が動くようになった

以前、システムが所属している以外の (= 非ルートな) Network Namespace 上では Linux Bridge の STP が動作しない件について書いた。

blog.amedama.jp

月日は流れて、去年の話ではあるけど Linux カーネルに以下のパッチが投稿されて取り込まれたようだ。

lore.kernel.org

つまり、新しいカーネル 1 であれば STP が動作するようになった。 今回は、それを試してみる。

使った環境は次のとおり。 Ubuntu 24.04 LTS の開発版ブランチを利用している。

$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=24.04
DISTRIB_CODENAME=noble
DISTRIB_DESCRIPTION="Ubuntu Noble Numbat (development branch)"
$ uname -srm
Linux 6.6.0-14-generic aarch64
$ ip -V
ip utility, iproute2-6.1.0, libbpf 1.3.0
$ brctl -V
bridge-utils, 1.7

もくじ

下準備

あらかじめ、必要なパッケージをインストールしておく。

$ sudo apt-get -y install iproute2 tcpdump

ネットワークを作る

ここからは、ループしたネットワークを作っていく。

まずは新しく Network Namespace を作成する。

$ sudo ip netns add bridge

Network Namespace に Linux Bridge を作成する。

$ sudo ip netns exec bridge ip link add dev br0 type bridge

続いて veth (Virtual Ethernet Device) のネットワークインターフェースを作成する。

$ sudo ip link add br-veth0 type veth peer name br-veth1

作成したインターフェイスを Network Namespace に所属される。

$ sudo ip link set br-veth0 netns bridge
$ sudo ip link set br-veth1 netns bridge

インターフェイスを使えるように UP に設定する。

$ sudo ip netns exec bridge ip link set br-veth0 up
$ sudo ip netns exec bridge ip link set br-veth1 up

インターフェイスを両方 Linux Bridge につなぐ。 これでループができた。

$ sudo ip netns exec bridge ip link set br-veth0 master br0
$ sudo ip netns exec bridge ip link set br-veth1 master br0

ストームを起こす

まだ肝心の Linux Bridge が DOWN したままなのでストームは起こっていない。

$ sudo ip netns exec bridge ip -s link show br0
2: br0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 00:00:5e:00:53:01 brd ff:ff:ff:ff:ff:ff
    RX:  bytes packets errors dropped  missed   mcast           
             0       0      0       0       0       0 
    TX:  bytes packets errors dropped carrier collsns           
             0       0      0       0       0       0

ここで Linux Bridge を UP に設定してみよう。

$ sudo ip netns exec bridge ip link set br0 up

すると、インターフェイスの統計情報でパケットがどんどん増えていく。

$ sudo ip netns exec bridge ip -s link show br0
2: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 00:00:5e:00:53:01 brd ff:ff:ff:ff:ff:ff
    RX:  bytes packets errors dropped  missed   mcast           
     682266792 9739306      0       0       0 9739306 
    TX:  bytes packets errors dropped carrier collsns           
        627472    7296      0       0       0       0

このとき tcpdump(8) するとストームの様子を観察できる。

$ sudo ip netns exec bridge tcpdump -tnl -i br0

STP を有効にする

それでは Linux Bridge の STP を有効にしてみよう。

$ sudo ip netns exec bridge ip link set br0 type bridge stp_state 1

すると、先ほど確認した統計情報の増加がピタッと止まる。

$ sudo ip netns exec bridge ip -s link show br0
2: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 00:00:5e:00:53:01 brd ff:ff:ff:ff:ff:ff
    RX:   bytes   packets errors dropped  missed     mcast           
    12533565936 192857688      0       0       0 192857688 
    TX:   bytes   packets errors dropped carrier   collsns           
        8275968     96232      0       0       0         0

どうやら、STP が正常に動作しているようだ。

いじょう。


  1. v5.6 あたりからいけそうな感じ (デフォルトの Ubuntu であれば 22.10 以降が該当する)

Lima で仮想マシンのディスプレイを表示する

Lima 1 の仮想マシンは、デフォルトではディスプレイのない Headless モードで動作する。 とはいえ、作業の都合からディスプレイが欲しくなる場面もある。 そこで、今回は Lima の仮想マシンでディスプレイを表示する方法について書く。

使った環境は次のとおり。

$ sw_vers                                           
ProductName:        macOS
ProductVersion:     14.3.1
BuildVersion:       23D60
$ uname -srm                                   
Darwin 23.3.0 arm64
$ lima --version
limactl version 0.20.1

もくじ

下準備

下準備として Lima と wget をインストールしておく。

$ brew install lima wget

続いて、仮想マシンで利用する OS のイメージファイルをダウンロードしておく。 今回は Ubuntu 22.04 LTS を使った。

$ wget https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-arm64.img

ディスプレイのある仮想マシンを作る

ダウンロードしたイメージファイルを使って仮想マシンを起動するために Lima の設定ファイルを用意する。 このとき video.display の項目を設定するのがポイント。 デフォルトでは none に設定されるため Headless になる。 ここを default に設定すると、システムで利用できるディスプレイの表示方法を自動で選んでくれる。

$ cat << 'EOF' > jammy.yaml
images:
- location: "jammy-server-cloudimg-arm64.img"
video:
  display: default
EOF

念のため limactl validate コマンドで、設定ファイルの記述を検証しておこう。

$ limactl validate jammy.yaml 
INFO[0000] "jammy.yaml": OK

あとは limactl start コマンドで仮想マシンを起動するだけ。

$ limactl start --tty=false jammy.yaml

すると、次のように仮想マシンのディスプレイが表示される。

Lima で起動した仮想マシンのディスプレイ

めでたしめでたし。

YAMAHA RTX830 で DS-Lite と PPPoE を併用して IKEv2 VPN を使う (ひかり電話なし)

今回は YAMAHA RTX830 で DS-Lite と PPPoE を併用しながら IKEv2 のリモートアクセス VPN を使えるようにする設定を紹介する。

リモートアクセス VPN を利用するためにはインターネット側を起点にした通信が必要になる。 しかし、DS-Lite の IPv4 通信は CGN が間に挟まっているので VPN のエンドポイントとして利用できない。 そこで、ISP の PPPoE で得られる動的な IPv4 アドレスを代わりに VPN のエンドポイントにする。 通常の IPv4 トラフィックは DS-Lite に流しながら、VPN のトラフィックだけをポリシーベースルーティングで PPPoE に向ける。

なお、動作に必要な最低限のコマンドだけを記述している。 実際に稼働させる場合には、セキュリティの観点からフィルタ (IPv4 / IPv6) を追加で設定するのが望ましい。 具体的な設定については、公式ドキュメントの設定例を参照のこと。 基本的には、以下の方針で設定することになるはず。

  • インターネットから LAN への通信を原則として遮断する静的フィルタ
  • LAN からインターネットへの通信を原則として通過させる動的フィルタ

また、インターフェイス的には LAN2 がインターネット側、LAN1 が自宅の LAN 側になる。 LAN のプライベート IP アドレスには 172.16.0.0/16 のサブネットを想定している。 ひかり電話の契約はないため IPv6 のアドレスはルータ広告で得られるプレフィックスから生成する。

使った環境は次のとおり。

# show environment 
RTX830 BootROM Ver. 1.01
RTX830 FlashROM Table Ver. 1.02
RTX830 Rev.15.02.30 (Wed Aug  9 16:15:25 2023)

もくじ

LAN2

まずはインターネット側に対応するインターフェイス LAN2 の設定から。 基本的には IPv6 でルータ広告とステートレス DHCPv6 を受け取る設定を入れる。

# LAN2 はキャリア回線として NTT NGN に接続している
ngn type lan2 ntt
# LAN2 で受信したプレフィックスを登録する
ipv6 prefix 1 ra-prefix@lan2::/64
# LAN2 で DHCPv6 を要求する (Inform-Request)
ipv6 lan2 dhcp service client ir=on

LAN1

続いて LAN 側に対応するインターフェイス LAN1 を設定する。

IPv4 に関してはプライベートアドレスを静的に付与してプロキシ ARP を有効にする。

# LAN1 に IPv4 アドレスを付与する
ip lan1 address 172.16.0.1/16
# IKEv2 のリモートアクセス VPN を使うので LAN1 はプロキシ ARP を有効にする
ip lan1 proxyarp on

IPv6 に関してはルータ広告のプレフィックスを元にアドレスを付与する。 また、LAN 側にいる端末が IPv6 で通信できるようにルータ広告と DHCPv6 サーバを有効にする

# LAN2 で受信したルータ広告のプレフィックスで LAN1 に IPv6 アドレスを付与する
ipv6 lan1 address ra-prefix@lan2::1/64
# LAN1 でルータ広告を有効にする (Other フラグ付き)
ipv6 lan1 rtadv send 1 o_flag=on
# LAN1 で DHCPv6 サーバを有効にする
ipv6 lan1 dhcp service server

PPPoE

続いては ISP の PPPoE を設定する。 いわゆる、一般的に NTT のフレッツを利用する際に使用する設定になる。 コマンドの pp は Peer-to-Peer の略で接続先情報を表すらしい。 <id><password> は ISP から提供された接続アカウントの内容に置換する。

# PP 1 番で PPPoE を設定する
pp select 1
 # 常時接続する
 pp always-on on
 # LAN2 で PPPoE を利用する
 pppoe use lan2
 # 自動で切断しない
 pppoe auto disconnect off
 # 認証方式に CHAP を利用する
 pp auth accept chap
 # 認証に使うアカウント
 pp auth myname <id> <password>
 # IPCP で IP アドレスを取得する
 ppp ipcp ipaddress on
 # IPCP で DNS サーバの IP アドレスを取得する
 ppp ipcp msext on
 # パケットを圧縮しない
 ppp ccp type none
 # NAT の設定に 1 番のデスクリプタを利用する
 ip pp nat descriptor 1
 # この PP 設定でネットボランチ DNS サービスを有効にする
 netvolante-dns use pp server=<num> auto
 # ネットボランチ DNS サービスのドメイン名を指定する
 netvolante-dns hostname host pp server=<num> <hostname>.<sub-domain>.netvolante.jp
 # PP 1 番を有効にする
 pp enable 1

上記で、PPPoE の IPCP で得られる動的 IP アドレスをネットボランチ DNS サービスに登録している点がポイントになる。 これによって VPN のエンドポイントが FQDN で解決できるようになる。

初回の設定をするときは pp select 1 した状態で、次のように <hostname> の部分だけを指定する。 この部分は、ドメイン名に沿った形でユーザが使いたいものを任意に指定する。

netvolante-dns use pp auto
netvolante-dns hostname host pp <hostname>

その上で、以下のコマンドを使って現在の IPv4 アドレスをネットボランチ DNS サービスに登録する。

netvolante-dns go pp 1

すると、設定が以下のような形に置き換わるはず。

netvolante-dns use pp server=<num> auto
netvolante-dns hostname host pp server=<num> <hostname>.<sub-domain>.netvolante.jp

置き換わったら設定を保存していく。

save

NAT

次に NAT (NAPT) を設定していく。

まずは PPPoE で Source NAT が使えるようにする。 先ほど設定した pp 1 では NAT のデスクリプタとして 1 番を指定していた。

# NAT のデスクリプタ 1 番で Source NAT を有効にする
nat descriptor type 1 masquerade

次に IPsec 関連のトラフィックを LAN1 に付与した IPv4 アドレスに Destination NAT する。 つまり、実際に VPN のエンドポイントになるのは LAN1 に付与された IPv4 アドレスということになる。

# ESP パケット
nat descriptor masquerade static 1 1 172.16.0.1 esp
# IKE パケット
nat descriptor masquerade static 1 2 172.16.0.1 udp 500
# NAT-T パケット
nat descriptor masquerade static 1 3 172.16.0.1 udp 4500

DS-Lite

次に DS-Lite を設定する。 といっても、単なる IP-IP トンネルに過ぎない。

# トンネル 1 番で DS-Lite を設定する
tunnel select 1
 # トンネル方式に IP-IP トンネリングを利用する
 tunnel encapsulation ipip
 # トンネルのエンドポイント (AFTR) を指定する
 tunnel endpoint name gw.transix.jp fqdn
 # トンネル 1 番を有効にする
 tunnel enable 1

ルーティング

続いて IPv4 のルーティングを設定する。 次のような方針で設定する。

  • メインの経路に DS-Lite のトンネルを利用する
  • バックアップの経路に PPPoE を利用する
  • IPsec 関連のトラフィックはポリシーベースルーティングで PPPoE に向ける

なお、YAMAHA のルータではポリシーベースルーティングをフィルタ型ルーティングと呼んでいる。

上記を反映したコマンドが次のとおり。 hide は接続が有効なときだけ経路として利用することを示す。 また weight はどれくらいの割合でトラフィックを流すかを指定する。 filter は、フィルタに合致したトラフィックだけをそのゲートウェイに流すことを意味する。

ip route default gateway tunnel 1 hide gateway pp 1 weight 0 gateway pp 1 filter 44051 44052 44053

つまり、DS-Lite が使える時はトラフィックをそちらに全て流し、使えない時は全て PPPoE に流す。 ただしフィルタに合致するトラフィックだけは常に PPPoE に流す、という意味になる。

コマンドの区切りを改行して分かりやすくしてみると次のようになる。 なお、あくまで例示のために改行しているだけで、この状態では有効なコマンドにならない。

ip route default
  gateway tunnel 1 hide
  gateway pp 1 weight 0
  gateway pp 1 filter 44051 44052 44053

上記で設定されているフィルタ型ルーティングに対応するフィルタは次のとおり。 LAN1 に付与した IPv4 アドレスが送信元になっている IPsec 関連のパケットがフィルタに合致する。 なお、フィルタ番号については任意の整数なので、取り違えにくいようなルールで自由に指定して構わない。

ip filter 44051 pass 172.16.0.1 * esp
ip filter 44052 pass 172.16.0.1 * udp 500 *
ip filter 44053 pass 172.16.0.1 * udp 4500 *

なお、IPv6 のルーティングに関してはルータ広告を元にデフォルトルートが自動で設定されるようだ。

DHCP サーバ

次に DHCP (DHCPv4) サーバを設定する。 といっても、サービスを有効にして払い出すアドレスレンジを指定するくらい。

# DHCP サーバを有効にする
dhcp service server
# リース情報を持たないクライアントからの DHCPREQUEST を無視する以外は RFC2131 の挙動に準拠する
dhcp server rfc2131 compliant except remain-silent
# DHCP サーバで払い出す IPv4 のアドレスレンジ
dhcp scope 1 172.16.1.1-172.16.1.254/16

DNS サーバ

DNS サーバに関してはプロキシのサービスを有効にする。 フルサービスリゾルバのアドレスは LAN2 から DHCPv6 で取得する。 PPPoE から取得する場合は dns server pp 1 を入れても良いけど、両方入っている場合は常に PPPoE が優先されるらしい。

# LAN1 で DNS サーバ (プロキシ) を有効にする
dns host lan1
# DNS サーバを LAN2 から DHCP で取得する
dns server dhcp lan2

IKEv2

そして IKEv2 のリモートアクセス VPN を設定する。 ipsec ike local name にはネットボランチ DNS サービスの FQDN を使っている。 ただ、もしかすると FQDN として解釈できる任意の文字列で良いかもしれない。 ipsec ike remote name には FQDN として解釈できる任意の文字列を入れる。 クライアント側で VPN の設定を作る際は localremote が入れ替わる点に注意する。 <psk> は IPsec の事前共有鍵なので、複雑で十分に長い文字列を指定する。

# トンネル 10 番で IKEv2 リモートアクセス VPN を設定する
tunnel select 10
 # トンネル方式に IPsec を利用する
 tunnel encapsulation ipsec
 # IPsec トンネル 1 番を設定する
 ipsec tunnel 1
  # IPsec ポリシー 1 番 / セキュリティゲートウェイ 1 番は ESP モードで動作する
  ipsec sa policy 1 1 esp
  # IKEv2 を利用する
  ipsec ike version 1 2
  # IKE Keepalive をログに出力しない
  ipsec ike keepalive log 1 off
  # RFC4306 方式で 10 秒毎に IKE Keepalive を送って 3 回失敗したら切断する
  ipsec ike keepalive use 1 on rfc4306 10 3
  # ローカル ID を指定する
  ipsec ike local name 1 <domain>.<sub-domain>.netvolante.jp fqdn
  # 事前共有鍵を指定する
  ipsec ike pre-shared-key 1 text <psk>
  # リモート ID を指定する
  ipsec ike remote name 1 <remote> fqdn
  # 配布するアドレスプールに 1 番を利用する
  ipsec ike mode-cfg address 1 1
  # IKE の鍵交換をルータ側から始動しない
  ipsec auto refresh 1 off
 # トンネル 10 番を有効にする
 tunnel enable 10

また、上記の設定で指定されている IKEv2 リモートアクセス VPN で配布するアドレスプールを定義する。

ipsec ike mode-cfg address pool 1 172.16.2.1-172.16.2.254/16

以上で RTX830 で DS-Lite と PPPoE を併用しながら IKEv2 のリモートアクセス VPN が使えるようになるはず。

クライアント側を設定する

あとはクライアント側に IKEv2 リモートアクセス VPN の設定をするだけ。 主な設定項目は前述のコンフィグから抜き出すと次のようになる。

  • エンドポイント
    • <domain>.<sub-domain>.netvolante.jp
  • リモート ID
    • <domain>.<sub-domain>.netvolante.jp
  • ローカル ID
    • <remote>
  • ユーザ認証
    • なし
  • 事前共有鍵
    • <psk>

つながらないときは syslog debug on にしたり、コンフィグを確認したり、パケットキャプチャしながら切り分けていく。

いじょう。

参考

www.rtpro.yamaha.co.jp

www.rtpro.yamaha.co.jp

www.rtpro.yamaha.co.jp

Raspberry Pi で AdGuard Home を動かす

AdGuard Home は、インターネット広告や端末のトラッキングなど 1 をブロックすることを目的とした OSS の DNS サーバ (プロキシ) のひとつ。 フィルタリングルールを入れた状態で DNS サーバとして利用することで、端末に依存しないフィルタリング 2 が可能になる。 今回は、そんな AdGuard Home を Raspberry Pi にインストールして試してみた。

使った環境は次のとおり。

$ cat /etc/*-release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
$ uname -srm
Linux 6.1.0-rpi7-rpi-v8 aarch64

もくじ

下準備

公式のインストールガイドには、AdGuard Home のバイナリをダウンロードして実行する方法が紹介されている。 ただ、それだとアップデートなどの手間がかかると思っていたところ、どうやら snap 3 のパッケージがあるらしい。

なので、まずは snap をインストールする。 また、動作確認用に dnsutils もインストールしておく。

$ sudo apt-get update
$ sudo apt-get -y install snapd dnsutils

snap のインストールガイドに従って再起動する。

$ sudo shutdown -r now

再起動したら、以下を実行して snap の状態を最新にする。

$ sudo snap install core

インストール

あとは snap コマンドで adguard-home をインストールする。

$ sudo snap install adguard-home

セットアップする

インストールが終わると TCP の 3000 番ポートを AdGuard Home が Listen し始める。 これは AdGuard Home の管理用 Web UI が、初期セットアップが終わるまで使うポート番号になる。

$ ss -tlnp | grep 3000
LISTEN 0      4096               *:3000            *:* 

この状態で、Raspberry Pi の IP アドレスの 3000 番ポートにブラウザでアクセスすれば管理用 Web UI が表示される。 Raspberry Pi をデスクトップ環境で使っているなら、単純にインストール済みのブラウザで http://localhost:3000/ を開けば良い。

初期設定のウィザードでは動作するネットワークインターフェイスやポート番号などを聞かれるので好みで変更しておく。 特にこだわりがなければ何もいじらなくても良いと思う。

初期設定が終わると DNS 用の 53 番ポートを Listen し始める。 また、管理用の Web UI が Listen するポートも、初期設定で指定したものに変化する。

$ sudo ss -tlnp | grep AdGuard | grep 53
LISTEN 0      4096               *:53              *:*    users:(("AdGuardHome",pid=679,fd=15))
$ sudo ss -ulnp | grep AdGuard | grep 53
UNCONN 0      0                                *:53               *:*    users:(("AdGuardHome",pid=679,fd=14))

デフォルトのフィルタリングルールとして AdGuard DNS filter というルールが入っている。 そのルールが適用されていることを確認する。

まずは www.google.com など、普段から利用するドメイン名が正引きできることを確認する。

$ dig +short www.google.com @127.0.0.1
172.217.175.4

次に、広告配信に使われているドメインが 0.0.0.0 に解決されることを確認する。 この動作は設定から変更が可能で NXDomain にすることなどから選べる。

$ dig +short doubleclick.net @127.0.0.1
0.0.0.0

なお、既存のサーバとして DMZ などで動作している場合にはオープンリゾルバにならないように注意する。

設定を調整する

あとは自分が意図した動作になるように設定を調整していく。 たとえば DNS のアップストリームサーバを変更したり、フィルタリングルールを追加・変更する。

AdGuard が公式で提供しているフィルタリングルールは以下に掲載されている。

adguard.com

一般的なユースケースで利用頻度が高そうなのは以下あたり。

  • Japanese filter
  • Mobile ads filter

フィルタを利用することで、自身が日頃から使っているサービスに何らかの問題が生じる可能性がある点には留意が必要になる。

一通りの設定が終わったら、あとは自身のネットワークで Raspberry Pi を DNS サーバとして利用する。 一般的なネットワークであれば、ルータの DHCP サーバで Raspberry Pi の IP アドレスを DNS サーバとして配布するように設定する。

参考文献

github.com

https://snapcraft.io/docs/installing-snap-on-raspbian


  1. アダルトサイトの閲覧やマルウェアの通信を阻害する目的でも用いられる場合がある
  2. 一般的には DNS フィルタリングと呼ばれている
  3. https://snapcraft.io/