OpenVPNで自宅RaspberryPiサーバを公開

Google Cloud Compute Engine の micro インスタンスが無料で使えるようになりました。みなさん使っていますか？僕は http://manga.dog/ で使っています。が、しかし、新しい機能を追加しようと思ったのですが、どうも全文検索エンジンの Elasticsearch を使わないといけないようです。そして Elasticsearch はメモリを食いまくるので現在の micro インスタンスに同居させるのはとても難しい。Amazon Elasticsearch Service を使って機能を実装してはみたのだけど、そもそも無料の micro インスタンスを使っていたので有料サービスを使うのは負けた気がする！そこで自宅に転がっていた Raspberry Pi3 に Elasticsearch を入れシステムに組み込み勝利をつかみ取ることにしました。

# 必要な機能

+ ラズパイで Elasticsearch を動かす
+ アプリケーションサーバから自宅のラズパイにアクセスする

このような機能が必要なのですが、まぁいわゆる自宅サーバを公開すればよいわけですね。一般的には自宅のルーターに外部からきたアクセスを特定の機器にルーティングする設定をおこなう方法だと思います。でもこの方法だと自宅のIPが変わるとアクセスできなくなってしまいます。それを回避するためにダイナミックDNSを使います。でもこの方法はかっこ悪いなと前々から思っていました。

そこで VPN を使った方法で構築したいと思います。全体の構成はこのようになります。

