コンテンツにスキップ

Linux + KVM で仮想化ホストを構築してみた(bridge / cloud-init編)

KVM/libvirt の基本的な仮想化ホストを構築した後、Kubernetes ノードとして利用するための仮想マシン環境を整備します。本記事では、Debian のネットワーク管理を ifupdown から systemd-networkd に移行し、Linux ブリッジによる VM ネットワークを構成します。さらに cloud-init とクラウドイメージを利用して、仮想マシンを高速にデプロイする方法を紹介します。


基本編では、KVM/libvirtで仮想化ホストを構築しました。今回は、その仮想化ホストの上に、複数の仮想マシンを作成しKubernetesのノードとして設定していきます。

ただし、Kubernetesを実際にインストールする前に、いくつかの準備及び最適化を実施します。

ホスト:ifupdownsystemd-networkdに置き換える

初めて Debian を使う人は、最新版の Debian 13(2025年8月リリース)の標準 CLI インストールで、ネットワーク管理ツールとして ifupdown がいまだに使われていることに驚くかもしれません。

ifupdown の起源は 1990 年代後半にさかのぼります。/etc/network/interfaces に設定を書き、ifup / ifdown でインターフェースを制御するという非常にシンプルな仕組みで、長年 Linux の基本的なネットワーク設定方法として使われてきました。

Debian は、派手な流行に飛びつくディストリビューションではありません。Debian Project の Debian Social Contract にも表れているように、安定性と透明性を重視する文化があります。そのため、ifupdown は「古いから置き換える」という理由だけでは廃止されていません。

しかし クラウドネイティブの文脈では事情が少し変わります。例えば Kubernetes のノードでは、コンテナの起動・停止に伴い多数の仮想インターフェースやルーティングが動的に作成されます。このような環境では、イベント駆動でネットワークを管理する systemd-networkd の方が相性が良いと言われています。

今回は、仮想化ホスト上に仮想マシンを作成し、それらを Kubernetes のノードとして利用する構成を想定します。仮想化ホスト自体は ifupdown のままでも問題ありませんが、ゲスト OS との構成を統一するため、本記事では systemd-networkd への移行を行います。

現行ifupdownの設定の確認

まずはcat /etc/network/interfacesで現在の設定を確認します。今回は、DHCPサーバーが存在するものの、サーバーであるため固定IPにしています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
allow-hotplug enp0s25
iface enp0s25 inet static
  address 192.168.0.121
  netmask 255.255.255.0
  gateway 192.168.0.1
# This is an autoconfigured IPv6 interface
iface enp0s25 inet6 auto

systemd-networkd用設定ファイルの作成

systemd-networkdは、/etc/systemd/networkディレクトリを使用して構成を管理します。このディレクトリに、3種類のファイルを含むことができます。

ファイルの種類 レイヤー 用途
.linkファイル L1 - L2下層 物理NICの属性、MACアドレス、MTUなど
.netdevファイル L2上層 L2仮想デバイス(bridge / vlan / bondなど)
.networkファイル L3 IPアドレス、DHCLクライアントとしての動きなど

このように、TCP/IPの階層を意識した役割分担になっています。

今回は、MACアドレスの指定もbondなどの利用もないため、.networkファイルのみ作成していきます。

1
sudo vi /etc/systemd/network/10-enp0s25.network

他のsystemdの設定と同様に、ファイル名に数字をつけることで優先順位を調整できます。中身はこんな感じです。

1
2
3
4
5
6
7
[Match]
Name=enp0s25

[Network]
Address=192.168.0.121/24
Gateway=192.168.0.1
IPv6AcceptRA=yes

切り替えの実施

設定に問題がないことを確認したら、一旦ifupdownの設定を退避させます。ifupdownは、設定ファイルが存在しない場合サービスが動いているものの、何も設定変更しようとしないため、制御権が自然とsystemd-networkdに移ります。

