Clojure と Leiningen と時々 Container 〜そしてデプロイへ〜
この記事は Clojure Advent Calendar 2016 の19日目の記事です。
Clojure を一度も使ったことが無かったのですが、少しは触ってみようと思い、今回 Clojure Advent Calendar 2016 に参加させてもらいました。
書くにあたり、凝ったことなんて全くできるはずもないので、自分らしい記事を書こうと思います。プログラミングには熟れており、これから初めて Clojure 触ってみる人の足がかりになるような記事になればと思います。
環境整備〜開発
とにかく、開発するなら Clojure を実行できなければダメだろうということで、Docker コンテナのイメージを使って楽に環境整備することに決めました。
Docker container
ここにあるコンテナイメージを pull して使用します。
$ docker pull clojure
タグでよく使うのは Description に記載されている通りすかね。
- latest, lein-2.7.1
- onbuild, lein-2.7.1-onbuild
- alpine, lein-2.7.1-alpine
- alpine-onbuild, lein-2.7.1-alpine-onbuild
コンテナイメージで不要なものが削減されたものを使用したければ alpine
タグのを、実行するイメージなら onbuild
タグのものを使用します。
まずは実行してみたいので、何も考えずに latest
で取得して実行します。
$ docker pull clojure:latest $ docke run -it -v ~/.clojure:/data-share clojure /bin/bash
これでインタラクティシェルでアタッチします。
Leiningen
アタッチしても Clojure の動かし方がわからなかったのですが、 /usr/local/bin
に謎の lein
というコマンドがあるので、「こいつが肝なんだろうな」とピンときました。
$ lein -h | head -n1 Leiningen is a tool for working with Clojure projects.
調べてみたところ、本当に色々やってくれるやつらしいので、こいつに乗っかることにします。

