Antrea – Yet Another CNI Plug-in for Kubernetes

Antreaとは

2019年11月にSan Diegoで行われたKubeConのタイミングで、Kubernetesのためのネットワークプラグインとして、新たにAntreaというプロジェクトが公開されました。CNI (Container Networking Interface) に準拠したプラグインで、現在はVMwareが中心となって開発をしていますが、オープンソースのプロジェクトですので、今後は徐々にコミュティーからのサポートが得られていくのではないかと思います。GitHubのリポジトリはここにあります。

“Antrea”という名前ですが、実はAntrea Netから来ています。Antreaはフィンランドに近いロシアの街で、世界最古の漁網が見つかった場所として知られており、その網は見つかった場所にちなみんで「Antrea Net」と呼ばれています。Kubernetesという名前は「操舵手」というギリシャ語から付けられたものですが、これに倣ってKubernetes関連のプロジェクトには船や海にちなんだ名前(例えばhelm/tillerなど)がつけられることが多くなっています。一方、CNIのプラグインであるCalicoやFlannelはどちらも繊維(ファブリック)に関係のある名前になっています(「ネットワーク」と「ファブリック」をかけている??)。この「海」と「ファブリック」の二つの要素を兼ね合わせた「漁網」であるAntrea Netに因んでAntreaという名前がつけられました。

Antreaの特徴は以下のような点になります。

  • Open vSwitchによるデータプレーン
  • Network Policyのサポート(iptablesからの脱却)
  • LinuxとWindowsサポート(予定)
  • 幅広いプラットフォームサポート
  • Octantとの連携
  • rspanやIPFIXのサポートなど(予定)

Antreaは仮想スイッチとして広く使われているOpen vSwitch (OVS)をデータパスに使用しているのがもっとも大きな特徴です。OVSをデータパスに使用することでいくつかのメリットが得られます。KubbernetesのNetwork PolicyやServiceのロードバランスを実現するために、他のCNIプラグインの多くはiptablesを使っています。iptablesは古くからある実績豊富な機能ではありますが、性能はあまりよろしくありません。iptabblesはルール数が多くなると、CPUに対する負荷、遅延、設定時間が非常に大きくなることが知られています。複雑なNetwork Policyや多数のServiceを使っているKubernetes環境では、iptablesベースのCNIでは期待した性能が得られないことが予想されます。一方、AntreaはOVSをデータプパスに使っているため、iptablesに起因する性能的問題を回避することができます。

OVSはLinux以外のOS(例えばWindows)もサポートしているので、多様な環境にまたがったKubernetesネットワーク環境を実現することができます。現時点のAntreaはLinuxのみのサポートですが、将来的にはWindowsもサポートする予定です。KubernetesはすでにWindowsをサポートしていますので、AntreaがWindowsをサポートするようになれば、LinuxとWindowsにまたがったネットワークを作って、そこに統一的なNetwork Policyを適用することができるようになります。また、OVSは仮想スイッチの機能だけではなく、他にもさまざまな機能(NetFlow/sFlow/IPFIXやRSPANなど)をサポートしています。将来的にはこれらの機能をAntreaから利用できるようにしていく予定です。

Antreaは幅広いプラットフォームで実行されれることを想定しています。オンプレの仮想環境はもちろん、ベアメタルや各種パブリッククラウドで実行することを想定して作られています。AntreaはKindで作ったKubernetesクラスタもサポートしていますので、簡易的にクラスタを用意してテストをすることもできて便利です。ただし、現在はLinuxの上のKindのみのサポートになります。MacOSのKindはまだサポートされてません。Kindで作ったクラスタでAntreaを動かす場合は、OVSのカーネルモジュールは使われず、OVSのユーザ空間のデータパスが使われます(ブリッジ作成時にdatapath_type=netdevが指定される)。

EncapsulationはデフォルトではVXLANが使われますが、GeneveやGRE、STTを使うこともできます。またこれらのEncapsulationとIPsecを組み合わせて使うこともできます。将来的には、Encapsulationをしないモードもサポートが予定されています。