1
$ sudo mv /etc/network/interfaces /etc/network/interfaces.old

systemd-networkdを有効化し、再起動します。

1
2
$ sudo systemctl enable systemd-networkd
$ sudo reboot

systemd-resolvedについて

ここで、「systemd-networkdを有効化したのに、一緒にsystemd-resolvedも有効化しないのはおかしくない?」というご指摘があるかもしれません。

確かに私自身も、その中途半端な感じを否めませんが、ここでは一旦systemd-resolvedなしで進めます。

再起動後、動作確認します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ networkctl
IDX LINK            TYPE     OPERATIONAL SETUP
  1 lo              loopback carrier     unmanaged
  2 enp0s25         ether    routable    configured


$ networkctl status enp0s25
● 2: enp0s25
                   Link File: /usr/lib/systemd/network/99-default.link
                Network File: /etc/systemd/network/10-enp0s25.network
                       State: routable (configured)
                Online state: online
...


$ sudo journalctl -u systemd-networkd
Mar 14 01:31:09 debox systemd[1]: Starting systemd-networkd.service - Network Configuration...
Mar 14 01:31:09 debox systemd-networkd[417]: lo: Link UP
Mar 14 01:31:09 debox systemd-networkd[417]: lo: Gained carrier
Mar 14 01:31:09 debox systemd[1]: Started systemd-networkd.service - Network Configuration.
Mar 14 01:31:09 debox systemd-networkd[417]: enp0s25: Configuring with /etc/systemd/network/10-enp0s25.network.
Mar 14 01:31:09 debox systemd-networkd[417]: enp0s25: Link UP
Mar 14 01:31:12 debox systemd-networkd[417]: enp0s25: Gained carrier
Mar 14 01:31:14 debox systemd-networkd[417]: enp0s25: Gained IPv6LL

systemd-networkdが正常に動作していることを確認してから、ifupdownのサービスを停止します。

1
$ sudo systemctl stop networking.service

再度、ネットワークに問題ないことを確認してから、ifupdownパッケージを完全に削除します。

1
2
$ sudo apt purge ifupdown
$ sudo rm -rf /etc/network

この手順を踏まえることで、リモート(SSHなど)からでも切り替えが可能です。

ブリッジネットワークの構成

基本編では、デフォルトのNATネットワーク上で仮想マシンを構築しました。

これでもKubernetesのテスト環境として問題ありませんが、もし複数の仮想化ホストが存在し、その上にある仮想マシンを、物理ホストを跨いでKubernetesクラスタを構成する場合、フラットな通信が必要なため、NAT環境では正常に動作しません。

そのため、ブリッジネットワークを構成し、仮想マシンをブリッジネットワークに移動します。

Linuxブリッジの作成

ここでは、systemd-networkdの場合のブリッジの作成方法を記載します。ifupdownの場合は手順が異なり、レガシーパッケージbridge-utilsのインストールも必要です。

前のステップで/etc/systemd/network/10-enp0s25.networkを作成しましたが、以下のように変更および新規作成します。

1
2
3
4
5
6
$ cat /etc/systemd/network/10-enp0s25.network
[Match]
Name=enp0s25

[Network]
Bridge=br0
1
2
3
4
$ cat /etc/systemd/network/20-br0.netdev
[NetDev]
Name=br0
Kind=bridge
1
2
3
4
5
6
7
$ cat /etc/systemd/network/20-br0.network
[Match]
Name=br0

[Network]
Address=192.168.0.121/24
Gateway=192.168.0.1

ポイントは

  • enp0s25のL3設定では、IPアドレスの設定を削除し、代わりにbr0というブリッジに参加する旨のBridge=br0を記述
  • L2上層部機能を定義する20-br0.netdevで、ブリッジを定義
  • br0というインターフェースに対して、L3のIP設定を付与

