SSD Nodes Learn 🎉 VPS $5.50/月〜
ガイド Matt Connor著者 Matt Connor

VPSクローンのmachine-id重複を安全に直す方法

クローンしたVPSで同じ/etc/machine-idを使うと、DHCPリースが競合します。4つのコマンドで安全に再生成し、golden image作成前に空にする手順を解説します。

/etc/machine-id の概要と重複が問題になる理由

クローンした VPS は、クローン元のサーバーと同じ /etc/machine-id で起動します。この値は、1 つのインストールにだけ割り当てられるものです。対処は 4 つのコマンドで行います。ファイルを空にし、D-Bus のコピーが実体のあるファイルであれば削除し、再生成して、再起動します。再起動は省略されがちですが、変更を反映させるために必要な手順です。

/etc/machine-id には、改行で終わる小文字の 32 文字の 16 進文字列が格納されます。これはデコードすると、16 バイト (128 ビット) の値です。machine-id(5) のマニュアルページは、この値を機密情報とし、ネットワーク上に公開してはならないと説明しています。読み取ったものが、後で同じマシンを識別できるためです。この値はシステムのインストール時に 1 回だけ書き込まれ、その後は変更されません。

ここでは 3 つの識別子が混同されやすいため、分けて考える必要があります。hostname は任意に設定するラベルで、いつでも変更できます。/sys/class/dmi/id/product_uuid にある DMI (desktop management interface) の product UUID は hypervisor が指定する値で、root だけが読み取れます。machine ID は 3 つ目の識別子です。これはオペレーティングシステムが生成し、そのマシン上のすべてのユーザーが読み取れます。

実際にマシン ID を読み取るもの

DHCP クライアント識別子。 これが最も問題になります。systemd.network(5) は、[DHCPv4] セクションで ClientIdentifier= のデフォルト値を duid と説明しています。これは IAID と DUID(DHCP unique identifier)から作成した RFC 4361 クライアント ID を送信します。networkd.conf(5) では、DUID のデフォルト種別を vendor と説明しています。この場合、DUID 値はベンダー識別子として 43793(systemd)を使用し、マシン ID のハッシュ化した内容から生成されます。DHCPv6 でも同じ DUID が使用されます。同じマシン ID を持つ 2 台のクローンは同じ DUID にハッシュされます。さらにインターフェイス名も同じであれば、バイト単位で同一のクライアント識別子を送信します。DHCP サーバーは 2 台ではなく 1 台のクライアントとして認識し、両方のサーバーに同じリースを提示します。2 台の間でアドレスが移動する、または一方が更新すると他方のサーバーでアドレスを失う、といった症状が現れます。

journald。 ジャーナルファイルは /var/log/journal/<machine-id>/ に保存されます。このディレクトリ名は ID に基づいて付けられます。2 台のクローンのジャーナルを 1 台のコレクターに送信すると、1 つのディレクトリに保存され、1 台のホストとして扱われます。

D-Bus。 /var/lib/dbus/machine-id は、このファイル形式が最初に使用された場所です。Debian と Ubuntu では、これは /etc/machine-id へのシンボリックリンクです。一部のシステムでは、独自のコピーを保持する別個の実ファイルになっています。このコピーが、以下の手順で問題になります。

ホスト単位のエージェント。 監視エージェント、ライセンスチェック、インベントリツール、バックアップクライアントは、マシン ID をデフォルトのホスト識別子として使用することがよくあります。値が安定しており、設定が不要だからです。2 台のサーバーが 1 つの識別情報を報告すると、メトリクス系列が 1 つに統合されたり、1 つのライセンスシートで 2 台のマシンを処理したりします。エージェントがホスト ID をどのように生成するかを確認し、hostname を使用すると決めつけないでください。

重複しているかどうかを確認する方法

両方のサーバーで実行し、出力を比較します。

cat /etc/machine-id
ls -l /var/lib/dbus/machine-id
sudo cat /sys/class/dmi/id/product_uuid

稼働中の 2 台のサーバーで machine ID が同一なら、一方をもう一方からクローンしたことを意味します。1 つのコマンドで確認する場合は、hostnamectlMachine ID: 行にも同じ値が表示されます。

次の手順は ls -l の結果で決まります。シンボリックリンクは次のように表示されます。

lrwxrwxrwx 1 root root 15 Aug 21 09:12 /var/lib/dbus/machine-id -> /etc/machine-id

-rw-r--r-- で始まる行は、古い ID のコピーを自身で保持する実ファイルを示します。systemd-machine-id-setup は他の処理より先にこのファイルを読み取るため、削除する必要があります。

product UUID も重要です。systemd-machine-id-setup(1) はランダム生成にフォールバックする前に KVM UUID を使用します。そのため、プロバイダーが両方のクローンに同じ SMBIOS(system management BIOS)UUID を割り当てている場合、再生成しても同じ machine ID が 2 つ生成されます。2 台の product UUID が異なっていれば、この点については問題ありません。

