nmstate 宣言的なネットワーク管理

この記事は Linux Advent Calendar 2020 の 12 日目です。RHEL 8.2 で Tech Preview として紹介されていた nmstate を軽く調査しました。

検証環境

CentOS 8 と nmstate-0.2.10 を利用する。

# cat /etc/redhat-release
CentOS Linux release 8.2.2004 (Core)

# rpm -q nmstate
nmstate-0.2.10-1.el8.noarch

nmstate とは

nmstate は、宣言的にネットワーク管理ができる Python ライブラリ。次のように構造化された形式(デフォルトは YAML。設定すれば json も利用できる。) でネットワーク設定を記述できる。

interfaces:
- name: eth1
  type: ethernet
  state: up
  ipv4:
    enabled: true
    address:
    - ip: 192.0.2.10
      prefix-length: 24
    dhcp: false

nmstate を利用した nmstatectl という CLI も提供しており、たとえば上記の YAML ファイルをもとに nmstatectl を実行すると、

# nmstatectl set sample.yaml
2020-12-12 09:33:56,439 root         DEBUG    Checkpoint /org/freedesktop/NetworkManager/Checkpoint/2 created for all devices: 60
2020-12-12 09:33:56,439 root         DEBUG    Adding new interfaces: []
2020-12-12 09:33:56,440 root         DEBUG    Editing interfaces: ['eth1', 'eth0']
2020-12-12 09:33:56,444 root         DEBUG    Executing NM action: func=add_connection_async
2020-12-12 09:33:56,451 root         DEBUG    Connection adding succeeded: dev=eth1
2020-12-12 09:33:56,451 root         DEBUG    Executing NM action: func=commit_changes_async
2020-12-12 09:33:56,455 root         DEBUG    Connection update succeeded: dev=eth0
2020-12-12 09:33:56,455 root         DEBUG    Executing NM action: func=_safe_modify_async
2020-12-12 09:33:56,461 root         DEBUG    Device reapply failed on eth1: error=nm-device-error-quark: Can't reapply changes to 'connection.autoconnect-slaves' setting (3)
Fallback to device activation
2020-12-12 09:33:56,464 root         DEBUG    Connection activation initiated: dev=eth1, con-state=<enum NM_ACTIVE_CONNECTION_STATE_ACTIVATING of type NM.ActiveConnectionState>
2020-12-12 09:33:56,497 root         DEBUG    Connection activation succeeded: dev=eth1, con-state=<enum NM_ACTIVE_CONNECTION_STATE_ACTIVATED of type NM.ActiveConnectionState>, dev-state=<enum NM_DEVICE_STATE_ACTIVATED of type NM.DeviceState>, state-flags=<flags NM_ACTIVATION_STATE_FLAG_LAYER2_READY | NM_ACTIVATION_STATE_FLAG_IP4_READY | NM_ACTIVATION_STATE_FLAG_IP6_READY of type NM.ActivationStateFlags>
2020-12-12 09:33:56,497 root         DEBUG    Executing NM action: func=_safe_modify_async
2020-12-12 09:33:56,505 root         DEBUG    Device reapply succeeded: dev=eth0
2020-12-12 09:33:56,506 root         DEBUG    NM action queue exhausted, quiting mainloop
2020-12-12 09:33:56,525 root         DEBUG    Checkpoint /org/freedesktop/NetworkManager/Checkpoint/2 destroyed
Desired state applied:
---
interfaces:
- name: eth1
  type: ethernet
  state: up
  ipv4:
    address:
    - ip: 192.168.11.100
      prefix-length: 24
    dhcp: false
    enabled: true

YAML ファイルで定義した通りにネットワーク設定が完了する。

# ip addr show eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:0b:06:2c brd ff:ff:ff:ff:ff:ff
    inet 192.168.11.100/24 brd 192.168.11.255 scope global noprefixroute eth1
       valid_lft forever preferred_lft forever

アトミックな操作が可能になっており、一部の設定でエラーを返すと全体がロールバックする。一部の項目でエラーを返す YAML ファイルで nmstatectl を実行すると、

interfaces:
- name: eth1
  type: ethernet
  state: up
  ipv4:
    enabled: true
    address:
    - ip: 192.168.11.100
      prefix-length: 24
    dhcp: false
# 存在しない eth を指定する
- name: eth3
  type: ethernet
  state: up
  ipv4:
    enabled: true
    address:
    - ip: 192.168.11.101
      prefix-length: 24
    dhcp: false

設定全体が失敗することがわかる。

