こちらの講演時にいただいた質問への回答です。

「テスト自動化とテスト駆動開発」講演資料 - やっとむでぽん

質問1. テスト駆動開発した結果のテストコードは、最終的にテスト自動化に寄与しますか?

はい、テストコード自体は寄与しないことも多いですが、テスト駆動開発自体はテスト自動化に寄与します。

テスト自動化にあとから取り組むとき、最大のネックは「テストが書けない」ことです。これはテストを差し込んだり、テスト対象を独立して取り出したりするのが、依存関係のせいでできない状態です。典型的には、1つの処理が何百行にも渡っていて、一部だけをテストできない。1クラスのロジックをテストしたいのに、他のクラスやデータベース、ファイルやシステム間通信まで必要になって、対象だけを取り出せない。

これらは設計の問題ですが、テストを書こうと思わなければ特に問題にならない場合もあります。(もっと言うと、変更しようと思わなければ問題はない。) そのため、後になってからテストを書こうとすると、設計変更が必要になってしまい、よけいに手間がかかります。

テスト駆動開発を導入していると、個々のクラス、個々のロジックについてテストを書いているので、自動的にテストが書ける設計になります。こうなっていると、テストを書き足すのは簡単です。また、設計を評価する一般的な指標である、高凝集・低結合や、単一責任原則、抽象への依存などにも近づきやすくなります(これは自動的にというよりは、意識しやすくなるという感じです)。そういう意味で、テスト自動化をやりやすい状態が実現できます。

上記の議論は、ほぼユニットテスト(コンポーネントテスト)の話になります。テスト自動化はそれ以外の、統合テストやシステムテストも対象になりますが、こうしたテストについては、テスト駆動開発はあまり関係ないと思います。BDDなど、近いけれど別のアプローチが有用かもしれません。

テスト駆動開発で書いたテストコード自体は、テストの整理(テストのリファクタリング)で整理統合されたり、品質観点のテストを書く中で吸収されてしまったりすることも多く、テスト駆動開発でテストをたくさん書いたから自動化は早く終わる、というふうにはいかないと思います。

質問2. リファクタリングはかなりの高技術だと思います。今回はそこには触れないのでしょうか?

リファクタリングについて、今回の講演では主にその効果の話だけをして、具体的なやり方には触れませんでした。それは実際のコードに触れながらでないと、理解が難しいと思います。和田卓人さんによるテスト駆動開発の講演とライブコーディングの動画が、リファクタリングの実際についても基本がわかるので、おすすめです。

 

youtu.be

ここでリファクタリングについて、もう少し詳細な話を補足します。リファクタリングという言葉は、3つくらいの側面があります。

  1. 技法としてのリファクタリング、「リファクタリング」という名詞 ― コードの動作(機能)を変えずに構造を変えて、読みやすくしたり、変更しやすくする一連のテクニック。ほんの数行を変える程度の、小さな規模のものが主。1つ1つはシンプルだが、種類が多い。
  2. プロセスとしてのリファクタリング、「リファクタリングする」という動詞 ― 一連のリファクタリング(A)を適用して、ソフトウェアの機能を変えずに維持したまま、再構築すること。数時間以上かかることもある。テスト駆動開発のサイクルでは毎回「リファクタリングして」ソフトウェアを直し続ける。
  3. 取り組みとしてのリファクタリング ― 問題意識を持って、ソフトウェアの一部についてきれいに作り直すべく、仕事として取り組む活動。数日から数ヶ月かかることもある。

Aは技法としては簡単ですし、ツール(IDE)が自動実行できるものもあり、誰でもできるようになります。ただしAのリファクタリングには「方向性」がありません。X→Yというリファクタリングも、X→Zというリファクタリングも、Y→Xというリファクタリングもあります。XとYとZのどちらがよりきれいなのかは、リファクタリングを越えた設計論が必要になります。どう命名すれば読みやすくなるか? 長くても読みやすい方がいいのか、短い方がいいのか? 似た処理を共通化した方がいいのか、意味が違うからと別にしておいたほうがいいのか? このメソッドはpublicかprivateか?

Bのリファクタリングは、設計論を知った上で「こういうふうにしたらきれいだろう」という方向性を持ち、そちらに向けてAのリファクタリングを積み重ねる作業になります。そのためには設計論を知っていたり、チームとして合意している必要もあります。