クローンした VPS で machine ID を再生成する

実行順序が重要です。systemd-machine-id-setup(1)は、システムに有効な D-Bus machine ID がすでに設定されている場合、その D-Bus machine ID をコピーして /etc/machine-id の初期化に使用します。実際の /var/lib/dbus/machine-id を残したままにすると、削除したかった値とまったく同じ値を再生成することになります。

sudo truncate -s 0 /etc/machine-id
sudo rm -f /var/lib/dbus/machine-id       # only if ls -l showed a real file
sudo systemd-machine-id-setup
sudo ln -sf /etc/machine-id /var/lib/dbus/machine-id
cat /etc/machine-id

最初に切り詰める必要があります。このツールはファイルが存在しないか空の場合にだけ動作し、有効な ID がすでに格納されているファイルには何もしません。systemd-machine-id-setup は、実行結果を標準エラー出力に表示します。KVM VPS では通常、次のように表示されます。

Initializing machine ID from KVM UUID.

Initializing machine ID from random generator. は、ハイパーバイザー UUID を利用できない場合のメッセージです。cat /etc/machine-id が他のサーバーとは異なる値を表示するようになっていれば、どちらの結果でも問題ありません。

このシンボリックリンクにより、D-Bus と systemd が同じ値を使用します。別の実ファイルを使用する場合は、代わりに sudo dbus-uuidgen --ensure を実行してください。ファイルが存在しない場合は、新しい UUID を持つファイルを作成します。dbus がインストールされていない場合、/var/lib/dbus ディレクトリ自体が存在せず、lnNo such file or directory で失敗します。その場合は、これら2行を省略できます。

その後、再起動します。

sudo reboot

再起動が省略できない理由

古い値をすでに読み込んだプロセスは、引き続きその値を使用します。sd_id128_get_machine() は呼び出し元プロセス内に ID をキャッシュするため、実行中の daemon はファイルが変更されても認識しません。journald はすでに /var/log/journal/<old-id>/system.journal を開いており、そこへ追記し続けます。systemd-networkd は起動時に DUID を決定し、更新のたびに古いクライアント識別子を送信し続けます。これは通常、解決しようとしていた問題そのものです。D-Bus も起動時に ID を読み込みます。サービスを1つずつ再起動することはできますが、再起動漏れが発生します。PID 1 も古い値を保持しています。

再起動後は、次の2つを確認します。

cat /etc/machine-id
ls /var/log/journal/

/var/log/journal/ には、新しい ID に基づく2つ目のディレクトリが作成され、そこに新しいエントリが保存されます。通常の journalctl は現在のマシンのディレクトリだけを読み取るため、クローン前の履歴はデフォルトの表示から消えます。ログ自体はディスク上に残っています。journalctl --merge は古いディレクトリを含むすべての journal ディレクトリを読み取ります。不要になったログであることを確認してから、古いディレクトリを削除してください。

このため、コンテナ内で手順を予行演習することもできません。コンテナはホストの kernel を共有し、独自の PID 1 で起動することもありません。この手順では再起動そのものが重要です。本番環境と同じ方法でテストしてください。VM をクローンし、コマンドを実行して再起動した後、ID を元のマシンと比較します。

スナップショットを取得する前に切り詰める。クローン後ではない

クローンを1台ずつ修正する方法でも対応できます。ただし、イメージを修正する方が適切です。不正なスナップショットから復元したすべてのサーバーが、同じ値を引き継ぐためです。テンプレートを停止する直前に、最後の作業として実施してください。

sudo truncate -s 0 /etc/machine-id
sudo rm -f /var/lib/dbus/machine-id
sudo ln -s /etc/machine-id /var/lib/dbus/machine-id
sudo shutdown -h now

ファイルの内容を空にします。ファイル自体は削除しないでください。複数のマシンで使用するイメージには、空のファイルを用意することを machine-id(5) は推奨しています。ファイルが存在して空であれば、イメージを read-only で使用する際に、一時ファイルを実際のファイルの上に bind mount できるためです。read-only の /etc では、ブート時に生成された ID がその一時ファイルに保存され、ファイルシステムが書き込み可能になると systemd-machine-id-setup --commit がその値を書き込みます。

事前に考慮すべき影響が1つあります。machine ID が未設定だと、次回のブートが初回ブートとして扱われます。そのため、ConditionFirstBoot=yes を持つ unit はそのブートで実行され、それ以降のブートではスキップされます。テンプレートを作成する前に、grep -rl ConditionFirstBoot /usr/lib/systemd/system/ でイメージ上で実行される処理を確認してください。

テンプレートとスナップショットは異なるオブジェクトです。この違いによって、identity がコピーされるかどうかが決まります。テンプレートは意図的に準備するビルド成果物です。一方、スナップショットは稼働中の1台のサーバーを特定時点でコピーしたものであり、そのデータとともにサーバーの identity も保持します。

