riffはPivotalが開発中のk8s nativeなFaaS (Function as a Service)です。
現在のバージョンは0.0.3です。
Function
、Topic
という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 build
もriff 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プロジェクトでもいいのですが、javac
とjar
コマンドだけで関数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を集計します。