Debian.pro/

Про Debian


Запускаем один NoVNC для доступа к нескольким VNC. Например, к консолям KVM-виртуалок.

Статью пишу «по памяти», воспроизвести вживую негде (да и лень).
Если найдете ошибки и что-то не заработает — обязательно пишите.
Я бы пока не рассматривал эту статью, как решение из разряда «copy-paste и работает».
Но если вы понимаете происходящее ниже — то никакой проблемы запустить эту штуку нет, там всё банально.
Ценность здесь, скорее, именно в самой идее использования novnc и том, что гуглить придётся на 20 минут меньше.



Думаю, все помнят, что KVM позволяет смотреть в «экран» виртуальной машины через VNC (ну и кнопочки в туда нажимать). На «вручную» поднятых libvirt+kvm все это делают через локально установленный virsh или проброс порта vnc на локальный хост по ssh.
Эти способы, мягко говоря, не очень удобны. А главное — нельзя дать доступ к «экрану» (буду в дальнейшем использовать именно эту формулировку, чтобы не путать аббревиатуры KVM — keyboard/video/mouse и kernel virtual machine) одной виртуалки отдельному пользователю, не позволяя ходить в экраны других виртуалок. Если быть совсем точным, то нет _простого_ способа сделать это (из говна, палок, баша и кастомного скрипта в качестве шелла — можно, в принципе).
Вот тут на помощь и приходит NoVNC. Вообще эта штука — «браузерный VNC-клиент». На сервере крутится написанная на питоне фигулина, которая одним концом ходит в VNC (в нашем случае от kvm-виртуалок), а другим концом показывает позволяет пользователю зайти по http(s) и посмотреть в эту консоль (ну и кнопки понажимать да мышку подвигать). Подойдет для этого любой современный браузер. Даже с мобильника получится посмотреть (хотя кнопки как раз понажимать уже, скорее всего, не выйдет).
Ну а доступом по https, по давно уже сложившейся здесь традиции, мы будем рулить через nginx, который будет проксировать весь трафик от пользователя к novnc.
В принципе, ничего сложного в запуске NoVNC нет. Счекаутил из гита (не, ну можно пакет поставить, но там совсем старая версия), запустил, иди в host:6080 браузером. Но просто это только в том случае, если нам нужна одна консоль одной виртуалки. Когда у нас несколько пользователей, много виртуалок (а тем более, когда они по разным dom0-хостам разнесены) — то всё уже не так тривиально. Поэтому и родилась идея оформить статью на эту тему.
Собственно, секрет простой — нужно долго и внимательно читать документацию, дойдя до информации про опцию target-config у бинарника websockify/run в репозитории novnc. Но читать придётся достаточно долго =) Да и знать нужно, что искать. Поэтому погнали.

Первым делом нужно «прибить гвоздями» порты VNC всех виртуалок. Если вы используете libvirt, то все виртуалки по умолчанию создаются без указания конкретного порта для VNC. Первая запустившаяся виртуалка забирает себе 127.0.0.1:5900, а дальше все инкрементят порт. Виртуалки запускаются в случайно порядке. А NoVNC рассчитан всё же на статические порты (он-то бедненький ничего про виртуалки не знает, а знает только про порты).
Смотрим название своей машины в virsh list. Допустим, vds1 для примера. Запускаем редактирование конфига (именно этой командой, иначе потом изменения потрутся — ну и на всякий случай напомни, что из вима выйти с сохранением можно написав :wq):

root@server:~# virsh edit vds1

В xml-ке находим такую строчку:

<graphics type='vnc' port='-1' autoport='yes'/>

Меняем на что-то такое:

<graphics type='vnc' port='5910' autoport='yes'/>

Порт лучше взять чуть выше, чем 5900 (мало ли libvrit-у снесёт голову). Я беру порт 5900+максимально возможное количество виртуалок на хосте+единица. Себе другую логику придумайте. Ну или вообще берите порты вида 15900, это на самом деле не так важно.
После редактирования конфига виртуалку нужно целиком потушить. Не ребутнуть, а именно потушить. Если она не отключится сама halt-ом — придется сказать ей virsh destroy; virsh start. Пытался я virsh-ем на лету сменить порт после смены конфига, оно меня проигнорировало. Но, в целом, удалось сделать это из virt-manager (удалив VNC и добавив новый с явным указанием порта).
Далее подготавливаем nginx с https — раз, два, три. Передавать через голые интернеты рутовый пароль в VNC-трафике по http — точно не самая разумная затея (при этом по локалхосту у нас тоже будет https на всякий случай).
А теперь переходим к запуску, собственно, novnc.
Создаём пользователя:

