【Folio LSP】チュートリアル Single Server Deployment（complete版）をEC2で

#Q3-2020に対応

##前置き
- この記事では、Folioのチュートリアルのうち、Single Server Deploymentを**EC2**で実行する場合の紹介をします。
- ここでは、**Complete版**をデプロイします。
- **Edge modulesは対象外としています。**
- Core版と比較すると以下が違ってきますが、この記事に沿って作っていけば大丈夫です。

    - cloneしてくるリポジトリとそれに伴うパスの変更
    - 必要なRAMの容量
    - runbookのスクリプトの種類（core版ではなくcomlete版を使う） 

:exclamation: 記事内のパスについては、エラーになったら適宜変更してください。

- 元ネタ https://github.com/folio-org/folio-install/tree/master/runbooks/single-server#folio-deployment-single-server

先に以下に目を通しておくと理解しやすいかと思います。
 
- Vagrant/VirtualBoxでSingle Server Deploymentチュートリアルを実行する(https://qiita.com/ayungn/items/f84e55893b7817af15a7)
- チュートリアルで出てくるCURLオプション(https://qiita.com/ayungn/items/f713f3a142f093a63e0e)

##凡例
:bulb:  参考情報
:exclamation: core版やVM版と手順が異なるところや間違いやすそうなところ

##Q&A
- 元ネタの中にmainbar/sidebarとありますが、mainbar/sidebarとは何ですか？
    - sidebarを実行すると「最新スナップショット」をもとにしたビルドができます。mainbarは四半期に一度のリリースをもとにビルドします。
- plartform-core/platform-completeとは何ですか？
    - platform-coreは基本機能、platform-comleteは全機能をビルドします。バックエンドモジュールの数でいうとplatform-coreは25、platform-completeは55です。この記事では、platform-coreのビルドについて説明します。

##大まかな流れ
1. EC2を準備する
2. EC2内に環境を整える(Okapi、Postgre、Java、Dockerエンジンなど)
3. Folioのテナント(diku)を作る
4. Stripesのビルド
4. テナントの使うモジュールのDockerをOkapi経由でpull
5. サンプルレコードの準備


##EC2の準備
:exclamation: 課金など自己責任で実行してください。

#####インスタンスを起動

<img width="70%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342082/e20c085a-2215-9d0c-0ba5-18b1e16db93e.png">


#####選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342082/930914f5-e881-41b2-9959-38df7c27a736.png)



#####インスタンスタイプはt2.2xlarge(8vCPU, 32GiB)を選択。
:exclamation: core版より大きなインスタンスが必要です


<img width="70%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342082/65e590e1-a5ec-231f-6497-66dab4b7c5dc.png">


#####「確認と作成」押下
<img width="10%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342082/2513cc8d-7535-96b4-e854-a94b98a390ed.png">

#####「起動」押下
<img width="8%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342082/8f13fa5a-146e-91c1-89be-942cf7e288e6.png">

#####ローカルからEC2にSSH接続をしたいので、キーペアを取得しておく。
任意のキーペア名を入力し、「キーペアのダウンロード」を押下して秘密鍵を取得する。
<img width="60%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342082/bc75c027-feeb-e5ee-5251-65c59b5330b5.png">

#####「インスタンスの生成」押下。
インスタンスが作成されるまでしばらく待つ。
<img width="15%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342082/53f97917-f0e5-37d4-ad55-ec944a0c49c6.png">

##インスタンスの確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342082/8aec2b85-53f2-abc2-3cb8-da5da0e60716.png)

パブリックIP、プライベートIPを確認します。
セキュリティグループはここからリンクしています。
:exclamation: パブリックIPはインスタンスをstopすると変わるので注意。Elastic IPを使って固定してもいいですが、課金体系にご注意ください。再起動の場合はパブリックIPは変わらないはずです。

##セキュリティグループの設定
#####ポートを開けます。
上記の「セキュリティグループ」から、
「インバウンドルールを編集」を押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342082/5971e95a-0066-3bc8-2171-8d3237e0775c.png)

#####「ルールを追加」を押下
以下のルールを記述（これだと開けすぎ。後日編集します）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342082/4fac602f-0397-a0ae-9348-ede3e8b78a0b.png)


#####「ルールを保存」を押下

##SSHでEC2に接続する