Cは大規模な設計改善です。ひとつのプロジェクトと考えられます。計画もあるし、改善後の設計イメージをあるていど固めてから実施します。

このように考えていくと、リファクタリングというのは確かに高度な技術です。これはしかし、リファクタリングそのものが高度なのではなく、作っているソフトウェアをきれいに直すために高度な技術が必要だと考えることもできます。リファクタリングは、その技術をちゃんと適用していく時間だと言えます。

www.amazon.co.jp

「テスト自動化とテスト駆動開発」講演資料

クライアント企業から依頼をいただいて、「テスト自動化とテスト駆動開発」という講演をしました。その資料を公開してよいことになったので、(若干手を入れて)公開しています。

 

speakerdeck.com

以下のような内容です。 

ねらい

  • 主に顧客向けの業務システム(B2B)を開発している、
  • プロジェクトベース、ウォーターフォールプロセスが主流の開発現場や運用保守の現場にいる、
  • マネージャーのかたに向け、
  • テスト自動化が自分たちのメリットになると納得してもらい、
  • その道筋として2つのアプローチを紹介して、
  • 組織的・長期的に取り組む価値を感じてもらう

アジェンダ

  1. 自動化したい理由
  2. 必要な人材を考える
  3. テスト自動化の端緒 ~テスト駆動開発について~
  4. 深めつつ広げる鍵 ~ペアプログラミングについて~
  5. 見る夢について

質問と回答

また、講演の際に質問をたくさんいただきました。回答をテキストで書いたものを、こちらも公開しています(いきます)。質問11件に対して、Wordで書いていたら21ページ分になったので分割しています。

最初のツイートが400いいね超という、ちょっとびっくりするくらい見てもらえているようです。

 

ここから下は、資料公開時の補足・言い訳です。

講演全体の構成として以下のような話の流れです。現在テストの自動化ができていない、稼働中システムのカバレッジという意味でも、自動化できるスキルが足らないという意味でも、できていないという状況にある。テスト自動化を推進しようと思ったとき、テスト駆動開発に取り組んで少しずつテストを書きながら、メンバーの学習と実践の場を作っていく、という作戦を提案しています。

別の作戦としてはSETの軍団に一気に自動化してもらう(雇用は難しいと思うんだけど発注はできそう)とか、自分たちでやるにしてもE2Eテストの自動化を中心にするという方法も、あると思います。そもそもアーキテクチャから変えればいい、みたいな話もあり得ます。このときはテスト駆動開発は関係なさそうです。

今回テスト駆動開発作戦を選んで提案した理由は、以下のようになるかなと考えています。

  • 中の人が、テスト駆動開発を推進したいというパッションを持っている。組織で推進する上ではとっても重要なファクター
  • 新技術・動向へのキャッチアップの力は組織全体で見たとき強くなく、安定とか効率化への力が強い。雇用も比較的安定している
  • 長期的に組織を強くしたいという、発注者の方の思い
  • テスト駆動開発の話ならできる。他の作戦なら、他の人が話すべき

逆に、そういう状況・前提・背景を取っ払った上でテスト自動化とかテスト駆動開発とかペアプログラミングとかを考えたら「今どきはこうだろー!」とか「こっちのほうが最強!」とか「やっとむてめーわかってないな!」とか、そういうツッコミがたくさんあるんじゃないかとも、思います。たとえば、モブプロじゃなくペアプロを取り上げたのは中の人の希望をいれた結果だったりします(モブプロいいよね。ペアプロもいいんだけど)。

そういうツッコミは歓迎ですし(あんまり痛くしないでくださいね)、意見や議論が出てきて盛り上がったりしたら、また嬉しいなあとも思います。

 

Dave Thomasが"Agile Is Dead (Long Live Agility)"を書いたきっかけ

あやふやな記憶からFacebookのみなさんに助けていただいたので、記録として。

pragdave.me

あらすじ(動画の話をベースに):
インドで開催されるカンファレンスへ飛行機で移動していたときのこと。時間の関係で、登壇者がたくさん乗っていた。自分の前の席にもアジャイルコンサルタントかトレーナーが2人いて、しゃべってるのが聞こえてきた。聞いていると、話題はプログラミングの話へ。
「プログラム書いてないなー」
「もう10年書いてないよ」
「こっちは12年だよ」
 
