部署Common Lisp Web应用程序

Mad*_*ist 12 lisp common-lisp amazon-ec2 hunchentoot amazon-web-services

我想知道如何部署一个用Hunchentoot,Wookie,Woo甚至Clack编写的Common Lisp Web应用程序.

也就是说,假设我编写了一个包含一些文件,软件包等的应用程序.通常,当我在本地工作时,我只需在REPL中运行一个启动服务器的命令,然后使用localhost:8000或类似的方式访问它.

但是,对于将应用程序部署到AWS EC2等生产服务器的过程,我感到有些疑惑.我应该以什么形式部署Lisp代码?有不同的选择吗?如果需要重新启动服务器或遇到问题,会发生什么?

Ehv*_*nce 17

我最近通过为Web应用程序构建自包含的可执行文件找到了一些东西,我在lisp-journey/web-dev(运输和部署部分)以及Common Lisp Cookbook/scripting的构建部分上写了这篇文章. #for-web-apps.

我在这里复制有趣的部分,每个资源上都有更多.欢迎编辑,主要是关于这些资源谢谢!

如何构建一个自包含的可执行文件

另请参阅https://github.com/CodyReichert/awesome-cl#interfaces-to-other-package-managers以获取与Homebrew和Debian软件包的绑定.

随着SBCL

如何构建(自包含)可执行文件是特定于实现的(请参阅下面的Buildapp和Rowsell).对于SBCL,正如 其文档所述,它是一个问题:

sb-ext

:executable t是一个运行外部进程的SBCL扩展.请参阅其他 SBCL扩展 (其中许多扩展是在其他库中实现可移植的).

load 告诉你构建一个可执行文件而不是图像.我们可以构建一个图像来保存当前Lisp图像的状态,以后再回来使用它.如果我们做了大量计算密集型工作,那么特别有用.

如果您尝试在Slime中运行它,您将收到有关运行的线程的错误:

无法在运行多个线程的情况下保存核心.

从简单的SBCL repl运行该命令.

我想你的项目有Quicklisp依赖项.你必须:

  • 确保在Lisp启动时安装并加载Quicklisp(您已完成Quicklisp安装)
  • --load 项目的.asd
  • 安装依赖项
  • 构建可执行文件.

这给了:

--eval

从命令行或Makefile中,使用makeasdf:make :my-system:

ros build

有了ASDF

现在我们已经看到了基础知识,我们需要一种便携式方法.自版本3.1以来,ASDF允许这样做.它引入了从.asd读取参数的ros install my-app命令.将此添加到.asd声明中:

apt install buildapp

并打电话main.

所以,在Makefile中:

(sb-ext:save-lisp-and-die #P"path/name-of-executable" :toplevel #'my-app:main-function :executable t)
Run Code Online (Sandbox Code Playgroud)

使用Roswell或Buildapp

Roswell,一个实现管理器以及更多,也有这个bordeaux-threads命令,应该适用于许多实现.

我们还可以使用Roswell使我们的应用程序可以安装(ql:quickload "bordeaux-threads").查看其文档.

我们将在Buildapp上完成一个词 ,这是一个久经考验且仍然流行的"SBCL或CCL应用程序,用于配置和保存可执行的Common Lisp镜像".

许多应用程序使用它(例如, pgloader),它可以在Debian上使用:bt但是现在你不应该使用asdf:make或Roswell.

适用于网络应用

我们可以类似地为我们的web应用程序构建一个自包含的可执行文件.因此它将包含一个Web服务器,并且能够在命令行上运行:

(load "my-app.asd")
(ql:quickload :my-app)
(sb-ext:save-lisp-and-die #p"my-app-binary" :toplevel #'my-app:main :executable t)
Run Code Online (Sandbox Code Playgroud)

请注意,这会运行生产Web服务器,而不是开发Web服务器,因此我们可以立即在VPS上运行二进制文件,并从外部访问应用程序.

我们有一件事需要处理,它是找到并将正在运行的Web服务器的线程放在前台.在我们的uiop函数中,我们可以这样做:

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)"
Run Code Online (Sandbox Code Playgroud)

我们使用了uiop:quit库(sb-ext:quit,别名M-x slime-connect)M-x slime-eval-region,它是已经加载的ASDF的一部分,以便以可移植的方式退出(fab update带有可选的返回代码,而不是git pull).

解析命令行参数

看到食谱.

部署

直接使用可执行文件.从外面可以看到Web应用程序.

在Heroku上

看到这个buildpack.

守护进程,在崩溃的情况下重新启动,处理日志

了解如何在您的系统上执行此操作.

大多数GNU/Linux发行版现在都带有Systemd.

示例搜索结果:

它就像编写配置文件一样简单:

:build-operation "program-op" ;; leave as is
:build-pathname "<binary-name>"
:entry-point "<my-system:main-function>"
Run Code Online (Sandbox Code Playgroud)

运行命令启动它:

LISP ?= sbcl

build:
    $(LISP) --non-interactive \
        --load my-app.asd \
        --eval '(ql:quickload :my-app)' \
        --eval '(asdf:make :my-system)' 
Run Code Online (Sandbox Code Playgroud)

检查其状态的命令:

$ ./my-web-app
Hunchentoot server is started.
Listening on localhost:9003.
Run Code Online (Sandbox Code Playgroud)

和Systemd可以处理日志记录(我们写入stdout或stderr,它写日志):

(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))))
Run Code Online (Sandbox Code Playgroud)

