woshidan's blog

そんなことよりコードにダイブ。

ネットワークスクランブルHTTP 2版物理本に対する訂正

woshidan.hatenablog.com

2版紙本の(及び1版紙本)にて上記エントリに追加で以下の修正が見つかりました。

電子版では修正されていますが、物理版は修正が間に合いませんでしたので必要に応じて下記をご参照ください。

ページ数 訂正前 訂正後
p.12 ところでTCPのハンドシェイクでは、最初にSYNセグメントを送ったクライアントとサーバからSYN+ACKセグメントを受け取るクライアントが一致しない場合、コネクションが確立されません。これを利用すると、コネクションが確立できるかどうかによって、クライアントが送信元IPアドレスを偽装していないかの確認ができるわけですね。この送信元IPアドレスの真偽確認により、たとえば犠牲者のコンピュータへ大量のレスポンスをサーバに返送させるリフレクション攻撃のようなセキュリティ上の危険を防いでいます。 ところで、TCP Fast Openではハンドシェイクを経ずにアプリケーションデータを送信できます。ということは、いきなり大きなアプリケーションデータのついたSYNセグメントをたくさん送りつけることでサーバのメモリを比較的簡単にあふれさせることができそうですね。
p.12 IPアドレスを偽装してないよ 一度ハンドシェイクをきちんとした相手だよ
p.12 具体的には、偽装してないかの確認でもある初回ハンドシェイクの時点から 具体的には、初回ハンドシェイクの時点から
p.13 サーバに送信可能です。 サーバに送信可能です。サーバはCookieが有効な場合のみ、データをバッファに格納します。
p.26 ファイルパスなどの設定でできる? Webサーバでgzip圧縮用設定を行う際にファイル形式の指定を行うなどの形で可能です[久保達彦・道井俊介(2016)『nginx実践入門』(WEB+DB PRESS plus)、電子版「動的なgzip圧縮」、技術評論社]
p.29 15番 14番
p.51 // style.cssについてはサーバプッシュを行わないが、script.jsについては // style.cssについてはサーバプッシュを行わないが、script.jsについてはサーバプッシュを行う
p.51 HTMLのドキュメント本体の処理と並行してHTMLが必要とするリソースを先読みさせるというものです。 そして、ブラウザ上で動くWebサイトなどのアプリケーションから見た場合、ブラウザがHTMLのパースと並行して先読みするリソースの順序を調整する、というだけで両者に違いはありません。 この挙動はServer Pushのものとよく似ていて、どちらのリクエストに対するレスポンスもブラウザをはじめとしたユーザーエージェントにキャッシュされます。また、どちらで取得されたレスポンスに対する処理もクライアントがキャッシュと合致するリクエストを投げたときに実行します。
p.62 しかし、肝心の実装状況がFirefoxではバージョン58で対応されています が、Google ChromeについてはChromiumの関連issueがオープンのまま未対応という状況なので、 実装状況についてはFirefoxではバージョン58やChromeにて対応されていますがすべてのブラウザでサポートされているわけではないので*1

*1:初版紙本で参考にしていたbugのissuehttps://bugs.chromium.org/p/chromium/issues/detail?id=789599は放置されているが、425 Too Earlyのサポートのリリースノートhttps://chromium.googlesource.com/chromium/src.git/+/58097ec3823e0f340ab5abfcaec1306e1d954c5aを見つけたので変更

技術書典6で頒布したネットワークスクランブルHTTP編とTLS編の訂正

技術書典6で頒布したネットワークスクランブルHTTP編とTLS編の訂正で、以下は再販予定の2版では修正されています。 ご確認いただけますとさいわいです。

基本的に「誤り」=>「訂正後」で、脚注番号やページ番号は訂正前のものをさします。

HTTP編

