なぜアジャイルの方が成功しやすいのか?

スクラムギャザリングというカンファレンスの実行委員会がありました。実行委員会といっても、会議をやってるというより、淡々と決めて作業を進める会です。このカンファレンスは2011年からやっていて、来年でちょうど10年になります。紆余曲折ありながらも、ここ数年は実行委員業も枯れてきて、ああうまくいくアジャイルチームってこういう特性あるのかもな、と気づくところもありまして。今日はそのあたりを記してみます。

今日は趣意書の公開まで

今日は 4-5時間ほどで、趣意書(スポンサー向け資料含む)と、スポンサー受付のサイト作成までが終わりました。ちなみにWebサイトまでは終わらなかったので基盤まで整えたところで、次回の作業になりました。

趣意書はこちらです。

f:id:wayaguchi:20190630072527j:image

f:id:wayaguchi:20190630072338j:image

うまくいってることは変えないでおく

やりながら考えたことは、

  • うまくいっていることは変えないでおく
  • というか問題なければ変えない
  • なので問題だけ議論して決めて
  • なる早でなるべく細かい単位でデプロイする

ということです。

趣意書は去年のものをベースに、昨年実績の追加記載と会場が変わるのでそれに伴う変更を入れてます。逆にいうとそれ以外は変えてません。

チームの中で作業を進めているので、面倒くさいところや、議論は盛り上がるけど前に進まなさそうな点は「あとで必要になったら話そう」と一旦話を切って進めました。今日中にある程度アウトプットしたいからです。

議論と作業のバランス

分業をしていないので、議論も作業も同じ人たちがやります。決定や作業計画だけ詰めたところで、作業ができなければアウトプットが出ません。一方で議論や決定が必要な部分ももちろんあるので、適宜、必要なタイミングで議論をやっていきます。

これが、多重請負のウォーターフォールだったとしたらどうなるでしょう。「私たちの責務は意思決定。今日は議論と意思決定に集中して、実作業は全体工程が決まった後でやりましょう。作業はそれぞれの担当や、その道のプロにお願いしましょう」ということになりがちかなと思います。たぶんそれ自体は「そういうもの」なのだと思いますので、悪い話ではないと思うのですが、問題は「いつ結果がわかるか?」です。もし今日、方針やスケジュールだけ決めたのだとすると、間違いがわかるのは実装後になります。来月か、もっと先か。

一方、趣意書の実装と即日公開で得られたものは以下です。

  • 会場見取り図でスポンサー受け入れ可能数を見積もる
  • 収益見込みを確認し、キャッシュフローとリスクのある変数について見当をつける
  • 公開後、すぐにスポンサーの応募を数件いただく。それによって趣意書が機能しているという認識を持つ
  • スポンサー申し込みフォームも機能していることがわかる。いくつか細かな改善を行いつつ今後のオペレーションの見当がつく

このようにいくつかのアウトプットと、その結果のアウトカム(成果)がありました。最大のアウトカムはスポンサーのお申し込みをすぐもらえたことです。嬉しい誤算。本当にありがたいご支援でした。

イムリーなアウトプットがアジャイル成功のヒント

ほとんどが去年と同じことをしているにもかかわらず、「マンネリ」「つまらない」と感じることがない点も特筆すべきかもしれません。それどころか一年ぶりの作業なので、むしろ多くの発見や学びがありました。使っているサービスの機能が進化していたり、逆にうまく動かなくなってて直したり。ConfEngineというサービスの開発者に「前回のイベントからのコピーで新しいイベントを作る」という要望を出していたのですが、なんとそれが実装されていて、喝采が上がりました。

これももし多重請負のウォーターフォールだったら、企画会議で「昨年と同じではつまらないのではないか?」という話になって、アイデアが足された挙句に、実施部隊に情報が渡るのが遅れ、実施部隊は膨らんだ企画と変わった状況の狭間で右往左往しながらギリギリでリリースにこぎつけた後に問題が発覚して予定外かつ緊急の作業に追われる未来が見えます(知らないけど)。

アジャイルでは、というか別にアジャイルと呼ばなくても良いのですが、私たちが重視していたのは、ローコストで、タイムリーに、アウトプットしながら、結果を得て、それを通じて学びを得て、時間内で手仕舞いする。...ということです。私たちはアジャイルの実践を通じて、この「あたり前」のことを学んできたに過ぎないのかもしれません。

