部署 Common Lisp Web 应用程序
Deploying Common Lisp Web Applications
我想知道如何部署用 Hunchentoot、Wookie、Woo 甚至 Clack 编写的 Common Lisp Web 应用程序。
也就是说,假设我编写了一个包含一些文件、包等的应用程序。通常,当我在本地工作时,我只是 运行 REPL 中的一个命令来启动服务器,然后使用 localhost:8000
或类似的东西。
但是,我对将应用程序部署到 AWS EC2 等生产服务器的过程感到有些困惑。我应该以什么形式部署 Lisp 代码?有不同的选择吗?如果服务器需要重新启动或遇到问题会怎样?
要运行 生产中的 lisp 图像,您可以使用 lisp 代码生成 fasl 文件:
(compile-file "app.lisp")
运行 您通过调用 sbcl 生成的 .fas 文件。
sbcl --noinform \
--load app.fas \
--eval "(defun main (argv) (declare (ignore argv)) (hunchentoot:start (make-instance 'hunchentoot:easy-acceptor :port 4242)))"
我找到了一个博客,其中包含一个解决方案,我已经根据我对 linux 盒子上的生产系统的需求进行了调整。不幸的是,我再也找不到那个博客的参考资料了,所以我只能向你展示我的解决方案,它是针对 CCL 的(而最初的解决方案是针对 SBCL 的),我更熟悉它。这是启动系统的程序:
(require 'swank)
(require 'hunchentoot)
(defparameter *httpd-port* 9090) ; The port Hunchentoot will be listening on
(defparameter *shutdown-port* 6700) ; The port CCL will be listening for shutdown
; this port is the same used in /etc/init.d/hunchentoot
(defparameter *swank-port* 5016) ; The port used for remote interaction with slime
;; Start the Swank server
(defparameter *swank-server*
(swank:create-server :port *swank-port* :dont-close t))
(require 'YOUR-PACKAGE)
(YOUR-PACKAGE:YOUR-STARTING-FUNCTION)
(princ "Hunchentoot started on port ")
(princ *httpd-port*)
(terpri)
(let* ((socket (make-socket :connect :passive :local-host "127.0.0.1" :local-port *shutdown-port* :reuse-address t))
(stream (accept-connection socket)))
(close stream)
(close socket))
(print "Stopping Hunchentoot...")
(YOUR-PACKAGE:YOUR-STOPPING-FUNCTION)
(dolist (proc (all-processes))
(unless (equal proc *current-process*)
(process-kill proc)))
(sleep 1)
(quit)
这个想法是,您可以通过指定 swank 使用的端口,使用 slime 连接到 运行 系统。我用过它几次,例如动态更改数据库 link,这种可能性的力量给我留下了深刻的印象。
运行 系统可以通过以下方式终止:
telnet 127.0.0.1 6700
并由以下内容发起:
nohup ccl -l initcclserver.lisp >& server.out &
在以前版本的脚本中,我找到了 SBCL 特定的部分,因此如果您使用它,您可以修改脚本。
接受终止连接:
(sb-bsd-sockets:socket-bind socket #(127 0 0 1) *shutdown-port*)
(sb-bsd-sockets:socket-listen socket 1)
(multiple-value-bind (client-socket addr port)
(sb-bsd-sockets:socket-accept socket)
(sb-bsd-sockets:socket-close client-socket)
(sb-bsd-sockets:socket-close socket)))
关闭系统:
(dolist (thread (sb-thread:list-all-threads))
(unless (equal sb-thread:*current-thread* thread)
(sb-thread:terminate-thread thread)))
(sleep 1)
(sb-ext:quit)
希望对您有所帮助。
我最近通过为网络应用程序构建独立的可执行文件找到了一些东西,我在 lisp-journey/web-dev (shipping and deployment sections), as well as for the building part on the Common Lisp Cookbook/scripting#for-web-apps.
上写了一篇关于它的文章
我把有趣的部分复制到这里,每个资源都有更多内容。欢迎编辑,主要是在那些资源上,谢谢!
编辑 2019 年 7 月:我在食谱上贡献了一页:https://lispcookbook.github.io/cl-cookbook/web.html
edit:另请参阅提供专业 CL 支持的工具和平台列表:https://github.com/CodyReichert/awesome-cl#deployment
(已编辑)如何 运行 网络应用程序作为脚本
我在下面解释如何构建和 运行 可执行文件,但我们当然可以 运行 将应用程序作为脚本。在 lisp 文件中,说 run.lisp
,确保:
- 加载项目的 asd 文件:
(load "my-project.asd")
- 加载它的依赖:
(ql:quickload :my-project)
- 调用其主要函数:
(my-project:start)
(给定start
是导出符号,否则::start
)。
这样做时,应用程序将启动并返回一个 Lisp REPL。您可以与 运行ning 应用程序进行交互。您可以更新它甚至安装新的 Quicklisp 库 运行s.
如何构建独立的可执行文件
另请参阅 https://github.com/CodyReichert/awesome-cl#interfaces-to-other-package-managers 以了解与 Homebrew 和 Debian 软件包的绑定。
有 SBCL
如何构建(独立的)可执行文件是特定于实现的(参见
低于 Buildapp 和 Rowsell)。使用 SBCL,如前所述
its documentation,
这是一个问题:
(sb-ext:save-lisp-and-die #P"path/name-of-executable" :toplevel #'my-app:main-function :executable t)
sb-ext
是 运行 外部进程的 SBCL 扩展。看其他
SBCL extensions
(其中许多在其他库中实现可移植)。
:executable t
告诉构建一个可执行文件而不是一个
图片。我们可以构建一个图像来保存我们当前的状态
Lisp 图像,稍后回来使用它。特别有用,如果
我们做了很多计算密集型的工作。
如果你在 Slime 中尝试 运行 这个,你会得到一个关于线程的错误 运行ning:
Cannot save core with multiple threads running.
运行 来自简单 SBCL repl 的命令。
我想你的项目有 Quicklisp 依赖项。然后你必须:
- 确保在 Lisp 启动时安装并加载了 Quicklisp(你
完成 Quicklisp 安装)
load
项目的.asd
- 安装依赖项
- 构建可执行文件。
这给出:
(load "my-app.asd")
(ql:quickload :my-app)
(sb-ext:save-lisp-and-die #p"my-app-binary" :toplevel #'my-app:main :executable t)
从命令行或 Makefile,使用 --load
和 --eval
:
build:
sbcl --non-interactive \
--load my-app.asd \
--eval '(ql:quickload :my-app)' \
--eval "(sb-ext:save-lisp-and-die #p\"my-app\" :toplevel #my-app:main :executable t)"
有 ASDF
现在我们已经了解了基础知识,我们需要一种可移植的方法。由于其
3.1 版,ASDF 允许这样做。它介绍了 make
command,
从 .asd 中读取参数。将此添加到您的 .asd 声明中:
:build-operation "program-op" ;; leave as is
:build-pathname "<binary-name>"
:entry-point "<my-system:main-function>"
并调用 asdf:make :my-system
.
因此,在 Makefile 中:
LISP ?= sbcl
build:
$(LISP) --non-interactive \
--load my-app.asd \
--eval '(ql:quickload :my-app)' \
--eval '(asdf:make :my-system)'
使用 Roswell 或 Buildapp
Roswell,一名实施经理和许多
更多,还有 ros build
命令,应该适用于许多人
实现。
我们还可以通过 ros install my-app
让我们的应用程序可以通过 Roswell 安装。请参阅其文档。
我们会用一句话来结束
Buildapp,久经沙场
仍然流行的“配置和保存 SBCL 或 CCL 的应用程序
一个可执行的 Common Lisp 映像。
许多应用程序都使用它(例如,
pgloader), 它可以在
Debian:apt install buildapp
,但现在 asdf:make 或 Roswell 不需要它。
对于网络应用程序
我们可以类似地为我们的网络应用程序构建一个独立的可执行文件。它
因此将包含一个网络服务器,并且能够 运行 在
命令行:
$ ./my-web-app
Hunchentoot server is started.
Listening on localhost:9003.
请注意,此 运行 是生产网络服务器,而不是开发网络服务器,
所以我们可以 运行 我们 VPS 上的二进制文件,然后从
外面。
我们只有一件事要处理,那就是找到并放置线程
前台的 运行ning Web 服务器。在我们的 main
函数中,我们
可以这样做:
(defun main ()
(start-app :port 9003) ;; our start-app, for example clack:clack-up
;; let the webserver run.
;; warning: hardcoded "hunchentoot".
(handler-case (bt:join-thread (find-if (lambda (th)
(search "hunchentoot" (bt:thread-name th)))
(bt:all-threads)))
;; Catch a user's C-c
(#+sbcl sb-sys:interactive-interrupt
#+ccl ccl:interrupt-signal-condition
#+clisp system::simple-interrupt-condition
#+ecl ext:interactive-interrupt
#+allegro excl:interrupt-signal
() (progn
(format *error-output* "Aborting.~&")
(clack:stop *server*)
(uiop:quit)))
(error (c) (format t "Woops, an unknown error occured:~&~a~&" c))))
我们使用了 bordeaux-threads
库((ql:quickload "bordeaux-threads")
,别名 bt
)和 uiop
,它是 ASDF 的一部分所以
已经加载,以便以可移植的方式退出(uiop:quit
,与
一个可选的 return 代码,而不是 sb-ext:quit
).
解析命令行参数
参见食谱 here。 TLDR;使用 uiop:command-line-arguments
获取参数列表。要真正解析它们,有库。
部署
直接使用可执行文件。从外部可以立即看到网络应用程序。
在 Heroku 上
参见 this buildpack。
守护进程,在崩溃的情况下重新启动,处理日志
查看如何在您的系统上执行此操作。
大多数 GNU/Linux 发行版现在都带有 Systemd。
例子search结果:
写配置文件一样简单:
# /etc/systemd/system/my-app.service
[Unit]
Description=stupid simple example
[Service]
WorkingDirectory=/path/to/your/app
ExecStart=/usr/local/bin/sthg sthg
Type=simple
Restart=always
RestartSec=10
运行要启动它的命令:
sudo systemctl start my-app.service
检查其状态的命令:
systemctl status my-app.service
并且 Systemd 可以处理 日志记录(我们写入 stdout 或 stderr,它写入日志):
journalctl -f -u my-app.service
它会处理崩溃并重新启动应用程序:
Restart=always
它可以重启后启动应用程序:
[Install]
WantedBy=basic.target
启用它:
sudo systemctl enable my-app.service
调试 SBCL 错误:ensure_space:分配 n 个字节失败
如果您在服务器上使用 SBCL 时遇到此错误:
mmap: wanted 1040384 bytes at 0x20000000, actually mapped at 0x715fa2145000
ensure_space: failed to allocate 1040384 bytes at 0x20000000
(hint: Try "ulimit -a"; maybe you should increase memory limits.)
然后禁用 ASLR:
sudo bash -c "echo 0 > /proc/sys/kernel/randomize_va_space"
正在连接到远程 Swank 服务器
这里的小例子:http://cvberry.com/tech_writings/howtos/remotely_modifying_a_running_program_using_swank.html.
它定义了一个永远打印的简单函数:
;; a little common lisp swank demo
;; while this program is running, you can connect to it from another terminal or machine
;; and change the definition of doprint to print something else out!
;; (ql:quickload :swank)
;; (ql:quickload :bordeaux-threads)
(require :swank)
(require :bordeaux-threads)
(defparameter *counter* 0)
(defun dostuff ()
(format t "hello world ~a!~%" *counter*))
(defun runner ()
(bt:make-thread (lambda ()
(swank:create-server :port 4006)))
(format t "we are past go!~%")
(loop while t do
(sleep 5)
(dostuff)
(incf *counter*)
))
(runner)
在我们的服务器上,我们 运行 它与
sbcl --load demo.lisp
我们在开发机器上做端口转发:
ssh -L4006:127.0.0.1:4006 username@example.com
这将安全地将 example.com 服务器上的端口 4006 转发到
我们本地计算机的端口 4006(swanks 接受来自
本地主机)。
我们使用 M-x slime-connect
连接到 运行ning swank,输入
端口 4006.
我们可以编写新代码:
(defun dostuff ()
(format t "goodbye world ~a!~%" *counter*))
(setf *counter* 0)
并像往常一样用 M-x slime-eval-region
评估它。输出应该改变。
CV Berry 的页面上有更多指针。
热重载
示例 Quickutil。请参阅有关 lisp-journey 的注释。
它必须是 运行 在服务器上(一个简单的 fabfile 命令可以调用这个
通过 ssh)。事先,fab update
上有 运行 git pull
服务器,因此存在新代码但未 运行ning。它连接到
本地 swank 服务器,加载新代码,停止并启动应用程序
行。
持续集成,持续交付可执行文件,Docker
见https://lispcookbook.github.io/cl-cookbook/testing.html#continuous-integration
我想知道如何部署用 Hunchentoot、Wookie、Woo 甚至 Clack 编写的 Common Lisp Web 应用程序。
也就是说,假设我编写了一个包含一些文件、包等的应用程序。通常,当我在本地工作时,我只是 运行 REPL 中的一个命令来启动服务器,然后使用 localhost:8000
或类似的东西。
但是,我对将应用程序部署到 AWS EC2 等生产服务器的过程感到有些困惑。我应该以什么形式部署 Lisp 代码?有不同的选择吗?如果服务器需要重新启动或遇到问题会怎样?
要运行 生产中的 lisp 图像,您可以使用 lisp 代码生成 fasl 文件:
(compile-file "app.lisp")
运行 您通过调用 sbcl 生成的 .fas 文件。
sbcl --noinform \
--load app.fas \
--eval "(defun main (argv) (declare (ignore argv)) (hunchentoot:start (make-instance 'hunchentoot:easy-acceptor :port 4242)))"
我找到了一个博客,其中包含一个解决方案,我已经根据我对 linux 盒子上的生产系统的需求进行了调整。不幸的是,我再也找不到那个博客的参考资料了,所以我只能向你展示我的解决方案,它是针对 CCL 的(而最初的解决方案是针对 SBCL 的),我更熟悉它。这是启动系统的程序:
(require 'swank)
(require 'hunchentoot)
(defparameter *httpd-port* 9090) ; The port Hunchentoot will be listening on
(defparameter *shutdown-port* 6700) ; The port CCL will be listening for shutdown
; this port is the same used in /etc/init.d/hunchentoot
(defparameter *swank-port* 5016) ; The port used for remote interaction with slime
;; Start the Swank server
(defparameter *swank-server*
(swank:create-server :port *swank-port* :dont-close t))
(require 'YOUR-PACKAGE)
(YOUR-PACKAGE:YOUR-STARTING-FUNCTION)
(princ "Hunchentoot started on port ")
(princ *httpd-port*)
(terpri)
(let* ((socket (make-socket :connect :passive :local-host "127.0.0.1" :local-port *shutdown-port* :reuse-address t))
(stream (accept-connection socket)))
(close stream)
(close socket))
(print "Stopping Hunchentoot...")
(YOUR-PACKAGE:YOUR-STOPPING-FUNCTION)
(dolist (proc (all-processes))
(unless (equal proc *current-process*)
(process-kill proc)))
(sleep 1)
(quit)
这个想法是,您可以通过指定 swank 使用的端口,使用 slime 连接到 运行 系统。我用过它几次,例如动态更改数据库 link,这种可能性的力量给我留下了深刻的印象。
运行 系统可以通过以下方式终止:
telnet 127.0.0.1 6700
并由以下内容发起:
nohup ccl -l initcclserver.lisp >& server.out &
在以前版本的脚本中,我找到了 SBCL 特定的部分,因此如果您使用它,您可以修改脚本。
接受终止连接:
(sb-bsd-sockets:socket-bind socket #(127 0 0 1) *shutdown-port*)
(sb-bsd-sockets:socket-listen socket 1)
(multiple-value-bind (client-socket addr port)
(sb-bsd-sockets:socket-accept socket)
(sb-bsd-sockets:socket-close client-socket)
(sb-bsd-sockets:socket-close socket)))
关闭系统:
(dolist (thread (sb-thread:list-all-threads))
(unless (equal sb-thread:*current-thread* thread)
(sb-thread:terminate-thread thread)))
(sleep 1)
(sb-ext:quit)
希望对您有所帮助。
我最近通过为网络应用程序构建独立的可执行文件找到了一些东西,我在 lisp-journey/web-dev (shipping and deployment sections), as well as for the building part on the Common Lisp Cookbook/scripting#for-web-apps.
上写了一篇关于它的文章我把有趣的部分复制到这里,每个资源都有更多内容。欢迎编辑,主要是在那些资源上,谢谢!
编辑 2019 年 7 月:我在食谱上贡献了一页:https://lispcookbook.github.io/cl-cookbook/web.html
edit:另请参阅提供专业 CL 支持的工具和平台列表:https://github.com/CodyReichert/awesome-cl#deployment
(已编辑)如何 运行 网络应用程序作为脚本
我在下面解释如何构建和 运行 可执行文件,但我们当然可以 运行 将应用程序作为脚本。在 lisp 文件中,说 run.lisp
,确保:
- 加载项目的 asd 文件:
(load "my-project.asd")
- 加载它的依赖:
(ql:quickload :my-project)
- 调用其主要函数:
(my-project:start)
(给定start
是导出符号,否则::start
)。
这样做时,应用程序将启动并返回一个 Lisp REPL。您可以与 运行ning 应用程序进行交互。您可以更新它甚至安装新的 Quicklisp 库 运行s.
如何构建独立的可执行文件
另请参阅 https://github.com/CodyReichert/awesome-cl#interfaces-to-other-package-managers 以了解与 Homebrew 和 Debian 软件包的绑定。
有 SBCL
如何构建(独立的)可执行文件是特定于实现的(参见 低于 Buildapp 和 Rowsell)。使用 SBCL,如前所述 its documentation, 这是一个问题:
(sb-ext:save-lisp-and-die #P"path/name-of-executable" :toplevel #'my-app:main-function :executable t)
sb-ext
是 运行 外部进程的 SBCL 扩展。看其他
SBCL extensions
(其中许多在其他库中实现可移植)。
:executable t
告诉构建一个可执行文件而不是一个
图片。我们可以构建一个图像来保存我们当前的状态
Lisp 图像,稍后回来使用它。特别有用,如果
我们做了很多计算密集型的工作。
如果你在 Slime 中尝试 运行 这个,你会得到一个关于线程的错误 运行ning:
Cannot save core with multiple threads running.
运行 来自简单 SBCL repl 的命令。
我想你的项目有 Quicklisp 依赖项。然后你必须:
- 确保在 Lisp 启动时安装并加载了 Quicklisp(你 完成 Quicklisp 安装)
load
项目的.asd- 安装依赖项
- 构建可执行文件。
这给出:
(load "my-app.asd")
(ql:quickload :my-app)
(sb-ext:save-lisp-and-die #p"my-app-binary" :toplevel #'my-app:main :executable t)
从命令行或 Makefile,使用 --load
和 --eval
:
build:
sbcl --non-interactive \
--load my-app.asd \
--eval '(ql:quickload :my-app)' \
--eval "(sb-ext:save-lisp-and-die #p\"my-app\" :toplevel #my-app:main :executable t)"
有 ASDF
现在我们已经了解了基础知识,我们需要一种可移植的方法。由于其
3.1 版,ASDF 允许这样做。它介绍了 make
command,
从 .asd 中读取参数。将此添加到您的 .asd 声明中:
:build-operation "program-op" ;; leave as is
:build-pathname "<binary-name>"
:entry-point "<my-system:main-function>"
并调用 asdf:make :my-system
.
因此,在 Makefile 中:
LISP ?= sbcl
build:
$(LISP) --non-interactive \
--load my-app.asd \
--eval '(ql:quickload :my-app)' \
--eval '(asdf:make :my-system)'
使用 Roswell 或 Buildapp
Roswell,一名实施经理和许多
更多,还有 ros build
命令,应该适用于许多人
实现。
我们还可以通过 ros install my-app
让我们的应用程序可以通过 Roswell 安装。请参阅其文档。
我们会用一句话来结束 Buildapp,久经沙场 仍然流行的“配置和保存 SBCL 或 CCL 的应用程序 一个可执行的 Common Lisp 映像。
许多应用程序都使用它(例如,
pgloader), 它可以在
Debian:apt install buildapp
,但现在 asdf:make 或 Roswell 不需要它。
对于网络应用程序
我们可以类似地为我们的网络应用程序构建一个独立的可执行文件。它 因此将包含一个网络服务器,并且能够 运行 在 命令行:
$ ./my-web-app
Hunchentoot server is started.
Listening on localhost:9003.
请注意,此 运行 是生产网络服务器,而不是开发网络服务器, 所以我们可以 运行 我们 VPS 上的二进制文件,然后从 外面。
我们只有一件事要处理,那就是找到并放置线程
前台的 运行ning Web 服务器。在我们的 main
函数中,我们
可以这样做:
(defun main ()
(start-app :port 9003) ;; our start-app, for example clack:clack-up
;; let the webserver run.
;; warning: hardcoded "hunchentoot".
(handler-case (bt:join-thread (find-if (lambda (th)
(search "hunchentoot" (bt:thread-name th)))
(bt:all-threads)))
;; Catch a user's C-c
(#+sbcl sb-sys:interactive-interrupt
#+ccl ccl:interrupt-signal-condition
#+clisp system::simple-interrupt-condition
#+ecl ext:interactive-interrupt
#+allegro excl:interrupt-signal
() (progn
(format *error-output* "Aborting.~&")
(clack:stop *server*)
(uiop:quit)))
(error (c) (format t "Woops, an unknown error occured:~&~a~&" c))))
我们使用了 bordeaux-threads
库((ql:quickload "bordeaux-threads")
,别名 bt
)和 uiop
,它是 ASDF 的一部分所以
已经加载,以便以可移植的方式退出(uiop:quit
,与
一个可选的 return 代码,而不是 sb-ext:quit
).
解析命令行参数
参见食谱 here。 TLDR;使用 uiop:command-line-arguments
获取参数列表。要真正解析它们,有库。
部署
直接使用可执行文件。从外部可以立即看到网络应用程序。
在 Heroku 上
参见 this buildpack。
守护进程,在崩溃的情况下重新启动,处理日志
查看如何在您的系统上执行此操作。
大多数 GNU/Linux 发行版现在都带有 Systemd。
例子search结果:
写配置文件一样简单:
# /etc/systemd/system/my-app.service
[Unit]
Description=stupid simple example
[Service]
WorkingDirectory=/path/to/your/app
ExecStart=/usr/local/bin/sthg sthg
Type=simple
Restart=always
RestartSec=10
运行要启动它的命令:
sudo systemctl start my-app.service
检查其状态的命令:
systemctl status my-app.service
并且 Systemd 可以处理 日志记录(我们写入 stdout 或 stderr,它写入日志):
journalctl -f -u my-app.service
它会处理崩溃并重新启动应用程序:
Restart=always
它可以重启后启动应用程序:
[Install]
WantedBy=basic.target
启用它:
sudo systemctl enable my-app.service
调试 SBCL 错误:ensure_space:分配 n 个字节失败
如果您在服务器上使用 SBCL 时遇到此错误:
mmap: wanted 1040384 bytes at 0x20000000, actually mapped at 0x715fa2145000
ensure_space: failed to allocate 1040384 bytes at 0x20000000
(hint: Try "ulimit -a"; maybe you should increase memory limits.)
然后禁用 ASLR:
sudo bash -c "echo 0 > /proc/sys/kernel/randomize_va_space"
正在连接到远程 Swank 服务器
这里的小例子:http://cvberry.com/tech_writings/howtos/remotely_modifying_a_running_program_using_swank.html.
它定义了一个永远打印的简单函数:
;; a little common lisp swank demo
;; while this program is running, you can connect to it from another terminal or machine
;; and change the definition of doprint to print something else out!
;; (ql:quickload :swank)
;; (ql:quickload :bordeaux-threads)
(require :swank)
(require :bordeaux-threads)
(defparameter *counter* 0)
(defun dostuff ()
(format t "hello world ~a!~%" *counter*))
(defun runner ()
(bt:make-thread (lambda ()
(swank:create-server :port 4006)))
(format t "we are past go!~%")
(loop while t do
(sleep 5)
(dostuff)
(incf *counter*)
))
(runner)
在我们的服务器上,我们 运行 它与
sbcl --load demo.lisp
我们在开发机器上做端口转发:
ssh -L4006:127.0.0.1:4006 username@example.com
这将安全地将 example.com 服务器上的端口 4006 转发到 我们本地计算机的端口 4006(swanks 接受来自 本地主机)。
我们使用 M-x slime-connect
连接到 运行ning swank,输入
端口 4006.
我们可以编写新代码:
(defun dostuff ()
(format t "goodbye world ~a!~%" *counter*))
(setf *counter* 0)
并像往常一样用 M-x slime-eval-region
评估它。输出应该改变。
CV Berry 的页面上有更多指针。
热重载
示例 Quickutil。请参阅有关 lisp-journey 的注释。
它必须是 运行 在服务器上(一个简单的 fabfile 命令可以调用这个
通过 ssh)。事先,fab update
上有 运行 git pull
服务器,因此存在新代码但未 运行ning。它连接到
本地 swank 服务器,加载新代码,停止并启动应用程序
行。
持续集成,持续交付可执行文件,Docker
见https://lispcookbook.github.io/cl-cookbook/testing.html#continuous-integration