谷本 心 in せろ部屋

はてなダイアリーから引っ越してきました

Intel Mac miniを10GbE対応のNAS代わりにしてみた。

Mac Studioが発表されてすぐに発注し、発売から1週間ほどで手元に届きました。Mac Studioは有線LANがデフォルトで10GbEになっているという思い切りの良さでして、せっかくなのでこの10GbEを活かすべく、家のネットワークを見直すことにしました。

目的

10GbE化して何をするかって言うと、ファイル共有です。

元々はIntel Mac miniで外付けのSSDを3台(Thunderbolt 3、USB-C 3.1 Gen2、USB 2.0)使っていましたが、足りなくなるたびに買い足してポートが埋まっていってしまうため、いずれNASを使いたいなと思っていました。ただ1Gbpsの環境では100MB/sec程度しか出ないため、性能的には微妙です。

外付けSSDはThunderbolt 3のもので2000MB/sec(理論値 4000MB/sec程度)、USB 3.1のもので700MB/sec(同 1000MB/sec程度)が出るため、それらに比べても100MB/secというのはあまりに遅すぎます。

ただ10GbEならUSB 3.1に相当するスピードですから、この環境でならNASも快適に使えそうです。ただ10GbE対応のNASは高いですし、既存のNASSATASSD(HDD)のものがほとんどで、M.2 NVMeのSSDが使えるものがあまりありません。

そんなこんなで迷ってるときに「Mac miniNAS代わりにしちゃえば、十分に性能が出るし便利だよ」と教わったので、今回、Mac Studioを導入する機会にMac miniNAS代わりにすることにしました。

買ったもの

  • 10GbE対応のハブ
  • Thunderbolt 3 → 10GbEアダプタ

10GbE対応のハブ

10GbE対応のハブはまだまだ製品も少なく値段も高めです。今回はこんな条件で選びました。

ただ、すべての条件を満たそうとすると厳しくなるため、今回はポートの数だけ我慢して、比較的コスパの高いTP-Linkの5ポートの製品を選びました。

www.amazon.co.jp

他にもQNAPがいくつか10GbE対応のハブを出しています。

www.amazon.co.jp

ただ、こちらの製品だとSFP+ポートを10GbEのRJ-45ポートに変換するためのアダプタも必要になると少し出費がかさむため、まずは安いTP-LinkのTL-SX105で試してみることにしました。

Thunderbolt 3 → 10GbEアダプタ

ハブと同じくらい高いのが、MacのThunderbolt 3ポートで10GbE対応の有線LANに変換できるアダプタです。今回はOWCの製品を選びました。

www.amazon.co.jp

通常は2〜3万円ぐらいするのですが、公式サイトで箱破損のものが1万円くらいで破格のセールになっていたため、飛びつきました。セール開始から3時間後には品切れになっていましたね。

構成

接続はこんな構成になっています(10Gbpsの部分のみ)

他にもメッシュルーターやら家電やらゲーム機がつながっていますが、今回の話題の対象外なので割愛します。

ベンチマーク

構成を作ったらまずベンチマーク

SSDベンチマーク

まずはMac miniのThunderbolt 3ポートに繋いだSSDベンチマークです。

f:id:cero-t:20220326175311p:plain
Mac miniのThunderbolt 3ポートに接続したSSD

外付けSSDでも2000MB/secを超えてくるあたり、USBやeSATAの時代からすると隔世の感があります。

Mac miniの共有設定でこのSSDに外部からアクセスできるようにして、Mac Studioからアクセスしてみました。そのベンチマークがこちらです。

f:id:cero-t:20220326175243p:plain
10GbE経由でMac miniSSDにアクセス

10GbE = 10000Mbps = 1250MB/sec と考えると、シーケンシャルリードは上限に近い数字が出ています。それに比べてライトは劣りますし、ランダムリードライトはかなりイマイチな感じです。ランダムアクセスはネットワークがオーバーヘッドになりやすいので仕方ないのでしょうけど、もう少しランダムの性能が出てくれれば、直接ThunderboltやUSBで繋いだ場合と遜色ない感じで使えるのでしょうね。

使用感

テキストファイルや画像ファイル、小さな動画ファイルなどは、ローカルファイルと全く変わらない体感で操作できます。この辺は別に1Gbpsのネットワークでもさして変わらないでしょう。

問題は、大きな動画ファイルです。

10GBくらいの動画ファイルをVLCで再生したところ、ローカルで開くのと変わらないスピードですぐに再生が始まりました。ただ、20GBを超えるファイルになると、開くまで数秒くらい掛かることがありました(パッと開くこともあります。謎)

また、無線LAN環境では再生中に止まってしまうようなビットレートの高い動画でも、10GbE環境ではまったく止まることがありませんでした。シーク(見たいところにジャンプ)しても待たされることはありません。このあたりはまさに期待通りの挙動ですね。

そんなわけで、ネットワークを10GbEにすることでファイル共有の使い方が一段階変わったという感想になりました。無線LANWi-Fi 6/6Eにすれば、使い勝手が変わるんですかね? またいずれ試してみたいと思います。

おまけ: 固定回線のベンチマーク

ついでに固定回線をauひかりの10Gbps契約をしたので、そのベンチマークも載せておきますね。

f:id:cero-t:20220326180816p:plain
auひかり 10G契約の回線テスト

めっちゃはやい。

Intel Mac miniをmacOS 12.3にアップデート後、映像が出なくなる現象が起きている。

※本事象はmacOS 12.3.1にアップデートすることで改善されました。記録のために残しておきます。

事の起こり

待望のユニバーサルコントロールが提供されたmacOS 12.3、ついにリリースされたので僕も喜び勇んで即アップデートをしました。

ただ、アップデート後、Mac miniが起動しない。正確にはジャーーンという音がしてリンゴマークが出た後に、画面が真っ暗なまま。何度か電源長押しで強制シャットダウン→再起動を繰り返すも症状は変わらず。

画面は真っ暗でもキーボードを叩いているとビープ音がするので、画面が映っていないだけで、起動はしているようです。

映像出力がおかしいのだと思って、ディスプレイと接続しているUSB-CやHDMIを接続し直して何度か起動を繰り返した結果、何とか起動できる状態まで復旧したのですが(方法は後述)こんな不安定な状態になってしまうのは、さすがに困ります。

他にも同症状の人が

他にも同じ症状が出ている人はいないかと思ってツイッターで検索してみたところ、同症状の人が何人かいました。

Redditにもスレッドがありました。

www.reddit.com

共通点はIntel Mac mini、LGのディスプレイでの報告が多い

全員に確認したわけではないのですが、この症状が起きている人に聞いてみたところ、だいたい共通点がありました。

  • Intel Mac mini(2018だけ?)
  • USB-Cの映像出力がされなくなる
  • LGのディスプレイが多い。BenQDellPhilipsなどでも報告あり(4Kディスプレイだけ?)
  • デュアルディスプレイの場合、症状の出方が人によって少しずつ違う

ちなみに僕の場合は、LG 27UL850 + Intel Mac mini (2018) でした。

同じ機種を使う人がみな発現しているのか、個体差があるのかはまだ分かっていません。

