VPSの次回起動カーネルを固定する方法
UbuntuのクラウドイメージでGRUB_DEFAULTが効かない原因を確認します。実際のメニュー項目を読み取り、SSH接続でもレスキューコンソールを避けて次回起動を固定する手順です。
VPS が次回起動するカーネルを決めるもの
VPS が次回起動するカーネルは、1 つの生成ファイルである /boot/grub/grub.cfg によって決まります。このファイルを直接編集してはいけません。入力ファイルを編集して再生成します。Ubuntu のクラウドイメージでは、入力ファイルの 1 つがイメージベンダーから提供されます。この設定によってメニュー選択が無効になることがあります。そのため、GRUB_DEFAULT=1 に続けて update-grub を実行しても、レンタルサーバーでは何も変わりません。一方、同じ 2 つの手順はラップトップへのインストールでは機能します。
次の順序で作業します。まず、カーネルを自分で選択できるか確認します。ベンダーが追加したファイルも含め、すべての入力ファイルを読みます。生成された出力を読み、実際に保持されているエントリ数を数えます。その後で、固定方法を選びます。SSH だけで接続しているマシンでこの手順を誤ると、レスキューコンソールが必要になります。そのため、最も安全な方法はこのページの後半に記載されており、多くの場合、それらが適切な選択です。
まず固定する対象が自分のカーネルか確認します
uname -r
systemd-detect-virt
ls -1 /boot/vmlinuz-*systemd-detect-virtで kvm、qemu、または xen が表示される場合は、自分でカーネルを実行しているため、以下の内容がすべて適用されます。lxc または openvz が表示される場合、サーバーはホストのカーネルを共有しています。そのため、自分のブートローダーは存在せず、固定できるものもありません。この場合、uname -r が報告するバージョンは /boot/vmlinuz-* にはまったく表示されません。実行中のカーネルはホストのものであり、ディスク上の設定では変更できないためです。
ls -1 /boot/vmlinuz-* には、実際に選択できるカーネルの一覧が表示されます。1 行しかない場合、以前のカーネルはすでに削除されており、ブートローダーの設定で復元することはできません。通常は autoremove の実行時に発生します。大切なサーバーで Ubuntu の古いカーネルを削除する前に、この動作を理解しておくとよいでしょう。
編集するファイルは、GRUB が読み込むファイルではありません
/etc/default/grub には、シェル変数への単純な代入が記述されています。これは入力です。/boot/grub/grub.cfg が出力であり、# DO NOT EDIT THIS FILE とその理由で始まります。出力に直接書き込んだ内容は、次回カーネルパッケージをインストールまたは削除したときに失われます。パッケージのスクリプトが出力を再生成するためです。
cat /usr/sbin/update-grubupdate-grub はラッパーです。grub-mkconfig -o /boot/grub/grub.cfg を実行すると、変数を読み込み、/etc/grub.d/ 内のすべてのスクリプトを実行し、その結果を書き出します。2 つのコマンドが一方向に処理します。入力を渡すと、grub.cfg が生成されます。
設定を上書きする場所: /etc/default/grub.d
grep -rn '^[^#]' /etc/default/grub /etc/default/grub.d/見落とされやすいのは、2 つ目のパスです。grub-mkconfig は最初に /etc/default/grub を読み込み、その後、/etc/default/grub.d/ 内のすべての *.cfg ファイルを glob の順序で読み込みます。実際の処理を確認してください。
grep -n 'default/grub' /usr/sbin/grub-mkconfig読み込みは通常の shell 処理なので、最後の代入が有効になります。Ubuntu cloud image はこのディレクトリにファイルを提供しており、タイムアウトや kernel command line などを、すでに自分のファイルを読み込んだ後で設定します。/etc/default/grub 内の GRUB_TIMEOUT=10 は、その直後に値を 0 に設定する vendor ファイルによって上書きされます。上記の grep は使用中の image にある正確な代入を表示するため、この文をそのまま信頼せず、表示された内容を確認してください。
ここから導かれる実用的なルールは、自分の設定を /etc/default/grub に直接編集するのではなく、/etc/default/grub.d/99-local.cfg のようにソート順が最後になるファイルへ記述することです。これにより、image が提供するファイルに後から上書きされることがなくなります。
GRUB_FORCE_PARTUUID によりメニュー選択が意味を持たなくなる理由
grep -rn GRUB_FORCE_PARTUUID /etc/default/grub /etc/default/grub.d/
grep -n GRUB_FORCE_PARTUUID /etc/grub.d/10_linux
sudo grep -n 'root=PARTUUID' /boot/grub/grub.cfgGRUB_FORCE_PARTUUID は、起動時にファイルシステム UUID を検索するのではなく、パーティション UUID で root ファイルシステムを特定するようジェネレーターに指示します。この UUID は root=PARTUUID=... としてカーネルコマンドラインに直接書き込まれます。イメージベンダーがこの設定を使用するのは、別のハードウェア向けに作成されたディスクイメージでも確実に起動できるようにするためです。2 つ目の grep では、/etc/grub.d/10_linux にあるこの変数を処理するコードを確認できます。このスクリプトは実際のディスク上にあり、イメージの動作を判断する基準になります。
ここで重要なのは、その結果です。この経路では、ジェネレーターはインストール済みカーネルの一覧ではなく、直接起動エントリを書き込みます。最終的にいくつ生成されたかを数えてください。
sudo grep -cE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg
sudo grep -nE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg数が 1 なら、選択できる 2 つ目のエントリはありません。そのため GRUB_DEFAULT=1 は存在しないエントリを指定します。GRUB はそれを解決できず、最初のエントリを起動します。これは避けようとしていた新しいカーネルです。grub-set-default も役に立ちません。問題はデフォルト値ではないためです。選択しようとしているメニュー自体が生成されていません。
完全なメニューを戻すには、ベンダーのファイルを別の場所へ移動し、適用する前に結果を確認します。grub-mkconfig を -o なしで実行すると、標準出力に書き出すだけで、ディスク上のファイルは変更しません。
sudo grub-mkconfig 2>/dev/null | grep -cE '^\s*(menuentry|submenu) '
sudo mkdir -p /root/grub-backup
sudo mv /etc/default/grub.d/<the file your grep named> /root/grub-backup/
sudo grub-mkconfig 2>/dev/null | grep -cE '^\s*(menuentry|submenu) '数が 1 から複数に増えた場合は、強制指定を解除するとエントリが表示されます。この時点では何も書き込まれていません。2 回目の数が適切でなければ、ファイルを元に戻してください。強制された PARTUUID は、プロバイダーのイメージが root ファイルシステムを特定するために使用しているためです。これを削除すると、システムは検索経路を使用するようになります。update-grub を実際に実行する前に、スナップショットを取得してください。
目的が 1 つの問題のあるカーネルを回避することだけなら、ここで止めて、後述する安全性の高い方法を使用してください。1 回のアップグレードを回避するためにリモートサーバーのブートメニューを再構築するのは、問題に対してリスクが大きすぎます。
エントリー番号を固定してはいけない理由
GRUB_DEFAULTは、番号、タイトル、識別子を受け付けます。番号はトップレベルのエントリーを0から数えます。ネストしたエントリーでは>を区切り文字として使用します。そのため、GRUB_DEFAULT="1>2"はインデックス1のサブメニュー内にあるインデックス2のエントリーを示します。
インデックスは変わります。10_linuxはカーネルを新しい順に一覧表示するため、カーネルをインストールすると、それより古いすべてのエントリーが1つ下がります。カーネルを削除すると、逆に1つ上がります。慎重に設定した1>2も、その後はそのまま解釈されます。ただし、別のカーネルを指定することになります。エラーも警告も表示されないため、再起動後に初めて問題に気付きます。
識別子は、それぞれにカーネルのバージョンが含まれているため変わりません。次のコマンドで確認します。
sudo awk -F"'" '/menuentry_id_option/ {print $2, "==>", $4}' /boot/grub/grub.cfg出力の最初の数行は無視してください。ヘッダーで定義されている変数です。それ以降は、左側に利用者が見るタイトル、右側にツールへ渡す識別子が表示されます。サブメニュー内のエントリーでは、サブメニューの識別子とエントリーの識別子を>で、この順序どおりに連結します。これは数値形式の場合と同じです。
grub-reboot で前回のカーネルを 1 回だけ起動する
リモートサーバーでは、1 回だけ選択する方法が適しています。選択が自動的に元に戻るためです。grub-reboot は /boot/grub/grubenv に next_entry を書き込みます。GRUB はその変数を読み取り、何かを起動する前に変数をクリアして、クリア後の値を保存します。そのため、カーネルが panic しても、次回の起動で同じカーネルが再試行されません。試行は 1 回だけ行われ、その後はマシンが自動的に通常のデフォルトへ戻ります。
まず、生成済みの設定がその変数を実際に読み取ることを確認します。
sudo grep -n -B2 -A5 'next_entry' /boot/grub/grub.cfgload_env の行と、next_entry から default を設定するブロックが必要です。grep の出力が何もない場合、イメージは起動時に grubenv を読み取っていません。そのため、grub-reboot はシェルでは受け付けられても、ブートローダーには無視されます。これは、前のセクションで説明した強制的な直接起動の経路が、別の場所にも現れている状態です。
sudo grub-reboot '<the identifier you copied>'
sudo grub-editenv listgrub-editenv list は、渡した値と完全に一致する next_entry= の行を出力するはずです。プロバイダーのコンソールをブラウザーのタブで開いてから再起動し、結果を確認します。
sudo rebootuname -runame -r が古いバージョンを報告すれば、指定が機能したことを意味します。新しいバージョンを報告する場合は、識別子を解決できなかったか、grubenv が読み取られていません。いずれの場合もマシンは起動しており、これが 1 回だけ指定する形式を使う目的です。
GRUB_DEFAULT=saved で選択を固定する
GRUB_DEFAULT=saved を指定すると、デフォルトのエントリが grubenv 内の saved_entry から取得されます。この値は grub-set-default で設定します。update-grub は grub.cfg を書き換えますが、grubenv には触れないため、カーネルをインストールしても設定は維持されます。
echo 'GRUB_DEFAULT=saved' | sudo tee /etc/default/grub.d/99-local.cfg
sudo update-grub
sudo grub-set-default '<the identifier you copied>'
sudo grub-editenv list
sudo grep -n 'set default' /boot/grub/grub.cfg最後のコマンドの出力は set default="${saved_entry}" になる必要があります。set default="0" と表示される場合は、ファイルの後に読み込まれた設定によって GRUB_DEFAULT がリテラル値に戻されています。そのため、/etc/default/grub.d/ をもう一度一覧表示し、99-local.cfg が本当に最後に並んでいることを確認してください。
GRUB_SAVEDEFAULT=true は別の設定であり、この設定と混同しやすい点に注意してください。これは、直前に起動したエントリを新しいデフォルトとして保存します。そのため、デフォルトは最後に正常起動したエントリに追従します。サーバーでは、無人再起動によって固定したエントリが気付かないうちに変わる可能性があります。意図的にこの動作を使う場合を除き、無効のままにしてください。
識別子で固定しても、1 つ問題が残ります。指定したカーネルを削除すると、その識別子を解決できなくなり、最初のエントリに戻ります。そのため、パッケージも hold するか、そのカーネルを autoremove の対象外にしてください。
プロバイダーのコンソールにメニューを表示する
対話的に選択するには、画面にメニューを表示する必要があります。クラウドイメージではメニューが非表示になっているため、これらの設定を最後に読み込まれるファイルに追加してから、sudo update-grubを実行します。
GRUB_TIMEOUT=10
GRUB_TIMEOUT_STYLE=menu
GRUB_RECORDFAIL_TIMEOUT=10GRUB_TIMEOUT_STYLE=hiddenとGRUB_TIMEOUT=0を組み合わせると何も表示されません。そのため、コンソールを監視しているユーザーには、カーネルメッセージがすぐに表示され、ブートローダーがスキップされたように見えます。GRUB_RECORDFAIL_TIMEOUTは、ブートが完了しなかった場合に使用される別のタイムアウトです。クラウドイメージではこれも0に設定されているため、起動に失敗したサーバーも停止して待機しません。
プロバイダーがグラフィカルコンソールではなくシリアルコンソールを提供していて、それでも何も表示されない場合、GRUBは確認できない端末に出力しています。次の2行を一緒に追加してください。1行目で出力先を選択し、2行目でポートを設定します。
GRUB_TERMINAL="console serial"
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"以後のすべてのブートで10秒の待機時間が追加されます。作業が終わったら、タイムアウトを0に戻してください。
ブートローダーを編集するより安全な方法
SSH でしか接続できないマシンのブートローダー入力を変更することは、このページで最もリスクの高い方法です。より簡単な方法があり、通常は本当の問題も解決できます。
カーネルパッケージを hold します。 目的が「新しいカーネルを適用しない」ことであれば、ブートローダーではなくパッケージマネージャーにその内容を指定します。
apt list --installed 2>/dev/null | grep -E '^linux-(image|headers|generic|virtual|kvm|aws|azure|gcp|oracle)'
sudo apt-mark hold linux-image-virtual linux-headers-virtual
apt-mark showhold最初のコマンドで表示された名前を使用してください。クラウドイメージでは、generic ではなく virtual または kvm の flavor がインストールされることがよくあります。hold されたパッケージは apt upgrade によってスキップされ、そのことが The following packages have been kept back: で通知されます。Ubuntu の unattended upgrades でも同様にスキップされます。ただし、hold したカーネルにはセキュリティ修正が適用されなくなるため、期限を決めた一時停止として扱い、sudo apt-mark unhold で解除してください。再起動によるダウンタイムが問題で、特定のカーネルが不良だからではない場合は、VPS での live kernel patching がその問題に適しています。
アップグレード前にスナップショットを作成します。 スナップショットなら、コンソールで入力する必要も、適用途中のブートローダー変更が発生する可能性もなく、数分で復元できます。スナップショットを作成し、アップグレードして、再起動後に確認します。新しいカーネルに問題があればロールバックでき、ブート経路は完全に元の状態に戻ります。
すでに停止しているサーバーには、コンソールまたは rescue image を使用します。 サーバーが起動しなくなった場合、ブートローダー設定をその場で修正することはできません。この復旧手順は独立した手順です。カーネル更新後に VPS が起動しない場合の対処方法。
発生する問題と表示されるメッセージ
/boot/grub/grub.cfgへの編集内容が消えています。 カーネルパッケージがインストールまたは削除され、メンテナースクリプトが update-grub を実行したため、入力元からファイルが再生成されました。# DO NOT EDIT THIS FILE ヘッダーに、2 つの入力場所が示されています。そこを編集してください。
grub-editenv: error: environment block too smallです。 /boot/grub/grubenv が存在しないか、内容が途中で切れています。sudo grub-editenv /boot/grub/grubenv create で再作成し、値をもう一度設定して sudo grub-editenv list で確認してください。
固定したカーネルが VFS: Unable to mount root fs on unknown-block(0,0) でパニックします。 固定したエントリが、ディスク上にもう存在しないカーネルまたは initrd を指しています。通常は、識別子が grubenv に残ったままパッケージが削除されたことが原因です。動作するエントリをコンソールから起動し、その後で古い値を消去して復旧します。
変更されるはずの再起動後も uname -r が変わりません。 次の 3 点を順に確認してください。grub-editenv list に値がまだ表示されているか、それとも消費されたか。設定した識別子が現在の grub.cfg に表示されているか。grub.cfg に、設定した変数を読み取る set default 行が含まれているか。この 3 点のいずれかで、毎回原因を特定できます。
クラッシュ後、メニューが自動的に表示されました。 GRUB は grubenv に起動失敗を recordfail=1 として記録します。これにより、次回の起動時に人が介入できるようメニューが表示されます。マシンが正常な状態になったら、sudo grub-editenv /boot/grub/grubenv unset recordfail でこの状態を消去してください。
覚えておくべき文は 1 つだけです。編集するファイルと GRUB が読み取るファイルは異なり、cloud image では、その間の差が混乱の原因になります。まず生成済みの設定を読み取ってください。このページのすべての判断は、実際の内容に基づいています。
FAQ
GRUB_DEFAULT=1 に設定しても VPS が起動するカーネルが変わらないのはなぜですか?
Ubuntu の cloud image では、生成された /boot/grub/grub.cfg に通常、単一のブートエントリしか含まれないためです。この場合、インデックス 1 は存在せず、GRUB は最初のエントリにフォールバックします。sudo grep -cE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg で確認できます。件数が 1 なら、これが原因です。原因は GRUB_FORCE_PARTUUID です。これはイメージのベンダーが /etc/default/grub.d/ 配下のファイルで設定しています。この設定により、ジェネレーターはインストール済みカーネルの完全な一覧を構築せず、直接ブートする経路を使用します。grep -rn GRUB_FORCE_PARTUUID /etc/default/grub /etc/default/grub.d/ でファイルを探してください。
以前のカーネルを 1 回だけ起動するにはどうすればよいですか?
sudo grub-reboot '<identifier>' を実行します。識別子には、自分の grub.cfg からコピーした値を使用してください。その後、プロバイダーのコンソールを開いた状態で再起動します。GRUB は起動前に next_entry を消去するため、この選択が適用されるのは 1 回だけです。パニックしたカーネルが再試行されることもありません。値が反映されたことを sudo grub-editenv list で確認してください。使用前に sudo grep -n next_entry /boot/grub/grub.cfg を実行してください。設定で grubenv が読み込まれないイメージでは、コマンドがエラーなしで無視されるためです。
エントリ番号と識別子のどちらで固定すべきですか?
識別子を使用してください。エントリ番号は、10_linux が新しい順に再構築する一覧内の位置です。そのため、カーネルをインストールまたは削除すると番号が変わります。古い 1>2 は警告を表示せず、実在する別のエントリとして解決される可能性もあります。識別子にはカーネルのバージョンが含まれるため、目的のカーネルと一致するか、解決に失敗します。sudo grep -n menuentry_id_option /boot/grub/grub.cfg で一覧を表示し、各エントリ行の後に続く引用符付き文字列をコピーしてください。
カーネルパッケージを hold するほうがブートローダーを変更するより安全ですか?
通常の目的であれば、安全です。sudo apt-mark hold linux-image-virtual linux-headers-virtual を実行すると、新しいカーネル自体がインストールされなくなります。そのためブート経路は変わらず、利用できない可能性があるコンソール上で設定を誤ることもありません。まず apt list --installed で、自分の環境にインストールされている flavour 名を確認してください。続いて apt-mark showhold で hold を確認します。ただし、hold したカーネルにはセキュリティ修正が提供されません。hold を実行する前に、いつ sudo apt-mark unhold を実行するか決めてください。