VMwareはNSX-TのためのCNIプラグインとしてNCPを提供しています。それなのになぜ別のCNIプログインを開発しているのか疑問に思われる方もいるでしょう。NCPはすでに多くのプロダクション環境で使われており、実績面ではAntreaよりもはるかに成熟しています。また、NCPはiptablesに依存しないServiceのロードバランシングを既に実現しているので、スケーラビリティ的にも優れています。一方、Antreaはまだ出来立てのホヤホヤで商用の実績はありません。ただ、Antreaは100%オープンソースで手軽に始めることができるのは魅力です。幅広いプラットフォームやOSで利用できるようになる潜在的能力に魅力を感じる人も多いでしょう。Antreaが機能面、堅牢面においてすぐにNCPのレベルに達するとは考えにくいですが、コミュニティのサポートが得られればかなりのスピードで進化していく可能性を秘めていると思います。現時点でAntreaをプロダクション環境で使うことはお勧めしませんが、将来的には何かしらの形でNSX/NCPとの連携のようなこともできるようになるかもしれません。その頃にはAntreaも十分に成熟しているはずですので、ユーザにとって環境やユースケースによって使い分けられる選択肢が広がることは喜ばしいことでしょう。

アーキテクチャ

Antrea はKubernetesに特化したネットワークプラグインです。例えばCalicoはKubernetesだけでなくOpenStackなど他のオーケストレーションシステムをサポートしていますが、AntreaはKubernetesのみを対象としています。そのぶんAntreaはKubernetesの持つ仕組み(API、Tool、Operator)を最大限に利用するようになっています。

アーキテクチャーは以下通りです。

Antreaはいくつかのコンポーネントによって構成されます。Antrea Conrollerはいわゆるコントローラで、Deploymentとして作成さられます。Kubernetes APIを使って関連するオブジェクトの追加や削除を監視しています。一方、Antrea AgentはDaemonSetとして作られ、各ノードで動作します。Antrea agentはOpenFlowの設定をOVS に対して行います。antrea-cniはKubeletからのCNIコマンドをAntrea Agentに引き渡します。antctlコマンドはAntrea Controllerに対するCLIインターフェースを提供するもので、現在絶賛開発中です。

現時点ではkube-proxyはKubernetes標準のものを使用しています(したがって、Serviceのロードバランシングにはiptablesを使います)が、将来的にはこれはOVSベースのロードバランシングを使ったkube-proxyに置き換えられる予定です。Network Policyは既に完全にOVSベースで、iptablesは使われていません。なお、Antreaの最初のリリース(v0.1.0)では、OVSのOpenFlowテーブルを設定するのにovs-ofctlコマンドを使ってしましたが、現在はlibOpenflowライブラリを使うように変更されています。

OctantはKubernetes Clusterの状況を可視化してくれるダッシュボードです。AntreaはOctant用のプラグインを用意しており、Octantから簡単にAntreaの状況を確認することができます。

インストールと動作確認

今回はオンプレの環境でAntreaを動かしてみます。Kubernetes環境はESXiの上にCluster APIで2つのWorker Nodesを作った素のKubernetes環境を用意することにします。ESXi向けCluster API(CAPV; Cluster API Provider for vSphere) はCentOS 7、Ubuntu 18.04、Photon3用のOVAイメージを提供していますが、今回はUbuntu 18.04を使うことにします。AntreaはKubernetes 1.16以降を要求しますので、v1.16.3のOVAイメージを使用することにします。

標準的な方法でCluster APIでKubernetes環境を作ります。ノードが上がってきたら、各ノードでOVSのカーネルモジュールを入れます。Cluster APIで作られるノードは再構築される可能性があるため、本来ならばCluster APIで使うOVAテンプレートに予めOVSを組み込んでおくべきですが、今回はテストなのでそのステップは省略することにします。各ノードで 以下のコマンド

sudo apt update ; sudo apt -y install openvswitch-switch

を実行して、OVSをインストールします。次にカーネルモジュールが正しく読み込まれたか確認しましょう。

$ lsmod | grep openvswitch
openvswitch           131072  0
nsh                    16384  1 openvswitch
nf_nat_ipv6            16384  1 openvswitch
nf_defrag_ipv6         20480  3 nf_conntrack_ipv6,openvswitch,ip_vs
nf_nat_ipv4            16384  2 openvswitch,iptable_nat
nf_nat                 32768  5 nf_nat_masquerade_ipv4,nf_nat_ipv6,nf_nat_ipv4,xt_nat,openvswitch
nf_conntrack          131072  12 xt_conntrack,nf_nat_masquerade_ipv4,nf_conntrack_ipv6,nf_conntrack_ipv4,nf_nat,nf_nat_ipv6,ipt_MASQUERADE,nf_nat_ipv4,xt_nat,openvswitch,nf_conntrack_netlink,ip_vs
libcrc32c              16384  4 nf_conntrack,nf_nat,openvswitch,ip_vs

この時点ではKubernetesのノードは以下のような状態になっているはずです。

bash-3.2$ kubectl get nodes
NAME                                       STATUS     ROLES    AGE   VERSION
workload-cluster-1-controlplane-0          NotReady   master   30m   v1.16.3
workload-cluster-1-md-0-78469c8cf9-5r9bv   NotReady   <none>   21m   v1.16.3
workload-cluster-1-md-0-78469c8cf9-tlkn9   NotReady   <none>   21m   v1.16.3

