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 = 'UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhBTGH0fPTgjMGHm7A7+OZSA0PVqzn77BdGAg+ltryxnMpBSl+zPLaizsIGGS57OihUBELTKXh8bllHgU2jdXzzn0vBSF1xe/glEILElyx6OyrWBUIQ5zd8sFuJAUuhM/z1YU2Bhxqvu7mnEoODlOq5O+zYBoGPJPY88p2KwUme8rx3I4+CRZiturqpVITC0mi4PK8aB8GM4nU8tGAMQYfcsLu45ZFDBFYr+ftrVoXCECY3PLEcSYELIHO8diJOQcZaLvt559NEAxPqOPwtmMcBjiP1/PMeS0GI3fH8N2RQAoUXrTp66hVFApGnt/yvmwhBTCG0fPTgjQGHW/A7eSaRw0PVqzl77BeGQc9ltvyxnUoBSh+zPDaizsIGGS56+mjTxELTKXh8bllHgU1jdT0z3wvBSJ0xe/glEILElyx6OyrWRUIRJve8sFuJAUug8/y1oU2Bhxqvu3mnEoPDlOq5O+zYRsGPJLZ88p3KgUme8rx3I4+CRVht+rqpVMSC0mh4fK8aiAFM4nU8tGAMQYfccPu45ZFDBFYr+ftrVwWCECY3PLEcSYGK4DN8tiIOQcZZ7zs56BODwxPpuPxtmQcBjiP1/PMeywGI3fH8N+RQAoUXrTp66hWEwlGnt/yv2wiBDCG0fPTgzQHHG/A7eSaSQ0PVqvm77BeGQc9ltrzxnUoBSh9y/HajDsIF2W56+mjUREKTKPi8blnHgU1jdTy0HwvBSF0xPDglEQKElux6eyrWRUJQ5vd88FwJAQug8/y1oY2Bhxqvu3mnEwODVKp5e+zYRsGOpPX88p3KgUmecnw3Y4/CBVhtuvqpVMSC0mh4PG9aiAFM4nS89GAMQYfccLv45dGCxFYrufur1sYB0CY3PLEcycFKoDN8tiIOQcZZ7rs56BODwxPpuPxtmQdBTiP1/PMey4FI3bH8d+RQQkUXbPq66hWFQlGnt/yv2wiBDCG0PPTgzUGHG3A7uSaSQ0PVKzm7rJeGAc9ltrzyHQpBSh9y/HajDwIF2S46+mjUREKTKPi8blnHwU1jdTy0H4wBiF0xPDglEQKElux5+2sWBUJQ5vd88NvJAUtg87y1oY3Bxtpve3mnUsODlKp5PC1YRsHOpHY88p3LAUlecnw3Y8+CBZhtuvqpVMSC0mh4PG9aiAFMojT89GBMgUfccLv45dGDRBYrufur1sYB0CX2/PEcycFKoDN8tiKOQgZZ7vs56BOEQxPpuPxt2MdBTeP1vTNei4FI3bH79+RQQsUXbTo7KlXFAlFnd7zv2wiBDCF0fLUgzUGHG3A7uSaSQ0PVKzm7rJfGQc9lNrzyHUpBCh9y/HajDwJFmS46+mjUhEKTKLh8btmHwU1i9Xyz34wBiFzxfDglUMMEVux5+2sWhYIQprd88NvJAUsgs/y1oY3Bxpqve3mnUsODlKp5PC1YhsGOpHY88p5KwUlecnw3Y8+ChVgtunqp1QTCkig4PG9ayEEMojT89GBMgUfb8Lv4pdGDRBXr+fur1wXB0CX2/PEcycFKn/M8diKOQgZZrvs56BPEAxOpePxt2UcBzaP1vLOfC0FJHbH79+RQQsUXbTo7KlXFAlFnd7xwG4jBS+F0fLUhDQGHG3A7uSbSg0PVKrl7rJfGQc9lNn0yHUpBCh7yvLajTsJFmS46umkUREMSqPh8btoHgY0i9Tz0H4wBiFzw+/hlUULEVqw6O2sWhYIQprc88NxJQUsgs/y1oY3BxpqvO7mnUwPDVKo5PC1YhsGOpHY8sp5KwUleMjx3Y9ACRVgterqp1QTCkig3/K+aiEGMYjS89GBMgceb8Hu45lHDBBXrebvr1wYBz+Y2/PGcigEKn/M8dqJOwgZZrrs6KFOEAxOpd/js2coGUCLydq6e0MlP3uwybiNWDhEa5yztJRrS0lnjKOkk3leWGeAlZePfHRpbH2JhoJ+fXl9TElTVEQAAABJTkZPSUNSRAsAAAAyMDAxLTAxLTIzAABJRU5HCwAAAFRlZCBCcm9va3MAAElTRlQQAAAAU291bmQgRm9yZ2UgNC41AA==';
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サイトをホストした時の備忘録でした。

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

