サーバー

## 自己紹介
ひつじです。あまり目に見えるtechな部分では貢献できませんでしたが、主にサーバー管理と一部フロントエンドを担当しました。
ハッカソンで初めてNext.jsを触りましたが、（というか、React含めプレーンじゃないjsを初めて触りましたが）これマジ便利。今後使っていこうと思います。

## はじめに...
私の所属するプログラミング系課題活動団体にて、株式会社pixiv様、およびOBの先輩方のご協力のもとハッカソンが行われました。pixiv様、OBの先輩方、ご協力等本当にありがとうございました。
さて、このハッカソンで私たちは「マチケンナンバ」という模擬店向けの待ち時間がすぐにわかるWebアプリを開発しました！
詳しい話はこちらの記事を参照していただければと思います！

https://qiita.com/shima14142/items/de1c328d233a6248518a

~~約2週間の命を削り~~ 何とか開発を終え、なんと！pixiv様より**最優秀賞**、OBの先輩方より**優秀賞**をいただくことができました！

ほんとチームメンバーの先輩方には感謝です。寝て起きたらなんか新しいの実装終わってるし...マジで僕なんもしてない...ほんと怖いですよ、いい意味で。また、初学者の僕に手取り足取りNext.jsから教えていただきました...先輩方一人でも欠けてたら何にもできなかったです。改めまして、本当にありがとうございます！

https://machiken-number.com/

## サーバーサイド
マチケンナンバで私がメインで担当したのがこちら！サーバー管理です。
### 概要
クライアントからのアクセスをazure上のnginxプロキシを通してtailnetでつながっている別のVPSに飛ばして運用しています。VPS上ではdockerやscreenを使ってフロントエンド(Next.js)、バックエンド(Nestjs)、データベース(MariaDB)を運用しています。
なんのこっちゃわからんと思うので、下で細かく語ります。
### 細かい話
#### サーバー構成
まず、使ったサーバーは全部で２つ。

1つ目が某Microsoft様のAzureです。学校から Azure for Students のアカウントをもらっているので、~~タダで使える~~活用させていただきました。
OSは ubuntu で今回は nginxプロキシ に使っています。

2つ目が個人的に借りていたVPS（レンタルサーバー）です。学割利かせるとなんと、月2500円程度で24GB ram のサーバーをレンタルできてしまっています！最高ですね。
OSは arch linux でメインのプログラムたちを動かすのに使っています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957354/f3351044-f541-b9b9-a119-61ba8ff8f199.png)
さて、まとめるとだいたいこんな構成です。

グローバルに公開されているのはAzureだけで、それ以外はVPN(Tailscale)でつながっています。
つまり、すべての通信はAzureを通して行われ、ポート開放されているのはAzureだけ、ってこと。
つまりつまり、ほぼLAN環境でアプリ運用ができるってこと。（LANの特定のIPをグローバルからアクセスできるようにしているだけ）
APIとかもすべてVPN内でしかアクセスできないので、セキュリティ的に安全です。（100%ではありませんが...）

#### nginxプロキシを使った話
nginx使いました。便利ですよねぇ...
nginx普段から使っていらっしゃる方は...次へどうぞ。
設定は主にazure上に建てたnginxで行いました。
**そもそもなぜnginx経由で通信するのか。**
答えはVPSのポート開放が難しかったから。
 ~~いやー、借りてるところにお願いしたら好きなところ開けてもらえるっぽいんですけど、いろいろと融通利かせてもらっててお世話になってるのでちょっと申し訳なくて...あとtechなことしたいよねって...~~ 

あとはazureさんのほうがセキュリティ的に安心かなぁと思いまして。

#### では早速設定します。

まずはnginxと諸々の環境を入れますが、割愛（コマンドだけ下に記載）
<details><summary>コマンドたち</summary>

arch linuxなのでpacmanってやつを使います。~~APIのテストで使ったPOSTMANと混じったなんて言わない。~~
```shell-session
$ sudo pacman -Syyu # アップデート
$ sudo pacman -S nginx # nginxをインスコ
```
もしubuntuとかなら
```shell-session
$ sudo apt update && sudo apt upgrade -y # アップデート
$ sudo apt install nginx # nginxをインスコ
```
だし、その辺はご自身の環境に合わせて...

</details>

まずはAzureに来た通信をVPSに飛ばすように設定をします。
```nginx:/etc/nginx/conf.d/server.conf
server {
    listen 80;
    server_name machiken-number.com; # ここはご自身のサーバーネーム（URL）に変えてください。

    location / {
            proxy_pass http://your-server-ip:3000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
```
`listen 80`でazureの80番ポートに来るアクセスを指定します。その後`location / `にある設定の通りアクセスをリダイレクトします。`proxy_pass`でアクセスを飛ばす先のipを指定します。今回はtailscaleを使っているので、tailscaleのdnsでipではなくマシンネームでアクセスしました。

