Raspberry Pi に Ubuntu を入れて SSH でログインするまでの A to B

Pocket

何番煎じかわからないが、 Raspberry Pi (以下、 RasPi) に Ubuntu を入れる手順についてまとめてみようと思う。

Ubuntu 公式の How to install Ubuntu on your Raspberry Pi チュートリアルは充実しているし、 単にインストールして動かすだけなら、既にある記事でも十分だろうとは思う。
しかし、初回セットアップ時の細かいカスタマイズについて書かれているものがあまり見当たらなかったので、この記事ではそれについて補足しながらまとめていく。

先に断っておくが、 cloud-init による IaC の話が中心になる。

TL;DR

  • 焼いた SD を RasPi に挿すにブートプロセスの設定を書き換える
  • cloud-init の挙動を理解しろ
  • モニター無し & 無線LAN Only だと、工夫がいる
  • ARP リクエストのブロードキャストが届かないと苦労する

きっかけ

完全に私事だが、 おしごとで Linux 使うことが増えてきて、 WSL (or WSL2) や仮想マシン上の知識だけではなかなか難しい、ネットワーク回りの知識が不足してきた。
そこで、勉強がてら何らかの物理的な Linux マシンが欲しかった。

中古の x86 ラップトップに Linux を入れるのでも良かったのだが、 それだと Linux 向けの無線やネットワーク回りのドライバーが揃っているか確認するのが難しい。
そこで、様々な Linux ディストリビューションが公式でサポートされ、 I/O も充実していて、 コスパの高い Raspberry Pi 4 B+ をチョイス。

これまで、 RasPi 自体は所持していたのだが、 電子工作目的であったことから Raspberry Pi OS (旧 Raspbian) しか入れていなかった。
より Linux サーバーらしい使い方も試してみたいと言うことから、今回は Ubuntu をいれることした。

いざ、セットアップはじめようとして、致命的な問題に気づく。
micro-HDMI のケーブルを持っていないと言うことに…

ということで、 なんとかセットアップを工夫して、 モニターが無くても一通り設定して ssh できる ところまで持って行くことを目標にする。

準備

用意するものは以下の通り。

  • Raspberry Pi 2 または 3 または 4
  • 8GB 以上の microSD カード
  • misroSD を書き込める PC (Win, Linux, mac)
  • インターネットに繋がる Wi-Fi または イーサネットケーブル

RasPi 無印 や Zero など、 SoC (CPU) が ARMv6 のものは、 Ubuntu ではサポートされていない。

microSD カードは、 IOPS が速いものの方が、後々うれしいだろう。
SD カードの規格で言えば、 アプリケーションパフォーマンスクラス A1 や A2 に対応しているものが良いというコトなのだろうが、正直モデル差やロット差が激しすぎるので、実際買ってみて IPOS が早いかどうか試してみるしかないと思う。

以下のものも有れば望ましいが、今回の手順ではなくても大丈夫。

  • HDMI (RasPi 4 の場合は micro-HDMI) で繋がるモニター
  • RasPi 繋げる USB キーボード

ブータブル SD の作成

まず、使用する Ubuntu の OS イメージを準備しよう。

2020年8月時点では、 20.04 LTS と 18.04 LTS に対して、それぞれ 32bit版 と 64bit版 が存在する。

特に理由が無ければ、 20.04 LTS の 64bit 版で良いだろう。
但し、 512MiB しか RAM がない RasPi 3A+ では、 64bit OS だと ssh でアクセスするだけで Out of Memory してしまうので、 32bit にすることをオススメする。
また、 RasPi 2 の CPU は 32bit 版なので、 32bit OS しか選べない。

microSD への書き込みは、 主に 以下の 2種類 の方法がある。
どちらか好きな方でどうぞ。

メモ:

以前は NOOBS という、 RasPi を起動後に OS 選択させてインストールさせるツールも存在したが、今回は触れない。
詳しくはググってほしい。

1. イメージファイルをダウンロードして 任意のツールで焼き込む


Install Ubuntu Server on a Raspberry Pi 2, 3 or 4 のページからダウンロードしたイメージファイルを、任意のツールで microSD に焼く方法。