The Phoenix Project : 自社の情報システムの開発・運営を体験するワークショップ

ITプレナーズさんで主催しているDevOpsの体験型研修がありまして、見学させていただきました。

f:id:wayaguchi:20190528223635j:plain

この研修はオランダに本拠をおく GamingWorks 社が開発したワークショップをITプレナーズさんが日本語化(のお手伝い)をしたものです。GamingWorks 社はAgileプロマネ向けのゲーム形式研修を手がけているようなので、今後の拡充が期待されます。

https://www.gamingworks.nl/business-simulations/the-phoenix-project/

 

継続的デリバリーの先の世界

ここでいう DevOpsについては、Phoenix Project を参考にしていただくとよいかと思います。当初の DevOps の定義はインフラやデプロイの自動化を中心に考えられたものですが、サイロ化が進行した会社が本当に変化のスピードを上げるためには、組織間の連携や会社全体のプロセス見直しが必要、ということになり、こちらを取り扱う本が多くなっています。

The DevOps 逆転だ!

The DevOps 逆転だ!

 
The DevOps ハンドブック 理論・原則・実践のすべて

The DevOps ハンドブック 理論・原則・実践のすべて

  • 作者: ジーン・キム,ジェズ・ハンブル,パトリック・ボア,ジョン・ウィリス,榊原彰,長尾高弘
  • 出版社/メーカー: 日経BP
  • 発売日: 2017/06/22
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る
 

 

改善すると次の壁が見えてくる

アジャイル開発の現場では、チーム開発をはじめてしばらくやっていると、だんだんデプロイが問題なくなり、次は開発や品質がボトルネックになり、そこも改善していくと、次はプロダクトオーナーが問題になる(バックログがジリ貧)、というのが黄金パターンのように感じます。得意なところ、手の届くところから初めていくと、だんだんその先にボトルネックが移動していくんですね。

 

以下の例は私が一時期やっていた流れなのですが、このパターンは多いかなと思います。

  1. バージョン管理や開発環境を整備して、常時結合をする。
  2. インフラを整備し、デプロイを自動化し、開発環境と本番環境の差異を縮める。
  3. 開発の人員安定化とスキル習熟が進み、品質が安定化する。
  4. プロダクトバックログの洗練が進む。不要なものを積み込む習慣がなくなる。新たな要望に即応できるようになる。
  5. 新しいビジネス施策に人員追加なしで対応できるようになる。イノベーションに継続的に対応できるようになる。

f:id:wayaguchi:20190821130602p:plain

 

自分のチームがうまくいったあとにぶつかる壁

後半で特に大事になってくるのが組織内の連携です。連携というと「みんなでやる」感じに聞こえるかもしれませんが、実際のところは全員にお願いして「気持ちよく動いてもらうように計らう」動きが必要になります。相手を尊敬・理解して、こちらを信じてもらって、自律的に動いてもらう。この部分は、なかなか外部のコーチには難しくて、社内の人の信頼貯金を試されるところかなと思います。

もし自分自身や仲間が十分な信頼貯金を持っていれば、バリューストリームマップのワークショップをしてデプロイや運用のムダをみんなで省くように改善を進めていく、とか選択肢が見えてくるのですが、信頼貯金がないとなかなかうまく始めにくいです。

f:id:wayaguchi:20190528115813j:plain

 

業務ITシステムを題材に

人事システム、給与システムやPOSシステムなど、実際によくある業務ITシステムを題材に、全社のプロジェクトを進めるのがワークショップの本線です。

f:id:wayaguchi:20190528122655j:plain

 

さまざまな役割の人と一緒に体験する

ワークショップのいいところは、自分だけでなく、他の人も一緒に参加できるところです。一人だけ研修を受けて他の人に全部伝えるのはかなり無理ゲーですので、関係者と一緒に参加して、やりながら実際の打ち手を考えていくことができれば、高速にトライアンドエラーを繰り返すことができるようになります。組織の課題は組織ごとに違いますので、それぞれで試行するしかありません。

f:id:wayaguchi:20190528223734j:plain

 