回避方法

  1. ディスプレイ側の設定でDisplayPortのバージョンを1.4 → 1.2に下げる
    • これが最も確度の高い回避策です
  2. Mac miniのThunderbolt3ポートで、右側2つのいずれかをディスプレイと繋いでいる場合、左側2つのどちらかに変える
    • これだけで回避できた人もいました
  3. HDMI接続に変える
    • HDMIが使える場合は、HDMI接続に変えれば問題を回避できます。なお、僕の場合はHDMI接続でもブラックアウトしたままでした。
  4. Thunderbolt3(左側2つのいずれか)と、HDMIの両方を接続して起動する
    • これでThuderbolt3(USB-C)がメインディスプレイとして表示され、HDMIがサブディスプレイとしてブラックアウトしたままになることがあります。
    • ディスプレイをミラーリングにすれば、少なくとUSB-Cをメインディスプレイとして使う分には困らなくなります。

まとめ

DisplayPortのバージョンを1.4 → 1.2に下げるのが最も効果的なようです。

※冒頭にも書きましたが、本事象はmacOS 12.3.1にアップデートすることで改善されました。

Dapr Advent Calendar 25日目 - Daprを使うべきかどうか

こんにちは Dapr Advent Calendar 25日目です。ついにファイナルを迎えました。私たちの挑戦を応援し続けてくれた世界中のファンの皆さん、そして、Dapr Advent Calendarに集まってくださった皆さん、本当にありがとうございます!

Daprを使うべきかどうか

一部のオタクにしか分からない挨拶から始めて申し訳ありません、いよいよDapr Advent Calendarも最終回です。最後は「Daprを使うべきかどうか」について論じたいと思います。

なぜDaprを使うのか

まずはDaprのメリットや、Daprが解決する課題について説明します。

この3つを順に説明します。

1. アプリとインフラを疎結合にする

Daprの主目的であり、このAdvent Calendarでも何度となく触れてきたことですが、Daprを使うことでアプリケーションとインフラ層を分離することができます。そのおかげで開発時にインフラ関連のツールは必要ありませんし、インフラのことを気にせずビジネスロジック(とWeb API)の開発に注力することができます。

その辺りはこの記事に書いていました。

cero-t.hatenadiary.jp

また、Spring Cloudのようにアプリケーションとインフラ層の結合がより密接しているフレームワークでは、新旧バージョンの混在が難しいことがありましたが、アプリケーションとインフラ層が分離されているDaprでは、アプリケーションだけのバージョンアップ、Daprやk8sだけのバージョンアップが行いやすいというメリットがあります。

その辺りについては、この記事にも書いた通りです。

cero-t.hatenadiary.jp

このアプリケーションとインフラ層を分離できるというのが、Daprの1つ目のメリットとなります。

2. 言語やフレームワークを選ばない

2つ目のメリットが、Daprがサイドカーという方式を採っているため、言語やフレームワークを選ばないことです。

僕はJava + Spring Bootで開発していますが、僕が大好きだったSpring CloudはJVM言語でしか開発ができません。Daprであればその縛りはなく、HTTP通信さえできれば好きな言語や好きなフレームワークでの開発ができます。

仮に組織の中で様々な言語やフレームワークを使う人たちがいたとしても、組織の共通技術基盤としてDaprを採用することができ、皆のノウハウを集めて育てることができます。

その気になれば、一つのシステムを別々の言語やフレームワークを混在させて構築することもできるでしょう。マイクロサービスの一要素としてポリグロット(多言語)が挙げられることがありますが、まさにそれを実現するための基盤としてDaprを利用することができます。

そんな好き放題に言語やフレームワークが使われることはあまりないとは思いますが、システムが非常に大きくてサブシステムごとに開発する組織が分かれる場合や、たとえばフロントエンドに近い部分はNode.jsで作り、コアに近い部分はJVM言語で作る、という程度のポリグロットなら現実にもあり得るでしょう。Daprはそうした状況でも共通的に使えるのです。

3. マルチクラウド

そしてメリットの最後が、マルチクラウドです。

たとえばAWS向けに開発していたシステムを、AWSの障害に備えてAzureやGCPでも運用したい時には、それぞれのマネージドサービスやミドルウェアを抽象化するDaprを利用できます。

そんな案件ほとんどないでしょ、せいぜいマルチリージョンまででしょ、と思っていたのですが、意外とそういうリクエストが "なくはない" という感じで、実際にマルチクラウドを目指している案件で、Daprを採用するかべきかどうかを一緒に検討して欲しいと言われています。

もちろんDaprなど使わなくても、各クラウド固有のマネージドサービスではなくPostgreSQLMySQL、Redis、またたKafkaやRabbitMQなどを使うようにすれば十分だという面もあるでしょう。しかしDaprを使えばマネージドサービスのメリットを引き出しながら、共通のAPIで利用できるというメリットがあるというのも確かです。

ネットワーク関連の設定などはDaprの範疇外ですし、マルチクラウドは簡単でもありませんが、せめてアプリケーションとその周辺のインフラ層においてはDaprを使うことで複雑さを排除できる可能性があります。

この辺りは実際に取り組む機会などあれば、また改めてお伝えしたいと思います。

Daprを使う上での課題

もちろん、Daprを使えば順風満帆というわけでも、全幅の信頼が置けるというわけでもありません。Daprのデメリットや課題についても目を向けてみましょう。

  • 導入事例が極めて少ない
  • 機能が少ない
  • メンテナンス期間の短さ

この3つです。ほぼ説明不要な気もしますが、順に説明します。

1. 導入事例が極めて少ない

まず一つ目、Daprは導入事例がほとんどありません。人によっては「cero-tさんがやってるやつ」みたいな印象があると耳にしたことすらあります。それくらい日本でも、おそらく世界でも、導入事例は多くありません。

全く枯れていない技術ですから、大きな問題があったり、情報が少なかったりするかも知れません。あるいは全く流行らなくてメンテナンスされなくなってフェードアウトしてしまうリスクだってあります。

新しいプロダクトを使うというのは、そういうリスクと戦うことになります。

2. 機能が少ない

Daprは世にアナウンスされから約2年、バージョン1.0がリリースされてから約1年くらいの新しいプロダクトですから、そこまで機能が多くありません。このDapr Advent Caledndarだけであらかたの機能を網羅できてしまう程度です。

Spring Cloudなどと比較すると、提供する機能も、対応するコンポーネント(マネージドサービス、ミドルウェア)も少ないですし、いくつかのコンポーネントはまだAlpha版やBeta版だったりします。Alpha版やBeta版の機能でも十分に使えるので僕は使っていますが、いつかAPIの仕様が変わるかも知れないというリスクを受け入れざるを得ません。

機能が少ない部分は自分でカバーしたり、クラウドの機能を直接使ったりする必要があるため、すべてがDaprだけで完結するわけでもありません。足りないところは自分で補う必要があるのです。

3. メンテナンス期間の短さ

DaprはMicrosoft社が中心に開発しているプロダクトですが、まだビジネス的に十分回っているわけではありませんから、サポートなどのエコシステムもそんなに整ってはいません。バージョン1.0からは「Production Ready」な品質として、少し古いバージョンでもパッチが提供されるようになりました。ただ、そのパッチの提供期間も長くはありません。