TLS

  • p.13 「年ごろまで」 => 「2003年ぐらいまで」
  • p.14 「検証速度上昇」 => 「計算速度上昇」
  • p.26 脚注 https://qiita.com/angel_p_57/items/437ca6235defc938b97d => https://qiita.com/angel_p_57/items/f2350f2ba729dc2c1d2e
  • p.27 結構書き換えました。。
  • p.39 「要約データを偽造しにくいように、元データを1bitいじったときにどうなるか予測しづらい」の後ろの部分を削除
  • p.39 図A => Fig.17, Fig.18
  • p.40 図A => Fig.18
  • p.41 TLSの復号時にエラーにならなければ => TLSの復号時にパディングに関してエラーにならなければ
  • p.42 GCMやCCMといったAEADが利用されるようになる前のTLSにおいて、パディングの長さが正しければ一旦その先処理へ進むことができます。 => goのtls眺めてみましたがやっぱり消しておいてください。
  • p.42 図Aの式 => Fig.18の図
  • p.42 脚注 「PKCS#7」=>「PKCS#7 パディング」
  • p.61 脚注 RFC7249 => RFC4279
  • p.43以降の図のキャプション
    • p.43 Fig.18「MACと暗号文の生成の仕方」 => Fig.19「MACと暗号文の生成の仕方」
    • p.47 Fig.19「TLS1.2のフルハンドシェイク(クライアント認証なし)」 => Fig.20「TLS1.2のフルハンドシェイク(クライアント認証なし)」
    • p.48 Fig.20「DHE鍵交換DHE認証のハンドシェイク」 => Fig.21「DHE鍵交換DHE認証のハンドシェイク」
    • p.49 Fig.21「RSA鍵交換RSA認証のハンドシェイク」 => Fig.22「RSA鍵交換RSA認証のハンドシェイク」
    • p.51 Fig.22「TLS1.2セッション再開(セッションID)を行う場合のハンドシェイク」 => Fig.23「TLS1.2セッション再開(セッションID)を行う場合のハンドシェイク」
    • p.56 Fig.23「TLS1.3のフルハンドシェイク(クライアント認証なし)」 => Fig.24「TLS1.3のフルハンドシェイク(クライアント認証なし)」
    • p.67 Fig.24「TLS1.3の鍵スケジュール(一部省略)」=>Fig.25「TLS1.3の鍵スケジュール(一部省略)」
  • p.62 「詳しくは6.4節にて。」=>「詳しくは6.5節にて」

TLS編 修正後の目次

節番号の誤りが多いので訂正後の目次を載せておきます。

f:id:woshidan:20190417173648p:plain:w300

f:id:woshidan:20190417173705p:plain:w300

f:id:woshidan:20190417173724p:plain:w300

TLS編 p.27 周りの修正

p.27の内容は、メジャーな本でしばしば言われている「鍵共有では公開鍵で暗号化して秘密鍵で復号」「電子署名では秘密鍵で暗号化して公開鍵で復号」に対するノラカン説みたいなもので*1、第二版では論旨をもっとよく伝えるために改稿した上で独自研究である旨を明記しました。そのため、差分がちょっと大きいです。

もし、TLS編の修正内容について気になる方がいらっしゃったら、レイアウト崩れの件もありますし、初版のp.27表紙を写真に撮って「ネットワークスクランブルTLS」というタイトルの空メールを bibro.pcg[at]gmail.com まで送ってくださると二版のpdfを先着220名まで、メールもらった日の次の土曜日日くらいまでに送ります*2

*1:他の節と違って節全体の参考文献がないという形で1版でも独自研究であることを示してはいますが

*2:ワンオペなのでご了承ください

JAWS-UG コンテナ支部 #13 に参加しました

ちょっとバタバタしていたため遅くなってすみません。。

jawsug-container.connpass.com

上記にブログ枠で参加したので各発表についてまとめます。

AWS サービスアップデート

toricls さんの発表でした。

EKSのTokyoリージョンおめでとうごさいます!

Kubernetes関連を勉強しはじめだったのでアップデート内容についてはうなずくしかできない...という感じだったのですが、re:Inventのアップデート内容の中で、DNSについて、SQSキューなどのリソースのURIを固定にするためRoute53にCNAMEレコードをたくさん登録したことがあったのであとで試してみたいと思います。

今日から始める人のための Kubernetes on AWS ベストプラクティス 2018版

mumoshu さんの発表。

https://www.slideshare.net/mumoshu/kubernetes-on-aws-2018

まさに、これからKubernetesAWSでさわってみようかな、と考えていたので、各種ツールのアプローチやできることの差異などが非常に参考になりました。