STATUSが “Not Ready” になっていますが、これはCNIプラグインがまだ何もインストールされていないためで、期待された動きです。

Antreaのインストールは非常に簡単です。以下のコマンドを実行するだけで必要なコンポーネントが全てインストールされます。

kubectl apply -f https://raw.githubusercontent.com/vmware-tanzu/antrea/master/build/yamls/antrea.yml

ここではAntreaの最新のものを使うようにしていますが、Antreaの開発のペースは早いので、一時的に最新版ではうまく動作しないケースもあるかもしれません。その場合には、

kubectl apply -f https://github.com/vmware-tanzu/antrea/releases/download/<TAG>/antrea.yml

の<TAG>の部分を適当なものに置き換えてください(現時点で最新のリリース版タグは “v0.2.0” です)。

インストールが無事に終了するとkube-system Namespaceに以下のようなPodができて、全てRunningのSTATUSになっているはずです。

bash-3.2$ kubectl get pods -n kube-system
NAME                                                        READY   STATUS    RESTARTS   AGE
antrea-agent-dvpn2                                          2/2     Running   0          8m50s
antrea-agent-qqhpc                                          2/2     Running   0          8m50s
antrea-agent-tm28v                                          2/2     Running   0          8m50s
antrea-controller-6946cdc7f8-jnbxt                          1/1     Running   0          8m50s
coredns-5644d7b6d9-878x2                                    1/1     Running   0          46m
coredns-5644d7b6d9-pvj2j                                    1/1     Running   0          46m
etcd-workload-cluster-1-controlplane-0                      1/1     Running   0          46m
kube-apiserver-workload-cluster-1-controlplane-0            1/1     Running   0          46m
kube-controller-manager-workload-cluster-1-controlplane-0   1/1     Running   2          46m
kube-proxy-4km5k                                            1/1     Running   0          38m
kube-proxy-cgxcp                                            1/1     Running   0          38m
kube-proxy-h4css                                            1/1     Running   0          46m
kube-scheduler-workload-cluster-1-controlplane-0            1/1     Running   2          46m
vsphere-cloud-controller-manager-gfgbp                      1/1     Running   1          45m
vsphere-csi-controller-0                                    5/5     Running   0          45m
vsphere-csi-node-6mvpt                                      3/3     Running   0          7m4s
vsphere-csi-node-gwr67                                      3/3     Running   0          7m4s
vsphere-csi-node-hb6rk                                      3/3     Running   0          7m4s

DaemonSetも確認してみましょう。

bash-3.2$ kubectl get daemonset -n kube-system
NAME                               DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR                     AGE
antrea-agent                       3         3         3       3            3           beta.kubernetes.io/os=linux       9m1s
kube-proxy                         3         3         3       3            3           beta.kubernetes.io/os=linux       47m
vsphere-cloud-controller-manager   1         1         1       1            1           node-role.kubernetes.io/master=   46m
vsphere-csi-node                   3         3         3       3            3           <none>                            46m

antrea-agentがDeamonSetとして実行されているのを確認することができます。

ConfigMapを確認してみましょう。

bash-3.2$ kubectl get configmap -n kube-system
NAME                                 DATA   AGE
antrea-config-tm7bht9mg6             3      10m
coredns                              1      48m
extension-apiserver-authentication   6      48m
kube-proxy                           2      48m
kubeadm-config                       2      48m
kubelet-config-1.16                  1      48m
vsphere-cloud-config                 1      47m
bash-3.2$ kubectl get configmap antrea-config-tm7bht9mg6 -n kube-system -o yaml
apiVersion: v1
data:
  antrea-agent.conf: |
    # Name of the OpenVSwitch bridge antrea-agent will create and use.
    # Make sure it doesn't conflict with your existing OpenVSwitch bridges.
    #ovsBridge: br-int
    # Datapath type to use for the OpenVSwitch bridge created by Antrea. Supported values are:
    # - system
    # - netdev
    # 'system' is the default value and corresponds to the kernel datapath. Use 'netdev' to run
    # OVS in userspace mode. Userspace mode requires the tun device driver to be available.
    #ovsDatapathType: system
    # Name of the interface antrea-agent will create and use for host <--> pod communication.
    # Make sure it doesn't conflict with your existing interfaces.
    #hostGateway: gw0
    # Encapsulation mode for communication between Pods across Nodes, supported values:
    # - vxlan (default)
    # - geneve
    # - gre
    # - stt
    #tunnelType: vxlan
    # Default MTU to use for the host gateway interface and the network interface of each Pod. If
    # omitted, antrea-agent will default this value to 1450 to accomodate for tunnel encapsulate
    # overhead.
    #defaultMTU: 1450
    # Whether or not to enable IPSec encryption of tunnel traffic.
    #enableIPSecTunnel: false
    # CIDR Range for services in cluster. It's required to support egress network policy, should
    # be set to the same value as the one specified by --service-cluster-ip-range for kube-apiserver.
    #serviceCIDR: 10.96.0.0/12
  antrea-cni.conf: |
    {
        "cniVersion":"0.3.0",
        "name": "antrea",
        "type": "antrea",
        "ipam": {
            "type": "host-local"
        }
    }
  antrea-controller.conf: ""