Daprはおおむね2ヶ月に1度程度、マイナーバージョンアップ(1.4→1.5、1.5→1.6など)が行われます。またバグフィックスや軽微な機能追加のためのパッチ(1.4.1→1.4.2、1.5.0→1.5.1など)が提供されます。このパッチの提供は「現行バージョンと、その一つ前のバージョン」のみが対象となっています。

docs.dapr.io

2021-12-25現在の最新バージョンは 1.5.1 で、その前バージョンである 1.4 はパッチ提供の範囲内なので 1.4.4 までリリースされていますが、バージョン 1.3 以前にはパッチは提供されません。

おおむね2ヶ月に一度バージョンが上がることを踏まえると、特定バージョンのメンテナンス期間は4ヶ月程度しかありません。もちろん最新バージョン(1.x.0)がリリースされてすぐには使わず、パッチが1つ2つリリースされてから使うことも多いでしょうから、実質的には長くとも2ヶ月に一度くらいにはDaprのバージョンを上げる必要があります。

次のドキュメントにあるように1コマンドでバージョンアップできるとは言え、その頻度には抵抗がある組織だってあるかも知れません。

docs.dapr.io

このサポートの短さは課題の一つと言えます。

Daprの課題を覆す

僕は上に書いたようなリスクを受け入れてDaprを使っているのですが、もちろん僕の「Daprを使いましょう」という提案を受け入れてくれたお客様も相当な勇気(あるいは無謀さ)があったように思います。*1

ただ、Daprに問題があった時に心中するつもりで使っているわけでもありません。Daprにどうしても受け入れられない問題があると分かったときには「Daprを捨てて自分で作る」つもりでいます。

DaprはHTTP通信で使うサイドカーですから、同様の機能を自分で作り込み、URLの向き先を自分で作った機能にすれば、Daprを使わずに同様の機能を提供できます。Daprを使っている以上はアプリケーションとインフラ層が疎結合になっているので、別のものに差し替えることだって不可能ではないのです。

もちろん口で言うほど容易なことではないですし、そんな状況が訪れることは願ってないですが、「Daprを使って開発すれば、最悪、自分で作り直すことだってできる」という感覚ではいるのです。そのため、リスクを受け入れることができました。

サイドカーコンテナ

ところで、Daprのようなサイドカーを、単独プロセスではなくコンテナとして提供するパターンもあります。

qiita.com

ここで記載されているサイドカーパターンやアンバサダーパターンを使えば、アプリケーション開発はビジネスロジックに注力し、サイドカーやアンバサダーがインフラとの連携に注力するという責任分解もやりやすくなるでしょう。Daprで提供されているような主要機能を、自分で好きなように実装して提供することもできるはずです。

ではなぜサイドカーコンテナパターンにしなかったのかと言うと、単純に、自分で実装するのが面倒だったためです。そこにDaprがあるんだから、Daprを使おう、ダメだった時には自分で作ろう、というくらいの感覚です。

そうやってダメな場合の撤退パターンも考えたうえで、僕はDaprを使うことに決めたのです。

まとめ

  • なぜDaprを使うのか
  • Daprを使う上での課題
    • 導入事例が極めて少ない
    • 機能が少ない
    • メンテナンス期間の短さ
  • 最悪、自分で作り直すという覚悟を持って使い始めた

最後になりますが、僕はDaprというプロダクトに魅力を感じているのもそうですが、それ以上にDaprという「アプリケーション寄りのサイドカーサービス」というアーキテクチャそのものに魅力を感じて使っているとも言えます。そのアーキテクチャに利があるという確信があるため、最悪は自分で作れば良いという覚悟ができましたし、他にもっと良いものが出てきたら乗り換えれば良い、きっと乗り換えやすいはずだと考えています。

それがいまの僕の考え方なんだ、ということをお伝えして、Dapr Advent Calendarを締めくくりたいと思います。

それでは、またの機会に!

See you!

*1:ちなみにこのお客様は5〜6年くらい前に「マイクロサービスで作ってみましょう」という提案も受け入れてくれました

Dapr Advent Calendar 23日目 - Daprのロードマップ

こんにちは Dapr Advent Calendar 23日目です。これまでのAdvent CalendarではDaprの機能や使い方を紹介してきましたが、今後、Daprはどういう方向が強化されるのでしょうか。そのロードマップを少し眺めてみたいと思います。

Daprのロードマップを見てみよう

Daprのロードマップは、Github Projectsで管理されています。

github.com

ロードマップにはいくつかのレーンが設けられています。

Backlog は対応予定だけど優先順位が確定していないもの。👍をつけていけば優先度を上げられるようです。

Planned (Committed) はだいたいマイルストーンが決まって設計を検討しているもの。

In Progress (Development) は開発中のもの。

DoneReleased はそれぞれ開発完了のものとリリース済みのものです。

もちろんすべてのIssueは紹介できませんので、In Progress、Planned、Backlogの中から、主要なものや面白そうなものをピックアップして紹介します。

In Progress

現在開発中の In Progress には、2021-12-23時点で8個のIssueがあります。4つが現在AlphaやBetaのコンポーネントをGAにするとか、作り直そうというものです。

残りの4つを簡単に紹介します。

Planned

今後対応予定の Planned には、2021-12-23時点で26個のIssueがあります。

品質周りのIssueが多く、現在AlphaやBetaのコンポーネントをGAにするとか、DaprコンポーネントのE2Eテストや結合テストを強化するなど、内部の品質を高めるissueが全体の半数を超えています。

残りの半数のうちから、興味深いものをいくつか紹介します。

Backlog

まだマイルストーンなど決まっていない Backlog には、2021-12-23時点で25個のIssueがあります。いつ出てくるかは分かりませんが、逆に言えば少し規模や影響の大きなIssueも多いです。

Pub/sub関連

分散トレーシング関連

ネットワーク関連

その他

また、ロードマップにはないのですが components-contribのissue で、次のIssueが重要なものとしてピン止めされています。

ECS上でDaprを動かせるようにしようという提案。以前にもこのAdvent Calendarの中で触れたことがありましたが、ECSで使えるようになればユーザーも増えるんじゃないかと期待しています。

まとめに代えて

全体を眺めた感じ、品質向上のテーマや既存機能の改善がほとんどで、とんでもない大物が待っているような気配はありません。まずはアーリーアダプターが使って満足できるように足場を固めているところ、という感じでしょうかね。利用者が増えてきたり、Azureと絡めて予算がつくようになってくれば、また傾向が変わるかも知れません。

ところで、「とんでもない大物」と言えば、ロードマップにはありませんが、こんなIssueがありました。

github.com

SQLを発行するためのAPIを作ろう、トランザクションも有効にしよう、というものです。さすがにそれは考えないようにしてるのかな、と思ってたのですが、少なくとも提案はされているようですね。ちょっと注目しておきたいIssueです。

そんなわけで、今回はここまでにしたいと思います。

明日のAdvent Calendarは、@backpaper0さんがDaprのActorについて書いてくれる予定です。Actorはよく分からなくて飛ばしたところなので、楽しみですね!