# nmstatectl set sample.yaml
2020-12-12 13:12:59,448 root         DEBUG    Checkpoint /org/freedesktop/NetworkManager/Checkpoint/5 created for all devices: 60
2020-12-12 13:12:59,449 root         DEBUG    Adding new interfaces: ['eth3']
2020-12-12 13:12:59,451 root         DEBUG    Editing interfaces: ['eth1', 'eth0']
2020-12-12 13:12:59,453 root         DEBUG    Executing NM action: func=add_connection_async
2020-12-12 13:12:59,457 root         DEBUG    Connection adding succeeded: dev=eth3
2020-12-12 13:12:59,457 root         DEBUG    Executing NM action: func=commit_changes_async
2020-12-12 13:12:59,460 root         DEBUG    Connection update succeeded: dev=eth1
2020-12-12 13:12:59,460 root         DEBUG    Executing NM action: func=commit_changes_async
2020-12-12 13:12:59,464 root         DEBUG    Connection update succeeded: dev=eth0
2020-12-12 13:12:59,464 root         DEBUG    Executing NM action: func=safe_activate_async
2020-12-12 13:12:59,465 root         ERROR    NM main-loop aborted: Connection activation failed on connection_id eth3: error=nm-manager-error-quark: No suitable device found for this connection (device eth0 not available because profile is not compatible with device (mismatching interface name)). (3)
2020-12-12 13:12:59,467 root         DEBUG    Checkpoint /org/freedesktop/NetworkManager/Checkpoint/5 rollback executed: dbus.Dictionary({dbus.String('/org/freedesktop/NetworkManager/Devices/3'): dbus.UInt32(0), dbus.String('/org/freedesktop/NetworkManager/Devices/1'): dbus.UInt32(0), dbus.String('/org/freedesktop/NetworkManager/Devices/2'): dbus.UInt32(0), dbus.String('/org/freedesktop/NetworkManager/Devices/4'): dbus.UInt32(0)}, signature=dbus.Signature('su'))
Traceback (most recent call last):
  File "/usr/bin/nmstatectl", line 11, in <module>
    load_entry_point('nmstate==0.2.10', 'console_scripts', 'nmstatectl')()
  File "/usr/lib/python3.6/site-packages/nmstatectl/nmstatectl.py", line 59, in main
    return args.func(args)
  File "/usr/lib/python3.6/site-packages/nmstatectl/nmstatectl.py", line 217, in apply
    statedata, args.verify, args.commit, args.timeout
  File "/usr/lib/python3.6/site-packages/nmstatectl/nmstatectl.py", line 241, in apply_state
    rollback_timeout=timeout,
  File "/usr/lib/python3.6/site-packages/libnmstate/deprecation.py", line 40, in wrapper
    return func(*args, **kwargs)
  File "/usr/lib/python3.6/site-packages/libnmstate/nm/nmclient.py", line 96, in wrapped
    ret = func(*args, **kwargs)
  File "/usr/lib/python3.6/site-packages/libnmstate/netapplier.py", line 73, in apply
    state.State(desired_state), verify_change, commit, rollback_timeout
  File "/usr/lib/python3.6/site-packages/libnmstate/netapplier.py", line 163, in _apply_ifaces_state
    con_profiles=ifaces_add_configs + ifaces_edit_configs,
  File "/usr/lib64/python3.6/contextlib.py", line 88, in __exit__
    next(self.gen)
  File "/usr/lib/python3.6/site-packages/libnmstate/netapplier.py", line 232, in _setup_providers
    mainloop.run(timeout=MAINLOOP_TIMEOUT)
  File "/usr/lib/python3.6/site-packages/libnmstate/nm/nmclient.py", line 177, in run
    f"Unexpected failure of libnm when running the mainloop: {err}"
libnmstate.error.NmstateLibnmError: Unexpected failure of libnm when running the mainloop: run execution

# ip addr show eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:0b:06:2c brd ff:ff:ff:ff:ff:ff

nmstate の現状

nmstate は、RHEL 8.2 から Tech Preview な機能として導入されている。アップストリームの Contributors に Red Hat の人が何名かいたので、力を入れている感がある。

nmstate がテクノロジープレビューとして利用可能になりました。

Nmstate は、ホストのネットワーク API です。テクノロジープレビューとして利用できる nmstate パッケージでは、ライブラリーおよび nmstatectl コマンドラインユーティリティーを利用でき、ホストのネットワーク設定を宣言型で管理できます。ネットワークの状態は事前定義済みのスキーマで説明されています。現在の状態と、必要な状態への変更の報告は、両者ともこのスキーマに一致します。 引用元: RHEL 8.2 リリースノート

またアップストリームでも数日前に v1.0.0 がリリースされた(nmstate/nmstate Version 1.0.0 release)ので、ある程度は使える品質だと思われる。

nmstate がなぜ生まれたか?

詳細はnmstate designを見たほうがいいが、Linux ディストリビューションにはネットワークを管理するための共通的なインタフェースが存在しないため、この役割を目指すらしい。色々な仕組みがネットワーク管理を自作している(たとえば Cockpit や Ansible や OpenStack の os-net-config など)ので、これらの共通化を nmstate が実現するようなイメージじゃないかと思う。

nmstate の特徴

  • 宣言的な設定
  • トランザクションのサポート
  • ネットワークのモニタリングとイベント(API 見る感じまだ実装されてない気がする)

nmstate のアーキテクチャ

nmstate が各種ネットワーク管理サービスの仲介役になり(今の実装は NetworkManager だけだけど)、CLI ツールや Ansible や クラスタのネットワークマネージャがこれを利用する。


引用元: https://www.nmstate.io/devel/design/networking-api.html

Ansible は plugin の実装を提供しているっぽいansible-nmstateので、今後ほかの分野も内部の実装として nmstate を使うようになるかもしれない。

最後に

コンセプトに実装が追いついてない感じがあるのでまだまだ道半ば感ありますが、今後流行るかもしれないですね。ただしその場合も、表面的なインタフェースは変わらずに内部で nmstate が動いてた、というような、縁の下の力持ち的な感じになる気がします。

参考