UZABASE Tech Blog

〜迷ったら挑戦する道を選ぶ〜 株式会社ユーザベースの技術チームブログです。

Clojure 1.10.x時代の新しいデバッグツール

こんにちは!こんにちは!SPEEDA開発チーム(通称PDT)に所属しているあやぴーid:ayato0211です。

弊チームでは新しいモノを開発するときに、よくClojureという言語が採用されています。だいたい言語別でシェア2位といったところでしょうか。1位はみんな大好き(?)Kotlinで、こちらはサーバーサイドでの利用が主になっていて、次にE2Eのテストコードを書くときに良く利用されています。

今日はそんな弊チームでよく使われているClojureについて、Clojure 1.10.x時代の新しいデバッグツールをお伝えできれば良いなと思い、この記事を書こうと思った次第です。

大きなデータ構造を簡単に把握したい

Clojureは素晴らしい言語です。Clojureはとても素晴らしい言語です。大事なことなので、2回言いました。それはさておき、Clojureでアプリケーションを書いていると、どうしても大きなデータ構造が出てくる瞬間があります。例えば、Ringのリクエストマップや、Ductのコンフィグマップなどが該当します。それ以外にもマップを引数に取る関数を書くことは往々にしてあるかと思います。

これらの大きなデータ構造というのは、実際に中身を確認するのが非常に面倒です。まず思いつくのは、伝統的なプリントデバッグだと思います。このとき、よくあるのはそのマップがどんなキーを持っているのか分からないので、①とりあえずマップ全体を出力してみて、②注目したい目的のキーをなんとか探し出し、③目的のキーと対応する値だけを出力するように修正して④再度確認を行うという①~④の繰り返しをすることです。

この伝統的な方法はデータ構造が大きければ大きいほど目当ての値を見つけるのが難しいです。僕はよく出力した後に迷子になっています。できればもっとインタラクティブに出力したデータ構造を把握しやすい方法が欲しいところです。そういうツールを今回は紹介したいと思います。

Cognitect's REBLを使う

インタラクティブにデータ構造を把握するためのツールとして、オススメしたいのがCognitectが開発しているREBLというツールです。

REBL、つまりRead Eval Browse Loopということで、その名前の通りただ標準出力に吐く代わりに値をブラウジング(走査)できるというわけです。REBLを使うために必要なのはClojureのバージョンが1.10.0以上であることです。Clojure 1.10.0で新しく追加されたtap>を使うと、REBL上に任意のデータを送ることができ、簡単にブラウジングすることができます(より厳密に言えばDatafyなどの話も出てくるんですが一旦脇に置いておく)。

このREBLをどうやって既存プロジェクトで利用できるようにするのか、Ductの簡単なアプリに組み込む例を見せながら解説したいと思います。

REBLのダウンロードとインストール

REBLは以下のページからダウンロードすることができます。

ライセンス条項をよく読んでほしいのですが、個人の趣味として利用する場合にはあまり問題ありませんが、仕事で利用する場合はDatomicのアクティブな有償ユーザーもしくは、REBLのパトロンでなければなりません。このことに注意してダウンロードしてください。

zipファイルをダウンロードしたら適当な場所に展開しておきます。今回は/home/{{username}}/opt/REBL-0.9.172/REBL-0.9.172.jarとなるようにしました。これでREBLのインストールは完了です。

プロジェクトの準備

次にREBLを使う対象のプロジェクトを準備します。今回はDuctのAPIを簡単に作成して、ついでにexampleハンドラーも生やしてしまいます。以下のコマンドでプロジェクトを作成します。

$ lein new duct demo --template-version 0.12.1 -- +api +ataraxy +example
$ cd demo
$ lein duct setup

普段、意識して--template-versionを指定することはありませんが、この例と全く同じ状況を作りたければ、このように指定することで同じ雛形のバージョンを利用できます。

REBLを利用できるようにする

まずは/home/{{username}}/.lein/profiles.cljに以下の情報を追記します。REBLは開発時に使うもので、開発環境(Javaのバージョン)によって必要となる依存関係が微妙に異なるので、開発者のマシン毎に以下の設定はあると良いでしょう(REBLのREADMEに書いてあるUsageを参照)。僕の手元の環境が今はJava 11になっているのでJava FX関連の依存関係が入っています。また先程インストールしたREBLのJarのパスを:resource-pathsに記述します。