イノベーションは安定したチームから

あらゆる企業活動にこれが通じるなんて思いませんが、多くの作業はこうして日々回っていくような気もします。その結果、本人たちすら気づかないうちに「簡単には真似できないチームのノウハウ」が溜まってるような気もしなくもないです。小さなイノベーションの積み重ねは安定したチームから。...そんな気もしました。

きっとそんなことはみんなわかってるけど、意外とそこまで我慢できずに新しいアイデア投入をやってしまったり、人を入れ替えてみたり、売り上げが立たずに予算が尽きたり、ステークホルダーや偉い人お客さんに振り回されたりするものなのかもなー、とも思います。頑張っていきましょう。

アジャイルを始める時の成功のヒントは、今いる人で今できるアウトプットを最速で目指してみる。...そんなところにあるんじゃないかと思ったのでした。

 

...というわけで、今日も作業が進んで満足でした。早速のスポンサーのお申し込みもありがとうございます。「ほんとこのカンファレンスは愛されてるね(私たちではなく)」ということで、またちょっとやる気も補充できました。フィードバック大事ですね。次回作業日を決めて今日は終了しました。

 

P.S. 4-5時間作業して3時間飲んでたみたい。ワークライフバランス。まあ仕事じゃないですけど。

f:id:wayaguchi:20190630072850j:image

 

実行委員のてやまぐさんの資料もいいですね。こんな話をいつもしてる。XP祭りの実行委員も2010年くらいからやってました。

Conference Organize Tips at 2019/06/22 #devlove #devlovex

Electron + Angular でマルチウインドウタイマー

前回の続きの作業記録です。

kawaguti.hateblo.jp

 

Electronでのマルチウインドウ化

マルチウィンドウを積み残しにしたのでその点を進めました。

Electron ではウインドウを起動するために、new BrowserWindow する、という記述があり、これを使うことを考えました。

BrowserWindow | Electron
... が、このウインドウはネイティブのウインドウでした。今回欲しいのは JavaScript 上で起動し、JavaScript で通信できる別ウインドウなので、ちょっと用途が違いました。

今回、ここの理屈を理解するまでに多くの試行をしました。最終的に、イベントでモブをしまして、よた(@y0t4) さんの手助けもあって、筋違いに気がついた次第です。モブすごい。

ayumi-hsz.hatenablog.com

 

ほんとうに必要だったものは window.open

結局のところ、必要なコードは window.open でした。あ、それもう20年ぐらい使ってるわ。どうってことのないこの一行のために費やした時間は数日(のちょっとした時間)。学びが多いです。

window open · kawaguti/tiimer@8618f42 · GitHub

ということで、二枚目をウインドウを起動できるようになりました。普通にJavaScriptなので、複数ウインドウ間のメッセージングについても、前回と同じく npx-multi-windowが使えそうです。

Angular 7 でカウントダウンタイマー - kawaguti’s diary

ということで、組み込みました。

普通にメッセージのやり取りができるようになったので、引き続き、メッセージにコマンドを仕込んで、時計のセットをできるようにして、アプリ完成です。

f:id:wayaguchi:20190623062358p:plain

 

Mac用にパッケージ

Mac OSX 用にパッケージしたものをこちらにおいておきます。セキュリティ上、App Store以外のものは動かないと思いますので、実行する場合は、詳細設定-セキュリティ から許可をしてあげる必要があります。

https://www.tiimer.net/tiimer-darwin-x64/tiimer.app.zip

Windows用も動作したのですが、うまく配布用のZipが作れていないので、また調べまーす。

 

はこだて未来大学さんと DevLOVEXさんでお話しました

先週、はこだて未来大学の大場みち子先生に呼んでいただきまして、学生さん向けにアジャイル開発のお話をしてきました。どうしてそういうものが生まれたのか、というところを私なりに整理してみたものです。「アジャイル開発の時代」という大仰なタイトルになっており、恐縮です。釣りバカ日誌の話をしたかっただけです。

speakerdeck.com

 

また、昨日は DevLOVE Xさんに呼んでいただきまして、モブプロのお話をしました。こちらは5月の連休で行ってきた Hunter Industries さんのモブプロの報告になってます。

speakerdeck.com

学んだことの詳しい内容は、以下の記事ですでに書いていますので、よかったらどうぞ。

kawaguti.hateblo.jp

