何番煎じかわからないが、 Raspberry Pi (以下、 RasPi) に Ubuntu を入れる手順についてまとめてみようと思う。
Ubuntu 公式の How to install Ubuntu on your Raspberry Pi チュートリアルは充実しているし、 単にインストールして動かすだけなら、既にある記事でも十分だろうとは思う。
しかし、初回セットアップ時の細かいカスタマイズについて書かれているものがあまり見当たらなかったので、この記事ではそれについて補足しながらまとめていく。
先に断っておくが、 cloud-init による IaC の話が中心になる。
完全に私事だが、 おしごとで 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 できる ところまで持って行くことを目標にする。
用意するものは以下の通り。
RasPi 無印 や Zero など、 SoC (CPU) が ARMv6 のものは、 Ubuntu ではサポートされていない。
microSD カードは、 IOPS が速いものの方が、後々うれしいだろう。
SD カードの規格で言えば、 アプリケーションパフォーマンスクラス A1 や A2 に対応しているものが良いというコトなのだろうが、正直モデル差やロット差が激しすぎるので、実際買ってみて IPOS が早いかどうか試してみるしかないと思う。
以下のものも有れば望ましいが、今回の手順ではなくても大丈夫。
まず、使用する 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 選択させてインストールさせるツールも存在したが、今回は触れない。
詳しくはググってほしい。
Install Ubuntu Server on a Raspberry Pi 2, 3 or 4 のページからダウンロードしたイメージファイルを、任意のツールで microSD に焼く方法。
個人的には、 balenaEtcher を使って焼くのが、以下の理由でオススメ。
Raspberry Pi 財団公式サイトの Raspberry Pi Downloadsi ページからダウンロードできる、 Raspberry Pi Imager を使って、 OS のイメージをダウンロードしながら microSD に焼く方法。
Raspberry Pi Imager をインストールして起動すると、 インストールしたい OS を尋ねられるので、 ここで Ubuntu の任意のバージョンを選択する。
ダウンロードしながらそのまま焼き込むため、本来なら (1) よりも早くてオススメのハズ…
…なのだが、少なくとも私が試した限り、 (1) のほうがずっと早いんだよねぇ。。。
OS イメージを DL元 が違って、ダウンロード速度がネックになって遅くなっているのかな…
あまり深く調べてないけど。
作成した 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 ログインできるように、 こららのファイルを順に書き換えていこう。
network-config ファイルには、 以下の 2つ の大きな役割がある。
/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 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 では、このファイルを書き換えただけだとキーボードレイアウトの変更が有効にならないので、後述の runcmd
で dpkg-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
で再起動させている。
cloud-init とは関係ないが、 必要があれば config.txt の構成ファイル書き換えてしまおう。
例えば、 HDMI の解像度が一般的でなかった場合や、 端末とのネゴシエーションがヘタクソで適切な解像度で表示できない場合などは、 このファイルで HDMI の解像度を指定してやる必要がある。
参考:
* Raspberry Pi 4B 画面解像度設定(TV出力版) - Qiita
* Video options in config.txt - Raspberry Pi Documentation
但し、 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 についてもう少し掘り下げて説明しようと思ったのだが、記事が長くなってしまったので、次回の更新に回す。
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント