サーバレスで動く研究室のHPを1年間運用してみて


指導教員の無茶振りで作り始めたサーバレス+SPA+GraphQL+ChatOpsで研究室のHPが完成してからだいたい1年が経過しました。(参考: 研究室のHPをサーバレス、SPA、GraphQL、ChatOpsで作った)研究室という、人が数年でほとんど入れ替わる特殊な組織内でどのようにHPを継続的に運用していったのか振り返ります。

HPの概要

Slack Botで更新する研究室のHPです。YAMLを添付して@labbot posts createのようにメンションを飛ばすと記事が作成されます。更新、削除もできます。

このHPは、(1) 研究室のみんなが更新できる, (2) 運用、ID/PASS周りの管理コストを最小限にする, の2点を要件として開発したものです。

大きく分けてフロントエンド、サーバーサイド、Slack Botの3つの部分で構成されており、フロントエンドはNuxt(Vue.js)、Buefy、AtomicDesign、ApolloClientなど、サーバサイドはNodejs、GraphQL、DynamoDBなど、Slack BotはNodejsやjestなどから作られています。そしてこれら全てのインフラをserverless framework(Lambda)で動かしています。以下の図は現在のシステム構成概要です。

_人人人人人人人人人人人人人_
> 全部sererless framework <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^ ̄

serverless frameworkの開発者達には足を向けて寝られない。

数字で見るこの1年間

(1) アクセス数

GAでトラックしていたのでそのキャプチャです。去年書いた記事がバズった時以外はだいたい週20アクセス前後です。いや20人も誰が見とんねんって感じですが。

(2) 運用費

運用費はRoute53とS3が毎月\$0.5ずつかかって年間\$12です。ドメイン代の\$12を合わせても年間\$24で運用できています。とても財布に優しいですね。

たぶん月100万アクセスくらいまではこんな感じの値段になると思います。

(3) 記事数

週一で記事を追加するので4 x 12 = 48になるはずですが、長期休暇等があって書いてない人もいるので37件でした。思ったより多くて満足しています。

HPの成果

一番大きな目的は、学科内の研究室配属の時に「うちの研究室はこんなアクティビティがあるよ」というのを紹介するというものでした。これは去年の研究室選択の時期に指導教員がみんなに見せていたので役割は果たせたと思います。

あと、うちの研究室に院試受けて入ろうという人の参考にもなったみたいです。

この1年の開発

以下はこの1年で行った開発に関する取り組みです。

  • (1) Vue.jsのSPAからNuxtでのSSRへリプレイス
  • (2) 毎週担当者をアサインするBotの稼働
  • (3) 投稿前にインタラクティブに内容を確認できる
  • (4) Algoliaでの記事検索
  • (5) 指導教員に内緒でシークレットモードを追加
  • (6) serverless slack botのバグ修正

そういえば、今でもちょくちょく指導教員から「ここ機能追加して欲しいです」みたいな要望があって興が乗った時にガリガリ作っているのですが、しばらく触っていなくてもすんなり触れる感じのコードになっていて(自分が書いたので当たり前といえば当たり前だけど)大変喜ばしい限りです。

(1) Nuxtへのリプレイス

上の記事の通り、最初はVue.jsによるSPAとして動かしていたのですが、以下の辛みを感じたので勢いに任せてNuxtへ置き換えました。

  • 頭を使わずにSPAしてたからレスポンスが返ってくるまでの0.5秒くらい何も表示されない
  • SPAだから記事ごとにOG設定できない
  • 勢いだけで書いたからかなり雑でテストが書きにくい(リプレイスの後押しとなった要因)

書いて動かしてる感触なんですが、NuxtはプレーンなVue.jsで書くよりも適度な規約があってとても書きやすかったです。

なお、このNuxtアプリケーションについてもLambdaの上で動かしています。NuxtをLambda上で動かす仕組みはNuxt.js on AWS Lambda with Serverless Frameworkとほとんど同じ物を採用しました。

(2) 週次アサインBotの作成

研究室の仲間たち(合計20人くらい)で継続的に更新を続けていくために、週に一回担当者を決めて更新してもらうようにしました。この週一の担当者決めはSlack Botが担当します。

