Raspberry Pi 5 に Ubuntu 24.04 LTS を入れて SSH するまで

Pocket

ついに日本でも、 Raspberry Pi 5 の技適対応モデルが発売された。

Raspberry Pi にインストールする OS と言えば、まずは Raspberry Pi OS (旧 Raspbian) だが、同じ Debian ベースの Ubuntu も Raspberry Pi 向けの公式イメージを出している。

但し、 Raspberry Pi 5 に対応しているのは、 Ubuntu 23.10 (コードネーム: mantic) 以降のみとなる。

Ubuntu 23.10 自体は「非LTS」バージョンとなり、サポートが 9ヶ月 (2024年6月まで) と短い。
常用するなら、サポートが 5年 (ESM なら 10年) と長い LTS バージョンが望ましい。

そこで今回は 2024年4月25日 にリリースされたばかりの、 24.04 LTS (コードネーム: noble) を使って、 ディスプレイやキーボードが無い状態でインストールから SSH 接続までやってみよう。

Raspberry Pi 5 をターゲットに書いているが、 Raspberry Pi 3, 4, Raspberry Pi Zero 2 W でも同じ手順でできるはずだ。

TL;DR

準備するもの

  • Raspberry Pi 5
    • Raspberry Pi 3, 4 でも同じ手順でいけるはず
  • 4GB 以上の micorSD カード
    • IOPS が高い、「アプリケーションパフォーマンスクラス」が A2 のものが良いだろう。値段や速度を考えると、実用上は 64GB 以上になるだろうか。
    • 私は、ARCANITE 64GB microSDXCカード 【A2】、UHS-I U3、V30、4K、C10 値段の割にパフォーマンスが良かったため、これを使った。
    • 後述するが、最終的には NVMe SSD にした方が断然良い
  • microSD を書き込める PC (Win, Linux, mac)
  • インターネットに繋がる Wi-Fi または イーサネットケーブル

以下もあると便利だが、今回の手順では無くても問題ない。

  • micro-HDMI で繋がるディスプレイ 又は 変換コネクタ
  • RasPi に繋げる USB キーボード

イメージファイルの取得

正式リリース前の Raspberry Pi 向けデイリースナップショットイメージは、 24.04 のリリースページ の中から以下のいずれかが手に入る。

今回は、サーバーOS的に SSH して使う事が目的なので、後者のサーバー版を利用する。

リンク先からダウンロードしよう。

24.04 LTS が正式リリースされて時間が経てば、 Install Ubuntu on a Raspberry Pi | Ubuntu のページからもリンクが張られるはずだ。

ブータブル SD の焼き込み

ダウンロードしたイメージファイルを、ブート可能な状態で microSD に書き込む。
ダウンロードしたファイルをただ SD カードに保存するのではなく、専用のツールで書き込む必要があるぞ。

方法は色々あるのだが、今回は個人的にオススメな balenaEtcher を使った方法で紹介しよう。

  1. microSD を SDカードスロットに挿す
  2. balenaEtcher を DL & 起動する。
    • DLする balenaEtcher は Portable 版で良い
  3. 前項でダウンロードするした ***.img.xz を選択して、書き込みを開始する
    • ***.img.xz は、 ***.img ファイルを .xz 形式で圧縮したものなのだが、手動で解凍せず直接 balenaEtcher で選択してしまって問題ない。 balenaEtcher は書き込み時にオンザフライで解凍する。

なお、リリース済みの Ubuntu (や Raspberry Pi OS など) であれば、一般的に Raspberry Pi Imager というツールを使った方法がオススメされるのだが、DLが遅いからか、全体的に時間がかかるので私はあまりオススメしない。

system-boot パーティション内の cloud-init 等の設定を書き換える

作成した micorSD を早速 RasPi に挿す …前に 、 初回セットアップ設定を書き換える。

一度書き込んだ microSD を再び PC に挿し直すと、 "system-boot" という 500MB 位の FAT32 パーティションが開けるはずだ。