#### httpsで通信できるように！
ご存じかとは思いますが、httpsで通信するためには証明書を取得して...(以下略)って手間がありますね。この辺の記事はたくさん出ているので省略し、今回はさらっと流します。
証明書はみんな大好き「Let's Encrypt」を使って諸々取得しましたが、証明書を取得するときに一工夫！
legoっていうのを使いました。
コマンドで証明書とかを取ってきてくれる素晴らしいやつです。コマンドをshell scriptで保存してcronで定期的に実行すれば、更新の手間が省けます！はい！みんなもやってね！
詳しいやり方とlegoの入れ方は各自で調べてください。

まずコマンド
```console
$ sudo crontab -e
```
で、cronを設定して...
```shell
0 3 1 * * /path/to/lego.sh
```
shファイルはこれ
```shell:lego.sh
sudo lego --accept-tos --domains machiken-number.com --email example@sheeplab.net --path /path/to/certificate/ --http --http.webroot /path/to/acme-challenge run
sudo systemctl restart nginx
```
とりあえず`lego.sh`を実行しますと、`/path/to/ssl/`に
```shell-session:/path/tp/ssl/
machiken-number.com.crt
machiken-number.com.key
```
ができてるかと思います。
これをnginxで設定すればヨシ！
```nginx:/etc/nginx/conf.d/server.conf
server {
    listen 80;
    server_name machiken-number.com;

    return 301 https://$host$request_uri;
}

server{
    listen 443 ssl;
    server_name machiken-number.com;

    ssl_certificate /path/to/certificate/machiken-number.com.crt;
    ssl_certificate_key /path/to/certificate/machiken-number.com.key;

    location / {
            proxy_pass http://your-server:3000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
```
80番できた通信をいったん443番(sslのポート)に301リダイレクトしてからproxyしてます。
ssl認証関係で
```
ssl_certificate /path/to/certificate/machiken-number.com.crt;
ssl_certificate_key /path/to/certificate/machiken-number.com.key;
```
の二行増えてます。ここは上で取得したssl証明書のパスを指定してください。
これで`http://~` `https://~`できたものすべて`http://your-server:3000`に飛ばせるようになりました。

**だだーし**設定をする前に一つ注意をば。
これだと「Let's Encrypt」でサーバーを持ってるかの証明をするためにサーバーにアクセスがあるんですが、先にこの設定をしちゃうと通信がAzureじゃないところに行っちゃう。つまりAzure上のファイルにアクセスができない...（特に更新の時とかはこれ結構マズいですね。）
証明書等はAzure上にあるんですが、アクセス先はVPSになっちゃうので、ちょっと嫌なことになるんですね。
ので、認証のためにアクセスが必要なところ（証明書を置いているところ）はAzure上のパスにアクセスができるようにlocation設定しました。
(前述 `location / { ...` の上に)
```nginx:/etc/nginx/conf.d/server.conf
location /.well-known/acme-challenge/ {
    root /path/to/acme-challenge;
}
```
これで `/.well-known/acme-challenge/` にアクセスがあるとazure上の `/path/to/acme-challenge` にアクセスされます。

ちなみにちなみに余談ですが、ogp用の画像もazure上にのっけてます。
~~ogp画像はフルパスで設定する必要があるので、next.jsだとちょっと設定がめんどかった...~~
```nginx:/etc/nginx/conf.d/server.conf
location /ogp/ {
    alias /path/to/ogp/;
}
```
<details><summary>server.confフル</summary>

```nginx:/etc/nginx/conf.d/server.conf
server {
    listen 80;
    server_name machiken-number.com;

    return 301 https://$host$request_uri;
}

server{
    listen 443 ssl;
    server_name machiken-number.com;

    ssl_certificate /path/to/certificate/machiken-number.com.crt;
    ssl_certificate_key /path/to/certificate/machiken-number.com.key;

    location /.well-known/acme-challenge/ {
        root /path/to/acme-challenge;
    }

    location /ogp/ {
        alias /path/to/ogp/;
    }
    
    location / {
            proxy_pass http://your-server:3000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
```
</details>

これで難なくnginxを使ってssl化したWebを公開できましたね！
しかし、ここで問題が...
#### ssl化で躓いた話
**記事公開直前に気づきましたが、これ多分間違ったこと言ってるぜ☆見流してください**

nginxでsslの設定をし、実際にテストしてみるとエラーが。
APIリクエストをhttpで送っていたため、ブロックされてしまったようです。
ここで、VPN内でも通信をssl化すべく、VPNでもnginxプロキシを設定しました。こちらの設定でのssl化ですが、VPN内でしか使わないためmkcertで作ったローカル証明書を使用してssl化を行いました。
先の設定と違うところですが、これはグローバルに公開するようのsslではないため、認証局がローカルにある状態となっています。（つまり、LAN内ではssl証明書を保証する人がいるけど、その人の活動勢力がLAN内に限定されてる状態。ちなみに先のやつは世界中を担当してる保証人がssl証明書を保証してくれてるような感じ）

