趣味でも業務でも日々Webサービスを開発しているzaruです。こんにちは。ついにアドベントカレンダーも最終日です。まだサンタとしての仕事が残っています。さて今回は仕事としてWebサービスを開発するときに気をつけたいポイントを紹介します。まぁ仕事に限った話じゃないですが…参考になれば幸いです。特に新卒プログラマあたりに読んでもらえればと思います😀
なお僕の業務上インフラ周りはAWSが多いです。
RASISという指標
RASISという指標があります。コンピュータシステムの評価指標5つの頭文字を取ったものです。
- Reliability(信頼性)
- Availability(可用性)
- Serviceability(保守性)
- Integrity(保全性)
- Security(機密性)
今回はこの5つの指標に沿ってポイントを紹介していきます。RASIS自体については色々なところで解説されていると思うので、今回は省きます。
信頼性
システムの故障しにくさを表します。
デプロイ/ロールバックの手順を完璧にしよう
Webアプリはデプロイしないと始まりません。まずはデプロイを自動化しましょう。誰がやっても必ずデプロイできるように手順を明記し環境を作りましょう。今時のWebアプリであればデプロイツールをCI通じて、マージされたらテスト実行して自動デプロイ…というのが一般的だと思います。僕の場合はCapistrano + CircleCI + GitHubで構成することが多いです。
また、よく見落としがちなのがデプロイのロールバックです。新機能をリリースしたは良いけれど思わぬ障害が発生し、とりあえず前のコードに戻そう!ということがあります。嫌ですね😖。でも大丈夫、たいていのデプロイツールにはロールバックの機能がついています。
ただし…ロールバックするのですがぶっつけ本番で、使い方もわからないし動かしては見たけど別の障害が発生してしまった…!ということがありえます。二次災害怖いですね。
大切なのはWebアプリの本番公開前にかならずデプロイとロールバックのテストをして手順を完璧にしておくことです。心理的不安をなるべく取り除いていきましょう。
例外設計をしよう
例外設計をしようというのは至極当然の話なんですが意外と抜けていたりします。コードレビューをする中でも特に気をつけたいところです。正常系だけを想定してコードを書いて手元では上手く動いても本番の環境では上手く動かないことがあります。というか、たいてい上手く動きません。
僕の中では以下の様なことを心がけています。
- 例外を乱用しない
- 例外は呼び出す側がINPUTの条件を満たしているが、呼び出された側でOUTPUTの条件を満たせなくなったときに投げる
- 例外は無視しない
- 受け取るけど握りつぶさない
- だましだましでアプリを延命させない
例外については下記の記事が大変参考になります。
死活監視/障害検知をしよう
Webアプリが提供できない=事業の機会損失にダイレクトに繋がるので、インフラの死活監視や障害検知は特に大事です。必ずリリース前に各種設定がなされているか確認をしましょう。
- エラー検知ツール(sentry|AirBrake)を導入する
- リソースモニタリングツール(newrelic|mackerel|zabbix)を導入する
- アラートがslackやメールなど担当者に適切に届くように設定をする
- プロセス管理ツール(monit)を導入する
個人的には sentry / mackerel がお気に入りです。サーバー台数やサービス数がそこまで多くなければ費用もそこまで高くなく、運用コストが低いのが良いです。
ログファイルを適切に扱おう
時々見かけるのがログファイルのローテーションがされずにディスクフルで死亡するケースです。プロビジョニングツールなどで標準化するなどして人の手でうっかり設定漏れをなくしましょう。
また、アプリケーション内で必要なログを別途出力するようにすると障害時に検証しやすくなると思います。
バックアップを取ろう
はい。当たり前ですね。どの程度のバックアップが求められるかはサービスによって違いますが、少なくともまったくバックアップが取られていないという状況は今すぐやめましょう。
- DBはAWSならRDSを使って自動バックアップを取る
- またはバッチで深夜定時バックアップを行う
- 画像などのリソースはAWS S3のバージョニングを使う
可用性
システムが継続して稼働できる能力。サービスによってSLAと予算が変わってくるため、どこまでやるかはバランスを見ながらになります。
単一障害点をなくす
どこかのサーバや機器が壊れたらシステム全体が提供不能になるような単一障害点は、なるべく取り除いていきましょう。特にコストが殆どかからないような取り組みは積極的にやりましょう。一度ローンチしたシステムのインフラ構成をガッツリ変えるのは精神的に削られるので…。
- ALB/ELBなどのロードバランサを利用する
- 別のAZにインスタンスをたてる
- 要件によってはオートスケールに対応する
- CloudFront / AkamaiなどのCDNを利用する
- オリジンにはS3を利用する
- RDSのMultiAZを利用する
- Auroraを利用する
参考記事
属人化を防ぐ
インフラなどの単一障害点をなくす話はよく出てきますが、人的単一障害点もなくすようにしましょう。組織としてのスケールと強さが出てくるところだと思います。そのためには属人化を防ぐ必要があります。
- ドキュメントを整備して誰でもできるようにする
- レビュイー固定をなくしてレビュー速度を上げる
- こんな固定タスクがあるよ!と声を上げる
参考記事
保守性
メンテナンス/障害対応のしやすさ。
テストコードを書こう
散々言われてきていることなので割愛します。テストコードを書きましょう。TDDである必要も、カバレッジ100%である必要もありませんが、テストコードを書かなくても良いということはないと思っています。
ドキュメントを残そう
ドキュメントを残すことを残業のように思うのではなくドキュメントも含めて開発という意識にすると良いと思います。コードと同一視するということです。そうすることで、関係者誰でも閲覧・更新ができて、場合によってはバージョン管理することで差分を確認することができるようになり、ドキュメントを育てる土壌の第一歩が踏めます。
ドキュメントを育てる土壌に必要なのは以下のようなポイントだと思っています。
- ドキュメントの場所を集約する
- Markdownなどのプレーンテキストに近いフォーマットを採用する
- 誰でも閲覧・更新できるようにして、チームの共同作業と意識する
- 必要十分に書き、最初から全てを網羅しない。必要になったときに追記する
- 必要なくなったドキュメントは消す
障害情報をログに残そう
業務で開発をしていると必ず障害対応というものに出くわします。突発作業になることが多いため、どのような原因で、どうやって対処したのかをドキュメントに残さず終了してしまうことがありますが、それではチーム全体で共有できず再発してしまう可能性や、他プロジェクトを開発する際に教訓を活かせなくなってしまいます。このログが明日の自分を守る武器になります。
読みやすいコードを心がけよう
これも散々言われてきていることですね。まずはリーダブルコードを読みましょう。そして、チームで読みやすいコードとはなにかを議論しましょう。考え方が違うとコードレビューで無駄なやり取りが発生してしまいます。縛りすぎないコーディング規約を設定するのも良いと思います。
開発環境を簡単に作れる
一人でガリガリと書いているような環境だと、開発環境の構築がおざなりになってしまい、他のプログラマがジョインした時に環境構築だけで1日が終わる…しかもドキュメントが残ってないから質問にも答えなきゃいけない…ということになります。
今であればVagrantやDockerがありますし、プロビジョニングツールもChef / Ansible / Puppet / Itamaeといろいろあります。構築ドキュメントも含めて誰にも聞かずにすぐに立ち上げられるように最初から準備をしておきましょう。
負債を意識して使い、返済計画を作る
業務なので必ず期限が設定されています。そこに間に合わせるために生み出してしまった負債はちゃんと意識しましょう。後で取り返しのつかないような負債だけは積まないようにしましょう。取り返しが付くのであれば、いつどうやって返済するかの計画を、コードレビュー時にチームメンバーと共有すると良いと思います。
当たり前だけどバージョン管理をしよう
これは…さすがにね。
保全性
データの矛盾がなく一貫性が保てるかを表します。
モデル設計は最後まで拘ろう
モデルの設計が間違っていると後々までずっと負荷をかけてきます。常に大リーグボール養成ギブスをしているようなものです。リリースする最後の最後まで拘って、あやしいところはコストかけてでも直したほうが長期的に見たらメリットがあると思います。
トランザクション
新人のコードレビューをすると、トランザクションは知っているけど適切に使われていないというケースを時々見かけます。データの一貫性を保つのは非常に大事です。
DBの制約を利用しよう
まずは、SQLアンチパターンを読みましょう。そしてNOT NULL制約と外部キー制約をうまく使いましょう。
機密性
セキュリティ/不正アクセスがされにくいことを表します。
SSLは全ページで使う
とりあえずSSLを使うかどうかは迷うわず全ページ常時SSL一択にしましょう。購入費用自体も安価になりましたし、Let's EncryptやAWS ACMなど無料で取得できる証明書も登場しています。昔言われていた暗号化する際のCPUコストも正直無視できるレベルです。
なぜ常時SSLにするのかというと
- HTTP/2が利用できる
- HTTP / HTTPSの切り替えを行う必要がなくなる
というメリットがあるからです。
またSSLの診断ツールもあるので適宜利用しましょう。
パスワードはまじでハッシュ化しよう
個人情報の流出でパスワードがそのまま保存されていました…とか、2016年でも聞くことがありますが、本当に止めましょう。不可逆なハッシュ化をすることで最悪流出をしても悪用されないようにします。
そして単にハッシュ化すればOKなのか?というとそうではありません。以下の4つを意識する必要があります。
- ハッシュアルゴリズムを検討する
- saltを付け加える
- ストレッチングする
- 文字数/文字種を増やす制約を付ける
例えば単純なMD5でのハッシュを使って「password」をハッシュ化します。
Digest::MD5.hexdigest('password')
=> 5f4dcc3b5aa765d61d8327deb882cf99
これで大丈夫。とか言うエンジニアが身近に居たらなぐってOKです。レインボーテーブルというテクニックがあり、あらかじめハッシュから平文を参照できる逆引き用辞書を作っておくことで短時間で平文が取得できるようになります。
そこでパスワードの前後に文字列を付け加えるsaltと、SHA-2を利用して対抗します。
Digest::SHA256.hexdigest('salt-password-salt')
=> 4a83e8a5dcc4f9c394c34bf5db03bf5d9197e4a10ddf35dfe4d3406c79e21239
また、ストレッチングをすることで解析までの時間稼ぎもすることができます。
def stretching(str, n = 0)
str = Digest::SHA256.hexdigest(str)
return str if n > 1000
stretching(str, n + 1)
end
stretching('salt-password-salt')
=> 605a721f0126c3db44c2129c6dccb380ce84915357c16ef77dd7ba2ad55a74b6
ここまでの努力をしても入力されたパスワードが1文字とかになると無駄になるのでパスワードのバリデーションもちゃんとしましょう。
適切な認証認可
例えば管理画面のユーザが admin ただ一人とか、ユーザごとに分かれているけど全員スーパーユーザとか、「ID: taro / PW: yamada」みたいな安易なパスワード設定をして担当者に渡すのはやめましょう。
余計なライブラリ・アプリケーションは入れない
開発時は必要だったけど結局使わなくなったライブラリとか、検証するのに入れてたけど本番では必要ないアプリケーションとかは消して必要なプロセスだけ上げるようにしましょう。インフラ構築に慣れていない初心者の場合、けっこうこういうケースを見かけます。リソースも食うし余計な不具合を生む可能性が上がってしまいます。
SELinuxの存在
SELinuxちゃんと使おうよ!という話もありますが、個人的にはSELinuxは無効にして運用しています。理由としては単純に費用対効果で、SELinuxを理解して運用できる技術者が必要なのと対応工数のコストが、得られるメリットに見合わないと感じているからです。とはいえ、思考停止で無効にするのはアホなのでちゃんと向き合う必要があるとも思っています。
オレオレフレームワークは使わない
素直にRailsを使って周辺ライブラリ含めて適切にバージョンアップしましょう。
勉強としてのオレオレフレームワーク構築は非常に良いと思います😀
おまけ
パフォーマンス
Webサービスが想定しているユーザ数・トラフィック・データ量を適切に処理できるシステム設計になっているかを検証しておきましょう。極端ですが一人しか使わないようなシステムに複雑な分散アーキテクチャを適用しても豚に真珠🐷💎状態ですし、数億レコードを超えるようなシステムで1台のMySQLサーバだけというのは無理があります。
心構え的な所
昔書いたものがあるので良ければ読んでみてください。
リリース前チェックリスト
Webサービスを開発/リリースする前にチェックするリストです。会社やチームによって細かいところは違うと思いますが、思いつくものをリストアップしてみました。
サーバ
- エラー検知ツール(sentry|AirBrake)を導入している
- リソースモニタリングツール(zabbix|newrelic)を導入している
- アラートがslackやメールなど担当者に適切に届くように設定している
- ソースコードのバージョン管理がされている
- GitHubでプルリク開発フローを整備し、ドキュメント化されている
- サーバの起動/再起動/停止などのオペレーションがドキュメント化されている
- Itamae、Chefなどでインフラの自動構築が本番・ローカル問わず可能
- 開発環境構築がドキュメント化されている
- ステージング環境が存在している
- ステージング環境にはBASIC認証などがあり、Googleにクロールされないようになっている
- ダウンタイムなしで、スケールアウト/サーバの入れ替えが可能な構成になっている
- メール送信が迷惑メールにならないように設定されている
- AWS SESの場合
- DKIMを設定している
- バウンスメール/迷惑メールに対応している
- AWS SESの場合
- 各種アプリケーションのログがローテーションされる
- 想定トラフィックに耐えられるか事前に本番データ量で検証している
- ServerTokenを表示しないようにしている
- テキストコンテンツにgzip圧縮が設定されている
- アセットファイルにexpiresが設定されている
- SSHでパスワード認証ができないようになっている
- rootユーザでログイン出来ないようになっている
- 本番環境で、アプリケーションのエラーメッセージが表示されないようになっている
- 設定ファイルなど外部から参照できるようなパーミッション/パスになっていないか
- WAFを導入しているか
サービス運用
- デプロイツールが用意されている
- 自動デプロイが可能になっている
- 対象ブラウザでの動作テストを行っているか
- 404 / 500エラーページを用意しているか
データベース
- 特定のサーバからのみ接続できるようになっている
- データベースのバックアップが設定されている
- データベースのロールバック手順がドキュメント化されている
- 実行されるSQLにそくしたインデックスが作成されているか
- 想定のレコード数をいれた状態でパフォーマンステスト済みか
SEO
- Googleタグマネージャーを導入している
- クリックイベントを設計しているか
- Google Analyticsが登録されている
- Search Consoleが登録されている
- title/descriptionなどMETAタグが適切に設定されている
- 本番環境のrobots.txtやmetaタグでクロールされないようになっていない
- sitemap.xmlを用意しているか
- ogpが適切に設定されているか
- TwitterCardが適切に設定されている
- wwwありなしなどドメインが統一されているか
アプリケーション
- 環境に依存するようなパラメータをハードコーディングしていない
- ユニットテストが作られている
- E2Eテストが作られている
- 自動テストの環境が構築されている
- favicon.icoは設置されているか
- サーバ側で生成されるファイルが、サーバに依存していない(S3を使うなど)
パフォーマンス
- 無駄にファイルサイズの大きい画像を使用していないか
- レスポンシブデザイン時に
srcset
を適切に使えているか
- レスポンシブデザイン時に
- ページ表示速度が適切か
- 定期的な測定ができるようになっているか
- Google PageInsightで必要最低限の対応ができているか
さいご
Webサービスの開発に終わりはありません。常に始まりであり終わりであり改良をし続けていく必要があると思っています。自分が作っているWebサービスが解決しようとしている問題を、技術的な部分で邪魔しないように、意識できる所・工夫できる所はどんどん増やしていっていきたいですね😀