設定内容を確認後、systemd-networkdを再起動します。通常、ネットワーク断は発生しませんが、設定ミス等があると切れることがあるので、リモートではなく目の前で作業したほうが良いかもしれません。

1
sudo systemctl restart systemd-networkd

動作確認

networkctlコマンドで、br0とenp0s25の詳細状況を確認します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ networkctl status br0
● 7: br0
                 NetDev File: /etc/systemd/network/20-br0.netdev
                   Link File: /usr/lib/systemd/network/99-default.link
                Network File: /etc/systemd/network/20-br0.network
                       State: routable (configured)
                Online state: online
                        Type: bridge
                        Kind: bridge
                      Driver: bridge
                     Address: 192.168.0.121
                     Gateway: 192.168.0.1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ networkctl status enp0s25
● 2: enp0s25
                   Link File: /usr/lib/systemd/network/99-default.link
                Network File: /etc/systemd/network/10-enp0s25.network
                       State: enslaved (configured)
                Online state: online
                        Type: ether
                      Driver: e1000e
                      Vendor: Intel Corporation
                      Master: br0

仮想マシンのbr0への移動

停止状態のvm1のネットワークI/Fを確認します。

1
2
3
4
$ virsh domiflist vm1
 Interface   Type      Source    Model    MAC
-------------------------------------------------------------
 -           network   default   virtio   52:54:00:10:8e:77

変更します。

1
2
$ virt-xml vm1 --edit --network bridge=br0
Domain 'vm1' defined successfully.

変更後の確認をします。

1
2
3
4
$ virsh domiflist vm1
 Interface   Type     Source   Model    MAC
-----------------------------------------------------------
 -           bridge   br0      virtio   52:54:00:10:8e:77

IPアドレスの確認

ただし、ここの問題は、ゲストOSのIPアドレスがわからないのです。例えば今までdefaultのNATに存在するVMに対して、IPアドレスを確認すると

1
2
3
4
$ virsh domifaddr vm2
 Name       MAC address          Protocol     Address
-------------------------------------------------------------------------------
 vnet9      52:54:00:ae:09:d5    ipv4         192.168.122.20/24

このように、IPアドレスが表示されますが、br0に切り替えたVMに対して同じコマンドを実行すると

1
2
3
$ virsh domifaddr vm1
 Name       MAC address          Protocol     Address
-------------------------------------------------------------------------------

このように、何も表示されません。ただし、実際にシリアルコンソールで接続して、IPアドレスを確認すると、問題なくIPアドレスがDHCPよりアサインされていることがわかります。

1
2
3
$ ip -brief add
lo               UNKNOWN        127.0.0.1/8 ::1/128
enp1s0           UP             192.168.0.5/24 metric 100 240d:f:dd5:9c00:5054:ff:fe10:8e77/64 fe80::5054:ff:fe10:8e77/64

では、IPアドレスを持っているのに、なぜホストがそれを検出できないのか?次のセクションでこれを解決していきます。

IPアドレスとQEUMエージェント

domiflistの情報源

virsh domifaddr vm1を実行すると、IPアドレスが表示される仕組みについて、virshは2つの情報源からゲストOSのIPアドレスを取得できます。

  • 1つは、libvirt内蔵のDHCPサーバーのリース情報からです。例えばdefaultのNATネットワークの場合、DHCPサーバーが192.168.122.0/24のネットワークからIPアドレスをリースしているため、当然ながらどのVMがどのIPアドレスを持っているかを知っています。
  • もう1つは、ゲストOSにインストールされているQEUMエージェントです。これはVMwareでいうvmtoolsに該当するパッケージです。ブリッジネットワークなど、libvirt管理外のネットワークに接続しているゲストOSのIPアドレスを取得するには、このエージェントが必要です。

また、デフォルトではリースから情報を取ってきていますので、エージェントを情報源として使用するには、明示的に指定する必要があります。

今回は、VMをbr0に切り替えたため、当然ながらリース情報からIPアドレスを取得できません。また、QEUMエージェントも未インストールのため、情報を取得できないはずです。試してみましょう。