これもサーバレスで動いています。去年のre:InventでLambdaのRubyサポートが始まったので試してみたかったのでRubyで書きました。

(3) 投稿前の確認

ある日研究室で隣の席の人が「投稿時に確認画面が欲しい」と言ったのでガガっと実装したのがこの機能です。

こういう風に、Slack BotにSnipetとして送信するとプレビュー画像と共に「これ本当に投稿する?キャンセルする?」の2択を迫ってくるようにしました。

投稿プレビューの仕組みは擬似的なもので、実はプレビュー段階で本番環境にデータが作成されます。そしてPublishボタンを押すと作成済みデータのpublished_atに現在時刻が代入されてニュース一覧で見られるようになり、Cancelボタンを押すとこのデータが削除されます。

つまりプレビュー時はURL直打ちだと見える状態になっており、プレビューのスクリーンショットはここに直接アクセスして取得したものを返すようにしています。

ここで必要になったのが、任意のWebページのスクリーンショットを撮影してその画像を取得するという仕組みです。

スクリーンショット撮影部分

欲しかったのは「あるURLを渡すとそのサイトのスクリーンショットが返ってくる」機能だったので、作りました(asmsuechan/sshot)。

これもサーバレスで動かしています。ヘッドレスchromeを無理やりLambdaに押し込んでAPI Gateway経由で呼ぶような形式です。

以下のURLは公開されているもので、誰でもアクセスすることができます。あ、このままだとスクショは研究室のAWSアカウントのS3に保存されるので、保存されたくない方は自分で立てるかbase64オプション使うかで。

$ curl "https://vckvs9l162.execute-api.ap-northeast-1.amazonaws.com/production/screenshot?url=https://github.com/asmsuechan"
{"screenshot":{"url":"https://s3-ap-northeast-1.amazonaws.com/sshot-images/kvd6ajor2hbprpb9.png"}}

urlをパラメータに入れたcurlを投げるとそのサイトのスクリーンショットがアップロードされたS3のURLが返ってきます(このバケットは研究室のAWSアカウント上のものです)。また、base64パラメータをtrueに設定する事によってbase64にエンコードした画像を返すようにする事もできます。

また、設定ファイルをよしなにいじる事で自分のAWSアカウント上にデプロイすることもできます。

開発で一番大変だったのが日本語フォントへの対応で、かなり無理くりやって何とか対応しました。が、上の画像を見る通りフォントがダサいのでここもうちょっとどうにかならんかったんか・・・って感想です。

(4) Algoliaでの記事検索

Algolia使ってみたくて、なんとなく興が乗った時に記事検索機能https://moriokalab.com/news に追加しました。誰が使うんだろう、って感じですが。。。

NuxtへのAlgoliaの導入は公式のexampleと公式ドキュメント以外特に参照せず実装できました。すごく簡単。Algoliaめちゃくちゃ速くてびっくりです。Algoliaすごい。

(5) 指導教員に内緒でシークレットモードを追加

トップページにある指導教員の顔を30回クリックするとシークレットモードが発動し、イカしたデザインになります。なお画面から元に戻す方法はありません。戻したい方はLocalStorageでそれっぽいフラグをdeleteしてください。

(6) serverless slack botのバグ修正

Slack Botはjohnagan/serverless-slackbotを元に作ったのですが、画像を2回以上連続でSlackにアップロードするとBotが落ちてしまうという問題がありました。最初は放置していたのですがあまりにも不便だったので原因を探したところserverless-slackbot側のバグだったので直してPRを送りました。
https://github.com/johnagan/serverless-slackbot/pull/3

サーバレスで良かったこと・悪かったこと

良かったことは

  • serverless frameworkを使うとコマンド1つで簡単にデプロイできる
  • インフラのメンテコストがかからない
  • 料金が安い

悪かったことは

  • ローカル環境構築が大変
  • DB選択が悩ましい
  • 初回アクセスが遅い

1年経ちましたが、あんまりずっと面倒を見続けるようなシステムではないのでサーバレスを選択して良かったな、と思っています。

運用を振り返る

また、更新操作をSlackで完結させることで、CMSと比べて誰でも手軽に更新できる仕組みとなったのが良かったです。CMSだとどうしてもID/Passの問題があって、ここの管理が面倒でだんだん更新されなくなる未来が見えていました。