特に前半について、KubernetesAWSで動かすための選択肢として、kops, kube-aws, EKSの選択肢があげられていたのですが

  • 連携しているツールの違い(kops: Terraform/kube-aws: CloudFormation/EKS: SDK, CLIの手作業ベース , kubeconfig(まだ貧弱))
  • サポートしているホストOSの違い(kops: いろいろ/kube-aws: CoreOs/ Container Linux)
  • ツールで扱える範囲(kops/kube-aws: マスタ+ノードの運用少し, EKS: Control-Plane*1の構築、運用)

など、一筋縄でいかなさそうです...。

AWSで仮想コンピューティングが行えるサービスとしては、Lambda・EKS・ECS(Fargate)がありますが、それらの使い分けについて触れられていたのも助かりました。

後半はまた改めてお世話になると思います。本当に濃かったです...。

Kubernetes Scheduler のカスタマイズ

チェシャ猫さんの発表。EKSの話ははじめてだったのでついていけるか不安だったのですが、ECSの復習からはじまったので非常に助かりました!

LTがわりの連続Tweetの最初のアドレスはこちら

メンバーごとのリモートで試せる開発環境を用意したい場合*2に、常にメンバーの人数分のEC2インスタンスを立ち上げていても無駄なので、もしすでにDockerのタスクが動作しているインスタンスがあり、余裕があるなら新しいタスクもそちらに配置するようにしたい、という場合にEKSではどう設定したらいいか、について*3

KubenetersのPod*4のSchedulingは、

  • 配置しようとしているPodの条件に合うNodeをフィルタリングする
  • 残ったNodeを優先度にもとづいてスコアリングする

の二段階で行われています。

EKSを利用する場合、上記を実行するSchedulerの指定が行えませんが、追加のSchedulerを指定することで追加の指定を行うことはできます。

しかし、これも追加のSchedulerをPodごとに用意する必要があったりで煩雑なので、Scheduler Extenderの利用が考えられます。

が、こちらは実行タイミングの問題でEKSのデフォルトのSchedulerでフィルタリングされたNodeを選べない、などややこしく、こういった状況にたいして Scheduling Framework が開発されていますが果たして、という内容でした。

犬でもわかった気になってしまった!!

Firecracker とは何か

理解できているか自信ないですが、今回の発表の中で一番面白かったです。

https://speakerdeck.com/pottava/what-is-firecracker

  • アカウントごとにGuest OSのリソースを確保して動かすから従来のLambda(EC2利用)はリソースの無駄な確保もあり、遅かった
  • アカウントごとではなくプロセス単位で分離できていれば十分なのでより細かくリソースを分離、既に起動しているGuest OSを利用できるので早くなる

くらいのノリで理解していました。

発表とは直接関係ないですが、あとで見返す時用にスライドの4ページの各層についてメモを置いておきます。

  • Our code ... 人間が扱いやすいようなレベルに抽象化された命令が書いてある
  • Lambda runtime ... コードの実行環境。(各種サンドボックス相対パスから見て)Lambdaは特定のバージョンのPythonが実行できるようにライブラリなどを決まったパスに入れている
  • Sandbox ... 外部から受け取ったプログラムを保護された領域で動作させて、システムが不正に操作されるのを防ぐセキュリティ・システムのことをいいます。この決まった領域内(多分usrディレクトリ以下はOKでsys以下はだめとか、そういう)にLambdaの実行環境を構築する https://blog.codecamp.jp/sandbox
  • ゲストOS ... ハイパーバイザによって実行されているOS. Macの中にWindowsが!!! とか?
  • ハイパーバイザ (hypervisor) とは、コンピュータの仮想化技術のひとつである仮想機械(バーチャルマシン)を実現するための、制御プログラム https://ja.wikipedia.org/wiki/%E3%83%8F%E3%82%A4%E3%83%91%E3%83%BC%E3%83%90%E3%82%A4%E3%82%B6
  • ホストOS ... ハイパーバイザを動かしているOS

Envoyを分かりやすく例えつつApp Meshの話をします

_mpon さんの発表。

https://speakerdeck.com/mpon/envoywofen-kariyasukuli-etutuapp-meshfalsehua-wosimasu

App Meshどころか、envoyすら初見だったのですが、nginxでできることに合わせて説明されていたのでめちゃくちゃ助かりました...。 マイクロサービス方面についてもっと調べてみようと思います。

GameDay @ re:Invent のこと

