Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
81
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Go + WebAssembly に入門して Dockerfile の依存グラフを図にしてくれるサービスを作ったので、知見とハマりポイントを共有します。

こんにちはpo3rinです。 「Go + WebAssembly + BuildKit」 で Dockerfile の依存グラフを図にしてくれる誰得サイトを作ったので紹介します。その名も「Dockerfile Dependency graph」!!!

:whale: https://po3rin.github.io/dockerdot/ :whale:
(PCブラウザだけ対応してます)

今回はこれをどのように作ったのかの共有とハマった点を紹介します。リポジトリはこちら!!

どのように作ったか

全体像はこちらになります。

all.png

内部では Dockerfile から LLB(プロセスの依存関係グラフを定義するために使用されるバイナリ中間言語)を取得して、それをdot言語(データ記述言語)に変換しています。今回はその処理を Go + WebAssembly で書いています。WebAssemblyの基本的な使い方に関してはこちらをご覧ください!Hello Worldから解説してくれます!https://github.com/golang/go/wiki/WebAssembly

もちろん内部ではBuildKitのパッケージを利用しています。コード内でDockerfileをdot言語に変換する関数を記述しています。実装はBuildKitの非公開関数を使えるようにしただけです。

// Docker2Dot convert dockerfile to llb Expressed in DOT language.
func Docker2Dot(df []byte) ([]byte, error)

Dockerfileのバイト列を渡せばそのDockerfileから生成した依存を記述したdot言語が所得できます。

Go + WebAssembly が Dockerfile を dot言語に変換したら後は JavaScript 側で viz.js を使ってグラフにしています。viz.jsは、dot言語で記述された構造からグラフを作成するためのパッケージです。下記のように使います。

let viz = new Viz()
graph = document.getElementById("graph")
showGraph = (dot) => {
    viz.renderSVGElement(dot).then((element)=> {
        if (graph.lastChild){
            graph.removeChild(graph.lastChild)
        }
        graph.appendChild(element)
    })
}

これでshowGraph関数が、引数のdotにdot言語で記載されたLLBを渡されることで依存グラフにしてくれます。

ハマったところ

OSやアーキテクチャ固有の機能に依存しているパッケージは使えない。

BuildKitは内部でhttp://golang.org/x/sys/unixを使っていたので、最初、wasmのビルドに失敗しました。

Twitterでボヤいていたところ、wasmの鬼の @__syuumai さんとGoの鬼の @tenntenn さんにアドバイスいただきました。wasmをビルドするときはGOOS=js GOARCH=wasmなのでビルドタグでビルド対象から外されてしますようです。

Twitterでの会話はこちら
https://twitter.com/po3rin/status/1139568570239635456

よって今回は moby/buildkit の中からOSやアーキテクチャ固有の機能に依存している処理を使わないようにmoby/buildkitのコードから必要部分だけをmirrorして使っています。

内部でゴールーチンを読んでいるパッケージの処理をコールバック関数で呼ぶとデッドロックが起きる

こちらのコードラボで注釈されている問題に見事ハマりました。

コールバック関数は1つのゴールーチンの中で1つずつ処理されます。そのため、コールバック関数の中で、コールバック関数によって結果を受け取るような処理があるとデッドロックが起きてしまいます。

moby/buildkit では sync.ErrGroup でバリバリ並行処理が行われていたのでデッドロックが起きていました。そのため別のゴールーチンを起動して呼び出す必要がありました。

func registerCallbacks() {
    var cb js.Func
    document := js.Global().Get("document")
    element := document.Call("getElementById", "textarea")

    cb = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
        text := element.Get("value").String()
        dockerfile := []byte(text)

        // https://github.com/golang/go/issues/26382
        // should wrap func with goroutine.
        go func() {
            dot, err := docker2dot.Docker2Dot(dockerfile)
            if err != nil {
                fmt.Println(err)
            }
            showGraph := js.Global().Get("showGraph")
            showGraph.Invoke(string(dot))
        }()
        return nil
    })

    js.Global().Get("document").Call("getElementById", "button").Call("addEventListener", "click", cb)
}

初めて触った所感

最初はハマってましたが、慣れてきたらJavaScriptからGoの処理を呼ぶ簡単さに感動を覚えます。そしてGoで書いたのにデプロイが楽チンというのも気持ちよかったです。

今回の開発で参考にした記事

だいたいここ読んでおけば良い

go wiki: WebAssembly
https://github.com/golang/go/wiki/WebAssembly
(公式による解説)

Go 1.11: WebAssembly for the gophers
https://medium.zenika.com/go-1-11-webassembly-for-the-gophers-ae4bb8b1ee03
(syscall/jsの解説が充実している)

GoでWebAssemblyに触れよう
https://golangtokyo.github.io/codelab/go-webassembly/?index=codelab#0
(ハンズオンとして最適)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
81
Help us understand the problem. What are the problem?