kind: ConfigMap
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"antrea-agent.conf":..<snipped>..}}
  creationTimestamp: "2020-01-06T06:57:51Z"
  labels:
    app: antrea
  name: antrea-config-tm7bht9mg6
  namespace: kube-system
  resourceVersion: "4819"
  selfLink: /api/v1/namespaces/kube-system/configmaps/antrea-config-tm7bht9mg6
  uid: e6115864-4179-471d-a382-a1bb4afdeca1

これをみて分かるように、ConfigMapでは、

  • Integration Bridge名(default: br-int)
  • DataPath タイプ(default: system)
  • ホストと通信するインターフェース名(default: gw0)
  • トンネル(Encapsulation)タイプ(default: vxlan)
  • MTU値(default: 1450)

などを設定することができます。

それでは何かDeploymentを作ってみましょう。今回はお手軽にnginxのDeploymentをreplicas=2で作って、port-forwardでPodにアクセスしてみましょう。

bash-3.2$ cat nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
bash-3.2$ kubectl apply -f nginx.yaml
deployment.apps/nginx-deployment created
bash-3.2$ kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-54f57cf6bf-fx4mz   1/1     Running   0          44s
nginx-deployment-54f57cf6bf-mg74x   1/1     Running   0          44s
bash-3.2$ kubectl port-forward nginx-deployment-54f57cf6bf-fx4mz 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

無事にnginxにアクセスするすることができました。

次にantrea-agentの内部がどう動いているのか見てみましょう。

bash-3.2$ kubectl exec -ti antrea-agent-dvpn2 -n kube-system /bin/bash
Defaulting container name to antrea-agent.
Use 'kubectl describe pod/antrea-agent-dvpn2 -n kube-system' to see all of the containers in this pod.
root@workload-cluster-1-md-0-78469c8cf9-tlkn9:/# ovs-vsctl show
2fa33701-4b83-4016-a070-08fc90921b2d
    Bridge br-int
        Port "coredns--f4e323"
            Interface "coredns--f4e323"
        Port "tun0"
            Interface "tun0"
                type: vxlan
                options: {key=flow, remote_ip=flow}
        Port "nginx-de-70ebff"
            Interface "nginx-de-70ebff"
        Port "vsphere--7be010"
            Interface "vsphere--7be010"
        Port "gw0"
            Interface "gw0"
                type: internal
    ovs_version: "2.11.1"

Pod用のインターフェースが作成されて、Integrationブリッジ(br-int)に接続されている様子が分かると思います。

最後にOctantとの連携を見てみましょう。

AntreaにはOctant用のプラグインが用意されていて、Octant側からAntreaの状況を確認することができるようになっています。AntreaのOctantプラグインはPodとして動かす方法とプロセスとして動かす方法と2つあります。今回はPodとして動かしてみます。

まず、必要なSecretを作ります。

kubectl create secret generic octant-kubeconfig --from-file=/Users/shindom/work/cluster-api/out/workload-cluster-1/kubeconfig -n kube-system

次にbuild/yamls/antrea-octant.ymlを使用したkubeconfigに合わせてファイル名を書き換えます。

# Change admin.conf to the name of kubeconfig file in your set up.
- name: KUBECONFIG
  value: "/kube/kubeconfig"

Antrea用のOctantプラグインはAntreaのレポジトリにソースがあるので自分でビルドすることもできますが、私のDockerhubにビルド済みのものを置いておきましたので、こちらをお使いいただくのが簡単です。build/yamls/antrea-octant.ymlを以下のように書き換えて、

containers:
 - name: antrea-octant
   image: mshindo/octant-antrea-ubuntu:latest

Podとしてデプロイします。

kubectl apply -f build/yamls/antrea-octant.yml

上記のyamlファイルはNodePortを作るようになっていますので、そこに接続するとOctantのUIにアクセスすることができます。

今後の予定

