Kubernetes の co author として著名な Brendan Burns が始めたプロジェクトで、Metaparticle というのがあり、触ったことがなかったので試して見た。

とりあえず、Go と、Javascript のtutorial を試して見たが、どちらもそのままでは動かなかった。だから、2つともIssue を上げておいた。

ちなみに、Javascript の方は、既に fix済みで、npm パッケージに反映されていないだけなので、対応が簡単なので、2つから選ぶなら、今はjavascript にしておくのが無難かもしれない。fix の方法はあとで追加のしておく。言語は他には、Java, .Net, Python が存在する。

Metaparticle とは

Kubernetes 上のクラウドネイティブアプリのためのスタンダートライブラリだ。分散システムをだれでも使えるようにするためのもの。初心者でも扱えて、ベテランは時間を節約できるといいうためのもの。

最初のチュートリアルを流すだけで今回はいっぱいで、時間がないなかったが、チュートリアルには、シャーディングのチュートリアル
や、分散環境でも、ステートを保持できるStorage プロジェクト、分散環境コンテナでのロック、排他制御、リーダーエレクションを司るSync プロジェクトなど面白そうなものが沢山ある。主なプロジェクトへのリンクを付けておこう。Metaparticle のコンパイラはmp-compiler コマンドのソースで、Metaparticleのコンフィグをk8sのAPIに変換するもの。

ちなみに、メインページによると、Metaparticle はこんなことを助けてくれるらしい。

  • アプリをコンテナ化する
  • k8s にdeployする
  • ロードバランスされたリプリケーションされたサービスを素早く開発する
  • ロックや、分散レプリカのリーダーエレクションを実施する
  • クラウドネイティブのパターンを簡単に実装する。シャーディングとか。

tutorial を始める

先ほど書いた通り、そのままでは、チュートリアルは動かない。基本的にチュートリアルの手順でいいのだが、@npm install -s @metaparticle/package のパッケージが現在のMaster より古く、Mac で動作しないという感じになっている。fix はこれなので、このソースの部分のみを変更してあげれば良い。

コードを愛でる

下記のコードを書いて、npm start を実施すると、node のサーバのアプリのを作って、コンテナにパックしてくれる。そして、ポートとかも公開してくれるといった具合だ。ちなみに、自分のdockerhubに公開もしてくれる。

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],
        repository: 'docker.io/docker-user-goes-here',
        publish: true,
        public: true
    },
    () => {
        server.listen(port, (err) => {
            if (err) {
                return console.log('server startup error: ', err);
            }
            console.log(`server up on ${port}`);
        });
    }
);

コンテナが立ち上がって、実行された。

$ npm start

> metaparticle-example@0.0.1 start /Users/ushio/Codes/metaparticle/package/tutorials/javascript
> node ./index.js

Sending build context to Docker daemon  676.9kB
Step 1/4 : FROM node:6-alpine
 ---> f03aeae05553
Step 2/4 : COPY ./ /metaparticle-example/
 ---> a4df8ddccd9f
Step 3/4 : RUN npm --prefix /metaparticle-example/ install
 ---> Running in 31ac3e1c2e5b
npm WARN metaparticle-example@0.0.1 No repository field.
Removing intermediate container 31ac3e1c2e5b
 ---> 83c8fc972b49
Step 4/4 : CMD npm --prefix /metaparticle-example/ start
 ---> Running in 7a5dee27e6a2
Removing intermediate container 7a5dee27e6a2
 ---> bdc2b5d88438
Successfully built bdc2b5d88438
Successfully tagged tsuyoshiushio/metaparticle-example:latest
The push refers to repository [docker.io/tsuyoshiushio/metaparticle-example]
143f071599a1: Preparing
1c97fb857dde: Preparing
2653039e3c74: Preparing
5a72cbf266a2: Preparing
e53f74215d12: Preparing
e53f74215d12: Layer already exists
5a72cbf266a2: Layer already exists
2653039e3c74: Layer already exists
143f071599a1: Pushed
1c97fb857dde: Pushed
latest: digest: sha256:aae574948fa86f44dec58dd0d622dbad79540c9f9d6a41e602a88e90b45a3e35 size: 1368
c5c4b27ba06f0861952ae1961f1dce14d430f93aeb43289390da06288eca5e86

> metaparticle-example@0.0.1 start /metaparticle-example
> node ./index.js

server up on 8080

ちなみに、metaparticle のコードを見てみると、コードをパックして、イメージを作って、コンテナの中でもこのアプリを動かす。そのファイルが、先ほど修正したindex.js だ。この辺りのコードを読んでいると、今自分が、ローカルマシンか、Container (docker) か、Container (kubernetes) の中であるかを判断している。

/package/javascript/metaparticle-package/index.js

     inDockerContainer = () => { |

         switch (process.env.METAPARTICLE_IN_CONTAINER) { 
             case 'true': 
             case '1': 
                 return true; 
             case 'false': 
             case '0': 
                 return false; 
         } 
         try { 
             var info = fs.readFileSync("/proc/1/cgroup"); 
         } catch (err) { 
            return false; 
         } 
         // This is a little approximate... 
         if (info.indexOf("docker") != -1) { 
             return true; 
         } 
         if (info.indexOf("kubepods") != -1) { 
             return true; 
         } 
         return false; 
     }; 

で、このdocker-runner.js が動いているという感じ

/package/javascript/metaparticle-package/docker-runner.js

 (function () { 

     var shell = require('shelljs'); 

     portString = (options) => { 
         if (!options || !options.ports) { 
             return ''; 
         } 
         var portArr = options.ports.map((port) => { 
             return `-p ${port}:${port}` 
         }); 
         return portArr.join(' '); 
     }; 

    module.exports.run = (img, name, options) => { 
         ports = portString(options); 

         shell.exec(`docker run --name ${name} ${ports} -d ${img}`); 
         shell.exec(`docker logs -f ${name}`, () => { 
             console.log('done'); 
         }); 
     }; 

     module.exports.cancel = (name) => { 
         shell.exec(`docker kill ${name}`); 
         shell.exec(`docker rm ${name}`); 
     }; 
 })(); 