:::note info
#### mkcertとは？
ssl化の際に必要な認証をローカルで行えるようにするライブラリです。
```shell-session
$ mkcert -install
```
でローカル内での認証局(ローカルの中でsslの証明書が正しいよ、って保証するやつ)を作成できます。その後、
```shell-session
$ mkcert your-server-name
```
を実行するとその場にサーバー証明書、秘密鍵の二つが作成されます。この二つをnginxで設定をすることで、ローカル内でもhttpsでの通信ができるようになるんです。
:::
もともとhttpの3001番をバックエンド(nestjs)のエンドポイントにしていたので、httpsの3002番できたリクエストをhttpの3001番に飛ばすよう設定しました。
```nginx:nginx.conf(VPS上)
server {
    listen 3002 ssl;
    server_name your-server-name;

    ssl_certificate /etc/ssl/certs/your-server-name.crt.pem;
    ssl_certificate_key /etc/ssl/certs/your-server-name.key.pem;

    location / {
        proxy http://your-server:3001;
    }
}
```
念のため注意ですが、3001番でlistenしてしまうと、もともとバックで使っているためエラーを吐きます。ので、3002番（別のポート）をlistenして元の3001番へ飛ばすようにしましょう！ポートはご自身で自由に変えてね。

さて、ここまでサーバーのproxyの話をしてきましたが、いったんそれはここまでとします。

#### VPS内での話
まず、ほかにももっといいやり方あるし、というかちょっとどうなのってのもつかってる（気がする）ので**あくまで参考程度に**お願いします。あと、本番環境とここで紹介する環境ですが、ちょっと違うところありますので悪しからず。

<details><summary>Nestjs,Next.jsの環境がない？</summary>

頑張って環境構築してください。
```shell-session:npm
$ sudo apt install nodejs npm -y # node.jsとnpmをインスコ
$ sudo npm install n -g # n(バージョン管理)をインスコ

$ sudo n stable # 最新版をインスコ（しなくてもいい）
$ sudo apt purge -y nodejs npm # 上をしたなら（最初に入れたやつの削除）
$ sudo apt autoremove -y # 上をしたなら

$ node -v # nodeのバージョンチェック（インスコされてるか確認）
$ npm -v # npmのバージョンチェック（インスコされてるか確認）
```
```shell-session:nest
$ sudo npm install -g @nestjs/cli # nestをインスコ

$ nest -v # 念のためバージョンチェック

$ npm run start:dev # 開発モードでサーバー起動（この辺は各自勉強してください）
```
```shell-session:next
$ npm install --save next react react-dom # --saveはいらないらしい
$ next -v # 念のためバージョンチェック
```

</details>

まず、databaseについて。こちらはバックエンド担当のinukaki氏が諸々をdocker作ってくれたのでdockerを起動するだけ。
```shell-session
$ sudo apt install docker docker-compose
$ cd /your/docker/path
$ sudo docker-compose up -d
```
`-d`あってもなくてもいいですが、`-d`あるとディタッチ状態で起動してくれるので、そのディレクトリのまま作業できる！

次にバックエンド。こちらはバックエンドのルートに移動して
```shell-session:/backend/root-path/
$ screen -dmS backend # screenのセッションを名前指定で作る
$ screen -r backend # screenにアタッチ
$ npm install # 念のためinstall
$ npm start # 起動するだけならこれでOK
```
で起動します。
screenを使ってるのはssh切ってもセッションが残り続けてくれるのと、どのデバイスからでも同じコンソールにアクセスできるのがいいと思ったから！
起動できたら`Ctrl + A` → `Ctrl + D` でディタッチしてください。

次フロントエンド。フロントのルートに移動して
```shell-session:/frontend/root-path/
$ screen -dmS frontend
$ screen -r frontend

$ npm install # 念のためね
```
開発者モードで起動なら
```shell-session
$ npm run dev # 開発者モードで起動
```
本番環境なら 
```shell-session
$ npm run build # ビルドして
$ npm start # ビルドしたやつを起動するなら（本番環境）
```
ってすればOK
起動できたら`Ctrl + A` → `Ctrl + D` でディタッチしてください。

これでアプリの起動ができました。もしコンソールにアクセスするなら、
`screen -r [名前]`ってコマンド打つとコンソールに入れます。もしくは、`screen -x [名前]`でも大丈夫。もし誰かがすでにコンソールにアクセスしてたとかなら`-x`のほうがいいかも(?)

正直サーバーの環境に関しては個々でそれぞれ頑張ってください。環境は自分がやりやすいように設定してね！

### 今回のポイントまとめ
- nginx proxyを使おう
- Apiを使うときはsslに気を付けよう
- ポートのconflictに気を付けよう
- ハッカソンは早め早めの計画をしよう
  - 特に本番環境でのテストは余裕をもって
- ~~Tailscale は神~~
- ~~Azure for Student は神~~

## さいごに
変なこととかわからないところがあればコメントお願いします。
Qiita初心者なのでなんもわからず記事を書いております。いろいろとご教授くださいませ。
## 参考

https://qiita.com/mokichi/items/f915c28aec6da147d722

https://qiita.com/k_kind/items/b87777efa3d29dcc4467