Всем привет. В данной статье я бы хотел рассказать про свой домашний проект. Если коротко: Script server является веб-сервером для предоставления пользователям доступа к вашим скриптам через web-интерфейс. Сервер и скрипты запускаются локально, а параметризуются и показываются удалённо.



Предыстория


На новом месте работы, первые мои задачи были весьма рутинны и однообразны. Схематично они выглядели так:

  1. Создать файл my_file.txt
  2. Прописать туда новую конфигурацию
  3. Задеплоить на сервер
  4. Закомиттить

Шагов было больше и они были посложнее, но уже через пару таких итераций лень победила и для всех повторяющихся задач были написаны shell-скрипты, которые запускались с несколькими параметрами и иногда запрашивали дополнительные входные данные в процессе работы. В то же время, рабочий процесс стал выглядеть примерно так:

  1. спросить у руководителя проекта нужные параметры
  2. запустить скрипт
  3. доложить о готовности

Все эти пункты явно напрашивались на схлопывание в один, чтобы руководитель проекта указывал параметры и сам запускал скрипт. Но давать ему скрипты было очень проблематично по ряду причин — скрипты были адаптированы под моё окружение, они могли сбоить, они были достаточно дружелюбны для меня, но не для нетехнических людей, и т.п.

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

Поэтому я решил создать инструмент, который позволит удалённо запускать мои скрипты другим людям, без необходимости создавать необходимое окружение и в более удобной форме настраивать их запуск.

На пути создания


Схема работы



Администратор сервера (т.е. я) создаёт файлы конфигурации для каждого скрипта, в которых описывает предназначение скрипта, путь запуска и требуемые параметры. Эти файлы конфигурации используются сервером для предоставления данных о скриптах пользователю, а также для их запуска. Информация на страницу пользователя передаётся Ajax запросами.

Пользователь заполняет параметры и запускает скрипт на сервере, где его выполнение передаётся специальному обработчику. Обработчик в асинхронном режиме принимает input и предоставляет output, а также следит за процессом выполнения скрипта.

Веб сервер служит прослойкой между этим обработчиком и страницей, и через веб-сокеты осуществляет обмен данными.

Выбор инструментов


В то время я учил Python и для разработки решил использовать именно его, как неплохую практику. Для проекта был выбран Python 3, без поддержки 2й версии, поскольку необходимости в этом нет, а тратить лишнее время не хотелось.

В начале разработки для сервера я использовал Flask, но не смог сделать (читай разобраться) с асинхронностью и отслеживанием подключения/отключения клиентов, поэтому довольно быстро перевёл все на Tornado.

Касаемо web разработки: Web-разработчик из меня никакой, поэтому здесь я тоже решил немного попрактиковаться в синтаксисе и основах. В связи с этим не было использовано почти никаких фреймворков и библиотек, за исключением materializecss для более-менее приличного оформления.

Web страница представляет собой «одностраничное приложение» с минимумом HTML и созданием контента в JS по Ajax запросам.

Первая эксплуатация и улучшения


Примерно через месяц у меня получился сервер, который худо-бедно можно было использовать и я поделился им с коллегами-разработчиками, которым тоже приходилось выполнять эти рутинные задачи (которые как назло в этот период почти закончились).

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

Интересный момент: для некоторых мелких задач (связанных с работой в консоли и со скриптами) я также перестал пользоваться консолью, а перешёл на этот UI. Т.е. у меня заведено несколько конфигураций скриптов, которые я использую чаще, чем другие люди.

Эксплуатация реальным пользователем


Спустя некоторое время локального тестирования (мною и другими разработчиками), инструмент был наконец предоставлен руководителю проекта, который начал потихоньку им пользоваться. И надо отметить, что он очень доволен, т.к. это экономит в том числе и его время. Процесс «внедрения» и обучения пользователя занял примерно 5 минут.

Из-за особенностей процессов и ограничений, Script server используется в основном для тестового окружения. Но некоторые его части уже стали использоваться и для Production (в той части, где это безопасно).

Основные камни


Ниже приведён список проблем и моих личных открытий, которые мне больше всего запомнились.

Передача выходных данных из процесса выполнения скрипта на страницу в асинхронном режиме. Тут сказалось отсутствие опыта как во Flask, так и в вебе: как лучше организовать такую передачу. В итоге реализовал на вебсокетах передачу данных в виде событий (тип и данные). Также в событиях можно отправлять и другие команды во время выполнения, но пока это не используется.

Передача ввода пользователя в исполняемый скрипт. Пытался сделать запрос на ввод только при ожидании такового в скрипте, но не нашёл способа. Поэтому пользователи во время выполнения скрипта могут вводить столько информации, сколько хотят (обычный терминал ведет себя так же). Данные отправляются через тот же вебсокет.

Отслеживание отключения пользователей. Именно из-за этой проблемы отказался от Flask: в тот момент я делал не на вебсокетах, а на SSE (ну почти). Однако позже я все же перевёл передачу input/output скрипта на вебсокеты, так что возможно и Flask бы подошёл. В Торнадо же просто можно подписаться на закрытие вебсокета.