それでは!

Dapr Advent Calendar 22日目 - Dapr vs Spring Cloud

こんにちは Dapr Advent Calendar 22日目です。おとといから合宿をしていたため今日は更新が遅くなってしまいました。今回はDaprをSpring Cloudと比べてみたいと思います。

Spring Cloud vs Dapr

僕はもともとSpring BootとSpring Cloudを使って、いわゆるマイクロサービスを開発してました。

Spring CloudはいわゆるCloud Nativeな分散アプリケーションに必要となる要素をフルスタックで提供するプロダクトで、サービスディスカバリー、メッセージング/ストリーミング処理、分散トレーシング、サーキットブレイカーなどの機能をSpring Bootから扱いやすい形で提供しています。こんな機能をSpring Cloud以上に使いやすく提供しているプロダクトは、おそらく今のところ他にはないでしょう*1

一時期、僕はJava以外の言語、たとえばGoなどでマイクロサービスの開発をしようかと考えたこともあったのですが、結局はSpring Cloudを使いたいからSpring Boot、そのためにJavaJVM言語)を使うという選択をしていました。そうやって言語選択に影響するくらい、Spring Cloudは分散アプリケーション開発における重要なプロダクトでした。

そんなSpring Cloudと、Daprを、次の3つの機能で比べたいと思います。

  • サービス呼び出し
  • 非同期メッセージング
  • 分散トレーシング

この3つが、分散アプリケーションの柱ですよね。

1. サービス呼び出し

まずはアプリケーションから別アプリケーションを呼び出す機能を比較します。

DaprのInvoke API

Daprのサービス呼び出しはInvoke APIを利用します。このAdvent Calendarでも何度となく触れてきました。

cero-t.hatenadiary.jp

ソースコードは次のようになります。

@Value("${baseUrl}")
private String baseUrl;

@GetMapping("/invokeHello")
public Map<String, ?> invokeHello() {
    Map<?, ?> result = restTemplate.getForObject(baseUrl + "/hello", Map.class);
    return Map.of("baseUrl", baseUrl, "remoteMessage", result);
}

baseUrl の値に http://localhost:${DAPR_HTTP_PORT}/v1.0/invoke/hello-app/method というDaprのInvoke APIを指定することで、Dapr経由で対象のアプリケーションを呼ぶことができます。

Daprはアプリケーション名から対象サービスが動いているホストを発見するために、ローカル環境ではmDNS(マルチキャストDNS)を利用し、k8sではk8s自身が持つ名前解決機能を利用します。いずれも利用できない環境では、現時点ではConsulを利用する必要があります。

たとえば、Amazon EC2上でもConsulを利用すればDaprを動かすことができます。

cero-t.hatenadiary.jp

そのような場合を除けば、Daprは基本的にはサービスレジストリなしで運用できるところが強みですね。

Spring Cloud Service Discovery

Spring Cloudのサービス呼び出しは、名前解決のためにNetflix Eurekaを利用します。

spring.pleiades.io

名前解決のためのサーバとなるEureka Server(上で言うConsulに相当します)を立て、それぞれのアプリケーションではEureka Discovery Clientを利用して自分自身をEureka Serverに登録したり、他のアプリケーションの情報をEureka Serverから取得してアクセスするという仕組みになっています。

サーバ側であるEureka Serverのコードは次のようになります。

@EnableEurekaServer
@SpringBootApplication
public class ServiceRegistrationAndDiscoveryServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceRegistrationAndDiscoveryServiceApplication.class, args);
    }
}

@EnableEurekaServer アノテーションをつけたアプリケーションを起動するだけで、難しいことはありません。もちろん設定ファイルを追加して細かな設定をしたり、クラスタを組んだりすることができます。

クライアント側のソースコードは次のようになります。

@Value("${baseUrl}")
private String baseUrl;

@GetMapping("/invokeHello")
public Map<String, ?> invokeHello() {
    Map<?, ?> result = restTemplate.getForObject(baseUrl + "/hello", Map.class);
    return Map.of("baseUrl", baseUrl, "remoteMessage", result);
}

Dapr側のソースコードと同じです。

baseUrlhttp://hello-app というアプリケーション名さえ指定すれば、RestTemplateが自動的にEureka Discovery Clientを利用して対象のアプリケーションにアクセスします。URLのシンプルさで言えば、Daprを使うよりも分かりやすいですね。

どちらが良いか?

サービス呼び出しについて、DaprとSpring Cloud、どちらが良いでしょうか。

DNSを別に立てる必要がないDaprのほうが使いやすいですが、Spring Cloudはローカル環境でもEureka Serverを立てる必要があります。ただ、立てることは大して難しくないので、そこまでのデメリットとも言えません。

また、呼び出す際のURLはSpring Cloudのほうが分かりやすく、DaprのInvoke APIはどうしても長くなってしまいます。しかし、これもそこまで大きなデメリットと言えません。

判定としては「引き分け」というか、大差ないというのが正味の所ですね。

2. 非同期メッセージング

続いて、非同期メッセージングについて比較します。

DaprのPus/sub APIとSubscription

DaprではメッセージングのPub/sub APIを用いてメッセージを送信し、Subscriptionの設定ファイルとWeb APIを作るだけでメッセージを受信できます。

cero-t.hatenadiary.jp

メッセージを送る側のソースコードは次のようになります。

@Value("${pubsubUrl}")
private String pubsubUrl;

@PostMapping("/publish")
public void publish(@RequestBody MyMessage message) {
    restTemplate.postForObject(pubsubUrl, message, Void.class);
}

pubsubUrlhttp://localhost:${DAPR_HTTP_PORT}/v1.0/publish/rabbitmq-pubsub/my-message というDaprのPub/sub APIを指定して、メッセージブローカーにメッセージを送ることができます。

続いて、メッセージを受け取る側のソースコードです。次のようになります。

@PostMapping("/subscribe")
public void subscribe(@RequestBody CloudEvent<MyMessage> cloudEventMessage) {
    System.out.println("subscriber is called");
    System.out.println(message);
}

メッセージを受け取るWeb APIを作成するだけです。

このWeb APIを利用するために、次のような設定ファイルを作成します。

apiVersion: dapr.io/v1alpha1
kind: Subscription
metadata:
  name: subscription
spec:
  pubsubname: rabbitmq-pubsub
  topic: my-message
  route: /subscribe
scopes:
  - subscribe-app

詳細な説明は割愛しますが、メッセージブローカーからメッセージを受け取ると /subscribe という上で作ったWeb APIにアクセスするという設定を書いています。

シンプルに、やりたいことを実現できる感じです。

Spring Cloud Stream

Spring Cloudでメッセージングを行うためには、Spring Cloud Streamを利用します。

spring.pleiades.io

Spring Cloud Stream 2.xから3.xの間でAPIに大きな変化があり、よりストリーム処理に特化したような形になりました。ここでは3.xの記法で紹介します。

メッセージを送る側は次のようなソースコードになります。

@PostMapping("/publish")
public void publish(@RequestBody MyMessage message) {
    streamBridge.send("my-message-0", message);
}

StreamBridge クラスを使って、メッセージを送ります。