kawaguti.hateblo.jp

並行セッションの裏番組の登壇者の皆さんが豪華すぎて、全部聞きに行きたい感じでした。そんな中で選んで聞いていただいた方に大感謝です。同窓会のようなカンファレンスに呼んでいただき、ありがとうございました。楽しかったです。今日は二日目、盛会をお祈りしております。

 

f:id:wayaguchi:20190623064646p:plain

 

Electron でカウントダウンタイマーをアプリにする

前回のカウントダウンタイマーに関して、早速ネットで繋がってないときにも使いたいという要望をいただいたので、Electronを試してみようと思いました。....の、作業ログです。

kawaguti.hateblo.jp

Chroniumブラウザを使って Webを単体アプリにパッケージするElectron

Electronというのは、ChromeやSaferiなどのブラウザのコアになっているChroniumを使って、Webフロントエンドの技術でスタンドアローンのアプリを作れるというものです。必要なソースを全部固めてexeとか実行ファイルにするツール群とデバッグ環境、という感じかなと思います。

人気のあるクロスプラットフォームエディタのAtomとかVSCodeがElectronで作られているそうなので、信頼性もありそう。

Electron Apps | Electron

2000年代前半くらいにあったWindowshtaという仕組みがあるのですが、それに近い感じもします。AngularやReactなどのWebフロントエンドのツールがそのまま使えるというのが面白いところかなと思います。普通にツール作るならなんとなく Swift とかのほうが簡単なんじゃないかと思うのですが、クロスプラットフォームで動くものを作れる(ChroniumとかJavaScriptのおかげ)ので、そういうときには便利なのではないかという感じがします。その分複雑度は上がってしまうので、ハマった時に抜け出すにはそれなりに熟練が必要そうな気がします。まあなんでもそうですね。

とりあえずマルチウインドウは避け、カウントダウンタイマーが動くところまで

今回はまず Electron で Angular のカウントダウンタイマーが動くところまでやりました。マルチウインドウでの同期は少しハードルが高いかもしれないので、まずはありがたいサンプルの組み合わせで、できそうなところまで。

前回のソースは agx-multi-window を組み込んでしまったので、その前まで戻します。別のフォルダを切って一からやることにしました。

ElectronでAngular初期画面まで

結構ここで時間を消費して調べることになってしまったのですが、AngularもElectronも変化が速くて、しかも別の組み合わせもあるので(React+Electronとか)、ハマった時に情報を調べるのがなかなかしんどかったです。Angular7 + Electronというキーワードで見つけたこちらがドンピシャでした。

qiita.com

ということで淡々と作っていきました。

npm i -g @angular/cli

ng new single-window

cd single-window

npm install --save-dev electron@latest

npm install --save-dev electron-packager@latest

npm install --save-dev ngx-build-plus@latest

https://github.com/kawaguti/tiimer/commit/fa170f51078e35a0ea80f51516cffc5e060ec902

プロジェクトを作ったら、上記のサイト通りに修正を入れました。

https://github.com/kawaguti/tiimer/commit/122398d0a3b38053134dd5c785fd682ec83ce5ba

実はこの時、ちょっとした失敗をしました。package.json の main 項の追記を忘れて、ビルドエラーはないのに起動しないという状況になりまして。手がかりが見つからずに数時間迷子になりました。簡単なサンプルが動かないと、疑うところが無限にあってしんどいですね。ミスなのでだいたい情報もなくて。

"main": "main.js",

f:id:wayaguchi:20190616194921p:plain

別の環境でもう一回作ったらけろりと出なくなって、気がついた次第。対照実験だいじです。

ちょっとハマった先で Angular の初期画面がElectron上で表示できたときには感動しました(自作自演)。同じプロジェクトで Web版も動きます。

f:id:wayaguchi:20190616195601p:plain

リアルタイム時刻表示は動くか?

次は時刻表示です。rxjs の Observable (pub/subイベント機構) が動くことの確認です。上記のサンプルがちゃんと動いたので、なんとなくシングルウィンドウものは動きそうな感じがしますが、一歩一歩確認します。

ありがたいこちらのブログを見ながら、コードに時刻表示を入れていきました。

【Angular】小ネタ:現在時刻の表示 - Qiita

コミットログはこちら

Add clock · kawaguti/tiimer@d69efb2 · GitHub

f:id:wayaguchi:20190616201328p:plain

