PagerDutyとAsanaをzapierをつかって連携させてみた

zapierをつかってPagerDutyとAsanaを連携させる方法をまとめます。

zapier.com

モチベーション

PagerDutyのIncidentをSlackに通知をして、その対応をAsanaでタスク化して運用をするうえで Asanaのタスク化を自動化させたい。そんなときzaiperは zap という単位でAサービスで発生したイベントをトリガーにBサービスにアクションを行うという一連の運用フローを自動化することができる。

PagerDutyでIncidentが新しく作られたら Asanaでタスクを作成する、このような運用フローがzaiperのダッシュボードからポチポチと設定するだけで自動化できる。

連携させてみた

f:id:n_soushi:20170426093701p:plain

zaiperのダッシュボードは直感的で設定フローの理解もすぐにできた。
PagerDutyをトリガーにAsanaでタスクを作るというフローだけど、他にもステップを追加することができる。例えばGoogleCalendarにイベントを追加したりgithubでissue化したり認証できるサービスであれば多様なステップを作ることができる。

ここからはzaiperの設定フローで補足したいところをまとめていく。

設定フローの補足

PagerDutyの設定

  • Incidentの発生条件はオプションで設定できる。
    • Incidentが誰にアサインされたかを指定したり、どのイベントタイプをトリガーにするか指定できる。
    • 今回は誰にアサインされたかは指定せず、イベントタイプはTriggerとした。つまり認知していない新規のIncidentの発生がトリガーとなる。
  • zaiperはPagerDutyのイベントを検知するためにEndpoinURLを生成するのでPagerDuty側でそのURLをWebhookに追加する必要がある。

Asanaの設定

  • 今回はAsanaのタスクを作成するアクションにしたが、StoryProjectなど他のアクションも設定できる。
  • タスクを生成するうえでタスクのタイトルやメモなどをテンプレートとして設定できる。
    • PagerDutyのIncidentから要素を取得することができるので、タイトルにIncidentのServiceNameやメモにIncidentのURLStatusなどを変数としてテンプレート化できる。
    • 今回は次のように設定してみた。 f:id:n_soushi:20170426100129p:plain
  • その他にもタスクを誰にアサインするかなどタスクの要素を細かく設定できる。 f:id:n_soushi:20170426100510p:plain

動かしてみる

あらかじめPagerDutyとAsanaはSlackと連携させているのでIncidentとタスクのイベントに応じてSlackに通知されることが確認できた。

f:id:n_soushi:20170426100917p:plain

実際にPagerDutyでIncidentを発生させてみて、Incidentが発生した通知の後にAsanaのタスクが作成されていることが分かる。

f:id:n_soushi:20170426101532p:plain

AsanaのタスクにはIncidentの情報が参照されていてzaiperで設定したAsanaのテンプレートが適応されていることが確認できる。

まとめ

zaiperをつかって運用フローの自動化を試してみた。zaiperのダッシュボードは使いやすい印象でつくってる方たちも好印象だった。
zaiperのプランはzapとタスクの数でグレードが分かれている。あとはzapierが稼働するのがBasicだと15分毎でBusiness以降は5分毎なので運用要件に合わせたプラン選択が必要だと感じた。

他のzapの組み合わせとしてはGoogleカレンダーにイベントが登録されたのをトリガーにSlackにイベントを通知する連携方法なども試しみたりした。

f:id:n_soushi:20170426161411p:plain

GoogleカレンダーのイベントをトリガーにしてSlackで告知するような運用はよくあるケースなので、まずはこういったところから導入を始めて、運用に合わせてzapを増やしていくとよさそうである。

KotlinでgRPC。SSL/TLSを有効にする方法をまとめた。

前回のエントリではgrpc/grpc-javaをベースにkotlinでgRPCを試しました。今回はSSL/TLSを有効にする方法をまとめていきます。grpc/grpc-java/SECURITY.mdを参照しながら進めました。

証明書を準備する

手元に適当な証明書がなかったのでgrpc-go/testdataにある証明書を利用しました。

Subject Alternative Name を確認するとマルチドメイン*.test.google.fr, waterzooi.test.google.be, *.test.youtube.comが定義されていますので、hostsにwaterzooi.test.google.beを追加しました。

