Ubuntu Weekly Recipe

第738回リモートのLXDインスタンスの画面をローカルから操作してVDI環境を構築する

LXDが仮想マシンに対応したことで、気軽にデスクトップ環境やWindowsマシンを構築できるようになりました。しかしながらその操作にあたってはvirt-viewerをはじめとするSPICEクライアントが必要になります。そこで今回はリモートマシンで動いているLXDのデスクトップインスタンスを、ローカルからSPICEプロトコルを利用して操作する方法を紹介しましょう。これができればLXDだけで、簡易VDI(Virtual Desktop Infrastructure)を構築できます。

なぜRDPではだめなのか

本連載ではこれまでにも、LXDで仮想マシンを構築し、その上にデスクトップ版のUbuntuやWindowsをインストールする方法を紹介してきました。

これらはいずれも、⁠ローカルのLXDに仮想マシンインスタンスを構築し、ローカルのvirt-viewerからアクセス」する方法をとっています。しかしながらvirt-viewerが使うSPICEプロトコルは本来ネットワーク経由でも利用できるものです。

つまり「リモートのLXDに仮想マシンインスタンスを構築し、ローカルのvirt-viewerからアクセス」できるようになれば、ローカルマシンのスペックはそこまで高くなくても済むようになります。これは端的に言うと仮想マシンのSPICE接続用のアドレスがわかれば可能です。さらにLXDを使うことで、SPICE接続の部分をローカルかリモートかに区別なく同じ方法で、なおかつ安全に利用できるようになるのです。

ちなみにリモートデスクトップ 」やVDI(Virtual Desktop Infrastructure) 」と言うと、 RDP(Remote Desktop Protocol)VNC(Virtual Network Computing) などが定番ですが、これらはデスクトップ側になんらかの「サーバー」を用意しておく必要があります。つまりインストール済みの環境であれば十分役に立つのですが、⁠インストール中」の画面の共有には向いていないのです。

それに対してSPICEはハイパーバイザー側で画面の描画情報を取得します。つまりVMの上で動いているソフトウェアがなんであれ、その情報を取得し他の環境と共有できます。つまりディスプレイが必要な何かシステムをインストールし、その後RDP等をセットアップするまでは、SPICEクライアントによる共有ができると便利なのです。

リモートのLXDを操作する方法

LXDはデーモンとして動作していますがREST APIも提供しています。LXDを操作するlxcコマンドはUnixドメインソケット経由でREST APIを呼び出すことで、インスタンスの作成や操作を行っているのです。

LXDのREST APIはローカルシステムからしか呼び出せないようになっています。しかしながらサーバー側で設定を行うことで、特定のクライアントに対してネットワーク越しにREST APIを呼び出す仕組みも用意しています。この際、通信データのフォーマットとしてJSONを使用し、通信経路を暗号化するためにHTTPS/TLSを使用しています

ちなみにREST APIを呼び出すクライアント機能だけであれば、WindowsやmacOS上でも動作可能です。HomebrewやChocolateryでmacOSやWindowsにインストールできるLXDは、このクライアント機能に特化したLXDと言えます。

まずはローカルのLXDシステムを、リモートのLXDシステムと連携させる方法を紹介しましょう。

  • 方法1:リモート側でパスワードを登録し、ローカルからパスワードを入力して登録する方法
  • 方法2:リモート側でワンタイムトークンを発行し、ローカルからトークンを入力して登録する方法
  • 方法3:リモート側にローカルのクライアント証明書を登録する方法

本質的に方法1と方法2は方法3を簡単にするための方法です。他にも証明書を生成するためのPKIを独自のものにしたり、Candidを利用して認証したりもできるようですが、今回は除外します。

方法1については第574回のLXD 3.0のスナップショットとマイグレーションで紹介しました。リモート側でパスワードを設定し、それをもってライアント側を認証・登録するという方法です。これはお手軽ではあるのですが、パスワードを知っている人なら誰でもリモートのLXDを操作できてしまいます。そのため、登録の時だけパスワードを有効化するという方法になってしまうのですが、それなら方法2と手間は大差ありません。方法2であれば、クライアント側でリモートの名前を管理しやすくなるため、今なら方法2をおすすめします。

まずリモート側の設定です。LXDの初期設定だとリモート接続が不許可になっているため、それを許容する必要があります。ポートを指定可能です。未指定の場合は8443番ポートを利用します。

remote$ lxc config set core.https_address "[::]"
remote$ lxc info | grep certificate_fingerprint
  certificate_fingerprint: 7ba2d3fab2f05419323d6f7827e020437b74bfc12c5c477ec7ac3bb7c27a8f96