共犯関係を作る

実践的なワークショップを一緒に体験し、議論したり苦労したりすると、共犯関係ができます。同じ場所で同じことを学んで、語り合った仲というのは、その人が会社にいる限り、宝になるでしょう。私たち vs あの人たち、ではなく、問題 vs 私たちの関係になりやすくなります。

f:id:wayaguchi:20190528223926j:plain

 

お金のことを考える

あと、これは日本の開発コミュニティの特徴なのかなと思いますが、お金の流れを把握していない時があります。マネジメントですら企業会計がわかってない人も結構いるので、なかなか難しいところなのですが、売上とコストは連動しているものなので、感覚を掴んで、施策を人任せ(またはブラックボックス)にせず、一緒に考えていける組織文化を育めれば、より健全に経営できるようになるのではないかと思います。

f:id:wayaguchi:20190528121050j:plain

 

役割に分かれて実際にITプロジェクトを進行していく

CFOの視点としてどうしていくか?必要な情報システムの予算配分や、当面のプロジェクト進行をどうしていくのか。みんなの意見がどうか。役割分担を変えるとどのように次のイテレーションでのアウトプットが変化するか、優先順位を変えるべきか、各役割に分かれて議論しながら進めていきます。

f:id:wayaguchi:20190528115617j:plain

 

タイムボックス: 計画とふりかえりの力

ワークショップでは定期的に計画とふりかえりを入れていきます。次にどのような優先順位で進めるのか、前のイテレーションの結果を受けてもっとよくできるところはどこか。

まや、最後に全体のふりかえりを通じて、今日何を学んだのか、持って帰れるものはなにかを話し合います。

f:id:wayaguchi:20190528123240j:plain

 

緊急の問題に対処し、計画に反映する

プロジェクトが無風で進むことはほとんどありません。問題が起きた時こそ、組織の力が試されるともいえます。緊急の作業を、計画していた作業とどうバランスするのか。どのように意思決定していくのか、練習していきます。しかも障害というのは重なるもので...。時間は刻々と過ぎていき、議論する時間も、対応する時間を削っていきます。なにを諦め、なにを守るのか....。

「ooとxxはやめることになりました」「えっ?なになに?じゃあ、aa は?」「それは残るみたいです」「わっ、残り5分」「はーい、まとめまーす。aaとbbは残します」「えっと、それでも溢れてます」「おっと、どうしようか」

f:id:wayaguchi:20190528121801j:plain

 

とにかく歩き回る、とにかく声をかける

一つの部屋でワークショップで、部署間のコミュニケーションをシミュレートします。

情報は足で集める...というのは昔から言われることですが、リアルタイムで起きている課題に複数の担当間で調整しながら対応するには、歩き回って情報を見回し、声をかけて情報を確認するのが一番早く、必然的に歩き回って話し合いながら進みます。

f:id:wayaguchi:20190528123827j:plain

 

全体ふりかえりで学びを確認する

最後に全体ふりかえりで今日の学びを確認をします。見学した回では、以下のような意見が出ていました。

  • 「ふりかえりはやっていたが、ファシリテートや質問で深掘りすることができていなかったことに気づいた」
  • 「他の担当に対して、上から目線でなく、相手の立場に立ってコミュニケーションすることの重要性を再認識した。」
  • 「監査対応や標準化の意味がわかった」
  • 「システム投資にビジネス側の視点を入れることが重要だと思った」
  • 「3回目のイテレーションで、進め方を見直して改善を考えることができた」
  • 「IT部門に勤務しているが、ユーザー部門に参加してほしい」
  • 「ビジネス部門、IT部門、管理部門でどのように同期を取るのかがわかった」
  • 「仕事を進めるのが楽しいって思えた。これが働き方改革なのでは」

いつも一緒に仕事していない他部門の人と話し合うきっかけとしてもいいでしょう。意外と議論やファシリテートが上手い同僚を発見することになるかもしれません。

f:id:wayaguchi:20190528223931j:plain

 

技術的負債の根源にある相互無理解をつぶす

技術的負債が多くなるのは、開発者以上に、マネジメントが無理解であることが原因だということが多いのではないかと思います。解消するためのコストを取りにくい、評価されにくい、理解されない。その結果、次の手が打ちにくくなってしまうのですが、その領域に詳しくない人にはぱっと見、わからないものです(基礎的理解がない人に、わかるように説明することも難しいものです)。その辺りをちゃんと見抜く眼力や、他の専門家たちとの人間関係を築いていくことが重要になってきます。