chie8842 さんの発表。

GameDayに行った際、英語がうまく話せなくても自分の担当領域の作業を見つけて実行していく様子や優勝したときに採点されたポイントに他のチームがあまり気づいていなかった話が聞いてて参考になりました。

EKSのいーかんじなサービス公開方法

sugimount さんの発表。

https://speakerdeck.com/sugimount/expose-service-eks-externaldns-acm

EKSでは外部に公開しようと思った時にELBの利用が必要で、その具体的な手順のまとめでした。お名前.comなどからドメインを予約するところから始まっていてありがたかったです。

Virtual Kubelet + Fargate + EKSでノードレス Kubernetes を夢見た話

bbrfkr さんの発表。

https://speakerdeck.com/bbrfkr/virtual-kubelet-plus-fargate-plus-eksdefalsedoresu-kubernetes-womeng-jian-tahua

Kubernetes外のAPIにPodの作成を依頼可能、ということはkubuletが属しているクラスタの外部(たとえば、今回だとEKSのMasterに対してリクエストを投げている)にリクエストを投げられる、ということだったのですが、今回初めてMasterも含めて1つのクラスタの中に構成していく、ということの方を知ったので*5精進せねば...と焦る感じでした。

ECSばかりさわっていて、この辺の話を聞いたことがなかったので大丈夫かな、と思ったのですが、わかりやすい発表が多くてとても助かりました。

*1:kubectlコマンドの結果を受け付けて、そのコマンドによるリクエスト結果をクラスタに反映させるために常駐しているプロセスのことをさすみたい

*2:マルチテナント化、といった用語で説明される状況が恐らくこれ

*3:ECSではBinpackで設定すればよいみたいです

*4:同じ仮想ENIを利用するコンテナの集合。 Webサーバとログサーバを1つのPodに入れて扱うなど。ECSでいうとタスクに近い

*5:https://ja.wikipedia.org/wiki/Kubernetes など

AWS Web Servicesを使ったサーバーレスアプリケーション開発ガイドを読みました

Amazon Web Servicesを使ったサーバーレスアプリケーション開発ガイド

Amazon Web Servicesを使ったサーバーレスアプリケーション開発ガイド

上の本を読みました。全体的に

  • Lambdaになれました
  • Vueになれました*1

という感じなのですが、個人的に特に気になったトピック2つだけ並べておきます。

サーバレスで何をやるにしろまずIAMのことを考える件

  • サーバレスのサービスを利用する

と書いてあったときにやる手順が、

  1. AWSサービスへの信頼ポリシーがインラインポリシーとして埋め込まれたロールを作成する
  2. サーバレスで実行したい関数で使う予定のリソースへのアクセス権限を記述したポリシーを書く
  3. 1.のロールに2.のポリシーを付与する
  4. 関数実行時のロールとして3.のロールを指定する
  5. (あるいは0.)Lambdaで実行したい関数などを書く
  6. 5の関数をデプロイして実行

となっていて、サーバレスをやるぞ、といったときに、想定しているのが5とか6だけだったので、自分がAWSに慣れてないだけかもな、と思いつつも、サーバレスよりまずIAMだなぁ、と思いました。

いや、自分で実行するタイミングとか制御しにくくなるので権限管理は余計に大事なんですがが。

めんどくさがっていても仕方ないので、せめて

  • Lambda
  • CloudFormation

の2つについては、それぞれ何をするサービスで、どういう権限が必要かをざっくりまとめておきます。

Lambda *2

使い方

あらかじめPython3.6など特定のバージョンの特定の言語のプログラムが動かせる実行環境がいくらか用意されているので、利用する実行環境を指定して、zipなどでコードをアップロードする。

実行条件はSDKからの呼び出し、タイマーでの設定(CloudWatchを利用)、他サービスでのイベント駆動(S3にファイルがアップロードされたらメタデータが入ったJSONを引数に実行開始、的な)などがある *3

与える必要のある権限
  • 連携先のサービス(たとえばS3に)に与える権限で、Lambda関数へのアクセスを許可するための権限( lambda:InvokeFunction )
  • 実行するコードでアクセスするリソースへのアクセス権限
  • ログを吐くためCloudWatchへのアクセス権限

CloudFormation *4

使い方

