ラボ:compose ファイルで作るコンテナー環境
1つのファイルでは足りない授業もあります。クエリを実行するデータベース、その前に置く API、隣で動くキャッシュなどです。ラボはそうしたサービスの組み合わせで、ラボのページで書くか、自分の GitHub リポジトリに置いた compose ファイルで定義します。
ラボを設定し、各生徒に何が渡るかを確認して公開し、自分のコピーで試してから、ほかの課題と同じように配布します。生徒はそれぞれラボのページから自分のコピーを起動し、コピーはセッションをまたいで保存されます。終わったら生徒が提出し、教員が採点します。
モジュールを使わずにまず自分で試すには、バーの「つくる」を開き、「コンテナーを実行」を選びます。訪問者にも表示され、アカウントなしでは誰でも Git ラボと SQL ラボを起動でき、ほかのサンプルには無料アカウントが必要です。「サンプルラボ」のカードには、Web API 付きのデータベース、ショップのデータが入った MySQL と phpMyAdmin、Gitea で学ぶ Git、Juice Shop・DVWA・WebGoat のセキュリティラボの6つが並び、それぞれこのマニュアルに専用のページがあり、「起動」でワンクリック起動でき、「先にファイルを編集」で変更してから起動することもできます。このページでは、「起動」を押すと画面に表示されている内容が実行され、ボリュームは保持されます。ページを開くだけでは何も書き込まれません。ファイルは、アカウントなら入力をやめた1秒後に保存され、訪問者の場合は確認だけが行われます。「元に戻す」を押すと、読み込んだサンプルを取り消せます。
ラボは Ubuntu VM にすることもできます。ラボの種類として Ubuntu VM を選ぶと、各生徒に専用のマシンが用意され、教員が書いたスクリプトで設定されます。下の「VM ラボを設定する」をご覧ください。
ラボを設定する
「ラボ」の中の「コンテナーラボ」のカードで課題を追加します。新しいラボは「ここで書く」の状態で始まり、サンプルがすでに入っています。データベースとその前に置いた Web API で、ターミナルからpsql と curl でアクセスできます。そのまま公開して動くラボを見ることも、先に変更することもできます。ファイルは入力をやめて1秒後に保存され、保存のたびにチェックされます。ここで書いたファイルではイメージしか使えません。ビルド元のリポジトリも、env_file を読み込むリポジトリもないからです。
もっと大きなものには「GitHub リポジトリ」を選びます。ラボの履歴が残り、自分のマシンでもdocker compose で同じように動きます。「サンプルを使う」を選ぶと、ラボは mitsurutech/lab-sample を指します。その README がそのまま授業の教材になっており、フォークすれば自分のものにできます。もう1つの mitsurutech/lab-mysql-phpmyadmin は、学習用データベース入りの MySQL と phpMyAdmin で、README に SQL の演習があります。使うにはそのアドレスを指定します。3つ目の mitsurutech/lab-git-gitea は Git サーバーの Gitea で、git の入ったターミナルからクローン、コミット、プッシュできるリポジトリがあります。それ以外の場合は、リポジトリのアドレス、ブランチ、リポジトリのルートから見た compose ファイルの場所を指定します。非公開リポジトリの場合は「非公開リポジトリ」にチェックを入れ、そのリポジトリ1つだけに読み取り専用でアクセスできる fine-grained personal access token を貼り付けます。トークンは暗号化して保存され、二度と表示されません。
フォームは自動で保存されます。保存のたびにブランチの最新コミットのファイルを読み込み、各生徒に何が渡るかを表示します。すべてのサービス、その元になるイメージ、メモリ、ポート、保持されるボリュームです。