個人的には、 balenaEtcher を使って焼くのが、以下の理由でオススメ。

  • .gz, .xz, zip などで圧縮された状態のイメージをそのまま焼ける
    • オンザフライで解凍しながら焼かれるので、あらかじめ展開しておく必要が無い
    • ... と思っていたけど、最近のバージョンでは一旦一時ファイルに展開されてから焼かれるっぽい。 それでも早いけど。
  • Portable 版がある (インストール不要)

2. Raspberry Pi Imager を使う


Raspberry Pi 財団公式サイトの Raspberry Pi Downloadsi ページからダウンロードできる、 Raspberry Pi Imager を使って、 OS のイメージをダウンロードしながら microSD に焼く方法。

Raspberry Pi Imager をインストールして起動すると、 インストールしたい OS を尋ねられるので、 ここで Ubuntu の任意のバージョンを選択する。

ダウンロードしながらそのまま焼き込むため、本来なら (1) よりも早くてオススメのハズ…
…なのだが、少なくとも私が試した限り、 (1) のほうがずっと早いんだよねぇ。。。

OS イメージを DL元 が違って、ダウンロード速度がネックになって遅くなっているのかな…
あまり深く調べてないけど。

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

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

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

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

An overview of the files on the /boot/firmware partition (the 1st partition
on the SD card) used by the Ubuntu boot process (roughly in order) is as
follows:

* bootcode.bin   - this is the second stage bootloader loaded by all pis with
                   the exception of the pi4 (where this is replaced by flash
                   memory)
* config.txt     - the first configuration file read by the boot process
* syscfg.txt     - the file in which system modified configuration will be
                   placed, included by config.txt
* usercfg.txt    - the file in which user modified configuration should be
                   placed, included by config.txt
* start*.elf     - the third stage bootloader, which handles device-tree
                   modification and which loads...
* uboot*.bin     - various u-boot binaries for different pi platforms; these
                   are launched as the "kernel" by config.txt
* boot.scr       - the boot script executed by uboot*.bin which in turn
                   loads...
* vmlinuz        - the Linux kernel, executed by boot.scr
* initrd.img     - the initramfs, executed by boot.scr
* meta-data      - meta-data for cloud-init; usually just contains the
                   instance id
* network-config - network configuration for cloud-init; edit this to set up
                   wifi access points and other networking settings
* user-data      - user-data for cloud-init; edit this to configure initial
                   users, SSH keys, packages, etc.

このパーティションには、 Raspberry Pi の BIOS の変わりとなる config.txt, bootcode.bin, start.elf と言った構成ファイルや、 ブートコード、 そして cloud-init の構成ファイルが入っている。

このうち cloud-init というのは、 Infrastructure as Code (IaC) の一種だ。 その名の通り、 AWS 等のクラウド上の仮想マシンの初期設定を得意としている。
校正用の設定ファイル (YAML) を使って設定内容を記述することで、 クラウドやディストリビューションの違いをある程度吸収して初期設定を記述することができる。
OS のイメージ内に予め cloud-init のサービス(デーモン) が組み込まれており、 OS の起動時に各クラウドのサービスの UI 経由で設定ファイルを読み取って、 主に初回起動時に初期設定処理が走る… というのが主な仕組みとなっている。

RaspPi 用の Ubuntu Server イメージでは、この cloud-init 用の設定ファイルの取得元が上記の system-boot パーティションとなっている。
イメージ組み込みの定義と system-boot パーティション内の定義を組み合わせて、 初期設定が実行される。

例えば、 このイメージを使ってそのまま Ubuntu を起動すると、 "ubuntu" という ログイン ID に "ubuntu" というパスワードが設定され、 SSH のパスワード認証が有効になった状態で起動している。
このうち、 "ubuntu" というユーザー名と SSH サーバーの起動は、 OS 組み込みの /etc/cloud/cloud.cfg の定義によるもの。
そして、 "ubuntu" というパスワードの設定と、 SSH のパスワード認証の有効化は、 system-boot パーティションの user-data ファイル内の以下の定義によるものだ。