Electronでも時刻が表示されました。同じプロジェクトで Angular CLI も動くので、ブラウザでも動きます。WindowsでもMacでも配布用のビルド(packager)できました。

f:id:wayaguchi:20190616223156j:plain

カウントダウンタイマーにする

時刻表示までいければ、あとはカウントダウンタイマーをちょっと作り込むだけです。

Add countdown timer · kawaguti/tiimer@e5c8253 · GitHub

remove example · kawaguti/tiimer@20167d7 · GitHub

Add timer set button · kawaguti/tiimer@40da1ff · GitHub

Angularのロゴを消すなどして、カウントダウンタイマーになりました。

f:id:wayaguchi:20190616202140p:plain

 

Beep音の実装

あ、beepもありました。こちらもありがたく拝借

JavaScript で Beep 音を鳴らす方法 - Qiita

音もちゃんと出るようです。

Add beep · kawaguti/tiimer@9f42b66 · GitHub

Beep音がオフィスで鳴り響いてドキッとしたので、BeepOffボタンも追加しました。押さなくても10回で勝手に止まるようにしてます。

Add BeepOff button · kawaguti/tiimer@86598e3 · GitHub

f:id:wayaguchi:20190616202626p:plain

以上で、 Electron でカウントダウンタイマーが動きました。

 



まだデバッグ版なので、本番用のpackage作成は次回。あとマルチウインドウもやりたいですね。

 

 

Angular 7 でカウントダウンタイマー

Angularチュートリアルを一通りこなしました。わーい。HTTPの章がボリューミーでしんどかったですが勉強になりました。

angular.jp

インメモリで仮設のAPIを作って試せるところがなかなかいいですね。

で、ちょっとなにか作ってみようと思って、デュアルモニターに対応するカウントダウンタイマーを作ってみました。よかったら使ってみてください。

http://tiimer.net/


f:id:wayaguchi:20190610164229p:plain

使い方はシンプルです。

サイトにアクセスして、”Open a new Window” ボタンで別ウインドウ(タブ)を作って、それをデュアルディスプレイ側に持っていきます。そのあとでどちらかのウインドウで分秒を入力して”Set”をクリックするとタイマーが両側のウインドウでスタートします。00:00になるとアラームが鳴ります。音を止めるには”BeepOff”をクリックしてください。

起動時やウインドウオープン時のソースの読み込み以外はサーバとの通信はありません。

 

ソースコードはこちらです。

tiimer/angular at master · kawaguti/tiimer · GitHub

作った過程を以下に記録しておきます。

 

要件: デュアルモニター対応のカウントダウンタイマー

研修でよくカウントダウンタイマーを使うのですが、よく使っていたアプリケーションがOSアップデートの波についていけず、次々と使えなくなるという問題がありました。いろんな人が軽い気持ちでつくてみることができるような性質のアプリですので、マーケットにも常に似たようなアプリが存在していて、次のものを見つければいいのですが、ちょっとめんどくさいんですよね。今回は一つ加えたい要件もあったので、自作してみることにしました。

その要件とは

デュアルモニターでプレゼンしているときに、発表者ディスプレイ側で操作して、受講者向けディスプレイに時間を出したい!

です。

 これを実現する方法は数限りなくあると思いますが、今回はAngularの練習ということもあるので、こういうソリューションを目指しました。

JavaScriptでタイマーを実現し、かつ、HTML の New Window で新しいウインドウを起動して、子ウィンドウ側にタイマーを表示する 

結果的にはもう少し踏み込んだ、複数ウインドウを同期するカウントダウンタイマーになりました。(実装の都合により)

 

要素技術1: AngularのObservableを使って時刻を更新する 

まずはカウントダウンタイマー部分の実装ですが、Angular Tutorial に出てきた、 pub/sub型のイベント処理を扱う Observable という仕組みをつかうと簡単にできそうです。

....と思って探したら素晴らしいサンプルがありました。

qiita.com

このサンプルのいいところは、Observable の使い方を端的に示すだけではなく、html側のパイプの使い方も教えてくれているところです。「もっとシンプルな方法や、間違いの指摘等あればコメントよろしくお願いします。」ということですが、全く思いつきませんでした。ありがとうございます。

要素技術1の派生: カウントダウンタイマー