f:id:wayaguchi:20190528223937j:plain

 

さまざまな視点で組織を見直そう

一歩引いたところから、いくつかの視点を取り入れて、自分たちの現状を見つめ直すために、こうした研修やワークショップに参加する機会をぜひ利用していただければと思います。

f:id:wayaguchi:20190528125310j:plainf:id:wayaguchi:20190528122218j:plain

おまけ : 組織で新しいことを進めたい方へ

組織内でさまざまな人を巻き込んでいくなら、この本がバイブルだと思っています。モブプロの聖地 Hunter Industries で学んだこと - kawaguti’s diary の Chris Lucian さんもオススメしてました!

Fearless Change アジャイルに効く アイデアを組織に広めるための48のパターン

Fearless Change アジャイルに効く アイデアを組織に広めるための48のパターン

 

電子版が出てました!

 

なお、研修の問い合わせ先はこちらだそうです。

講師育成含めた研修受講にご興味がある方

株式会社ITプレナーズジャパン・アジアパシフィック

https://www.itpreneurs.co.jp/contact-us/

 

純粋な研修受講にご興味がある方

クリエーションライン株式会社

https://www.creationline.com/contact

 

Azure DevOps の パイプラインで静的Webサイトを GitHub からデプロイ

Azure DevOps を使って、GitHub に置いてある静的なWebサイトをクラウドにデプロイしてみたので、そのやり方をメモしておきます。

やりたいこと : Webサイトにファイルをごそっとおきたい

長らく放置していた静的Webサイトの引越しをやるにあたって、ついでに自動デプロイの練習をすることにしました。

結果として、masterにpushすると、自動的にサイトに配置されるようになりました。

f:id:wayaguchi:20190528160503p:plain

初心者がやるタスクとしては最高にシンプルなやつだと思ったのですが、あまりに簡単すぎるのか、ちょうどいい記事が見当たらなくて、ちょっと調べながら進めることになりました。

Azure DevOps とは

Azure DevOps はSam Guckenheimer さんたちが作っているクラウドサービスで、もともとはVisual Studio Team Services とか Team Foundation Server とか言っていたものです。もともと DVDに入れてインストール型で売っていたアジャイル支援ツールを、クラウドに移行しました。プロダクトバックログ管理、デプロイパイプライン、アラート管理などが統合されています(バラバラでも使えますので今回はパイプラインだけ)。

azure.microsoft.com

 

ビルドを作る

今回やりたいのはGitHubからコピーするだけの作業ですが、それを行う「ビルド」を作ります。

[Pipelines]-[Builds] -> [+ New]-[New build pipeline]

f:id:wayaguchi:20190528142243p:plain

https://dev.azure.com/

 

更新元のファイルはGithub.comにあるのでGitHubを選択。

f:id:wayaguchi:20190528142416p:plain

プロジェクトにGithubを連携してあると、自分のリポジトリを検索できます。

f:id:wayaguchi:20190528142638p:plain
プロジェクトのGitHub連携は左下の project settings から GitHub Connections で [ + New Connection]でアカウントに紐づけて、連携対象のリポジトリを指定します。

f:id:wayaguchi:20190528143040p:plain

GitHubリポジトリ直下に azure-pipelines.yml があると自動的に読み込むみたいです。

f:id:wayaguchi:20190528143422p:plain

設定ファイル azure-pipeline.yml の内容

今回の azure-pipeline.yml の内容はこれです。

masterブランチの更新をトリガーにして、WindowsVM上で AzureFileCopyコマンドを実行し、SourcePath (規定値では作業ディレクトリ直下 = 対象リポジトリをクローンしたルートディレクトリ) から、ディレクトリの内容をごっそり対象のBlobストレージにコピーします。

trigger:
- master

pool:
vmImage: 'windows-2019'

steps:
- task: AzureFileCopy@3
inputs:
SourcePath:
azureSubscription: 'Free Trial (3372c....)'
Destination: 'AzureBlob'
storage: 'innov......'
ContainerName: '$web'

 Azure DevOps上で設定を変更すると、このファイルに保存され、GithubリポジトリにPushされます。ここで master ブランチが更新されるため、すぐにこのビルドジョブがキックされて実行されます。

pool: vmImageは、Linuxや、低いバージョンのWindowsでは動かないようです。AzureFileCopyの制約かと思います。

The Microsoft-hosted agent pool provides 7 virtual machine images to choose from:

Microsoft-hosted agents for Azure Pipelines - Azure Pipelines | Microsoft Docs