また、ソースコード内で指定したメッセージのキーに対して、どのメッセージブローカーに送るかを設定ファイルに書きます。

spring.cloud.stream.bindings.my-message-0.destination=my-message

ここで指定した値がRabbitMQのExchangeの名前として使われます。

続いて、メッセージを受け取る側のソースコードです。

@Bean
public Consumer<MyMessage> subscribe() {
    return (map) -> {
        System.out.println("subscriber is called");
        System.out.println(map);
    };
}

DaprのようなWeb APIではなく、java.util.funciton パッケージの ConsumerFunction などを使って実装します。

そして、メッセージを受け取った際にこのメソッド(Bean)を呼び出すよう設定ファイルを作成します。

spring.cloud.stream.bindings.subscribe-in-0.destination=my-message
spring.cloud.stream.bindings.subscribe-in-0.group=my-message-subscribe

上の値がExchangeの名前、下の値がQueueの名前として使われます。設定に若干クセがあって微妙に分かりづらいですね。

ただ、subscribe側を「API」などではなく「Function」と捉えて実装させるというのは納得感もあります。このバージョンのSpring Cloud Streamのソースコードを読んだことはないので推測になりますが、おそらくこの前段の処理は、メッセージブローカーからメッセージを1つずつ受け取って処理するのではなく、WebFluxを使ってノンブロッキングに複数の呼び出しをまとめているのでしょう。その方が性能的なメリットがありますからね。

どちらが良いか?

サービス呼び出しについては、DaprとSpring Cloud、どちらが良いでしょうか。

設定ファイルも含めた構成としては、Daprの方が分かりやすいです。しかし性能やメモリ使用量の少なさに関しては(計測したことはないですが)Spring Cloud Streamの方が上になると推測できます。特に大量のメッセージを同時に捌くような必要がある際には差が出そうです。

またDaprのほうはHTTPを使ってメッセージングをしていますが、Spring Cloud Streamでは独自クラスを使ってメッセージングをしています。そのためDaprの方がテスト時に別の処理に置き換えたり、curlコマンドでのテストがやりやすい一方で、HTTPを経由することのオーバーヘッドがあるとも言えます。

取り回しやすさではDaprに軍配が上がり、性能についてはSpring Cloudに軍配があがる、と言えるでしょうか。

3. 分散トレーシング

最後に、分散トレーシングについて比較します。

Daprの分散トレーシング対応

Daprでは設定ファイルを書くだけで分散トレーシングが有効になります。

cero-t.hatenadiary.jp

設定ファイルは次のような内容です。

apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
  name: daprConfig
spec:
  tracing:
    samplingRate: "1"
    zipkin:
      endpointAddress: http://localhost:9411/api/v2/spans

分散トレーシングのサンプリングレートやzipkinサーバのアドレスを指定するだけで、分散トレーシングが有効になります。

ただしトレースIDを自分で伝播させる必要があるため、次のようなコードを書く必要があります。

@GetMapping("/invokeHello")
public Map<String, ?> invokeHello(@RequestHeader("traceparent") String traceparent) {
    HttpHeaders httpHeaders = new HttpHeaders();
    httpHeaders.set("traceparent", traceparent);
    HttpEntity<?> request = new HttpEntity<>(httpHeaders);

    Map<?, ?> result = restTemplate.exchange(helloUrl + "/hello", HttpMethod.GET, request, Map.class).getBody();
    return Map.of("baseUrl", helloUrl, "remoteMessage", result);
}

HTTPヘッダで受け取った traceparent というヘッダの値を、次のリクエストのHTTPヘッダに渡しています。これをすべてのエンドポイントの処理に書くことは現実的ではないですから、何か共通的な処理にする必要があるでしょう。その辺りは自分で用意しなければなりません。

Spring Cloud Sleuth

Spring Cloudで分散トレーシングを行うには、Spring Cloud Sleuthを利用します。

spring.pleiades.io

Spring Cloud Sleuthをdependenciesに追加して次のように設定ファイルを作成します。

spring.sleuth.sampler.rate=100
spring.zipkin.sender.type=web
spring.zipkin.baseUrl=http://localhost:9411

このように設定すれば、分散トレーシングが有効になりZipkinにトレース情報が送られます。

分散トレーシングは、RestTemplateやWebClientによるHTTP通信や、Spring Cloud Streamによるメッセージングなどすべてが対象になりますし、Daprのほうで問題となったトレースIDの伝播も自動的に行われます。

それだけでなく、トレースIDなどの情報がロガーのMDC(Mapped Diagnostic Context)に保存されるため、ログの設定を書けばログにトレースIDなどを出力することができ、トレースIDを起点としたログ検索がやりやすくなります。

このような事が何の実装もなく実現でき、トレースIDの伝播も自動で行われるのは、Spring Cloudがフルスタックで提供されていることが引き出すメリットだと言えるでしょうね。

どちらが良いか?

分散トレーシングについては、トレースIDの伝播も自動的に行うSpring Cloudの方が便利だと言えます。

また、Spring Cloud Sleuthではキュー経由でトレース情報を送ることでトレーシングの余計な待ち時間を減らしたり、Zipkin以外の様々な分散トレーシングツールと連携したりすることも可能です。

さすがは5年以上の歴史あるプロダクトだと言ったところでしょうか。僕が分散トレーシングという言葉を知ったのもこのSpring Cloud Sleuthがキッカケでしたし、他のプロダクトに先駆けてフルスタックで機能を提供してきた筋の良いプロダクトだと思います。

総合的な比較

ここまでDaprとSpring Cloudの3機能を比べてきましたが、総合的に見て、どちらの方が良いのでしょうか。

分かりやすさやHTTPによる疎結合という点ではDaprの方が上ですが、非同期メッセージングの性能や分散トレーシングの機能はSpring Cloudに強みがあります。歴史の浅いプロダクトと、歴史あるプロダクトの差、と言い換えることができるかも知れません。またこの3機能だけではなく他の機能を含めて、あるいは世の中にある情報量の差なども考えれば、Spring Cloudの方が上だと言えるでしょう。

バージョンアップに伴う苦痛

それではなぜ、僕はSpring CloudをやめてDaprを選んだのでしょうか。一言でいうと「バージョンの互換性やバージョンアップに伴う問題」が要因でした。

たとえば古いバージョンのJavaとSpring Bootで運用しているシステムがあり、新システムではより新しいバージョンのJavaとSpring Bootで開発しようとした場合、それぞれのシステムでSpring Cloudを使おうとすると、それぞれのSpring Bootに対応したSpring Cloudのバージョンが異なってしまうため、そこで互換性が失われていることがありました。たとえばSpring Cloudのバージョンアップに伴って、内部で使っているEurekaのバージョンが上がった際にプロトコルが変わってしまったことがありましたし、また上にも述べたとおりSpring Cloud Streamは2.xと3.xで大きくAPIの形が変わっています。

であれば常にJava、Spring Boot、Spring Cloudを最新バージョンに更新し続ければ良いのかも知れません。ただ、Spring Cloudはバージョンアップに伴う作業が大がかりになりがちでした。これはCloud Nativeという分野が比較的新しいため、対応するプロダクトやトレンドが変わり、それにSpring Cloudが追従しようとした際に互換性を失わざるを得ないという面があったのだと思います。

