この記事は、「Elixir Advent Calendar 2020」の24日目です
昨日は、@hidetaka-f-matsumoto さんの「Elixir の Elixir による Elixir のためのログ解析」でした
Elixir Digitalization Implementors/fukuoka.ex/kokura.exのpiacereです
ご覧いただいて、ありがとうございます
2020年は、何かとElixirのお仕事で「エッジサーバ」を構築する機会がいただけましたので、そこで使った技術をシェアします
… と言っても、いつものElixir標準機能を普通に使うだけなので、Elixirをまぁまぁご存知の方であれば、特に突飛なものは1つもありません
内容が、面白かったり、役に立ったら、「LGTM」よろしくお願いします
Advent Calendar、fukuoka.ex1位、Elixir2位達成ヽ(=´▽`=)ノ
fukuoka.ex Advent Calendar、Webテクノロジーカテゴリで堂々1位 … 各コラムぜひお読みください
https://qiita.com/advent-calendar/2020/fukuokaex
そして、プログラミング言語カテゴリは、1位がRust、2位がElixir、3位がGoとモダン言語揃い踏みでのトップ3、熱いネー
https://qiita.com/advent-calendar/2020/elixir
Elixirエッジサーバ構築の宣言は1年前に済ませていた
エッジサーバの構築活動自体は、昨年のfukuoka.ex Advent Calendarで書いた下記コラムや、その手前にElixirConf JP 2019で披露した「Elixir、そしてあなたと共に歩む道(Way with Elixir and YOU)」で宣言した通りの実現でもあり、「語り広めたことは実現する」… という実証を自ら実施した形となります
Nerves/Elixir/Phoenixによるエッジコンピューティングの可能性+α
https://qiita.com/piacerex/items/ba30d9dfa0059902dc10
ElixirConf JP 2019
http://fukuokaex.fun/
また、こうした構築には、Elixirコミュニティで集まった仲間達を巻き込んで、仕事をシェアする形でアレンジもしました
このような思考パターン、つまり、計画を広めることで機会や協力者を募り、そして自分だけじゃ無いチカラを結集して実現していく … この思考パターンこそが、現代のビジネスとテクノロジーには重要です
本コラムでは、主にElixir標準機能を使い、Elixir/Phoenixによるエッジサーバを構築するノウハウを共有します
なお、エッジサーバにどんな恩恵があるのかや、その手前にある「クラウドの限界」については、世に色々なコラムや書籍もあるので、ここでは解説を割愛します
Elixirでエッジサーバを構築するメリット
まず、Elixirでエッジサーバを構築するメリットについて整理します
Elixir/Phoenixと各種エッジ技術(Flutter、Electron、Nervesなど)のセットを用いれば、クラウド上だけに限定されることなく、エッジサーバや、手元のスマホ/IoTセンサー、そしてクラウドサーバの全てで、Elixirという共通言語を使い、容易に分散コンピューティング環境を作っていくことができます(下図は @takasehideki さん図解)
また最近では、5Gのような高速/大容量/低遅延通信のハードウェア面がじょじょに整備されつつあり、クラウドを介さず、エッジとエッジサーバの間で完結する処理に注目が集まっていますが、Elixirの大量アクセス時でも維持される低レイテンシや並行・並列処理の記述やマルチコア性能を引き出すのが容易なことも、エッジサーバを作りやすい特徴です
更に、VR/ARのような、別種のエッジとの相互接続も現れ、以下の流れとループが実現されていきますが、そういうシーンにおいて、日常使いの性能面を安価なコストで支えられることも、Elixirを使うメリットと言えるでしょう
1.現実世界のデータ化
2.データ化された現実世界をデジタルワールド内で特徴分析したり、次のアクションを判断する
3.デジタルワールドで導出した次のアクションを、現実世界に戻し、現実世界に影響を与える
4.(上記3でアップデートされた現実世界にて、上記1から繰り返す)
ちなみに、上記ループのような世界観は、「Society 5.0」や「インダストリー 4.0」と呼ばれています
アフターコロナ/withコロナ、かつ更なる資金不足や人手不足がますます加速する「不況の時代」へと突入してく私達にとっては、欠かせない領域になると私は捉えています
Society 5.0 - 科学技術政策 - 内閣府
https://www8.cao.go.jp/cstp/society5_0/
インダストリー 4.0とは
https://seizo-net.com/industry40/
このように仕事や生活といった現実世界が、今と全く異なるものへと変貌を遂げていく予測を、別コラムでも書いていますので、上記内容だけではイメージが難しい方は、下記をどうぞご覧ください
「NeosVR |> AR投影アプリ」が拓く生活と仕事:人はより「現実」へと出ていき、世界が変わったことを理解する
https://qiita.com/piacerex/items/fcb29251e37b1ac3e063
クラウドの外でエッジサーバを作る際の注意点
さて、エッジサーバを作るにあたって、気を付けなければならないのは、
「クラウドサービスが一切無い状況下で構築できるか?」
という点に尽きます
現代のコンピューティングやWebサービスは、過剰とも言えるレベルでクラウドに依存しているため、いざ何かサービスを作ろうとしたとき、クラウドが無いと何も作れないという方も増えているかと思います … これは、クラウドが無かった2000年代では普通の事柄でしたが、2008年以降にクラウドが流行して以降、かなり多くの開発者が面食らい、違和感を感じる事柄だと思います
たとえば、下記の経験をしていないエンジニアは、若年層を中心に、確実に増えています
- DBサーバを自前で構築したことが無い
- インターネットに繋いでいない環境で開発したことが無い
- PCのキッティングもやったことが無い
これらは、決して珍しいことでは無くなってきているので、古くから開発されている方で、若い部下の指導にあたっている方なんかは、ドラスティックに頭を切り替える必要があります(そうしたことを体感するために、日本では無く、世界のトレンドに対し、遅くとも2~3年遅れ以内で最新技術をトレースできていることが大事になります)
なお、キャリアが提供するモバイルエッジサーバ設備(MECと言います)もこの領域は埋め合わせてくれません … こうしたリアルが、実はあまり共有されていないのですが、確実に、エッジサーバによるソリューションの高いハードルだと思います
エッジサーバ構築で活きるElixirの恩恵
Elixirは、30年以上の歴史の中で、クラウドコンピューティングが備えるのと同じような機能を、クラウドサービスやインターネットが無かった時代に蓄積してきているErlangをベースとしているため、クラウドと同等の分散コンピューティングを叶えることができます
他言語でも、これらの一部を実現しているものはありますが、全てを備えているのは、Elixir位で、まさにエッジコンピューティングのためにElixirが存在するんじゃ無いかと私は思っています
①マルチクラスタ構築 ②マルチマスタDB構築 ③APIサーバ構築 ④WebSocketサーバ構築 ⑤API/WebSocket以外のサーバ構築 ⑥マルチワーカプロセス ⑦スーパーバイザ ⑧アンブレラ ⑨リモートデバッグ以降では、これら機能を紹介していきます
①マルチクラスタ構築
サービス拡大やグロースによるアクセス数増加に対応するためのマルチクラスタ化は、現代のITサービス提供においては、かなり重要であり、AWSやGCP等のComputeEngineや、GKEに代表されるKubernetesサービスは、まさにクラウドによるスケーラビリティの確保を提供します
エッジサーバでも、サービス拡大やグロースによるアクセス数増加は、当然の如く発生するので、この領域のサポートが重要です
Elixirは、下記のように複数台のサーバ接続をビルトイン提供しているため、分散環境をカンタンに構築することができます
両サーバ上に、あらかじめPhoenix PJを作っておき、下記コマンドでiexを起動すると、Phoenix起動と同時に、マルチクラスタ接続をするためのiexを起動できます
まず1台目のサーバを起動します
iex --name node1@192.168.10.103 --cookie edge-servers -S mix phx.server
iex(node1@192.168.10.103)> Node.self
:"node1@192.168.10.103"
iex(node1@192.168.10.103)> Node.list
[]
2台目のサーバを起動し、1台目のサーバに接続した後、接続済み情報を確認します
iex --name node2@192.168.10.101 --cookie edge-servers -S mix phx.server
iex(node2@192.168.10.101)> Node.list
[]
iex(node2@192.168.10.101)> Node.connect :"node1@192.168.10.103"
true
iex(node2@192.168.10.101)> Node.list
[:"node1@192.168.10.103"]
1台目のサーバから、2台目のサーバが認識されているかも確認してみましょう
iex(node1@192.168.10.103)> Node.list
[:"node2@192.168.10.101"]
これでマルチクラスタ構築が完了です
あとは、台数分だけ接続すれば、クラウドで言うところのGKEによるサービスメッシュと同等のマルチクラスタ環境が作れます
その配下で、後述する分散DBや、マルチワーカプロセスやリモートデバッグが、一通り使えるようになります
なお、上記したiex上でのNodeモジュールによる接続などは、Elixirコードとして実行可能なため、Github Actions等のCIに含めておけば、マルチクラスタ構築の自動化もできます
注意点
この機能は、手動でのクラスタ構築のため、負荷上昇に伴うオートスケールによるマルチクラスタスケールアップができません
そのため、クラウドのComputeEngineやKubernetesサービスと同じノリで使えない領域があることは、ご注意ください
②マルチマスタDB構築
Google CloudSQLやAmazon Aurora、もしくはRedisで構築するようなマルチマスタDBを、Elixirでは、「Mnesia」という機能でビルドイン提供しています
上記①でクラスタ構築した後、下記のような感じでMnesiaは構築できます(なお、下記ではディスク永続化オプションを付けていますが、より高速なインメモリモードもあります)
iex(node2@192.168.10.101)> :mnesia.create_schema( [ node() ] )
:ok
iex(node2@192.168.10.101)> :mnesia.start
:ok
iex(node2@192.168.10.101)> :mnesia.create_table( :members, [ attributes: [ :id, :name, :age ], disc_copies: [ node() ] ] )
{:atomic, :ok}
iex(node2@192.168.10.101)> :mnesia.transaction( fn -> :mnesia.write( { :members, 1, "zacky", 48 } ) end )
{:atomic, :ok}
iex(node2@192.168.10.101)> :mnesia.transaction( fn -> :mnesia.write( { :members, 2, "piacere", 46 } ) end )
{:atomic, :ok}
iex(node2@192.168.10.101)> :mnesia.transaction( fn -> :mnesia.read( :members, 1 ) end )
{:atomic, [{:members, 1, "zacky", 48}]}
iex(node2@192.168.10.101)> :mnesia.transaction( fn -> :mnesia.read( :members, 2 ) end )
{:atomic, [{:members, 2, "piacere", 46}]}
マルチマスタDB化するには、2つ目以降のサーバから、下記をすることで、Mnesiaを複数サーバで共有できます
iex(node2@192.168.10.103)> :mnesia.start
:ok
iex(node2@192.168.10.103)> :mnesia.change_config( :extra_db_nodes, [ :"node2@192.168.10.101" ] )
{:ok, [:"node2@192.168.10.101"]}
以下のように、2つ目のサーバ上から、1つ目のサーバで追加したデータを見ることができます
iex(node2@192.168.10.103)> :mnesia.transaction( fn -> :mnesia.read( :members, 1 ) end )
{:atomic, [{:members, 1, "zacky", 48}]}
iex(node2@192.168.10.101)> :mnesia.transaction( fn -> :mnesia.read( :members, 2 ) end )
{:atomic, [{:members, 2, "piacere", 46}]}
このようにマルチマスタDBが、Elixirではビルトイン状態からカンタンに構築できます
なお、Mnesiaは、耐障害性も持っており、サーバが数台クラッシュしても、復旧できる冗長系でもあり、下記コラムで詳細はご覧ください
Mnesiaで分散ノードに入門してみた
https://www.slideshare.net/TakahiroKobaru/mnesia-102813339
Mnesiaを普通のDBのようにSQL文で利用する
Mnesiaを、SQL文で使えるようにするライブラリのようなものを、下記で作っていますので、ご参考ください
Phoenix軽量APIを使ったREST APIのコード(データはMnesia DBに保持)
https://qiita.com/piacerex/items/a41176fb0c7c68523a4b#mnesia-db%E3%82%A2%E3%82%AF%E3%82%BB%E3%83%83%E3%82%B5
③APIサーバ構築
Elixirは、DBアクセスするREST APIを、下記のようなコマンド1発で生成できます
mix phx.gen.json ThreeD Vertice vertices title:string sort:integer body:text
その後、下記でマイグレートとPhoenix起動をすれば完成です
mix ecto.migrate
mix phx.server
DB無しのAPI開発をカンタンにする「Shotrize」
DB無しのAPI開発をカンタンにするために、「Shotrize」という、templatesフォルダ配下にElixirコードやJSONテンプレートを置けばREST APIが構築できるPhoenixプラグインも開発しました(ルーティングやMVCを書く必要が無くなります)
下記コラムシリーズで、初期バージョンは公開しています
Phoenixお気軽API開発①:PHP的ハックを応用してNode.js Express/Go的な軽量APIをPhoenixで実現してみた
https://qiita.com/piacerex/items/144089aa6f2052f4d1cb
なお最新版は、OSSとして公開予定ですが、もしご興味あれば、私のTwitter DMでご連絡ください
piacere の Twitter DM(リンク先の赤枠部分をクリックしてください)
https://twitter.com/piacere_ex
④WebSocketサーバ構築
Elixirでは、WebSocketサーバの構築も、非常にカンタンです(しかも高性能です)
Phoenix PJ配下にある、下記コードのコメントアウトを解除するだけで、WebSocketサーバの構築は完了です
defmodule BasicWeb.UserSocket do
use Phoenix.Socket
## Channels
channel "room:*", BasicWeb.RoomChannel # <- remove comment-out here
…
WebSocket受信時の処理は、下記のようなコードを作成し、handle_in()でWebSocket受信時のアクションを書いてあげればOKです(下記は受信したメッセージを全てのWebSocket接続先にブロードキャストする例)
defmodule BasicWeb.RoomChannel do
use Phoenix.Channel
def join( "room:lobby", _message, socket ) do
{ :ok, socket }
end
def handle_in( "new_msg", %{ "body" => body }, socket ) do
broadcast!( socket, "new_msg", %{ body: body } )
{ :noreply, socket }
end
end
LiveViewにおけるWebSocket構築
なお参考情報までに、Channelを利用してリアルタイムフロントを提供する「LiveView」のChannel定義は、以下で行われています
https://github.com/phoenixframework/phoenix_live_view/blob/master/lib/phoenix_live_view/socket.ex
1: defmodule Phoenix.LiveView.Socket.AssignsNotInSocket do
2: @moduledoc false
3: Struct for socket.assigns while rendering.
…
67: channel "lvu:*", Phoenix.LiveView.UploadChannel
68: channel "lv:*", Phoenix.LiveView.Channel
…
⑤API/WebSocket以外のサーバ構築
「GenServer」というビルトイン機能で、サーバプロセスをカンタンに作ることができるので、APIやWebSocketといったフロントのリクエスト受付以外のサーバを気軽に作れます
GenServerで作られたサーバは、GenServer.call(同期呼び出し)や、GenServer.cast(非同期呼び出し)で叩くことができます
更に素晴らしいことに、マルチクラスタをまたいでもGenServerはcall/castできるので、サーバの負荷を見ながら、別サーバにオフロードする … といった操作も気軽にできます
GenServerのコード例は、@kikuyuta さんの下記コラムが分かりやすいので、ご参考ください
はじめてなElixir(15) もっとラクに並行プログラミングする(あるいは はじめてな GenServer)
https://qiita.com/kikuyuta/items/2c41082305d5ec6809da
⑥マルチワーカプロセス
上記したAPIやWebSocket、GenServerで受け付けたリクエストを、「Task」でマルチワーカプロセス分散できます
同期/非同期の両方に対応しています
コチラも素晴らしいことに、マルチクラスタをまたいでワーカプロセスが生成できるので、サーバの負荷の低いところを優先的にワーカ起動するなんてこともカンタンにできてしまいます
Taskのコード例は、@pojiro さんのコラムが分かりやすいので、下記をご覧ください
Elixirで並行コマンド実行サーバーを作ったら感動した話
https://qiita.com/pojiro/items/050da725dde4d05fed9e
⑦スーパーバイザ
上記のようなマルチサーバやマルチワーカを作った場合、そのサーバプロエスやワーカプロセスが、想定外のリクエスト等でダウンした場合、復帰処理やリトライが面倒です
しかし、Elixirなら、「スーパーバイザ」という機能を使って、コレも楽々と処理できます
スーパーバイザのコード例も、@pojiro さんのコラムで読めます
Elixirで並行コマンド実行サーバーを作ったら感動した話
https://qiita.com/pojiro/items/050da725dde4d05fed9e
⑧アンブレラ
最初はシングルクラスタで性能充分でも、途中でサービスがグロースした際に性能不足に陥った際、分散化や、マイクロサービス化したいといったことを見越すと、アンブレラPJが便利です
まず、子PJの入れ物となる、親PJを --umbrella
オプション付きで作成します
mix new holding --umbrella
次に、子PJをappsフォルダ配下にPJ作成するだけです
cd holding/apps
mix new child_app1
mix new child_app2
mix new child_app3
この状態で、親PJでiex起動すると、子PJの全てのモジュールが親PJから利用可能になります(子PJがPhoenixなら、全てのPhoenixが起動するため、各子PJのポート番号は4000番以外にしておくと良いでしょう)
cd ../..
iex -S mix phx.server
アンブレラで子PJを作ると、子PJのフォルダを、別のクラスタや親PJ配下にフォルダ移動/コピーするだけでサービス複製/移設ができ、移動先で起動コントロールできるので、DockerやKubernatesと同じようなマルチコンテナ運用を、フォルダ操作だけで気軽にできるという、大きなメリットがあります
⑨リモートデバッグ
iex経由で、マルチクラスタで接続済みの他サーバ上での操作やデバッグが可能なため、リモートデバッグも簡単にできます
同様に、ssh接続したNervesも、エッジデバイス上でiexが使えて、母艦からリモートデバッグ可能です
Elixirは、何かとマルチクラスタもしくはエッジのデバッグ自由度が高い言語です
最後に
Elixirという言語が、ただのプログラミング言語では無く、エッジサーバ構築に特化した機能が充実したクラウドのような存在だということが、ジワジワと伝わってきたでしょうか?
なお、今回コラムでは、「エッジサーバ」という部分を強調して解説しましたが、これらの機能は全て、クラウド、もしくはエッジデバイス上でも利用することが可能なため、Elixirは、分散コンピューティングをする上で、エッジデバイス/エッジサーバ/クラウドの全てをハンドリングできるプログラミング環境だということも共有できていたら幸いです
p.s.このコラムが、面白かったり、役に立ったら…
明日の記事は、@zacky1972 さんのコラムです