core.https_addressにはリモートホストでbindするIPアドレスを設定します。上記のように[::]と設定しておけば、リモートホスト上のすべてのアドレスにbindします。また末尾に:443と加えることで特定のポートを指定可能です。未指定の場合は8443番ポートを利用します。最後に表示しているのはリモートホストの証明書のフィンガープリントです。クライアント側から接続する際に正しいリモートに接続しようとしているかを確認する際に使用します。

さらにリモート側でlxc config trust addを実行し、特定の名前でトークンを発行しておきます。名前は任意ですが、クライアント側のホスト名にしておくとわかりやすいでしょう。

remote$ lxc config trust add
Please provide client name: nuc
Client nuc certificate add token:
トークン文字列

「Please provide client name」でクライアント側を識別する名前を入力します。そうするとトークン文字列が表示されるので、これをクライアント側で使います。ちなみに作成したもののまだ使われていないトークンはlxc config trust list-tokensで確認できます。

次にクライアント側の対応です。先ほど生成したトークンを使い、リモートに接続します。

client$ lxc remote add meet トークン文字列
Generating a client certificate. This may take a minute...

client$ lxc remote list -f compact
       NAME                          URL                       PROTOCOL      AUTH TYPE   PUBLIC  STATIC  GLOBAL
  images           https://images.linuxcontainers.org        simplestreams  none         YES     NO      NO
  local (current)  unix://                                   lxd            file access  NO      YES     NO
  meet             https://10.42.0.239:8443                  lxd            tls          NO      NO      NO
  ubuntu           https://cloud-images.ubuntu.com/releases  simplestreams  none         YES     YES     NO
  ubuntu-daily     https://cloud-images.ubuntu.com/daily     simplestreams  none         YES     YES     NO

これでリモートとの接続完了です。ちなみにリモート側でも接続許可済みのクライアントを一覧表示できます。lxc config trust list-tokensすると先ほどのトークンが消えていることもわかるはずです。

remote$ lxc config trust list -f compact
   TYPE   NAME  COMMON NAME  FINGERPRINT           ISSUE DATE                   EXPIRY DATE
  client  nuc   shibata@nuc  4978f1e4cd11  Nov 5, 2022 at 9:16am (UTC)  Nov 2, 2032 at 9:16am (UTC)

リモート側ではlxc config trust removeで、一度許可したクライアントを無効化することも可能です。

これまでのLXDコマンドに「リモート名:」を付けることで、リモート側のLXDを操作できます。

client$ lxc ls meet:
+-------------------------------+---------+------+------+-----------+-----------+
|             NAME              |  STATE  | IPV4 | IPV6 |   TYPE    | SNAPSHOTS |
+-------------------------------+---------+------+------+-----------+-----------+
| dotnet                        | STOPPED |      |      | CONTAINER | 0         |
+-------------------------------+---------+------+------+-----------+-----------+
| review                        | STOPPED |      |      | CONTAINER | 0         |
+-------------------------------+---------+------+------+-----------+-----------+
| snapcraft-thorium-reader-snap | STOPPED |      |      | CONTAINER | 0         |
+-------------------------------+---------+------+------+-----------+-----------+

ちなみに「リモート名:」を指定しなかった場合は「ローカルのLXD」が使われます。この未指定時の接続先はlxc remote switchコマンドで切り替えられます。

client$ lxc remote switch meet
client$ lxc remote list -f compact
       NAME                         URL                       PROTOCOL      AUTH TYPE   PUBLIC  STATIC  GLOBAL
  images          https://images.linuxcontainers.org        simplestreams  none         YES     NO      NO
  local           unix://                                   lxd            file access  NO      YES     NO
  meet (current)  https://10.42.0.239:8443                  lxd            tls          NO      NO      NO
  ubuntu          https://cloud-images.ubuntu.com/releases  simplestreams  none         YES     YES     NO
  ubuntu-daily    https://cloud-images.ubuntu.com/daily     simplestreams  none         YES     YES     NO

「current」「local」から「meet」に切り替わりました。

リモートのデスクトップVMの作成

ここからはこれまでとデスクトップVMの作成方法は同じです。あらかじめローカル側にSPICEクライアントであるvirt-viewerをインストールして、LXDを再起動しておいてください。これは一度だけ実施すれば大丈夫です。

client$ sudo apt install virt-viewer
client$ sudo systemctl reload snap.lxd.daemon.service

次に空のVMインスタンスを作成し、各種リソースを設定して、インストール用のISOファイルを紐付けます。

