Dockerで簡単構築!Common Lisp学習環境


この記事では、Dockerを用いてCommon Lispの学開環境を構築する方法について書きます。

次の①〜④の流れで進めます。

 ① Dockerについて

 ② 利用するDockerfileについて

 ③ イメージの取得とコンテナの起動

 ④ Common Lisp製アプリケーションの実行

① Dockerについて

Dockerについては、以下の記事が参考になります。

・Dockerについてなるべくわかりやすく説明する / kasanaxnさん

・今更Docker入門 - コンテナ化することで何が嬉しいか / zgmf_mbfp03さん

この記事でのDockerの使い方を要約すると、docker pullでイメージのダウンロード後、docker runでコンテナの作成と起動をして、Common Lispの学習環境を構築する流れです。コンテナを起動すると、Caveman2やLem等のCommon Lisp製アプリケーションを実行することができます。

では、Dockerをインストールして次に進んでください。OSはmacOSかUbuntuを想定しています。Windowsのユーザの方は、Virtual BoxにUbuntuを入れて進めてください。

Docker for Mac のインストール手順
Docker CE for Ubuntu のインストール手順

② 利用するDockerfileについて

今回は、Alpine Linuxをベースに、Common Lispの環境構築のためにRoswell、エディタとしてLem、notebook形式の開発環境としてDarkmatter、WebアプリケーションフレームワークとしてCaveman2がインストールされたイメージを作成するDockerfileを作成します。Dockerfileはeshamsterさんのcl-baseを元に作成しました。

FROM frolvlad/alpine-glibc:alpine-3.6

ARG work_dir=/tmp/setup
RUN mkdir ${work_dir} && \
    chmod 777 ${work_dir}

# --- Roswellのインストール --- #
RUN apk add --no-cache git automake autoconf make gcc build-base curl-dev curl glib-dev libressl-dev ncurses-dev sqlite libev-dev su-exec libpq postgresql-client postgresql-dev postgresql-contrib && \
    mkdir /docker-entrypoint-initdb.d && \
    chmod +x docker-entrypoint.sh && \
    cd ${work_dir} && \
    git clone --depth=1 -b release https://github.com/roswell/roswell.git && \
    cd roswell && \
    sh bootstrap && \
    ./configure --disable-manual-install && \
    make && \
    make install && \
    cd .. && \
    rm -rf roswell && \
    ros run -q

# --- .roswell/binにPATHを通す --- #
ENV PATH /root/.roswell/bin:${PATH}

# --- Caveman, Lem, Darkmatter をインストールする--- #
RUN ln -s ${HOME}/.roswell/local-projects work && \
    ros install fukamachi/clack && \
    ros install fukamachi/caveman && \
    ros install cxxxr/lem && \
    ros install tamamu/darkmatter && \
    ros install Shinmera/dissect && \
    ros install fukamachi/qlot && \
    ros install fukamachi/utopian && \
    mv ${HOME}/.roswell/bin/lem ${HOME}/.roswell/bin/lem2 && \
    mv ${HOME}/.roswell/bin/lem-ncurses ${HOME}/.roswell/bin/lem

# --- Webアプリケーションにアクセスできるようにポートを開ける --- #
EXPOSE 8888

③ イメージの取得とコンテナの起動

では、このDockerfileでビルドしたイメージを用いて実践に移ります。

まず、Dockerを起動してください。Dockerの起動後、docker pullでイメージをダウンロードします。

$ docker pull tcool/cl-alpine

次に、コンテナの作成と起動を行います。イメージを元にcl-alpineという名前でコンテナを作成後、ホストOSのポート8888番へのリクエストをコンテナのポート8888番につなぎます。-itオプションを使うことで、シェルを対話的に実行することができます。

docker run -it -p 8888:8888 --name cl-alpine tcool/cl-alpine

④ Common Lisp製アプリケーションの実行

Roswellについて

Roswellを使うと、ros install <Githubアカウント名/レポジトリ名>でLispアプリケーションをインストールできます。試しに、JSONのエンコードとデコードを行うライブラリ Jonathanをインストールしてみましょう。

#\ ros install Rudolph-Miller/jonathan
Installing from github Rudolph-Miller/jonathan
To load "jonathan":
  Load 1 ASDF system:
    jonathan
; Loading "jonathan"
..To load "proc-parse":
  Load 2 ASDF systems:
    alexandria babel
(中略)
up to date. stop
$ 

ros runとすると、REPLを起動することができます。

#\ ros run
* 

先ほどインストールしたJonathanをREPLから利用するには、ql:quickloadでJonathanを読み込み、Jonathanの後に:コロンをつけて関数を実行します。試しに、リストをjsonにエンコードするto-json関数を使ってみます。

* (ql:quickload :jonathan)
To load "jonathan":
  Load 1 ASDF system:
    jonathan