openssl x509 -text -in ./server1.pem
127.0.0.1 waterzooi.test.google.be

OpenSSLを有効にする

netty-tcnativeをプロジェクトに追加します。

buildscript {
  repositories {
    mavenCentral()
  }
}

dependencies {
    compile 'io.netty:netty-tcnative-boringssl-static:1.1.33.Fork26'
}

netty-tcnativeはDynamicStaticがあります。このエントリではStaticを利用しました。grpc/grpc-java/SECURITY.mdを参照するとStaticの利用を推奨していますがOpenSSLのセキュリティパッチが提供された場合、Staticではパッチ反映が即座には行われないためプロジェクト方針によってはDynamicの利用を検討する必要があります。

GrpcSslContextsでSslContextを生成する

サーバ側とクライアント側ではGrpcSslContextsに定義されているforServerforClientのメソッドをつかってSslContextを生成します。

サーバ側

(serverBuilder as NettyServerBuilder).sslContext(
        GrpcSslContexts.forServer(
                File(classLoader.getResource("server1.pem").file),
                File(classLoader.getResource("server1.key").file))
                .clientAuth(ClientAuth.OPTIONAL)
                .build())

クライアント側

NettyChannelBuilder.forAddress("waterzooi.test.google.be", 50051)
        .sslContext(
                GrpcSslContexts.forClient()
                        .trustManager(File(classLoader.getResource("ca.pem").file))
                        .build())
        .build()

上記の実装を行えばSSL/TLSが有効になります。

コードを公開しています

このエントリのコードはgithubに公開しています。
SSL/TLSを有効にするにあたり情報が少ない印象をもちました。java or kotlinで実装をする方に少しでも参考になると嬉しいです。

github.com

関連エントリ

naruto-io.hatenablog.com

KotlinでgRPC。実運用にも活かせるWEBアプリケーション構成で試してみた。

KotlinでgRPCを試していきます。protocol buffersがkotlinに対応していないのでjavaに生成したものを使います。次のようなアプリケーション構成でKotlinを使ったgRPC通信を試してみました。

アプリケーション構成

f:id:n_soushi:20170413103848p:plain

  • エンドクライアントからのアクセスはGateway Serverが窓口となりHTTP/1.1で通信を行います。
  • Gateway ServerのバックエンドにいるgRPC ServerとはgRPC(HTTP/2)で通信を行います。
  • monitoring toolGateway ServergRPC Serverの監視を行いHTTP/1.1で通信を行います。

モチベーション

何度かgRPCについてのエントリをまとめてきました。kotlinでgRPCを試してみたいと感じていたのとSpring Framework 5.0のリリースを控えた状況でSpring Framework 5 on Kotlinを試してみたい欲求がありました。 そのためアプリケーション構成図にあるとおりGateway ServerにはRouter機能を試したいので spring-webfluxでアプリケーションを作りました。

次のエントリではSpring Framework 5.0でReactive Programmingを活用しながらkotlinらしいコードの紹介がされています。

spring.io

またgRPC ServerではHTTP/1.1gRPC(HTTP2)の2つの通信方式を有効にしたいです。Spring Bootでどのように実現するのか?この課題についても理解を深める必要がありました。

そして実戦に向けて実運用をイメージしたアプリケーション構成を構築する必要がありました。


ここからは構築にいたるまでの勘所や課題などについてまとめていきます。

gRPC Server

まずはgRPC Serverからです。
ここでの課題はHTTP/1.1gRPC(HTTP2)の2つの通信方式を有効にすることです。

  • monitoring toolからはヘルスチェックなどの監視リクエストに応えるためにHTTP/1.1で通信を行いたい
  • Gateway Serverとの通信にはgRPC(HTTP2)で通信を行いたい

この課題を解決するために次のspring-boot-starterを使いました。

github.com

こちらを使えばgRPC Serverを実装したクラスに@GRpcServiceをつけるだけでgRPC SeverをSpring Boot上に起動できます。またSpring Boot(spring-boot-starter-web)で起動していますのでHTTP/1.1の通信も有効です。

@GRpcService
class EchoServer : EchoServiceGrpc.EchoServiceImplBase() {