またもう少しマイナーな問題としては、Spring Bootの提供スピードとSpring Cloudの提供スピードの違いや依存性の複雑さなどにより、Spring Bootのバージョンを上げようとすると、Spring Cloudがまだ対応していないとか、参照しているライブラリのバージョンが異なってエラーになることがありました。

その辺りの痛みを経験したことで、Spring Cloudが悪いというよりは、「Web APIを提供するアプリケーションと、その下支えをするインフラとの境界になるレイヤーは、もっと疎結合にして、それぞれ個別にバージョンアップできるようにしたほうが良い」と考えるようになったのです。

それがSpring CloudではなくDaprに使うに至った経緯です。

まとめ

  • サービス呼び出しについては、DaprもSpring Cloudもあまり変わらない
  • メッセージングについては、Daprの方がシンプルだがSpring Cloudのほうがおそらく性能が良い
  • 分散トレーシングについては、DaprよりもSpring Cloudの方が高機能だし使いやすい
  • Spring Cloudはバージョンアップに伴う苦痛が大きかった

ところで、最近しばらくSpring Cloudをあまり追っていなかったためドキュメントなどを見直しながら今回のブログを書いたのですが、Spring Cloudのドキュメントはどこに何があるのか少し分かりづらくなっていて、どうしちゃったのかなという気持ちになりました。歴史あるプロダクトに様々な機能がついて複雑になっていく中で、ドキュメントも複雑になっていくということがあるのかも知れません。もちろん、その辺りを補うようなガイドや、ブログ、発表資料なども多いのが、Springの良いところなのですけどね。

いずれにせよ、SpringはJavaのCloud Nativeアプリケーションの開発を牽引する立場として、今後も応援したいと思います。

それでは、また明日!

*1:もしあればこっそり教えてください

Dapr Advent Calendar 21日目 - Daprの開発環境

こんにちは Dapr Advent Calendar 21日目です。今回からは実践編として、実際に開発する上で役立つノウハウやTipsを紹介したいと思います。

Daprの開発環境

ここまでDaprの機能を説明してきて、なんだかよさげには見えるけど、実際に効率良く開発できるのかについてはまだ疑問があるかも知れません。今回は開発の効率に直結する「開発環境」や「デバッグ」について話したいと思います。

DaprのIDE Support

DaprのIDE対応について、公式ドキュメントにはVisual Studio CodeIntelliJについて記載されています。

docs.dapr.io

Visual Studio CodeIntelliJのどっちを使うと良いですかって聞かれれば、そりゃJava開発なんだから今はIntelliJでしょう、としか答えられないのですが、Visual Studio Code対応がどのようになっているのかを見ておきます。

Visual Studio Code + Dapr extensionの所感

Visual Studio Codeの拡張としてDapr extensionが提供されている。このextensionでできることは次の通りです。

  • componentファイルのscaffold作成(pubsub、statestore、zipkinの設定ファイルの作成)
  • dapr起動コマンドのscaffold作成
  • 起動中のDaprアプリケーション一覧の表示
  • 起動中のDaprアプリケーションに対するinvokeとpublish

設定ファイルのscaffoldは ~/.dapr/components と同じファイルを生成するだけなので大したことはありません。デモ用ですかね。

dapr起動コマンドのscaffold作成は、Visual Studio Codeのlaunch.jsondapr run でアプリケーションを起動するconfigurationを追加するというものです。dapr run コマンドを手打ちしなくて済むようになります。

Daprアプリケーション一覧は、次のように表示されます。

f:id:cero-t:20211221005228p:plain:w300
Dapr extensionで見るアプリケーション一覧

起動中のアプリケーション一覧が表示されます。dapr list コマンドで取得できるものと同様ですね。ちなみにk8s上にデプロイしたDaprアプリケーションは見えませんでした。

これらのアプリケーションを選択し、invokeやpublishを実行することができます。invokeする時にわざわざcurlコマンドを叩いたり、ちょっと長めのURLを打ったりしなくて済みます。

Dapr extensionが提供する機能はこのくらいなので、そんなに高機能というわけでも生産性がすごく上がるというわけでもないですが、Daprアプリケーションの簡易UIとして使うのも悪くないという印象でした。

IntelliJによるデバッグ

続いて、IntelliJによるDaprアプリケーションのデバッグ方法についても説明しておきます。

対象のソースコードGitHubにあるHello Worldアプリケーションを利用します。

https://github.com/cero-t/dapr-advent-2021/

ここにある「hello」モジュールを利用します。

Daprの単独起動

まずはDaprを単独で起動します。公式ドキュメントではIntelliJのExternal Toolsを利用していましたが、わざわざ使う必要もないので次のコマンドでDaprを起動します。

dapr run --app-id hello-app --app-port 8080 --dapr-http-port 18080

これでDaprが単独で起動します。まだアプリケーションは起動していません。

IntelliJ側でアプリケーションを起動

続いて、IntelliJhello モジュールの HelloApplicationデバッグ起動します。HelloApplication を右クリックし、Debug 'HelloApplition' を選択するなどで起動できます。

f:id:cero-t:20211221010340p:plain
IntelliJデバッグ起動

アプリケーションを起動したら HelloControllerreturn Map.of("message", "Hello, world!");ブレークポイントを張っておいてください。

アプリケーションへのアクセス

続いて、次のコマンドでアプリケーションにアクセスします。

curl localhost:18080/v1.0/invoke/hello-app/method/hello

このコマンドを実行すると、IntelliJ側で指定したブレークポイントで止まっているはずです。

つまり、Daprを単体起動し、IntelliJからDaprで指定した app-port のアプリケーションを起動すればDapr経由でアプリケーションにアクセスできる状態になるのです。この方法で起動すれば、IntelliJ側でデバッグができるということです。仕組みが分かってしまえば簡単な話ですね!

いつどのツールを使って開発するのか

これまで半年くらい業務でDaprを使って開発し、実運用も始めているのですが、正味の話こんな風にデバッグをできることは、いま初めて知りました。

なぜこれまで知らなかったというと、ローカル環境で開発する時には(担当範囲にもよるのですが)Daprを使うことはあまりないためです。その辺りについて説明したいと思います。

Daprアプリケーションの開発の実際

例として、運用するシステムが Amazon EKS + Dapr + RabbitMQ + PostgreSQL + Spring Boot という構成だったとします。

その際、開発フェーズごとに使うツールは次のようになります。

  • 開発時
  • Dapr経由での動作検証時
    • Java + Docker + Dapr
  • k8sでの動作検証時
    • Java + Docker + Dapr + Minikube + kubectl
  • Amazon EKSでの動作検証時
    • Java + Docker + kubectl

この辺りを順番に説明していきます。

開発時 (Java + Docker)

アプリケーションを開発する際には、Spring Bootで開発するためにJavaJDK)を利用し、PostgreSQLを利用するためにDockerを利用します。ここでDaprは使いません。

これまでのAdvent Calendarでも少し説明していましたが、ローカル環境で開発する際にはアプリケーション同士を直接呼び出しています。

