NimでWebアプリ作って公開した

# まえがき

* NimでWebアプリを作って公開した
* フロントエンドもバックエンドもNim
* インフラも一部Nim
* 自動リリースできるようにしたり色々やった

# 作ったもの

ブラウザ上でシェルのコマンド列を入力するとバックエンドのDockerコンテナ内でシェルを実行してその実行結果をブラウザに返すWebアプリ

* リポジトリ: https://github.com/jiro4989/websh
* サイト: https://websh.jiro4989.com/

追記：2020/02/01 デザインを変えました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69665/64ea17ab-4cba-7fbc-5389-890cc161dfd1.png)

初期UI
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69665/9abb5146-2002-b6f0-9cfb-ab9ae327edbb.png)

#使い方

シェルのコマンドを入力してボタンを押すと結果が表示されます。
シェル実行のタイムアウト時間は3秒です、

# 作った経緯

- シェル芸botのDockerイメージが公開されていたので、それを使用してwebアプリにしたかった
- https://github.com/theoremoon/ShellgeiBot-Image
- webアプリの設計から全部作ってみたかった
- webアプリの保守運用も全部やってみたかった
- Nimでwebアプリを作りたかった

# システム構成

ざっくりサービスの構成を書くと以下のような構成です。

![websh app-Copy of Page-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69665/fb2190ba-b7a3-1304-2337-558d5016b7f5.png)

ひとりで全部Webアプリを組むのは初めてだったので、とりあえずサーバ1台で組んでいます。
いずれは踏み台サーバ、監視サーバも追加することを考えています。

データ処理のフローとしては、ブラウザで入力したシェルをAPIにpostリクエストして、サーバ側でdockerを起動し、コンテナ内でシェルを実行。
実行結果を返却して画面を再描画しています。

# やったこと

Webアプリ公開に向けてやったことを書きます。

## アプリ

実はアプリ側はほとんどやってること無いです。
複雑な処理はすべてフレームワークがやってくれてますし、
やってることも実行結果を画面に描画するだけでです。
コード量もフロントとバックエンド合わせて200行くらいでした。

### フロントエンド開発

[Karax](https://github.com/pragmagic/karax)フレームワークを使用しています。
NimでSPAなフロントエンド開発ができるフレームワークです。
まだバグっぽい挙動がちょいちょいありますが、勉強の一環と思って採用しました。

Nimの構文とほとんど変わっていないうえに、
HTMLやJSPなどのテンプレート記法よりもずっと簡易な構文なので
学習コストは割と低いと感じました。

CSSは最初は[Materialize.css](https://materializecss.com/)を使ってましたが、途中でJS不要、CSSのみで使える[Bulma](https://bulma.io/)に変えました。
理由は、ちょいちょいMaterializeのJavaScriptがノードを更新するのがKaraxの仮想DOM更新とコンフリクトしてエラーになってたからです。

### バックエンド開発

[Jester](https://github.com/dom96/jester)フレームワークを使用しています。
APIサーバとかを簡単に実装できそうだなぁ、と思って採用しました。
この予想は正しくて、メチャクチャ簡単に簡単にAPIが実装できました。

また、ビルド時に`--threads:on`フラグを持たせることでマルチスレッドで動作するようにできるので、パフォーマンスも良さそうです。

注意点として、`--threads:on`ありでビルドするとjesterの組み込みのロギング機能が機能しません。
さらっとJesterのコードを追った感じではJester（httpbeast）のバグっぽいです。
ログが記録されない原因もだいたい特定してるのでそのうち修正のPRだそうかなぁと思ってます。

今回は仕方なく自前でロギング用のプロシージャを実装しています。

## インフラ

### サーバのレンタル

さくらのVPSと契約しました。
初めはAWSとTerraformで構築しようと思っていました。
ですがそれなりのスペックかつ24時間稼働させるならさくらのVPSのがずっと安価になる見積もりになったので、
さくらのVPSで契約しました。

サーバの環境構築はAnsibleを使用して行っています。
Infrastructure as Codeしたかったので。

### ドメインの取得

初めはFreenomで無料ドメイン取ろうと思ったのですが、
年間1,200円くらいなら別に有料でいいかと思ったので
普通に有料ドメインとりました。


### 自動リリースフローの構築

GitHub Actionsを使ってリリースを自動化しました。
サーバにいちいち接続してリリースは**絶対に**やりたくなかったからです。

NimでバイナリとJSをビルドして、成果物だけをサーバ上に配置するようにしています。
なので、サーバ上にNimのコンパイラはインストールしていません。

ビルド環境はなるべくローカルPCにもサーバ上にもしたくなかったので、Dockerコンテナ内でビルドして成果物だけとりだすようにしています。

GitHub Actionsを使用してCIで自動テストするようにしています。
GitHubでタグを切った時に、バイナリとJSをビルドして圧縮してGitHub Releaseにリリースします。
全てのフローが正常に通った時に、サーバのWebhookにリクエストを送信し、リリースした圧縮ファイルを取得してサーバ上に展開して配置します。

図にすると以下のようなリリースフローになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69665/9cdc9fcd-d734-d6bc-b6af-64cbe7bc5006.png)

これでビルド、リリースはPCが手元になくても実施可能になりました。出社途中の電車の中でスマホからバグを直してリリースしたりもしてます。

CIにかかる時間も可能な限り短くして1分半から2分でテストが完了してリリースされます。

webhook側のタスクをNimで実装しています。リリース物をタグ名のディレクトリで切って、latestディレクトリにシンボリックリンクしてで切り替えるようにしています、

### サービス監視

無料で使えるサービス監視のサービスを導入しました。
UptimeRobotというサービスです。
Public Status Pageも無料で用意できるので助かってます。

https://stats.uptimerobot.com/EZnRXc325K

これでいちいち管理画面にログインしなくてすみます。

### セキュリティ対策

Webサーバを起動した時から不正アクセスがチラホラ記録されていることを確認しました。
Webサーバは常に攻撃にさらされている、とは知っていたのですが、こうして自分が作った環境が攻撃を受けていることを目の当たりにするとやはり放置できない、と思いセキュリティ対策も入れるようにしました。ファイアウォールから不正アクセスやdos攻撃のアクセス元の自動banなどいろいろ。

プロセス監視、CPUリソース監視とかも導入しようと考えています。Grafana + Prometheusあたりを使おうかと。

特にサーバ内に何も大事な情報を抱えていないので盗まれて困るものはないのですが、勉強のためにガチガチにセキュリティ強化したいです。踏み台からしかsshてきないようにもしないと…。
（不正アクセスの踏み台にされてもかなわないですし）

# 感想

Nimでwebアプリを作って公開するという一つの目標を達成しました。
11月頃に着手し始めて、サボってた期間を含めると一ヶ月半くらいでリリースできた形になります。ほとんどがインフラタスクでした。

#本番環境でやらかしちゃった

このインフラ構築中に三回iptablesの設定を間違えてsshできなくなりましたテヘペロ。
cronでiptables-restoreするようにしていたので、やはり保険は大事だなぁと。今はcronオフにしてます。

#今後の課題

セキュリティ、IaC、CI、自動化、監視などとにかくバックエンドにしか力を入れないつもりで開発を進めていました。

フロントエンドは何もわからないのと、手を抜くつもりだったのでだいぶおそまつです。
もうちょいいい感じにしたいです。
現状問題が起きていても画面に何も出さないですし。

Nimで実装するのは楽しいので、今しばらく勉強続けようと思います。

以上です。