Развертывание общих Lisp веб-приложений

Мне интересно, как начать развертывание веб-приложения Common Lisp, написанного, например, Hunchentoot, Wookie, Woo или даже Clack.

То есть предположим, что я пишу приложение, содержащее некоторые файлы, пакеты и т.д. Обычно, когда я работаю локально, я просто запускаю команду в REPL, которая запускает сервер, а затем набирает его с помощью localhost:8000 или что-то в этом роде.

Однако я немного озадачен тем, что происходит для развертывания приложения на производственном сервере, таком как AWS EC2. В какой форме я должен развернуть код Lisp? Существуют ли разные варианты? Что произойдет, если сервер необходимо перезапустить или испытывать проблемы?

Ответ 1

В последнее время я кое-что выяснил, создав автономные исполняемые файлы для веб-приложений, и написал об этом в lisp-trip/web-dev (разделы по доставке и развертыванию), а также в части по сборке в Common Lisp Cookbook/scripting # для веб-приложений.

Я копирую интересные части здесь, там немного больше на каждом ресурсе. Редактирование приветствуется, прежде всего на этих ресурсах, спасибо!

изменить июль 2019 г. Я добавил страницу в кулинарную книгу: https://lispcookbook.github.io/cl-cookbook/web.html

изменить: см. также список инструментов и платформ, которые предоставляют профессиональную поддержку CL: https://github.com/CodyReichert/awesome-cl#deployment

Как создать автономный исполняемый файл

Смотрите также https://github.com/CodyReichert/awesome-cl#interfaces-to-other-package-managers о привязках к пакетам Homebrew и Debian.

С SBCL

Как создавать (автономные) исполняемые файлы зависит от реализации (см. ниже Buildapp и Rowsell). С SBCL, как говорит его документация, это вопрос:

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

sb-ext является расширением SBCL для запуска внешних процессов. См другие SBCL расширения (многие из них сделаны переносимыми для реализации в других библиотеках).

:executable t говорит, что нужно создать исполняемый файл вместо образ. Мы могли бы создать изображение, чтобы сохранить состояние нашего текущего Lisp, чтобы вернуться к работе позже. Особенно полезно, если мы проделали большую работу, требующую больших вычислительных ресурсов.

Если вы попытаетесь запустить это в Slime, вы получите сообщение об ошибке:

Невозможно сохранить ядро с несколькими запущенными потоками.

Запустите команду из простой реплики SBCL.

Я полагаю, у вашего проекта есть зависимости Quicklisp. Вы должны тогда:

  • убедитесь, что Quicklisp установлен и загружен при запуске Lisp (вы завершена установка 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, который читает параметры из .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

Розуэлл, менеджер по внедрению и многое другое более того, также есть команда ros build, которая должна работать для многих Реализации.

Мы также можем сделать наше приложение устанавливаемым с помощью Roswell ros install my-app. Смотрите документацию.

Мы закончим с словом на 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 и получить доступ к приложению из снаружи.

У нас есть одна вещь, чтобы заботиться, это найти и положить нить работающий веб-сервер на переднем плане. В нашей функции 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 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).

Разбор аргументов командной строки

см. Поваренную книгу.

развертыванияПросто с исполняемым файлом. Веб-приложение сразу видно снаружи.

На Heroku

Смотрите этот сборочный пакет.

Демонизация, перезапуск в случае сбоев, обработка журналов

Посмотрите, как это сделать в вашей системе.

Большинство дистрибутивов GNU/Linux теперь поставляются с Systemd.

Примеры поиска результата:

Это так же просто, как написать файл конфигурации:

# /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: sure_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 [email protected]

это безопасно перенаправит порт 4006 на сервере на example.com Порт нашего локального компьютера 4006 (Swanks принимает подключения от локальный).

Мы подключаемся к бегущему Суонку с помощью M-x slime-connect и набираем порт 4006.

Мы можем написать новый код:

(defun dostuff ()
  (format t "goodbye world ~a!~%" *counter*))
(setf *counter* 0)

и оцените его, как обычно, с M-x slime-eval-region, например. Выход должен измениться.

На странице CV Berry больше указателей.

Горячая перезагрузка

Пример с Quickutil. Смотрите заметки о путешествии.

Это должно быть выполнено на сервере (простая команда fabfile может вызвать это через сш). Предварительно fab update запустил git pull на сервер, поэтому новый код присутствует, но не работает. Это подключается к локальный сервер swank, загружает новый код, останавливает и запускает приложение в строка.

Непрерывная интеграция, непрерывная доставка исполняемых файлов, Docker

Видеть https://lispcookbook.github.io/cl-cookbook/testing.html#continuous-integration

Ответ 2

Чтобы запустить созданный образ lisp, вы можете сгенерировать файл fasl из вашего кода lisp с помощью:

(compile-file "app.lisp")

запустите созданный файл .fas, вызвав sbcl.

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

Ответ 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. Я использовал его несколько раз, например, чтобы изменить ссылку на базу данных "на лету", и был впечатлен силой такой возможности.

Бегущая система может быть прервана:

telnet 127.0.0.1 6700

и инициируется чем-то вроде:

nohup ccl -l initcclserver.lisp >& server.out &

В предыдущей версии script я нашел детали, специфичные для SBCL, поэтому, если вы ее используете, вы можете изменить script.

Для принятия завершающих соединений:

(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)

Надеюсь, это поможет.