現在時刻が更新できたら、次はカウントダウンタイマーです。毎秒このイベントが起こることを信じて、残り秒を remainedSec という変数に持ち、これを秒ごとに減算して、ゼロになったら完了、ということにしてみます。

targetSec が最初にセットした残り秒数、remainedSec が現在の残り秒数、elapsedSecが現在の経過秒数です。

https://github.com/kawaguti/tiimer/blob/master/angular/src/app/app.component.ts

export class AppComponent implements OnInit, OnDestroy {
now: Observable<Date>;
intervalList = [];
targetSec: number;
elapsedSec: number;
remainedSec: number;
remainedString: string;
public ngOnInit() {
this.now = new Observable((observer) => {
this.intervalList.push(setInterval(() => {
if ( this.targetSec > 0 && this.remainedSec > 0) {
this.elapsedSec++;
this.remainedSec = this.targetSec - this.elapsedSec;
this.remainedString =
Math.floor(this.remainedSec / 60).toString().padStart(2, '0') + ':' +
Math.floor(this.remainedSec % 60).toString().padStart(2, '0');
}
if ( this.remainedString === '00:00' && this.beepFlag ) { AppComponent.beep(); }

observer.next(new Date());
}, 1000));
});

残り秒数表示が秒だと分単位がわかりにくいので、表示用にremainedStringというのを作っています。これは mm:ss 形式で残り秒数を表示します。

表示が 00:00 になったらビープ音を鳴らす条件式が最後に入っています。ビープ音については次項で説明します。。

要素技術2: ビープ音を鳴らす

カウントが 0 になったら (表示が 00:00 なら) ビープ音を鳴らしたいです。いくつも思いつくのですが、できる限り簡単で、ネットワークに依存しない実装が欲しかったので、こちらを参考にさせていただきました。

qiita.com

音声データもそのまま頂いてしまいました。すみません。

https://github.com/kawaguti/tiimer/blob/master/angular/src/app/app.component.ts

public static beep() {
// tslint:disable-next-line:max-line-length
const base64 = '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';
const sound = new Audio('data:audio/wav;base64,' + base64);
sound.play();
}

beepFlag という変数を持っておいて、カウントダウン開始時にこれをTrueにします。カウントがゼロになったときに、このフラグがTrueだったら、このbeepメソッドを呼んでビープを鳴らします。ユーザーが止めない限り毎秒鳴らしておきます。

ユーザーが止める操作をしたら、beepFlagを False にして次のイベントからはbeepが呼ばれないようになります。

if ( this.remainedString === '00:00' && this.beepFlag ) { AppComponent.beep(); }

要素技術3: マルチウインドウでの同期

普通のHTMLでは、window.openというメソッドで新しいウインドウを開くことができます。このときの戻り値がウインドウオブジェクトというもので、これを経由して小ウインドウのオブジェクトにアクセスすることができます...のですが、ちょっとめんどくさいので、ライブラリを探しました。いいのがありました。

nolanus.github.io

メッセージのキューを実装していて、ウインドウ間でメッセージをやりとりできます。Angular の Observable でメッセージを受け取ることができるところが素敵でした。サンプルがよくてきていて、初心者の私でもわかりやすく、とてもありがたかったです。

f:id:wayaguchi:20190610154950p:plain

サンプルをちょっと変更して、受け取ったメッセージが 0 < n < 3600 の数字だったら、 それを目標の秒数としてセットしてカウントダウンを開始せよ、という司令ということにします。

あと、ビープ音が鳴っているときに消す命令として「beefOff」を設定しました。この文字列が来たらビープ音をOffにします。

https://github.com/kawaguti/tiimer/blob/master/angular/src/app/app.component.ts

this.multiWindowService.onMessage().subscribe((value: Message) => {
// tslint:disable-next-line:radix
const newsec = parseInt(value.data);
if ( newsec > 0 && newsec < 60 * 60 ) {
this.targetSec = newsec;
this.elapsedSec = 0;
this.remainedSec = newsec;
}
if ( value.data === 'beepOff' ) { this.beepFlag = false; }
this.logs.unshift('Received a message from ' + value.senderId + ': ' + value.data);
});

ウインドウIDごとに出し先を変えられるのですが、それは不要なので、基本ブロードキャストします。ブロードキャストといっても、ウインドウのリストにforEachで全部メッセージを投げるだけの実装にしました。

public broadcastMessage( message: string ) {
this.windows.forEach((window) => {
this.sendMessage(window.id, message);
});
}

困ったところ: ビープ音がウインドウごとに鳴ってしまう

要素技術を合わせるときに困ったところは、ビープ音がウインドウごとに鳴ってしまうことでした。

これは、カウントダウンの指示を出したウインドウでだけビープ音が鳴るようにする( beepFlag を Trueにする) ということで解決しました。

https://github.com/kawaguti/tiimer/blob/master/angular/src/app/app.component.html

<button type="submit" class="btn btn-primary"
(click)="broadcastTime(minInput.value, secInput.value);minInput.value='';secInput.value='';beepFlag=true;">Set
</button>

ただ、カウントダウン開始の指示を出したウインドウ以外で止めたいこともあるので、beepFlag の Off はブロードキャストするようにしています。

https://github.com/kawaguti/tiimer/blob/master/angular/src/app/app.component.html

<button class="btn btn-secondary" (click)="beepFlag = false; broadcastMessage('beepOff');">BeepOff</button>

https://github.com/kawaguti/tiimer/blob/master/angular/src/app/app.component.ts

this.multiWindowService.onMessage().subscribe((value: Message) => {
      (中略)
if ( value.data === 'beepOff' ) { this.beepFlag = false; }
      (中略)
});

 

ウインドウサイズに合わせて文字を大きくしたい

細かい話ですが、ウインドウサイズに合わせて文字を大きくしたかったので、HTML5で加わった vh/vw というCSS要素を使いました。

dev.classmethod.jp

とりあえず style 要素で貼っています。そのうちCSSファイルに動かしたい。

https://github.com/kawaguti/tiimer/blob/master/angular/src/app/app.component.html

<p style="Font-Size: 20vw; Margin: 15vh 2vw 0">
{{ remainedString }}
</p>

 

ビルドとデプロイ

手元で動くようになったので、Angular で本番用にビルドしてデプロイしました。

$ ng build --prod

Date: 2019-06-10T06:17:12.545Z
Hash: acbc4eea2ebde4796da1
Time: 29359ms
chunk {0} runtime.26209474bfa8dc87a77c.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} es2015-polyfills.bda95d5896422d031328.js (es2015-polyfills) 56.6 kB [initial] [rendered]
chunk {2} main.780e3db59e24bd6cf759.js (main) 242 kB [initial] [rendered]
chunk {3} polyfills.8bbb231b43165d65d357.js (polyfills) 41 kB [initial] [rendered]
chunk {4} styles.3ff695c00d717f2d2a11.css (styles) 0 bytes [initial] [rendered]

Azureへの静的ファイルのデプロイは前回の記事の手順で行っています。

kawaguti.hateblo.jp

kawaguti.hateblo.jp

 

今後の課題

