2023年から室長として ACRi ルームの管理をしております、愛知工業大学の藤枝です。
ACRi ルームのウリの1つは、100枚の FPGA ボード (Arty A7-35T, CMod A7-35T, Nexys A7-100T) と、各ボードが接続された開発用の仮想マシンを、3時間ごとの時間貸しの形で利用できることにあります。ACRi ルーム構築のノウハウは、これまでにも組込み技術に関するサマーワークショップ SWEST25 での講演や、EdgeTech+ 2023 におけるミニ版の ACRi ルームの展示 (アヴネット株式会社の協力による) などを通じて、部分的に紹介する機会がありました。いつかはこのノウハウをまとめておきたい……と思っている間に1年ほど経ってしまいましたが、このたび ACRi ブログの連載という形にまとめることとしました。
このコースでは、2回の連載で、ACRi ルームにおける FPGA ボードの時間貸しがどのように実現されているか、またローカル環境でミニ版の ACRi ルームを作るにはどうすればよいかを、それぞれご紹介します。ただし、このコースで扱う範囲には、Alveo の接続されたサーバ (ACRi ルームでは as で始まるサーバ) についての内容を含みませんので、ご容赦ください。
ACRi ルーム管理のために開発されたソースコードなどのリソースは、GitHub の acri-room グループのリポジトリで公開・管理しています。併せてご参照ください。またこのリポジトリへの貢献者からもわかる通り、ACRi ルームは、三好健文氏や安藤潤氏をはじめとする、ACRi ルームの歴代のスタッフ・RA の貢献により構築・運営されてきました。この場を借りて御礼申し上げます。
ACRi ルームの概要
今更かもしれませんが、念のため……はじめに、ACRi ルームの概要について紹介します。ACRi ルームは、ACRi の主要な活動の1つとして公開されている、FPGA の遠隔利用環境です。2020年7月からプレ運用、同8月から本運用が始まり、この記事の公開時点で4年強が経過しました。これまでに登録されたユーザアカウントの数は 1,500 を超え、大学の授業や講習会などでもしばしば利用されています。
物理的な ACRi ルームは、運用開始当初は東京工業大学の学術国際情報センター (現・東京科学大学 情報基盤センター) に、2024年現在は東京科学大学大岡山キャンパス西8号館に設置されています。サーバに接続された FPGA ボードは、今日も元気に LED をチカチカさせています。

というのも、物理的なメンテナンスがしやすいように、ユーザの回路を書き込む前の FPGA には、サーバのホスト名の下2桁に対応する LED を点滅させる回路を書き込んでいるからです。例えば、vs103 に接続された FPGA ボードは、10進数の 3 → 2進数の 0011 に合わせて、右2つの LED を点滅させています。FPGA ボードを立てて設置するためのスタンドは、3D プリンタで手作りしているとのことです。これもメンテナンスのしやすさのためです。
さて、すでに ACRi ルームのアカウントをお持ちの方は、ACRi ルームでは2種類のアカウントが発行されることをご存じかと思います。1つは予約システム用、もう1つは利用環境サーバ用です。

ブラウザから https で WordPress 上の予約システムにアクセスするときには、前者のアカウントを使います。PowerShell などから ssh で利用環境のゲートウェイサーバ (2024年11月現在 fserv2) に接続したり、そこから (ポートフォワーディングを介した) リモートデスクトップで FPGA ボードの接続された各サーバを利用するときには、後者のアカウントを使います。それぞれのパスワードは別々のところで管理されています。
ACRi ルームのサーバ構成
先ほどの図にあった ACRi ルーム内のサーバ群を、もう少し細かく表すと、以下に示す図になります。