root@server:~# adduser novnc

Ставим пакеты, которые в дальнейшем понадобятся для запуска кода:

root@server:~# apt-get install git-core python-numpy

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

root@server:~# mkdir /usr/share/novnc; mkdir /usr/share/novnc/utils/websockify

Клонируем репозиторий:

root@server:~# git clone https://github.com/novnc/noVNC.git /usr/share/novnc
root@server:~# git clone https://github.com/novnc/websockify /usr/share/novnc/utils/websockify

Чоуним (там будут какие-то временные файлы, так что чоуним под novnc целиком. Лучше, конечно, посидеть поразбираться, в какие именно каталоги/файлы нужен доступ на запись пользователю novnc).

root@server:~# chown -R novnc:root /usr/share/novnc

Заводим каталог, в котором будут лежать секретные файлы для novnc:

root@server:~# mkdir /etc/novnc

Копируем сертификат nginx-а в каталог novnc (в статьях про nginx мы его положили именно сюда):

root@server:~# cp /etc/nginx/ssl/cert.pem /etc/novnc/cert.pem

Создаём файл /etc/novnc/tokens.list (о том, что писать в файл, поговорим чуть позже):

root@server:~# touch /etc/novnc/tokens.list

Чоуним каталог /etc/novnc и его содержимое. На этот раз только root может редактировать файлы, читать может группа novnc, а все остальные туда доступа иметь не должны.

root@server:~# chown -R root:novnc /etc/novnc
root@server:~# chmod -R 750 /etc/novnc
root@server:~# chmod -R 640 /etc/novnc/*

Пишем Unit для systemd (например, в /etc/systemd/system/novnc.service)

[Unit]
Description=NoVNC
After=network.target

[Service]
Type=simple
User=novnc
WorkingDirectory=/usr/share/novnc
ExecStart=/usr/share/novnc/utils/websockify/run --web /usr/share/novnc/ --target-config /etc/novnc/tokens.list 127.0.0.1:6080 --cert=/etc/novnc/cert.pem
Restart=on-abort

[Install]
WantedBy=multi-user.target

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

root@server:~# systemctl daemon-reload; systemctl enable novnc

Теперь о том, что нужно написать в файл /etc/novnc/tokens.list. У меня он выглядит примерно так:

# vds1:
IPJASFAKHFOIASFOIHAOISFHOIAHSFOIHAOIHFOIAHF: 127.0.0.1:5910

# vds2:
IAHFPOAHFSPHAFSIHAFSHIAHFSOIHAOSFOIAHSFOIHA: 127.0.0.1:5911

Комментарии — просто для моего удобства. Часть до двоеточия — это некий токен (лучше без спецсимволов, дабы не заниматься сексом с url-escaping в дальнейшем, чем длиннее — тем лучше), часть после первого двоеточия — хост и порт VNC (это может быть не только локалхост, но будьте осторожнее. Openstack, например, в свои молодые годы доинтегрировался до того, что гонял этот самый трафик до VNC-консолей на dom0-железках от novnc-сервера в открытом виде), куда novnc будет подключаться, получив этот токен в аргументах.
Теперь можно запустить novnc, если вы ничего не пропустили:

root@server:~# service novnc start

Если вот такая команда:

root@server:~# lsof -i :6080

Говорит, что порт занят процессом python под пользователем novnc, то, скорее всего, всё хорошо. Если нет — то по ссылке выше про systemd написано, как почитать лог юнита, чтобы подебажить, почему оно не запустилось.
Осталось только аккуратненько вывесить novnc наружу.
Возвращаясь к изначальной идеей — nginx должен обеспечивать минимальную защиту (пусть это будет хотя бы basic auth), потому что ссылку могут случайно пошарить (или гугл её насканит как-то, по логам браузера-шпиона, например). Ну и было бы неплохо, чтобы для каждого токена/порта novnc можно было сделать отдельный набор логинов-паролей. Ну.. Nginx так умеет (уж простите, люблю я его всей душой).
В /etc/nginx/sites-available/novnc.conf или куда-то туда (симлинк потом сделать в sites-enabled не забудьте) пишем конфиг ниже. server_name берите тот, для которого получали сертификат.