它处理崩溃并重新启动应用程序:

# /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
Run Code Online (Sandbox Code Playgroud)

它可以在重启后启动应用程序:

sudo systemctl start my-app.service
Run Code Online (Sandbox Code Playgroud)

启用它:

systemctl status my-app.service
Run Code Online (Sandbox Code Playgroud)

调试SBCL错误:ensure_space:无法分配n个字节

如果您的服务器上出现SBCL错误:

journalctl -f -u my-app.service
Run Code Online (Sandbox Code Playgroud)

然后禁用ASLR:

Restart=always
Run Code Online (Sandbox Code Playgroud)

连接到远程Swank服务器

这里的小例子:http://cvberry.com/tech_writings/howtos/remotely_modifying_a_running_program_using_swank.html.

它定义了一个永远打印的简单函数:

[Install]
WantedBy=basic.target
Run Code Online (Sandbox Code Playgroud)

在我们的服务器上,我们运行它

sudo systemctl enable my-app.service
Run Code Online (Sandbox Code Playgroud)

我们在开发机器上进行端口转发:

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.)
Run Code Online (Sandbox Code Playgroud)

这将安全地将example.com上的服务器上的端口4006转发到我们本地计算机的端口4006(swanks接受来自localhost的连接).

我们连接到正在运行的swank sb-ext,输入端口4006.

我们可以编写新代码:

sudo bash -c "echo 0 > /proc/sys/kernel/randomize_va_space"
Run Code Online (Sandbox Code Playgroud)

:executable t例如,像往常一样评估它.输出应该改变.

CV Berry的页面上有更多指针.

热重装

Quickutil的示例.请参阅有关lisp-journey的说明.

它必须在服务器上运行(一个简单的fabfile命令可以通过ssh调用它).事先,a load--load在服务器上运行,因此存在新代码但未运行.它连接到本地swank服务器,加载新代码,连续停止和启动应用程序.

持续集成,可执行文件的持续交付,Docker

请参阅https://lispcookbook.github.io/cl-cookbook/testing.html#continuous-integration

  • @coredump好点,谢谢!MadPhysicist:谢谢!哇,我很感谢你带我去看E. Fukamachi ^^我希望我不但是,我远没有那么有天赋和富有成效!我是我链接的两个页面的作者,所以你可以看到,我的github昵称是vindarel. (2认同)

pap*_*han 6

要在生产环境中运行Lisp图像,可以使用以下命令从Lisp代码生成fasl文件:

(compile-file "app.lisp")
Run Code Online (Sandbox Code Playgroud)

通过调用sbcl运行生成的.fas文件。

sbcl --noinform \
     --load app.fas \
     --eval "(defun main (argv) (declare (ignore argv)) (hunchentoot:start (make-instance 'hunchentoot:easy-acceptor :port 4242)))"
Run Code Online (Sandbox Code Playgroud)

  • 由于SBCL可以很好地使用源文件并即时对其进行编译,因此对于评估与编译并没有真正的区别。由于代码已经编译,因此可以减少加载fasl文件的加载时间。但是以这种方式调用SBCL是启动Lisp并使其执行一些代码的一种方法。 (2认同)
  • 为此,您最好还在映像中启动一个swank服务器。 (2认同)

Ren*_*nzo 5

我找到了一个博客,其中包含一个解决方案,我已经根据我对 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)
Run Code Online (Sandbox Code Playgroud)

这个想法是,您可以通过指定 swank 使用的端口来使用 slime 连接到正在运行的系统。我使用过它几次,例如动态更改数据库链接,这种可能性的强大功能给我留下了深刻的印象。

正在运行的系统可以通过以下方式终止:

telnet 127.0.0.1 6700
Run Code Online (Sandbox Code Playgroud)

并由类似以下内容发起:

nohup ccl -l initcclserver.lisp >& server.out &
Run Code Online (Sandbox Code Playgroud)

在该脚本的早期版本中,我找到了 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)))
Run Code Online (Sandbox Code Playgroud)

关闭系统:

(dolist (thread (sb-thread:list-all-threads))
  (unless (equal sb-thread:*current-thread* thread)
    (sb-thread:terminate-thread thread)))
(sleep 1)
(sb-ext:quit)
Run Code Online (Sandbox Code Playgroud)

希望这能有所帮助。