Invoke APIを使わず直接呼び出し

たとえばInvoke APIを使ったアプリケーションのコードは次のようになります。

@Value("${baseUrl}")
private String baseUrl;

@GetMapping("/invokeHello")
public Map<String, ?> invokeHello() {
    Map<?, ?> result = restTemplate.getForObject(baseUrl + "/hello", Map.class);
    return Map.of("baseUrl", baseUrl, "remoteMessage", result);
}

ここで baseUrl としてローカル環境で開発する際には http://localhost:8080 を指定します。そうすれば、ポート8080番で起動している別のアプリケーションにアクセスできますし、あくまでもSpring Bootだけの世界に閉じて開発ができます。

そしてDaprを利用する際には http://localhost:${DAPR_HTTP_PORT}/v1.0/invoke/hello-app/method というInvoke APIのURLに差し替えるのです。

このようにすればローカルではDaprなし、検証や運用はDaprあり、と切り替えることができます。

Pub/sub APIを使わず直接呼び出し

HTTPで呼び出す同期処理はそれで良いとして、非同期処理はどうなるでしょうか。

たとえばPub/sub APIを使ったアプリケーションのコードは次のようになります。

@Value("${pubsubUrl}")
private String pubsubUrl;

@PostMapping("/publish")
public void publish(@RequestBody MyMessage message) {
    restTemplate.postForObject(pubsubUrl, message, Void.class);
}

そしてメッセージを受け取るsubscribe側は次のように実装します。

@PostMapping("/subscribe")
public void subscribe(@RequestBody CloudEvent<MyMessage> cloudEventMessage) {
    doSubscribe(cloudEventMessage.data);
}

@PostMapping("/doSubscribe")
public void doSubscribe(@RequestBody MyMessage message) {
    System.out.println(message);
}

上がDaprから呼ばれる際にCloudEventのエンベロープがついたクラスを引数にしたメソッドです、下がその中のメッセージを使って実処理を行うメソッドです。

このような構成にして、ローカルの開発時には pubsubUrllocalhost:8084/doSubscribe などを指定して直接同期呼び出しをするようにします。そうすればDaprのことを気にせず、Spring Bootの世界に閉じてビジネスロジックに注力して開発ができます。しかも呼び出しが同期となるため、JUnitなどの自動テストの際に非同期処理が終わることを1秒待つ、みたいなことをしなくても、処理が終わればすぐにassertに進むことができます。

そしてDapr利用時には pubsubUrlhttp://localhost:${DAPR_HTTP_PORT}/v1.0/publish/pubsub/my-message などのPub/sub APIのURLにするのです。このようにすればローカルではDaprなし、検証や運用はDaprあり、と切り替えることができます。

そんな切り替えをして、Daprを使ったときに問題が起きることはないか? と疑問に思うかも知れませんが、もちろん何か問題が起きることはあるかも知れません。ただ基本的にはごく少しのコード修正だけで済むものです。

このような方針にするためにも、Dapr Java SDKは使わず、HTTP通信をするためのクライアントだけを利用しています。

Dapr経由での動作検証時 (Java + Docker + Dapr)

Daprのpub/sub機能が正しく動くかどうか試したい場合や、Daprの分散トレーシングで渡される traceparent ヘッダを利用した処理を行いたい場合には、上の構成に加えてDaprをインストールして動作検証を行います。

やや極端ですが、この時点ではDaprのAPIさえ利用すれば利用するミドルウェアは何でも構いません。たとえば、実際にRabbitMQを用いたpub/sub機能を使って開発をしていたメンバにヒアリングした所、開発時にはDaprが勝手にインストールするRedisを使っていたと話していました。開発時にRedisを使い、検証時からRabbitMQを使っていても、設定ファイル以外は変える必要がなく、何の問題もなくアプリケーションは動作していました。

もちろんRabbitMQ固有の機能、たとえばDead Letter Exchange (DLX)などを用いた処理の開発や検証を行うのであればDocker上にRabbitMQをデプロイして検証する必要がありますが、あくまでもpub/subの主機能の検証を行いたいだけであればRedisを使っても構わないのです。

別にそのような方針を推奨するわけでも何でもないですが、Daprがミドルウェアを抽象化するメリットがこういう所で活きてきます。

k8sでの動作検証時 (Java + Docker + Dapr + Minikube + kubectl)

アプリケーションの開発が終わり、Amazon EKSなどにデプロイする前には、k8sでの検証が必要となります。その場合にはMinikubeが必要となります。インフラ構築を担当する2〜3名ほどだけがローカルにMinikubeをインストールして検証をしていました。もちろんkubectlなどの関連ツールも使います。

逆に言えば、インフラを触らないアプリケーション開発者はMinikubeのインストールすらしていません。k8sに難しいイメージを持っていて、「k8sを使わなければならない」というだけで抵抗があるエンジニアにとっては、k8sを使わずに開発できるという方針にしたほうがハッピーでしょう。

Amazon EKSでの動作検証時 (Java + Docker + kubectl)

Minikube上での検証が終われば、また必要なツールは減ります。イメージを作成してEKSにデプロイするだけですから、イメージのビルドのためのJavaとDocker、またデプロイするためのkubectlが必要となるだけです。DaprやMinikubeは必要ありません。

ただしEKS上で問題が起きた際にログなどを確認できるよう、開発者全員がkubectlを利用できるようにすべきです。ログやメトリクスをCloudWatchやDataDogに集約していれば、開発者がkubectlを利用する機会は減るでしょう。

別にDevとOpsを分離することが正しいのだと主張するつもりはないですが、僕は「それぞれのフェーズにおいて注力すべき部分に注力する」というプロセスを大事にしており、それを実現できるという点で、Daprをとても気に入っています。

まとめ

  • Visual Studio CodeのDapr extensionを使えば、起動中のDaprアプリケーション一覧を表示したりinvoke/publishなどを容易に行えます
  • Daprを単体起動すれば、自分の好きなIDEを使ってアプリケーションをデバッグ起動し、Daprを使った処理のデバッグができます
  • 開発のフェーズごとに、注力すべきことに注力するという構成を取りやすいです
  • 開発時にはDaprもMinikubeもなしでビジネスロジックの開発に注力することができます
  • 検証時以降はそれぞれに必要なツールを少しずつ増やして環境を構築するという方針が良いでしょう

こんな風にしてDaprのアプリケーション開発を進めていました。もちろんこれから変わる所もあるでしょうし、今後も方法論を磨いていきたいと思います。

それでは、また明日!

Dapr Advent Calendar 20日目 - DaprをAmazon EC2で使う

こんにちは Dapr Advent Calendar 20日目です。錦鯉、M-1優勝おめでとうございます! うるさい漫才は好きではないんですが、錦鯉は、なんでしょうか、キャラのせいなんですかね、わりと好きなんです。優勝したペアが涙を流すことはあっても、審査員までもらい泣きしているのはなかなか見ないなと思いましたね!

DNSにConsulを使ってEC2でDaprを運用する

さて、以前 Dapr Advent Calendar 12日目 - Daprをk8s以外の分散環境で使う で、Amazon EC2のようにIPマルチキャストが使えない環境では、Daprを動かすためにConsulが必要ということを書きました。それについて、@kencharosさんからコメントを貰いました。