# мапим наши novnc-токены в аргументы novnc из tokens.list:
map $arg_token $passwd_file {
    IPJASFAKHFOIASFOIHAOISFHOIAHSFOIHAOIHFOIAHF /etc/novnc/vds1.passwd;
    IAHFPOAHFSPHAFSIHAFSHIAHFSOIHAOSFOIAHSFOIHA /etc/novnc/vds2.passwd;
    # дефолтный файл для остальных токенов:
    default /etc/novnc/default.passwd;
}

# ниже конфиг самого server{} с novnc внутри.
server {
    # допустим, dom0.example.com у нас есть в сертификате.
    server_name dom0.example.com;

    # параметры для https (редирект с http сами сделайте. Или вообще http не слушайте).
    listen 443 ssl;

    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/cert.pem;
    ssl_dhparam /etc/nginx/ssl/dhparam.pem;
    ssl_prefer_server_ciphers on;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers kEECDH+AES128:kEECDH:kEDH:-3DES:kRSA+AES128:kEDH+3DES:DES-CBC3-SHA:!RC4:!aNULL:!eNULL:!MD5:!EXPORT:!LOW:!SEED:!CAMELLIA:!IDEA:!PSK:!SRP:!SSLv2;
    ssl_session_cache shared:SSL:64m;
    ssl_session_timeout 28h;

    # Авторизация http-basic auth. Путь до htpasswd файла - через переменную
    auth_basic "VNC";
    auth_basic_user_file $passwd_file;

    # проксируем обычные файлы в novnc (2 следующих location):
    location /vnc_lite.html {
        proxy_pass https://127.0.0.1:6080;
    }
    # честно - не помню, нахрена тут mp3 =)
    location ~* ^.+\.(css|js|png|svg|ttf|woff|oga|mp3)$ {
        proxy_pass https://127.0.0.1:6080;
    }
    # проксируем websocket /websockify в novnc. Если ExecStart не меняли - то вот этот кусок как раз советую скопипастить.
    location /websockify {
        proxy_http_version 1.1;
        proxy_pass https://127.0.0.1:6080/websockify;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # VNC connection timeout
        proxy_read_timeout 14400;
        proxy_send_timeout 14400;

        # Disable cache
        proxy_buffering off;
    }
}

Когда конфиг положите в правильное место — рестартьте nginx.
Только проверьте конструкцию с авторизацией — я это писал по памяти, может навскидку и не заработать.
Из открытых вопросов — какой url дать пользователю, чтобы он зашел в консоль? Вот такой:

https://dom0.example.com/vnc_lite.html?path=websockify%3Ftoken=TOKEN

Если всё настроено правильно — у него там спросят логин-пароль для https, а потом он увидит экран виртуалки.

Ну и для тех, кто просто посмотреть зашел. Скриншотик открытой консоли какой-то виртуалки в браузере (фуллсайз по клику).
NoVNC

ЗЫ — я знаю про поддержку WSS в самом QEMU, но собрать из них готовое решение, которое поместилось бы в формате одной статьи без кода не вышло. Ни в 2010-м, ни в 2018-м. Насколько я успел понять, она нужна для SPICE, который ничем не удобнее VNC внутри virsh.
ЗЗЫ — vnc error 1000 означает, что вам стоит закрыть другой VNC-клиент, если что.

UPD, 22.10 — обновил статью для актуальной версии novnc на гитхабе (1.1+). Хотя, в целом, она должна быть актуальна и для старых версий, но имейте в виду.


Комментарии (6):

  1. karavan :

    Ко всем подобным статьям еще бы прикручивать инфу на пару-тройку популярных браузеров по отключению браузерных хот-кеев, чтобы полноценно использовать консоль.

  2. Ну это всё же не способ полноценно использовать консоль.
    Оно чаще нужно рутовый пароль сбросить или fsck прогнать. Ну и ядро старое выбрать, если что-то пошло не так.

    Работать в этой штуке вместо ssh — такое себе)

  3. А есть готовое решение для мультипросмотра VNC через NoVNC? Чтобы 3×4 экранчиков было? Можно даже без переключения, просто посмотреть.

  4. Не думаю, что кто-то заморачивался с готовым, но оно быстро делается через ifram-ы, просто воткните их в поля в таблице, как нужно.

  5. ptflp :

    yudai/gotty запустил и не паришься

  6. Допустим.
    А как это решает задачу «попыриться в серийную консоль гостя»? ) Или sol настраивать в каждом госте? Для гостевой винды не получится.

Написать комментарий