task: AzureFileCopy が今回の処理のキモです。いつくかパラメータがあります。

azureSubscriptionと storage は、あらかじめ Azure上で作成したBlobストレージの情報を書き込みます。

sourceはちょっとハマったのですが、全部消すとビルドのローカルパスになると気づいて、消したらうまくいったのでそのままにしています。

ドキュメントによると $(Build.Repository.LocalPath)¥hogehoge とかやると Github 上のサブディレクトリが指定できそうです。

Source Required. The source of the files to copy. Pre-defined system variables such as $(Build.Repository.LocalPath) can be used. Names containing wildcards such as *.zip are not supported.

Azure File Copy task - Azure Pipelines | Microsoft Docs


さて実行

右上の [Run] を押すと実行されます。GitHub で masterになにか push しても実行されます。私の場合は、azure-pipeline.yml でエラーになったので何度か書き換えたらそのつど実行されて解析が楽でした。

まずAzureのクラウド内にある待機中のエージェント(VM)がアサインされます。

f:id:wayaguchi:20190528151311p:plain

リポジトリからチェックアウト(ダウンロード)して...

f:id:wayaguchi:20190528151636p:plain

AzureFileCopyコマンドが実行されます。

f:id:wayaguchi:20190528151657p:plain

黄色い文字で警告(Warning)が出ていますがこれは後で調べるとして...

下の白い文字「Uploading files from ...」 の行でコピーができたことがわかります。

エラーの場合はここが赤文字になってエラー内容が表示されます。

f:id:wayaguchi:20190528151852p:plain

ジョブ終了。前述の2個の警告は気になりますが、ジョブは全て成功です。

f:id:wayaguchi:20190528152013p:plain

Azure Storage の Blob にファイルがコピーされていることを確認!

f:id:wayaguchi:20190528152751p:plain

https://portal.azure.com/

 

今回の作業にどれくらいかかったか

ちなみにいろいろ調べながら、10回くらいで成功したみたいです。エラーの原因はVMLinux だったこと、パス指定が違っていたことでした(消したらすんなりいった)。更新予定のないサイトですが、いつでも直せるのは気持ちいいし、引っ越しも簡単(と思いたい)。

f:id:wayaguchi:20190528154111p:plain

(備忘録) 試しに作ったビルドを消すとき

ビルドを消すときは、ビルドの名前を全部入力します。(1) の入力どうするんだろ、ってドギマギしたのですが、kawagutiの部分を入力していないことに気づきました。

f:id:wayaguchi:20190528151952p:plain

 

余談:  Sam Guckenheimer さんの Agile2014の基調講演

余談ですが、Sam Guckenheimer さんの「マイクロソフトがいかにしてクラウド時代にシフトしたか」というAgile2014の基調講演は、私の中でもピカイチに衝撃を受けたスピーチでした。

devblogs.microsoft.com

27インチディスプレイで簡易モブ環境

借りているオフィスの応接フロアでミーティングをすることがちょくちょくあるんですけど、モニタもホワイトボードも壁もないので話しながら迷子になることがしばしば。そこで、オフィスとミーティングスペースを行き来できるくらいのサイズのディスプレイを買ってみました。

USB-Cでつながるディスプレイ

一年半前の機種ということで、最新の4KとかHDRだとかはないのですが、USB-Cの給電かつディスプレイポートになるドッキングステーションがついていまして、きっとケーブルがすっきりして持ち運びやすいのではないかと。

買ってみると、ディスプレイとドッキングステーションの電源ケーブルが別で、かつドッキングステーションはでかい外部アダプタになっていて、置いておくには良いのだけど、今回の持ち運びたいというニーズにはちょっといまいちでした。いったん、マジックテープでグルグル巻きにしていますが、いずれ電源ケーブルの余長も含め、整理したいところです。

f:id:wayaguchi:20190526150746j:plain

しかし、USB-Cを挿すだけでディスプレイが繋がって電源が給電されるのは未来感ありますね。それ Nintendo Switch でできる?そうですかすごいですね。あ、こんどSwitchもつないでみなきゃ。

Bluetoothキーボードとマウス

で、作業のために無線のキーボードとマウスを購入しました。キーボードの質より確実につながることを優先して、安定のロジクールのモバイルキーボードです。WindowsMac二両対応している日本語Bluetoothキーボード。マウスも同様のものを。 

 作業してみると、二人だと一台のキーボードで交代しやすいのですが、四人だとちょっと回すのが面倒。ということで、もう1セット追加購入しました。これで資料作成だろうとコーディングだろうとモブでスイスイできるはずです。念のため、2セットとも一台のMacBluetooth接続してみましたが、軽く試したレベルでは問題なさそうです。次の実戦の機会が楽しみです。 