プログラムを書くことを教える立場の人が、プログラムを書いてない? それってもう書けないくせに教えてるってことだろ!? とすごく頭にきて、帰ってから書いたのが "Agile Is Dead (Long Live Agility)" だったんだ。

 

情報源
InfoQの記事: 
本人がその話をしている動画(24:20くらい):
角さんによる紹介(9枚目): 
 

www.slideshare.net

Facebookのスレ: 

www.facebook.com

asobannのAWS ECSデプロイでやったことと引っかかったこと その3

yattom.hatenablog.com

その1でasobannをAWS ECS上に構築その2でその構成をCloudFormation化した。したのだけどもう一息、Service Discoveryの対応と、MongoDBのデータ永続化が残っている。

SRVレコード対応にはPythonコードの実装が必要だった

その1にも書いたが、起動タイプがEC2でネットワークモードをbridgeにすると、個々のタスクはIPアドレスとポート番号の組み合わせでアクセスすることになる。1個のEC2インスタンスが10.0.0.82にあって、その上にアプリのタスクが3つ、それぞれポート32801, 32802, 32803で動いていたりする。公開ポートはDockerのポートフォワーディングによって、基本ランダムに決まる。ロードバランサーからアプリを指定するのはALBのTarget Groupを使うと、自動でマッピングしてくれる。

いっぽうアプリからMongoDBやRedisを指定するのは、Service Discoveryを使う。Service DiscoveryはプライベートのDNS名前解決なのだけど、DNSでIPとポート番号を両方解決するためにSRVレコードを使う。

このSRVレコードというのは初耳だったが、MongoDBは接続文字列でサポートしていた。mongodb+srv://... と書けばよいのだけど、気をつける点(引っかかった点)がいくつか。

  • 今回のアプリはPythonで、FlaskからPyMongoを使っている。SRVを使う接続文字列をPyMongoで扱うには、DnsPythonが必要で、パッケージをインストールしないといけない
  • サービスのURIのホスト名がhost.domain.tldという具合に3つ分ないと、エラーになる。たとえばmongodb.asobann-dev ではダメで、mongodb.asobann-dev.local ならいける。PyMongoがmongo+srv://に対応するときに利用するDnsPythonの制約。Private Namespaceを作るときに気をつける。
  • mongo+srv://を指定すると自動でtls=trueになるので、明示的にtls=falseを接続URIに付加する必要がある。とドキュメントに書いてあるが嘘で、ssl=falseじゃないとエラーになる。これはPyMongoの実装の問題な気がする。
  • SRVレコードは_Service._Proto.Nameという名前でレコードを作ることになっている。接続文字列が mongodb+srv://server.example.com/ だったら、AWS::ServiceDiscovery::ServiceのNameで指定するのは_mongodb._tcp.server.example.com.になる、という感じ。

Redisはそういう自動でSRVを解決するみたいな機能がまだないみたいなので、自前で何とかしないといけない。さっきDnsPythonを入れたので、以下のようなコードを書いて自分で解決してRedisに接続すればいい。(PyMongo内部の実装 を参考にした。なお本来のSRVの仕様では複数のAレコードに対応したり、TXTレコードから追加情報を引けたりするのだが、今回は端折っている。あとRedisはSSLもサポートしているが、それも省略。)

 def resolve_redis_srv(uri: str):
     '''
     Resolve redis+srv:// and return regular redis:// uri.
     Redis itself does not support connecting with SRV record but current AWS ECS
     configuration requires to use SRV record.
     Does not support TXT record.
     :param uri: connection uri starts with redis+srv://
     :return: redis://host:port/ uri resolved with SRV record
     '''
     assert uri.startswith('redis+srv://')
     import re
     from dns import resolver
     auth, host, path_and_rest = re.match(r'redis\+srv://([^@]*@)?([^/?]*)([/?].*)?', uri).groups()
     results = resolver.resolve('_redis._tcp.' + host, 'SRV')
     node_host = results[0].target.to_text(omit_final_dot=True)
     node_port = results[0].port
     node_uri = f'redis://{auth or ""}{node_host}:{node_port}{path_and_rest or ""}'
     return node_uri

EBSをDockerボリュームマウントするためREX-Rayドライバを使う

MongoDBはAWS DocumentDBを使えばマネージドサービスで楽ちんと思っていたら、課金がインスタンス時間単位で最小構成のdb.t3.mediumでも月額57USDとかになる。これにIO数やデータ量への課金もあるので、今のasobannで使うには贅沢すぎる。

