【问题标题】:Deploying Common Lisp Web Applications部署 Common Lisp Web 应用程序
【发布时间】:2018-01-04 20:59:38
【问题描述】:

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

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

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

【问题讨论】:

    标签: amazon-web-services amazon-ec2 lisp common-lisp hunchentoot


    【解决方案1】:

    我最近通过为 Web 应用程序构建独立的可执行文件发现了一些问题,我在lisp-journey/web-dev(运输和部署部分)以及Common Lisp Cookbook/scripting#for-web-apps 上的构建部分上写了它。

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

    编辑 2019 年 7 月:我在 Cookbook 上贡献了一个页面:https://lispcookbook.github.io/cl-cookbook/web.html

    编辑:另请参阅提供专业 CL 支持的工具和平台列表:https://github.com/CodyReichert/awesome-cl#deployment

    (已编辑)如何将 Web 应用程序作为脚本运行

    我将在下面解释如何构建和运行可执行文件,但我们当然可以将应用程序作为脚本运行。在一个 lisp 文件中,比如run.lisp,确保:

    • 加载项目的 asd 文件:(load "my-project.asd")
    • 加载它的依赖:(ql:quickload :my-project)
    • 调用其主函数:(my-project:start)(假设start是导出符号,否则::start)。

    这样做时,应用程序启动并返回一个 Lisp REPL。您可以与正在运行的应用程序进行交互。您可以更新它,甚至在它运行时安装新的 Quicklisp 库。

    如何构建一个独立的可执行文件

    另请参阅 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 中运行它,你会收到关于线程运行的错误:

    在运行多个线程时无法保存核心。

    从一个简单的 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 来使用它。

    对于网络应用

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

    $ ./my-web-app
    Hunchentoot server is started.
    Listening on localhost:9003.
    

    请注意,这运行的是生产网络服务器,而不是开发网络服务器, 所以我们可以立即在我们的 VPS 上运行二进制文件并从 外面。

    我们有一件事情要处理,那就是找到并放置线程 在前台运行的 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,使用 一个可选的返回码,而不是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 可以处理 logging(我们写入 stdout 或 stderr,它会写入日志):

    journalctl -f -u my-app.service
    

    它会处理崩溃并重启应用

    Restart=always
    

    它可以重启后启动应用

    [Install]
    WantedBy=basic.target
    

    启用它:

    sudo systemctl enable my-app.service
    

    调试 SBCL 错误:ensure_space: failed to allocate n bytes

    如果您的服务器上的 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

    这里的演示项目:https://lisp-journey.gitlab.io/blog/i-realized-that-to-live-reload-my-web-app-is-easy-and-convenient/

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

    ;; 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 连接到正在运行的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 在 服务器,因此新代码存在但未运行。它连接到 本地 swank 服务器,加载新代码,停止和启动应用程序 行。

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

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

    【讨论】:

    • 当你写asdf:make :my-package 时,我会想到asdf:make :my-system,因为即使系统和包通常命名相同,但情况不一定如此,它可能会带来混乱。除此之外,很好的答案!
    • 谢谢伙计!你的 Lisp 答案一如既往地棒极了!对了,你是深町荣太郎吗?如果是这样,只是想告诉你,你在 Lisp 社区的活动令人印象深刻:)
    • @coredump 好点,谢谢!疯狂物理学家:谢谢!哇,我很感谢你带我去 E. Fukamachi ^^ 我希望我是,但不是,我远没有那么有天赋和生产力!我是我链接到的那两个页面的作者,所以你可以看到,我的 github 昵称是 vindarel。
    • @Ehvince 我有几个问题。你是写这个的吗? lisp-journey.gitlab.io 作为一个对 CL 比较陌生的人,这对我很有帮助。事实上,我已经开始在自己的网站上做类似的事情,因为它可以帮助我保留材料并为将来保存笔记。另一个问题是,当涉及到 CL 时,您是否会建议一些关于调试、跟踪和其他类似主题的材料?简而言之,如果我需要通过查看这些函数中的一些值并单步执行它们来了解某些代码的工作原理,我该怎么做?
    • 是的,这就是我的 lisp 之旅,我在回答中毫不掩饰地自我推销,但我很高兴这对新人有帮助:我有这一点,因为我也是。是的,我们需要在食谱上有这样一章,我正在收集笔记。这里没有太多空间。见malisper posts,别忘了(declaimbreak example 和 log4cl。请参阅 tracestepdefadviceetc,David B. Lamkins 的“成功的 lisp”中的重要章节。在食谱中打开一个问题!
    【解决方案2】:

    要在生产环境中运行 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)))"
    

    【讨论】:

    • 这对于评估和编译没有真正的区别,因为 SBCL 可以很好地使用源文件并即时编译它。它减少了加载 fasl 文件的加载时间,因为代码已经编译。但是以这种方式调用 SBCL 是启动 Lisp 并让它执行一些代码的一种方法。
    • 如果我以这种方式将应用程序投入生产,我是否能够远程连接到它并以所谓的“热交换”的形式更改代码?
    • 为此,你最好也在你的镜像中启动一个 swank 服务器。
    【解决方案3】:

    我找到了一个博客,其中包含我已经适应了我对 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 连接到正在运行的系统。我用过几次,例如动态更改数据库链接,这种可能性的强大功能给我留下了深刻的印象。

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

    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)
    

    希望这能有所帮助。

    【讨论】:

      猜你喜欢
      • 2014-02-17
      • 1970-01-01
      • 2016-10-22
      • 2020-02-06
      • 2016-02-12
      • 2019-01-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多