![VPNサーバ公開.png](https://qiita-image-store.s3.amazonaws.com/0/33704/5c1fd43d-a44c-07c7-37a7-051a06183d1f.png)

VPNサーバを無料なGoogle Cloud Compute Engineに立てます。ラズパイは自宅からVPNサーバに接続します。するとサーバとクライアントは 10.8.0.1 と 10.8.0.6 の仮想ネットワーク上のIPアドレスで接続されます。繋がってしまえば簡単ですね。サーバに飛んできたHTTPリクエスを nginx をプロキシサーバにしてラズパイに投げてしまえばよいのです。

手順としては以下の４段階になります。

1. VPNサーバの設定
2. Raspberry Pi3 を VPN サーバに接続する
3. Raspberry Pi3 で Elasticsearch を動かす
4. VPNサーバに nginx でプロキシサーバを立てる

それでは設定していきます。

# VPNサーバの設定

環境: Google Cloud Compute Engine
OS: Ubuntu 16.04 LTS
開放ポート: 80-TCP, 1194-UDP

サーバを用意したら80と1194のポートを開放してください。1194はUDPになります。
まずは更新します。

```shell-session
$ sudo apt-get update
$ sudo apt-get upgrade
```
openvpn と git をインストールします。easy-rsa という鍵を作成するツールのリポジトリも取得します。

```shell-session
$ sudo apt install openvpn git
$ git clone https://github.com/OpenVPN/easy-rsa.git
```

鍵がいくつも必要になるので作っていきます。作成した鍵は `/etc/openvpn/keys` にコピーします。
`ca.crt` を作ります。phrase と Common Name は適当なものを入力してください。

```
$ cd easy-rsa/easyrsa3
$ ./easyrsa init-pki

$ ./easyrsa build-ca
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
Common Name (eg: your user, host, or server name) [Easy-RSA CA]: google
$ sudo mkdir /etc/openvpn/keys
$ sudo cp pki/ca.crt /etc/openvpn/keys
```

`server.crt` と `server.key` を作成します。途中で `ca.key` の phrase を求められるので入力します。

```
$ ./easyrsa build-server-full server nopass
Enter pass phrase for /home/akiraak/easy-rsa/easyrsa3/pki/private/ca.key:
$ sudo cp pki/issued/server.crt /etc/openvpn/keys
$ sudo cp pki/private/server.key /etc/openvpn/keys
```

`dh.pem` を作成します。こちらも `ca.key` の phrase を入力します。このキーの作成は少し時間がかかります。

```
$ ./easyrsa gen-dh
$ sudo cp pki/dh.pem /etc/openvpn/keys
```

`crl.pem` を作成します。途中で `ca.key` の phrase を入力します。

```
$ ./easyrsa gen-crl
Enter pass phrase for /home/akiraak/easy-rsa/easyrsa3/pki/private/ca.key:
$ sudo cp pki/crl.pem /etc/openvpn/keys
$ sudo chmod o+r /etc/openvpn/keys/crl.pem
```

クライアントの鍵を作成します。[client-name] に適当な名前を入れてください。僕の場合はクライアントがラズパイなので ras としました。また、クライアント鍵の phrase が求められるので適当なものを入力し、その後に `ca.key` の phrase も入力します。

```
$ ./easyrsa build-client-full [client-name]
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
Enter pass phrase for /home/akiraak/easy-rsa/easyrsa3/pki/private/ca.key:
```

`ca.crt` とクライアントの鍵はラズパイにアップロードする必要があるのでローカル環境にダウンロードしておきます。

```
$ cp pki/ca.crt ~
$ cp pki/issued/[client-name].crt ~
$ cp pki/private/[client-name].key ~

$ scp XXX.XXX.XXX.XXX:~/ca.crt ~/vpn/
$ scp XXX.XXX.XXX.XXX:~/ras.crt ~/vpn/
$ scp XXX.XXX.XXX.XXX:~/ras.key ~/vpn/
```

`/etc/openvpn/server.conf` を作成します。各パラメーターの詳しい説明は以下の記事を参考にしてください。
[OpenVPNのインストールとセットアップからインターネット接続までのガイドブック](http://qiita.com/noraworld/items/2fe6be489e1d93c748b8)

```:/etc/openvpn/server.conf
port   1194
proto  udp
dev    tun

ca          /etc/openvpn/keys/ca.crt
cert        /etc/openvpn/keys/server.crt
key         /etc/openvpn/keys/server.key
dh          /etc/openvpn/keys/dh.pem
crl-verify  /etc/openvpn/keys/crl.pem

ifconfig-pool-persist /etc/openvpn/ipp.txt

server 10.8.0.0 255.255.255.0

push "route 10.8.0.0 255.255.255.0"

client-to-client
keepalive 10 120
comp-lzo

user  nobody
group nogroup

persist-key
persist-tun

status      /var/log/openvpn-status.log
log         /var/log/openvpn.log
log-append  /var/log/openvpn.log

verb 3
```

`openvpn` コマンドで動作するか確認します。

```shell-session
$ sudo openvpn --config /etc/openvpn/server.conf
```

ログはこのようになります。

```:/var/log/syslog
Jul  3 01:13:09 vpn2 ntpd[1613]: Listen normally on 6 tun0 10.8.0.1:123
Jul  3 01:13:09 vpn2 ntpd[1613]: Listen normally on 7 tun0 [fe80::b142:7fdf:3c3c:7f79%3]:123
Jul  3 01:13:09 vpn2 ntpd[1613]: new interface(s) found: waking up resolver
```

`ifconfig` でIPが割り振られているか確認します。 `10.8.0.1` となっていますね。

```
$ ifconfig
tun0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  
          inet addr:10.8.0.1  P-t-P:10.8.0.2  Mask:255.255.255.255
          inet6 addr: fe80::b142:7fdf:3c3c:7f79/64 Scope:Link
          UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:3 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:100 
          RX bytes:0 (0.0 B)  TX bytes:144 (144.0 B)
```

`openvpn` での実行を止めて、サービスで起動させます。

```shell-session
$ sudo systemctl start openvpn@server
```

自動起動になっていないので有効にします。

```
sudo systemctl enable openvpn@server
```

VPNサーバの設定は以上です。

# Raspberry Pi3 を VPN サーバに接続する

OS は [Ubuntu MATE 16.04.2 LTS](https://ubuntu-mate.org/download/) を使用します。Mac を使ったインストール方法も書いておきます。

[ubuntu-mate-16.04.2-desktop-armhf-raspberry-pi.img.xz](https://ubuntu-mate.org/raspberry-pi/ubuntu-mate-16.04.2-desktop-armhf-raspberry-pi.img.xz) をダウンロードします。次に SDカードをアダプタなどを使い Mac の USB に刺します。

```
$ diskutil list
/dev/disk0 (internal, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *500.3 GB   disk0
   1:                        EFI EFI                     209.7 MB   disk0s1
   2:          Apple_CoreStorage Macintosh HD            499.4 GB   disk0s2
   3:                 Apple_Boot Recovery HD             650.0 MB   disk0s3

/dev/disk1 (internal, virtual):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:                  Apple_HFS Macintosh HD           +499.0 GB   disk1
                                 Logical Volume on disk0s2
                                 DF19241A-6A88-40A6-AC98-2B2929C13B2A
                                 Unencrypted

/dev/disk2 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *31.4 GB    disk2
   1:             Windows_FAT_32 PI_BOOT                 66.1 MB    disk2s1
   2:                      Linux                         31.4 GB    disk2s2
```

SDカードは `/dev/disk2` と認識されているようです。
`df` で `Mounted on` を確認し、unmount します。

```
$ df
Filesystem    512-blocks      Used Available Capacity iused      ifree %iused  Mounted on
/dev/disk1     974700800 735691904 238496896    76% 3494843 4291472436    0%   /
devfs                389       389         0   100%     674          0  100%   /dev
map -hosts             0         0         0   100%       0          0  100%   /net
map auto_home          0         0         0   100%       0          0  100%   /home
/dev/disk2s1      128912     42672     86240    34%     512          0  100%   /Volumes/PI_BOOT

$ sudo diskutil unmount "/Volumes/PI_BOOT"
```

イメージをSDカードにコピーします。このとき `/dev/rdisk2` と disk の前に r を入れます。また`bs=1m` も追加します。これをしないと何時間もかかってしまうので注意が必要です。

```
$ xzcat ~/Downloads/ubuntu-mate-16.04.2-desktop-armhf-raspberry-pi.img.xz | sudo dd of=/dev/rdisk2 bs=1m
```

`contrl+t` で進捗を確認できます。
終わったらSDカードをラズパイに刺せば Ubuntu MATE が起動します。

更新します。

```
$ sudo apt-get update
$ sudo apt-get upgrade
Reading package lists... Done
Building dependency tree       
Reading state information... Done
You might want to run 'apt-get -f install' to correct these.
The following packages have unmet dependencies:
 libc6-dev : Depends: libc6 (= 2.23-0ubuntu9) but 2.23-0ubuntu5 is installed
E: Unmet dependencies. Try using -f.
```

-f を使わないと更新ができないようです。

```
$ sudo apt-get -f upgrade
```

`openvpn` をインストールします。

```
$ sudo apt-get install openvpn
```

VPNサーバで作成した鍵をラズパイにアップロードし、 `/etc/openvpn/keys` に配置します。

```
$ scp ~/vpn/ca.crt XXX.XXX.XXX.XXX:~
$ scp ~/vpn/ras.crt XXX.XXX.XXX.XXX:~
$ scp ~/vpn/ras.key XXX.XXX.XXX.XXX:~
$ sudo mkdir /etc/openvpn/keys
$ sudo mv ca.crt /etc/openvpn/keys
$ sudo mv ras.crt /etc/openvpn/keys
$ sudo mv ras.key /etc/openvpn/keys
```

`/etc/openvpn/keys/pass` にクライアント鍵の phrase を入れておきます。

```
$ sudo vi /etc/openvpn/keys/pass
```

`/etc/openvpn/client.conf` にクライアントの設定を記載します。`XXX.XXX.XXX.XXX` はVPNサーバのIPアドレスを入れてください。

```:/etc/openvpn/client.conf
client

dev    tun
proto  udp

remote XXX.XXX.XXX.XXX 1194

resolv-retry  infinite
nobind

persist-key
persist-tun

ca      /etc/openvpn/keys/ca.crt
cert    /etc/openvpn/keys/ras.crt
key     /etc/openvpn/keys/ras.key
askpass /etc/openvpn/keys/pass

comp-lzo
verb 3
```

`openvpn` コマンドで接続できるか試します。

```
$ sudo openvpn --config /etc/openvpn/client.conf
```

別ウィンドウで `ifconfig` を実行してみます。 `10.8.0.6` とIPが割り振られたようです。

```
$ ifconfig
tun0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  
          inet addr:10.8.0.6  P-t-P:10.8.0.5  Mask:255.255.255.255
          UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1500  Metric:1
          RX packets:3 errors:0 dropped:0 overruns:0 frame:0
          TX packets:3 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:100 
          RX bytes:252 (252.0 B)  TX bytes:252 (252.0 B)
```

VPNサーバに `ping` が届くか確認します。
 
```
$ ping 10.8.0.1
PING 10.8.0.1 (10.8.0.1) 56(84) bytes of data.
64 bytes from 10.8.0.1: icmp_seq=1 ttl=64 time=68.8 ms
64 bytes from 10.8.0.1: icmp_seq=2 ttl=64 time=58.9 ms
```

逆にVPNサーバからラズパイに `ping` を打ってみます。

```
$ ping 10.8.0.6
PING 10.8.0.6 (10.8.0.6) 56(84) bytes of data.
64 bytes from 10.8.0.6: icmp_seq=1 ttl=64 time=63.5 ms
64 bytes from 10.8.0.6: icmp_seq=2 ttl=64 time=59.5 ms
```

問題がなさそうなので、`openvpn` の実行を終了し、サービスの起動と自動起動の設定を行います。

```
$ sudo service openvpn start
$ sudo systemctl enable openvpn
```

# ラズパイで Elasticsearch を動かす

オフィシャルのインストール方法はこちらに書いてあります。
[Install Elasticsearch with Debian Package](https://www.elastic.co/guide/en/elasticsearch/reference/current/deb.html)

インストールします。

```
$ wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
$ echo "deb https://artifacts.elastic.co/packages/5.x/apt stable main" | sudo tee -a /etc/apt/sources.list.d/elastic-5.x.list
$ sudo apt-get update
$ sudo apt-get install elasticsearch
$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.4.3.deb
$ sudo dpkg -i elasticsearch-5.4.3.deb
```

Elasticsearch は初期設定だとメモリを2GB使いますが、Raspberry Pi3 は1GBしか積んでいないので、jvm の設定を変更して512MBにします。

```:/etc/elasticsearch/jvm.options
#-Xms2g
#-Xmx2g
-Xms512m
-Xmx512m
```

外部のPCからアクセスできるように設定を追記します。

```:/etc/elasticsearch/elasticsearch.yml
network.host: 0.0.0.0
http.port: 9200
transport.host: localhost
transport.tcp.port: 9300
```

実行します。

```
$ sudo service elasticsearch start
```

起動には時間がかかります。ログを確認します。

```
$ tail -f /var/log/elasticsearch/elasticsearch.log
[2017-07-03T21:17:00,200][INFO ][o.e.n.Node               ] [d0hREp4] starting ...
[2017-07-03T21:17:04,266][INFO ][o.e.t.TransportService   ] [d0hREp4] publish_address {127.0.0.1:9300}, bound_addresses {127.0.0.1:9300}
[2017-07-03T21:17:04,471][WARN ][o.e.b.BootstrapChecks    ] [d0hREp4] system call filters failed to install; check the logs and fix your configuration or disable system call filters at your own risk
[2017-07-03T21:17:08,265][INFO ][o.e.c.s.ClusterService   ] [d0hREp4] new_master {d0hREp4}{d0hREp4RSZq1h-A-VUWbvw}{FSLOhsEiQfGR9w81wvmmgg}{localhost}{127.0.0.1:9300}, reason: zen-disco-elected-as-master ([0] nodes joined)
[2017-07-03T21:17:08,639][INFO ][o.e.h.n.Netty4HttpServerTransport] [d0hREp4] publish_address {10.0.1.30:9200}, bound_addresses {[::]:9200}
[2017-07-03T21:17:08,680][INFO ][o.e.n.Node               ] [d0hREp4] started
[2017-07-03T21:17:08,713][INFO ][o.e.g.GatewayService     ] [d0hREp4] recovered [0] indices into cluster_state
```

多くのログが吐かれますが、このようになれば起動しています。
自動起動の設定もしておきます。

```
$ sudo systemctl enable elasticsearch
```

動いているかローカルで確認してみます。

```
$ wget http://localhost:9200
$ cat index.html 
{
  "name" : "d0hREp4",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "QIfUBpUkTvm8NdxRmlQERg",
  "version" : {
    "number" : "5.4.3",
    "build_hash" : "eed30a8",
    "build_date" : "2017-06-22T00:34:03.743Z",
    "build_snapshot" : false,
    "lucene_version" : "6.5.1"
  },
  "tagline" : "You Know, for Search"
}
```

大丈夫なようなので、VPNサーバからも確認してみます。

```
$ wget http://10.8.0.6:9200
$ cat index.html 
{
  "name" : "d0hREp4",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "QIfUBpUkTvm8NdxRmlQERg",
  "version" : {
    "number" : "5.4.3",
    "build_hash" : "eed30a8",
    "build_date" : "2017-06-22T00:34:03.743Z",
    "build_snapshot" : false,
    "lucene_version" : "6.5.1"
  },
  "tagline" : "You Know, for Search"
}
```

# VPNサーバに nginx でプロキシサーバを立てる

さて、最後にプロキシサーバを立てます。これで外部からVPNサーバのパブリックなIPアドレスにアクセスすると、ラズパイにクエリが転送されるようになります。

nginx をインストールします。

```
$ sudo apt-get install nginx
```

プロキシサーバの設定を書きます。XXX.XXX.XXX.XXX はVPNサーバのIPアドレスを入れてください。

```:/etc/nginx/conf.d/ras.conf
server {
    server_name XXX.XXX.XXX.XXX;
    proxy_set_header Host $http_host;

    location / {
        proxy_pass http://10.8.0.6:9200;
    }
}
```

nginx を再起動します。

```
$ sudo service nginx restart
```

最後にブラウザでアクセスして動作確認します。
<img width="173" alt="elasticsearch.png" src="https://qiita-image-store.s3.amazonaws.com/0/33704/1a68c785-11fc-8645-9507-1eaff6d77ab7.png">

Elasticsearch on ラズパイの完成です。

# 性能

Elasticksearch へのデータのバルクインサートは、AWS の `t2.small.elasticsearch` で2分くらいかかっていたものがラズパイだと120分かかりました。インデックス作成がものすごく遅いのかな？search は2〜3倍遅いくらいなので実用はできています。

# おわりに

ラズパイをサーバにするのは面白いですね。電気代も月50円もかからないようですし、スペースも取らないのでいろいろ設置したくなります。こんなのとか楽しそう。

![vpn.png](https://qiita-image-store.s3.amazonaws.com/0/33704/fd820f72-8589-457b-44f3-b0ef5c2f7350.png)

あと、Raspberry Pi3 での設定方法を書きましたが、実際の運用は Asus Tinker board（CPU1.5倍速, メモリ2GB, ラズパイと同じ形なのでケースが使いまわせる） で行っています。ただこのボードはアメリカだと7,000円で買えるのですが、日本だと16,000円と割に合わないので Raspberry Pi3 の設定を書きました。

# Asus Tinker Board 発売

[Raspberry Pi3と同サイズで性能2倍のASUS製「Tinker Board」が国内発売](http://pc.watch.impress.co.jp/docs/news/1073926.html)
日本での公式な販売が決まったようです。

ラズパイと同様の手順で設定が可能ですが、OSがDebianなのでコマンドが少し変わります。

- `ifconfig` → `/sbin/ifconfig`
- `sudo service [サービス名] start` → `sudo systemctl start [サービス名]`

メモリが2GB搭載されているので Elasticsearch の使用メモリを1GBにします。

```:/etc/elasticsearch/jvm.options
#-Xms2g
#-Xmx2g
-Xms1g
-Xmx1g
```