あやふやな記憶からFacebookのみなさんに助けていただいたので、記録として。
あらすじ(動画の話をベースに):
情報源
Facebookのスレ:
あやふやな記憶からFacebookのみなさんに助けていただいたので、記録として。
Facebookのスレ:
その1でasobannをAWS ECS上に構築、その2でその構成をCloudFormation化した。したのだけどもう一息、Service Discoveryの対応と、MongoDBのデータ永続化が残っている。
その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://... と書けばよいのだけど、気をつける点(引っかかった点)がいくつか。
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
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に書いてあります。
その1でasobannをAWS ECSに手作業でインフラ構築できた ので、これを自動化したい。CloudFormationかTerraFormを使うといいらしいと聞いて、あまり考えずにCloudFormationを使ってみることにした。これも初めて。
軌道に乗るまでに、こんなことをした。
3番で見つけたテンプレートのサンプルが死ぬほど便利で、助かりました。
必要なリソース種類がわかれば、個々のリソースに必要な設定はリファレンスを見るだけなので、あまり苦労がない。
変える部分を見落とさないためにも、自分の理解のためにも、写経のように見ながら再入力しつつテンプレートを書くことに。理解は深まったけど、見落とし、ぽかミス、勘違いなどでたくさんトライ&エラーを繰り返すことになったのでありました。
あとはまあ、テンプレートを更新して実際にCloudFormationで更新するのに、実行開始→作業中…→失敗!になるまでの時間が割とかかる(10分とか)のが、トライ&エラーを繰り返すのに地味に厳しかった。また失敗したときの理由や状態を調べる方法が、なかなかつかめなかった。
インスタンスやコンテナのことはあるていど探れたが、ネットワーク周りはどうやって調査したらいいのかまだよくわからない。また、ECSのタスクが起動しないで、必要な要件を満たすインスタンスがないよというメッセージを出すのがとても困った。いちおうここにガイドラインがあるけどあんまり役に立たない。
これで環境はほぼ構築できたのだが、まだ動かない。クラスター内でアプリがMongoDBとRedisを見つけるところで、追加の作業(実装も含む)が必要になった。またMongoDBのデータがコンテナ内なので、なにかあったらデータが消えてしまう。その話はまた次回。
asobannは現在herokuにデプロイしてMongoDBを使っているが、「herokuのMongoDBが11月で使えなくなるよー」という通知が来た。なるほど。また、サーバーをスケールするためにはFlask-SocketIOを複数プロセス起動してロードバランサを置かないといけないのだけど、herokuでそれを実現できない。あるいはすごいお金がかかる(herokuのガイド通りに1プロセス1Dynoにすると、ロードバランサ1つ+メッセージキュー1つ+アプリn個ぶんのDynoが必要になる。処理負荷はぜんぜん小さいのにもったいない)。
もともとパフォーマンス改善を考えていて、複数プロセス構成に載せ替えたかったので、これを機会に取り組んでみることにした。いちおう開発環境レベルでは動くようになったので、そこでやったことを主に自分のメモとして、もしかしたら誰かの参考になる情報として、書いておく。どれも、今さら?と思われそうな新しくない要素だが、初めての人はいつでもいるし(←自分)。ハマったところもたくさんあり、調査不足や勘違いが多いけれど、それも誰かの役に立つといいな。
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)を使えばなにもせずそのまま動いたので、結果的にはそれで問題なかった。
前段の実験で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で自動構築できるようにするのだが……次回に続く。
プロダクトバックログアイテム(PBI)が完成したか、リリース可能と言えるかどうかは、開発チームとプロダクトオーナが判断する。判断基準は様々で、ちゃんと動くか、使いやすいか、重かったりしないか、デザインが整っているか、脆弱性が存在しないか、SLAを満たすか、十分テストしてあるか、アーキテクチャの一貫性を壊していないか、ドキュメントは書いてあるか…… どういう基準が必要か、基準をどのくらい緩く、厳しくするかは、プロダクトとチームによって千差万別になる。
受入条件は判定基準として、個々のプロダクトバックログアイテム固有になる。このように機能すること、動作がこうであること、前提がこうなら結果がこうなる、などのPBI個別で定める内容になる。言い換えると、受入条件はPBIの内容の一部、PBIがどんなものか説明する一部だ。PBIをユーザーストーリーで書いていれば、ユーザーストーリーは「プロダクトオーナーと開発チームが会話する約束」なので、会話の中に受入条件が登場し、相互に理解して、スプリント終了までに判定すると約束する。
完成の定義(Definition of Done)は、すべてのプロダクトバックログアイテム共通となる判定基準だ。 プロダクト全体で守るべき非機能要求や、組織・開発・運用などの観点から制約される、あるいは達成したいラインによって構成される。完成の定義は開発チームとプロダクトオーナーが相談して定める。
受入条件も完成の定義も、誰かが判定しなくてはならない。開発チームとプロダクトオーナーが議論したり伝達したりするものだが、最終的に判定する責任は誰にあるのか。受入条件はPBIの内容の一部であり、従ってプロダクトオーナーが責任を持つものだ。そのため判定するのはプロダクトオーナーである。プロダクトオーナーが受入条件を元に自分で受け入れテストを作り、自分でテストするというのが純粋な形となる。この部分だけ取り出すと、請負契約の納品物を発注者の責任で検収するのと同じ構図になる。
完成の定義は開発チームが判定するものである。PBIが完成したと言えるかの条件であり、すなわちリリース可能であるか、スプリントレビューの対象とできるか、受け入れ判定をしてよいかの条件である。受け入れ判定をする前に完成の定義の判定をクリアしないとならない。いっぽう、完成の定義は、PBIが完成してるのかどうか、透明性を高めるための道具立てでもある。そう考えると完成の定義は、判定のための仕事をしなくても判定できるような、見える化や自動化を進めるべき領域でもある。
モブプロ勉強会のリモート開催の可能性を考えて、簡単な素振りをしてみました。
なお来週のテスト駆動飲み会自体は、予定変更せず普通のモブでやる方向で考えています。その後、リモート開催に切り替えることになりました
インセプションデッキ Advent Calendar 2019 - Adventarの参加エントリです。
アジャイルコーチとして活動する中で、インセプションデッキ作りを手伝うことがよくあります。私がファシリテートする場で、どんなことを考え、どんなことをしているのか、思いついたところを書き出してみました。一例として参考になる部分があればいいなと思っています。書き出してみると、我ながらちゃんと考えられていないところがあるなあと気づいたりしますね。
目次
私がインセプションデッキを作るねらいは、参加者がプロジェクトの背景や目的、ねらいとこれからの進め方について議論し、理解し、合意する活動そのものです。その意味で、デッキ作りの成果は、参加者の中にある認識であったり、作ったデッキに対して合意したという仲間意識であったり、これから一緒にやっていこうという意気込みであったりします。
デッキの完成は、そうした成果に比べると優先順位は下がります。個々のスライドの完成度はあまりこだわりませんし、すべて完成しようともしません。組織、プロジェクト、参加者によって重要な要素は毎回異なるので、時間をかけたいスライドも変わってきます。
もちろん完成に意味がないというわけではありません。完成したデッキは有用で、目に付くところに貼っておいたり、将来のタイミングで見直したり手直しをしたりできます。ですが、完成のためにやっているわけではないのだと認識しておくほうが健全です。またデッキの完成を目標にしてしまうと、議論が足らなくなったり、空気を読んで“合意”してしまったり、しやすいようです。
タフクエスチョンカードを、最初に作ります。これは物理的な紙のカードで、「(タフクエスチョンするぞ…!)」という気持ちを表明するのに使います。質問、ツッコミ、反論をするときにこのカードを出してから言うという使い方をします。説明としてはそういうふうに言いますが、実態としては質問することを忘れないための手元のトークンになるものです。
インデックスカードを1人1枚配り、各自カードを作ってもらいます。デザインは人それぞれで、びっくりマークやクエスチョンマークを描く人や、人間の顔を描く人、爆発やツッコミのイラストなどもあります。インパクトあるデザインでもいいし、逆に雰囲気を和らげるように描くのもよいでしょう。
タフクエスチョンカードを手元に置いておくと、質問から議論が起こりやすくなります。話を聞いたときそのまま納得するのではなく、なにかツッコミを入れるように意識が向きやすくなるのではないかと考えています。
インセプションデッキ作りの進め方そのものも、議論と合意で決めていきます。最初に導入として、インセプションデッキの全体像や個々のスライドの内容を簡単に紹介し、スライドのタイトルを付箋に書いて並べます。ここからは、参加者全員でのプロセスになります。どのスライドを選ぶか、どの順番で作るか、どのくらい時間をかけたいか、その場で検討しながら決めていきます。結果として作るスライドを順番に並べたものができます。これはスクラムのプロダクトバックログと同じで、進みながら見直し、随時変更、入れ替えていきます。
スライドを自由に選ぶ流れではありますが、実際には「なぜここ」が外れることはありません。他にも「エレベーターピッチ」「ご近所さん」「夜も眠れないこと」「トレードオフスライダー」あたりは採用されることが多かったです(私がファシリテーターをやるときの話なので、私自身のバイアスが影響してそうな気もします)。順序は「なぜここ」が最初、あとは気になっている箇所を優先していくと、けっこうバラバラになります。Why→Howの順序が崩れることもあります。
少なくとも最初の数枚を決めてから、スライド作りに入ります。1枚終わったら、本当に完成にしていいかを確認したうえで、付箋に大きなチェックマークを付けていきます(確認にはサムズアップ・ダウンを使います)。また、そこで残り時間を確認し、次以降のスライドを見直します。予定時間を調整したり、順序を入れ替えたり、ときには準備が不足だと気づいてスケジュールを立て直すこともあります。
インセプションデッキ作りには、通常オーナーがいます。すでにバスに乗っていて、今回の参加者もバスに乗せたいという意図をもった人々です。この人たちには、作りたいスライドを事前に考えておいてもらい、いくつかは叩き台を準備してもらうこともあります。しかし当日、スライドを決めるに当たっては、そうした意見もあくまでいち参加者の意見として発言し、そうしたい理由を説明してもらい、全員の合意で決めるようにしています。
オーナーは場合によっては、事前に他の参加者に状況説明や参加してほしい理由などを説明していることもあります。インセプションデッキ作りは合意を一とする場で、アイデア出しや各論の検討、議論をするには時間が足りないようです。
(こう書いてみると、まるで根回ししまくりの、当日は意見もアイデアも出ない静寂とした、オーナーの都合に合わせた予定調和の、粛々と進行してシャンシャンとまとまる、つまらない会議みたいに聞こえますね。実際はそんなことなく、それぞれの人がそれぞれの立場から色々な案や反論や感想が出てきますし、はじめに思っていたのとは違う合意があらわれることも多いです。上に書いたような気を使うのは、参加者の数が多く多様で、仕事上の立場もミッションも違い、利害対立する可能性があるような場合で、オーナーの判断によります。一例として、いわゆる顧客や発注側の内部で合意を作りたいときです。)
完成を意識しすぎないよう、インセプションデッキではタイムボックスをあまり設けないようにしています。「なぜここ」を話し始めて、そのまま丸1日まとまりきらないこともありました。
議論をファシリテートするうえでのテクニックとして、時間を意識してもらうことはありますし、割と大事にしてもいます。ですが「このスライドはあと30分で打ち切りましょう」というような進め方は避けています。参加者が話し尽くしたと感じられる、挙げられた論点がひととおりカバーされている、熱意を持って「これでいこう!」という雰囲気ができているかで、終了にしてよさそうか判断します。ファシリテーターや、参加者から「これで完成にしましょう」のような意見が出て、みんなが喜んで合意すれば、完成です。合意できないときや、不承不承な感じがする、疲れてきてどうでもよくなっているようなときは、長めの休憩(15分~30分)をとってから再確認します。
スライドにより、時間あたりの収穫が急に下がるものや、時間で切らないと終わりにしにくいものもあります。最初にタイムボックスを決めることが多いのは「パッケージ」や「夜も眠れないこと」などです。
もっとも参加者が全員集まれる時間は限られているので、いくらでも時間をかけていいというわけにはいきません。時間がかかりそうな部分は、事前に叩き台を準備してもらったり、一部の関係者で議論を進めておいてもらったりするようアドバイスしています。
休憩は重要です。4時間、あるいは丸1日かけてインセプションデッキ作りをするのであれば、休憩もしっかり確保します。休憩には戦略性があって、お手洗いに行くていどの5~10分くらいの休憩は前の議論を続けやすくなります。議論が詰まったような場面では、15分~30分くらいの長めの休憩を取って、頭を切り替えます。場のあったまり方にもよりますが、休憩時間にリラックスしてコーヒーやお菓子をとりながら話しているときが、実は突っ込んだ話ができていたりもします。当然、ちゃんと休むのも大事なので、話が続いているのを遮ってしまうこともあります。
休憩を取るタイミングも工夫ができます。話が盛り上がったとき、特にテンションや感情が高まったときは、すこし落ち着くために休憩を挟むのも有効です。結論や合意になんとなく不満がありそうなときも、いちど休憩を入れて個別に話すタイミングを取るとよいです。デッキ完成など区切りがついたときに取るのもよいのですが、完成したときはテンションが上がっていて、そのまま休まず次に進んだ方がスムーズに移行できるようなときもあります。
なぜここでは、以下のような質問を使って、状況を深掘りすることが多いです。
会社組織の中で立ち上がるプロジェクトでは、プロダクトとして実現したい目標だけではなく、組織の事情、人の都合、予算や年度区切りなど、本質っぽくないドロドロしがちだけど必要な話というのがよくあります(面倒くさい……)。きれいごとだけではない背景まで明るみに出して、コアメンバーで共有するようにします。こうした話はデッキには書き残せないこともありました。
エレベーターピッチには苦手意識を感じています。おそらく、企業の社内システムに関わる仕事が多いためもあって、エレベーターピッチ作りで紛糾したり、議論がダレる場面を多く見ます。エンドユーザーにはっきりしたペインがない、他の選択肢がなく優位性を考えられない、上からやれと言われてやっている感がにじみ出てしまう、などです(そうした認識が表面化するのは、それ自体は有益だと思います)。ステークホルダーが顔の見える特定の個人で、その人を説得する言葉が政治じみていて、プロダクトからフォーカスが外れてしまう場合もあります。
パッケージデザインは、私は心の中で「プロダクトビジュアル」と呼んでいます。プロダクトのポスターを描く形式にすることが多いです。プロダクト名や特徴などの文字も使いますが、なにかしらの絵、イラスト、アイコン、ロゴを入れてもらいます。アイデアを出しながら、全員がペンを握って描くように、絵が上手い人に押しつけるようなことがないようにガイドします。上手だったり綺麗だったりするよりも、参加者全員が「自分が描いた」と感じられる方が大事です。できたポスターはそのまま、チームの仕事場に貼り出しておきます。
ご近所さんでは、顔が見える(個人名が分かる)ようにしています。部署だったり会社だったりではなく、その中の誰が担当なのか、内部にはどんな人がいるのか、何かあったときに誰に連絡することになるのか、わかる範囲で出してもらいます。
トレードオフスライダーの基本4項目(スコープ、予算、時間、品質)だけ議論するときは、必要ではあるもののあまり面白い話にならないことが多いようです。面白いかどうかは、大いに主観ですけれど。それ以外の項目の話が出てくると、このプロジェクトならではの話に発展します。
以上です。アドベントカレンダー次回は、12/21稲野さんの予定です。