というわけで、MongoDBも普通にServiceとしてECSで動かすことにした。なにもしないと、タスク再起動やEC2インスタンスの作り直しの時点でデータが消えてしまう。そこで、MongoDBのデータのみを別に保存するようにしたい。Dockerボリュームのマウントは、比較的最近(といっても2018年)にサポートされたらしい。EC2起動タイプではREX-Rayドライバを使えばEBSに保存すればいいようだ。

基本的な考え方から手順の解説まで見つけ、ほぼ同内容だけどYAMLで書いてあるものもあった。これに従ってTask Definitionを書き、スタックを更新したらすんなり成功して、よしこれでEC2インスタンスを停止してもデータが残るぞ!と思ったら残らなかった。コンソールから見てもEBSを使ってる様子がない。

結論としては、Task DefinitionのUserDataにあるrexrayドライバー(プラグイン)インストールのスクリプトが動いていなかった。この部分なんだけど、最終的にはdocker plugin ... の行だけにしたら動くようになった。

 #open file descriptor for stderr
 exec 2>>/var/log/ecs/ecs-agent-install.log
 set -x
 #verify that the agent is running
 until curl -s http://localhost:51678/v1/metadata
 do
    sleep 1
 done
 #install the Docker volume plugin
 docker plugin install rexray/ebs REXRAY_PREEMPT=true EBS_REGION=<AWS_REGION> --grant-all-permissions
 #restart the ECS agent
 stop ecs 
 start ecs

このスクリプトの結果は、/var/log/ecs/ecs-agent-install.logでわかる。curlでEC2インスタンス上のECSエージェントの起動を確認しているようなのだが、そんなものは起動していない。stop ecsだのstart ecsだのあるけど、そんなコマンドはない(systemctlのことだろうか?)。Metadataのcommandの02_start_ecs_agentという記述も、同じく動いていない。

   # 関係ない箇所を削除している
   ContainerInstances:
     Type: AWS::AutoScaling::LaunchConfiguration
     Metadata:
       AWS::CloudFormation::Init:
           commands:
             02_start_ecs_agent:
               command: start ecs

最終的にTask Definitionはこう、LaunchConfigurationはこんな設定となって、無事にデータを永続化できるようになった。関係ないけど、execとsetでこんなふうにログが出せるというのは知らなかったので勉強になりました。

ひとまずできた

以上で、asobann開発環境としての扱いで、AWS ECS上に構築できるようになった。この時点のCloudFormationテンプレートはこちらです。実行方法もREADMEに書いてあります。

github.com

yattom.hatenablog.com

yattom.hatenablog.com

asobannのAWS ECSデプロイでやったことと引っかかったこと その2

yattom.hatenablog.com

その1でasobannをAWS ECSに手作業でインフラ構築できた ので、これを自動化したい。CloudFormationかTerraFormを使うといいらしいと聞いて、あまり考えずにCloudFormationを使ってみることにした。これも初めて。

CloudFormationにアプローチしてたくさん間違える

軌道に乗るまでに、こんなことをした。

  1. デザイナーを用いたチュートリアルで流れを把握
  2. デザイナー上で自分で1から作ろうとしてみて、なにを作ればいいのか把握し切れてないことに気づく(コンソールからECSのクラスターを作ったときは、裏側で作ったり設定したりしてくれていたものがあって、それがわかってない)
  3. アプローチについて調べていたらこちらの記事からテンプレートのサンプル にたどり着き、これを参考にすることにした
  4. 手で作ったECSのクラスターからCloudFormationテンプレートを生成しておき、参照できるようにした

3番で見つけたテンプレートのサンプルが死ぬほど便利で、助かりました。

  • どんなリソースが必要で、どうリソース同士をつなげるのか書いてある
  • テンプレートファイルが分割してあり、わかりやすいし、分割するときの書き方もわかる
  • YAMLの書き方、!Refや!Subの書き方の例が豊富にある

必要なリソース種類がわかれば、個々のリソースに必要な設定はリファレンスを見るだけなので、あまり苦労がない。