{
 ;; 以下の部分を書き加える
 :tools/rebl
 {:dependencies [[org.clojure/core.async "0.4.500"]
                 [org.openjfx/javafx-fxml "11.0.1"]
                 [org.openjfx/javafx-controls "11.0.1"]
                 [org.openjfx/javafx-swing "11.0.1"]
                 [org.openjfx/javafx-base "11.0.1"]
                 [org.openjfx/javafx-web "11.0.1"]]
  :resource-paths ["/home/{{username}}/opt/REBL-0.9.172/REBL-0.9.172.jar"]}}

次に先程用意したプロジェクトのプロジェクトルート以下にあるprofiles.cljを以下のように書き換えます。profiles.clj.gitignoreされるので、個人用の設定を書く場所として適しています。

;; Local profile overrides
{:profiles/dev
 [:tools/rebl]}

最後にdev/src/local.cljを以下のように記述します。このように書くと、Ductのプロジェクトで最初に(dev)とするとこのファイルが自動的にロードされ、REBLの画面を立ち上げる関数(start-rebl)が使えるようになります。開発しているプロジェクトでチーム全員の合意がとれる場合は、dev/src/dev.cljなどに直接REBLを起動させるための関数を書いても良いかもしれません。

;; Local REPL configuration
(when-not (try
            (require 'cognitect.rebl)
            (catch Exception _))
  (defn start-rebl []
    ((resolve 'cognitect.rebl/ui))
    (add-tap clojure.pprint/pprint)
    (println "REBL started")))

ここまででREBLを利用する下準備が整いました。

REBLにデータを送信する

まずは普段どおりにREPLからシステムを起動をして、REBLもあわせて起動させておきます。

user> (dev)
;;=> :loaded
dev> (go)
:duct.server.http.jetty/starting-server {:port 3000}
;;=> :initiated
dev> (start-rebl)
REBL started
;;=> nil

このまま例えばターミナルなどからcurl localhost:3000/exampleとしても、REBLの方には何も出力されません。なので、src/demo/handler/example.clj:demo.handler/exampleコンポーネントを次のように少し書き換えます。

(defmethod ig/init-key :demo.handler/example [_ options]
  (fn [{[_] :ataraxy/result :as req}]
    (tap> req) ;; <- 追記
    [::response/ok {:example "data"}]))

リクエストマップをtap>に渡すようにしました。こうして、REPLなどからシステムを(reset)などを実行して再起動します。そして、先程と同様にcurl localhost:3000/exampleとターミナルから実行してみます。するとREBLの「tap」タブに次のようにリクエストマップが出力されていると思います。

f:id:ayato0211:20190702182751p:plain

この「tap」タブの画面下部にある「Browse」ボタンを押すと「browse」タブに表示が切り替わります。「nav->」入力欄に例えば0 :headersと入力してEnterを押してみましょう。すると次のような画面になっているはずです。nav->のところはget-inと同じ要領で書けると思えば問題ないと思います。

f:id:ayato0211:20190702182849p:plain

今までであれば出力したデータを見ることしかできませんでしたが、REBLを使えばこのように一度出力した値をREBL上で走査することができます。これにより今までより大きなデータ構造などを把握しやすくなり、デバッグなどもやりやすくなるはずです。

REBLのより具体的な使い方は実際に触るか、次の動画を見てもらえると良いかなと思います。

まとめ

今までプリントデバッグでprintlnpprintなどと書いていたところを、tap>と書くだけでREBLにデータの情報を送ることができ、REBL上でインタラクティブにデータを見ることができるようになりました。これにより、Clojureで開発されたアプリケーションをよりデバッグしやすくなったりするのではないでしょうか。

余談

今回は説明していませんが、Clojure 1.10.0で追加されたdatafyなどをうまく活用すると、REBL上で走査できる対象を広げることができます。実際、ネームスペース(clojure.lang.Namespace)などはDatafiableを実装しているため、REBL上でブラウジングすることができるようになっています。REBL左上のパネルに(the-ns 'dev)など入力して評価してみると分かると思います。このあたりの話もいずれ書けたら良いなーと思ったり思わなかったり。

Kubernetes + Istioでblue-green deploymentを実現する

こんにちは、SPEEDAのSREチームの阿南です。最近Kubernetes界隈が盛り上がっていますね。ここ一年で、各企業での利用事例やKubernetesを解説している書籍等もかなり増え、活用の仕方も徐々に確立されて来ているのではないでしょうか。一方Istioについては、色々できそうということはわかったんだけど、利用事例も少ないためどう使えばいいかわからない、本番運用しているという企業もまだあまり聞かないし導入に踏み切れない、という方も多いかと思います。弊社ではまだ2つのnamespaceのみですが、Istioで運用を開始しております。今回は、Istioで実現できることはなんとなく知っているが、具体的な設定方法がよくわからない方向けにblue-green deployの設定を参考に説明してみたいと思います。

Istioの通信について知りたい方はこちら

環境

GKE 1.9.7-gke.11 ,Istio1.0.2 を利用しています。

IstioのRouting Rule

IstioのRouting Ruleには大きく4つのリソースがあります。

resource 説明
Gateway HTTP/TCPのリクエストを受付. どのHost or Portのアクセスを許可するかのルールを記載。 Ingress / Egress のルールを適用できる
VirtualService ルールにマッチしたリクエストをKubernetesのサービスにルーティングする. weightやheaderの情報を利用した振り分けを定義でき、Istioの設定の要とも言える
DestinationRule trafficの振り分けルールを設定. (例: round robin, least connection 等)
ServiceEntry 外部サービスを登録. 例えば、Kubernetes(Istio)の外側にあるDBの接続先等

この4つが適用された通信のイメージを手書きして見ました。まず、クラスター外部から内部への通信です。

f:id:tanan55:20181118130152p:plain

Gatewayを通ったリクエストがVirtualServiceのルールに基づいて各サービスにルーティングされます。その際に、DestinationRuleに基づき最終的にリクエストが送られるpodが決まります。(DestinationRuleでsubsetが設定されていない場合は、RoundRobinで均等に分散される)

続いて、クラスター内部から外部への通信です。

f:id:tanan55:20181118132321p:plain

コンテナから外向きに通信が発生した後、ServiceEntryのルールに基づきRequestを許可するかどうか判断します。ルール次第で、Egressgatewayを通る場合とそうでない場合の2種類があります。個人的には、まだEgressgatewayを使ってないので、外部のエンドポイントを登録する際はServiceEntryに毎回登録しています。

blue-green deploymentの設定

まずは、Kubernetes のPodをサービスとして稼働させるためのリソースを作成します。

  • namespace

gist.github.com

Namespaceを作成する際に istio-injection: enabled を設定しておきます。この設定で自動的にistio-proxyがサイドカーコンテナとして起動するようになります。

  • deployment

gist.github.com

Deploymentでは、 version: blueversion: green の2つnginxを稼働させます。Istioの設定を行う際に、このlabelを元にBlue-Greenができるように設定します。

  • service

gist.github.com

serviceでは、port: 40001で待ち受け、app: nginxtargetPort: 80番へフォワードします。

続いて、Istioのリソースです。Istioのリソースについては、istioをインストールした際にCRDに登録されていますので、kubectl コマンドを利用して設定の反映が可能です。

  • gateway

gist.github.com

gatewayには許容するHostを指定します。正規表現も可能です。 今回は sample.hoge.com を登録します。

  • virtualservice

gist.github.com

gatewayのルールにマッチした通信が、virtualserviceのルールに基づいてkubernetesのServiceへルーティングされます。ポイントとして、headerに x-version: blue とあれば、subset: blue のdestinationruleが適用されるようにしています。このほかの通信については全てgreenにアクセスされます。この設定を利用してblue-greenの切り替え前にblue環境のテストに利用が可能です。

  • destinationrule

gist.github.com

subsetがblueであれば、version: blue のラベルを付与し、subsetがgreenであれば、 version: green のラベルを付与します。これによって、どのPodにアクセスが振り分けられるかが決まります。以上で、設定が完了です。設定を確認するには下記のコマンドを実行します。

$ kubectl get gateway
$ kubectl get virtualservice
$ kubectl get destinationrule

今回構築した環境のイメージは下記の通りです。

f:id:tanan55:20181118230108p:plain

では、実際にアクセスしてみます。今回は、nginxコンテナに出力されたログを見ることにより、どちらのPodにアクセスされたかを確認します。 下記のコマンドでコンテナのログをtailしておきます。

$ kubectl logs -f -n sample-ns $(kubectl get pod -n sample-ns | grep blue | cut -d' ' -f1) -c nginx
$ kubectl logs -f -n sample-ns $(kubectl get pod -n sample-ns | grep green | cut -d' ' -f1) -c nginx

まずは、headerにx-versionを記載せずアクセスします。

$ kubectl get svc istio-ingressgateway -n istio-system
// EXTERNAL-IP を確認
$ curl -XGET -H 'Host: sample.hoge.com' http://<EXTERNAL-IP>/

f:id:tanan55:20181119223402p:plain

最初にアクセスした際は、全てのリクエストがblueのPodにしかリクエストが送られません。

ここで、headerにx-version: green を付けてアクセスします。

$ curl -XGET -H 'Host: sample.hoge.com' -H 'x-version: green' http://<EXTERNAL-IP>/

f:id:tanan55:20181119223549p:plain

特別なheaderをつけることで、特定のリクエストのみを新規にリリースしたgreenに送ることができます。greenが正常なことを確認できたら、blueとgreenを切り替えます。切り替えの方法は、先ほど設定したvirtualserviceのsubsetのblue-greenを逆転させるだけです。

gist.github.com

上記のyamlを適用後、アクセスすると、blueとgreenが入れ替わり、Blue-Green Deploymentが出来ます。

Istioを使い始めてこれはいい!と思ったポイントとしては、本番環境と全く同じ環境でテストができるということです。検証環境と本番環境で差分があってリリースがうまく行かないというのはよくあることだと思いますが、Istioを使うことで、テストができている状態のPodにアクセスを切り替えることが簡単に実現できます。さらにこれを応用して、特定の割合を振り分けたり、社内ユーザのみに新しい環境にアクセスしたりといったことをPodに変更を加えることなく実現できるようになります。 今回は紹介していませんが、gateway や istio-proxy で telemetryを収集でき、grafana / jaeger 等でリクエストのトレースやレスポンス速度を簡単に可視化できるのでこの辺りをフルスタックで揃えているIstioには最初感動しました。ただし、ハマりどころの多さ(本当に多い)や安定性といった面ではまだまだ成熟していない(バグ結構ある)ので、まずは影響の少ないnamespaceから、かつ、メイン機能となるルーティング周りからIstioを使い始めて、運用ノウハウを貯めていくのがいいかなと個人的には思っています。

仲間募集!!

ユーザベースのSPEEDA SREチームは、 「No Challenge, No SRE, No SPEEDA」 を掲げて業務に取り組んでいます。

「挑戦しなければ、SREではないし、SREがなければ、SPEEDAもない」という意識の元、ユーザベースのミッションである 「経済情報で、世界をかえる」 の実現に向けて、日々邁進しています。

IstioやKubernetes以外にも様々なことにチャレンジしてますので少しでも興味を持ってくださった方はこちらまで!

Istioを使いこなすために知っておくこと

こんにちは、SPEEDAのSREチームの阿南です。最近Kubernetes界隈が盛り上がっていますね。ここ一年で、各企業での利用事例やKubernetesを解説している書籍等もかなり増え、活用の仕方も徐々に確立されて来ているのではないでしょうか。一方Istioについては、色々できそうということはわかったんだけど、利用事例も少ないためどう使えばいいかわからない、本番運用しているという企業もまだあまり聞かないし導入に踏み切れない、という方も多いかと思います。弊社ではまだ2つのnamespaceのみですが、Istioで運用を開始しております。今回は、Istioで実現できることはなんとなく知っているが、内部の通信の仕組みやどのようなコンポーネントがあるのかを追っていきたいと思います。その上でBlue-Green Deploymentを実現するための設定方法についても次回記事で触れていきます。

この記事で扱うこと

  • Istioを構成するコンポーネントについて
  • Istioの通信の流れ

Istioを構成するコンポーネント

Istioのコンポーネントについては、IstioのDocumentationに下記の図が紹介されています。

f:id:tanan55:20181125110511p:plain

図の上側はdata planeで、サービスのPodにサイドカーコンテナ(Envoy) が起動し、Podへのリクエストを中継しています。 図の下側はcontrol planeで、Pilot, Mixer, Citadel が稼働します。Pilotは各サイドカーコンテナにConfigを反映し、Mixerはtelemetry収集やPolicy check(アクセスコントロールや流量の制御等)を担います。Citadelは証明書の管理を担当します。

では、実際にIstioをデプロイしてみて確認してみます。

f:id:tanan55:20181008191459p:plain

デプロイの方法については省略しますが、コマンド一つで大量のPodが起動してきますので、一見面食らいますが、順に整理していきます。(grafana, prometheus, servicegraph, tracingについては実際のサービス通信とは関係が薄いため、省略します)

pod 役割
istio-citadel 証明書の発行、管理を実施
istio-egressgateway Istioの内部から外部へ通信するためのgateway
istio-galley ユーザが定義したIstio設定のvalidationを実施
istio-ingressgateway Istioの外部から内部へ通信するためのgateway
istio-pilot ユーザが定義したIstio設定を反映
istio-policy Mixerの一部. istio-proxyに来たrequestをチェック
istio-telemetry Mixerの一部. 各podからテレメトリを収集
istio-sidecar-injector Podが起動する際に、リクエストをhookしてサイドカーを auto Injectする

上記のコンポーネントでIstioの外からどのようにサービスのPodまでリクエストが到達しているのかを図で表現してみました。

f:id:tanan55:20181125114804p:plain

上図の番号順に処理内容を解説します。

  • リクエストが来る前にEnvoyをサイドカーコンテナとしてInjectしておく。(⓪)
  • ingress-gatewayへリクエストが到達。ここで、hostやpathベースで、どのサービスPodにリクエストをforwardするか決める。(①)
  • istio-proxyへリクエストが到達。実際には、サービスPodへのリクエストをPreroutingしている。例えば、サービスコンテナが80番で稼働していた場合、80番に来たリクエストを15001(Envoyポート)に流す。(②)
  • istio-proxyからistio-policyにアクセスし、Policyのチェックを実施。例えば、流量の制御(最大同時アクセス数100等)で条件にひっかかった場合は、サービスのコンテナに到達する前にその時点でリクエストが返される。(③)
  • Policyでチェックを通ったリクエストがサービスコンテナに到達。(④)

この辺りは、実際に通信の流れを理解するまでに時間がかかりました(今も勉強中です)。ご自身で確認したい方は、実際にistio-proxyのコンテナでshellを起動して、tcpdump, netstat等を取ってみると理解が進むと思います。istio-proxyにはデフォルトでこの辺りのコマンドがインストールされているので、スムーズに調査ができます。
今回、実際に通信をみていった結果、想像以上に色々なことをしてるなという印象でした。特に、PolicyチェックのためにMixerにアクセスしている点は結構重要で、istio-policyのPodがダウンするとサービスの通信もダウンしてしまいます。本番運用する際は、この辺りのConfigurationも注意して見ていきましょう。ちなみに、Istioは必要なコンポーネントに絞ってインストールが可能なので、最初はistio-policyなしで運用するのも十分ありだと思います。

今後Istioがどの程度、使われ広まっていくかはまだわかりませんが、Service Meshに必要な概念や機能を勉強するには良いソフトウェアだと思いますので、皆さんもぜひ一度触れてみてください。次回の記事では、通信の流れやコンポーネントを理解した上で、Istioを使ったBlue-Green Deploymentの方法をみていきます。

Kubernetes + Istioでblue-green deploymentを実現する

仲間募集!!

ユーザベースのSPEEDA SREチームは、 「No Challenge, No SRE, No SPEEDA」 を掲げて業務に取り組んでいます。

「挑戦しなければ、SREではないし、SREがなければ、SPEEDAもない」という意識の元、ユーザベースのミッションである 「経済情報で、世界をかえる」 の実現に向けて、日々邁進しています。

少しでも興味を持ってくださった方はこちらまで!