  • ネットに繋がってない状態でも使いたい!
  • スマホだとスリープに落ちたところでカウントがずれる!
  • スマホだとビープ音がならない!

といった課題が見つかったので、次は Electron か Swift で作ってみたいと思います。あ、二番目は内部ロジックの変更(命令を残り秒数ではなく、目標時間にする)で対応できそうですね。

蛇足ですが、こういう要望が見つかるところが、「動くソフトウェア」の大事なところだな、と思いました。

 

Azure DevOps アップロードの続き : Subscription ID を変える

前回のエントリの作業中に、サイトを Free Trial から Pay-as-you-go にサブスクリプションを変えたのでした。実質的には変わらないのかもしれないですが。

kawaguti.hateblo.jp

 

そうするとですよ、その前に作っていたデプロイパイプラインが動かなくなることに気がつきまして。

kawaguti.hateblo.jp

 

YAMLサブスクリプション名を変えないと!

じゃあということで、リポジトリにある azure-pipelines.yml のサブスクリプション部分を書き換えればいいのかな、と思って編集しました。平文で書いてあるサブスクリプションIDを編集して、pushするとパイプラインが自動で動きます。.......でコケました。

f:id:wayaguchi:20190601210003p:plain

考えてみれば、ここ書き換えるだけでどんな配置先にもいれられてしまうとなると、セキュリティなんてあったもんじゃないわけで、どこかで連携処理が必要、ということに気がつきました。

ああそっか、やはり。Project Settings にいました。

f:id:wayaguchi:20190601210407p:plain

ここを変えて再登録してあげないと!まず、[+ New Service Connection]で追加します。

f:id:wayaguchi:20190601210610p:plain

ここで Azure側のログインを求められて、認証が行われます。...ので、他人のリポジトリに突っ込んでしまうことはないわけですね。納得。

 

よしこれで大丈夫なはず!こんどは手動でパイプラインを実行してみる

ではもう一回ビルドパイプラインでコピーを実行します。

f:id:wayaguchi:20190601210959p:plain

なぜかまたコケてしまいました。サブスクリプションIDちゃんと直したのに。

そういえば、さっき YAML ファイルをいじった時に IDが平文記述されていることにちょっと気持ち悪さを感じたんですよね、そういうものなのかな?と思いつつ。そう言えばさっきサービス連携を追加する時に、Connection Name なるものを記入してました。

f:id:wayaguchi:20190601211454p:plain

 

YAMLの azureSubscription を書き換える

これをYAMLで指定すればいいのか!ということでYAMLを修正。平文にIDがでていたのは、何かの既定値だったんですねきっと。ついでにソースからマジックナンバーが除去できてよかったです。(ボーイスカウトルール)

trigger:
- master

pool:
vmImage: 'windows-2019'

steps:
- task: AzureFileCopy@3
inputs:
SourcePath:
azureSubscription: 'innovationsprint'
Destination: 'AzureBlob'
storage: 'innovationsprint'
ContainerName: '$web'

Pushするとまた自動的にビルドが走りました。

f:id:wayaguchi:20190601211915p:plain

実は最後のビルドは成功を確信していたので、ブログ書きながらメールで通知を受けたのですが、これははかどりますね。

f:id:wayaguchi:20190601212808j:plain

しかし、こうなると、単純な死活監視くらいは走らせてから終わらせたくなります。

どんどん色々やりたくなるから不思議ですね。

 

 

Azure Storage / CDN / DNS で Web公開

前回の投稿の結果、Azure Storage に Webサイトが投入できるようになったので、これをWebサイトとしてちゃんと公開するところまでやりました。確実に忘れそうなので備忘録です。

kawaguti.hateblo.jp

 

Azure Storage で Web公開の設定

まずは基本の Azure Storage での Web公開です。

