GCP Cloud Functions で Graphviz を使う

  • このエントリーをはてなブックマークに追加

GCP の Cloud Functions で Graphviz を使うにあたり、試行錯誤したのでメモを残しておきます。

Graphviz とは

Graphviz とは、グラフを描画することができるツールです。グラフと言っても折れ線グラフとか棒グラフではなく、有向グラフとか頂点 (node) とか辺 (edge) とかそっちの方のグラフです。例えば下記のような画像を生成できます。


(Graphviz サイトより拝借)

上記画像はどうやって生成するかと言うと、下記のような dot 言語で書かれたテキストファイルを用意しておきます。

digraph G {
	subgraph cluster_0 {
		style=filled;
		color=lightgrey;
		node [style=filled,color=white];
		a0 -> a1 -> a2 -> a3;
		label = "process #1";
	}
	subgraph cluster_1 {
		node [style=filled];
		b0 -> b1 -> b2 -> b3;
		label = "process #2";
		color=blue
	}
	start -> a0;
	start -> b0;
	a1 -> b3;
	b2 -> a3;
	a3 -> a0;
	a3 -> end;
	b3 -> end;
	start [shape=Mdiamond];
	end [shape=Msquare];
}

上記を sample.dot としてファイルを配置し、下記のように dot コマンドを叩けば out.png が生成完了です。非常に簡単です。

dot -Kdot -Tpng sample.dot -o out.png

Cloud Functions の制限

そして Cloud Functions 上で Graphviz、具体的には dot コマンドを動かしたいわけです。

2019/3 現在、Cloud Functions は基本的には Google が用意した環境でしか動かすことができません。例えば Cloud Functions 内の /bin の下にあるコマンドは下記です (2019/3/28 時点)。これ以外コマンドは、用意されていない、ということです。

bash, bunzip2, bzcat, bzcmp, bzdiff, bzegrep, bzexe, bzfgrep, bzgrep, bzip2, bzip2recover, bzless, bzmore, cat, chgrp, chmod, chown, cp, dash, date, dd, df, dir, dmesg, dnsdomainname, domainname, echo, egrep, false, fgrep, findmnt, grep, gunzip, gzexe, gzip, hostname, ip, kill, less, lessecho, lessfile, lesskey, lesspipe, ln, login, ls, lsblk, mkdir, mknod, mktemp, more, mount, mountpoint, mv, nisdomainname, pidof, ping, ping4, ping6, ps, pwd, rbash, readlink, rm, rmdir, run-parts, sed, sh, sh.distrib, sleep, ss, stty, su, sync, tar, tempfile, touch, true, umount, uname, uncompress, vdir, wdctl, which, ypdomainname, zcat, zcmp, zdiff, zegrep, zfgrep, zforce, zgrep, zless, zmore, znew

つまり Cloud Functions には Graphviz が入っていないので、dot コマンドがありません。

しかしながら Cloud Functions では任意のファイルをアップロードできますし、それをバイナリとして実行することもできます。そこで dot コマンドを持ち込んでみることにしました。

簡単な手順

Graphviz を動かすだけの一番シンプルな手順は、Cloud Shell を起動して、下記を実行します。

git clone https://github.com/68user/gcp-cloud-functions-graphviz-sample.git
cd gcp-cloud-functions-graphviz-sample
gcloud functions deploy do_graphviz --runtime python37 --trigger-http

しばらく待つと、下記のように URL が表示されるので、ブラウザでアクセスし、グラフ画像が表示されれば OK です。

Deploying function (may take a while - up to 2 minutes)...done.                                                            
availableMemoryMb: 256
entryPoint: do_graphviz
httpsTrigger:
  url: https://us-central1-myprojectxxxx.cloudfunctions.net/do_graphviz
(略)

詳細手順

上記の簡単手順には、graphviz.tar という当ページ管理人が作成したファイルが含まれています。以下、その作り方も含めて以下説明します。

まず、GCP の Cloud Shell を起動し、作業用ディレクトリを作成し、移動します。

mkdir ~/buildwork
cd ~/buildwork

Graphviz のソースをダウンロードし、展開します。

wget https://graphviz.gitlab.io/pub/graphviz/stable/SOURCES/graphviz.tar.gz
tar xf graphviz.tar.gz
(この時点では graphviz-2.40.1 だったので、ディレクトリ graphviz-2.40.1/ が展開される)
cd graphviz-2.40.1/

ビルドします。通常なら ./configure && make && sudo make install でいいのですが、普通にコンパイルすると /usr/lib や /usr/bin にインストールする形になります。これでなにが困るかと言うと、Cloud Functions で書き込みができるのは /tmp だけなので、/usr/lib や /usr/bin にファイルをコピーすることができません (/usr は read only でマウントされている)。

そこで ./configure –prefix=/tmp/graphviz && make && make install として、インストール先を /tmp/graphviz/ にします。

sudo apt-get install -y libgd-dev
sudo apt-get install -y flex
./configure --prefix=/tmp/graphviz
make
make install