client$ lxc init kinetic --empty --vm -c limits.cpu=2 -c limits.memory=8GiB
client$ lxc config device override kinetic root size=50GiB
client$ lxc config device add kinetic iso disk boot.priority=10 source=$HOME/ダウンロード/ubuntu-22.10-desktop-amd64.iso

VMはコンソールのタイプにVGAを指定すると、virt-viewerなどのSPICEクライアントが起動します。

起動と同時にSPICEクライアントを立ち上げたい場合
client$ lxc start kinetic --console=vga

起動済みのVMインスタンスのコンソールを取得したい場合
client$ lxc console kinetic --type=vga

これだけです。lxc remote switch meetでアクセス先を切り替えていますが、lxc remote switch localで標準の接続先はローカルに設定したまま、meet:kineticのように明示的にリモート接続先を指定する方法もあります。

図1 リモートでVMインスタンスが動いているので、ローカルのリソースはほぼ使わない
図1

これでディスプレイが繋がっていないマシンでも、気軽にデスクトップインスタンスを作れるようになりました。この先は、上記のようにvirt-viewerとSPICEを用いて操作しても良いですし、VMインスタンス側でRDP等を導入して、RDPクライアントを使う方法もあります。USBやオーディオなどのリソースの共有や、ファイル共有を考えるとRDPのほうがお手軽です。

LXDのプロジェクト機能を利用したクライアントの制限方法

LXDへのリモート接続を許可したクライアントは、接続先のLXDの機能をすべて利用可能です。この場合、特権コンテナを作れたり、各種物理デバイスの操作も可能になってしまい、セキュリティ的な不安要素となります。そもそも信頼できないクライアントを登録しないことが前提ではあるものの、信頼できるクライアントでもある程度の機能制限を行いたいことはあるでしょう。

一番手っ取り早い方法がそのクライアントを制限付きにしてしまうことです。具体的な手順を見ていきましょう。まず制限をかけたいクライアントのフィンガープリント(FINGERPRINT)を確認しておきます。

remote$ lxc config trust list
+--------+------+-------------+--------------+-----------------------------+-----------------------------+
|  TYPE  | NAME | COMMON NAME | FINGERPRINT  |         ISSUE DATE          |         EXPIRY DATE         |
+--------+------+-------------+--------------+-----------------------------+-----------------------------+
| client | nuc  | shibata@nuc | 4978f1e4cd11 | Nov 5, 2022 at 9:16am (UTC) | Nov 2, 2032 at 9:16am (UTC) |
+--------+------+-------------+--------------+-----------------------------+-----------------------------+

次にそのクライアント(上記だと「4978f1e4cd11⁠⁠)に対して設定ファイルを表示します。

remote$ lxc config trust show 4978f1e4cd11
name: nuc
type: client
restricted: false
projects: []
certificate: |
  -----BEGIN CERTIFICATE-----
  MIIB2TCCAV6gAwIBAgIQA0/jNgAolLEPoNhiJg7gTzAKBggqhkjOPQQDAzA0MRww
  GgYDVQQKExNsaW51eGNvbnRhaW5lcnMub3JnMRQwEgYDVQQDDAtzaGliYXRhQG51
  YzAeFw0yMjExMDUwOTE2MDNaFw0zMjExMDIwOTE2MDNaMDQxHDAaBgNVBAoTE2xp
  bnV4Y29udGFpbmVycy5vcmcxFDASBgNVBAMMC3NoaWJhdGFAbnVjMHYwEAYHKoZI
  zj0CAQYFK4EEACIDYgAEPBhMYqdJh8VvB/MrwLsJPRQAhASZcC7aMd0AwDUuEg1n
  wMzHWH67Q2qzCg5SHR/e8lTqg1PmH1NGSnsCdZPmCcU2zZCSqjCwRjKxETVoCTVV
  glq8coLiD+OeDvzN90UXozUwMzAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYI
  KwYBBQUHAwIwDAYDVR0TAQH/BAIwADAKBggqhkjOPQQDAwNpADBmAjEA5KLJ+12L
  HwK+d/HkCTwtZ+G/G94cT4fpwsiSi7el+ZcnO5tRS8uYzoWb+DMsj6fnAjEA6LXL
  vnKrvC+c3XKEIyuNJBG2qSyVHwXVgC3B5F8pEamE7uoTnwLKAkSVWUfRj8zh
  -----END CERTIFICATE-----
fingerprint: 4978f1e4cd118598ed82f50ae4f496946d35352c83152fab4b0accb2e5fb0d01