 [対象のストレージアカウント] - [静的な Webサイト] で 有効にします。

f:id:wayaguchi:20190530201734p:plain

プライマリエンドポイントのURLにアクセスできればOKです。httpsも勝手に対応してくれます。(DNS独自ドメインからの山椒がなければこれで終わりでいいんですけど....)

f:id:wayaguchi:20190530202107p:plain

 

Azure Storageだけでよいのか?CDNは必要か?httpsは?

次は独自のドメインで上記のサイトが出てくれればいいだけなのですが...。ちょっと手間が必要になりました。以下のサイトを参考にすると「httpsをちゃんと必要とするならCDN必要」ということです。

qiita.com

今回はhttpsを使うことを目指して、Azure CDNをつかってみることにしました。

Azure CDN で キャッシュする

CDNはお金高いかな?と思ってサイトを確認したら5桁の日本円が並んでいて、「うわっ」と思ったのですが、小数点以下が5桁でした。つまりGBあたり数円。

azure.microsoft.com

 

ということでストレージアカウントのメニューから[Azure CDN]を選んで設定を入れます。ホスト名は2つ前の項で確認した静的Webサイトのプライマリエンドポイントを入力します。

f:id:wayaguchi:20190530202847p:plain

リソースグループからエンドポイントを表示して内容を確認します。

f:id:wayaguchi:20190530203724p:plain

CDNエンドポイントからもちゃんと見えました。

f:id:wayaguchi:20190530203703p:plain

 

独自ドメインで表示したい - とりあえず http

Webサイトとしてはちゃんと表示できそうなので、次はDNSを振り替えて、独自ドメインで表示できれば完璧です。もともとドメイン名を購入したDNSには登録しているので、そのレコードをいじるだけでよさそうなのですが、契約しているプロバイダのDNSは、設定をいじる時にテキストを直接いじる感じで今ひとつです。そこで、今回はプロバイダではなく、Azure DNS を使ってみました。

独自ドメインhttpsは証明書の手配が必要なので後回しにして、httpで確認をとっていくことにします。そのために、静的Webサイトの設定を緩めます。 [ストレージアカウント]-[構成] で "安全な転送が必須" を無効 にします。

f:id:wayaguchi:20190530205400p:plain

Azure DNS を起動する

[全てのリソース]-[追加] で DNSを作成します。

f:id:wayaguchi:20190530210047p:plain

f:id:wayaguchi:20190530210044p:plain

ネームサーバー情報を確認して...

f:id:wayaguchi:20190530210617p:plain

ドメインのプロバイダ側にネームサーバを記入します。

f:id:wayaguchi:20190530210718p:plain

この反映には数時間かかります。

Azure DNS側に転送設定を入れておきます。

f:id:wayaguchi:20190530211322p:plain

テキストファイルじゃないし、適宜入力値チェックもしてくれるので便利。

 

数時間して、DNSの切り替えが有効になったら、CDNエンドポイントにカスタムドメインを設定します。切り替わるまではエラーになります(DNSの設定が間違っていてもなりますので注意)。DNSの設定変更は反映のタイムラグが大きいので、時間あるときに気長に試すとよさそうです。

f:id:wayaguchi:20190530211949p:plain 

だいぶ経ってから、アクセスできるように。

f:id:wayaguchi:20190530212417p:plain

(この時点でトラブって色々試行錯誤してしまったので、手順が抜けてたらごめんなさい...。)

 

補足: PC側のDNSキャッシュを消す

DNSの設定を変えた時に作業用の Mac だけどうにも切り替わらないなーと思ったらOSやChromeでキャッシュされていたみたいです。

qiita.com

波及に時間がかかるDNS設定変更の時は、特にハマりやすいなと思いました。スマホとか他の端末でも並行して確認する方がいいかもしれません。

 

Windowsはこちらのようです(試してません)。

qiita.com

 

www. なしでもうまく表示したい

www,なしのURLでも同じものを表示するか、wwwに転送したいところですが、DNSで単純に解決しようと思うと、「CNAMEは www.抜きのアドレス(@)には使うことができない」というのがRFCに書いてあるようで、ググるといろんなところでやっちゃダメって書いてあります。え、みんなどうしてるの?

Azure DNSだと、「エイリアスレコード」というので実現できるようです。AWSでも同じようにエイリアスでやるようです。

f:id:wayaguchi:20190530213627p:plain

ドキュメントも見つけたので貼っときます。

docs.microsoft.com

Traffic Manager プロファイルの場合と同様に、エイリアス レコードを使用して DNS ゾーンの頂点から Azure CDN エンドポイントをポイントすることもできます。 これは、Azure Storage と Azure CDN を使って静的な Web サイトを作成する場合に便利です。 DNS 名の前に "www" を付けなくても、Web サイトにアクセスできるようになります。

たとえば、静的な Web サイトの名前が www.contoso.com の場合、DNS 名の前に www を付ける必要はなく、ユーザーは contoso.com を使ってサイトにアクセスできます。

前述のように、CNAME レコードはゾーンの頂点ではサポートされていません。 そのため、CNAME レコードを使用して contoso.com から CDN エンドポイントをポイントすることはできません。 代わりに、エイリアス レコードを使用して、ゾーンの頂点から直接 CDN エンドポイントをポイントすることができます。

CDN側のカスタムドメインに追加しておきます。これをしないとエラーになることがあるみたい。(うまくアクセスできる時もあったりしたのでよくわかってませんが、DNSCDNに振ったら、CDN側にも設定を入れるものだと覚えておきたい)

f:id:wayaguchi:20190531000423p:plain

httpsも対応したい

最後に独自ドメインhttps対応 ですが、これはAzure KeyVaultから証明書を買うことでかなり自動的にできるみたいです(便利や)。

f:id:wayaguchi:20190530214143p:plain

ドメインの検証に時間がかかるようです。

f:id:wayaguchi:20190530214236p:plain

しばらくほっといたら終わってました。

f:id:wayaguchi:20190531000913p:plain

ということでサイトを公開できました。

www.innovationsprint.com

 

あ、www.無しのURLも証明書付けないといけないのか....。こっちも証明書を追加しました。

f:id:wayaguchi:20190531001624p:plain

 

 ※ 6/10 追記 : wwwなしのURLはSSL追加でエラーが出ます。

   f:id:wayaguchi:20190610145431p:plain

 

以上、Azure で静的Webサイトをホストした時の備忘録でした。

もっとこうしたほうがいいよとかあれば、ぜひお教えいただければありがたいです。