変える部分を見落とさないためにも、自分の理解のためにも、写経のように見ながら再入力しつつテンプレートを書くことに。理解は深まったけど、見落とし、ぽかミス、勘違いなどでたくさんトライ&エラーを繰り返すことになったのでありました。

  • 複数のアベイラビリティゾーンとサブネットを作って渡さないと、AutoScalingGroupでエラーになる(最低2つ、みたいな)。ここでサブネットを増やしたとき、AWS::EC2::SubnetRouteTableAssociationも合わせて増やさないと、ルーティングが狂って変な挙動になる(EC2インスタンスクラスターのインスタンスにならない、サブネットのルーティングテーブルが勝手に変化する(ように見える)、EC2インスタンスsshできたりできなかったりする)。エラーにはならなかったので気づきにくい。
  • Service Discoveryで指定したホスト名が解決できず、VPCの設定で「DNS解決」と「DNSホスト名」の両方を有効にしたら解決した。テンプレートでは AWS::EC2::VPCに対してEnableDnsHostnamesとEnableDnsSupportを両方trueにする。(DNSホスト名が必要なのは解せないけど、こうしたら動いた)。
  • Service DiscoveryのためにPrivate NamespaceリソースをCloudFormationで作成すると、HostedZoneが作られる(明示的に作らなくてよい)。サンプルにはなかったのでちょっと迷ったところ。
  • LoadBalancerからTargetGroup(アプリ)にアクセスするには、SecurityGroupの設定が必要。LB用のSGとアプリのSGが異なるなら、アプリのSGはLBのSGからのアクセスを明示的に許可する必要がある。手で作ったときはアプリとLBを同じSGにしていたので、気づかなかった。なお本来は同じSGであっても通信は許可されていなくて、これはSecurity Groupについての基礎知識っぽいのだけど、VPC作成時にデフォルトでできるSecurity Groupでは自動で許可されているので、どうやら気がつかなかったみたい。SGさんいつも厳しい。いつか仲良くなれるだろうか。
  • CloudFormationテンプレートを変更してスタックを更新するとき、Task Definitionが変わったりしてタスクが再起動することがあるが、このとき新しいタスクを起動→古いタスクを終了、という順序で動く(これはデプロイの設定で変わるような気がするんだけど、ちゃんと調べていない)。そのとき、新しいタスクを起動する分の余裕(CPUとメモリ)がコンテナインスタンス(EC2インスタンス)にないと、当然ながら起動しなくて、スタックの更新自体も先に進まなくなる(手で元のタスクを停止するとうまくいったりした気がする)。
  • よくわからなくなったら、コンソールからスタックを削除して作り直すと、きれいにうまくいく場合がある。後から考えると、同じ論理ID(テンプレート中のリソース名)で後から矛盾する設定変更をしたせいだった気がする。スタックの削除は普通自動で進み放っておけば終わる(5~10分くらいだった。コンソール上で進捗を追える)のだけど、たまに削除が進まなくなることがあった。VPCが消えないことが多かった気がする。対象リソースをコンソールから手で削除すると、スタックの削除も無事に完了した。
  • なお4番の、構築済みのClusterからCloudFormationのテンプレートを作ってくれるので、それで構築済みのものを見て参考にしようと思ったら、肝心なリソースがぜんぜん入ってなくてダメだった。リソースは手で選ぶので漏れたのかもしれないけど、再確認はしてないのでよくわからない。動いてるやつを元に作ったテンプレートを直すだけって、すごいいいアイデアだと思ったのになあ。

あとはまあ、テンプレートを更新して実際にCloudFormationで更新するのに、実行開始→作業中…→失敗!になるまでの時間が割とかかる(10分とか)のが、トライ&エラーを繰り返すのに地味に厳しかった。また失敗したときの理由や状態を調べる方法が、なかなかつかめなかった。

  • コンソール上でCloudFormationのスタックを指定してイベントを見る
  • ネストしたテンプレートは別のスタックになっているので、そのイベントを見る。削除済みとかのものを見ると新しいことがわかるときもある。
  • 作成したClusterをコンソールで開いて、Serviceを指定して、そこからイベントを見る
  • 作成したClusterをコンソールで開いて、Taskを指定して、そこからLogsを見る。反映が遅いことがあるので、ページ下の方のコンテナから詳細を開いてCloudWatchのログを表示した方がいいかも(CloudWatchを設定している必要がある)。Stoppedのタスクを見ると新しいことがわかるときもある。
  • EC2インスタンスsshして調べる。インスタンス自体のログは/var/logに出ているし、docker execしてコンテナの中を見ることもできる