AWS CloudFormationを使うと、設定ファイルを書くことで、AWSのリソースの集合をスタックという単位で壊したり更新したり作成したりできるようになります。

ひらたくいうと、AWSが用意したYAMLで書けるTerraform*5で、大きな特徴として、必ずデプロイ用の設定を保管するS3のバケットを用意する必要があります

Lambda + DynamoDB, API Gateway など、サーバーレス向きサービスだけ利用する場合は、SAMというサーバーレス用のフレームワークを用いて簡潔に記述できます。

与える必要のある権限

CLIで実行している間は開発者がなんでも権限を持っていたりするので気にならないんですが、CIの工程にCloudFormationによる環境構築を含めるときには

  • package コマンドで作成した設定ファイルetc. をアップロードするS3のバケットへのアクセス権
  • CloudFormationで操作する予定のサービスのアクションの許可(たとえば、DynamoDBをいじる設定を書いたのにDynamoDBを作成、削除、取得する権限がないと実行できない)
  • IAMパスロール
  • CloudFormationの変更セット*6を作成するための権限*7
  • Lambda関数などサービスのリソースのロールにポリシーをアタッチ/デタッチするための権限(iam:AttachRolePolicy / iam:DettachRolePolicy)

などが必要です。細かくは、本の p.246~ に例があって勉強になりました。

DynamoDBについて

本書を読んで

という具合に「RDSはLambdaに向かない」の意味を理解して、スキャンなどのAPIもさわって理解した気になっていたのですが、

などのスライドを見ていると、トランザクションの整合性に対する理解が特に弱いし全体的にまだよくわかっていないな...という感じなので精進します。

そういえば、indexを貼ってないキーでの検索はフルスキャンが強調されていますが、indexを貼ってないキーで検索するとフルスキャン、というのはRDBMSでもそうですね。

Kinesisについても触ってはみたもののよくわかってないな、という気持ちがあるのでがんばっていくことにします。

現場からは以上です。

*1:フロントエンドから直接データストア触ることが増えるんだったらバックエンドの人も何が起きてるのか調べるために最低限フロントエンド読めた方がよさそう、と思いましたがほどほどに頑張ります

*2:https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/with-s3-example.html

*3:テスト用のイベントデータ作成に困った時はコンソールでテストイベントの設定をクリックして出てくるイベントテンプレートのサンプルが便利です http://www.atmarkit.co.jp/ait/articles/1706/13/news008_2.html

*4:https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/with-s3-example.html

*5:作成、更新するときは、deployコマンドの前にpackageコマンドを実行するのですが、そうすると更新しようとしている内容を出力ファイルに吐き出してくれます。Terraformがapplyだろうがplanだろうがこれから更新する内容に差分があればコンソールに出力するようになっているので、え、何も出さずに更新するの? と最初少しビビりました

*6:https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-changesets-create.html

*7:CLIで同一のスタックに対し変更 => 再package & deploy を実行していたものの、変更セットについては特に意識せずに終わったので、CLIでスタックを更新する時は裏で作成されているっぽい

Rackの概要、RackミドルウェアとRackアプリケーションの違いについて

この記事はRubyアドベントカレンダーの14日目の記事です。

最近Rackにさわることがあったのでこの記事では

についてまとめます。

Rack概要

RackはWebサーバとRuby及びRubyフレームワークとの間でやりとりをするためのインタフェースを提供するためのライブラリです。

具体的にはRackは

  1. Webサーバに来たリクエストを決まったキーを持つハッシュに加工し *1 、そのハッシュを引数にしてRubyで動いているアプリケーションのプログラムを呼び出す
  2. Rubyのアプリケーションから受け取る['200', {'Content-Type' => 'text/html'}, ['A barebones rack app.']] のような形式の配列を加工して、Webサーバが扱う変数に代入する

のはたらきをする Rack ハンドラー 、設定ファイルを読み込み、実行環境に合わせたWebサーバを起動する rackup コマンド、設定ファイルを書く際に利用する Rack::Builder DSL を提供し、

Rackハンドラーから呼び出されるRuby及びRubyフレームワークのアプリケーションプログラム側には

  • ハッシュを引数に call メソッドを呼ぶと ['200', {'Content-Type' => 'text/html'}, ['A barebones rack app.']] のような形式のレスポンスを返す