    override fun echoService(request: EchoMessage?, responseObserver: StreamObserver<EchoMessage>?) {
        val msg = EchoMessage.newBuilder().setMessage("echo \\${request?.message}/").build()
        responseObserver?.onNext(msg)
        responseObserver?.onCompleted()
    }
}

レポジトリのREADMEにあるとおりinterceptorの提供(ログ差し込んだり)やServerビルド定義もカスタマイズできます。

$ ./gradlew clean generateProto bootRun
・・・
2017-04-13 14:48:45.479  INFO 30602 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-04-13 14:48:45.482  INFO 30602 --- [           main] o.l.springboot.grpc.GRpcServerRunner     : Starting gRPC Server ...
2017-04-13 14:48:45.528  INFO 30602 --- [           main] o.l.springboot.grpc.GRpcServerRunner     : 'app.grpc.server.EchoServer' service has been registered.
2017-04-13 14:48:45.531  INFO 30602 --- [           main] o.l.springboot.grpc.GRpcServerRunner     : 'app.grpc.server.GreetServer' service has been registered.
2017-04-13 14:48:45.765  INFO 30602 --- [           main] o.l.springboot.grpc.GRpcServerRunner     : gRPC Server started, listening on port 50051.
・・・

8080ポート(HTTP/1.1)と50051ポート(gRPC/HTTP2)の両方が起動ログで確認できます。これでgRPC Serverの課題は解決です。

Gateway Server

次にGateway Serverです。
ここではspring-web-fluxを使いreactor coreベースでアプリケーションが動いています。そしてエンドクライアントからリクエストをgRPC Serverへ渡すgatewayの役割を担います(gRPCクライアントの役割)。
spring-web-fluxを使うとnon-blockingなservletが起動するためgRPCクライントには okhttpを使います。gRPCのクライアントは標準でnettyが使われるためokhttpを指定します。これをしないとreactor coreのアプリケーションとgRPC Serverのnon-blokingなところがバッティングしてしまうようです。

private fun getChannel() = OkHttpChannelBuilder.forAddress(appProperties.grpc.server.hostname, appProperties.grpc.server.port!!)
            // for testing
            .usePlaintext(true)
            .build()

もしGateway Serverエンドクライアントとの通信にgRPCを使いたい場合はGateway ServerにgRPC Serverを置く必要がありバッティング問題を解消しなくてはなりません。これに関してはSpring Framework5.0の正式リリースやマイルストーンの動きを見て試していく必要があり課題として残りました。

protobuf-gradle-plugin

protoclo bufferの生成には次のgradleプラグインを使っています。

github.com

起動時に.protoからprotocol bufferを生成するようにgradleに次ような設定をしています。

protobuf {
    protoc {
        artifact = 'com.google.protobuf:protoc:3.2.0'
    }
    plugins {
        grpc {
            artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}"
        }
    }
    generateProtoTasks {
        ofSourceSet('main').each { task ->
            task.builtins {
                java {
                    outputSubDir = 'protoGen'
                }
            }
            task.plugins {
                grpc {
                    outputSubDir = 'protoGen'
                }
            }
        }
    }
    generatedFilesBaseDir = "$projectDir/src/"
}

task cleanProtoGen {
    doFirst{
        delete("$projectDir/src/main/protoGen")
    }
}
clean.dependsOn cleanProtoGen

上記の定義により./gradlew clean generateProtoを実行することでprotocol bufferを再生成できます。起動時に最新の.protoからprotocol bufferを使われるように起動コマンドは./gradlew clean generateProto bootRunを使っています。


まとめ

  • 現バージョンのSpring Boot 1.5.2ではLogNet/grpc-spring-boot-starterを使うことでHTTP/1.1gRPC(HTTP2)の共存課題は解決できました。
  • Spring Framework 5ではreactor coreの採用からgRPC Serverとの共存には課題が残りました。今後のjavaのエコシステムなどを使いながら課題解決に取り組みます。

コードを公開しています

今回のKotlinでgRPCを試したコードのすべてはgithubに公開しています。 起動して確認するにはgithubのコードからできますのでREADMEを参照してください。

github.com