at kaneshin

Free space for me.

Clojure と Leiningen と時々 Container 〜そしてデプロイへ〜

f:id:laplus-knsn:20161219001217p:plain

この記事は Clojure Advent Calendar 2016 の19日目の記事です。

Clojure を一度も使ったことが無かったのですが、少しは触ってみようと思い、今回 Clojure Advent Calendar 2016 に参加させてもらいました。

書くにあたり、凝ったことなんて全くできるはずもないので、自分らしい記事を書こうと思います。プログラミングには熟れており、これから初めて Clojure 触ってみる人の足がかりになるような記事になればと思います。

環境整備〜開発

とにかく、開発するなら Clojure を実行できなければダメだろうということで、Docker コンテナのイメージを使って楽に環境整備することに決めました。

Docker container

f:id:laplus-knsn:20161215020441p:plain

ここにあるコンテナイメージを 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.

調べてみたところ、本当に色々やってくれるやつらしいので、こいつに乗っかることにします。

f:id:laplus-knsn:20161218104126p:plain



  < 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 使いの人から…

f:id:laplus-knsn:20161218103156p:plain




f:id:laplus-knsn:20161218103645p:plain



Webアプリケーションデプロイ とな。笑止。

duct on GKE

duct については紹介してもらった Qiita の記事を、GKE については自分の記事を参考にしています。

qiita.com

blog.kaneshin.co

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 にデプロイすることが可能です。

blog.kaneshin.co

ClojureScript is 何者?

「まじでわからん。」という状態だったのですが、下記のスライド読んだら大体把握できたのと、「すごくね?」という感情が生まれました。

www.slideshare.net