インスタンスの設定時に取得した秘密鍵とパブリックIPを利用し、お好きな方法で。
Win10 Powershellからなら、下記で接続できます。

```
ssh -i <保存した秘密鍵へのパス> ubuntu@<パブリックIP>
```


##Linuxホストのビルド

①適当なディレクトリに、このチュートリアル用のスクリプトなどが入っているリポジトリをクローンします。

```
git clone https://github.com/folio-org/folio-install
cd folio-install
git checkout q3-2020
cd runbooks/single-server
```

:exclamation:folio-install/runbooks/single-server/scripts/nginx-stripes-complete.confをnginx-stripes.confにコピーしておく。

```
cp ~/folio-install/runbooks/single-server/scripts/nginx-stripes-complete.conf ~/folio-install/runbooks/single-server/scripts/nginx-stripes.conf
```


## 必要なパッケージをインストールして構成する
#### 実行環境の要件: Java 11, nginx（エンジンエックス）, PostgreSQL 10, Docker

①apt cacheのアップデート

```
sudo apt-get update
```

②Java8とnginxをインストールし、Java11をシステムデフォルトにする

```
sudo apt-get -y install openjdk-11-jdk nginx
sudo update-java-alternatives --jre-headless --jre --set java-1.11.0-openjdk-amd64
```
[20201207] Java8 -> Java11

③PostgreSQLのキーをインポートし、PostgreSQL aptリポジトリを追加し、PostgreSQLをインストールする


[20201207]以下削除
~~sudo add-apt-repository "deb http://security.ubuntu.com/ubuntu xenial-security main"~~
~~sudo apt-get update~~
~~sudo apt-get install libicu55~~
~~wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -~~
~~sudo add-apt-repository "deb http://apt.postgresql.org/pub/repos/apt/ xenial-pgdg main"~~
~~sudo apt-get update~~
~~sudo apt-get -y install postgresql-10 postgresql-client-10 postgresql-contrib-10 libpq-dev~~


```
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
sudo add-apt-repository "deb http://apt.postgresql.org/pub/repos/apt/ focal-pgdg main"
sudo apt-get update
sudo apt-get -y install postgresql-10 postgresql-client-10 postgresql-contrib-10 libpq-dev

```

④Dockerから接続できるようにPostgreSQLを構成する。

- /etc/postgresql/10/main/postgresql.confの「Connection Settings」に`listen_addresses = '*'`を追記
- /etc/postgresql/10/main/postgresql.confの「max_connections」を増やす（例:500）[20201203追記]
- /etc/postgresql/10/main/pg_hba.confに`host all all 0.0.0.0/0 md5`を追記
- PostgreSQLを`sudo systemctl restart postgresql`でリスタート

```
sudo vim /etc/postgresql/10/main/postgresql.conf
sudo vim /etc/postgresql/10/main/pg_hba.conf
sudo systemctl restart postgresql
```
⑤DockerのキーをインポートしてDocker aptリポジトリを追加し、Dockerエンジンをインストールする

```
sudo apt-get -y install apt-transport-https ca-certificates gnupg-agent software-properties-common
wget --quiet -O - https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt-get update
sudo apt-get -y install docker-ce docker-ce-cli containerd.io
```
⑥Dockerエンジンの構成

```
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo cp ~/folio-install/runbooks/single-server/scripts/docker-opts.conf /etc/systemd/system/docker.service.d
sudo systemctl daemon-reload
sudo systemctl restart docker
```

⑦docker-composeのインストール

```
sudo curl -L \
  "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" \
  -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
```
[20201207] 1.26.2 -> 1.27.4


####ビルド要件のインストール：git, curl, NodeJS, npm, Yarn, libjson-perl, libwww-perl libuuid-tiny-perl

①Ubuntuのaptリポジトリからインストール

```
sudo apt-get -y install git curl nodejs npm libjson-perl libwww-perl libuuid-tiny-perl
```

②npmからnをインストール
※nというのは、nodeの管理コマンドです。

```
sudo npm install n -g
```

③Yarnのキーをインポートし、Yarn aptレジストリを追加し、Yarnをインストールする

```
wget --quiet -O - https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
sudo add-apt-repository "deb https://dl.yarnpkg.com/debian/ stable main"
sudo apt-get update
sudo apt-get -y install yarn
```

