📝 BLOG.IK.AM

@making's memo
(🗃 Categories 🏷 Tags)

Kubernetes NativeなFaaSであるriff 0.0.3を試す

🗃 {Dev/FaaS/riff}

🏷 Docker 🏷 Kubernetes 🏷 Reactor 🏷 Riff 🏷 Spring Cloud Function

🗓 Updated at 2018-02-25T02:03:40+09:00 by Toshiaki Maki  🗓 Created at 2018-02-04T19:31:16+09:00 by Toshiaki Maki  {✒️️ Edit  ⏰ History}


⚠️ Caution: This content is a bit old. Please be careful to read.

riffはPivotalが開発中のk8s nativeなFaaS (Function as a Service)です。
現在のバージョンは0.0.3です。

FunctionTopicというKubernetesのCustom Resourceが用意されています。

各種言語で関数コードを書き、Dockerイメージとしてパッケージングします。
Functionリソースと入り口となるTopicリソースをk8sにデプロイします。
この段階でPodはできません。

Topicに対してHTTP Gatewayにリクエストを送ると、そのTopicをinputとしているFunctionに設定されているコンテナがFunction Controller経由で
作成され、Podができます。Podはサイドカーを持ち、関数を実行するためのいくつかのプロトコルをサポートします。現時点では

  • HTTP
  • stdio
  • gRPC
  • pipes

がサポートされています。

関数が呼ばれない時間が続くとPodが自動で削除されます (Scale to Zero)。アイドル時間はデフォルトでは20秒です。

Riffの特徴としては、FaaSの制約として(分散システムにおけるCAP定理のような)ICE定理があるとして、この3つの特徴のうち2つを(将来的に)選択できるようにすることが挙げられます。

ICE定理は次の3つのうち同時に2つしか選択できない、というものです。

  • Immediate (関数がすぐに起動する)
  • Consistent (コンテナを不変にする)
  • Efficient (使わなれければ0にスケールする)

では早速Riffを使ってみます。以下、メモです。

Docker for Mac Edgeの用意

ローカルk8sとしてDocker for Mac (Edge)を使います。

brew cask install docker-edge

メモリを4GBにして、KubernetesタブでEnable Kubernetesにチェック。

kubectl config uset-context docker-for-desktop

Hemlのセットアップ

RiffはHelmでインストールできます。

kubectl -n kube-system create serviceaccount tiller
kubectl create clusterrolebinding tiller --clusterrole cluster-admin --serviceaccount=kube-system:tiller
helm init --service-account=tiller

helm repo add riffrepo https://riff-charts.storage.googleapis.com
helm repo update

Riffのインストール

helm install riffrepo/riff --name demo \
     --version 0.0.3-rbac \
     --set httpGateway.service.type=NodePort
$ kubectl get all
NAME                                   DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deploy/demo-riff-function-controller   1         1         1            1           44s
deploy/demo-riff-http-gateway          1         1         1            1           44s
deploy/demo-riff-kafka                 1         1         1            1           44s
deploy/demo-riff-topic-controller      1         1         1            1           44s
deploy/demo-riff-zookeeper             1         1         1            1           44s

NAME                                          DESIRED   CURRENT   READY     AGE
rs/demo-riff-function-controller-5df6c848d5   1         1         1         44s
rs/demo-riff-http-gateway-7cc944f97c          1         1         1         44s
rs/demo-riff-kafka-65555dbb87                 1         1         1         44s
rs/demo-riff-topic-controller-67ccb96678      1         1         1         44s
rs/demo-riff-zookeeper-859688cdc8             1         1         1         44s

NAME                                   DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deploy/demo-riff-function-controller   1         1         1            1           44s
deploy/demo-riff-http-gateway          1         1         1            1           44s
deploy/demo-riff-kafka                 1         1         1            1           44s
deploy/demo-riff-topic-controller      1         1         1            1           44s
deploy/demo-riff-zookeeper             1         1         1            1           44s

NAME                                          DESIRED   CURRENT   READY     AGE
rs/demo-riff-function-controller-5df6c848d5   1         1         1         44s
rs/demo-riff-http-gateway-7cc944f97c          1         1         1         44s
rs/demo-riff-kafka-65555dbb87                 1         1         1         44s
rs/demo-riff-topic-controller-67ccb96678      1         1         1         44s
rs/demo-riff-zookeeper-859688cdc8             1         1         1         44s

NAME                                                READY     STATUS    RESTARTS   AGE
po/demo-riff-function-controller-5df6c848d5-xtj2w   1/1       Running   0          44s
po/demo-riff-http-gateway-7cc944f97c-8c9kx          1/1       Running   1          44s
po/demo-riff-kafka-65555dbb87-w7q95                 1/1       Running   0          44s
po/demo-riff-topic-controller-67ccb96678-9rkkb      1/1       Running   0          44s
po/demo-riff-zookeeper-859688cdc8-pxt5h             1/1       Running   0          44s

NAME                         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
svc/demo-riff-http-gateway   NodePort    10.97.115.228   <none>        80:32356/TCP   44s
svc/demo-riff-kafka          ClusterIP   10.110.227.80   <none>        9092/TCP       44s
svc/demo-riff-zookeeper      ClusterIP   10.101.79.249   <none>        2181/TCP       44s
svc/kubernetes               ClusterIP   10.96.0.1       <none>        443/TCP        17m