1
2
3
4
5
6
7
$ virsh domifaddr vm1 --source lease
 Name       MAC address          Protocol     Address
-------------------------------------------------------------------------------

$ virsh domifaddr vm1 --source agent
error: Failed to query for interfaces addresses
error: Guest agent is not responding: QEMU guest agent is not connected

QEMUエージェントのインストール

そこでゲストOSに接続し、QEMUエージェントをインストールします。

1
sudo apt install qemu-guest-agent

インストール後、再度ホスト側で確認します。

1
2
3
4
5
6
7
8
$ virsh domifaddr vm2 --source agent
 Name       MAC address          Protocol     Address
-------------------------------------------------------------------------------
 lo         00:00:00:00:00:00    ipv4         127.0.0.1/8
 -          -                    ipv6         ::1/128
 enp1s0     52:54:00:10:8e:77    ipv4         192.168.0.5/24
 -          -                    ipv6         240d:f:dd5:9c00:5054:ff:fe10:8e77/64
 -          -                    ipv6         fe80::5054:ff:fe10:8e77/64

このように、正常に表示されるようになります。

クラウドイメージの利用とcloud-init

これまでは、ISOイメージから仮想マシンのゲストOSをインストールし、ブリッジネットワークへの公開、QEMUエージェントのインストールまで実施してきました。

しかし、複数ノードのKubernetes環境を構築するには、たくさんの仮想マシンが必要で、1台ずつISOからインストールして、設定していくのは非常に煩雑です。

これを解決するには、2000年代頃ですと、「テンプレートとなる仮想マシンを作成しておいて、あとはテンプレートからクローンするだけで良い」という発想が王道ではないかと思います。

もちろん、KVM/libvirtはクローンや、VMwareでいう「Linked Clone」に対応しています(Linked Cloneに相当する概念はoverlay qcow2です)。しかし、このやり方は結構レガシーで、クローンした後にそれぞれの仮想マシンにログインして、個別設定(IPアドレスやホスト名などなど)を行う必要があります。

よりモダンな方法は、cloud-initです。cloud-initはVMの初回起動時に、自動で初期設定を行う仕組みで、2000年代の言葉で説明すると「WindowsのRunOnceやSysprep」、「Linuxの古典的な/etc/rc.local」に近いものです。

cloud-initを利用するにはcloud-initパッケージをインストールしておく必要がありますが、そこで各ベンダーがリリースしている「クラウドイメージ」があって、実体はOSインストール済み・cloud-initパッケージインストール済みのディスクイメージです。

クラウドイメージを使用することで、OSインストール不要、cloud-initによる迅速なデプロイが実現できます。

準備作業

cloud-initは主にAWSやGoogle Cloudなどのクラウド環境で使われる仕組みなので、今回のようなクラウド以外の環境(NoCloud)で使用するには、パッケージのインストールが必要です。

1
$ sudo apt install cloud-image-utils

このパッケージは、cloud-localdsgrowpartなどのクラウドイメージ関連ツールを提供しています。

次に、クラウドイメージをダウンロードします。今回は、ゲストOSとしてUbuntuを利用する予定なので、Ubuntuのクラウドイメージをダウンロードします。

1
$ curl -O https://cloud-images.ubuntu.com/questing/current/questing-server-cloudimg-amd64.img

クラウドイメージ

Ubuntu 25.10 (Questing Quokka)のdailyビルドを使用します。2026年3月14日現在の最新は、[20260310]のものです。

最後に、ゲストOSにログインするためのキーペアを作成します。パスワードを使用したログインも可能ですが、セキュリティの観点でキーペアを使用します。

1
2
3
4
$ ssh-keygen -t ed25519
...
$ cat .ssh/id_ed25519.pub
ssh-ed25519 AAA...(実際のパブリックキー)...nCO nogawa@host-pc