###Apache KafkaとApache ZooKeeperのインストール
※上記は[mod-subpub](https://github.com/folio-org/mod-pubsub)で必要です。
:bulb: Apache Kafka(http://kafka.apache.org/) とは

>分散ストリーミングプラットフォームです。「Pull型」「高スループット」などの特徴があり、ストリーミングデータパイプライン構築に利用できます。分散環境において「高スループット」かつ「低レイテンシ」で、大規模データを高速に取り込み配信できるメッセージングシステムです。
（引用元：https://www.ossnews.jp/oss_info/Apache_Kafka）

:bulb: Apache ZooKeeperとは

>大規模分散システムでよく利用される、設定情報の集中管理や名前付けなどのサービスを提供するソフトウェアである。
（引用元：https://ja.wikipedia.org/wiki/Apache_ZooKeeper）

`~/folio-install/runbooks/single-server/scripts/docker-compose-kafka-zk.yml`を修正する。

修正前 `KAFKA_ADVERTISED_LISTENERS: INTERNAL://10.0.2.15:9092,LOCAL://localhost:29092`
修正後 `KAFKA_ADVERTISED_LISTENERS: INTERNAL://<PRIVATE IP ADDRESS>:9092,LOCAL://localhost:29092`
:exclamation: \<PRIVATE IP ADDRESS>には、EC2インスタンスのプライベートIPアドレスを入れてください。

```
sudo mkdir /opt/kafka-zk
sudo cp ~/folio-install/runbooks/single-server/scripts/docker-compose-kafka-zk.yml /opt/kafka-zk/docker-compose.yml
cd /opt/kafka-zk
sudo docker-compose up -d
cd -
```

##データベースとロールの作成
PostgreSQLにスーパーユーザーでログイン

```
sudo su -c psql postgres postgres
```
OkapiとテナントのDBとロールを作成する。

```
CREATE ROLE okapi WITH PASSWORD 'okapi25' LOGIN CREATEDB;
CREATE DATABASE okapi WITH OWNER okapi;
CREATE ROLE folio WITH PASSWORD 'folio123' LOGIN SUPERUSER;
CREATE DATABASE folio WITH OWNER folio;
```

`\q` でpsqlを抜ける。

##Okapiのインストールと構成

```
wget --quiet -O - https://repository.folio.org/packages/debian/folio-apt-archive-key.asc | sudo apt-key add -
sudo add-apt-repository "deb https://repository.folio.org/packages/ubuntu focal/"
sudo apt-get update
sudo apt-get -y install okapi=4.7.2-1
sudo apt-mark hold okapi
```

[20200607 Okapiのバージョンを4.3.3-1から4.7.2-1に変更]
[20201203 Okapiのバージョンを3.1.2-1から4.3.3-1に変更]
[20201207 Ubuntu xenialからfocalに変更]
①Okapiの構成
`/etc/folio/okapi/okapi.conf`の以下を編集する

- role="dev"
- port_end="9230"
- host="\<PRIVATE IP ADDRESS>"
- storage="postgres"
- okapiurl="http://\<PRIVATE IP ADDRESS>:9130"
- ~~~postgres_host"\<PRIVATE IP ADDRESS>"~~~　[20201209追記→削除]

:exclamation: \<PRIVATE IP ADDRESS>には、EC2インスタンスのプライベートIPアドレスを入れてください。

②Okapiのリスタート

```
sudo systemctl daemon-reload
sudo systemctl restart okapi
```

③module descriptorsをセントラルレジストリからプル

```
curl -w '\n' -D - -X POST -H "Content-type: application/json" -d '{"urls":["https://folio-registry.dev.folio.org"]}'  http://localhost:9130/_/proxy/pull/modules     
```
:bulb: 渡したURLからModule Descriptorを取得しますが、このModule Descriptorはテナント（ここではdiku）独自のものではなく、共通のものです。 参照：https://github.com/folio-org/okapi/blob/master/doc/guide.md#module-descriptor-sharing
:bulb: /_/proxy/pull/modulesの詳細はOkapi Core API Documentationを参照: https://s3.amazonaws.com/foliodocs/api/okapi/okapi.html
:bulb: ModuleDescriptorのガイド: https://dev.folio.org/guides/module-descriptor/ 
※[Registory](https://dev.folio.org/guides/module-descriptor/#registry)に以下の記述があります

>As part of the continuous integration process, each ModuleDescriptor.json is published to the FOLIO Registry at https://folio-registry.aws.indexdata.com/


##Folioテナントを作成する

テナント初期化をOkapiにポストする

```
curl -w '\n' -D - -X POST -H "Content-type: application/json" -d '{"id" : "diku","name" : "Datalogisk Institut","description" : "Danish Library Technology Institute"}' http://localhost:9130/_/proxy/tenants
```
:bulb: これで「diku」というテナントがあることをOkapiが知るようになりました。

Okapiの内部モジュールをテナント用に有効化する。

```
curl -w '\n' -D - -X POST -H "Content-type: application/json" -d '{"id":"okapi"}' http://localhost:9130/_/proxy/tenants/diku/modules
```

##FolioStripesプラットフォームの最新版をビルドする

n <バージョン> で指定したNodeのバージョンに更新できます。
ここではltsに更新します。

```
sudo n lts
```

```
cd ~
git clone https://github.com/folio-org/platform-complete
cd platform-complete
git checkout q3-2020
yarn install
```

:bulb: yarnはnode.jsのパッケージマネージャです。
:bulb: .npmrcには`@folio:registry=https://repository.folio.org/repository/npm-folio/`の記述があります

`~/platform-complete/stripes.config.js`を以下のように修正する。

修正前：`okapi: { 'url':'http://localhost:9130', 'tenant':'diku' },`
修正後：`okapi: { 'url':'http://<YOUR PUBLIC IP>:9130', 'tenant':'diku' },`

:exclamation: \<YOUR PUBLIC IP>にはEC2インスタンスのパブリックIPを入力してください。
:exclamation:EC2を停止すると、パブリックIPが変更されます。
その場合、上記修正と以下のビルドをし直す必要があります。

```
NODE_ENV=production yarn build output
cd ..
```

##ウェブサーバを構成してStripes webpackをサーブする

nginxサーバを構成する。


`folio-install/runbooks/single-server/scripts/nginx-stripes.conf`の#Set pathの箇所を以下のように書き換える。
修正前：`/home/vagrant/platform-complete/output;`
修正後：`/home/ubuntu/platform-complete/output;`

`server_name` をパブリックIPv4 DNS名にする。[20201209追記]

```
sudo cp folio-install/runbooks/single-server/scripts/nginx-stripes.conf /etc/nginx/sites-available/stripes
sudo ln -s /etc/nginx/sites-available/stripes /etc/nginx/sites-enabled/stripes
sudo rm /etc/nginx/sites-enabled/default
sudo systemctl restart nginx
```


##対応するFolioバックエンドをデプロイしてテナントに有効にする

①　デプロイしたモジュールが利用するデータベースの情報をOkapiにポストする
:exclamation: \<PRIVATE IP ADDRESS>には、EC2インスタンスのプライベートIPアドレスを入れてください。

```
curl -w '\n' -D - -X POST -H "Content-Type: application/json" -d "{\"name\":\"DB_HOST\",\"value\":\"<PRIVATE IP ADDRESS>\"}" http://localhost:9130/_/env
curl -w '\n' -D - -X POST -H "Content-Type: application/json" -d "{\"name\":\"DB_PORT\",\"value\":\"5432\"}" http://localhost:9130/_/env
curl -w '\n' -D - -X POST -H "Content-Type: application/json" -d "{\"name\":\"DB_DATABASE\",\"value\":\"folio\"}" http://localhost:9130/_/env
curl -w '\n' -D - -X POST -H "Content-Type: application/json" -d "{\"name\":\"DB_USERNAME\",\"value\":\"folio\"}" http://localhost:9130/_/env
curl -w '\n' -D - -X POST -H "Content-Type: application/json" -d "{\"name\":\"DB_PASSWORD\",\"value\":\"folio123\"}" http://localhost:9130/_/env
```
[20201209追記]

```
curl -w '\n' -D - -X POST -H "Content-Type: application/json" -d "{\"name\":\"KAFKA_HOST\",\"value\":\"<PRIVATE IP ADDRESS>\"}" http://localhost:9130/_/env
curl -w '\n' -D - -X POST -H "Content-Type: application/json" -d "{\"name\":\"OKAPI_URL\",\"value\":\"http://<PRIVATE IP ADDRESS>:9130\"}" http://localhost:9130/_/env
```


②バックエンドモジュールのリストをポストして有効にする。テナントパラメータをセットして、サンプルデータと参照データをロードする。
※platform-completeは作業ディレクトリに応じて変更してください。これはPWDがhome/ubuntuの場合です。

```
curl -w '\n' -D - -X POST -H "Content-type: application/json" \
  -d @platform-complete/okapi-install.json \
  http://localhost:9130/_/proxy/tenants/diku/install?deploy=true\&preRelease=false\&tenantParameters=loadSample%3Dtrue%2CloadReference%3Dtrue

```
注意：Docker HubからDocker imageを持ってくるため、これは時間がかかります。
進捗を見る→Okapi log at /var/log/folio/okapi/okapi.log または sudo docker ps | grep -v "^CONTAINER" | wc -l

:exclamation: no space leftとなる場合は、まずは再起動を試してみてください。それでもだめならボリュームを追加するなど、適宜対応してください。

③Stripesモジュールのリストをポストして有効にする
※platform-completeは作業ディレクトリに応じて変更してください。これはPWDがhome/ubuntuの場合です。

```
curl -w '\n' -D - -X POST -H "Content-type: application/json" \
  -d @platform-complete/stripes-install.json \
  http://localhost:9130/_/proxy/tenants/diku/install?preRelease=false
```
## Folioのスーパーユーザを作成し、パーミッションをロードする
```
perl folio-install/runbooks/single-server/scripts/bootstrap-superuser.pl --tenant diku --user diku_admin --password admin --okapi http://localhost:9130
```
done!と表示されればOK。

##サンプルデータのロード

### MODSレコードをロードする
inventory用のサンプルデータをロードします。サンプルデータは https://github.com/folio-org/folio-install/tree/master/runbooks/single-server/sample-data/mod-inventory にあります。

```
curl -w '\n' -D - -X POST -H "Content-type: application/json" -H "Accept: application/json" -H "X-Okapi-Tenant: diku" -d '{"username":"diku_admin","password":"admin"}' http://localhost:9130/authn/login
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342082/d7b6cc89-64c1-bc20-02d9-4a1d1c960c9a.png)


下記の\<okapi token>には、返ってきたtokenの値を入れる。

※`./folio-install/runbooks/single-server`はディレクトリに応じて変更してください。これはPWDがhome/ubuntuの場合です。

```
for i in ./folio-install/runbooks/single-server/sample-data/mod-inventory/*.xml; do curl -w '\n' -D - -X POST -H "Content-type: multipart/form-data" -H "X-Okapi-Tenant: diku" -H "X-Okapi-Token: <okapi token>" -F upload=@${i} http://localhost:9130/inventory/ingest/mods; done
```
EC2のパブリックIPにブラウザからアクセス
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342082/15d66682-55b9-4359-992e-bafa409e926b.png)

diku_admin/adminでログイン

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342082/188ffca7-c462-0c3c-5f64-ea9f7de7d5f9.png)

#Appendix
CertbotでSSLに対応する場合
https://certbot.eff.org/lets-encrypt/ubuntufocal-nginx

nginx-stripes.confを書き換える。

```
server {
  listen 80;
  server_name ドメイン名;
  charset utf-8;
  # Serve index.html for any request not found
  location / {
    # Set path
    root /home/ubuntu/platform-complete/output;
    include mime.types;
    types {
      text/plain lock;
    }
    try_files $uri /index.html;
 }
}
#General HTTP to HTTPS
server {
        listen 80;
        listen [::]:80;
        server_name ドメイン名;
        location / {
                return 302 https://$host$request_uri;
        }
}
server {
        listen 443 ssl;
        ssl_certificate  /etc/letsencrypt/live/ドメイン名/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/ドメイン名/privkey.pem;
        server_name ドメイン名 default_server;
        #ssl on;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        location / {
                # Set path
                root /home/ubuntu/platform-complete/output;
                include mime.types;
                types {
                        text/plain lock;
                }
                try_files $uri /index.html;
        }
        location /okapi {
                rewrite ^/okapi/(.*) /$1 break;
                proxy_pass http://localhost:9130;
        }
```

stripes.config.jsは以下の内容にする

```
okapi: { 'url':'https://ドメイン名/okapi', 'tenant':'diku' },
```

書き換えたら忘れずビルド

```
NODE_ENV=production yarn build output
```