f:id:wayaguchi:20190526145832j:image

このキーボードは、iOSにも対応していて、ボタン一つで接続先を切り替えられるので、持ち歩いても面白いかなと思います。電車の中とかで猛烈にメール打ちたくなった時とか便利そう。

一人作業の時もつかってみる

モブタイマーをポモドーロタイマーに使えるというアイデアをもらったので、使って書いてます。

f:id:wayaguchi:20190526145846j:image

mobster.cc

 

あと、ビデオ視聴も捗りますね(ダメパターン)。

大雨なのでリモートモビングをやってみた

コミュニティである本を翻訳しようということになって、経験者もそれほどいないので、まあ、とにかく最初から訳してみようということになりました。

進めるにあたって、集まって相談したところ、まあとにかくモブで始めてみましょうということになり、今回は三回目。(私は前回欠席したので二回目。)

しかし、今夜は大雨の日。

news.yahoo.co.jp

全員がリモート参加を試してみることになりました。

基本のモビング環境

まず前回、人が集まった時のモブ環境を説明しておきます。まあ、特筆すべきことはないのですが、大きなディスプレイ一台、代表の人がPCをつないで表示、適当に交代しながら作業しました。

もともとリモートの人が1-2名いたりするので、Zoomを使ってリモート接続はしていました。Google Docsを大写しにして、それをスクリーン共有するのと、並行して全員でGoogle Docsにもアクセスしてコメントや、いろいろ調べられるようにしておきました。

席を外してコンビニに行くときにもZoomの音声はつなげっぱなしにできるので、心置きなく移動できました。あと、コンビニの注文も受けられます。

チャットは特につかっていません。音を聞きながらGoogle Docs共有しているので、ほかの画面を開く余裕がないという感じもします。

リモートモビング環境

まず、5分ごとの交代を基本ルールに入れてみました。Mobsterタイマーを使って5分ごとに通知が来るようにします。全員回ったら簡単にレトロスペクティブをして10分休憩を入れました。

mobster.cc

f:id:wayaguchi:20190521230214p:plain

モビングの注意点

5分に一回ドライバーを交代し、ドライバーは「考えながら話す(Thinking Aloud)」を心掛け、周りの人はどんどん話してアイデアを出していきます。基本的には目の前にある作業対象についての議論を進めます。

気になったところはGoole Docsのコメント機能でメモを残したりしました。Google Docsはちゃんとアカウント指定で共有しているので、誰のカーソルかとか、コメントかとかがわかるので、スムーズだった気がします。

とはいっても、ドライバーしか基本的には編集しないので、ほかの人は声で提案をするか、議論を聞いています。ドライバーは「こういうかんじでいい?」と聞き、周りの人が「いいと思いまーす」と返す感じです。すべての作業は二人以上の同意(Four Eyes)でもって進んでいきます。

 2巡目からは、このタイマーが走っているPCのスクリーンを共有することで時間もわかるようにしてみました。

f:id:wayaguchi:20190521230551p:plain

Zoomの音声を改善する

あと、周囲の環境音を消すアプリを紹介してもらったので、途中から投入してみました。

blog.animereview.jp

krisp.ai

環境音が低減されてよかったみたいです。人によっては隣で洗濯機が回っていたそうなのですが全く聞こえませんでした。すごい。

私の環境だと、なぜかマイクをオンにすると、ヘッドフォンが左耳しか聞こえなくなるという相性の悪さを発揮してくれましたが、そんなにこまらないので助かりました。

たぶん思ったよりスローペース

多くの人が最初に思ったよりもスローペースで進んでいると思います。でも、とりあえず初めての人が多いので、このペースが大事な気がしています。自分たちのペースをつかまずに、コミットしてしまうとしんどいんですよね。そういう意味ではヘルシーな作業といえると思います。今後ペースが上がっていくのか、違う方法を考えるのかは、また今後の話。

自分たちの実力がわからないうちの、初期の見積もりは信用できないので、まあこれでいいんじゃないか、という合意をしています。わかっていても、なかなかこういう作業の仕方をすることができないような気もします。私たちの敵は常に憶測であり、未検証な要素をそれと知らずに土台にしてしまうことなのでしょう。

みなさんのご参考になれば幸いです。