Consulはdevモードであれば立てるのがそんなに難しくないようなので、今回はこれで試してみましょう。

EKSで動かした翌日にEC2で動かした話を書くなんて、なんか技術スタックが巻き戻ってる気もするのですが、k8sを使うことに抵抗がある人も多いでしょうから、参考にしてもらえると嬉しいです。

f:id:cero-t:20211220101711p:plain
今回作るアプリケーション

EC2にインスタンスを作成

まずはAmazon EC2インスタンスを3つ作ります。1台はConsulサーバ、残り2台はアプリケーションをデプロイするサーバとして利用します。

EC2の使い方や手順などについては割愛しますが、Amazon Linux 2のt2.microインスタンスを3つ作りました。

EC2にConsul環境を構築

まずはEC2インスタンスの1台にConsulの環境を構築しましょう。

次のコマンドを実行してConsulのCLIツールをインストールします。

curl -LO https://releases.hashicorp.com/consul/1.11.1/consul_1.11.1_linux_amd64.zip
unzip consul_1.11.1_linux_amd64.zip
sudo mv consul /usr/local/bin/

続いて、Consulをサーバとして起動します。

consul agent -dev -client=0.0.0.0

開発用に1インスタンスで起動するだけなので -dev オプションをつけ、別のインスタンスからアクセスできるよう -client=0.0.0.0 オプションをつけました。

これでConsulサーバの構築は完了です。とても簡単でしたね。

アプリケーションのDapr実行環境を構築

続いて、残り2台のインスタンスにアプリケーションを実行するための環境を構築しましょう。

まずはJava 11 (corretto)をインストールします。

sudo yum install -y java-11-amazon-corretto-headless

続いて、Dapr CLIをインストールして初期化します。

wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash
dapr init --slim

Daprは --slim オプションを付けて、Dockerや設定ファイルを作らないスタンドアロンモードで初期化しました。

これでアプリケーションの実行環境が構築できました。

アプリケーションのデプロイ

ソースコード

続いてアプリケーションの準備です。サンプルアプリケーションのソースコードGitHubに置いてあります。

https://github.com/cero-t/dapr-advent-2021

このうち「hello」と「invoke」を使います。何度も使っているアプリケーションなので、ソースコードの説明は省略します。

名前解決にConsulを使う設定ファイルを作成

Daprが連携するために使う名前解決にConsulを利用できるよう設定ファイルを作成します。ComponentではなくConfigurationとして、次のように設定します。

consul-config.yaml

apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
  name: consul-config
spec:
  nameResolution:
    component: "consul"
    configuration:
      selfRegister: true
      client:
        address: "172.*.*.*:8500"

spec.nameResolution に名前解決の設定をします。

今回はConsulを利用するため componentconsul を指定しました。

そして、起動時にアプリケーションが自動登録されるよう configuration.selfRegistertrue とします。

また configuration.client.address にConsulサーバのプライベートIPアドレスを指定します。後ほど、アプリケーションを実行するインスタンス上でこの値を書き換えます。

この設定ファイルを使うことで、Daprが名前解決としてConsulを利用できるようになります。

ソースコードのダウンロードと書き換え

それでは、アプリケーションを実行する2台のインスタンスでそれぞれアプリケーションのソースコードをダウンロードしして解凍します。

curl -LO https://github.com/cero-t/dapr-advent-2021/archive/refs/heads/main.zip
unzip main.zip

続いて、Consulサーバのアドレスを書き換えましょう。

vi dapr-advent-2021-main/consul-config.yaml

一番最後の行にある 172.*.*.*:8500 の部分に、Consulサーバとして起動したインスタンスのプライベートIPを指定してください。この作業も2台のインスタンスでそれぞれ行います。

アプリケーションの起動

それではアプリケーションを起動しましょう。

1台目のインスタンスでhelloアプリケーションを起動します。

cd dapr-advent-2021-main/hello
dapr run --app-id hello-app --app-port 8080 --dapr-http-port 18080 --config ../consul-config.yaml ../mvnw spring-boot:run

--config ../consul-config.yaml オプションをつけて、設定ファイルが有効化されるようにしました。

2台目のインスタンスも同様にしてinvokeアプリケーションを起動します。

cd dapr-advent-2021-main/invoke
dapr run --app-id invoke-app --app-port 8081 --dapr-http-port 18081 --config ../consul-config.yaml -- ../mvnw spring-boot:run -Dspring-boot.run.profiles=dapr

オプションは上と同様です。また -Dspring-boot.run.profiles=dapr をつけて application-dapr.yaml の設定が有効になるようにしています。

これでアプリケーションの起動が完了しました。

アプリケーションにアクセス

そして、別のコンソールから任意のインスタンス上で次のコマンドでアクセスします。

curl (invokeアプリケーションを起動したインスタンスのプライベートIPアドレス):8081/invokeHello

次のような結果が表示されます。

{"baseUrl":"http://localhost:18081/v1.0/invoke/hello-app/method","remoteMessage":{"message":"Hello, world!"}}

問題なくinvokeアプリケーションからhelloアプリケーションにアクセスできましたね!

AWSではマルチキャストDNSが使えないため、名前解決の設定なしでは通信することができませんでした、Consulを利用することで無事にDapr上で動くアプリケーション同士が連携して通信することができました。

f:id:cero-t:20211220002142p:plain
Consulを使った名前解決

EC2かEKSか

Daprのことを話題にすると、HTTP通信だけですべてが解決できるとか、別のサーバなどなしで開発ができるところをメリットと思ってもらえるのですが、「ただ、k8sはちょっと・・・」というところで尻込みされることが何度となくありました。minikubeやEKSを使ってみて、そこまで難しいとは感じませんでしたが、とは言えやはりこれまで使ってきた技術スタックとは異なるものですし、Daprという新しい技術に加えてk8sまで使うとなると、抵抗があるというのも理解できます。

今回はConsulを使い、EC2アプリケーション上で動くDaprアプリケーション同士が通信できることを確認しました。この方法を使えばk8sを使わなくともDaprアプリケーションを運用することができます。もちろんConsulのサーバを立てて運用する必要がありますが、その辺りはSpring CloudのEurekaなども大きな差はないでしょう。欲を言えば、AWSがマネージドConsulサーバサービスを提供してくれると嬉しいところです。

いずれにせよk8sだけでなく、EC2のような仮想サーバに直接デプロイするとか、ECSのようなコンテナサービスにデプロイするとかが気軽にできるようになれば、またDaprを利用する人も増えるんじゃないかなと思います。名前解決の簡単さはDaprの強みとなるので、今後のアップデートでより洗練されることを期待しています。

まとめ

  • Consulの開発モードを使ってEC2上にConsulサーバを立てました
  • Daprの設定でConsulサーバを名前解決に使うよう設定しました
  • Consulを使えば問題なくEC2でも名前解決ができ、通信することができました

これを12日目のエントリーの時点で書けていれば、もっと良かったのですけどね🙄

ちょっと行き当たりばったりになってしまうところも、毎日ブログを書いてる醍醐味ひとつということで。

それでは、また!