#cloud-config
chpasswd:
  expire: true
  list:
  - ubuntu:ubuntu

ssh_pwauth: true

とうことで、 遠隔から SSH ログインできるように、 こららのファイルを順に書き換えていこう。

cloud-init の network-config

network-config ファイルには、 以下の 2つ の大きな役割がある。

  • cloud-init 初期設定実行前にデバイスをネットワークにつなぐ
  • netplan の /etc/netplan/50-cloud-init.yaml にファイルの内容を転記する

『何だ、同じことじゃないか』 と思われるかも知れないが、この二つは システムに適用されるタイミング と言う点で大きく異なる。

前者は、 cloud-init サービス の起動直後に、 cloud-init の通信を行うためにシステムに適用される。
一方後者は、後者は内容をファイルにコピーするだけである。

cloud-init サービスによって netplan の 50-cloud-init.yaml が書き換わるのは、ネットワークサービスの起動より後になる (systemd の /usr/lib/systemd/system/cloud-init.service の設定を参照)。
このため、システムやサービスが再起動されるか、能動的に netplan apply しないと、 50-cloud-init.yaml の内容は適用されない。

そしてもう一つ重要なのが、以下の一文。

Networking Config Version 2 — cloud-init 20.3 documentation

Cloud-init does not current support wifis type that is present in native netplan.

そう。
前者では、 Wi-Fi の設定が無視されるのだ。

すなわちこれは、 有線LAN が繋がっていない場合、 ファイルの更新や apt パッケージの取得などのインターネットに繋ぐような処理は、初回ブート時の cloud-init では全くできないことを意味する。
幸いにも、 Ubuntu イメージ組み込みの cloud-init の初期設定の内容は、ネットワークに繋がっていなくても処理が完走できる。

cloud-init の元々の用途を考えれば、 Wi-Fi が設定できる必要性はないだろうし、 仕方が無いことなのかも知れないが……

このファイルに Wi-Fi の設定を記載しただけでは、 初回起動時には Wi-Fi が有効にならないことは覚えておこう。
後の手順に関係してくる。

とりあえず、 netplan の設定に Wi-Fi アクセスポイントの情報は書いておきたいので、 network-config は以下のように書き換える。
SSID やパスワードは自分の環境のものに置き換えて書いてくれ。

--- network-config.org
+++ network-config
@@ -11,15 +11,15 @@
 ethernets:
   eth0:
     dhcp4: true
     optional: true
-#wifis:
-#  wlan0:
-#    dhcp4: true
-#    optional: true
-#    access-points:
-#      myhomewifi:
-#        password: "S3kr1t"
+wifis:
+  wlan0:
+    dhcp4: true
+    optional: true
+    access-points:
+      "SSID-of-myhomewifi":
+        password: "password-of-myhomewifi"
 #      myworkwifi:
 #        password: "correct battery horse staple"
 #      workssid:
 #        auth:

cloud-init の user-data

cloud-init 書き換えのメインとなる user-data。
これは、 Cloud Config Data の内容を記述する YAML 形式のファイルだ。
基本的には、 OS 組み込みの /etc/cloud/cloud.cfg の値を上書きしたり、 cloud.cfg で読み込まれた Modules の設定を記述していく。

書き換え部分が長いので、3カ所に分けて説明していく。

--- user-data.org
+++ user-data
@@ -14,12 +14,22 @@
 # expire user passwords
 chpasswd:
   expire: true
   list:
-  - ubuntu:ubuntu
+  - newusername:ubuntu
+
+# Override the default user name defined in cloud.cfg
+system_info:
+  default_user:
+    name: newusername

 # Enable password authentication with the SSH daemon
-ssh_pwauth: true
+ssh_pwauth: false
+ssh_authorized_keys:
+- ssh-rsa AAAA<中略>== rsa-key-of-user1
+
+# locale and timezone
+timezone: Asia/Tokyo

 ## On first boot, use ssh-import-id to give the specific users SSH access to
 ## the default user
 #ssh_import_id:

