2022年3月13日日曜日

dockerでclojure cliプロジェクトを動かす


背景

clojure cliのプロジェクト(deps.ednで管理するもの)をdockerで動かす際に、関連ディレクトリをvolumesとして管理しないとbuildし直したら動かなかったり、権限回りでビルドできなかったり苦労したので、備忘録を兼ねて対応内容を共有します。

使ったもの

  • docker
    v20.10.12
  • docker-compose
    v1.29.2
  • clojure cliプロジェクト
    clj-server-practiceのv2022.03が今回試行錯誤したコードです。
    下記のようなディレクトリ構成になっています。
    clj-server-practice
    |- back # サーバーのclojure cliプロジェクトです
    |- front # フロントエンドのclojure script用clojure cliプロジェクトです
    |- docker-compose.yml

clojureプロジェクトの動かし方: /root/.m2をvolumesで管理

下記のようなDockerfileとdocker-compose.ymlでbackディレクトリに配置しているclojureのプロジェクトをdockerで動かせます。
back/Dockerfile
FROM clojure:openjdk-18-bullseye
WORKDIR /usr/src/app/back
docker-compose.yml
version: "3.9"
services:
back:
# image: clojure:openjdk-18-tools-deps-bullseye
# working_dir: /usr/src/app/back
build: ./back
command: "clj -X:run-m"
volumes:
- ./back:/usr/src/app/back
- .volumes/m2:/root/.m2

/root/.m2をvolumesで管理しているのが重要です。
これが無いとdockerのコンテナをキャッシュを利用せずbuildしなおした際に、プロジェクトの.cpcacheにはライブラリがあるのに.m2に無い状態となり、ライブラリのダウンロードが始まらず下記のようなライブラリが見つからない旨のエラーが発生します。
Syntax error (FileNotFoundException) compiling at (asuki/back.clj:1:1).
Could not locate bidi/ring__init.class, bidi/ring.clj or bidi/ring.cljc on classpath.

上記のエラーが発生した場合は、[workdir]/.cpcacheを消せばライブラリのダウンロードが行われて期待通りに動きます。
buildし直したら動かなくなるのが手間なので/root/.m2をvolumeで管理しました。

clojure scriptプロジェクトの動かし方: clojureとwebpackの出力先を分ける

clojure + nodejs(yarn利用)のコンテナを作ってclojure scriptを動かします。
/root/.m2をvolumesで管理するのが良いのはclojureプロジェクトと同様です。
front/Dockerfile
FROM clojure:openjdk-18-bullseye

RUN apt update

# install nodejs
RUN apt install -y npm
RUN npm install -g npm
RUN npm install -g n
RUN n stable
RUN npm install -g yarn

WORKDIR /usr/src/app/front
docker-compose.yml
version: "3.9"
services:
front:
build: ./front
command: bash -c "yarn && clj -M -m cljs.main -co build.edn -v --watch ./src -c"
volumes:
- ./front:/usr/src/app/front
- .volumes/m2:/root/.m2

nodejsはroot権限で実行出来ない制約があるため、clojure scriptのwebpackの説明ページのコードをdockerで動かすと、下記のようなエラーが発生します。
Execution error (ExceptionInfo) at cljs.closure/run-bundle-cmd (closure.clj:3146).
:bundle-cmd :none failed
エラー発生時に示されるログファイルを見ると下記の情報があり、権限の問題で main.js を生成できなかったのが理由と分かります。
  :data
{:cmd
["npx" "webpack" "./out/index.js" "-o" "out" "--mode=development"],
:exit-code 2,
:stdout "",
:stderr
"[webpack-cli] [Error: EACCES: permission denied, open '/usr/src/app/front/out/main.js'] {\n errno: -13,\n code: 'EACCES',\n syscall: 'open',\n path: '/usr/src/app/front/out/main.js'\n}\n"}}
clojureで生成したディレクトリはrootで作られるため、localユーザーとして動くwebpackがroot権限のディレクトリにファイルを出力できないのが原因です。

上記の問題は、下記のようにbuild.ednの出力先ディレクトリを分けると回避できます。
  • clojure scriptの出力先 -> out-cljs(rootユーザーとして作成される)
  • webpackの出力先 -> out-webpack(localユーザーとして作成される)
front/build.edn
{:main front.core
:output-to "out-cljs/index.js"
:output-dir "out-cljs"
;:deps-cmd "yarn"
;:install-deps true
:target :bundle
:watch-fn (fn [] (println "Updated build"))
:bundle-cmd {:none ["npx" "webpack" "./out-cljs/index.js" "-o" "out-webpack" "--mode=development"]
:default ["npx" "webpack" "./out-cljs/index.js" "-o" "out-webpack"]}
:closure-defines {cljs.core/*global* "window"}} ;; needed for advanced

他の設定が間違っていなければ、上記の設定でcljsのビルドができると思います。

webpack(nodejs)がlocalユーザーとして実行されるなら、clojureもlocalユーザーとして実行すれば良いのでは?: 工夫すれば可能

自分が試した中では、下記の処理を追加したらビルドに成功しました。

localユーザーは1000という名前なので、Dockerfileに下記の記述を追加して1000ユーザー用のホームディレクトリを作ります。.m2配置先です。
また、コンテナ内での操作を1000ユーザーで行うよう設定を記述します。
RUN useradd 1000
RUN mkdir /home/1000
RUN chown -R 1000 /home/1000

USER 1000

docker-compose実行前に、[リポジトリのパス]/.volumes/.m2 というディレクトリをdocker外のローカルユーザーとして作成します。
docker-composeにディレクトリ作成を任せるとrootユーザーとして作ってしまうため、ローカルユーザーとしてclojureを実行すると.m2にファイルを置けないため関連ライブラリをダウンロードできないエラーが発生するためです。
mkdir -p [リポジトリのパス]/.volumes/.m2

docker-composeで.m2の1000ユーザーとして実行しつつ、volumesで管理する.m2を/home/1000/.m2にします。
これにより、frontはlocalユーザーとして実行され、先ほど作成したlocalユーザー権限の.m2がvolumes機能で配置されます。
  front:
volumes:
- .volumes/m2:/home/1000/.m2

上記の処理を行えば、clojureとwebpackの出力先が同じfront/outディレクトリでも、どちらもlocalユーザーとして動いているため期待通りに処理ができました。
しかしながら、dockerに頼らず.m2ディレクトリを用意する必要があって手間なので、自分はclojureとwebpackの出力先を分けるのが好みです。

おわり

試行錯誤しつつ、clojure cliのclojureとclojure scriptのプロジェクトをdockerで動かせました。
より良い設定などをご存知でしたら、コメントやtwitterで共有していただけると嬉しいです。

参考

ClojureScript with Webpack
Security issue: node is run as root #1

0 件のコメント :