Считывание выходных данных выполнения скрипта. Тут была проблема с тем чтобы считывать только текущие доступные данные и отправлять на клиент. Считывать по строкам нельзя, т.к. например «read -p input_prompt» не переносит на следующую строку. Считывать по символам можно, но отправлять так много запросов на клиент не стоит. Считывание буфером даёт строку обрезанную в непонятных местах (а ещё и в случае UTF-8 это порождает неправильные символы). Текущее решение изобилует набором компромиссов и костылей, но в целом это побуферное считывание с отключение блокировки чтения.

Output скриптов в (не)терминальном режиме. Честно говоря для меня это было открытием: output скриптов может отличаться если запускать их в терминале или вне. Например тот же…

read -p input_prompt

… показывает input_prompt только в терминальном режиме. Не очень хорошо чего-либо ждать от пользователей, не показывая им чего именно. Пришлось разбираться с pty, чтобы обманывать запускаемые скрипты. По этой причине у меня сейчас два типа обёрток для запуска скриптов: с поддержкой терминала и без. Первый включается по-умолчанию, второй можно включить с помощью конфигурации (оставил его на всякий случай, если терминальный режим будет сбоить).

Автоскролл выходных данных скрипта. С одной стороны это решается довольно просто, с другой стороны были проблемы с настройкой автоматического отключения этого (если пользователь скроллит вручную или выделяет текст), а также с тенью, вводом пользователя и т.п. Ничего экстраординаорного, но несколько часов потратить пришлось.

Настройка панели с выходными данными скрипта таким образом, чтобы она занимала всё доступное место, но при этом никогда не выходила за границы окна. Таким образом страница всегда умещается в окно и единственный скролл добавляется именно внутри output панели. Для меня это было особенно важно, т.к. я категорично не люблю скроллы в скроллах.

Производительность страницы при очень большом количестве выходных данных скрипта. Тут со мною злую шутку сыграл следующий код:

htmlElement.innerText += "text";

Дело в том, что получение innerText это сама по себе довольно ресурсоёмкая операция, которая пытается представить входящий html как строку, даже если это просто текст. Если вызывать эту операцию пару сотен раз и для тысячи символов, то эффект не заметен, но в моем случае данных было гораздо больше и браузер просто зависал на несколько минут от такой нагрузки. Решилось изменением кода на следующий:

var textNode = document.createTextNode("text");
htmlElement.appendChild(textNode);

Висящие процессы, т.е. никто ничего не выполняет, но в процессах висит выполняемый скрипт. Причины были разные, например: пользователь закрыл страницу, а сервер не обрабатывает это. Или не закрытый дескриптор файла. Или незакрытые дочерние процессы. И это не считая других очевидных багов. Но устранялось все гуглёнием, изучение и исправлением.

Безопасность


Её нет, совсем. Данный инструмент рассчитан на локальный запуск и работу в локальной сети с доверенными пользователями. Поскольку зачастую скрипты тоже пишутся для себя, они также не являются особо защищёнными. Соответственно прежде чем запускать такой сервер, нужно быть на 100% уверенным в фаерволле и пользователях.

С точки зрения скрытия данных, в Script server на текущий момент также ничего нет. Включать пароли в качестве параметров или в конфигурацию крайне не рекомендуется, поскольку они будут логироваться и храниться в чистом виде.

Примеры и скриншоты


Для отладки в проекте я завёл тестовые конфигурации, которые можно использовать для тестирования и демонстрации. В данном разделе все предоставленные скриншоты будут основываться на них.

Конфигурация скриптов


Относительно стандартный баш скрипт с параметрами, вводом пользователя во время работы и печатью: Write file.

Простой питон-скрипт, который выводит стену текста, разбивая его на абзацы и запрашивая input пользователя: Destroy world. Эта конфигурация используется для отладки пользовательского интерфейса при отображении параметров. Содержит все возможные типы параметров: Parameterized

Скриншоты


Полный экран



Слева область выбора скриптов, справа информация по текущему открытому скрипту:

  • Название
  • Описание
  • Список параметров
  • Кнопки запуска/остановки
  • Output скрипта

Панель параметров



В этом хаосе можно увидеть семейство разных типов параметров: обязательные, списки, числа, булеаны. Скриншот создан на основе конфигурации Parameterized

Панель input/output



Сверху кнопки запуска и остановки. Поскольку скрипт запущен, активна только последняя. Ниже панель выходных данных скрипта, в самом низу поле для ввода входных данных. Панель выходных данных сжата по высоте для демо-цели, в обычном окне она гораздо выше.

Дальнейшие шаги


На данный момент разработка не ведётся, т.к. в текущем виде Script server полностью покрывает возложенные на него задачи. Что-либо оптимизировать в текущем интерфейсе я также не вижу необходимости. Таким образом мы с ним оба находимся в ожидании новых вызовов и проблем.

