この記事では、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を押しながらx
、M-x
は、Esc(またはAlt)を押しながらx
、C-x o
はC-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の使用法についてはこちらをご参照ください。
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の関数を呼び出したりしてみたいと思います。