という規約( Rackプロトコル *2 )を守ることを要求します。

この規約に則ったRubyのオブジェクトを Rackアプリケーション と呼びます。

f:id:woshidan:20181214222452p:plain

RackアプリケーションとRackミドルウェア

Rackアプリケーションの中には、レスポンスを返すこと自体を目的とせず、Rackアプリケーションの call メソッドの前後に処理を挟むことを目的として実装される Rackミドルウェア と呼ばれるものがあります。

Rackアプリケーション/Rackミドルウェアと呼ばれるものは両方ともハッシュを引数に call メソッドを呼ぶと所定の形式の配列を返します。そういう意味ではまとめてRackアプリケーションといえますが、両者には以下のような違いがあります。

  • rackupコマンドに与える設定ファイル( config.ru )上で利用を指定するときのDSLが異なる
    • Rackアプリケーションは run Rack::SampleApp, Rackミドルウェアuse Rack::SampleMiddleware で指定
  • Rackミドルウェアには追加の規約があり、1番目の引数に app を受け取る initialize メソッドを定義する 必要がある

Rackミドルウェア(Rackアプリケーション)は自分のコンストラクタに引数として受け取ったアプリケーションに、さらにenvを渡す形で入れ子のように実行していくのですが、最後に実行されるRackアプリケーションを特別に エンドポイント と呼びます。

f:id:woshidan:20181214222517p:plain

参考

負荷試験のアプローチとWeb系でクラウドサービスを利用している場合のそれぞれのアプローチに対する感想について

この記事はソフトウェアテストアドベントカレンダーの8日目の記事です。

以前、担当しているサービスの負荷試験を行ったのですが、負荷試験やパフォーマンステストとふわっといった時、いくつかアプローチの種類があるようなので今回の記事ではそれらの簡単にまとめて実際Web系でAWSなどクラウドサービス使っててどう思ってるかについて書きます。

それぞれのアプローチの呼び方については、Oracleの定義*1であったり、ソフトウェアテスト標準用語集*2であったりに違いがあるようなので、この記事ではOracleが使っている名称に基づくことにします。

TL;DR

継続的な負荷の監視は実質的に負荷試験の一種だなぁ、と思いました。

負荷テストのアプローチについて

負荷テストを考える際のアプローチには大きく分けて

  • 性能テスト: 想定している負荷に対し、どの程度のスループットやピーク時の処理量が出せるか
  • 限界テスト: システムが想定している以上の負荷、あるいは限界以上の負荷を与えた場合何が起こるか
  • 耐久テスト: 高負荷で長時間運用時に何が起こるか

の3つがあると思います。実際は要件に合わせて、これらのアプローチを組み合わせて行っていることも多いですが、まずそれぞれのアプローチについてどういったことを計測するのかなどを確認します。

性能テスト

サービスや新機能のリリース前に、システムの結合試験や統合試験ではテスト用のユーザでのみ動作させていると思うのですが、ユーザの数が少ないと遅い処理でも問題なく動いてるように見えます。

性能テストでは、システムが想定している負荷に対し、どの程度のスループット応答時間を返すかを確認します。

限界テスト

システムが想定している以上の負荷を与えて、システムに何が起こるのかを確認します。

たとえば、リクエスト量が想定している負荷の2倍を与えた時のシステムの振る舞い、たとえば、

  • トップページが500エラーになってしまうか
  • 特定のデータストアにアクセスが集中してスループットが下がってしまわないか
  • クラッシュしないか

といったようなことを確認します。

耐久テスト

高負荷で長時間動かした場合にどう動かすかを確認します。

たとえば、

  • 微量のメモリリークがあるプロセスのせいで、メモリ使用量が時間とともに増加する
  • キャッシュの保持期限設定を間違えてキャッシュのメモリが不足する
  • ログの出力が多すぎて、ログファイルがディスクを圧迫
  • 処理にメモリを使いすぎて、GCなどが起こりすぎて処理速度に影響

といったようなことが起きないか確認します。

それぞれのアプローチに対する個人的な感想

個人的な感想ですが、本題です。

性能テストについて