既に上でも触れたが、 OS 組み込みの cloud.cfg の定義によって、 "ubuntu" というユーザーが作成されている。
しかし、 既知のユーザー名をそのまま使うのはセキュリティ的にも若干不安があるし、 作成後のユーザー名を書き換えるのは若干面倒なので、作成されるデフォルトのユーザー名自体を、他の名前に書き換えてしまおう。
system_info.default_user で、作成されるユーザ名を変更できる。
また、 chpasswd によるパスワード書き換えの対象となるユーザ名も変更しておこう。

ssh_pwauth で、 ssh のパスワード認証が有効になっているので、 コレは切ってしまいつつ、
代わりに、 ssh_authorized_keys[*] リストに公開鍵を登録する。
これで安全に ssh できるようになった。恵dc

ついでにタイムゾーンも JST (Asia/Tokyo) に書き換えておこう。

--- user-data.org
+++ user-data
@@ -53,19 +63,20 @@
 #- pastebinit
 #- [libpython2.7, 2.7.3-0ubuntu3.1]

 ## Write arbitrary files to the file-system (including binaries!)
-#write_files:
-#- path: /etc/default/keyboard
-#  content: |
-#    # KEYBOARD configuration file
-#    # Consult the keyboard(5) manual page.
-#    XKBMODEL="pc105"
-#    XKBLAYOUT="gb"
-#    XKBVARIANT=""
-#    XKBOPTIONS="ctrl: nocaps"
-#  permissions: '0644'
-#  owner: root:root
+write_files:
+- path: /etc/default/keyboard
+  content: |
+    # KEYBOARD configuration file
+    # Consult the keyboard(5) manual page.
+    XKBMODEL="pc105"
+    XKBLAYOUT="jp"
+    XKBVARIANT=""
+    XKBOPTIONS=""
+  permissions: '0644'
+  owner: root:root
+- path: /etc/cloud/cloud-init.disabled
 #- encoding: gzip
 #  path: /usr/bin/hello
 #  content: !!binary |
 #    H4sIAIDb/U8C/1NW1E/KzNMvzuBKTc7IV8hIzcnJVyjPL8pJ4QIA6N+MVxsAAAA=

お次は、ファイルの作成。

日本語キーボードを認識しないと面倒なので、 /etc/default/keyboard を書き換えて日本語キーレイアウトを認識させる。
但し、 Ubuntu Server では、このファイルを書き換えただけだとキーボードレイアウトの変更が有効にならないので、後述の runcmddpkg-reconfigure コマンドを対話無しのオプションをつけて実行しておく。

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

--- user-data.org
+++ user-data
@@ -72,8 +83,19 @@
 #  owner: root:root
 #  permissions: '0755'

 ## Run arbitrary commands at rc.local like time
-#runcmd:
+runcmd:
+# apply keyboard configration
+- [ dpkg-reconfigure, -f, noninteractive, keyboard-configuration ]
+# apply netplan config defined on 'network-config'
+- [ netplan, apply ]
+# after wlan0 is connected, write the arp entry to target machine on background job
+- [ nohup, sh, -c, 'sleep 30; ping 192.168.0.2 &' ]
 #- [ ls, -l, / ]
 #- [ sh, -xc, "echo $(date) ': hello world!'" ]
 #- [ wget, "http://ubuntu.com", -O, /run/mydir/index.html ]
+
+# Restart 2 minutes after running all config modules (for apply keyboard configration)
+power_state:
+  delay: '+2'
+  mode: reboot

最後に、任意のコマンドの実行。

最初の dpkg-reconfigure は、 keyboard の書き換えを反映させるため。

[ netplan, apply ] のコマンドで初めて、 network-config で設定した Wi-Fi 設定が有効になる。

最後の [ nohup, sh, -c, 'sleep 30; ping 192.168.0.2 &' ] は説明が難しいのだが、これは仮に ssh でゲストの IP アドレスが 192.168.0.2 であると仮定して、そこへ向かって Ping を打っている。
何故そのようなことをしているかというと、 ゲストPC から RaspPi の IP アドレスを ARP コマンドで確実に調べられるようにするためだ。

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