Windowsの対応、No Encapsulationモードの対応、OVSベースのService Load Balancing、RSPAN/IPFIXなどのDay 2 Operation機能への対応などは既に述べた通りですが、他にも

  • IPv6でのPod Network対応
  • DPDKやAF_XDPなどの高速データパス技術への対応

などがロードマップに上がっています。

本当はAntreaが採用しているデータパスのパイプラインについても説明したかったのですが、力尽きたので今回はここまでで(笑)。

Photo by Manuel Sardo on Unsplash

Dispatch Serverless Framework

Dispatch は Function as a Service (FaaS) のためのフレームワークで、VMware が中心となってオープンソースで開発が進められています。

一般的に FaaS は大きく分けるとホスティングされたサービスとして提供されるもの(e.g. AWS Lambda、Azure Functions、Google Cloud Function、など)と、自らインストールして使うもの(Apache OpenWhisk、OpenFaaS、Fission、Kubreless、Riff、など)がに分けられますが、この分類でいくと Dispatch は後者に分類されるものになります。ただし、Dispatch はいわゆる FaaS 機能の中核となる部分(エンジン)を提供しているわけでありません。OpenFaaS、Kubeless、Riff など、現在  FaaS エンジンには多くの選択肢があります。このような状況の中で Dispatch がこれらの FaaS エンジンと似たような機能を提供してもあまり価値がありません。まだ、どの FaaS エンジンが今後主流になって行きそうかも分からない状況なので、特定のエンジンを前提にするのもあまり筋がよろしくなさそう、ということで、Dispatch は FaaS エンジン部分を pluggable にして、その周辺機能を実装する、という戦略を取っています。Dispatch が「FaaS フレームワーク」と呼ばれる所以です。

現在、Dispatch には2つのバージョン(ブランチ)があります。一つは Dispatch-Solo と呼ばれるもの、もう一つは Dispatch-Knative と呼ばれるものです。

Knative というのは Google Cloud Next 2018 で発表されたプロジェクトで、Google、Pivotal、RedHat、IBM などが開発に関わっています。その狙いは Dispatch に近く、FaaS / Serverless に対して、Build / Serving / Events といった基本的な building block を提供しよう、というものです。Dispatch は Knative が発表された際に、Knative と競合路線を取るのではなく、Knative と協調していく、という判断をしました。つまり、Knative が提供する基本機能(Build、Serving、Events)は Knative のものを利用し、マルチテナンシーなど、Knative にはない Dispatch ならではの機能を Dispatch として提供していこう、というわけです。この Knative  をベースにした Dispatch が Dispatch-Knativeで、商用環境で使われることを想定したフル機能版の Dispatch の開発を目指しています。Dispatch-Knative はプラットフォームとして Kubernetes を利用しており、現在盛んに開発が行われています。

Dispatch-Knative アーキテクチャ

一方、Dispatch-Solo は、Dispatch-Knative の持つ様々な依存関係を極力最小化して、出来るだけ手軽に使えるものを提供することを目的に作られています。Dispatch-Solo はプラットフォームに Kubernetes を必要とせず、Docker だけで動くようになっています。また Photon OS をベースとした OVA としてパッケージされていますので、手軽に ESXi、VMware Workstation、VMware Fusion 上に展開して使うことができます。Dispatch-Solo は Dispatch-Knative とほぼ同様な機能を提供していますが、スケールアウト、マルチテナンシー、サービス連携の機能がない等、一部機能に制限があります。プロダクション利用ではなく、お手軽に利用できることに注力をしているバージョンです。

Dispatch-Solo アーキテクチャ

Dispatch は以下のような機能を持っています。

  • Kubernetes 上で動作(Dispatch-Knative のみ)
  • Pluggable FaaS Engine (OpenFaaS, Riff, Kubeless)
  • Runtime (Pyhton3, Nodejs, Java, Powershell, Clojure)
  • Let’s Encrypt サポート
  • OpenTracing 対応
  • 複数 IDP (Google, Auth0, vIDM, GitHub) のサポート(Dispatch-Knativeのみ)
  • Organization に基づいたマルチテナンシー(Dispatch-Knative のみ)
  • RBAC 機能
  • Cloud Events 対応
  • 拡張可能なイベントドライバ
  • Open Service Broker 統合(Dispatch-Knative のみ)

Dispatch によって作られた Function は大きく分けて2つの方法で呼び出されます。一つは Function に API の Endpoint を設けて、それを明示的(同期的)に呼び出す方法です。Dispatch には Kong というオープンソースの API Gateway が組み込まれており、これを利用して API Endpoint を Function に紐づけることができます。もう一つの Function の呼び出し方法はイベントドライバによる呼び出しです。イベントドライバがサポートしているイベントと Dispatch によって作られた Function を binding することにより、非同期的に発生するイベントによって Function を呼び出すことができます。