f:id:yach:20200807154138p:plain


インスタンスやコンテナのことはあるていど探れたが、ネットワーク周りはどうやって調査したらいいのかまだよくわからない。また、ECSのタスクが起動しないで、必要な要件を満たすインスタンスがないよというメッセージを出すのがとても困った。いちおうここにガイドラインがあるけどあんまり役に立たない。

続く

これで環境はほぼ構築できたのだが、まだ動かない。クラスター内でアプリがMongoDBとRedisを見つけるところで、追加の作業(実装も含む)が必要になった。またMongoDBのデータがコンテナ内なので、なにかあったらデータが消えてしまう。その話はまた次回。

 

yattom.hatenablog.com

asobannのAWS ECSデプロイでやったことと引っかかったこと その1

asobannは現在herokuにデプロイしてMongoDBを使っているが、「herokuのMongoDBが11月で使えなくなるよー」という通知が来た。なるほど。また、サーバーをスケールするためにはFlask-SocketIOを複数プロセス起動してロードバランサを置かないといけないのだけど、herokuでそれを実現できない。あるいはすごいお金がかかる(herokuのガイド通りに1プロセス1Dynoにすると、ロードバランサ1つ+メッセージキュー1つ+アプリn個ぶんのDynoが必要になる。処理負荷はぜんぜん小さいのにもったいない)。

もともとパフォーマンス改善を考えていて、複数プロセス構成に載せ替えたかったので、これを機会に取り組んでみることにした。いちおう開発環境レベルでは動くようになったので、そこでやったことを主に自分のメモとして、もしかしたら誰かの参考になる情報として、書いておく。どれも、今さら?と思われそうな新しくない要素だが、初めての人はいつでもいるし(←自分)。ハマったところもたくさんあり、調査不足や勘違いが多いけれど、それも誰かの役に立つといいな。

 

初めてのDocker Composeで、nginxが使えないと知る

Flask-SocketIOを複数並列で動かすには、gunicornのworkerを増やすのでは対応できず、プロセス自体を増やさないとならない。プロセスを増やすということはリバースプロキシあるいはロードバランサが必要になる。またサーバー同士が調整するためのメッセージキューも必要(RedisやRabbitMQなど)。公式ではnginxの設定方法が書いてあるので、これに取り組むことにした。

AWSに上げる前に、まず手元でリバースプロキシ+複数プロセスの構成を実験すべく、Docker Composeを使うことにした。docker-compose.ymlファイルを書くのは今回が初めて。このへんの成果は残っているが、結論としてはnginxをロードバランサにするとどうしても動かなかった。SocketIOがWebSocketに切り替えようとしても切り替わらず、結果としてまったく通信ができない。eventletをgeventに変えたり、gevent-websocketを入れたり、nginx.confの書き方をあちこち調べたりしたけどわからない。ごくシンプルなサンプルを書いて試したけど同じ現象が再現するので、諦めることに。AWS ALB (Application Load Balancer)を使えばなにもせずそのまま動いたので、結果的にはそれで問題なかった。

こんにちはAWS ECS、お前はいつもそうだなSecurity Group

前段の実験でDockerコンテナで動くようにしてあるので、AWS ECS (Elastic Container Service)を使うことにした。ECSはパフォーマンステストを作ったときにも使っているので、初めてではない。Docker Composeで一発デプロイができたら嬉しい気もしたけれど、まだそこまでECSの気持ちがわからないので、まずはWebコンソールからひとつずつ手作りすることにした。Cluster、VPC、Task Definition、Service、Load Balancerと作っていく。

起動タイプはEC2に。Fargateではネットワークが awsvpcになってネットワークの設定がシンプルになるが、軽いタスクをたくさん上げるとherokuのDyno数と同じ問題でお金がかかる。EC2 Launch Typeで1インスタンスに軽いタスクを複数入れて安上がりにしたい。最終的にはEC2のt3.smallインスタンス1個にMongoDB+Redis+アプリ3つが収まって、月15USDくらい(us-east-1リージョン)。