; Loading "jonathan"
....
(:JONATHAN)
* (jonathan:to-json '(:name "Common Lisp" :born 1984 :impls (SBCL KCL)))
"{\"NAME\":\"Common Lisp\",\"BORN\":1984,\"IMPLS\":[\"SBCL\",\"KCL\"]}"

REPLで(quit)と打つと、REPLから抜けることができます。

* (quit)

また、ros use <処理系/バージョン>とすると、Common Lispの処理系、バージョンを切り替えることができます。例えば、ros install sbcl/1.2.3の後ros use sbcl/1.2.3とすると、デフォルトでSBCLのversion 1.2.3を用いるように指定できます。

以上のように、RoswellはCommon Lispの環境構築を行うために便利なユーティリティーです。

1. Lem

Lemを起動します。

#\ lem

起動後、M-x slimeでSlimeを起動します。M-xは、「escキーかOptionキーを押しながらXを押す」という意味です。次のような画面になります。

Lemのキーバインドは、Emacsライクなものとvi-modeがあります。

vi-modeを利用される場合は、 ~/.lem/init.lispに次のように設定を書いてください。

(lem-vi-mode:vi-mode)

デフィルトのキーバインドはM-x describe-bindingsで確認できます。Emacsのキーバインドに慣れている方は、ほぼそのまま利用できます。C-xは、Controlを押しながらxM-xは、Esc(またはAlt)を押しながらxC-x oC-xのあとキーボードから指を離して、oを打ちます。

基本操作は、以下の通りです。

コマンド 機能
M-x lisp-mode Lisp編集モードに変更
M-x start-lisp-repl 同じプロセスでslimeを開始
M-x slime 別のプロセスでslimeを開始
C-x C-f 新規ファイルの作成、diredの起動
C-x o または M-o バッファの移動
C-x k バッファの削除
C-@ 選択開始
C-w カット
M-w コピー
C-y ペースト

Lemの使用法についてはこちらをご参照ください。

IMAGE ALT TEXT HERE

2. Darkmatter

Darkmatterを読み込み、サーバを起動します。

#\ ros run
* (ql:quickload :darkmatter)
* (darkmatter:start :server :hunchentoot :port 8888)

サーバの起動後、http://localhost:8888にアクセスすると、Darkmatterを利用できます。

Darkmatterに関する詳しい情報は、darkmatterのレポジトリをご参照ください。

3. Caveman2

最後に、Common Lisp製のWebフレームワーク「Caveman2」を使ってみます。プロジェクト雛形の生成機能を用いて、myappという名前でプロジェクトを生成します。

#\ ros run
* (ql:quickload :caveman2)
* (caveman2:make-project #P"/path/to/myapp/"
                         :author "<Your full name>")
;-> writing /path/to/myapp/.gitignore
;   writing /path/to/myapp/README.markdown
;   writing /path/to/myapp/app.lisp
;   writing /path/to/myapp/db/schema.sql
;   writing /path/to/myapp/shlyfile.lisp
;   writing /path/to/myapp/myapp-test.asd
;   writing /path/to/myapp/myapp.asd
;   writing /path/to/myapp/src/config.lisp
;   writing /path/to/myapp/src/db.lisp
;   writing /path/to/myapp/src/main.lisp
;   writing /path/to/myapp/src/view.lisp
;   writing /path/to/myapp/src/web.lisp
;   writing /path/to/myapp/static/css/main.css
;   writing /path/to/myapp/t/myapp.lisp
;   writing /path/to/myapp/templates/_errors/404.html
;   writing /path/to/myapp/templates/index.tmpl
;   writing /path/to/myapp/templates/layout/default.tmpl

サーバを起動するには、(プロジェクト名:start :port ポート番号)とします。例えば、myappというプロジェクトで8888番ポートでサーバを起動するには、次のようにします。

* (myapp:start :port 8888)

ブラウザでhttp://localhost:8888にアクセスをすると、"Hello, Caveman2!"と表示されます。

デプロイ

ここまでは、ローカルマシン上のDockerで作業を行ってきましたが、次はVPS上にDockerをインストールして環境構築してみます。ここではAWSのLightsailを用います。AWSにアカウントを作成後、次の初期化スクリプトを用いて、Ubuntuのインスタンスを作成、dockerとイメージをインストールしてください。

sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates
sudo apt-key adv --keyserver hkp://ha.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
echo "deb https://apt.dockerproject.org/repo ubuntu-xenial main" | sudo tee /etc/apt/sources.list.d/docker.list
sudo apt-get update
sudo apt-get install -y linux-image-extra-virtual
sudo apt-get install -y docker-engine
sudo service docker start

完了後、WebコンソールからSSH接続をして、次のコマンドを打てば、コンテナの中に入れます。

$ sudo docker pull tcool/cl-alpine
$ docker run -it -p 8888:8888 --name cl-alpine tcool/cl-alpine

ローカルのTerminalからSSHでサーバに接続するには、インスタンスの設定ページからpemファイルをダウンロードして、次のように接続します。

ssh -i <pemファイルのパス> ubuntu@http://<固定のグローバルIPアドレス>

コンテナの再起動

コンテナを停止した場合、再度ashにログインするには、コンテナを再起動する必要があります。起動中のコンテナを確認するには、docker psを実行します。

docker ps

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES

このようにコンテナが起動していないときには、docker ps -aでコンテナのIDを確認します。

$ docker ps -a
CONTAINER ID     IMAGE            COMMAND          CREATED          STATUS           PORTS                  NAMES
1d5659fdb4f3     tcool/cl-alphine   "/bin/bash"      31 minutes ago   Up 31 minutes    0.0.0.0:8888->8888/tcp cl-alpine

その後、docker start <コンテナID>でコンテナを起動します。

$ docker start 1d5659fdb4f3 

docker exec -it <コンテナ名> /bin/ashでコンテナのashにログインできます。ashを起動すると、作業の続きに戻ることができます。

docker exec -it 1d5659fdb4f3 /bin/ash 

まとめ

以上の方法を使うと、簡単にCommon Lispの開発環境を試すことができます。本番環境に適したDBやSSLの対応をしていないので本番運用では使えませんが、手軽にCommon Lispの学習環境を構築するには良いかと思います。

次回は、Docker Composeを用いてDBのコンテナと連携させながらLispアプリを運用したり、AWS Lambdaのカスタムランタイムを使ってCommon Lispの関数を呼び出したりしてみたいと思います。