自分がWeb系の自社サービスでAWSなどのクラウドサービスを使っているという前提で話すと、かなり大規模なサービスを持っている会社がそれに付随するようなサービスを新しく展開する、といったような場合以外は、あまり性能テストという形でやらないのかな、という感じです。

  • 小さいリリースを小刻みに行っていく場合リリースごとに想定される負荷があまり変化しない
  • クラウドサービスを利用している場合、サービスを継続的に監視してCPU使用率などが高くなったタイミングで一つ上のクラスのサーバにすぐ変更することが容易

なので、リリース後の継続的な監視で済ますことが多そうです。

弊社の場合、機能のリリース前にステージング環境などある程度用意しているデータの規模が大きい環境でしばらくサーバの負荷に変化がないかなどを監視するといった方法を取ることが多いです。

限界テストについて

テレビCMなどでのアクセス増予測や、新機能リリースのために試験している印象が実はそこまでないです。

  • 新機能がそこまでヒットする予測が毎回ない*3

というのと、

  • 高負荷で障害が発生してからの負荷に対応できるインフラの調達が30分以内とかで可能
    • オートスケールの設定台数を変える(数分くらい)、で済むことが多い
  • 小さいサービスが急にバズって高負荷で止まっても仕方ないよね、頑張ってねという業界の空気がある(気がする)

ので、決済とかクリティカルなサービス以外では、リリース後にその場で対応することが多い印象があります*4

来年ユーザー数を2倍に増やす事業計画とかがあって、その前提にあわせて試験したりすることもあるかな、という感じです。ぶっちゃけると新規リリース以外の場合、リリース直前の試験の段階でパフォーマンスの問題が見つかっても、負荷に対応するためのアーキテクチャの構築、ならびに新しいアーキテクチャへのデータ移行が間に合わないので、先手を打って試験をすることもあります。

テレビで急に取り上げられた場合は違いますが、テレビCMを打つ場合、Web系のベンチャーあたりだと「ここが社運をかけた投資・拡大のタイミング」と踏まえた事業計画に基づいて行なっているので、上記のような事業計画がありそれに沿って動くのかな、という気がしています。気だけですが。

耐久テスト

これもサーバ側だと監視ですます印象です。

  • 監視していると数日に一回メモリ使用量が80%などを超えるサーバがある
    • だいたい再起動など比較的簡単な対応をすればその場はしのげるので暫時対応で時間を稼ぎながら根本対応をしていく

みたいな。リリースしたらお客さんが運用する受託であったり、リリースしたらユーザの手元で動くアプリの場合はまた違うのかな、と思います。

締め切りギリギリのポエムですが、監視は試験である、とはこのことなんだなぁ、ということで現場からは以上です。

参考

*1:https://www.oracle.com/technetwork/jp/ats-tech/tech/useful-class-8-520782-ja.html

*2:http://jstqb.jp/dl/JSTQB-glossary.V2.3.J01.pdf

*3:このお話は一般論です

*4:それでなんとかならないソシャゲとかあった気がしますがどういう修羅なんでしょうか

いまさらRSpecに少し慣れてテストをすっきり書く方法について少し覚えた話

いまさらRSpecをさわってきて慣れて覚えた話もメモしておきます。

TL;DR

  • letで宣言した変数の初期化用パラメータで置き換えたくなったパラメータもletで宣言しておくとよさそう
  • shared_context や shared_examplesでまとめるのがちょうどいいくらいの事例としてちょうどいいのは、外部公開するAPIの異常系のレスポンスのテストくらいでは

慣れてきて覚えた話

letで宣言した変数の初期化用パラメータで置き換えたくなったパラメータもletで宣言しておく

これは同僚の方から教えていただいていいな、と思ったんですが、

describe "post comment" do
  let(:params) { { user_name: "HOGE", comment: "...", "book_id": 3 } }

  context "valid request" do
    it "can be posted" do
      ...
    end
  end

  context "invalid user" do
    let(:params) { { user_name: "", comment: "...", "book_id": 3 } }

    it "cannot be posted" do
      ...
    end
  end

  context "invalid comment" do
    let(:params) { { user_name: "HOGE", comment: "INVALID_FORMAT", "book_id": 3 } }

    it "cannot be posted" do
      ...
    end
  end

  context "invalid book" do
    let(:params) { { user_name: "HOGE", comment: "...", "book_id": nil } }

    it "cannot be posted" do
      ...
    end
  end
end