configure 前に libgd-dev と flex をインストールしているのは、libgd-dev なしだと PNG 生成ができない dot コマンドが生成されてしまうためです。具体的にlibgd-dev なしでビルドすると、下記のエラーが出ます。

% dot -Kdot -Tpng sample.dot -o out.png
Format: "png" not recognized. Use one of: canon cmap cmapx cmapx_np dot dot_json eps fig gv imap imap_np ismap json json0 mp pic plain plain-ext pov ps ps2 svg svgz tk vml vmlz xdot xdot1.2 xdot1.4 xdot_json

flex は字句解析パーサ生成プログラムです (参考)。libgd-dev をインストールした上で make すると下記のエラーが出てビルドできなかったので追加しています。

/home/myuser/buildwork/graphviz-2.40.1/config/missing: line 81: flex: command not found
WARNING: 'flex' is missing on your system.

make install で /tmp/graphviz/ に一式がインストールされました。

Cloud Functions 作成作業用ディレクトリを作成し、移動します。

mkdir ~/functionwork
cd ~/functionwork

Cloud Functions で動かすための main.py を作成します。ここでは Github にあげておいたので、下記でダウンロードしてください。

wget https://raw.githubusercontent.com/68user/gcp-cloud-functions-graphviz-sample/master/main.py

さらに dot コマンドの入力として与える dot ファイルもダウンロードしておきます。

wget https://raw.githubusercontent.com/68user/gcp-cloud-functions-graphviz-sample/master/sample.dot

そしてさきほど生成した /tmp/graphviz から、必要なファイル (bin/ と lib/) を tar で固めて、main.py・sample.dot と同じディレクトリに配置します。

tar cf graphviz.tar -C /tmp graphviz/{bin,lib}

カレントディレクトリは下記のようになっています。

% ls -l
-rw-r--r-- 1 myuser   mygroup  16803840 Mar 28 13:07 graphviz.tar
-rwxr-xr-x 1 myuser   mygroup      1050 Mar 28 13:07 main.py
-rw-r--r-- 1 myuser   mygroup      1353 Mar 28 13:07 sample.dot

なお、main.py スクリプトは、tar xfp ./graphviz.tar -C /tmp でアップロードした tar ファイルを /tmp/graphviz/ に展開した上で、/tmp/graphviz/bin/dot を実行し、生成された画像ファイルをレスポンスとして返す、というものです。

これで必要なファイルは揃ったので、Cloud Functions を作成します。

gcloud functions deploy do_graphviz --runtime python37 --trigger-http

しばらく待つと、下記のように完了するはずです。

Deploying function (may take a while - up to 2 minutes)...done.                                                            
availableMemoryMb: 256
entryPoint: do_graphviz
httpsTrigger:
  url: https://us-central1-myprojectxxxx.cloudfunctions.net/do_graphviz
(略)

上記に表示されている httpsTrigger のところの URL にアクセスすると、下記のように PNG 画像が表示されたら成功です。dot コマンドが sample.dot を元に動的に PNG 画像を生成した、ということです。

まとめ

Cloud Functions に外部コマンドを持ち込む方法を紹介しました。ああめんどくさい。こういうめんどくささを解消するのが Docker です。例えば Azure Functions は「Azure Function on Docker」で独自環境を作成し、Azure Function で動かせます。

AWS Lambda・GCP の Cloud Functions でも、早く Docker 対応してほしいものですね。

おまけ (試行錯誤 & その他)

当初は、Cloud Shell 上で apt-get install graphviz した上で、dot コマンドと動的ライブラリだけコピーし、実行時に LD_LIBRARY_PATH を設定しようとしていました。一応動きはしたのですが、/usr/lib/graphviz/config6a というファイルを見ようとしてエラーになったりで挫折。

また、./configure –enable-static=yes でビルドすると dot_static というバイナリができるのですが、これが静的リンクされたバイナリかと思ったらなぜか動的リンク。何を static にしているのかよくわからず。

さらに、/tmp/graphviz/lib/graphviz/libgvplugin_dot_layout.la というファイルがあって、下記のように書いてあったりする。拡張子 .la とは libtool 用のファイルらしい。どうやらプログラム内部でも動的にリンクしている模様で結局どのライブラリが必要なのかわかりませんでした。

# Libraries that this one depends upon.
dependency_libs=' -L/tmp/graphviz/lib /tmp/graphviz/lib/libgvc.la -L/usr/lib64 -ldl /tmp/graphviz/lib/libxdot.la /usr/lib/x86_64-linux-gnu/libexpat.la -lz /tmp/graphviz/lib/libcgraph.la /tmp/graphviz/lib/libcdt.la /tmp/graphviz/lib/libpathplan.la -lm'

「別の場所でビルドしたバイナリやライブラリが、Cloud Functions で動くのか?」というのは気になります。調査したところ、Cloud Shell は Debian 9 でした (/etc/os-release で確認)。Cloud Functions は Ubuntu 18.04 だそうです。よって「たまたま動いた」という表現が妥当かと思います。

  • このエントリーをはてなブックマークに追加

SNSでもご購読できます。

Leave a Reply

*

CAPTCHA