ここには、 cloud-init という仕組みで初回起動時の設定を定義するファイルがいくつか含まれている。
これらを書き換えていく。

書き換え後のファイルは こちらの Gist に置いてあるので、結果だけ欲しい人はここから DL をば。

cloud-init の network-config

以下のように書き換える。
(元のファイルからの差分を diff のフォーマットで表示している。 以降同様。)

--- network-config.org  
+++ network-config      
@@ -28,23 +28,11 @@
   ethernets:
     eth0:
       dhcp4: true
       optional: true

-#  wifis:
-#    wlan0:
-#      dhcp4: true
-#      optional: true
-#      access-points:
-#        myhomewifi:
-#          password: "S3kr1t"
-#        myworkwifi:
-#          password: "correct battery horse staple"
-#        workssid:
-#          auth:
-#            key-management: eap
-#            method: peap
-#            identity: "me@example.com"
-#            password: "passw0rd"
-#            ca-certificate: /etc/my_ca.pem
-
-#      regulatory-domain: GB
+  wifis:
+    wlan0:
+      dhcp4: true
+      access-points:
+        "SSID-of-myhomewifi":
+          password: "password-of-myhomewifi"

Ubuntu では Netplan を使ってネットワークが管理されるため、 Netplan の形式で記述する。

規定値では IPv4 の有線イーサネットのみ、 DHCP 自動取得で接続されるようになっている。
必要に応じて、上述のように Wi-Fi で接続する SSID 等の情報を追記したり、逆に有線LANを使わないなら ethernet: プロパティごと削除してしまってもよいだろう。

SSID やパスワードは、各自の環境のものに置き換えて書き換えてほしい。

コメントアウトされている wifi の記述例では、 wifis.wlan0.optionaltrue に設定しているが、もし Wi-Fi 単独でネットワークに接続するつもりならこの設定は行わないようにしよう。
さもないと、OS起動の度に2分も余計に時間かかる羽目になる。
これは、 systemd-networkd-wait-online.service デーモンが、(optional true を指定していない) 有効なネットワークの接続をタイムアウトするまで待機してしまうためだ。

network-config 自体のリファレンスはこちら。
https://cloudinit.readthedocs.io/en/23.4.1/reference/network-config.html

cloud-init の user-data

こちらは書き換え箇所が多いので、何箇所かに分けて説明していく。

まず最初はユーザー名まわり。

--- user-data.org
+++ user-data
@@ -17,13 +17,17 @@
 # Some additional examples are provided in comments below the default
 # configuration.

+# Override the `default_user` configuration from `/etc/cloud/cloud.cfg`. The `user` dictionary keys supported for the default_user are the same as the `users` schema.
+user:
+  name: newusername
+
 # On first boot, set the (default) ubuntu user's password to "ubuntu" and
 # expire user passwords
 chpasswd:
   expire: true
   users:
-  - name: ubuntu
-    password: ubuntu
+  - name: newusername
+    password: newpassword
     type: text

 ## Set the system's hostname. Please note that, unless you have a local DNS

規定では、 cloud-init パッケージの /etc/cloud/cloud.cfg ファイルの定義によって、 "ubuntu" というユーザーが作成される。

しかし、 既知のユーザー名をそのまま使うのはセキュリティ的にも若干不安があるし、 作成後のユーザー名を書き換えるのは若干面倒なので、作成されるデフォルトのユーザー名自体を、他の名前 (上記例だと "newusername") に書き換えてしまおう。

また、 chpasswd による、初回アクセス時のパスワード変更対象とするユーザー名も、併せて変更しておく。

この 2つ のユーザー名の設定が完全に一致していないとログイン不可能に陥るので要注意だ。

--

次にホスト名とキーボード周りの設定。

@@ -31,14 +35,14 @@
 ## setting the hostname here will not make the machine reachable by this name.
 ## You may also wish to install avahi-daemon (see the "packages:" key below)
 ## to make your machine reachable by the .local domain