のような場合、 params の値の一部がそれぞれの context で異なるので、それぞれの context ブロックでパラメータを宣言しなおしていますが、

describe "post comment" do
  let(:user_name) { "HOGE" }
  let(:comment) { "..." }
  let(:book_id) { 3 }
  let(:params) { { user_name: "HOGE", comment: "...", "book_id": 3 } }

  context "valid request" do
    it "can be posted" do
      ...
    end
  end

  context "invalid user" do
    let(:user_name) { "" }

    it "cannot be posted" do
      ...
    end
  end

  context "invalid comment" do
    let(:comment) { "INVALID_FORMAT" }

    it "cannot be posted" do
      ...
    end
  end

  context "invalid book" do
    let(:book_id) { nil }

    it "cannot be posted" do
      ...
    end
  end
end

のように、example内で触る変数はparamsだけかもしれないんですが、それぞれのcontextで注目しているパラメータだけあとで置き換えられるように、宣言の時に工夫しておくとすっきりするなと思いました。

shared_context や shared_examplesでまとめるのがちょうどいいくらいの事例としての外部公開するAPIの異常系のレスポンスのテスト

この話がしたくてこの記事を書きはじめたのに随分長くなってしまったのですが、RSpecでは、shared_contextを使ってspecの中で繰り返し登場するテストケースを

describe "post comment" do
  let(:user_name) { "HOGE" }
  let(:comment) { "..." }
  let(:book_id) { 3 }
  let(:params) { { user_name: "HOGE", comment: "...", "book_id": 3 } }

  context "valid request" do
    it "can be posted" do
      ...
    end
  end

  context "invalid user" do
    let(:user_name) { "" }

    it "cannot be posted" do
      ...
      expect(:status).to eq 400
      expect(:body).to eq "Invalid Request" # このケース自体がいいかは微妙ですが...
    end
  end

  context "invalid comment" do
    let(:comment) { "INVALID_FORMAT" }

    it "cannot be posted" do
      ...
      expect(:status).to eq 400
      expect(:body).to eq "Invalid Request"
    end
  end

  context "invalid book" do
    let(:book_id) { nil }

    it "cannot be posted" do
      ...
      expect(:status).to eq 400
      expect(:body).to eq "Invalid Request"
    end
  end
end

のような場合、

describe "post comment" do
  let(:user_name) { "HOGE" }
  let(:comment) { "..." }
  let(:book_id) { 3 }
  let(:params) { { user_name: "HOGE", comment: "...", "book_id": 3 } }

  context "valid request" do
    it "can be posted" do
      ...
    end
  end

  shared_examples "invalid request" do
    it "returns 400 response" do 
      expect(:status).to eq 400
      expect(:body).to eq "Invalid Request"
    end
  end

  context "invalid user" do
    let(:user_name) { "" }

    it "cannot be posted" do
      ...
      it_behaves_like "invalid request"
    end
  end

  context "invalid comment" do
    let(:comment) { "INVALID_FORMAT" }

    it "cannot be posted" do
      ...
      it_behaves_like "invalid request"
    end
  end

  context "invalid book" do
    let(:book_id) { nil }

    it "cannot be posted" do
      ...
      it_behaves_like "invalid request"
    end
  end
end

のように、同じテストコードを実行している部分を shared_examples に定義して it_behaves_like "SHARED_EXAMPLE_NAME" で呼び出すことができます。

こうするとテストケースはDRYになりますが、

  • 一方でテストが読みにくくなったり
  • バリデーションエラーなどは案外全く同じコードにならなかったり
    • たとえば、上記のサンプルコードは実際はパラメータごとに invalid user などのレスポンスを返したほうが親切でしょう

して、使いどころが難しいんですが、外部公開しているAPIの場合、

  • エラーの内容からAPI内部の処理を推測できないようにわざと詳細なレスポンスを返さないケースがある
  • 外部公開しているAPIが複数あるとしても、機能ごとにエラー時のレスポンスを変えない場合が多い
    • たとえば、一覧取得用API投稿用APIで認証エラー時のレスポンス内容は変えない方が親切でしょう

という感じで使ってみるとよいかもしれないな、と思ったのでメモです。

参考

https://qiita.com/jnchito/items/42193d066bd61c740612 https://qiita.com/etet-etet/items/7babe4856a1cd62b9ecb