クラウドイメージでは正しく処理されるのに、独自に作成したスナップショットでは処理されない理由

ディストリビューションのクラウドイメージはクローンを前提に構築されています。そのため、machine ID が未設定の状態で提供され、初回起動時に設定されます。cloud-init には、この処理専用の手順が文書化されています。cloud-init clean --machine-id は systemd システムで /etc/machine-id を文字列 uninitialized に設定します。cloud-init CLI リファレンスでも、golden image のクローン時のベストプラクティスとして説明されています。そのため、次回そのイメージを起動すると、一意の machine ID が生成されます。

自分で作成したスナップショットの場合は事情が異なります。スナップショットを作成した時点でファイルにはすでに値が設定されていました。そのため、そこから復元したすべてのサーバーが同じ値を持ち、復元処理の中でその値を消去する処理もありません。これは 稼働中のサーバーを新しい VPS に移行する 場合と同じ種類の問題です。コピー自体は正確ですが、コピーしたくなかった identity まで複製されています。

クローンが複製するその他の設定

  • SSH ホスト鍵。 /etc/ssh/ssh_host_* もコピーされるため、両方のサーバーがクライアントに同じフィンガープリントを提示します。これらのファイルを削除して sudo ssh-keygen -A を実行するか、Debian と Ubuntu では sudo dpkg-reconfigure openssh-server を実行します。その後、クライアントはホスト鍵の変更を警告します。これは正しい動作です。
  • ホスト名。 sudo hostnamectl set-hostname app02 で設定し、/etc/hosts が新しい名前を引き続き解決できることを確認します。
  • 静的ネットワーク設定。 静的アドレスを持つサーバーのクローンは、起動した瞬間に元のサーバーとアドレスが競合します。クローンがネットワークに接続する前に /etc/netplan/ を確認します。
  • 時刻。 復元したスナップショットは、取得時点の時刻で再開します。復元した VPS での大きな時刻のずれにより TLS 証明書の検証が失敗し、時刻同期が追いつくまでログの順序も乱れます。

クローンでも 新しい VPS の最初の 10 分間チェックリストを実施します。クローンしたサーバーには、元のサーバーのユーザーアカウント、SSH 鍵、ファイアウォールルール、スケジュール済みジョブが引き継がれます。これらは、クローンがこれから実行する用途に合わせて確認されたものではありません。

FAQ

/etc/machine-id を変更した後、再起動する必要はありますか?

はい。プロセスは machine ID を一度だけ読み込んでキャッシュするため、新しい値はすでに実行中のプロセスには反映されません。journald は古い ID に基づく名前の journal ディレクトリへ書き込み続けます。また、DHCP クライアントも古い値から生成したクライアント識別子を送信し続けます。通常、machine ID を変更する理由はこの動作にあります。個々のサービスを再起動すれば一部は修正できますが、PID 1 も古い値を保持しています。再起動後、cat /etc/machine-id で確認し、もう一方のサーバーと比較してください。

/etc/machine-id はハードウェア UUID と同じですか?

いいえ。/sys/class/dmi/id/product_uuid に表示される DMI product UUID は hypervisor から提供され、root だけが読み取れます。machine ID はオペレーティングシステムが生成し、一般ユーザーでも読み取れる通常のファイルに保存されます。両者には一方向の関係があります。KVM guest では、コピー元の D-Bus ID がない場合、systemd-machine-id-setup が hypervisor UUID を基に新しい machine ID を生成します。2 つの clone が同じ product UUID を共有していると、同じ machine ID が再生成されます。そのため、結果を信頼する前にこのファイルも比較してください。

/etc/machine-id は削除すべきですか、それとも空にしておくべきですか?

image を準備する場合は、空にしておいてください。machine-id(5) は空のファイルを優先します。image を読み取り専用の /etc で実行する場合、systemd はそのファイル上に一時ファイルを bind mount できるためです。書き込み可能なシステムではファイルを削除しても動作し、一部の clone script もその方法を使います。ただし、空のファイルを使用するほうが安全なデフォルトです。cloud-init は同じ目的でファイルに uninitialized という語を書き込みます。

2 台の clone したサーバーに同じ DHCP アドレスが割り当てられたのはなぜですか?

両方が同じクライアント識別子を送信したためです。systemd-networkd は DHCPv4 でデフォルトとして ClientIdentifier=duid を使用します。また、デフォルトの DUID は /etc/machine-id のハッシュから生成されるため、同じ machine ID を持ち、同じ interface name を保持している clone では識別子も同じになります。DHCP サーバーはこの識別子で照合し、両方の要求を 1 つのクライアントとして扱って 1 つの lease を割り当てます。それぞれのサーバーに固有の machine ID を設定し、両方を再起動してください。サーバーが古いアドレスを引き続き提示する場合は、DHCP サーバー自体で古い lease を削除してください。

#machine-id#systemd#cloning#snapshots#dhcp