В частности, я был бы крайне рад, если бы данный инструмент так же пригодился кому-либо из хабрасообщества и у меня бы появилась необходимость в новых улучшениях.

» Ссылка на репозиторий проекта: github.com/bugy/script-server

Список используемых библиотек/фрейворков/благодарностей:

  • materializecss — materializecss.com
  • tornado — www.tornadoweb.org
  • Отдельное спасибо моему прежнему коллеге за поддержку и помощь в решении некоторых web проблем, особенно касаемых вёрстки.

Спасибо всем тем, кто нашёл время прочитать данную статью. Буду признателен за ценные комментарии и критику (с точки зрения подхода, приложения или особенностей реализации).
Поделиться с друзьями
-->

Комментарии (16)


  1. aardvarkx1
    08.09.2016 16:27
    +2

    А не пробывали для этой задачи использовать rundeck?


    1. bugy
      08.09.2016 17:06
      +1

      Спасибо за совет, выглядит почти как то, что и хотелось получить изначально. Изучу инструмент подробнее.
      Честно говоря, прежде чем делать, посмотрел какие уже есть инструменты, но:
      1. Плохо знал по каким названиям эти инструменты искать
      2. Особо не старался, т.к. было желание опробовать свои силы и сделать именно то, что виделось.


      1. bugy
        08.09.2016 21:43

        Посмотрел rundeck подробнее, ещё раз спасибо. Появились идеи как его можно использовать. Однако несколько для других задач — он более расширенный и сложный из-за этого, поэтому попробуем его для разработчиков. И вполне возможно заменим когда-нибудь мою поделку на rundeck.
        Пока же для нетехнических коллег продолжим использовать мой сервер, т.к. для конкретных наших задач он получается гораздо проще в использовании пользователями.


  1. manefesto
    08.09.2016 16:31

    Это что-то вроде lite-версии ansible?
    Достаточно использовать paramiko и сделать над ним обертку
    Хотя есть плюс, сделано симпатично, но функционал урезан


    1. bugy
      08.09.2016 17:20

      перенесено в ветку выше


    1. bugy
      08.09.2016 17:20

      Что-то совсем вроде и совсем lite-версии ansible. Прям как из пушки по воробьям.

      paramiko — до этого с ним не сталкивался, но не совсем понятно как вы предлагаете его здесь использовать. Можете пожалуйста пояснить?

      > Хотя есть плюс, сделано симпатично, но функционал урезан
      Спасибо за комплимент интерфейсу.
      По функционалу вопрос потребностей, если честно. Из того, что на мой взгляд критично не хватает, это авторизация. Но опять же в моем случае не так много пользователей и всем им я доверяю.


    1. gigimon
      08.09.2016 21:18

      Обертка вокруг paramiko для этих целей называется fabric :)


  1. urtow
    08.09.2016 16:31

    А чем плох Jenkins для таких задач?


    1. bugy
      08.09.2016 16:58

      Насколько я знаю в Jenkins нельзя настроить интерактивное выполнение. Т.е. любой скрипт с запросом информации у пользователя обречен.
      А в моих скриптах таковой присутствует.
      Ну и честно говоря CI не рассматривал из-за их тяжеловесности. Т.е. для простой задачи слишком много действий потребуется в данном случае


      1. urtow
        08.09.2016 17:28

        Да, с интерактивом у Jenkins туго, но практически все скрипты можно переписать в пакетный режим.


        1. bugy
          08.09.2016 17:48

          Практически все скрипты можно переписать в пакетный режим.

          Не могу не согласится, особенно в моем случае, когда не так много взаимодействия с пользователями. Однако:


          • В ряде случаев интерактив более удобен (т.к. в рантайме можно предоставить выбор исходя из состояния выполнения скрипта или других часто изменяющихся значений)
          • Не всегда ты отвечаешь за эти скрипты (если исполняемое нечто это вообще скрипт) и можешь их изменять.


      1. SirEdvin
        08.09.2016 17:48

        У Jenkins Pipeline есть такая возможно через директиву input. Вот тут про это неплохо написали: https://habrahabr.ru/post/302274/


      1. hamnsk
        09.09.2016 08:55

        expect вам в помощь, все решаемо главное знать инструменты


        1. bugy
          09.09.2016 09:03

          Спасибо. Про expect я знаю. С помощью него можно заменить практически всё на пакетный режим.
          Но интерактивность это не всегда зло. К тому же expect обертки тоже нужно поддерживать (если скрипты меняются).
          Я даже подумывал прикрутить вынос некоторых интерактивных параметров через expect в стартовые параметра скрипта. Но по причинам выше не особо приоритезировал эту задачу.


          главное знать инструменты
          Согласен, но в данном случае из имеющихся инструментов мне больше по душе пришелся rundeck. Для конкретно моей задачи.


  1. DuD
    08.09.2016 18:25

    Вы изобрели Rundeck


  1. phikus
    15.09.2016 12:56

    Хотел было запустить на поиграться на ноуте с вин7, но оказалось проект не кросс-платформенный, для серверной части нужен fcntl
    Есть варианты обойти это?