DockerやKubernetesを使ってFaaS環境を構築できる「OpenFaaS」
ここ数年「FaaS」というサービス運用アーキテクチャが注目されている。しかし、FaaSではサービスを提供するプロバイダによって大きく仕様が異なり、相互運用性の面ではまだ課題があった。OpenFaaSはこういった課題を解決できる可能性があり、またオンプレミスでFaaSを利用したいという需要にも答えるものだ。今回はこのOpenFaaSの環境構築や、基本的な使い方を紹介する。
ロックインなしでFaaSを実現できる
前回記事では、ここ数年注目されているプラットフォームである「FaaS」について紹介した。FaaSは小規模なサービスを実装するには便利なプラットフォームだが、サービスを提供するプロバイダによって仕様が大きく異なる。そのため、利用するサービスの仕様に関する知識が必要なほか、もし利用するサービスを変えようとした場合、コードの修正も必要となる。
そういった背景で登場したのが、Dockerを使ったFaaSのような環境を目指す「OpenFaaS」というプラットフォームだ。OpenFaaSはオープンソースで開発されているFaaSプラットフォームで、リクエストに応じてコードを実行、状況に応じて自動的にインスタンスをスケールさせるといった、FaaSで実現される機能を実装している。
OpenFaaSはDockerやKubernetesが利用できる環境であればどこでも利用できるため、自前で管理するサーバー上だけでなく、各社のクラウドサービス上で利用することも可能だ。
ただ、OpenFaaSはあくまでコンテナ上でFaaS環境を構築するというものであり、そのためFaaSの特徴の1つである、コードを実行していない時間・リクエストを処理していないには課金されないというメリットは享受できない。とはいえ、開発コストの削減目的でFaaSを利用したい、という場合には十分実用的だろう。
OpenFaaSの構成
OpenFaaSは次の3つの要素から構成されている。
- Function Watchdog
- API Gateway / UI Portal
- CLI
Function Watchdogは、処理を実行するプログラム(以下、「ファンクション」と呼ぶ)の起動・終了などの管理を行うコンポーネントだ。リクエストを受けるHTTPサーバーもここに含まれている。Function Watchdogはリクエストを受信すると、ファンクションを起動してリクエストに含まれた情報をファンクションに与える。ファンクションがそれを処理して何らかの結果を出力すると、レスポンスとしてリクエストの送信元にそれを返すようになっている。
API Gateway / UI Portalは、OpenFaaS自体の管理を行うコンポーネントだ。Webブラウザから操作できるユーザーインターフェイスと、このユーザーフェイスやコマンドラインインターフェイス(CLI)から各種操作を実行するために使われるAPIエンドポイントを提供する。そのほか、各種統計情報の管理や、クラスタ外からファンクションにリクエストを送信するために利用できるゲートウェイといった機能も提供する。統計情報の集積にはモニタリングツールであるPrometheusを使用している。
CLIはコマンドラインでOpenFaaSクラスタの管理をおこなったり、ファンクションのデプロイやビルドなどを実行したりするツールだ。必ずしも必須ではないが、これによってアプリケーション開発などを効率化できる。
OpenFaaSで利用できる言語
OpenFaaSではファンクションとのやり取りに標準入力および標準出力を利用できるため、さまざまな言語でファンクションを実装できる。また、PythonやGo言語など、特定の言語向けにコードのひな形をテンプレートから生成する機能も用意されている。
OpenFaaS環境を構築してみる
それでは、実際にOpenFaaS環境を構築してシンプルなファンクションを実行する例を紹介していこう。
前提となる環境
OpenFaaSは、KubernetesもしくはDocker Swarmクラスタ上で利用できる。要件としてはKubernetesを利用する場合はKubernetes 1.8もしくは1.9、Docker Swarmを利用する場合はDocker 17.06以降となっている。今回はFedora 28上でKubernetes 1.9.3を使って構築した環境を使って検証を行っている。Dockerのバージョンは1.13.1だ。
$ kubectl version Client Version: version.Info{Major:"1", Minor:"9", GitVersion:"v1.9.3", GitCommit:"d2835416544f298c919e2ead3be3d0864b52323b", GitTreeState:"archive", BuildDate:"2018-02-13T11:42:06Z", GoVersion:"go1.10rc2", Compiler:"gc", Platform:"linux/amd64"} Server Version: version.Info{Major:"1", Minor:"9", GitVersion:"v1.9.3", GitCommit:"d2835416544f298c919e2ead3be3d0864b52323b", GitTreeState:"archive", BuildDate:"2018-02-13T11:42:06Z", GoVersion:"go1.10rc2", Compiler:"gc", Platform:"linux/amd64"} $ docker -v Docker version 1.13.1, build c301b04-unsupported
また、Kubernetesクラスタ上でDNS(kube-dns)が利用できるよう設定されている必要がある。
さて、OpenFaaSを構成するコンポーネントはDocker上で動作するコンテナとして提供されており、Kubernetes上で動作させるための設定ファイルがGitHub上で公開されている。次のようにこのリポジトリをクローンし、含まれるYAMLファイルからデプロイを行えば良い。
$ git clone https://github.com/openfaas/faas-netes $ cd faas-netes $ kubectl apply -f ./namespaces.yml,./yaml
この場合、OpenFaaSのコンポーネントは「openfaas」というネームスペース上で動作するようになっている。
$ kubectl -n openfaas get deploy NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE alertmanager 1 1 1 1 32m faas-netesd 1 1 1 1 32m gateway 1 1 1 1 32m nats 1 1 1 1 32m prometheus 1 1 1 1 32m queue-worker 1 1 1 1 32m
デプロイが完了すると、「gateway」コンポーネントが提供する「OpenFaaS Portal」にWebブラウザからアクセスできるようになる。OpenFaaS PortalはOpenFaaSの設定やファンクションの管理をWebブラウザから行えるユーザーインターフェイスだ。
デフォルトの設定では次のようにKubernetesのNodePort機能を利用して外部から31112番ポートでOpenFaaS PortalにアクセスできるようServiceが設定されている。
$ kubectl -n openfaas get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE alertmanager ClusterIP 10.254.58.247 <none> 9093/TCP 1d faas-netesd ClusterIP 10.254.36.18 <none> 8080/TCP 1d gateway NodePort 10.254.49.118 <none> 8080:31112/TCP 1d nats ClusterIP 10.254.202.183 <none> 4222/TCP 1d prometheus NodePort 10.254.180.15 <none> 9090:31119/TCP 1d
実際に「https://<KubernetesクラスタノードのIPアドレス>:31112/」というURLにWebブラウザでアクセスすると、次のような画面が表示される(図1)。
なお、デフォルトでは特に認証などによるアクセス制限は設けられていないため注意したい。外部から自由にアクセスできるサーバーで運用する場合は適宜アクセス制限などの対応が必要だろう。
ファンクションをデプロイする
OpenFaaS Portalの「Deploy New Function」リンクをクリックすると、新たにファンクションをデプロイできる。OpenFaaS Portalでは独自に作成したファンクションだけでなく、あらかじめ登録されているファンクションを選んでデプロイできる機能もある(図2)。
たとえば、ドメインやURLに対応するIPアドレスを返すファンクションである「nslookup」(「nslookup_faas」として公開されている)を選択して「DEPLOY」をクリックすると、このファンクションがデプロイされ、その稼動状況がOpenFaaS Portalから確認できるようになる(図3)。
デプロイしたファンクションを削除するには、この画面で右上に表示されているゴミ箱アイコンをクリックすれば良い。
ちなみに、デプロイしたファンクションはKubernetes上では「openfaas-fn」というネームスペース上にdeployment(deploy)として作成される。
$ kubectl -n openfaas-fn get deploy NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE nslookup 1 1 1 1 11m $ kubectl -n openfaas-fn get pod NAME READY STATUS RESTARTS AGE nslookup-758f6c8bd4-gqdxf 1/1 Running 0 11m
デプロイしたファンクションのテスト
OpenFaaS Portalでは、デプロイしたファンクションのテストも実行できる。OpenFaaS Portal上でデプロイしたファンクションを選択すると、「Invoke function」というフォームが表示されるので、ここで「Request body」に送信したいリクエストを入力して「INVOKE」ボタンをクリックすると、それがファンクションに送信され、その結果が「Response body」欄に表示される。
たとえば先ほどデプロイしたnslookupファンクションは、送信した文字列を引数としてnslookupコマンドを実行し、その結果を返すという動作を行う。「Request body」に適当なドメインを入力して「INVOKE」をクリックすると、nslookupを実行した結果が「Response body」欄に表示される(図4)。
任意のHTTPクライアントからリクエストを送ることも可能だ。ファンクションをデプロイすると自動的にKubernetesのService(svc)が作成されるので、クラスタ内からはこのServiceのcluster-IP宛にリクエストを送れば良い。
たとえば先に紹介したnslookupファンクションの場合、次のようにServiceが作成されていた。この場合、「10.254.53.135」の「8080」番ポートにリクエストを送信すれば良い。
$ kubectl -n openfaas-fn get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nslookup ClusterIP 10.254.53.135 <none> 8080/TCP 13m
curlコマンドで次のようにしてリクエストを送信すると、OpenFaaS Portalで実行したのと同様の結果が返ってくる。
$ curl -X POST http://10.254.53.135:8080 -d "osdn.jp" Name: osdn.jp Address 1: 202.221.179.11 osdn.jp
Kubernetesクラスタ外からは「http://<KubernetesクラスタにアクセスできるIPアドレスもしくはホスト名>:31112/function/<ファンクション名>」というURLを使ってリクエストを送信できる。
$ curl -X POST http://<KubernetesクラスタにアクセスできるIPアドレスもしくはホスト名>:31112/function/nslookup -d "osdn.jp" Name: osdn.jp Address 1: 202.221.179.11 osdn.jp
CLIを使ってファンクションをデプロイする
OpenFaaSではコマンドラインからファンクションのデプロイなどを実行するためのCLI(Command Line Interface)である「faas-cli」も用意されている。このCLIはGitHubのリリースページで公開されているので、ここから利用するプラットフォームに対応するものをダウンロードし、必要に応じて実行権限を付けたりパスの通ったディレクトリにコピーすれば良い。
faas-cliコマンドでは、「faas-cli store list」コマンドで登録済みのファンクションを表示できる。
$ faas-cli store list FUNCTION DESCRIPTION Colorization Turn black and white photos to color ... Inception This is a forked version of the work ... Figlet OpenFaaS Figlet image. This repositor... NodeInfo Get info about the machine that you'r... Dockerhub Stats Golang function gives the count of re... Tesseract OCR This function brings OCR - Optical Ch... QR Code Generator - Go QR Code generator using Go Nmap Security Scanner Tool for network discovery and securi... YouTube Video Downloader Download YouTube videos as a function OpenFaaS Text-to-Speech Generate an MP3 of text using Google'... SSL/TLS cert info Returns SSL/TLS certificate informati... nslookup Uses nslookup to return any IP addres... mememachine Turn any image into a meme. Left-Pad left-pad on OpenFaaS Docker Image Manifest Query Query an image on the Docker Hub for ... SentimentAnalysis Python function provides a rating on ...
登録されているファンクションのデプロイは「faas-cli store deploy <ファンクション名>」コマンドで行える。このとき、「--gateway <ホスト名/IPアドレス>:<ポート番号>」でOpenFaas Portalにアクセスできるホスト/IPアドレスの情報を指定する必要がある。
$ faas-cli store deploy nslookup --gateway <ホスト名/IPアドレス>:31112 Deployed. 202 Accepted. URL: http://<ホスト名/IPアドレス>:31112/function/nslookup
また、「faas-cli invoke <ファンクション名>」コマンドでファンクションをテストできる。このコマンドの標準入力に与えた値がファンクションに送信され、その結果が標準出力に表示される。たとえばnslookupファンクションを「osdn.jp」という文字列を与えて実行するには次のようにする。なお、この場合も「--gateway <ホスト名/IPアドレス>:<ポート番号>」でOpenFaas Portalにアクセスできるホスト/IPアドレスの情報を指定しておく。
$ echo osdn.jp | faas-cli invoke nslookup --gateway <ホスト名/IPアドレス>:31112 Name: osdn.jp Address 1: 202.221.179.11 osdn.jp
デプロイされたファンクションのリストは「faas-cli list」コマンドで確認できる。
$ ./faas-cli list --gateway <ホスト名/IPアドレス>:31112 Function Invocations Replicas nslookup 3 1
デプロイ済みファンクションの削除は「faas-cli remove <ファンクション名>」で行える。
$ ./faas-cli remove nslookup --gateway <ホスト名/IPアドレス>:31112 Deleting: nslookup. Removing old function.
ファンクションを作成する
続いては、OpenFaaSを使って独自のファンクションを作成する流れを紹介しよう。OpenFaaSでは、リクエストとレスポンスを管理するための「Watchdog」という仕組みが提供されている。
Watchdogは受け取ったHTTPリクエストのパースや実際に処理を実行するプログラムの起動、レスポンスの送信といった処理を実行するプログラムだ。Watchdogはプログラムの起動後、そのプログラムにHTTPリクエストとして受け取ったパラメータを送信し、続いてプログラムが出力したデータをHTTPレスポンスとして送信する。これによって開発者はデータの入出力だけに注力できるようになる。
ちなみに、OpenFaaSではDockerやKubernetesと同様、コンテナイメージ単位でファンクションを管理する。そのため、作成したファンクションをOpenFaaSに実際にデプロイするには処理を実行するプログラムをWatchdogとともに格納し、Watchdogがプログラムを呼び出せるよう適切に設定したコンテナイメージを作成する必要がある。
OpenFaaSのCLI(faas-cli)では、このコンテナイメージ作成に必要な設定ファイルを、テンプレートを使って自動作成する機能があるので、以下ではこの機能を使ってシンプルなファンクションを作成してみよう。
テンプレートの準備
faas-cliコマンドでテンプレート機能を利用するには、まず適当なディレクトリで「faas-cli template pull」コマンドを利用して最新のテンプレートをダウンロードする。このコマンドを実行すると、次のように作業ディレクトリ内に「template」というディレクトリが作成され、そこにテンプレートファイルが格納される。
$ faas-cli template pull Fetch templates from repository: https://github.com/openfaas/templates.git 2018/05/18 18:23:09 Attempting to expand templates from https://github.com/openfaas/templates.git 2018/05/18 18:23:10 Fetched 12 template(s) : [csharp dockerfile go go-armhf node node-arm64 node-armhf python python-armhf python3 python3-armhf ruby] from https://github.com/openfaas/templates.git $ ls template
続いて、「faas-cli new」コマンドを使ってテンプレートから設定ファイルなどのひな形を作成する。このとき、「--lang <言語名>」オプションで使用する言語を指定する必要がある。指定できる言語は「faas-cli new --list」コマンドで確認できる。
$ faas-cli new --list Languages available as templates: - csharp - dockerfile - go - go-armhf - node - node-arm64 - node-armhf - python - python-armhf - python3 - python3-armhf - ruby
なお、サポートされている言語について詳しくはOpenFaaSのドキュメントにまとめられているので、そちらも参照して欲しい。
コマンドを実行するファンクションを作成する
それでは、まずはシンプルなファンクションの例として、与えられた入力をsortコマンドでソートして返す、というファンクションを作成してみよう。このようにコマンドを実行させてその結果を返すというファンクションを作成する場合、「--lang」オプションで「dockerfile」を指定すれば良い。また、ここでは作成するファンクション名を「sort」とした。
$ faas-cli new sort --lang dockerfile Folder: sort created. ___ _____ ____ / _ \ _ __ ___ _ __ | ___|_ _ __ _/ ___| | | | | '_ \ / _ \ '_ \| |_ / _` |/ _` \___ \ | |_| | |_) | __/ | | | _| (_| | (_| |___) | \___/| .__/ \___|_| |_|_| \__,_|\__,_|____/ |_| Function created in folder: sort Stack file written: sort.yml
コマンドを実行すると、作業ディレクトリ内に指定したファンクション名を持つディレクトリと、「<ファンクション名>.yml」というファイルが作成され、前者には指定したプログラミング言語でファンクションを実装するためのひな形となるファイルが格納される。
今回の例の場合、「sort」というディレクトリと「sort.yml」というファイルが作成される。sortディレクトリ内には「Dockerfile」ファイルが用意されており、ここに実行するコマンドなどの情報を記述したり、コンテナ内に追加するファイルなどを記述したりする。
今回はサンプルということで、標準入力に入力されたデータをソートして標準出力に出力する次のようなシェルスクリプト(sort.sh)をsortディレクトリ内に作成した。
#!/bin/sh cat - | sort
続いて、sortディレクトリ内のDockerfileを編集し、このsort.shスクリプトを作成するコンテナ内にコピーする処理と、実行するコマンドを指定する処理を追加する。次のリスト中、太字の部分が追加した部分だ。
FROM alpine:3.7 # 1. Use any image as your base image, or "scratch" # 2. Add fwatchdog binary via https://github.com/openfaas/faas/releases/ # 3. Then set fprocess to the process you want to invoke per request - i.e. "cat" or "my_binary" #ADD https://github.com/openfaas/faas/releases/download/0.8.0/fwatchdog /usr/bin #RUN chmod +x /usr/bin/fwatchdog RUN mkdir -p /home/app RUN apk --no-cache add curl \ && echo "Pulling watchdog binary from Github." \ && curl -sSL https://github.com/openfaas/faas/releases/download/0.8.0/fwatchdog > /usr/bin/fwatchdog \ && chmod +x /usr/bin/fwatchdog \ && cp /usr/bin/fwatchdog /home/app \ && apk del curl --no-cache ↓実行するスクリプトを追加する ADD sort.sh /home/app/sort.sh RUN chmod +x /home/app/sort.sh # Add non root user RUN addgroup -S app && adduser -S -g app app RUN chown app /home/app WORKDIR /home/app USER app # Populate example here - i.e. "cat", "sha512sum" or "node index.js" ↓fprocess環境変数で実行するコマンドを指定する #ENV fprocess="cat" ENV fprocess="/home/app/sort.sh # Set to true to see request in function logs ENV write_debug="false" HEALTHCHECK --interval=5s CMD [ -e /tmp/.lock ] || exit 1 CMD [ "fwatchdog" ]
続いて「faas-cli new」コマンドを実行したディレクトリ(sort.ymlファイルが存在するディレクトリ)で「faas-cli build」コマンドを実行すると、コンテナの作成が行われる。
$ ./faas-cli build -f ./sort.yml [0] > Building sort. Building: sort with Dockerfile. Please wait.. Sending build context to Docker daemon 4.096 kB Step 1/13 : FROM alpine:3.7 ---> 3fd9065eaf02 : : Removing intermediate container d334da38982f Successfully built 19a60337d628 Image: sort built. [0] < Building sort done. [0] worker done.
作成されたイメージをデプロイするには、これをKubernetesからアクセスできるコンテナイメージリポジトリにプッシュしておく必要がある。今回の例の場合、「sort」という名称でイメージが作成されているので、これに適切にタグを付けて「docker push」コマンドを実行する。なお、今回はパブリックリポジトリサービスであるDockerHub上を利用しているが、もしプライベートリポジトリを利用したい場合、Kubernetesからそのリポジトリにアクセスできるよう設定を行っておく必要がある。
↓「sort」という名前でイメージが作成されている $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE sort latest 19a60337d628 11 minutes ago 13.1 MB ↓DockerHubにログインする $ docker login Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one. Username: <ユーザー名> Password: <パスワード> Login Succeeded ↓タグを付けてプッシュする $ docker tag sort <ユーザー名>/sort $ docker push <ユーザー名>/sort The push refers to a repository [docker.io/<ユーザー名>/sort] 3e0c67c23573: Pushed 0c5d6e20706a: Pushed 2c4be372ef98: Pushed 6fe49c1140af: Pushed 4c20f56aa0ab: Pushed fc76639c0830: Pushed cd7100a72410: Mounted from library/alpine latest: digest: sha256:a01f080c597ef8fc8ba7e8c0ce1487e2c345357923242d7dfed09f270e89266f size: 1775
これでファンクションをデプロイするための準備は完了だ。OpenFaaS Portal、もしくはfaas_cliコマンドを使い、プッシュしたイメージを指定してファンクションをデプロイすれば良い(図5)
デプロイの完了後、OpenFaaS Portal上から適当な文字列を与えてテストを実行してみると、レスポンスとして与えた文字列をソートした結果が返ってくることが確認できる(図6)。
Pythonを使ってファンクションを作成する
OpenFaaSでは標準入出力経由でデータをやり取りするプログラムだけでなく、各言語に特化した形でデータをやり取りするプログラムを利用できる。続いては、Python(Python 3系)を使ってファンクションを作成してみよう。この場合、「faac-cli new」コマンドのオプションには「--lang python3」を指定すれば良い。
ここでは、入力として与えられた数値の平方根を返すファンクションを作成してみよう。ファンクション名は「sqrt」とする。この場合、次のようにfaas-cliコマンドを実行することになる。
$ ./faas-cli new sqrt --lang python3 Folder: sqrt created. ___ _____ ____ / _ \ _ __ ___ _ __ | ___|_ _ __ _/ ___| | | | | '_ \ / _ \ '_ \| |_ / _` |/ _` \___ \ | |_| | |_) | __/ | | | _| (_| | (_| |___) | \___/| .__/ \___|_| |_|_| \__,_|\__,_|____/ |_| Function created in folder: sqrt Stack file written: sqrt.yml
今回の例の場合、「sqrt」というディレクトリと「sqrt.yml」というファイルが作成される。ここで作成されたsqrtディレクトリ内にある「handler.py」に実際に処理する内容を記述すれば良い。また、「requirements.txt」にはファンクションの実行に必要なモジュール(pipモジュール)を記載しておく。ただし、今回は特に外部モジュールには依存しないため、この作業は省略している。
さて、自動生成されたhandler.pyファイルには、次のように「handle()」関数が実装されている。この関数の引数にはリクエストとして受け取ったパラメータが渡され、さらにこの関数の戻り値がレスポンスとして返されるようになっている。
def handle(req): """handle a request to the function Args: req (str): request body """ return req
今回はこのhandler.pyを次のように編集した。
import math def handle(req): """handle a request to the function Args: req (str): request body """ try: res = math.sqrt(float(str)) except ValueError: # 例外が発生したら「NaN」という文字列を返す return "NaN" return res
コードの実装が終わったら、あとは先ほどと同様に「faas-cli build」コマンドを実行してイメージを作成してリポジトリにプッシュする。
↓イメージを作成する $ ./faas-cli build -f ./sqrt.yml [0] > Building sqrt. Clearing temporary build folder: ./build/sqrt/ Preparing ./sqrt/ ./build/sqrt/function Building: sqrt with python3 template. Please wait.. Sending build context to Docker daemon 7.68 kB Step 1/20 : FROM python:3-alpine : : Successfully built cef01fcf930e Image: sqrt built. [0] < Building sqrt done. [0] worker done. ↓イメージにタグを付けてプッシュする $ docker tag sqrt hylom/sqrt $ docker push hylom/sqrt
以上でOpenFaaSへのデプロイが可能になる。デプロイしてOpenFaaS Portal上でテストしてみると、適切に動作していることが分かる(図7)。
OpenFaaSまとめ
このように、OpenFaaSではKubernetesクラスタ上で簡単にFaaSのような仕組みを実現できる。独自のファンクションを実装するのも容易で、とりあえずFaaSという仕組みを体験してみたい、という開発者にもおすすめできる。
ただし、OpenFaaSではGoogleやAmazonなどが提供するFaaSプラットフォームとは異なり、ファンクションをデプロイするとその時点でコンテナが作成される。プログラム自体はリクエストに応じて起動/終了されるのだが、たとえばKubernetesクラスタをクラウドサービス上で構築していた場合、このアーキテクチャでは「リクエスト数に応じた料金支払い」は実現できない。自前でプラットフォームを管理するためのコストもかかる。
また、注目されているプロジェクトではあるものの、最初のリリースが行われたのは2017年というまだ比較的歴史の浅いプロジェクトであり、十分な導入実績はまだ足りない状況だ。そのため、実運用での導入にはまだ注意したほうが良いだろう。