Kubernetes to Metaparticle. 分散プログラミングへの新しいアプローチ

こんにちは、プレイドの @nashibao です。今回は少し前に話題になった Metaparticle というプロジェクトについて紹介します。

Metaparticlea standard library for cloud native applications on Kubernetes.と唄っていますが、要はjava/javascript/goなどのapplication code上にdocker/k8sのInfrastructure/Orchestrationのうち、Applicationとして関心がある部分は組み込んでしまおう、というプロジェクトです。

多機能のサービスを開発していると、様々なところで有限のベストプラクティス(ただし単一では無い)に沿ったDistributed Programmingをして堅牢性や冗長性を高めたり負荷分散をしたくなります。

ただ、これらを実現したいと考えて設計/実装をするのは多くはApplicationエンジニアですが、Infrastructure/Orchestrationの様々なツールがその間に横たわっていて機動性が失われてしまいます。

CloudのThread/ProcessとしてのContainer

Threadは扱いやすいです。パフォーマンス等を考えて様々なパターンを駆使して劇的にApplicationのパフォーマンスを上げることができます。

Processは?そこまで隔たりはありません。プレイドで好んで使っているNode.jsでいえば Cluster moduleを使うだけです。

const cluster = require('cluster');
if (cluster.isMaster) {
  // master
  cluster.fork();
} else {
  // worker
}

ThreadもProcessもどちらもworkerへの分散には色々なパターンがあって、それらを使うと並列性が高いプログラムを容易にかけます。iOSのNSOperationQueueなどその代表ですが、各言語に様々なパターンがあります。そしてこれはApplicationエンジニアの手元にあって自由に設計できます。

Containerは?yamlの世界、kubectlの世界です。Applicationエンジニアの手元にあるように見えて遠くに行きがちです。PortとVolumeがコントロールされていて、古き良きPubsub/Queueやmessaging用の各ツール(zeromqは好きだ)を使って自由に協調はできますが、言語が異なる他のPlatformというものが邪魔をします。

Language idiomatic cloud

分散性をある程度ラップしつつ、コントローラブルにするアプローチは昔からあって、例えば MapReduce があります。Metaparticleと目的は似ているがPipeline状のData処理に特化している所が異なります。PySparkを使うとlambda関数を単にローカルで動かしているのと同じように見えます。Google DataflowApatch Beamもまま似てる。

Microservice流行りで、AWS LambdaCloud Functions のような便利なツールが出てきてるので pywrenのように、それらをwrapしちゃえば良くね?みたいなアプローチもあります。

  def my_function(b):
    x = np.random.normal(0, b, 1024)
    A = np.random.normal(0, b, (1024, 1024))
    return np.dot(A, x)

  pwex = pywren.default_executor()
  res = pwex.map(my_function, np.linspace(0.1, 100, 1000))

サイトに書いてあるコードそのままだけれど、中が同一スレッドだろうが、別プロセスだろうがAWS Lambdaだろうが同じようなマナーで書けます。

Metaparticle

Sparkpywrenはかなり抽象化されていて、やれることも制限されますが(その代わりパフォーマンスに焦点がある)、Metaparticleはもう少し自由度に焦点があります。つまりdocker/k8sのconfigへcompileするためのメタ言語のようなものです。

const mp = require('@metaparticle/package');

mp.containerize(
	{
        	ports: [8080],
        	replicas: 4,
		    runner: 'metaparticle',
		    repository: 'docker.io/docker-user-goes-here',
		    publish: true,
		    public: true
	},
	() => {
		// closure for each workers
	}
);

これだけです。これを素で使っても良いけれど、先ほどいった有限のパターンについては standard libraryなので当然提供する。sharingなら

const http = require('http');
const os = require('os');
const mp = require('@metaparticle/package');

const port = 8080;

const server = http.createServer((request, response) => {
	console.log(request.url);
	response.end(`Hello World: hostname: ${os.hostname()}\n`);
});

mp.containerize(
	{
		ports: [8080],
		shardSpec: {
			shards: 3,
			"urlPattern": "\\/users\\/(.*)[^\\/]"
		},
                repository: 'docker.io/your-docker-user-goes-here',
                publish: true,
                public: true,
		runner: 'metaparticle',
	},
	() => {
		server.listen(port, (err) => {
			if (err) {
				return console.log('server startup error: ', err);
			}
			console.log(`server up on ${port}`);
		});
	}
);

だし、electionなら

var mp = require('@metaparticle/sync');

var election = new mp.Election(
    // Name of the election shard
    'test',
    // Event handler, called when a program becomes the leader.
    () => {
        console.log('I am the leader');
    },
    // Event handler, called when a program that was leader is no longer leader.
    () => {
        console.log('I lost the leader');
    });

election.run();

という感じです。上に書いたように、ここは有限のパターンがそれなりにあるので、それらが今後作られていくのだろうと思うが今の所ほぼ上の二つくらいしか書かれてないw

感想

thread/processとのアナロジーなどからContainerが扱いやすくなることについて書きましたが、metaparticle自体はdynamicにcluster/podを変更するわけではありません。scriptでdynamicに変更できるようになると、より上記のアナロジーがハマり、k8sに乗っかったままかなり柔軟に分散プログラムが書けると思ったりします。
プレイドでも下手にクラスタ管理や組込済みの単純なオートスケールを使うより、nodeのgcp/api経由でインスタンス数の増減をさせるやり方をやってみたりしつつ、なんか違うな、と思ったりしていますw

今回はa standard library for cloud native applications on Kubernetes.Metaparticleを紹介しました。個人的にはこのアプローチの先にDistributed Programmingの桃源郷があるのかもしれないし無いかもしれないと思っています。

CXプラットフォーム「KARTE」を運営するプレイドでは、 KARTEを支える技術に興味を持つエンジニア(インターンも!)を募集しています。詳しくはこちら(Wantedly)をご覧ください。