また、サーバレスな構成にしてサーバーサイドのインフラの面倒を見なくても動き続けるようにしたので「管理/運用したくない」という目標は達成したと思います。

更新を続ける仕組み

研究室の愉快な仲間たちみんなで更新していくための仕組みとして、毎週月曜日の18時にその週の更新担当者をアサインするCronを動かすようにしました。

これ最初は「みんな無視するんじゃね」って思ってたんですが案外そんなことはなく、当てられた人は結構マジメに更新してくれています。

打ち捨てられないための仕組み

このようなHPというのは作ったっきりで打ち捨てられてしまうことがままあります。これはだいたいの場合「めんどくさい」という気持ちと「優先度が低い」という状況のどちらか、またはどちらもが原因になります。この辺りで意識したことについて運用面と開発面の両方で書きます。

運用面

Botに指名されているのにも関わらずHPの更新をしていない人には指導教員が面談中に「書いてくれ」と言っていたことで、(その人の中での)優先度をあげていました。とにかくあたった人に、書くまでpingを送るという戦法ですね。

なるべく記事のハードルを下げるというのも気をつけたことです。目的は「アクティビティがあることを示すこと」なので内容のクオリティはそもそも問うていません。クオリティは必要になってから考えます。

あ、あと、こういうのはやる気がありすぎても飽きて打ち捨てられてしまう事があるのでほどほどのやる気で取り組んだというのも良かったと思います。指導教員に「これ欲しいから作って」と言われた時に「興が乗ったら作ります」と言って数カ月後ガリガリ作り出す、みたいな感じのゆるさ。

開発面

開発上では研究室のGitHubオーガナイゼーションを作りそこに全てのコードを置いてどこからでもコマンド1つでデプロイできるような仕組みを最初から作り、デプロイがめんどくさくてコードいじる気がなくなるという状況を緩和しました。

そしてコードも最初から予めある程度の決まった枠組みを作り、何か機能を追加する時には既存のものを見ながら思考停止でも基本はコピペで動くものができるようにすることで「コードの書き始めのだるさ、めんどくささ」を軽減しました。既存システムへの機能追加って、どうしても「壊すかもしれない」という気持ちがあって最初は億劫なものなので。最初さえ頭を使わずサクッとできればあとは流れに乗って書けるはずです。

現状の問題点

去年も同じこと言ってたじゃねえか!って話もありますが、今の問題点です。

(1) ローカル環境が適当

インフラのローカル環境が全然構築できていなくて、毎回デプロイしてテスト用Slackチャンネルで直接いじるという形にしていて非常にめんどくさいです。いい感じにDocker環境立ててやるとうまくできるのでしょうが、そこまでやる体力がなくてそのままにしています・・・

(2) 後任の不在

研究室は人間の流動性が高く、1人の学生がずっと面倒を見続けるわけにはいきません。このHPももちろんそうなのですが、指導教員と話していてこれから先Web人材が研究室に入ってきて自分の残したコードを読み保守運用してくれる人が現れる確率は低そうという結論になったので、卒業後はたまに報酬(うまい焼肉)を貰いながら私が運用していくという感じの話をしています。

(3) LambdaでSSRすると初回アクセス遅い

Lambdaがcold standbyしている時にアクセスすると表示に5秒くらいかかってしまうという残念な問題があります・・・解決策は10分に1回だかヘルスチェック回して常にHot standbyな状態にしてやることです。そんなに難しくはないと思うのですが気持ちが乗らないので手をつけていません。

(4) UIの改善は特になし

やはり仕事のコードではないのでUI上の細かい部分はおざなりになりがちです。どこかであまり良くない部分を改善したいとは思っているのですが後回しにしています。もっとカッコよくしていきたい。

まとめ

この手のHPの目的はだいたい「自分たちの組織がマトモに活動していることを対外的にアピールして印象を悪くしないこと」であることが多いので、作って公開することより公開して更新し続けることがよっぽど重要だと思います。ですのでここで書いた継続する仕組み打ち捨てられないための仕組みというのが、私と似たような状況に置かれた人の助けになれば幸いです。