ところがEC2でawsvpcに設定すると、EC2インスタンスのタイプによって持てるネットワークインターフェース(ENI)の数に制限があって、たくさん実行できないと制限があり、今回はここに引っかかった。いくら待ってもTaskが起動せず、コンソール上でServiceのイベントを見ると、条件の合うインスタンスがないからタスクを起動しないよというエラーになっている。調べていたら、ENIの数に制限があるというドキュメントにたどり着いて原因がわかった。いったんawsvpcで作ってしまったので、いまは練習として放置し、EC2インスタンスを2つにしてしのごう。なおいま調べたら2019年にENI制限が緩和されたらしいけれど、NITRO以降のタイプのみだったので関係ない。

今回は練習としてawsvpcのままだが、本番のときはネットワークをbridgeに設定する。こうするとENI数の制約はなくなる。同じIPに複数のTaskが存在して、ポート番号で呼び分けることになる。Service Discoveryを使うとき、AレコードではなくSRVレコードを使う必要が出てくるのだが、これがあとで一手間増える原因となった。

ALB (Application Load Balancer)もServiceを作るときに設定する。ALBはそのままでWebSocketに対応していて追加設定不要だが、SocketIOにはスティッキーセッションが必要なのでそこだけ設定をONにする。設定はしたのだけど、ALBのURLをアクセスしても503になってアプリに到達できない。これはSecurity Groupの設定が悪くて、ALB自体にもSecurity Groupが働くので、ポート80番を開けておかないといけない。Security Groupはいろんなところに影響するのに、設定が悪いと気づける方法がなくて、いつもハメられている気がする。キミの気持ちがわからないよ。

続く

これでいったんAWS ECS上でasobannが動くようになった。固定URLがないし、MongoDBがコンテナで動いているので何かあるとデータが消えてしまうが、開発環境という位置づけならとりあえず大丈夫だ。httpsがないのはちょっとダメかも。

今度は今回手作りしたものを参考に、CloudFormationで自動構築できるようにするのだが……次回に続く

 

 

yattom.hatenablog.com

yattom.hatenablog.com

 

 

受入条件はプロダクトオーナーのもの、完成の定義は開発チームのもの

プロダクトバックログアイテム(PBI)が完成したか、リリース可能と言えるかどうかは、開発チームとプロダクトオーナが判断する。判断基準は様々で、ちゃんと動くか、使いやすいか、重かったりしないか、デザインが整っているか、脆弱性が存在しないか、SLAを満たすか、十分テストしてあるか、アーキテクチャの一貫性を壊していないか、ドキュメントは書いてあるか…… どういう基準が必要か、基準をどのくらい緩く、厳しくするかは、プロダクトとチームによって千差万別になる。

受入条件は判定基準として、個々のプロダクトバックログアイテム固有になる。このように機能すること、動作がこうであること、前提がこうなら結果がこうなる、などのPBI個別で定める内容になる。言い換えると、受入条件はPBIの内容の一部、PBIがどんなものか説明する一部だ。PBIをユーザーストーリーで書いていれば、ユーザーストーリーは「プロダクトオーナーと開発チームが会話する約束」なので、会話の中に受入条件が登場し、相互に理解して、スプリント終了までに判定すると約束する。

完成の定義(Definition of Done)は、すべてのプロダクトバックログアイテム共通となる判定基準だ。 プロダクト全体で守るべき非機能要求や、組織・開発・運用などの観点から制約される、あるいは達成したいラインによって構成される。完成の定義は開発チームとプロダクトオーナーが相談して定める。

受入条件も完成の定義も、誰かが判定しなくてはならない。開発チームとプロダクトオーナーが議論したり伝達したりするものだが、最終的に判定する責任は誰にあるのか。受入条件はPBIの内容の一部であり、従ってプロダクトオーナーが責任を持つものだ。そのため判定するのはプロダクトオーナーである。プロダクトオーナーが受入条件を元に自分で受け入れテストを作り、自分でテストするというのが純粋な形となる。この部分だけ取り出すと、請負契約の納品物を発注者の責任で検収するのと同じ構図になる。

完成の定義は開発チームが判定するものである。PBIが完成したと言えるかの条件であり、すなわちリリース可能であるか、スプリントレビューの対象とできるか、受け入れ判定をしてよいかの条件である。受け入れ判定をする前に完成の定義の判定をクリアしないとならない。いっぽう、完成の定義は、PBIが完成してるのかどうか、透明性を高めるための道具立てでもある。そう考えると完成の定義は、判定のための仕事をしなくても判定できるような、見える化や自動化を進めるべき領域でもある。