ACRi ルームのサーバ群は、大きく3つに分かれています。ACRi ルームのシステム全体の基幹的機能を担う管理サーバ群、Arty A7-35 をはじめとした小規模な FPGA ボードが接続された Arty サーバ群、それと Alveo (や AMD GPU) が接続された Alveo サーバ群の3つです。本記事では管理サーバ群と Arty サーバ群に注目します。
Web サーバ
管理サーバ群は Web サーバを含みます。Web サーバは、PHP、Apache、MariaDB からなる、WordPress を使った小規模サイトの典型的な構成です。WordPress には、オンラインレッスン予約システム (OLB) のプラグインを導入しており、サーバの予約機能は基本的にこのプラグインを使って実現しています。元々は、小規模なマンツーマン型のレッスンサイト向けに開発されたもののようですが、ACRi ルームでは「講師」を「サーバ」と読み替えて使用しています。つまり、サーバごとに「講師」役の WordPress アカウントが1つ割当てられています。予約情報は、WordPress のデータベース内に保存されます。
予約システムのトップページには、その日1日の全サーバの予約状況 (予約可能な時間枠は予約ページへのリンクを含む) を記したスケジュール表が表示されています。このスケジュール表の表示機能だけは、別プラグインとして独自開発しており、OLB-list リポジトリでソースコードを公開しています。OLB プラグインも、まさか100人以上の「講師」がいる状況を想定はしていなかったでしょう。既存のスケジュール表は、1つのマスの予約状況を取得するごとに1回のデータベースアクセスを必要とする仕様になっていて、トップページを開くたびに Web サーバにかなりの負荷がかかっていました。独自開発したスケジュール表では、その日の予約状況をまとめて1回のデータベースアクセスで取得するようにして、負荷を軽減しています。
ACRi ルームの Web サーバには、WordPress とは別に、予約情報を管理・監視するスクリプトがインストールされています。WordPress のデータベースに保存されている予約情報に対して、抽出や変更を行うスクリプトは、acri-sql リポジトリで公開しています。
予約情報の抽出は、WordPress のデータベースに対してクエリを発行することで行います。具体的なクエリは以下の通りです (読みやすくするために適宜改行しています)。
select server.user_login, time, user.user_login from wp_olb_history
inner join wp_users as server on wp_olb_history.room_id = server.id
inner join wp_users as user on wp_olb_history.user_id = user.id
where date = '$1'
OLB プラグインでは、予約履歴を wp_olb_history というテーブルに保存しています。単にこのテーブルを引くだけで終わらないのはなぜかというと、このテーブルにはユーザ ID (user_id) やサーバ ID (room_id) しか記録されていないからです。具体的なユーザ名やサーバ名を得るためには、ユーザ情報を管理する wp_users テーブルを内部結合して、ID とユーザ名 (user_login) とを結びつける必要があります。
ところで、「講師」役の WordPress アカウントのユーザ名は、(vs001 などといった) サーバ名そのものです。このことが、皆さんのユーザアカウントを u_ から始まるものに限定している理由の1つです。もし新しいサーバを立てたときに、そのサーバ名と同じ名前のユーザがすでに存在していると困りますので。
ということで、このクエリを実行すると、例えば以下のような出力が得られます。
vs001 15:00:00 u_*******
vs001 18:00:00 u_*******
vs002 15:00:00 u_********
vs002 21:00:00 u_*****
これで OK ……と言いたいところですが、実際にはもうひと手間加えています。具体的には、枠に誰も予約していないという状態と、予約を閉じている状態とを区別するためのクエリを、このあと追加で発行しています。この区別がないと、まれに、後で説明する仮想マシンの再起動処理が働かない場合があったためです。ちょっと長いですが、閉じられている枠を特定するには、以下のクエリを使っています。
create temporary table opens as
select time, sv.user_login from wp_olb_timetable as tt
inner join wp_users as sv on tt.room_id = sv.id
where date = @date;
create temporary table vservs as
select wp_users.user_login from wp_usermeta
inner join wp_users on wp_usermeta.user_id = wp_users.id
where wp_usermeta.meta_key = 'olbgroup' and
wp_usermeta.meta_value = 'teacher' and
wp_users.user_login not like "as%";
create temporary table times (time time);
insert into times (time) values
('00:00:00'), ('03:00:00'), ('06:00:00'), ('09:00:00'),
('12:00:00'), ('15:00:00'), ('18:00:00'), ('21:00:00');
select vservs.user_login, times.time, 'closed' from times
cross join vservs
left join opens on times.time = opens.time and vservs.user_login = opens.user_login
where opens.time is NULL;
ここでは、3つの一時的なテーブルを作成しています。opens は、(実際に予約されたかどうかにかかわらず) その日に対して開かれたすべての予約枠を、wp_olb_timetable テーブルから抽出したものです。vservs には vs で始まるサーバ群の一覧が入り、times には時間帯の一覧が入ります。
まず、times と vservs を交差結合すると、すべてのサーバと時間帯の組合せが得られます。さらに、これに対して opens を左結合すると、予約枠が開かれている (opens に該当する行がある) 場合は opens.time に times.time と同じ値が入ります。そうでない場合には、opens.time は NULL になります。できあがったテーブルから opens.time が NULL のものを取り出せば、予約枠が開かれていないサーバと時間帯の一覧のできあがりです。予約したユーザ名にあたる場所に便宜上 closed という値が入るようにして、結果を出力します。結果は例えば以下のようになります。
vs001 12:00:00 closed
vs002 12:00:00 closed
vs003 12:00:00 closed
得られた予約情報は、物理サーバからのみアクセスできる専用のローカル Web サーバを通じて、物理サーバから読み取られます。このローカル Web サーバに関するスクリプトは、acri-olb リポジトリ の server フォルダで公開しています。ローカル Web サーバは予約情報を JSON 形式で出力する CGI スクリプトを提供します。CGI スクリプトの仕様については、このコースの後半の記事で説明します。
Arty サーバ群の物理サーバ
Arty サーバ群では、VirtualBox を使い、1つの物理サーバの中で10台の仮想マシン (VM) を起動しています。VM を利用可能なユーザの切り替えや、FPGA へのデフォルトのビットストリームの書き込みは、後述する VM の起動時に処理します。物理サーバが普段行わなければならないのは、一定時間おきにローカル Web サーバにアクセスして、予約情報の差分をチェックすることです。予約が変更されている場合は、予約者変更を反映させるために VM を再起動します。また、このチェックの際に VM が起動していなければ、単に VM を起動します。2024年11月現在、チェックの間隔は2分に設定しています。
物理サーバで動かす必要のあるスクリプトは、acri-olb リポジトリ の vm-host フォルダにあります。上述した定期チェックのためのスクリプト (restart-vm.rb) の核になる部分の Ruby コードを以下に抜粋します。
if ! force
vm.each{|host|
excl = check_exclusion(host)
flag = check_user_diff(host, log)
if excl
log.puts "#{host} is listed for exclusion"
elsif flag
log.puts "#{host} has to be restarted"
restarts << host
elsif ! running.include?(host)
log.puts "#{host} is not started yet"
restarts << host
end
}
else
restarts = vm
end
log.puts "restart: #{restarts}"
restarts = [] if dry
スクリプトは、特定のファイルの中にサーバ名が記載されていたときに、そのサーバを自動再起動から除外するように設定されています。このチェックは check_exclusion の中で行っています。自動再起動の対象でない場合、前回スクリプトを実行したときの予約情報と、Web サーバから取得した現在の予約情報とを check_user_diff で比較します。もし除外対象のサーバでなく、かつ予約情報が異なっていた場合には、再起動の対象となります。それはそれとして、サーバが現在起動しているサーバの一覧 (running) の中にない場合にも、そのサーバは (再) 起動の対象です。
手動でこのスクリプトを実行する際は、すべてのサーバを再起動する強制再起動オプション (force) と、メッセージを表示して実際の再起動は行わないドライランオプション (dry) が用意されています。強制再起動の場合は、サーバごとのチェックをすべて無視します。ドライランの場合は、サーバごとのチェックを一通り行ってから、再起動対象のサーバのリストを空にしています。
最後に、スクリプトは、再起動対象のサーバを順番に再起動するとともに、予約情報を VM と共有されたフォルダに保存します。この予約情報は、各 VM が起動時に読み取ることになります。
Arty サーバ群の VM
各 VM が起動時に動かす必要のあるスクリプトは、acri-olb リポジトリ の client フォルダにあります。スクリプトは acri-startup.service で定義された systemd のサービスになっていて、ファイルサーバにアクセスできるようになった後、SSH やリモートデスクトップ (xrdp) のデーモンを起動する前に実行されるように設定されています。acri-startup.rb がメインのスクリプトで、ACRi ルーム全体を管理するユーザの権限で動作します。acri-startup-pre.sh と acri-startup-post.sh は必要な前処理と後処理を行うシェルスクリプトで、各サーバの root 権限で動作します。
メインのスクリプトでは、Vivado を起動して各サーバのデフォルトのビットストリームを書き込んだあと、物理サーバが用意した予約情報のファイルを読み取り、SSH やリモートデスクトップの設定ファイルを用意します。後処理では、準備された設定ファイルを実際に /etc 以下にコピーします。メインと前処理・後処理とで異なる権限でスクリプトを実行しているのは、書き込む必要のあるファイルのアクセス権限の都合によるものです。前処理が何をしているかは後述します。
仮想マシンのセットアップの自動化
全部で100台ある VM を、いちいちセットアップしていては大変です。そのため、あらかじめ共通のセットアップを済ませた VM を用意しておきます。このクローン元の VM はスケルトンとよばれ、skel で終わるサーバ名を持っています。すべての VM はスケルトンからのクローンで作成します。クローン後に、サーバ名や IP アドレスなど、各サーバ固有のセットアップを行います。
各 VM が起動時に動かすスクリプトには、この各サーバ固有のセットアップを自動化するための処理も含まれています。具体的には、まず、物理マシンがクローンを作成するときに、クローン先のサーバ名や IPアドレス などの情報と、そのサーバが未セットアップであるという情報を、VM との共有フォルダ上に保存します。そして、各 VM の起動時に、自身のサーバ名が skel で終わっていて、かつ未セットアップであるサーバが存在しているかどうかのチェックを行います。条件を満たした場合、そのサーバの情報をセットアップ済に書き換えてから、サーバ固有のセットアップに移ります。
もう少し具体的に、具体的なコードを出して説明します。クローンを作成するためのスクリプトはacri-olb リポジトリ の vm-host/create-vms.rb にあります。主要部分の Ruby コードを以下に示します。
created = []
subnet = (ip_prefix[0..2] == '10.') ? 8 :
(ip_prefix[0..2] == '172') ? 16 : 24
0.upto(num_vm.to_i).each do |i|
ip_host = (subnet == 24 && i == 0) ? 100 : i
vmname = "%s%02d" % [host_prefix, i]
vmip = "%s.%d/%d" % [ip_prefix, ip_host, subnet]
system("VBoxManage clonevm #{skel} --basefolder=/usr/local/vm --name=#{vmname} --register")
created << {name: vmname, ipaddr: vmip, state: 'created'}
end
File.open(VMFILE, 'w'){|f| f.puts(JSON.generate(created)) }
このスクリプトは、サーバ名のプレフィクス (host_prefix) や IP アドレスのプレフィクス (ip_prefix) などを引数として取ります。VirtualBox の CUI の管理ツール (VBoxManage) を使って VM をクローンする際に、プレフィクス + 連番の形で作成したサーバ名や IP アドレスを配列 created に登録しています。最後の1行で、作成された配列を JSON 形式でファイルに保存しています。
こうして作成された VM をまとめてセットアップするスクリプトが、vm-host/setup-created-vms.rb にあります。このスクリプトは、物理サーバに VM と同じ数の FPGA ボードが接続された状態で実行します。主要部分の Ruby コードは以下の通りです。
# Enumerate USB devices
# Note: Can we assume the serial number of Digilent devices begins with "21"?
usb_devices = `VBoxManage list usbhost | grep SerialNumber`.strip.split("\n")
usb_devices = usb_devices.select{|x| x =~ /21[0-9A-F]{10}$/ }.map{|x| x[-12..-1] }
usb_map = Hash.new
setup_vms.each do |vmname|
vmindex = vmname[-2..-1].to_i
if vmindex != 0 && vmindex <= usb_devices.size
usb_map[vmname] = usb_devices[vmindex - 1]
end
end
# Remove old setup log (if exists)
FileUtils.rm_f(setup_vms.map{|vmname| LOGFILE % vmname })
# Setup each of the VMs
setup_vms.each{|vmname| autosetup_vm(vmname, usb_map[vmname]) }
これよりも前の部分で、セットアップが必要な VM のサーバ名の一覧が、配列 setup_vms に入っています。VBoxManage を使って、物理サーバに接続されている USB 機器の一覧を表示して、その中から 21 で始まる16進数12桁のシリアルナンバーを抜き出します。少なくとも、現在 ACRi ルームで使用している Digilent 社の FPGA ボードは、そのようなシリアルナンバーを持っているためです。得られたシリアルナンバーを順番に VM に割当てていきます。
その後、古いログファイルを削除した上で、autosetup_vm を呼び出します。この中では、シリアルナンバーをもとに FPGA ボードを VM に割当てたあと、VM を起動し、起動した VM がログファイルを生成するまで待機します。ログファイルが生成されたら、サーバ固有のセットアップが完了したとみなし、VM をシャットダウンします。
サーバ固有のセットアップは、VM が起動したときの処理の後処理 (client/acri-startup-post.sh) の一部として行われています。後処理のシェルスクリプトの該当部分を、以下に抜粋します。
if [ -e ${HOST_FILE} ]; then
NEWSERV=`head -1 ${HOST_FILE}`
NEWIP=`tail -1 ${HOST_FILE}`
if [ ${NEWSERV} != ${SERV} ]; then
TMPFILE=`mktemp`
python3 ${SCRIPT_DIR}/vm-host-setup.py ${NEWSERV} ${NEWIP} > ${TMPFILE}
chmod 644 ${TMPFILE}
sudo -u ${USER_NAME} cp ${TMPFILE} ${SCRIPT_DIR}/log/setup-${NEWSERV}.txt
rm ${TMPFILE}
fi
fi
メインのスクリプトにおいて、サーバ固有のセットアップを行う条件を満たした場合、新しいサーバ名と IP アドレスがファイルに保存されています。まずはそれを読み取ります。もしそれが現在のサーバ名と異なっていれば、client/vm-host-setup.py を実行します。この Python スクリプトでは、ネットワーク設定に関する各種のファイル (/etc/hostname、/etc/hosts や、/etc/netplan/ にある yaml ファイル) の書き換えと、SSH 接続に使うホスト鍵の再生成を行っています。ログファイルはいったん一時ファイルに保存され、Python スクリプトの実行完了後に、物理サーバとの共有フォルダにコピーされます。
以上の仕組みにより、スケルトンの VM イメージさえ用意してしまえば、各物理サーバでスクリプトを2つ実行するだけで、その物理サーバ上の VM をすべて生成できるようになっています。物理サーバやスケルトンのセットアップについては、このコースの後半の記事で紹介します。
複数ユーザで利用するときの落とし穴
最後に、VM 起動時の前処理について紹介します。以下に示すのは、client/acri-startup-pre.sh で行っている処理の抜粋なのですが……
#!/bin/bash
touch /tmp/digilent-adept2-shm-dvtbl
touch /tmp/digilent-adept2-shm-dvtopn
touch /tmp/digilent-adept2-shm-ftdevcmg
# (中略)
chmod 777 /tmp/digilent-adept2-shm-dvtbl
chmod 777 /tmp/digilent-adept2-shm-dvtopn
chmod 777 /tmp/digilent-adept2-shm-ftdevcmg
# (後略)
書かれているのは、/tmp に root 権限で数十個の空のファイルを作成し、誰もが読み書きできる権限を付与する、という処理だけです。一見するとあまり意味のない処理であるように見えますが、この処理を行わないと、VM の起動時に FPGA にデフォルトのビットストリームを書き込むと、ユーザの起動した Vivado から FPGA ボードが認識できなくなります。
より詳細には、複数のユーザで1つの FPGA ボードを使用しようとすると、以下のような現象が起こります。
- VM を起動した後、最初に FPGA ボードにアクセスしたユーザ以外は、FPGA ボードの認識に失敗する。
- 他のユーザが FPGA ボードの認識に失敗した後でも、最初にボードにアクセスしたユーザは引き続き FPGA ボードの認識に成功する。
- Ubuntu 18.04 (ACRi ルーム開設当初に使っていた) では、すべてのユーザが問題なく FPGA ボードを認識できる。
- Ubuntu 20.04 や 22.04 だと問題が発生する。
どこに落とし穴があったかというと、2018年8月に Linux カーネルに導入されたセキュリティ対策 です。これが、Digilent 社製 FPGA ボードのドライバとの相性問題を引き起こしていました。この対策を適用すると、/tmp のような sticky bit のついているフォルダにファイルを作成しようとする (O_CREAT オプションをつけてファイルを開こうとする) と、そのファイルがそのフォルダの所有者以外の他人によってすでに作られていた場合には、失敗するようになります。
一方、Digilent 社のドライバは、/tmp 上に一種のロックファイルを作成する仕様があります。このファイルは、Vivado から FPGA ボードを認識しようとしたときに作成されます。作成されるファイルの名前は、例えば /tmp/digilent-adept2-shm-dvtbl です。……もう、おわかりですね。最初に FPGA ボードにアクセスしたユーザがこのファイルを作成すると、2人目以降は同じ名前のファイルを作成できず、FPGA ボードの認識に失敗してしまうのです。
この問題を解決する方法はいくつか考えられますが、ACRi ルームでは /tmp の所有者、つまり root が事前にファイルを作成しておく、という方法を取りました。最終的には、この問題について議論する Digilent 社のフォーラムへの投稿が見つかりました。そこでは、上述のセキュリティ対策そのものを無効化する、/tmp の sticky bit を除去する、などの方法も挙げられていました。しかし、それらの方法はセキュリティ的な副作用を伴います。幸い、作成される可能性のあるファイルの名前はフォーラム投稿の中に列挙されていたので、事前のファイル作成で対応できました。ファイル作成のタイミングは、サーバごとのデフォルトのビットストリームを書き込む前でないといけないので、この処理だけを前処理として記述しています。
まとめ
この記事では、ACRi ルームで100枚の FPGA ボードの時間貸しをどのように実現しているかについて解説しました。予約情報を管理・監視する Web サーバ、VM を必要に応じて再起動する物理サーバ、そして起動時に予約情報を反映させる VM とがうまく連携して、自動化を達成しています。
次の記事では、ローカル環境でミニ版の ACRi ルームを作るために、物理サーバや VM をどのようにセットアップすればよいかについて解説したいと思います。
愛知工業大学 藤枝直輝