CaaSって高いじゃん！IaaSでしょ！

この記事は、[Yahoo! JAPAN 18 新卒 2 Advent Calendar 2018](https://adventar.org/calendars/3419)の4日目の記事です。
Yahoo! JAPAN の新卒1年目が有志で行っているアドベントカレンダーです。
次回は[@orleika](https://qiita.com/orleika) さんによる「[アセンブリでQuine](https://qiita.com//orleika/items/de929566c26626ecc802)」です。

# 注意
この記事では決して`CaaS`を否定している訳ではありません。
商用や開発環境ということに関しては言えば、とても適していると思います。
ただ今回の条件では`IaaS`（`VPSサーバ`）が適しているというだけです。

# 前提
**個人** が **学習用途** 込みで複数のサイトを公開することを想定しています。
連続稼働を必要としていますが、大きな責任は伴わない状況です。
責任が伴う場合は、サーバの管理を他に任せることのできる環境（`CaaS`や`PaaS`など）を選択してください。

## 目的
- 複数のパソコンで開発が行える環境が欲しい
- サーバの機能を後々追加するかもしれない
- 自分がこれまでしてきたアウトプットをまとめたサイトが欲しい
- 今後公開するサイトが増える可能性がある
- サイトを公開するため連続稼働を望む

これらの目的を達成するため、次の構成で作成します。

## 作成するもの
- Git Server
- リバースプロキシサーバ（nginx）
- Node.jsアプリ（サイト公開の準備のみ）

![無題.png](https://qiita-image-store.s3.amazonaws.com/0/183540/9828fe98-8f9b-1153-3305-22de00283a81.png)

## 準備するもの
- VPSサーバ（以下で言及）
- ドメイン（本記事ではサンプルとして`walk8243.com`を使用）

サーバを**IaaS**、それも**VPS**にした理由を考えてみましょう。

# サーバの比較
![Book1.jpg](https://qiita-image-store.s3.amazonaws.com/0/183540/39da186d-7ea6-0fde-5973-69d5c3a0be4b.jpeg)

## IaaSとCaaSの比較
### IaaS
- メリット
    - ディスク容量が許す限り機能の追加は可能
    - 機能の設定が自由
- デメリット
    - 機能の追加を自分でする必要がある（マネジメントシステムにより補助が可能）
    - 追加した機能の管理をしなければならない

### CaaS
- メリット
    - 多数のコンテナイメージが用意されている
    - コンテナイメージにより機能の設定が簡単
    - 機能の管理はコンテナイメージの更新で可能
- デメリット
    - 機能追加がコンテナに引っ張られる

## VPSとクラウドの比較
### VPS
- メリット
    - 値段が安い
    - サーバにグローバルIPアドレスが付いてくる
    - 料金は前払い
- デメリット
    - サーバのディスク容量やメモリサイズを変更するときは契約のし直し

### クラウド
- メリット
    - ディスク容量やメモリサイズを簡単に変更できる
    - サービスが多種多様
    - オートスケーラを付ければ自動でサイズ変更してくれる
    - 料金は使った分だけで後払い
- デメリット
    - 値段は割高
    - サーバとグローバルIPアドレスは別サービス

## VPSを選んだ理由
- グローバルIPアドレスが必須
- サーバがダウンしてもすぐに復旧すれば大丈夫
- 機能を自分で追加・設定したい（学習のため）
- なるべく安く済ませたい

では実際に手を動かして、環境を作っていきましょう。

# サーバセットアップ
借りたサーバは次のものです。
https://www.onamae.com/server/vps/

- サーバ管理会社  
    お名前.com
- メモリ  
    1GB
- CPU  
    2コア
- HDD  
    100GB

## OSセットアップ
デフォルトでインストールされているものが `CentOS6.5` です。
それをそのまま使いました。

# Gitサーバ
この後作成するNode.jsアプリはセキュリティを考えると、ソースコードの公開はするべきではありません。
複数のパソコンで開発が行える環境を整えるために、Gitサーバを作成しましょう。
詳しくは、以前書いた[この記事](https://qiita.com/walk8243/items/1f003ea07d9b0cc5bc92)をご覧ください。
使用するユーザは `root`(スーパーユーザ) と `git`(gitサーバ用) です。

```.
# gitをインストール
[root@Server:~]# yum install git

# リポジトリを作成
[git@Server:~]# mkdir sample.git
[git@Server:~]# git init sample.git --bare --shared

# リポジトリをクローン
[Client:~]# git clone git@{ServerIP}:sample.git
```

ざっと流すとこんな感じです。
詳しくは、以前書いた[この記事](https://qiita.com/walk8243/items/1f003ea07d9b0cc5bc92)をご覧ください（再び）。

# リバースプロキシサーバ
今回、一つのサーバ内に複数のサイトを公開する予定です。
そのサイトはNode.jsを使って作成するつもりです。
そのため、それぞれのNode.jsアプリへリクエストを振り分けるためにリバースプロキシサーバを作成することとしました。
使用実績、ドキュメント量、設定ファイルで構成できる、リクエストの処理方法というところを総合的に考えて `apache` と `nginx` の2択としました。
駆動方法の違いから、使用するものは `nginx` にしました。

両者の比較は[ApacheとNginxについて比較](https://qiita.com/kamihork/items/49e2a363da7d840a4149)を参考にしてください。

以下はデフォルト設定でのインストール方法です。

```.sh
# nginx最新版（2018/11/19現在）のダウンロード
cd /usr/local/src
wget https://nginx.org/download/nginx-1.15.6.tar.gz
tar -xf nginx-1.15.6.tar.gz
cd nginx-1.15.6

# nginxのインストール
./configure
ln -s /usr/local/bin/nginx /usr/local/nginx/sbin/nginx
which nginx

# nginxのService登録
vi /etc/init.d/nginx # サンプルコードに内容記載
chmod +x /etc/init.d/nginx
chkconfig --add nginx
chkconfig --list | grep nginx
chkconfig nginx on
service nginx start
ps aux | grep nginx
```

これから作成する複数のWebアプリを振り分けられるようになりました。
設定ファイルを編集して、自由にリクエストの振り分けを行ってみてください。
[サンプルコード](#nginx/conf/nginx.conf)

## Tips
### キャッシュ
キャッシュは通過する全ての箇所で設定されている有効期限のうち、最短のものが適用されます。
キャッシュが動作しない場合、それぞれのサーバ間でのリクエスト・レスポンスのヘッダーも確認してください。

### リダイレクト
`return`を使うことでリダイレクトさせることができます。
そのとき、HTTPステータスコードとURLを指定できます。
リダイレクトさせずにリクエストを投げる場合は、`proxy_pass`を使用してください。

# Node.jsアプリ
Nodeのインストールは `nvm` を用いました。
https://github.com/creationix/nvm

```.sh
# nvmのインストール
cd /usr/local/src
wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
nvm --version

# nodeのインストールと確認
nvm install lts/dubnium
node -v
npm -v
```

サーバサイドのソースコードは、セキュリティリスクを伴うため不特定多数の人が見れる環境にあることはよくありません。
なので、先ほど作成したGitサーバでソースコードを管理しましょう。
サイトを公開するための環境を作ることが目的なので、この章は軽く流します。

## 個人サイト
GitHubやQiitaをまとめたサイトとして作りました。
そのため、このサイトではGitHubやQiitaのAPIを使っています。
また、先ほど作成したリバースプロキシを用いて、テスト環境も作成しています。

### GitHub API v3
https://developer.github.com/v3/
[サンプルコード](#GitHub API - Node.js)

API使用に必要なトークンは、[ココ](https://github.com/settings/tokens)から取得してください。

### Qiita API v2
https://qiita.com/api/v2/docs
[サンプルコード](#Qiita API - Node.js)

## 外部サイト
現状所属しているコミュニティのサイトを運営できないかなと思い、そのための環境を作っていました。
Expressを使って、テンプレートエンジンはtwigを指定しました。
現状はnginxで作成したリバースプロキシが正常に稼働しているかの確認のために使用しています。

### Express のアプリケーション生成プログラム
http://expressjs.com/ja/starter/generator.html

```.sh
# 初期設定
git init
npm init --yes

# Expressアプリの生成
npm i express
npm install express-generator -g
express -v ejs -f

# git commit
git add .
git commit -m "app init"
```

# まとめ
したかったことは、
・自分がこれまでしてきたアウトプットをまとめたサイトが欲しい
・今後どこかのコミュニティのサイトを運営できればいいなぁ
・ある程度安定し継続して遊べる外部とつながっている環境が欲しい
ということです。
複数のサイト（Webアプリ）を持ち、継続的な環境ということで、今回はVPSサーバを選びました。
1つ借りるだけで、色々な機能を持たせられるというのは、とても懐に優しいですね。

最初にも述べた通り、上のような条件であっても、ビジネスとして捉える場合、様々な責任が伴ってくると思います。
何かあってからでは遅いです。
保障に入ると思って、[Azure](https://azure.microsoft.com/ja-jp/)なり、[AWS](https://aws.amazon.com/jp/)なり、[Google Cloud Platform](https://cloud.google.com/)なりを使いましょう。

適材適所ということで、これからも色々使ってみて、使いどころや自分に合う合わないを見極めていきたいと思います。

# サンプルコード
## nginx/conf/nginx.conf
```nginx.conf
worker_processes  1;

events {
  worker_connections  1024;
}

http {
  include                 mime.types;
  default_type            application/octet-stream;
  keepalive_timeout       65;
  proxy_redirect          off;
  proxy_connect_timeout   3s;
  proxy_cache_path        /usr/local/nginx/cache keys_zone=mycache:10m max_size=128m inactive=24h;
  proxy_temp_path         /usr/local/nginx/cache_tmp;
  proxy_cache_valid       200 302 10m;
  server_name_in_redirect on;
  ssl_certificate         /usr/local/nginx/conf/cert.pem;
  ssl_certificate_key     /usr/local/nginx/conf/key.pem;
  ssl_protocols           TLSv1.2 TLSv1.3;
  ssl_session_cache       builtin:1000 shared:SSL:10m;
  add_header              X-Nginx-Cache $upstream_cache_status;
  proxy_buffering         on;

  # walk8243.com（本番環境）
  server {
    listen                80;
    listen                443 ssl;
    server_name           walk8243.com;
    return 301 https://www.walk8243.com;
  }
  server {
    listen                80;
    server_name           www.walk8243.com;
    return 301 https://www.walk8243.com;
  }
  server {
    listen                443 ssl;
    server_name           www.walk8243.com;
    proxy_cache           mycache;

    location / {
      proxy_pass  http://localhost:8080;
    }
  }

  # dev.walk8243.com（開発環境）
  server {
    listen                80;
    server_name           dev.walk8243.com;

    location / {
      proxy_pass  http://localhost:3000;
    }
  }
}
```

## /etc/init.d/nginx
```nginx
#!/bin/sh
# chkconfig: 2345 99 01
# description: nginx

. /etc/init.d/functions

case "$1" in
  start)
    /usr/local/bin/nginx
    ;;
  stop)
    /usr/local/bin/nginx -s quit
    ;;
  status)
    ;;
  restart)
    /usr/local/bin/nginx -s stop
    /usr/local/bin/nginx
    ;;
  reload)
    /usr/local/bin/nginx -s reload
    ;;
  *)
    ;;
esac
```

## GitHub API - Node.js
```index.js
const express = require('express');
const request = require('request');
const router = express.Router();

const githubToken = 'xxxxxxxxxx';

router
  .get('/', (req, res, next) => {
    const options = {
      url: 'https://api.github.com',
      headers: {
        Authorization: `token ${githubToken}`,
        'User-Agent': 'request'
      },
    };
    request(options, (error, response, body) => {
      if(error) return reject(error);
      res.send(response.statusCode, JSON.parse(body));
    });
  });
```

## Qiita API - Node.js
```index.js
const express = require('express');
const request = require('request');
const router = express.Router();

const paging = 1;
const perPage = 5;
const username = 'walk8243';

router
  .get('/', (req, res, next) => {
    const options = {
      url: `https://qiita.com/api/v2/items?page=${paging}&per_page=${perPage}&query=user:${username}`,
    };
    request(options, (error, response, body) => {
      if(error) return reject(error);
      res.send(response.statusCode, JSON.parse(body));
    });
  });
```