kubernetes にデプロイする

kubernetes にデプロイ先をかえるのはちょっと設定部を変えるだけ。Brendanが常々、kubernetes は、開発する人が、サーバーとかそんなものを意識しなくてもいいようにするためのものみたいな発言をしているけど、これを見てもその方向を感じられる。

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],
            replicas: 4,
        runner: 'metaparticle',
        repository: 'docker.io/docker-user-goes-here',
        publish: true,
        public: true
    },
    () => {
        server.listen(port, (err) => {
            if (err) {
                return console.log('server startup error: ', err);
            }
            console.log(`server up on ${port}`);
        });
    }
);

さっきまで ローカルの docker で動いてたけど、k8s に変わった。コードの違いは、runner: 'metaparticle' のところと、replicas: 4, のところだけ。

$ npm start

> metaparticle-example@0.0.1 start /Users/ushio/Codes/metaparticle/package/tutorials/javascript
> node ./index.js

Sending build context to Docker daemon  676.9kB
Step 1/4 : FROM node:6-alpine
 ---> f03aeae05553
Step 2/4 : COPY ./ /metaparticle-example/
 ---> 861905ecb943
Step 3/4 : RUN npm --prefix /metaparticle-example/ install
 ---> Running in 66f0ba1a4eb4
npm WARN metaparticle-example@0.0.1 No repository field.
Removing intermediate container 66f0ba1a4eb4
 ---> af31bf35f46c
Step 4/4 : CMD npm --prefix /metaparticle-example/ start
 ---> Running in 486dc81df5b2
Removing intermediate container 486dc81df5b2
 ---> 50774bea555b
Successfully built 50774bea555b
Successfully tagged tsuyoshiushio/metaparticle-example:latest
The push refers to repository [docker.io/tsuyoshiushio/metaparticle-example]
32c06497258b: Preparing
c2154a7e64c3: Preparing
2653039e3c74: Preparing
5a72cbf266a2: Preparing
e53f74215d12: Preparing
2653039e3c74: Layer already exists
5a72cbf266a2: Layer already exists
e53f74215d12: Layer already exists
32c06497258b: Pushed
c2154a7e64c3: Pushed
latest: digest: sha256:9ff2e4588bc206c839f4d73cf279dd366e780d480e5171716edbe7162268e9e4 size: 1368
2018/03/03 23:48:41 object is being deleted: deployments.extensions "metaparticle-example" already exists
==> Detected container metaparticle-example-78b84568df-d7ct9:metaparticle-example-0
==> Leaving container metaparticle-example-78b84568df-d7ct9:metaparticle-example-0

kubectl コマンドで見て見たら、いい感じで動いている。レプリカもセットされているね。

 kubectl get pods
NAME                                    READY     STATUS             RESTARTS   AGE
metaparticle-example-78b84568df-6d7kv   1/1       Running            0          33s
metaparticle-example-78b84568df-cm9gh   1/1       Running            0          33s
metaparticle-example-78b84568df-d7ct9   1/1       Running            0          33s
metaparticle-example-78b84568df-qslf9   1/1       Running            0          33s
minecraft-57cdddb95d-tbzgp              0/1       ImagePullBackOff   0          6d
omsagent-2nqdz                          1/1       Running            0          29d
omsagent-bp4nd                          1/1       Running            0          29d
omsagent-ngnhd                          1/1       Running            0          29d
invincible:ARM ushio$ kubectl get services
NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)                           AGE
kubernetes             ClusterIP      10.0.0.1       <none>          443/TCP                           31d
metaparticle-example   LoadBalancer   10.0.121.123   52.186.84.XXX   8080:30532/TCP                    47s
minecraft-svc          LoadBalancer   10.0.13.15     52.170.2.XXX    25575:31322/TCP,25565:32563/TCP   6d

シャーディング

さて、シャーディングを決めてみよう。

Sharding

さっきの所に、shards: 3 と、その、urlPattern を追加したのみ。urlPattern がシャーディングキーとして使われる。

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}`);
        });
    }
);

ステートフル(ストレージ)

k8s はステートをコンテナ間で共有するのは苦手っぽいけど、先のストレージプロジェクトでできる感じ。Service Fabric の Stateful Dictionary のようだ。

var mp = require('@metaparticle/storage');
mp.setStorage('file');

var server = http.createServer((request, response) => {
    // Define a scope for storage, in this case it is a shared 'global' scope.
    mp.scoped('global', (scope) => {
        if (!scope.count) {
            scope.count = 0;
        }
        scope.count++;
        return scope.count;
    }).then((count) => {
        response.end("There have been " + count + (count == 1 ? ' request.' :  ' requests.'));
    });
});

リーダーエレクション

こんな感じでできる模様。

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();

まとめ

いきなり動かなくて辛かったが、プロジェクト自体は相当いけているのではないだろうか?個人的には、今は Microservice の世代だけど、色々めんどくさい。だから、Serverless とか、PaaS/SaaS/FaaS とかがどんどんはやってきてる感じ。k8s もマネージドのオファーが普通になってきて、今後は、k8s が基盤になって、コンテナやk8sすら意識しなくてよくなるようになるのだろうか。凄い世界が来そうでプログラマとしてはとても楽しい世界だなと思う。元々DevOpsやクラウドのテクノロジーが気に入ったのは、インフラですらコードで書けてしまう世の中になったのが楽しかったのだ。これがどんどん簡単になっていっていますね。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.