< Clojure はワシが育てた。
さて、作業ディレクトリですが、私は Docker 起動時に /data-share
というディレクトリをマウントしているので、そこで作業するとします。
まず、 lein new app
でアプリケーションの雛形を作成します。
$ cd /data-share
$ lein new app hello-world
次に、 lein run
で実行します。 勝手に色々とやってくれるので便利ですね。
$ cd hello-world $ lein run Retrieving org/clojure/clojure/1.8.0/clojure-1.8.0.pom from central Retrieving org/sonatype/oss/oss-parent/7/oss-parent-7.pom from central Retrieving org/clojure/tools.nrepl/0.2.12/tools.nrepl-0.2.12.pom from central Retrieving org/clojure/pom.contrib/0.1.2/pom.contrib-0.1.2.pom from central Retrieving clojure-complete/clojure-complete/0.2.4/clojure-complete-0.2.4.pom from clojars Retrieving org/clojure/tools.nrepl/0.2.12/tools.nrepl-0.2.12.jar from central Retrieving org/clojure/clojure/1.8.0/clojure-1.8.0.jar from central Retrieving clojure-complete/clojure-complete/0.2.4/clojure-complete-0.2.4.jar from clojars Hello, World!
もう Hello world
出ちゃいました。
REPL
Clojure でなく、 Leiningen でも REPL が起動できるんですね。なんかすごい。
$ lein repl nREPL server started on port 59269 on host 127.0.0.1 - nrepl://127.0.0.1:59269 REPL-y 0.3.7, nREPL 0.2.12 Clojure 1.8.0 OpenJDK 64-Bit Server VM 1.8.0_111-8u111-b14-2~bpo8+1-b14 Docs: (doc function-name-here) (find-doc "part-of-name-here") Source: (source function-name-here) Javadoc: (javadoc java-object-or-class-here) Exit: Control+D or (exit) or (quit) Results: Stored in vars *1, *2, *3, an exception in *e hello-world.core=> (-main) Hello, World! nil
完璧じゃないっすか…!と、いうことで、ここで終わりにしたいと思ったのですが、Clojure 使いの人から…
・
・
・
・
・
・
Webアプリケーション を デプロイ とな。笑止。
duct on GKE
duct については紹介してもらった Qiita の記事を、GKE については自分の記事を参考にしています。
duct
Leiningen にテンプレートがあるので、そちらから作成します。
+example
は example というエンドポイントをリソースとして用意してくれるようです。
$ lein new duct myduct +example +cljs
ローカルの設定ファイルを生成するために、セットアップを行います。
$ lein setup
次に、サーバーを起動するために REPL を起動します。Clojure の REPL 文化は素晴らしいですね。
$ lein repl nREPL server started on port 53589 on host 127.0.0.1 - nrepl://127.0.0.1:53589 REPL-y 0.3.7, nREPL 0.2.12 Clojure 1.8.0 OpenJDK 64-Bit Server VM 1.8.0_111-8u111-b14-2~bpo8+1-b14 Docs: (doc function-name-here) (find-doc "part-of-name-here") Source: (source function-name-here) Javadoc: (javadoc java-object-or-class-here) Exit: Control+D or (exit) or (quit) Results: Stored in vars *1, *2, *3, an exception in *e user=> (dev) :loaded dev=> (go) 2016-12-18 01:59:12.911:INFO::nREPL-worker-0: Logging initialized @14796ms Compiling "target/figwheel/myduct/public/js/main.js" from ["src" "dev"]... Successfully compiled "target/figwheel/myduct/public/js/main.js" in 12.965 seconds. notifying browser that file changed: target/figwheel/myduct/public/js/goog/deps.js notifying browser that file changed: target/figwheel/myduct/public/js/cljs_deps.js notifying browser that file changed: out/cljs/user.js :started dev=>
参考:Getting Started · duct-framework/duct Wiki · GitHub
起動したら、 curl などで叩いてみましょう。ポートは3000番で起動しています。
# execute command from outside. # $ docker exec -it [Container Name] curl localhost:3000/example $ curl localhost:3000/example This is an example endpoint
REPL 上で (reset)
すればリロードされるようです。 +cljs
を付与しているので、 ClojureScript もここでコンパイルされるようです。(正直、よくわからない)
dev=> (reset) :reloading (myduct.endpoint.example myduct.main myduct.endpoint.example-test cljs.stacktrace dev user) :resumed dev=>
エンドポイント追加
エンドポイントをひとつ追加してみます。
$ vim src/myduct/endpoint/hello.clj
ファイルの中身は example とほぼ同じですが、Hello 用にカスタマイズしました。
(ns myduct.endpoint.hello (:require [compojure.core :refer :all])) (defn hello-endpoint [config] (context "/hello" [] (GET "/" [] "Hello, World!")))
次に、 system.edn ファイルにエンドポイントを定義します。
$ vim resources/myduct/system.edn
:endpoints
で先ほどの Hello world を定義し、それを :app
に登録します。
{:components {:app #var duct.component.handler/handler-component :http #var ring.component.jetty/jetty-server} :endpoints {:example #var myduct.endpoint.example/example-endpoint :hello #var myduct.endpoint.hello/hello-endpoint} :dependencies {:http [:app] :app [:example :hello] :example []}
ここまでできたら REPL でアプリを一度リセットします。
dev=> (reset) :reloading (myduct.endpoint.hello) Compiling "target/figwheel/myduct/public/js/main.js" from ["src" "dev"]... Successfully compiled "target/figwheel/myduct/public/js/main.js" in 0.227 seconds. :resumed
また curl などで /hello
のパスを叩くと、Hello, World! が返却されると思います。
$ curl localhost:3000/hello
Hello, World!
duct をコンテナ化
ここまでできたらコンテナイメージとして作成し、Google Container Registry にプッシュします。
Dockerfile を duct プロジェクトのルートに置き、下記のように onbuild
タグのコンテナイメージを使って docker build します。
FROM clojure:onbuild # FROM clojure:alpine-onbuild
軽量にしたい場合は alpine-onbuild
を使用してください。
# ビルド $ docker build -t hello-duct . # 実行 $ docker run -p 8080:3000 hello-duct # 確認 $ curl -XGET localhost:8080/hello Hello, World!
Google Container Registry にプッシュします。
# タグのエイリアス $ docker tag hello-duct asia.gcr.io/[YOUR_PROJECT_ID]/hello-duct # Registry にプッシュ $ gcloud docker push asia.gcr.io/[YOUR_PROJECT_ID]/helloworld
ここまでできれば後は下記を参考にして GKE にデプロイすることが可能です。
ClojureScript is 何者?
「まじでわからん。」という状態だったのですが、下記のスライド読んだら大体把握できたのと、「すごくね?」という感情が生まれました。