-#hostname: ubuntu
+hostname: ubuntu-raspi5

 ## Set up the keyboard layout. See localectl(1), in particular the various
 ## list-x11-* sub-commands, to determine the available models, layouts,
 ## variants, and options
-#keyboard:
-#  model: pc105
-#  layout: gb
+keyboard:
+  model: pc105
+  layout: jp
 #  variant:
 #  options: ctrl:nocaps

ここはまぁ、見た通りなので追加での説明はいらないかな。

--

お次は SSH まわり。

@@ -53,6 +57,8 @@
 #ssh_import_id:
 #- lp:my_launchpad_username
 #- gh:my_github_username
+ssh_authorized_keys:
+- ssh-rsa AAAA<中略>== rsa-key-of-user1

 ## Add users and groups to the system, and import keys with the ssh-import-id
 ## utility

ssh_authorized_keys に直接指定するか、ssh_import_id: のところで自分の GitHub のユーザー名を指定してインポートすることにより、 デフォルトのユーザー (上記例だと "newusername") に登録する公開鍵を指定する。
(上記例では鍵の種類を RSA としているが、現代で新しく秘密鍵を作るなら迷わず Ed25519 としておくことをおススメする。)

SSH に登録するパスワードログインは規定で禁止されているので、この設定をしておかないと SSH による遠隔からのアクセスができない。

--

タイムゾーンや起動後の処理まわり。

@@ -82,8 +88,13 @@
 #- python3-gpiozero
 #- [python3-serial, 3.5-1]

+# locale and timezone
+timezone: Asia/Tokyo
+
 ## Write arbitrary files to the file-system (including binaries!)
-#write_files:
+write_files:
+- path: /etc/cloud/cloud-init.disabled
+  defer: true
 #- path: /etc/default/console-setup
 #  content: |
 #    # Consult the console-setup(5) manual page.

とりあえずタイムゾーンは日本標準時にしておこう。

次に、 write_files: のほうだが、これは次回以降のブート時に cloud-init が余計に起動しないようにするための設定だ。

cloud-init の設定の大半は、 初回ブート時にただ一度だけ実行されるのだが、 cloud-init サービス自体は基本的に毎回常に起動して、サービスとして常駐しっぱなしになっている。
Modules で、 Module frequency: per always となっているモジュールの実行などは、ブートする度に毎回実行される。
そうすると、場合によっては cloud-init の実行ログがログイン画面を覆ってしまうことが毎回の起動ごとに発生して鬱陶しい。
このため、 /etc/cloud/cloud-init.disabled という 空ファイルを作成しておく ことで、 次回以降の起動時に cloud-init を無効にしている。

defer: true が指定されている部分についてだが、これは cloud-init の初期化処理が大きく分けて Network (cloud_init_modules), Config (cloud_config_modules), Final (cloud_final_modules) という 3ステージ に分かれて いることに関係する。
write_files: モジュール は Ubuntu の既定だと Network ステージに実行されてしまい、このタイミングで cloud-init.disabled ファイルが作成されてしまうと、 Config や Final ステージの処理が実行されなくなってしまう。
このため、 defer オプションを true にして Final ステージで cloud-init.disabled ファイルが作成されるよう調整しているのだ。