compose ファイルに書けること
ラボはクラス全員で共有するマシンで動くため、ファイルで求められるのは30人の生徒に同時に渡しても安全なものだけです。イメージは Docker Hub か GitHub のレジストリ(ghcr.io)から取得するか、公開リポジトリ内のフォルダーからビルドします。後者は公開の際にまず GitHub 上でビルドされ、できたイメージは一般に公開されるため、パスワードは Dockerfile ではなく env_file に置いてください。1つのラボには最大6個のサービスと、生徒1人のコピーあたり 2048 MB のメモリを割り当てられます。独自の上限がないサービスは 512 MB として数えます。
マシン自体に届くものはすべて拒否されます。特権コンテナー、マシンのフォルダーのマウント、Docker ソケット、ホストのネットワーク、マシン上に公開するポート、そして ${...} によるマシンの環境変数からの値の埋め込みです。これらを求めるファイルは行ごとに拒否され、すべての問題が一度に、それぞれの行番号付きで示されるので、1回の push でまとめて修正できます。コンテナーに渡したいドル記号は、docker compose と同じく $$ と書きます。そのため、コマンドでは $$HOME や "$$@" も使えます。
自分のコピーで試す
コミットを公開したら、ラボのページで「起動」を押します。サービスはファイルの depends_on の順に起動し、それぞれサービス名でほかのサービスから到達でき、横にはターミナルがあります。起動したら「ターミナルを開く」を押してください。「停止して保存」ですべての名前付きボリュームとターミナルのホームフォルダーが保持され、もう一度「起動」すると元に戻ります。
ターミナルでは、ラボ自身のサービスに対してファイル内の名前で docker を使えます。docker ps でサービスとその正常性を確認し、docker logs api で出力を読み、docker exec -it db sh で中のシェルに入れます。docker restart、stop、start、docker inspect、top、stats、cp も使えます。作成、pull、ビルド、削除はできず、到達できるのはこのコピーのサービスだけです。シェルを含まないイメージには、実際のサーバーと同じく入れません。
サービスをブラウザーから開けるようにするには、ファイルでそのサービスに lab.open: "8080" というラベルを付けます。ports または expose に記載したポートが対象です。コピーの実行中は、カードに開いた各ポートへのリンクが表示され、それぞれ専用のアドレスを持ちます。開けるのはそのコピーの持ち主の生徒とモジュールの教職員だけです。Postman、curl、プログラムから API を呼び出すには、カードに表示されるコピーのトークンを X-Lab-Token ヘッダーで送ります。漏れた場合は「新しいトークン」で置き換えられます。ポートで通るのは Web の通信だけです。自分のパソコンのデータベースクライアントからは接続できないので、ラボのターミナルを使ってください。データベースなど Web 以外のサービスがよく使うポート(5432、3306、6379、27017 など)を開こうとすると、ページがそのことと、代わりの接続先を知らせます。これは注意の表示であり、拒否されることはありません。
これらの合計は 200 MB までです。コピーが上限に近づくとページで警告され、停止時に上限を超えていたコピーは、その前の保存内容が維持されます。
配布する
ほかの課題と同じく、モジュールの「課題」タブでラボにチェックを入れて「配布」を押します。生徒には通常のお知らせとメールが届きます。リンクを開くとラボ自身のページが表示され、教員の説明、自分のコピーを起動する「起動」ボタン、実行される compose ファイルがあります。
生徒は起動したときのコミットのまま使い続けます。修正を公開すると、ラボのページに古いままのコピーの数が表示され、「全員を更新」を押すと、各生徒が次に起動したときに新しいコミットに移ります。保存した内容は維持されます。
ラボで進めるレッスン
レッスンを 1 つのラボ(コンテナーラボまたは Ubuntu VM ラボ)に紐付けると、クラスは同じ場所で読み、入力できます。レッスンの編集画面で、上部の「ラボ」からラボを選びます。選べるのはそのモジュールのラボだけです。「Git パート 1」「Git パート 2」のように複数のレッスンで 1 つのラボを共有でき、その場合も各自のコピーとターミナルは 1 つです。
手順ごとにコマンドブロックを追加します。最大 20 行のシェルコマンドです。レッスンでは、上部にラボのバーが表示されます。起動と停止、公開した各ポートへのリンク、そしてターミナル全体を新しいタブで開く「ターミナルを開く」です。コマンドブロックの「実行」は、そのコマンドを読者自身のターミナルに入力するため、レッスンから実行した内容は履歴に残り、出力はブロックの下に表示されます。数秒たっても終わらないコマンド(パスワードの入力待ちなど)は、ターミナルで続けてください。「実行」はラボの起動中だけ押せ、ラボを自動で起動することはありません。VM ラボのターミナルは Ubuntu のマシンなので、「実行」はマシンが起動し、セットアップスクリプトが終わってからマシンに入力されます。それより前に押すと、マシンがまだ起動中であると表示され、何も入力されません。
スタッフにも自分のコピーがあるため、プロジェクターで実行したコマンドはあなたのコピーで動きます。ラボの課題を提出済み、または採点済みの生徒は、レッスンからもラボを起動できません。ラボのないレッスンのコマンドブロックは、編集画面の「準備ができていない項目」に表示されます。
見守る、操作を代わる
ラボのページにはクラスの一覧があり、誰がコピーを起動したか、誰がいまターミナルを開いているかと、生徒ごとに2つのボタンが表示されます。「見る」はターミナルを読み取り専用で開き、生徒の入力をその場で見られます。「操作を代わる」ではそのターミナルに入力できます。生徒のページには教員が入力している旨が表示され、アクセスログに記録されます。「操作を戻す」で終了します。どちらかを初めて開くときは、ほかのターミナルと同じくコードがメールで届きます。
欠席している生徒については、「保存済みコピーを開く」で最後に保存された状態を、専用のターミナル付きの教員自身のセッションで起動できます。そこで変更した内容は生徒の側には保存されません。その生徒自身のコピーが実行中のあいだは開けないので、代わりに監視してください。
提出と採点
生徒はラボを停止してから「提出」を押します。コピーに保存された内容はその時点のまま保持され、コピーは閉じられます。教員が差し戻すまで、生徒は再び起動できません。実行中のラボは提出できないため、採点するのは保存された内容そのものです。
ラボのページのクラス一覧には、誰が提出し誰が採点済みかが表示され、各生徒の横に「採点」があります。これを押すと、その生徒の状態のラボが開きます。提出日時、VM で変更した内容、その生徒とのやり取り、点数とフィードバックです。クラス一覧の「保存済みコピーを開く」では、提出された内容を変更せずに、教員自身のセッションで起動できます。
採点すると生徒に通知され、ラボは閉じたままになります。「差し戻す」で再び開けるようになり、理由を書けば、生徒はそれをお知らせとやり取りの中で読めます。以前に提出した内容は、再提出するまで残ります。
公開する
公開すると、確認したばかりのコミットに固定されます。生徒のコピーはそのコミットから起動し、ブランチに push しても、再び公開するまで生徒には何も変わりません。
セッション間で生徒ごとに保存されるのは、名前付きボリュームとターミナルのホームフォルダーだけです。コンテナー内でそれ以外に変更したものは、本番環境のコンテナーと同じく、ラボが停止すると消えます。新しいイメージにしても、ボリュームにすでにあるデータは変わりません。初期データや起動スクリプトは、空のボリュームに対してしか実行されません。
各サービスは、ファイルでユーザーを指定しない限り、自分のマシンで docker compose を使うときと同じくイメージ自身のユーザーで実行されます。ボリューム以外に2 GB を超えて書き込んだコピーは、ボリュームを保存したうえで停止されます。暴走したログ1つでクラス共有のディスクがいっぱいにならないようにするためです。
VM ラボを設定する
「ラボ」の中の「Ubuntu VM ラボ」のカードで課題を追加します。ページにはcompose ファイルの代わりにマシンの短い記述を書きます。cpus、メモリ(MB)、ディスク(GB)、ブラウザーで開くポートを指定する open、そして各生徒の VM が初めて起動するときに root で実行されるスクリプトの setup です。Web サーバーをインストールするサンプルから始まるので、公開して「起動」すればすぐに動く様子が見られます。
各生徒の VM はディスク上のものをすべて含めてセッション間で保持され、ほかのラボと同じく生徒のターミナルを見たり操作を代わったりできます。setup は生徒ごとに一度だけ実行され、その出力は生徒の VM の /var/log/mitsurutech-setup.log にあります。VM がアクセスできるのは Ubuntu のパッケージ、PyPI、npm、GitHub だけです。
提出された VM を自分で読まずに採点するには、setup と同じ形式のスクリプト check を追加します。クラス一覧の生徒の行で「チェックを実行」を押すと、保存されたマシンのコピーが(元には保存されない形で)起動し、スクリプトが root で実行されて、合格したか(終了コード 0)、何を出力したか、いつかが表示されます。結果は生徒には知らされず、スクリプトが何をしても生徒のマシンは変わりません。
学校の生徒には、あなたが書いたサイズが割り当てられます。上限はCPU 4個、メモリ 4096 MB、ディスク 20 GB です。省略した項目は学校の既定値で、CPU 2個、2 GB、10 GB です。学校に所属していない人がこのラボを開く場合は、その人自身のティアのサイズに制限されます。
生徒が VM を停止すると、クラス一覧に変更内容が表示されます。インストールしたパッケージ、システムのフォルダー以外に書いたファイル、/etc の履歴です。生徒の VM を起動しなくても、これを読んで採点できます。
1台の VM で動くネットワークラボ
ルーターには専用のマシンが必要ですが、生徒が持つ VM は1台です。そこでネットワークのサンプルラボは、トポロジー全体を VM の中に作ります。すべてのルーターとホストがネットワーク名前空間、すべてのリンクが仮想ケーブルのペアで、各ルーターが自分専用の FRRouting を動かし、生徒は実機で使うのと同じ vtysh コンソールに触れます。VM ラボのページには、スタティックルーティング、OSPF、2つの自律システム間の BGP の3つが用意されています。使うとテキストが置き換わり、「元に戻す」で自分のテキストに戻せます。
すべてはラボ自身の setup スクリプトが、VM の初回起動時に Ubuntu のパッケージアーカイブからインストールします。所要時間は約10秒です。netlab コマンドも一緒に入ります。netlab status でマシン一覧、netlab shell r1 でルーターのコンソール、netlab shell h1 でホストのシェル、netlab reset でラボのやり直しができます。本物のコマンドもそのまま使えます。それが狙いです。sudo ip netns exec h1 ping 10.0.3.2、sudo ip netns exec r1 tcpdump -ni to-r2、sudo vtysh -N r1 などです。各ラボは生徒のホームフォルダーに、やることだけを書いて答えは書かない README を置きます。
各サンプルにはチェックが付いています。チェックは、保存されたマシンのコピーを起動し直して実行するため、生徒が write memory で保存した内容(netlab shell を終了しても保存されます)だけが見えます。実際の状態が成り立つときだけ合格になります。ルーティングテーブルにルートがあること、OSPF の隣接が Full、または BGP セッションが Established であること、h1 がルーターを経由して h2 に届くこと、プライベートなネットワークには届かないことです。アドレスの付け替え、ファイアウォールのルール、ip route で入力しただけで保存していないルートでは合格しません。出力の1行目にその理由が出ます。
独自のトポロジーを作るには、サンプルをコピーして setup 内のトポロジーを書き換えます。ルーター、ホスト、リンクが各1行で、daemons ospfd または daemons bgpd と書くと、そのプロトコルがすべてのルーターで起動します。ツール、トポロジー、README は setup の 16 KB に十分収まります。
よくある質問
- ラボはここで書くべきですか、GitHub に置くべきですか?
- 何かを試すなら「ここで書く」が最も手軽で、既成のイメージだけで作るラボならそれで十分です。Dockerfile からイメージをビルドする、履歴を残す、自分のマシンで docker compose を使ってテストする、同僚と共有する、といった場合は GitHub に置いてください。2つを切り替えても、もう一方で設定した内容は保持されます。
- 自分でビルドしたイメージを使えますか?
- はい。ghcr.io に公開イメージとして push してファイルで指定するか、リポジトリ内のフォルダーからファイル自体にビルドさせてください。
- 生徒は compose ファイルを見られますか?
- はい。ラボのページで、自分のコピーの横に読み取り専用で表示されます。ラボが何でできているかを学ぶ手段になります。トークンは誰にも表示されません。