ここのrestricted: falserestricted: trueにすると、セキュリティリスクの高い機能を制限できます。具体的に何を制限できるかはLXDドキュメントのプロジェクト設定にある、restricted.で始まる各設定を参照してください。

編集の方法はlxc config trust editコマンドを使います。LXDに共通する話ではあるのですが、この手の「編集系コマンド」を使うと環境変数EDITORの値を参照します。もしEDITORが未設定ならLXDパッケージ内蔵のnanoを利用します。よって、好みのテキストエディタを使わないと人生に重大な影響を及ぼす方はあらかじめ環境変数EDITORを設定しておくと良いでしょう。

remote$ export EDITOR=nvim

もしくは~/.profileあたりに最初から上記を書いておくという手もあります。さて実際に編集するには次のように実行します。

remote$ lxc config trust edit 4978f1e4cd11
  「restricted: true」にして、保存・終了する。

こうするとクライアントからいくつかの機能が制約されます。たとえばsecurity.privileged=trueを設定して、特権コンテナを作ってみましょう。

client$ lxc launch ubuntu:22.04 meet:docker -c security.privileged=true
Creating docker
Error: Failed instance creation: not authorized

このように許可されていない旨のメッセージが表示されました。これで最低限の制約はできます。

もしリモート側に作られたインスタンスをクライアントに見せたくない場合は、LXDの 「プロジェクト機能」 が使えます。LXDのプロジェクト機能は、LXDのインスタンスを複数のプロジェクトに分離し、プロジェクトごとのリソース制限などを行えるようになる機能です。今回のように複数のクライアントからアクセスするケースや、複数のユーザーがLXDサーバーを利用する場合に使えます。

まずはプロジェクトを作りましょう。これはリモートからでもローカルからでもかまいません。

$ lxc project create j2 -c restricted=true
Project j2 created

$ lxc project show j2
config:
  features.images: "true"
  features.profiles: "true"
  features.storage.buckets: "true"
  features.storage.volumes: "true"
  restricted: "true"
description: ""
name: j2
used_by:
- /1.0/profiles/default?project=j2

現在のプロジェクトは「default」なので、これをlxc project switchで切り替えてみます。

$ lxc project list -f compact
        NAME         IMAGES  PROFILES  STORAGE VOLUMES  STORAGE BUCKETS  NETWORKS      DESCRIPTION      USED BY
  default (current)  YES     YES       YES              YES              YES       Default LXD project  6
  j2                 YES     YES       YES              YES              NO                             1

$ lxc project switch j2
$ lxc list
+------+-------+------+------+------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+-------+------+------+------+-----------+

そうすると上記のように、インスタンスが何もない状態になるのです。リモート側はプロジェクトj2を参照することはないと思うのでlxc project switch defaultで元に戻しておきましょう。

次にクライアント側をこのプロジェクトj2にのみ所属するようにしてみましょう。

remote$ lxc config trust edit 4978f1e4cd11
(前略)
restricted: true
projects:
  - j2
certificate: |
(後略)

この状態でクライアントからリモートのインスタンスリストを参照しようとすると、⁠default」のほうを見ようとしてしまうために、権限がないと弾かれてしまいます。

client$ lxc list meet:
Error: not authorized

ここで--projectオプションを使ってプロジェクトj2の利用を明示するとアクセス可能になります。

client$ lxc project list meet:
+--------------+--------+----------+-----------------+----------+-------------+---------+
|     NAME     | IMAGES | PROFILES | STORAGE VOLUMES | NETWORKS | DESCRIPTION | USED BY |
+--------------+--------+----------+-----------------+----------+-------------+---------+
| j2 (current) | YES    | YES      | YES             | NO       |             | 1       |
+--------------+--------+----------+-----------------+----------+-------------+---------+

client$ lxc list meet: --project j2
+------+-------+------+------+------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+-------+------+------+------+-----------+

ただし毎回--projectを付けるのは大変なので、リモートの規定のプロジェクトをj2に変更してしまいましょう。

client$ lxc project switch meet:j2
client$ lxc list meet:
+------+-------+------+------+------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+-------+------+------+------+-----------+

これでこのクライアントは、リモートの他のインスタンスにはアクセスできなくなりました。

LXDではプロジェクト単位でのリソース制限も可能です。また、リモート接続する際のトークン発行時lxc config trust add実行時)に、--projectオプションなどで最初から所属するプロジェクトを明示する方法もあります。

これらの機能を組み合わせれば、リモートにある強いサーバーマシンを複数人で共有しながら、個別にコンテナやVM、さらにはデスクトップ環境を立ち上げて運用することも可能になるのです。

おすすめ記事

記事・ニュース一覧