なお、どのモジュールがどのステージで実行されるかは、 /etc/cloud/cloud.cfg で定義されている。
(各ディストリビューション毎の cloud.cfg のテンプレートソースはこちら


最後に、コマンドの実行。
以下の 192.168.0.2 の部分は、 ssh で RasPi に接続するゲスト PC の IPアドレス に書き換えてくれ。

@@ -106,7 +117,9 @@
 #  permissions: '0644'

 ## Run arbitrary commands at rc.local like time
-#runcmd:
+runcmd:
+# after wlan0 is connected, write the arp entry to target machine on background job
+- [ sh, -c, 'sleep 2; ping -c 4 192.168.0.2' ]
 #- [ ls, -l, / ]
 #- [ sh, -xc, "echo $(date) ': hello world!'" ]
 #- [ wget, "http://ubuntu.com", -O, /run/mydir/index.html ]

このコマンドは、仮に ssh ゲストの IP アドレスが 192.168.0.2 であると仮定して、そこへ向かって Ping を打っている。
なぜなら、 ゲストPC から RaspPi の IP アドレスを ARP コマンドで確実に調べられるようにするためだ。

RasPi に繋げるディスプレイがないことを前提とすると、 SSH するために DHCP によって RasPi に割り当てられた IP アドレスをどうやって調べるのか、と言う問題に直面する。

RasPi の MAC アドレスの先頭 24bit は、 Raspberry 財団の所持する OUI から割り当てられるはずなので、以下のいずれかになるはずだ。

28:CD:C1
2C:CF:67
B8:27:EB
D8:3A:DD
DC:A6:32
E4:5F:01

通常、 RasPi がオンラインになったときに、 ARPリクエストがブロードキャストされるため、 同一ネットワーク内の PC の ARP テーブルに RasPi のレコードが追加され、 以下のような arp コマンドで対象の IP アドレスを調べることができるようになる。。。 多くの場合は。

while(1) { arp -a | ?{ $_ -match '\s(28-cd-c1|2c-cf-67|b8-27-eb|d8-3a-dd|dc-a6-32|e4-5f-01)' }; Start-Sleep -Seconds 1}

ところが、 RasPi が Wi-Fi で繋いでいる先が (単なるアクセスポイントではなく) ルーターだったりすると、 RasPi が 同じサブネット内に ARPリクエスト をブロードキャストしたのにも関わらず、 ルーターが無駄に気を利かせて ARPリクエスト を代替して投げてしまい、 同じサブネットの別の端末の ARP テーブルには、 RasPi の MAC アドレスのレコードが記録されないことがある。

そこで、 RasPi 側から SSH のアクセス元となる PC を指定して ping を投げることで、 その PC の ARP テーブルに確実に RasPi が載るようにしている。

--

ここまで書き換えたら、 SD カードを取り外して RasPi に挿し、いよいよ起動する。

cloud-init 周りの補足

system-boot パーティション内の中身について、もう少し細かい仕組み周りを深堀りして補足したい。
興味が無ければ、読み飛ばしてしまって次の章に進んでもらって問題ない。

system-boot パーティション直下にある README というファイルを開くと、このパーティションに存在するファイルの概要が簡単に書いてある。

==================
The Boot Partition
==================

This file belongs to the boot partition of the Ubuntu Server for Raspberry Pi.
This partition is usually mounted at /boot/firmware at runtime (in contrast to
RaspiOS where it is typically mounted on /boot).

...

このパーティションには、Raspberry Pi に於ける BIOS の代替となる config.txt といった H/W 設定用のファイルや、 cloud-init の構成ファイルなどが含まれていることがわかる。

このうち、cloud-init は Infrastructure as Code (IaC) の一種だ。
その名の通り、 AWS などのクラウド上の仮想マシンの初期設定を念頭においた仕組みだが、それに留まらず、一般的な Linux インスタンスの初期設定に活用される。
設定内容は、YAML形式の設定ファイルを使用して記述し、クラウドやディストリビューションの違いをある程度吸収して初期設定を行うことができる。
Ubuntu の OS のイメージ内には、事前に cloud-init のサービス(デーモン)が組み込まれており、OS の起動時に何らかの方法で指定された設定ファイルを読み取って、初回設定処理が実行される仕組みとなっている。

Raspberry Pi 用の Ubuntu Server イメージでは、この cloud-init 用の設定ファイルが上記の system-boot パーティションから取得される。
イメージ内のデフォルト値の定義と、system-boot パーティション内のユーザー定義が組み合わされ、初期設定が進められるワケだ。

たとえば、今回 microSD に書き込んだイメージをそのまま書き換えずに Ubuntu を起動すると、ユーザー認証が以下のように構成される。

  1. ubuntu というユーザーID
  2. ubuntu というパスワード
  3. 初回起動時にパスワードの変更を求める
  4. SSH サーバーの起動

このうち、 (1), (4) は、OSに組み込まれた /etc/cloud/cloud.cfg の定義によるもの。
そして、 (2), (3) は system-boot パーティション内の user-data ファイル内の以下の部分の定義によるモノだ。

#cloud-config
chpasswd:
  expire: true
  users:
  - name: ubuntu
    password: ubuntu
    type: text

このデフォルトの構成だと流石にセキュリティ的にアレなので、書き換えを行っている… というのが、前項の設定の真相となる。

Raspberry Pi に割り当てられた IP アドレスを調べられるようにして起動

こんな感じでひととおりファイルを書き換えたら、いよいよ RasPi を起動する。

…と、その前に、 ssh でつなぐ予定のゲスト端末で、管理者権限で以下のコマンドを実行し、端末内の「arp テーブル」をリセットする。

arp -d *

その後、ようやくだが RasPi に microSD を挿して起動させよう。
初回ブート時の設定を適用しながら起動してくれる。

ssh でつなぐ予定のゲスト端末の PowerShell で、以下のような arp コマンドのループを実行し、ゲストPC から RasPi の IPアドレスを調べよう。
初回起動され安定しただろうところ(SDカードの I/O 性能によるが 1~5分?)で、 RasPi の IP アドレスがヒットしてくるようになるはずだ。

while(1) { arp -a | ?{ $_ -match '\s(28-cd-c1|2c-cf-67|b8-27-eb|d8-3a-dd|dc-a6-32|e4-5f-01)' }; Start-Sleep -Seconds 1 }

公開鍵の登録が適切に行えれば、その IP アドレスに対し SSH できるはずだ。

初回アクセス時は、以下のようなパスワード変更を求められる。

WARNING: Your password has expired.
You must change your password now and login again!
Changing password for newusername.

古いパスワード ("newpassword") と変更後のパスワードを改めて入れる。

おわりに

一般的な1列端子の microSD で最速な UHS-I SDR104 のバスインターフェースに Raspberry Pi 5 が対応したことや、最近の安価な microSD の I/O 速度もだいぶ向上したのも相まって、 RasPi 4B が登場した頃と比べると、体感できる I/O 性能が大きく向上している。
おかげで、起動やパッケージのインストール、更新などが快適になった。

だいぶ高価になってしまった価格的な面からも、 Raspberry Pi の 非Zero/Pico シリーズは「Linuxが動くマイコン」から「小型のLinuxマシン」という面がより強くなったと感じる。
(それはどうなんだという気もするけど。)

更に RasPi 5 では PCI Express (PCIe) にも対応しているため、 NVMe で繋いだ SSD からブートさせると、更に桁違いの高速化が臨める。

近いうちに、そちらも試してみようと思う。

…ただ、どうせ PCIe に対応するなら、microSD Express に対応してくれれば、 NVMe 用 HAT とか買い足さんで済むから良かったのにな~…と、思わんでもない。
しかし、2019年にSD7.1の規格として登場してから4年も経った2024年現在その製品が売られているとこを見たことないので、まぁ無理な相談か。

Raspberry Pi 5 に Ubuntu 24.04 LTS を入れて SSH するまで」への2件のフィードバック

  1. SSH接続いぜんの話しなのですが。
    ラズパイOSのImagerからインストールしてたちあげたとき、オートログインを選択するとフェイルとなってしまいます。SSD、マイクロSDともです。シンプルにLAN接続、都度ログインのまま設定を継続すると問題なく立ち上がりました。インストーラーにバグがあるようですね。
    SSD破損したかと焦ってしまいましたW

  2. ピンバック: Raspberry Pi に Ubuntu を入れて SSH でログインするまでの A to B | Aqua Ware つぶやきブログ

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください