VESPAをAWS EC2で動かしてみる

# 記事の目的

![VESPA](http://static.tumblr.com/f842123824d1c9bab74b7b1108c67854/gj4rohd/zzEp28f8h/tumblr_static_el760p2adjscg00s40kc4kk44.png)

VESPAは検索エンジンを中心とした、高速に配信できるOSSの[ビッグデータサービングエンジン](https://techblog.yahoo.co.jp/oss/vespa_oss/)です。

内容は[公式チュートリアル](http://docs.vespa.ai/documentation/vespa-quick-start-multinode-aws.html)をAWS上で実行したもので、VESPAのインストール手順とクラスタの概要の把握を目的としたものです

### アーキテクチャ
![Vespa overview](http://docs.vespa.ai/documentation/img/overview/vespa-overview.png)

|クラスタの種類 | 役割 |
|:-----------|:------------|
|Admin/Config|他のClusterの管理|
| (Stateless)Container Cluster|検索、ドキュメントプロセス、|
| Content Cluster| データのストア、クエリの分散実行|

## チュートリアルの概要

- AWS/EC2にVESPAのクラスタを構築する
- クラスタは4台で構成
 - Admin/Config １台(Container Clusterを兼ねる)
 - (Stateless)Container Cluster 2台
 - Content Cluster 2台

# 前提

## 作業環境
- AWSのアカウント

## 記事の前提
- AWSのプライベートDNSは、今回のチュートリアル時のものなので毎回変わるはず
- AWSのパブリックDNSは、一部をマスク化

# EC2インスタンスの起動
AWS上でのセットアップした際の設定をまとめています

EC2インスタンスの起動の前にSecurity Groupの設定をし、リモートからアクセスできるようにします。そのあとにEC2インスタンスを起動します

## Security Group

vespa_tutorial_sgというSecurity Groupを作成しinboundを二行追加します

|タイプ | ポート範囲 | ソース ||
|:-----------|:------------|:------------|:------------|
|カスタム TCP ルール| 8080|マイIP|データ取得用|
| SSH|22|AWS Marketplace|管理用|
|すべてのTCP| 0 - 65535|vespa_tutorial_sg|クラスタ間の内部通信|

### EC2

EC2の主な設定です
表に記載されていない項目はデフォルトです

|項目 | 設定値 | 補足 |
|:-----------|:------------|:------------|
|リージョン| オレゴン|
| AMI|CentOS 7 (x86_64) （ ami-0ebdd976 )|AWS Marketplace|
|インスタンスタイプ| t2.medium|$0.046/時|
|台数|4||
|EBS| 8 GiB SSD||
|Security Group|vespa_tutorial_sg ||

### クラスタ情報

構築したクラスタは、Admin/Config１台と (Stateless)Container Cluster２台、 Content Cluster 2台です

|No | Role | AWSのプライベートDNS |
|:-----------|:------------|:------------|
|1| admin0,stateless0|ip-172-31-17-44.us-west-2.compute.internal|
|2| stateless1|ip-172-31-30-196.us-west-2.compute.internal|
|3| content0|ip-172-31-21-68.us-west-2.compute.internal|
|4| content1|ip-172-31-20-216.us-west-2.compute.internal|

# VESPAのインストール

EC2のクラスタ起動時に取得した秘密鍵を使ってNo1インスタンスにログインをします
No1インスタンスからNo1-4インスタンスにVESPAをインストールします
## EC2インスタンスにログイン

sshを利用してNo.1にログイン

```bash
ssh-add my-aws-key.pem
ssh -A centos@<administration-node-public-dns>
```

## hostsの設定
#1-#4インスタンスのAWSのプライベートDNS をhosts.txtに記載する

```txt:hosts.txt
$cat hosts.txt
ip-172-31-17-44.us-west-2.compute.internal
ip-172-31-30-196.us-west-2.compute.internal
ip-172-31-21-68.us-west-2.compute.internal
ip-172-31-20-216.us-west-2.compute.internal
```

## aws用のVESPA bootstrapスクリプトをダウンロード

VESPA bootstrapスクリプトをダウンロードをNo1インスタンスにダウンロードし、そのあとNo1-4のインスタンスにコピーをしています

```
$ curl -s https://raw.githubusercontent.com/vespa-engine/sample-apps/master/aws_bootstrap.sh \
  > aws_bootstrap.sh
$ for host in $(cat hosts.txt); do \
  scp aws_bootstrap.sh $host:.; done
```

※ログインしていないサーバにscpをするとエラーになる場合は事前にsshしてください

## bootstrapスクリプトの実行
No1-4 のサーバで、bootstrapスクリプトを実行してVESPAをインストールします

```
$ for host in $(cat hosts.txt); do \
  (ssh $host "sudo bash aws_bootstrap.sh $(head -1 hosts.txt)" 2>&1 | tee /tmp/aws_bootstrap_$host.log) & done; \
  wait; \
  echo "Bootstrap done"

```

※なぜか２回目で成功しました

<details><summary>インストールログ</summary><div>

```txt:/tmp/aws_bootstrap_ip-172-31-17-44.us-west-2.compute.internal.log
Installing Vespa
読み込んだプラグイン:fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirror.sigmanet.com
 * epel: mirrors.cat.pdx.edu
 * extras: mirrors.cat.pdx.edu
 * updates: centos.sonn.com
パッケージ yum-utils-1.1.31-42.el7.noarch はインストール済みか最新バージョンです
依存性の解決をしています
--> トランザクションの確認を実行しています。
---> パッケージ epel-release.noarch 0:7-9 を 更新
---> パッケージ epel-release.noarch 0:7-11 を アップデート
--> 依存性解決を終了しました。

依存性を解決しました

================================================================================
 Package                アーキテクチャー バージョン        リポジトリー    容量
================================================================================
更新します:
 epel-release           noarch           7-11              epel            15 k

トランザクションの要約
================================================================================
更新  1 パッケージ

総ダウンロード容量: 15 k
Downloading packages:
Delta RPMs disabled because /usr/bin/applydeltarpm not installed.
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  更新します              : epel-release-7-11.noarch                        1/2 
  整理中                  : epel-release-7-9.noarch                         2/2 
  検証中                  : epel-release-7-11.noarch                        1/2 
  検証中                  : epel-release-7-9.noarch                         2/2 

更新:
  epel-release.noarch 0:7-11                                                    

完了しました!
読み込んだプラグイン:fastestmirror
adding repo from: https://copr.fedorainfracloud.org/coprs/g/vespa/vespa/repo/epel-7/group_vespa-vespa-epel-7.repo
grabbing file https://copr.fedorainfracloud.org/coprs/g/vespa/vespa/repo/epel-7/group_vespa-vespa-epel-7.repo to /etc/yum.repos.d/group_vespa-vespa-epel-7.repo
repo saved to /etc/yum.repos.d/group_vespa-vespa-epel-7.repo
読み込んだプラグイン:fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirror.sigmanet.com
 * epel: s3-mirror-us-west-2.fedoraproject.org
 * extras: mirrors.cat.pdx.edu
 * updates: centos.sonn.com
パッケージ vespa-6.229.8-1.el7.centos.x86_64 はインストール済みか最新バージョンです
パッケージ 32:bind-utils-9.9.4-51.el7_4.2.x86_64 はインストール済みか最新バージョンです
パッケージ git-1.8.3.1-12.el7_4.x86_64 はインストール済みか最新バージョンです
何もしません
Setting hostname to fqdn 'ip-172-31-17-44.us-west-2.compute.internal'
Setting VESPA_CONFIGSERVERS=ip-172-31-17-44.us-west-2.compute.internal
Starting vespa configuration server 
Redirecting to /bin/systemctl start vespa-configserver.service
Starting vespa services
Redirecting to /bin/systemctl start vespa.service
```
</div>
</details>

## src/main/application/hosts.xml の設定
hosts.xml はインスタンスごとにサービスを設定します
hostnameをservices.xml に書き換える

```xml:src/main/application/hosts.xml 
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
<hosts>
  <host name="ip-172-31-17-44.us-west-2.compute.internal">
    <alias>admin0</alias>
    <alias>stateless0</alias>
  </host>

  <host name="ip-172-31-30-196.us-west-2.compute.internal">
    <alias>stateless1</alias>
  </host>

  <host name="ip-172-31-21-68.us-west-2.compute.internal">
    <alias>content0</alias>
  </host>

  <host name="ip-172-31-20-216.us-west-2.compute.internal">
    <alias>content1</alias>
  </host>
</hosts>
```

## src/main/application/service.xml  の設定
インスタンスとホストをマッピングします

```xml:src/main/application/service.xml
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
<services version="1.0">
  <admin version="2.0">
    <adminserver hostalias="admin0"/>
    <configservers>
      <configserver hostalias="admin0"/>
    </configservers>
  </admin>

  <container id="container" version="1.0">
    <document-api/>
    <search/>
    <nodes>
      <node hostalias="stateless0"/>
      <node hostalias="stateless1"/>
    </nodes>
  </container>

  <content id="music" version="1.0">
    <redundancy>2</redundancy>
    <documents>
      <document type="music" mode="index"/>
    </documents>
    <nodes>
      <node hostalias="content0" distribution-key="0"/>
      <node hostalias="content1" distribution-key="1"/>
    </nodes>
  </content>
</services>
```

#アプリケーションを デプロイ
VESPAのアプリケーションは、デプロイ後にリスタートは不要です

```
vespa-deploy prepare src/main/application && vespa-deploy activate
```

##　アプリケーションの確認
デプロイが終わりアプリケーションが実行可能になったかは、ApplicationStatus のREST APIのレスポンスコードでわかります
レスポンスコードが200になるまで確認してください

```
$ curl -s --head http://localhost:8080/ApplicationStatus
HTTP/1.1 200 OK
Date: Fri, 13 Apr 2018 13:44:08 GMT
Content-Type: application/json
Transfer-Encoding: chunked
```

# クラスタの検証
vespa-get-cluster-state　コマンドで systemの状況を検証できます
up以外に、down,initializing,stopping,Maintenance,Retiredのstateがあります
以下はすべてのnodeが起動しています

```
$ vespa-get-cluster-state

Cluster music:
music/distributor/0: up
music/distributor/1: up
music/storage/0: up
music/storage/1: up
```

# データの取得、クエリの実行

## データの取得
ドキュメントはREST形式で取得できます。
取得したデータはJSON形式です

```
$ curl -s -H "Content-Type:application/json" --data-binary @music-data-1.json \
>     http://localhost:8080/document/v1/music/music/docid/1 | python -m json.tool
{
    "id": "id:music:music::1",
    "pathId": "/document/v1/music/music/docid/1"
}
```

## 検索

以下の例は、リモートサーバから実行しています
(Stateless)Container ClusterのグローバルIPを指定してください

```
curl  http://ec2-54-187-xx-xxx.us-west-2.compute.amazonaws.com:8080/search/?query=bad
{
  "root": {
    "id": "toplevel",
    "relevance": 1,
    "fields": {
      "totalCount": 2
    },
    "coverage": {
      "coverage": 100,
      "documents": 2,
      "full": true,
      "nodes": 2,
      "results": 1,
      "resultsFull": 1
    },
    "children": [
      {
        "id": "id:music:music::1",
        "relevance": 0.254574922399675,
        "source": "music",
        "fields": {
          "sddocname": "music",
          "artist": "Michael Jackson",
          "title": "Bad",
          "duration": 247,
          "year": 1987,
          "documentid": "id:music:music::1"
        }
      },
      {
        "id": "id:music:music::2",
        "relevance": 0.05447959677335429,
        "source": "music",
        "fields": {
          "sddocname": "music",
          "artist": "Eminem",
          "title": "So Bad",
          "year": 2010,
          "documentid": "id:music:music::2"
        }
      }
    ]
  }
}
```

# まとめ
tensorFlowとの連携までやりたい