Riff CLIのインストール

ヘルパースクリプトとしてriff CLIが用意されています。

curl -Lo riff https://github.com/projectriff/riff/releases/download/v0.0.3/riff && chmod +x riff && sudo mv riff /usr/local/bin/

今はshell scriptですが、goで書き直し中です。

Sample Functions

現時点でFunction Invokerは

  • JavaScript
  • ShellScript
  • Python2
  • Java

が公式に利用可能です。

次のディレクトリで作業します。

mkdir samples
cd samples

JavaScriptの場合

mkdir -p node/square
cd node/square
cat <<EOF > square.js
module.exports = (x) => x ** 2;
EOF

Promiseも使えます。

cat <<EOF > square.js
module.exports = (x) => Promise.resolve(x ** 2);
EOF

async/awaitも使えます。

cat <<EOF > square.js
module.exports = async (x) => x ** 2;
EOF
riff init -i numbers -u making

次のファイルができます。

$ ls -l
total 32
-rw-r--r--  1 maki  staff  113  2  3 22:31 Dockerfile
-rw-r--r--  1 maki  staff  154  2  3 22:31 square-function.yaml
-rw-r--r--  1 maki  staff   90  2  3 22:31 square-topics.yaml
-rw-r--r--  1 maki  staff   38  2  4 03:26 square.js

Dockerfile

FROM projectriff/node-function-invoker:0.0.3
ENV FUNCTION_URI /functions/square.js
ADD square.js ${FUNCTION_URI}

square-function.yaml

apiVersion: projectriff.io/v1
kind: Function
metadata:
  name: square
spec:
  protocol: http
  input: numbers
  container:
    image: making/square:0.0.1

square-topics.yaml

apiVersion: projectriff.io/v1
kind: Topic
metadata:
  name: numbers
spec:
  partitions: 1

dockerコマンドとkubectlコマンドでビルドとデプロイをしても良いですが、
riffコマンドでラップされています。こちらの方が楽です。

riff build -u making
riff apply

以上のriffコマンドのショートカット版が

riff create -i numbers -u making

です。

riff buildriff create--pushをつけるとDocker Registryにpushできます。

同期リクエストを送る場合は

$ riff publish -i numbers -d 10 -r
100

非同期リクエストを送る場合は

$ riff publish -i numbers -d 10 
message published to topic: numbers

Shell Scriptの場合

mkdir -p shell/upper
cd shell/upper
cat <<'EOF' > upper.sh
#!/bin/bash
echo $1 | tr [:lower:] [:upper:]
EOF
chmod +x upper.sh
riff create -i lower -u making
riff publish -i lower -d hello -r

Pythonの場合

mkdir -p pyhon/lower
cd pyhon/lower
cat <<'EOF' > lower.py
# -*- coding: utf-8 -*-
def process(data):
    print(data.lower())

if __name__ == '__main__':
    data = raw_input()
    process(data)
EOF
cat <<'EOF' > requirements.txt
EOF
riff create -i upper -u making --handler process
riff publish -i upper -d HELLO -r

Javaの場合

cd ../..
mkdir -p java/hello
cd java/hello

Mavenプロジェクトでもいいのですが、javacjarコマンドだけで関数jarファイルを実装します。

mkdir -p src/functions
cat <<EOF > src/functions/Hello.java
package functions;

import java.util.function.Function;

public class Hello implements Function<String, String> {
    public String apply(String name) {
        return "Hello " + name;
    }
}
EOF
mkdir classes
javac -sourcepath src -d classes src/functions/Hello.java
jar -cvf func.jar -C classes .
riff create --input names -u making --artifact func.jar --handler functions.Hello
riff publish -i names -d world -r

この関数はSpring Cloud Function上のIsolated Classloader上で実行されます。

JavaでWindow処理の場合

Reactorを使ってWindow処理ができます。

cd ../..
mkdir -p java/wordcounter
cd java/wordcounter
mkdir -p src/functions
cat <<'EOF' > src/functions/WordCounter.java
package functions;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import reactor.core.publisher.Flux;

public class WordCounter implements Function<Flux<String>, Flux<Map<String, Integer>>> {

    @Override
    public Flux<Map<String, Integer>> apply(Flux<String> words) {
        return words.window(Duration.ofSeconds(3))
                .flatMap(f -> f.flatMap(word -> Flux.fromArray(word.split("\\W")))
                        .reduce(new HashMap<String, Integer>(), (map, word) -> {
                            map.merge(word, 1, Integer::sum);
                            return map;
                        }));
    }
}
EOF
mkdir classes
mkdir libs
cd libs
curl -L -O -J http://central.maven.org/maven2/org/reactivestreams/reactive-streams/1.0.2/reactive-streams-1.0.2.jar
curl -L -O -J https://repo.spring.io/milestone/io/projectreactor/reactor-core/3.2.0.M1/reactor-core-3.2.0.M1.jar
cd ..
javac -cp .:libs/* -sourcepath src -d classes src/functions/WordCounter.java -cp libs/*
jar -cvf func.jar -C classes .

ReactorとReactive Streamsはjava-function-invokerに含まれるのでjarに同梱する必要がありません。

riff create --input words -u making --artifact func.jar --handler functions.WordCounter
riff publish -i words -d "Hello World" -r

3秒間で受信したメッセージのWord Countを集計します。