趣味でも業務でも日々Webサービスを開発しているzaruです。こんにちは。ついにアドベントカレンダーも最終日です。まだサンタとしての仕事が残っています。さて今回は仕事としてWebサービスを開発するときに気をつけたいポイントを紹介します。まぁ仕事に限った話じゃないですが…参考になれば幸いです。特に新卒プログラマあたりに読んでもらえればと思います😀
なお僕の業務上インフラ周りはAWSが多いです。
RASISという指標
RASISという指標があります。コンピュータシステムの評価指標5つの頭文字を取ったものです。 ●Reliability︵信頼性︶ ●Availability︵可用性︶ ●Serviceability︵保守性︶ ●Integrity︵保全性︶ ●Security︵機密性︶ 今回はこの5つの指標に沿ってポイントを紹介していきます。RASIS自体については色々なところで解説されていると思うので、今回は省きます。 ●RASIS - Wikipedia信頼性
システムの故障しにくさを表します。デプロイ/ロールバックの手順を完璧にしよう
Webアプリはデプロイしないと始まりません。まずはデプロイを自動化しましょう。誰がやっても必ずデプロイできるように手順を明記し環境を作りましょう。今時のWebアプリであればデプロイツールをCI通じて、マージされたらテスト実行して自動デプロイ…というのが一般的だと思います。僕の場合はCapistrano + CircleCI + GitHubで構成することが多いです。 また、よく見落としがちなのがデプロイのロールバックです。新機能をリリースしたは良いけれど思わぬ障害が発生し、とりあえず前のコードに戻そう!ということがあります。嫌ですね😖。でも大丈夫、たいていのデプロイツールにはロールバックの機能がついています。 ただし…ロールバックするのですがぶっつけ本番で、使い方もわからないし動かしては見たけど別の障害が発生してしまった…!ということがありえます。二次災害怖いですね。 大切なのはWebアプリの本番公開前にかならずデプロイとロールバックのテストをして手順を完璧にしておくことです。心理的不安をなるべく取り除いていきましょう。例外設計をしよう
例外設計をしようというのは至極当然の話なんですが意外と抜けていたりします。コードレビューをする中でも特に気をつけたいところです。正常系だけを想定してコードを書いて手元では上手く動いても本番の環境では上手く動かないことがあります。というか、たいてい上手く動きません。 僕の中では以下の様なことを心がけています。 ●例外を乱用しない ●例外は呼び出す側がINPUTの条件を満たしているが、呼び出された側でOUTPUTの条件を満たせなくなったときに投げる ●例外は無視しない ●受け取るけど握りつぶさない ●だましだましでアプリを延命させない 例外については下記の記事が大変参考になります。 ●Railsアプリケーションにおけるエラー処理︵例外設計︶の考え方 ●例外設計における大罪 ●Java/Androidにおける例外設計、あるいは﹁契約による設計﹂によるシンプルさの追求死活監視/障害検知をしよう
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を利用する 参考記事 ●ちょっと待って!Auroraを使う時にMulti-AZが本当に必要ですか?属人化を防ぐ
インフラなどの単一障害点をなくす話はよく出てきますが、人的単一障害点もなくすようにしましょう。組織としてのスケールと強さが出てくるところだと思います。そのためには属人化を防ぐ必要があります。 ●ドキュメントを整備して誰でもできるようにする ●レビュイー固定をなくしてレビュー速度を上げる ●こんな固定タスクがあるよ!と声を上げる 参考記事 ●大規模サービスあるある、”属人化”解消のための開発体制パターン保守性
メンテナンス/障害対応のしやすさ。テストコードを書こう
散々言われてきていることなので割愛します。テストコードを書きましょう。TDDである必要も、カバレッジ100%である必要もありませんが、テストコードを書かなくても良いということはないと思っています。 ●たった1人から始める社内テストコード文化 ●テストコードの期待値はDRYを捨ててベタ書きする ~テストコードの重要な役割とは?~ドキュメントを残そう
ドキュメントを残すことを残業のように思うのではなくドキュメントも含めて開発という意識にすると良いと思います。コードと同一視するということです。そうすることで、関係者誰でも閲覧・更新ができて、場合によってはバージョン管理することで差分を確認することができるようになり、ドキュメントを育てる土壌の第一歩が踏めます。 ドキュメントを育てる土壌に必要なのは以下のようなポイントだと思っています。 ●ドキュメントの場所を集約する ●Markdownなどのプレーンテキストに近いフォーマットを採用する ●誰でも閲覧・更新できるようにして、チームの共同作業と意識する ●必要十分に書き、最初から全てを網羅しない。必要になったときに追記する ●必要なくなったドキュメントは消す障害情報をログに残そう
業務で開発をしていると必ず障害対応というものに出くわします。突発作業になることが多いため、どのような原因で、どうやって対処したのかをドキュメントに残さず終了してしまうことがありますが、それではチーム全体で共有できず再発してしまう可能性や、他プロジェクトを開発する際に教訓を活かせなくなってしまいます。このログが明日の自分を守る武器になります。 ●システム障害と僕達はいかにして戦えば良いのか、障害対応について考えた読みやすいコードを心がけよう
これも散々言われてきていることですね。まずはリーダブルコードを読みましょう。そして、チームで読みやすいコードとはなにかを議論しましょう。考え方が違うとコードレビューで無駄なやり取りが発生してしまいます。縛りすぎないコーディング規約を設定するのも良いと思います。開発環境を簡単に作れる
一人でガリガリと書いているような環境だと、開発環境の構築がおざなりになってしまい、他のプログラマがジョインした時に環境構築だけで1日が終わる…しかもドキュメントが残ってないから質問にも答えなきゃいけない…ということになります。 今であればVagrantやDockerがありますし、プロビジョニングツールもChef / Ansible / Puppet / Itamaeといろいろあります。構築ドキュメントも含めて誰にも聞かずにすぐに立ち上げられるように最初から準備をしておきましょう。負債を意識して使い、返済計画を作る
業務なので必ず期限が設定されています。そこに間に合わせるために生み出してしまった負債はちゃんと意識しましょう。後で取り返しのつかないような負債だけは積まないようにしましょう。取り返しが付くのであれば、いつどうやって返済するかの計画を、コードレビュー時にチームメンバーと共有すると良いと思います。 ●技術的負債に立ち向かうための心構え当たり前だけどバージョン管理をしよう
これは…さすがにね。保全性
データの矛盾がなく一貫性が保てるかを表します。モデル設計は最後まで拘ろう
モデルの設計が間違っていると後々までずっと負荷をかけてきます。常に大リーグボール養成ギブスをしているようなものです。リリースする最後の最後まで拘って、あやしいところはコストかけてでも直したほうが長期的に見たらメリットがあると思います。トランザクション
新人のコードレビューをすると、トランザクションは知っているけど適切に使われていないというケースを時々見かけます。データの一貫性を保つのは非常に大事です。 ●良く分かるMySQL Innodbのギャップロック ●MySQLのトランザクション制御がキモい話 ●メールのトランザクション設計DBの制約を利用しよう
まずは、SQLアンチパターンを読みましょう。そしてNOT NULL制約と外部キー制約をうまく使いましょう。 ●データベースアプリケーション開発を炎上させる負のスパイラル機密性
セキュリティ/不正アクセスがされにくいことを表します。SSLは全ページで使う
とりあえずSSLを使うかどうかは迷うわず全ページ常時SSL一択にしましょう。購入費用自体も安価になりましたし、Let's EncryptやAWS ACMなど無料で取得できる証明書も登場しています。昔言われていた暗号化する際のCPUコストも正直無視できるレベルです。 なぜ常時SSLにするのかというと ●HTTP/2が利用できる ●HTTP / HTTPSの切り替えを行う必要がなくなる というメリットがあるからです。 またSSLの診断ツールもあるので適宜利用しましょう。 ●SSL Server Testパスワードはまじでハッシュ化しよう
個人情報の流出でパスワードがそのまま保存されていました…とか、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を理解して運用できる技術者が必要なのと対応工数のコストが、得られるメリットに見合わないと感じているからです。とはいえ、思考停止で無効にするのはアホなのでちゃんと向き合う必要があるとも思っています。 ●﹁SELinuxのせいで動かない﹂撲滅ガイド ●SELinux を使おう.使ってくれ.オレオレフレームワークは使わない
素直にRailsを使って周辺ライブラリ含めて適切にバージョンアップしましょう。 ●フレームワークに見る Web セキュリティ対策 勉強としてのオレオレフレームワーク構築は非常に良いと思います😀おまけ
パフォーマンス
Webサービスが想定しているユーザ数・トラフィック・データ量を適切に処理できるシステム設計になっているかを検証しておきましょう。極端ですが一人しか使わないようなシステムに複雑な分散アーキテクチャを適用しても豚に真珠🐷💎状態ですし、数億レコードを超えるようなシステムで1台のMySQLサーバだけというのは無理があります。心構え的な所
昔書いたものがあるので良ければ読んでみてください。 ●良いプログラマとは、僕が今思っていることをまとめてみたリリース前チェックリスト
Webサービスを開発/リリースする前にチェックするリストです。会社やチームによって細かいところは違うと思いますが、思いつくものをリストアップしてみました。サーバ
●エラー検知ツール︵sentry|AirBrake)を導入している ●リソースモニタリングツール︵zabbix|newrelic︶を導入している ●アラートがslackやメールなど担当者に適切に届くように設定している ●ソースコードのバージョン管理がされている ●GitHubでプルリク開発フローを整備し、ドキュメント化されている ●サーバの起動/再起動/停止などのオペレーションがドキュメント化されている ●Itamae、Chefなどでインフラの自動構築が本番・ローカル問わず可能 ●開発環境構築がドキュメント化されている ●ステージング環境が存在している ●ステージング環境にはBASIC認証などがあり、Googleにクロールされないようになっている ●ダウンタイムなしで、スケールアウト/サーバの入れ替えが可能な構成になっている ●メール送信が迷惑メールにならないように設定されている ●AWS SESの場合 ●DKIMを設定している ●バウンスメール/迷惑メールに対応している ●各種アプリケーションのログがローテーションされる ●想定トラフィックに耐えられるか事前に本番データ量で検証している ●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で必要最低限の対応ができているか