パブリックキーをメモしておきます。

パブリックキーについて

パブリックキーのフォーマットは、<type> <base64-key> [comment]です。

typekeyそのものは必須ですが、コメントはオプションです。

seedの作成

seedとは、cloud-init実行時の設定情報を保存する仮想ディスクです(.isoや.imgなど)。このseedは、主に3つの部分から構成されています。

構成要素 必須 内容
user-data 必須 cloud-init のメイン設定。ユーザー作成、SSH鍵、パッケージインストール、コマンド実行、hostname など VM 初期化の設定を書く。
meta-data オプション インスタンス識別情報を与える。VM を clone する場合に instance-id を変えるためにも使う。
network-config オプション ネットワーク設定を渡す。DHCP / static IP / gateway / DNS などを cloud-init 経由で OS のネットワーク設定(netplan / networkd など)に反映させる。

今回は、以下の内容でこの3つのファイルを作成します。どれもYAML形式ですが、user-datameta-datacloud-initの書式、network-configは対象サーバーがUbuntuなのでNetplan形式で記述します。

  • user-data
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#cloud-config
users:
  - name: nogawa
    sudo: ALL=(ALL) NOPASSWD:ALL
    groups: users, admin
    shell: /bin/bash
    lock_passwd: true
    ssh_authorized_keys:
      - ssh-ed25519 AAA...(実際のパブリックキー)...nCO
packages:
  - qemu-guest-agent
runcmd:
  - systemctl enable --now qemu-guest-agent

user-dataについて

このファイルの書き方は、公式サイトをご参照ください。

https://docs.cloud-init.io/en/latest/reference/modules.html

  • meta-data
1
2
instance-id: ubuntu1
local-hostname: ubuntu1
  • network-config
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
network:
  version: 2
  ethernets:
    server-main-nic:
      match:
        driver: virtio_net
      addresses:
        - 192.168.0.131/24
      routes:
        - to: default
          via: 192.168.0.1

network-configについて

Netplanの設定ファイルでは、通常だと「enp1s0」のようなインタフェース名が必要ですが、match: driver: virtio_netによって、インターフェース名が分からなくても仮想NICがマッチされ、設定されます。

仮想NICが1つしかない場合、正しく動作することが保証されます。

上記の3つのテキストファイルを使用して、seedを作成します。

1
$ cloud-localds seed.iso user-data meta-data -N network-config

仮想マシンの作成

クラウドイメージとseedをコピーします。

1
2
$ sudo cp questing-server-cloudimg-amd64.img /var/lib/libvirt/images/ubuntu.img
$ sudo cp seed.iso /var/lib/libvirt/images/seed.iso

仮想マシンを作成します。既存ディスクイメージをそのまま使用するので、必ず--importオプションをつけます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
virt-install \
    --name ubuntu-1 \
    --memory 8192 \
    --vcpus 2 \
    --import \
    --disk path=/var/lib/libvirt/images/ubuntu.img,format=qcow2,bus=virtio \
    --disk path=/var/lib/libvirt/images/seed.iso,device=cdrom \
    --network bridge=br0,model=virtio \
    --os-variant ubuntu25.10 \
    --graphics none \
    --noautoconsole

作成後、起動されます。しばらくすると、IPアドレスが確認できるようになります。

1
2
3
4
5
6
7
8
$ virsh domifaddr ubuntu-1 --source agent
 Name       MAC address          Protocol     Address
-------------------------------------------------------------------------------
 lo         00:00:00:00:00:00    ipv4         127.0.0.1/8
 -          -                    ipv6         ::1/128
 enp1s0     52:54:00:09:bd:03    ipv4         192.168.0.131/24
 -          -                    ipv6         240d:f:dd5:9c00:5054:ff:fe09:bd03/64
 -          -                    ipv6         fe80::5054:ff:fe09:bd03/64

これで、問題なくSSHでログインできるようになります。