How to install Ubuntu on your Raspberry Pi #4 に書かれているように、 RasPi の MAC アドレスの先頭 24bit は b8-27-eb (Pi 2~3) もしくは dc-a6-32 (Pi 4) と決まっている。
通常、 RasPi がオンラインになったときに、 ARPリクエストがブロードキャストされるため、 同一ネットワーク内の PC の ARP テーブルに RasPi のレコードが追加され、 以下のような arp コマンドで対象の IP アドレスを調べることができるようになる。。。 多くの場合は。

while(1) { arp -a | ?{ $_ -match 'dc-a6-32' }; Start-Sleep -Seconds 1}

ところが、 RasPi が Wi-Fi で繋いでいるのがルーターだったりすると、 ルーターが気を利かせて、 同じ サブネット内であっても ARPリクエストを代替して投げてしまい、 同じサブネットの端末全員の ARP テーブルには、 RasPi の MAC アドレスのレコードが記録されないことがある。

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

ちなみに、 30秒 sleep しているのは、 netplan apply した直後はまだ Wi-Fi に繋がっていないことが多いためだ。

そして最後に、 cloud-init の停止や タイムゾーンの変更を確実に反映するため、 power_state で再起動させている。

RasPi の config.txt

cloud-init とは関係ないが、 必要があれば config.txt の構成ファイル書き換えてしまおう。

例えば、 HDMI の解像度が一般的でなかった場合や、 端末とのネゴシエーションがヘタクソで適切な解像度で表示できない場合などは、 このファイルで HDMI の解像度を指定してやる必要がある。

参考:

但し、 config.txt には、

# Please DO NOT modify this file; if you need to modify the boot config, the
# "usercfg.txt" file is the place to include user changes.

って書いてあるので、 書き換えるなら config.txt ではなく usercfg.txt にしておくと良い。

--- usercfg.txt.org
+++ usercfg.txt
@@ -1,3 +1,9 @@
 # Place "config.txt" changes (dtparam, dtoverlay, disable_overscan, etc.) in
 # this file. Please refer to the README file for a description of the various
 # configuration files on the boot partition.
+
+framebuffer_width=1024
+framebuffer_height=600
+hdmi_group=2
+hdmi_mode=87
+hdmi_cvt=1024 600 60 1 0 0 0

起動する

こんな感じで一通りファイルを書き換えたと、 RasPi に microSD をさして起動すると、初回ブート時の設定を適用しながら起動してくれる。

cloud-init の処理が終わらなくても、 ログイン画面には進み、 その後も cloud-init のサービスは稼働し続ける。
このため、 最初の起動でログイン可能になったとしてもログインせず、 全ての処理が終わって一度再起動されるのをおとなしく待つ方が良い。

RasPi 3A+ の場合、全ての処理が終わるのに5分以上ったので、気長に待つようにしよう。

前述の arp で RasPi の IPアドレスを調べて、そこに対して SSH できるようになっていれば、とりあえずは完成だ。

cloud-init についてもう少し掘り下げて説明しようと思ったのだが、記事が長くなってしまったので、次回の更新に回す。

オマケ: Raspberry Pi を買う

Raspberry Pi は元々薄利で販売されているので、ディスカウントされていることはほとんど無く、むしろ少し高めで販売されていることが多いくらいだ。

その中でも、主な販売サイトの表示価格だけ見れば、 RSコンポーネンツKSY, マルツ あたりが、 Pi 4 2GB モデルで 5,000円 前後、 4GB モデルで 6,800円 前後と、比較的安い。

しかし、自分がいつも使っていて個人的にオススメなのは、 ソリノベ研究所Yahoo!通販 だ。
表示価格こそ、 4GB モデル: 7,580円 と少し高めだが、

  • 送料無料
  • 翌日配送
  • 定期セールなどの条件によっては、 15~20% ほど Yahoo!ショッピング のポイント還元を狙える
  • 初心者向けのガイドや資料を添付してくれる

などの理由で使い勝手が良く、条件によっては一番安く買える場合もある。

コメントを残す

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

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