さくらVPSでFreeBSD＋Jailを動かして仮想的にVPSが複数使えているような環境を作ってみる

遅ればせながら [FreeBSD Advent Calendar 2018](https://qiita.com/advent-calendar/2018/freebsd) 28日目の記事です。
本来は22日目の記事だったのですが、私の不手際で投稿が伸びてしまいました。申し訳ありません。
22日の記事は私に代わって[みんみん](https://qiita.com/belgianbeer)さんが[ZFSのsnapshotをまとめて削除する](https://qiita.com/belgianbeer/items/822733b30200a2b6727f)という記事を書いてくださっています。ぜひご覧いただければと思います。

今日は[さくらのVPS](https://vps.sakura.ad.jp/)上でFreeBSD＋Jailを動かす手順を紹介しようと思います。

## さくらのVPSでFreeBSD

[さくらのVPS](https://vps.sakura.ad.jp/)(Virtual Private Server)では、512MB～32GBの仮想サーバが利用できます。
私は1Gのプランで利用しているのですが、あまりVPSを活用しきれていない感がありました。複数台のVPSがあったりすると便利かもしれませんが、それはそれでお金がかかってしまいます。そこでFreeBSD＋Jailで個別の環境を構築すれば、仮想的に複数のVPSを利用する形にできないかと考えました。

## FreeBSD＋Jailの環境を構築する

## 今回構築するJail環境

今回構築するJail環境では、以下を実現できるようにしてみましょう。

 * Jailコンテナにsshでログインできること
 * VNCでX11な環境が利用できること

### 構成図

今回は以下のような構成でJail環境を構築してみましょう。VPS上のFreeBSD(ホストOS)で `192.168.0.0/24` な内部ネットワークを用意し、そこにJailなコンテナを用意する構成にしてみます。

```
..................................................
.  +---------+     +---------+     +---------+   .
.  | Jail 01 | ... | Jail 02 | ... | Jail 05 |   .
.  +---+-----+     +---+-----+     +--+------+   .
.      |               |              |          .
.  ----+------+--------+--------------+------- 192.168.0.0/24
..............|....................Jail環境.......
              |
  +-----------+--------------+
  | VPS上のFreeBSD (Host OS) |
  +----------+---------------+
             |
         [Internet]
             |
         +---+----+
         | 自宅PC |
         +--------+
```

### Host OS側でのJail環境の構成

ホストOS側でのJail環境のディレクトリは以下のようにしてみましょう。

```
/
`-- jail/
    |-- FBSD11_DIST/
    |   `-- setup_script/
    |       `-- base.txz
    |       `-- kernel.txz
    `-- jail01/
        `-- (Jail環境のルートディレクトリ)
```

### 環境設定

ホストOS側でのJail環境設定です。最低限、以下のファイルを設定すれば準備OKです。 `/etc/rc.conf` と `/etc/pf.conf` にはJail環境のネットワークをNAT構成にするための設定を記述しておきます。 `/etc/jail.conf` には各Jail環境の設定を記述します。

 * /etc/rc.conf
 * /etc/pf.conf
 * /etc/jail.conf

### /etc/rc.conf

`/etc/rc.conf` の設定は以下になります。 `lo0` にJail環境で使用するプライベートIPをエイリアスとして割り当てておきます。

```
# 「さくらのVPS」コントロールパネルの「標準ホスト名」を指定します。
hostname="<VPSのホスト名>"

# 「さくらのVPS」コントロールパネルの「アドレス」に記載されているIPアドレスを指定します。
ifconfig_vtnet0="inet XXX.XXX.XXX.XXX netmask 255.255.254.0"

# 「さくらのVPS」コントロールパネルの「ゲートウェイ」に記載されているIPアドレスを指定します。
defaultrouter="XXX.XXX.XXX.XXX"

ifconfig_lo0="inet 127.0.0.1"
ifconfig_lo0_alias0="inet 192.168.0.254 192.168.0.255 netmask 255.255.255.0"
ifconfig_lo0_alias1="inet 192.168.0.10"
ifconfig_lo0_alias2="inet 192.168.0.11"
ifconfig_lo0_alias3="inet 192.168.0.12"
ifconfig_lo0_alias4="inet 192.168.0.13"
ifconfig_lo0_alias5="inet 192.168.0.14"

# NATの設定を行うため、pf(packet filter)を有効にします。
pf_enable="YES"
pf_rules="/etc/pf.conf"
pf_flags=""

pflog_enable="YES"
pflog_logfile="/var/log/pflog"
pflog_flags=""
```

### /etc/pf.conf

Jail環境からNATするための設定は `/etc/pf.conf` に記述します。また、ホストOS側から各Jail環境にsshでログインできるよう、ポートフォワード設定も記述しておきます。

ポートフォワード設定は以下のようにJail環境の設定と関連する形にしておくと管理が楽になります。以下の例では、Jail環境のプライベートIPアドレス( `192.168.0.10` )の4オクテット目の `10` をホストOS側の `2200` 番ポートにインデックスの形で加算し、 `2210` とする管理方法にしています。

```
# ホストOSの2210番ポートへのアクセスをJail環境(192.168.0.10)の22番ポートにポートフォワードする。
rdr pass on $ext_if proto tcp from any to ($ext_if) port 2210 -> 192.168.0.10 port 22
```

`/etc/pf.conf` の全体は以下のような感じになります。

```
# external interface
ext_if="vtnet0"

# internal interface
int_if="lo0"

# NW setting for internal NW
table <private> const { 192.168.0.0/24 }

# IP mascarade
nat on $ext_if inet from ($int_if) to ! <private> -> ($ext_if)

# redirect
rdr pass on $ext_if proto tcp from any to ($ext_if) port 2210 -> 192.168.0.10 port 22
rdr pass on $ext_if proto tcp from any to ($ext_if) port 2211 -> 192.168.0.11 port 22
rdr pass on $ext_if proto tcp from any to ($ext_if) port 2212 -> 192.168.0.12 port 22
rdr pass on $ext_if proto tcp from any to ($ext_if) port 2213 -> 192.168.0.13 port 22
rdr pass on $ext_if proto tcp from any to ($ext_if) port 2214 -> 192.168.0.14 port 22

# packet filter
pass in  quick all
pass out quick all
```

```
$ sudo service pf restart
```

### /etc/jail.conf

あとはJail環境の設定です。以下の1エントリが1個のJailコンテナの設定になります。

微妙なハマりどころとして、IPv6を利用せずとも `ip6.addr = fe80::11;` の設定は記載しておく方が良いかもしれません。これはLL言語等で、Socketまわりの機能を利用する際に、内部的にIPv6( `PF_INET6` )でソケット作成を試すものがあるようで、IPv6アドレスが振られていないと、ソケットを利用する機能が常にエラーになってしまう(！)という問題にハマってしまうためです。

```
test10 {
        jid             = 10;
        path            = /jail/test10;
        ip4.addr        = 192.168.0.10;
        ip6.addr        = fe80::11;
        host.hostname   = docs.furandon.net;
        allow.raw_sockets;
        exec.start      = "/bin/sh /etc/rc";
#        exec.stop       = "/bin/sh /etc/rc.shutdown";
        interface       = lo0;
        mount.devfs;
        persist;
}
```

これでJailコンテナを作成する準備が整いました。

## Jailコンテナを作成する

### コンテナ用のFreeBSD配布物を用意する。

Jailコンテナは、特定のディレクトリ以下にFreeBSDの(最小限の)配布物を展開し、それをルートディレクトリに指定します。
以下の手順でFreeBSDの配布物をダウンロードします。

```
$ sudo mkdir /jail
$ sudo mkdir /jail/FBSD12_DIST
$ cd /jail/FBSD12_DIST/
$ sudo curl -O http://ftp.jaist.ac.jp/pub/FreeBSD/releases/amd64/12.0-RELEASE/base.txz
$ sudo curl -O http://ftp.jaist.ac.jp/pub/FreeBSD/releases/amd64/12.0-RELEASE/kernel.txz
```

### コンテナの実体を用意する

次に、コンテナの実体を用意します。といってもJailコンテナのルートとなるディレクトリを作成し、FreeBSDの配布物を展開するだけです。加えて以下の例では `/etc/resolv.conf` と `/etc/rc.conf` の設定を行っています。

```
$ cd /jail
$ sudo mkdir ./jail10
$ sudo tar Jxfp FBSD12_DIST/base.txz -C ./jail10
$ sudo tar Jxfp FBSD12_DIST/kernel.txz -C ./jail10
$
$ sudo cp /etc/resolv.conf ./jail10/etc/resolv.conf
$ echo 'sshd_enable="YES"' | sudo tee -a ./jail10/etc/rc.conf
```

### コンテナを起動する

一通りの準備が整ったので `service jail [one]start <jail名>` を実行してJailコンテナを起動してみます。 

```
$ sudo service jail onestart jail10
```

無事にJailコンテナが起動できました。

```
$ jls
   JID  IP Address      Hostname                      Path
    10  192.168.0.10    jail10                        /jail/jail10
```

次に、最低限のパッケージインストールと一般ユーザを作成します。 `sudo` できるようにしておきたいので、 `adduser` コマンドでユーザを作成する際に、追加グループとして `wheel` を指定しておきます。

```
$ sudo pkg --jail 10 update
$ sudo pkg --jail 10 install -y sudo bash
$ sudo jexec 10 visudo
$ sudo jexec 10 adduser
Username: freebsd
Full name:
Uid (Leave empty for default):
Login group [freebsd]:
Login group is freebsd. Invite freebsd into other groups? []: wheel
Login class [default]:
Shell (sh csh tcsh bash rbash nologin) [sh]: bash
Home directory [/home/freebsd]:
Home directory permissions (Leave empty for default):
Use password-based authentication? [yes]:
Use an empty password? (yes/no) [no]:
Use a random password? (yes/no) [no]:
Enter password:
Enter password again:
Lock out the account after creation? [no]:
Username   : freebsd
Password   : *****
Full Name  :
Uid        : 1001
Class      :
Groups     : freebsd wheel
Home       : /home/freebsd
Home Mode  :
Shell      : /usr/local/bin/bash
Locked     : no
OK? (yes/no): yes
adduser: INFO: Successfully added (freebsd) to the user database.
Add another user? (yes/no): no
Goodbye!
```

### sshでログインできるようにする

ローカルPCの `~/.ssh/config` に以下の設定を追加し、Jailコンテナにsshでログインできるようにします。
VPS上のホストOSの2210番ポートからJailコンテナの22番ポートにリダイレクトされる形になります。

```
Host jail10
        # HostNameには、「さくらのVPS」コントロールパネルの
        # 「アドレス」に記載されているIPアドレスを指定します。
        HostName        XXX.XXX.XXX.XXX
        Port            2210
        User            freebsd
```

以下のようにsshでログインできることが確認します。

```
$ ssh jail10
Last login: Thu Dec 27 21:46:08 2018 from :0
FreeBSD 12.0-RELEASE r341666 GENERIC

Welcome to FreeBSD!

Release Notes, Errata: https://www.FreeBSD.org/releases/
Security Advisories:   https://www.FreeBSD.org/security/
FreeBSD Handbook:      https://www.FreeBSD.org/handbook/
FreeBSD FAQ:           https://www.FreeBSD.org/faq/
Questions List: https://lists.FreeBSD.org/mailman/listinfo/freebsd-questions/
FreeBSD Forums:        https://forums.FreeBSD.org/

Documents installed with the system are in the /usr/local/share/doc/freebsd/
directory, or can be installed later with:  pkg install en-freebsd-doc
For other languages, replace "en" with a language code like de or fr.

Show the version of FreeBSD installed:  freebsd-version ; uname -a
Please include that output and any error messages when posting questions.
Introduction to manual pages:  man man
FreeBSD directory layout:      man hier

Edit /etc/motd to change this login announcement.
"man security" gives very good advice on how to tune the security of your
FreeBSD system.
[freebsd@jail10 ~]$ 
```

### VNCでも接続できるようにする

続けてVNCでもJailコンテナに接続できるように設定してみます。まずは必要なパッケージをインストールします。

```
[freebsd@jail10 ~]$ sudo pkg install -y tigervnc-server tigervnc-viewer xauth xterm icewm
[freebsd@jail10 ~]$ sudo pkg install -y ja-uim-mozc uim-gtk ja-font-sazanami
[freebsd@jail10 ~]$ sudo pkg install -y leafpad
```

`~/.vnc` ディレクトリ以下にデフォルト設定ファイルを生成するため、VNCサーバの起動・停止を行います。
(なのでここでは単にVNCを起動して停止するだけ)

```
[freebsd@jail10 ~]$ vncserver :0
[freebsd@jail10 ~]$ vncserver -kill :0
```

`~/.vnc/xstartup` に以下を追記します。

```
# 生成されたファイルではtwmが起動する記述になっているのでコメントアウトする。
#twm &

export GTK_IM_MODULE=uim
export QT_IM_MODULE=uim
export XMODIFIERS=@im=uim
export XIM=uim
/usr/local/bin/mozc start
uim-xim &

exec icewm
```

改めてVNCサーバを起動します。

```
[freebsd@jail10 ~]$ vncserver :0
```

ローカルのPCから接続する場合は、以下のようにsshの `-L` オプションでVNCの通信路をトンネリングすると良いかと思います。
VNC自体はデータを暗号化しないので、sshのトンネリングで暗号化しつつ、Jailコンテナへのポートフォワーディング設定も一緒に行える形になるのでおススメの方法です。

```
$ ssh -L 5900:localhost:5900 jail10
```

ローカルPCからはVNCクライアントで `vnc://localhost:5900` に接続することでJailコンテナ上のVNCに接続できます。

![sample8.gif](https://qiita-image-store.s3.amazonaws.com/0/61137/35152ef7-4125-6acb-2894-a31da7a0b185.gif)

これでJailコンテナへのssh接続とVNCによるGUI環境の構築まで行えました。

## まとめ

さくらVPS上でFreeBSDを動かし、さらにJail環境を構築してVPSを仮想の仮想(？)的に複数台あるような形で利用する手順を紹介しました。
今回のJail環境は手動で構築する方法を紹介していますが、ある程度利用方法が固まったらスクリプト等で定型的な処理を行わせるとよりVPCを使い倒せるかと思います。