現在 Dispatch がサポートしているイベントドライバは以下の通りです。

  • CloudEvent
  • vCenter
  • AWS
  • EventGrid
  • Cron

VMware が開発を主導していることもあり、vCenter のイベントドライバがサポートされているのが特徴的です。これにより例えば ESXi 上の仮想マシンの Power ON/OFF などをトリガーにして Dispatch に登録された Function を実行しオペレーションの自動化を実現する、といったことが可能になります。

今回はお手軽に利用できる Dispatch-Solo を使って、簡単に動作をみてみることにしましょう。

Dispatch-Solo の最新版の OVA は以下の方法で入手することができます。

$ curl -OL http://vmware-dispatch.s3-website-us-west-2.amazonaws.com/ova/dispatch-latest-solo.ova

次にこの OVA ファイルをデプロイします。今回は VMware Fusion を使うことにします。アプリケーションおよびネットワークの設定を聞かれますが、root パスワードだけ設定すれば大丈夫です。

Dispatch のバイナリをダウンロードしてインストールします。現在 Mac 用と Linux 用のバイナリが用意されています(残念ながら Windows 用のバイナリは今のところ用意されていません)。

(Mac/Linux共通)
$ export LATEST=$(curl -s https://api.github.com/repos/vmware/dispatch/releases/latest | jq -r .name)

(Macの場合)
$ curl -OL https://github.com/vmware/dispatch/releases/download/$LATEST/dispatch-darwin$ chmod +x dispatch-darwin$ mv dispatch-darwin /usr/local/bin/dispatch

(Linuxの場合)
$ curl -OL https://github.com/vmware/dispatch/releases/download/$LATEST/dispatch-linux$ chmod +x dispatch-linux$ mv dispatch-linux /usr/local/bin/dispatch

インストールした Dispatch-Solo の仮想マシンに振られた IP アドレスを確認し、それを DISPATCH_HOST という環境変数に設定して Dispatch が使用する設定ファイルを作っておきます。

$ export DISPATCH_HOST=10.156.250.95
$ cat << EOF > $HOME/.dispatch/config.json
{
"current": "solo",
"contexts": {
"solo": {
"host": "${DISPATCH_HOST}",
"port": 8080,
"scheme": "http",
"organization": "dispatch",
"cookie": "cookie",
"insecure": true,
"api-https-port": 443
}
}
}
EOF

これで Dispatch を使う準備が一通り整いました。ここから Dispatch を使って Function を作っていきます。まず、Funciton を実行するベースとなるイメージを作ります。各ランタイム(言語)ごとにイメージが用意されていますが、”create seed-image” コマンドを使うと、Node.js、Python 3、Java、Powershell 用のイメージを一発で作ることができるので便利です。

$ dispatch create seed-images
Created BaseImage: nodejs-base
Created BaseImage: python3-base
Created BaseImage: powershell-base
Created BaseImage: java-base
Created Image: nodejs
Created Image: python3
Created Image: powershell
Created Image: java
$ dispatch get images
     NAME    | URL |    BASEIMAGE    |  STATUS  |         CREATED DATE         
--------------------------------------------------------------------------
  java       |     | java-base       | CREATING | Sat Dec  8 16:16:24 JST 2018 
  nodejs     |     | nodejs-base     | CREATING | Sat Dec  8 16:16:24 JST 2018 
  powershell |     | powershell-base | CREATING | Sat Dec  8 16:16:24 JST 2018 
  python3    |     | python3-base    | CREATING | Sat Dec  8 16:16:24 JST 2018 

“create seed-image” コマンド実行直後は “get image” コマンド結果の STATUS の欄が “CREATING” になっていますが、しばらく待つと全てのランタイムの STATUS が “READY” になります。

$ dispatch get images
     NAME    |                         URL                          |    BASEIMAGE    | STATUS |         CREATED DATE         
-------------------------------------------------------------------------------------------------------------------------
  java       | dispatch/dd3c73a4-2cf7-40db-8944-6c0d11f9157b:latest | java-base       | READY  | Sat Dec  8 16:16:24 JST 2018 
  nodejs     | dispatch/bbd15fbc-cfef-43d6-9d56-105c359fdedd:latest | nodejs-base     | READY  | Sat Dec  8 16:16:24 JST 2018 
  powershell | dispatch/1efa88c4-55cc-4bf8-a6d9-0a80c084cc89:latest | powershell-base | READY  | Sat Dec  8 16:16:24 JST 2018 
  python3    | dispatch/bcd7a157-117e-47c3-a276-3012d07b1848:latest | python3-base    | READY  | Sat Dec  8 16:16:24 JST 2018 

次に実際に実行される Function を作っていきます。今回は Python を使って簡単な関数を作ってみましょう。

$ cat << EOF > hello.py
def handle(ctx, payload):
    name = "Noone"
    place = "Nowhere"
    if payload:
        name = payload.get("name", name)
        place = payload.get("place", place)
    return {"myField": "Hello, %s from %s" % (name, place)}
EOF

handle() という関数がいわゆるハンドラ関数で、関数のエントリポイントとなります。この handle() という関数は2つの引数を取ります。”ctx” は Context の意で、ホスト側から渡される情報(例えば Secret など)が入ってきます。一方、”payload” の方は HTTP(S) のリクエストで渡されれてくる情報が入ってきます。今回の例では HTTP POST されてくる “name” というアトリビュートと “place” というアトリビュートを payload  から取り出しています。

次に “create function” コマンドで今しがた作った関数を Dispatch に登録します。今回はその関数に “hello-world” という名前を付けることにします。

$ dispatch create function hello-world --image python3 ./hello.py
Created function: hello-world

“get function” コマンドで関数が作られたのを確認することができます(STATUS の欄が READY になれば OK です)。

$ dispatch get function
     NAME     |                       FUNCTIONIMAGE                       | STATUS |         CREATED DATE         
--------------------------------------------------------------------------------------------------------------
  hello-world | dispatch/func-02625403-7cf4-477f-aca8-793a9cc2d55c:latest | READY  | Sat Dec  8 16:18:18 JST 2018 

では、この関数を実際に呼び出してみましょう。それには “exec” コマンドを使います。HTTP のパラメータは “–input” に続く文字列で指定できます。

$ dispatch exec hello-world --input '{"name": "Jon", "place": "Winterfell"}' --wait
{
    "blocking": true,
    "executedTime": 1544253540926528854,
    "faasId": "02625403-7cf4-477f-aca8-793a9cc2d55c",
    "finishedTime": 1544253540930869980,
    "functionId": "4a550524-85da-4d28-9d5f-47b35a425cd8",
    "functionName": "hello-world",
    "input": {
        "name": "Jon",
        "place": "Winterfell"
    },
    "logs": {
        "stderr": null,
        "stdout": [
            "Serving on http://0.0.0.0:9000"
        ]
    },
    "name": "440256c2-7710-4ea1-bc20-5231c2bd4363",
    "output": {
        "myField": "Hello, Jon from Winterfell"
    },
    "reason": null,
    "secrets": [],
    "services": null,
    "status": "READY",
    "tags": []
}

“–wait” というパラメータはこの関数の呼び出しを同期的に行う、という意味です。”–wait” を付けないと、関数は実行されますが、”exec” コマンドはその関数の終了を待たずに終了しますので、結果は別途確認する必要があります。今回は “–wait” を付けて実行していますので、返された JSON の中の “output” に期待された結果が返ってきているのが確認できます。

次に、この関数に API Endpoint を設定してみましょう。API Endpoint の設定には “create api” コマンドを使用します。

$ dispatch create api api-hello hello-world --method POST --path /hello
Created api: api-hello
shindom-a01:Downloads shindom$ dispatch get api api-hello
    NAME    |  FUNCTION   | PROTOCOL  | METHOD | DOMAIN |                   PATH                   |  AUTH  | STATUS | ENABLED 
--------------------------------------------------------------------------------------------------------------------------------
  api-hello | hello-world | http      | POST   |        | http://10.156.250.95:8081/dispatch/hello | public | READY  | true    
            |             | https     |        |        | https://10.156.250.95/dispatch/hello     |        |        |         
--------------------------------------------------------------------------------------------------------------------------------

では、この API Endpoint を呼び出してみましょう。

$ curl http://10.156.250.95:8081/dispatch/hello -H "Content-Type: application/json" -d '{"name": "Motonori", "place": "Tokyo"}'
{"myField":"Hello, Motonori from Tokyo"}

さて、ここまでは関数を CLI もしくは API Endpoint から同期的に関数を呼び出す例をみてきましたが、今度はイベントドライバによる非同期的な関数の呼び出しを試してみましょう。

Dispatch でサポートされているいくつかのイベントドライバの中から、今回は最もシンプルな Cron Event Driver を使ってみましょう。Cron Event Driver はその名の通りで、定期的に関数を呼び出してくれるイベントドライバで、UNIX の cron と同じような書式で起動するタイミングを指定をすることができます。

Dispatch のイベントドライバを使うためには3つのステップを踏む必要があります。1つ目は Event Driver Type の設定です。今回はdispatchframework にある Cron Event Driver に “cron” という名前を付けてやります。

$ dispatch create eventdrivertype cron dispatchframework/dispatch-events-cron:0.0.1
Created event driver type: cron

次に行うのは Event Driver の登録です。先ほど作った “cron” という Event Driver Type のドライバを作り “every-10-seconds” という名前を付け、イベントドライバに cron 書式(e.g. “0/10 * * * *”)で実行されるべきタイミングを指定します。

$ dispatch create eventdriver cron --name every-10-seconds --set cron="0/10 * * * *"
Created event driver: every-10-seconds

最後にイベントに対する subscription の設定を行います。具体的には Funciotn(今回の例で hello-world)とイベントドライバが生成するイベント(今回の例では cron.trigger)の紐付け( binding) 設定を行うことになります。

$ dispatch create subscription hello-world --name cron-sub --event-type cron.trigger
created subscription: cron-sub

これで設定は終わりです。この設定を行うと、10秒ごとに hello-world という Function が呼ばれることになり、その結果は “get runs” コマンドで確認をすることができます。

$ dispatch get runs
                   ID                  |  FUNCTION   | STATUS |               STARTED               |              FINISHED               
-------------------------------------------------------------------------------------------------------------------------------------
  5f9a79e0-1612-4169-90e8-e28e1f26640c | hello-world | READY  | 2018-12-08T16:35:30.003557189+09:00 | 2018-12-08T16:35:30.006887242+09:00 
  55fc2066-bd53-4e04-b0b6-bc1bb7940833 | hello-world | READY  | 2018-12-08T16:35:20.003363373+09:00 | 2018-12-08T16:35:20.007476442+09:00 
  19878a0d-312d-44ca-94b7-dc4999caf433 | hello-world | READY  | 2018-12-08T16:35:11.482092927+09:00 | 2018-12-08T16:35:11.485768711+09:00 
  14d2f6dc-2aa6-4bef-b9c4-a1cd6db6fe1f | hello-world | READY  | 2018-12-08T16:35:00.003163345+09:00 | 2018-12-08T16:35:00.007290792+09:00 
  d55328b0-53a5-48b0-b828-0ddff6648cd9 | hello-world | READY  | 2018-12-08T16:34:50.002078415+09:00 | 2018-12-08T16:34:50.006185049+09:00 
  3bc0d86b-1d73-4a94-bd7b-142f6d4743ae | hello-world | READY  | 2018-12-08T16:34:41.469205488+09:00 | 2018-12-08T16:34:41.472881958+09:00 

いかがでしたでしょうか? Dispatch はまだまだ若いプロジェクトで、活発に開発が行われています。みなさん、是非 Dispatch を使ってみてフィードバックをしていただだけると助かります。また、Dispatch はまだまだ把握しやすい規模のプロジェクトですので、開発にも参加しやすいと思います。色々な形で皆で Dispatch を盛り上げていけると嬉しいです!

VMware is the Dialtone for Kubernetes / 20代の人には分からないだろうけどw

最近たびたび “Dialtone” という言葉を耳にすることがあります。例えば今年(2018年)のVMworld U.S. Day 1 の General Session で、CEO の Pat Gelsinger が “VMware is the Daltone for Kubernetes” というフレーズを使っていました。最近の若者には馴染みがないかもしれませんが、この “Dialtone”、いわゆる電話の受話器をあげた時にするあの「ツーー」という音のことです。Pat も壇上で「20代の人には分からないかもしれないけど・・」とジョークを飛ばしてしましたね。

私は思い切り “元祖 Dialtone” 世代な人間ですが、上記のような新しい使われ方をした “Dialtone” という言葉を聞くたびに、何となく意味が分かるような分からないようなで、眠れない日々を送っていました(嘘)。このままではいかん、ってことで、この Dialtone ってのは一体どういう意味なんだ、と上司(Bruce Davie)に聞いてみたところ明快な説明をしてくれました。最近の Dialtone という言葉は、「世界中どこにいても普遍的(ubiquitous)に存在していて、誰しもがそれの意味を理解していて使うことができる」ということを比喩的に表しているんだそうです。確かに昔の電話の「ツーー」という音も、ほぼグローバルで共通で皆がその意味を理解できていたと思います。別の Dialtone の例としては、例えば HTTP is the dialtone for the Internet なんて言い方もできるそうです(HTTP はほぼインターネット上の共通言語なので)。

なるほど、これですっきり眠れるようになりました!(笑)

画像クレジット:unsplash-logoPaweł Czerwiński