Сценарий знаком многим администраторам. На рабочей машине стоит десяток клиентов для удалённых подключений - PuTTY для SSH, Remote Desktop Connection для Windows-серверов, какой-нибудь TightVNC для старых машин. Каждый со своими настройками, профилями, сохранёнными паролями. При смене ноутбука всё это богатство переносится с приключениями. А если нужно подключиться с чужого компьютера или планшета, то начинается отдельная история про установку нужных клиентов.
Apache Guacamole решает эту головную боль одним приёмом. Это бесплатное приложение с открытым исходным кодом для безклиентского удалённого доступа к рабочим столам и серверам через обычный веб-браузер. Платформа поддерживает протоколы VNC, RDP и SSH, а отображение делается через HTML5. На клиентской стороне не нужно ставить вообще ничего - открыл браузер, ввёл адрес сервера, выбрал нужное подключение и работаешь. Работает в большинстве дистрибутивов Linux, а клиент запускается в любом современном браузере. Ниже разобрана пошаговая установка серверной части Guacamole и клиента через Docker на Ubuntu 22.04.
Подготовка чистого сервера и предварительные требования для развёртывания связки контейнеров
Понадобится сервер с установленной Ubuntu 22.04 LTS. Минимальные требования к ресурсам у Guacamole скромные, но с учётом всей связки из трёх контейнеров (база данных, сервер и клиент) стоит закладывать как минимум 2 гигабайта оперативной памяти и пару процессорных ядер. На сервере должен быть настроен root-пароль, поскольку часть команд требует привилегий суперпользователя. Все команды в туториале выполняются от имени root - если работа идёт под обычным пользователем, перед каждой командой подставляйте sudo.
Архитектурно Guacamole в контейнеризованном виде разбит на три отдельных компонента. Первый - база данных MySQL, в которой хранятся учётки пользователей, описания соединений и история подключений. Второй - сам сервер Guacamole (guacd), который реализует протоколы VNC, RDP и SSH и преобразует их в поток данных для веб-клиента. Третий - веб-клиент (guacamole), который рендерит интерфейс в браузере и общается с guacd через свой внутренний протокол. Такое разделение даёт гибкость - например, можно держать базу на отдельной выделенной машине или масштабировать клиентскую часть горизонтально.
Установка движка Docker Engine из официального репозитория для последующего запуска компонентов Guacamole
Перед установкой контейнеров нужен сам Docker. В стандартных репозиториях Ubuntu лежит пакет docker.io, но он отстаёт от актуальных релизов на несколько месяцев. Для production-сценариев правильнее тянуть свежую сборку прямо из репозитория Docker.
Сначала ставятся вспомогательные пакеты, без которых не получится корректно подключить внешний репозиторий по HTTPS.
apt install ca-certificates curl gnupg lsb-release -y
Сюда входят корневые сертификаты, утилита curl для скачивания GPG-ключа, набор gnupg для работы с подписями и lsb-release для определения кодового имени релиза Ubuntu. Флаг -y подтверждает все запросы автоматически.
Добавляем GPG-ключ Docker и сам репозиторий в список источников APT.
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor > /etc/apt/trusted.gpg.d/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list
Флаги curl стоит разобрать отдельно. Опция -f возвращает ошибку при HTTP-неудачах вместо тихого сохранения страницы с ошибкой. Параметр -s отключает прогресс-бар, -S всё-таки оставляет вывод сообщений об ошибках, а -L разрешает следовать редиректам. Утилита gpg --dearmor конвертирует текстовый ASCII-armored ключ в бинарный формат для APT. Команда dpkg --print-architecture возвращает архитектуру системы (обычно amd64), а lsb_release -cs выдаёт кодовое имя релиза - для 22.04 это jammy.
Обновляем кэш и устанавливаем сам движок.
apt update -y
apt install docker-ce docker-ce-cli containerd.io -y
Здесь ставятся три пакета. Первый, docker-ce, это собственно демон-движок, который рулит всеми контейнерами. Второй, docker-ce-cli, добавляет утилиту командной строки docker. Третий, containerd.io, представляет собой низкоуровневый рантайм, на котором фактически крутятся контейнеры.
Проверка установленной версии.
docker --version
Вывод подтвердит работоспособность.
Docker version 20.10.18, build b40c2f6
Конкретный номер сборки зависит от момента установки.
Установка Docker Compose для удобного управления связкой из нескольких контейнеров
Docker Compose позволяет описывать многоконтейнерные приложения декларативно через YAML-файл и поднимать всю связку одной командой. В рамках текущего туториала контейнеры запускаются вручную через docker run, но Compose всё равно ставится для последующих более сложных сценариев.
curl -sL "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
Команда curl с параметрами -sL скачивает бинарник Compose под текущую архитектуру. Подстановки $(uname -s) и $(uname -m) автоматически определяют операционную систему (Linux) и архитектуру процессора (x86_64 или aarch64), что делает команду универсальной. Утилита chmod +x делает скачанный файл исполняемым.
Проверка установленной версии.
docker-compose --version
docker-compose version 1.29.2, build 5becea4c
Загрузка образов Guacamole-сервера и клиента из Docker Hub перед запуском контейнеров
Перед запуском контейнеров полезно отдельно загрузить нужные образы. Это разделяет фазу скачивания (которая может занять время на медленном канале) и фазу запуска, плюс позволяет проверить корректность образов до старта.
Загрузка серверной части Guacamole.
docker pull guacamole/guacd
Вывод покажет процесс по слоям.
Using default tag: latest
latest: Pulling from guacamole/guacd
4b7b4a8876e2: Pull complete
4e542e9cf89d: Pull complete
9741340fbbb2: Pull complete
96fa725029d6: Pull complete
0f0a6df13f2a: Pull complete
a4fa6e99a790: Pull complete
07365dfaa371: Pull complete
Digest: sha256:efdc09beba21e2c5d7c8c77e8c6b95ffd173d75645c9c41b6024f5b835d2a034
Status: Downloaded newer image for guacamole/guacd:latest
docker.io/guacamole/guacd:latest
Каждая строка с Pull complete означает успешно загруженный слой. Контейнерные образы устроены как набор слоёв, что экономит трафик и место при наличии общих базовых слоёв с другими образами. Длинная строка с Digest - криптографическая подпись для проверки целостности.
Загрузка клиентской части.
docker pull guacamole/guacamole
Процесс аналогичный.
Using default tag: latest
latest: Pulling from guacamole/guacamole
2b55860d4c66: Pull complete
2ca45fc4c4ca: Pull complete
0cd32add6672: Pull complete
ac52cbbb8ca2: Pull complete
7f7dc3a9f4cc: Pull complete
5d7996a24402: Pull complete
4819d3e4118d: Pull complete
055afbac1f72: Pull complete
23ee772344b7: Pull complete
e44569de6126: Pull complete
f7d7e8014b18: Pull complete
Digest: sha256:8a8db8cf9f5359aa20547382213a42a720ea1c5fe86460ded727061e1995d9f2
Status: Downloaded newer image for guacamole/guacamole:latest
docker.io/guacamole/guacamole:latest
Клиентский образ толще серверного - в нём лежит веб-приложение со всеми статиками, JavaScript-кодом и зависимостями.
Проверка списка скачанных образов.
docker images
Таблица покажет оба образа с их идентификаторами и размерами.
REPOSITORY TAG IMAGE ID CREATED SIZE
guacamole/guacd latest 4086ac9e35a7 9 hours ago 271MB
guacamole/guacamole latest 959856a45436 10 hours ago 432MB
Серверный guacd весит 271 мегабайт, клиент guacamole около 432 мегабайт - вполне типичные значения для контейнеров с полноценной функциональностью.
Запуск контейнера с базой данных MySQL и наполнение её схемой для нужд Guacamole
Guacamole требует базы данных для хранения учёток пользователей и описаний соединений. Контейнер MySQL стартует следующей командой.
docker run --name guacamoledb -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=guacdb -d mysql/mysql-server
Разберём параметры. Флаг --name задаёт человекочитаемое имя контейнера для последующих обращений. Опции -e задают переменные окружения внутри контейнера. Переменная MYSQL_ROOT_PASSWORD устанавливает пароль root-пользователя MySQL - в production это должен быть длинный криптостойкий пароль, а не строка password из примера. Переменная MYSQL_DATABASE автоматически создаёт базу с указанным именем при старте контейнера. Флаг -d переводит контейнер в фоновый режим. Последний аргумент - имя образа из официального репозитория MySQL.
Вывод покажет загрузку образа и идентификатор созданного контейнера.
latest: Pulling from mysql/mysql-server
cdd8b07c6082: Pull complete
c2f1720beca1: Pull complete
39f143a8d6de: Pull complete
118a8285b641: Pull complete
b45cbcaf75c7: Pull complete
d4574372e600: Pull complete
1f565a3cbc52: Pull complete
Digest: sha256:e30a0320f2e3c7b7ee18ab903986ada6eb1ce8e5ef29941b36ec331fae5f10b2
Status: Downloaded newer image for mysql/mysql-server:latest
c7a9309eac20a7d0bb6f0a16460bf2b678aae741c201efae8974ea64a3736595
Создаём каталог для хранения скрипта инициализации базы.
mkdir -p /opt/guacamole/mysql
Флаг -p создаст всю цепочку каталогов разом, если промежуточных папок не существует.
Образ guacamole/guacamole содержит вспомогательный скрипт, который умеет генерировать SQL для создания схемы базы. Запускаем его через одноразовый контейнер и сохраняем результат в файл.
docker run --rm guacamole/guacamole /opt/guacamole/bin/initdb.sh --mysql > /opt/guacamole/mysql/01-initdb.sql
Параметр --rm заставит Docker удалить контейнер сразу после завершения работы команды - идеальный вариант для одноразовых утилитарных запусков. Флаг --mysql указывает скрипту генерировать SQL именно под MySQL (поддерживается также PostgreSQL и SQL Server). Перенаправление через > сохраняет вывод в локальный файл.
Копируем сгенерированный SQL внутрь работающего контейнера MySQL.
docker cp /opt/guacamole/mysql/01-initdb.sql guacamoledb:/docker-entrypoint-initdb.d
Утилита docker cp работает в обе стороны - можно копировать как с хоста в контейнер, так и обратно. Каталог /docker-entrypoint-initdb.d внутри образа MySQL имеет особое значение - все SQL-файлы оттуда автоматически выполняются при первой инициализации базы.
Подключаемся к контейнеру MySQL через интерактивный bash.
docker exec -it guacamoledb bash
Флаг -i делает stdin интерактивным, флаг -t выделяет псевдо-терминал. Без этой связки команды работают, но интерактивная оболочка получается кривой.
Приглашение терминала меняется на bash контейнера.
bash-4.4#
Проверяем, что SQL-файл действительно лёг в нужный каталог.
cd /docker-entrypoint-initdb.d/
ls
01-initdb.sql
Подключаемся к shell самого MySQL.
mysql -u root -p
При запросе пароля вводим тот, что был задан через переменную окружения MYSQL_ROOT_PASSWORD.
Переключаемся на базу guacdb и выполняем скрипт инициализации.
use guacdb;
source 01-initdb.sql;
Команда source в MySQL-клиенте читает указанный файл и построчно выполняет содержащиеся в нём команды. Это удобный способ применить большой SQL-скрипт без копирования его в командную строку.
Проверяем созданные таблицы.
show tables;
В выводе появится длинный список таблиц Guacamole.
+---------------------------------------+
| Tables_in_guacdb |
+---------------------------------------+
| guacamole_connection |
| guacamole_connection_attribute |
| guacamole_connection_group |
| guacamole_connection_group_attribute |
| guacamole_connection_group_permission |
| guacamole_connection_history |
| guacamole_connection_parameter |
| guacamole_connection_permission |
| guacamole_entity |
| guacamole_sharing_profile |
| guacamole_sharing_profile_attribute |
| guacamole_sharing_profile_parameter |
| guacamole_sharing_profile_permission |
| guacamole_system_permission |
| guacamole_user |
| guacamole_user_attribute |
| guacamole_user_group |
| guacamole_user_group_attribute |
| guacamole_user_group_member |
| guacamole_user_group_permission |
| guacamole_user_history |
| guacamole_user_password_history |
| guacamole_user_permission |
+---------------------------------------+
Каждая таблица отвечает за свой кусок функциональности. Например, guacamole_user хранит учётки пользователей, guacamole_connection описывает настроенные подключения, guacamole_connection_history фиксирует историю сеансов работы. Такая нормализованная структура позволяет гибко управлять правами доступа на уровне отдельных соединений и групп пользователей.
Создаём отдельную учётку MySQL для работы Guacamole с базой - использовать root для приложения было бы грубейшим нарушением принципа минимальных привилегий.
create user guacadmin@'%' identified by 'password';
grant SELECT,UPDATE,INSERT,DELETE on guacdb.* to guacadmin@'%';
Знак процента означает, что подключаться этой учёткой можно с любого хоста (в данном случае - из соседнего Docker-контейнера). Выдаём только четыре базовых права - SELECT для чтения, UPDATE для изменений, INSERT для добавления записей, DELETE для удаления. Привилегий вроде DROP или ALTER, способных снести таблицы целиком, у учётки приложения быть не должно.
Сбрасываем кэш привилегий и выходим из MySQL.
flush privileges;
exit;
Выходим из bash-оболочки контейнера.
exit
Проверяем, что контейнер базы работает.
docker ps
В выводе должна появиться строка с guacamoledb и статусом healthy.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c7a9309eac20 mysql/mysql-server "/entrypoint.sh mysq…" 2 minutes ago Up 2 minutes (healthy) 3306/tcp, 33060-33061/tcp guacamoledb
Метка healthy в статусе означает, что встроенная проверка работоспособности образа отрабатывает успешно - база отвечает на тестовые запросы.
При желании можно посмотреть логи запуска MySQL.
docker logs guacamoledb
В выводе будут стандартные сообщения о запуске сервера.
[Entrypoint] Starting MySQL 8.0.30-1.2.9-server
2022-10-02T11:31:52.589985Z 0 [Warning] [MY-011068] [Server] The syntax '--skip-host-cache' is deprecated and will be removed in a future release. Please use SET GLOBAL host_cache_size=0 instead.
2022-10-02T11:31:52.592352Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.30) starting as process 1
2022-10-02T11:31:52.602604Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
2022-10-02T11:31:52.875859Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
2022-10-02T11:31:53.131572Z 0 [Warning] [MY-010068] [Server] CA certificate ca.pem is self signed.
2022-10-02T11:31:53.131639Z 0 [System] [MY-013602] [Server] Channel mysql_main configured to support TLS. Encrypted connections are now supported for this channel.
2022-10-02T11:31:53.163561Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Bind-address: '::' port: 33060, socket: /var/run/mysqld/mysqlx.sock
2022-10-02T11:31:53.163648Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.30' socket: '/var/lib/mysql/mysql.sock' port: 3306 MySQL Community Server - GPL.
Строка ready for connections внизу подтверждает, что база полностью готова принимать запросы.
Запуск серверной части Apache Guacamole в виде отдельного контейнера
Серверная часть guacd работает как фоновый демон, принимающий запросы от веб-клиента и устанавливающий реальные удалённые подключения по VNC, RDP или SSH.
docker run --name guacamole-server -d guacamole/guacd
Запуск минималистичный - только имя и фоновый режим. Никаких переменных окружения или проброса портов наружу не нужно, поскольку guacd общается только с веб-клиентом через внутреннюю Docker-сеть.
Проверка логов запуска.
docker logs --tail 10 guacamole-server
Параметр --tail 10 показывает только последние 10 строк - удобно для быстрого взгляда без листания тонны вывода.
guacd[7]: INFO: Guacamole proxy daemon (guacd) version 1.4.0 started
guacd[7]: INFO: Listening on host 0.0.0.0, port 4822
Строка о прослушивании порта 4822 подтверждает успешный старт. Этот порт открыт только внутри Docker-сети и не виден снаружи хоста, что хорошо для безопасности.
Проверка списка работающих контейнеров.
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
51b2efdab0db guacamole/guacd "/bin/sh -c '/usr/lo…" 26 seconds ago Up 25 seconds (health: starting) 4822/tcp guacamole-server
c7a9309eac20 mysql/mysql-server "/entrypoint.sh mysq…" 3 minutes ago Up 3 minutes (healthy) 3306/tcp, 33060-33061/tcp guacamoledb
Два контейнера крутятся, статус health starting у guacd говорит о том, что проверка работоспособности ещё проводит начальные тесты. Через минуту-другую он переключится на healthy.
Запуск веб-клиента Guacamole со связками к базе и серверу через механизм Docker links
Финальный кусок мозаики - веб-клиент, который связывает базу данных и сервер guacd в единое целое и выставляет наружу веб-интерфейс.
docker run --name guacamole-client --link guacamole-server:guacd --link guacamoledb:mysql -e MYSQL_DATABASE=guacdb -e MYSQL_USER=guacadmin -e MYSQL_PASSWORD=password -d -p 80:8080 guacamole/guacamole
Команда длинная, поэтому стоит разобрать её по частям. Параметр --name guacamole-client задаёт имя контейнера. Флаги --link создают традиционные Docker-связки между контейнерами - --link guacamole-server:guacd делает контейнер с сервером guacd доступным изнутри клиентского контейнера под алиасом guacd, аналогично --link guacamoledb:mysql даёт доступ к базе под алиасом mysql.
Стоит заметить, что механизм --link считается устаревшим в современном Docker и вместо него рекомендуется использовать пользовательские сети через docker network create. Но для простых сценариев links продолжают работать и удобны своей наглядностью.
Параметры -e задают переменные окружения с настройками подключения к базе - имя базы guacdb, имя пользователя guacadmin и пароль. Эти значения соответствуют тому, что было настроено в MySQL-контейнере.
Флаг -d запускает контейнер в фоне. Параметр -p 80:8080 пробрасывает порт - снаружи на хосте слушается 80-й порт, внутри контейнера приложение работает на 8080.
Проверка списка контейнеров.
docker ps
Теперь все три контейнера видны в выводе.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d4034a72bb69 guacamole/guacamole "/opt/guacamole/bin/…" 58 seconds ago Up 57 seconds 0.0.0.0:80->8080/tcp, :::80->8080/tcp guacamole-client
51b2efdab0db guacamole/guacd "/bin/sh -c '/usr/lo…" About a minute ago Up About a minute (health: starting) 4822/tcp guacamole-server
c7a9309eac20 mysql/mysql-server "/entrypoint.sh mysq…" 5 minutes ago Up 5 minutes (healthy) 3306/tcp, 33060-33061/tcp guacamoledb
В строке клиента видна публикация порта 0.0.0.0:80->8080/tcp - это означает, что запросы из внешней сети на 80-й порт перенаправляются на 8080-й внутри контейнера.
Проверяем прослушивание 80-го порта на уровне хоста.
ss -altnp | grep :80
Опции утилиты ss перечислены через объединённый флаг. Параметр -a выводит все сокеты, -l ограничивает выводом только слушающие сокеты, -t показывает только TCP, -n выводит порты числами без разрешения в имена, -p добавляет информацию о процессе. Конвейер через grep отфильтровывает строки с подстрокой :80.
LISTEN 0 4096 0.0.0.0:80 0.0.0.0:* users:(("docker-proxy",pid=4006,fd=4))
LISTEN 0 4096 [::]:80 [::]:* users:(("docker-proxy",pid=4013,fd=4))
Процесс docker-proxy - это вспомогательный механизм Docker для проброса портов между хостовой сетью и внутренней сетью контейнеров. Видим прослушивание и на IPv4, и на IPv6.
Доступ к веб-интерфейсу Guacamole и первичная настройка через браузер
Наконец, самый приятный момент - открытие веб-интерфейса. В любом современном браузере вводим адрес http://адрес-сервера/guacamole. Если сервер находится в локальной сети с IP вроде 192.168.1.50, адрес будет http://192.168.1.50/guacamole. Для облачного сервера используется его публичный IP или привязанное доменное имя.
Откроется экран входа с логотипом Guacamole. Учётные данные по умолчанию хорошо известны и не блещут оригинальностью.
User: guacadmin
Password: guacadmin
После клика по кнопке Login открывается основная панель управления Guacamole. Первое, что обязательно нужно сделать после входа - сменить пароль администратора. Дефолтные учётки руcского сорта хорошо известны автоматическим сканерам, и оставлять их означает приглашать незваных гостей. Смена пароля делается через настройки профиля в правом верхнем углу интерфейса.
После смены пароля можно приступать к настройке самих подключений. Guacamole поддерживает создание соединений по SSH для серверов Linux, RDP для машин Windows и VNC для всего остального. У каждого протокола свой набор параметров - адрес сервера, порт, учётные данные, разрешение экрана и так далее. Соединения можно объединять в группы для удобства навигации, раздавать права доступа отдельным пользователям и сохранять историю сеансов.
Практические сценарии использования Guacamole и типичные подводные камни при эксплуатации
Несколько моментов из практики, которые стоит держать в голове при работе с развёрнутой системой.
- Дефолтный пароль guacadmin нужно сменить сразу после первого входа. Это единственный способ закрыть лазейку для автоматических атак из интернета.
- Связки --link при подходе через docker run считаются устаревшими. Для production-сценариев лучше использовать Docker Compose с описанием связки через networks - это даёт больше гибкости и контроля.
- Хранение пароля password для базы данных в туториальных целях нормально, но в реальной эксплуатации все пароли должны быть длинными случайными строками. Утечка такого пароля даёт атакующему полный доступ ко всем учётным данным удалённых серверов.
- Доступ к Guacamole стоит закрыть HTTPS-сертификатом через обратный прокси Nginx или Traefik перед контейнером. Передавать пароли и сессии в открытом виде по 80-му порту небезопасно.
- Регулярные обновления образов через docker pull guacamole/guacamole и последующий перезапуск контейнеров закрывают накопленные уязвимости. На большой инфраструктуре стоит автоматизировать это через инструменты вроде Watchtower.
- Бэкап базы данных MySQL делается через docker exec guacamoledb mysqldump -uroot -ppassword guacdb > backup.sql. Регулярные бэкапы - страховка от потери всех настроенных подключений.
Освоение такого инструмента превращает повседневный труд системного администратора. Вместо разрозненной коллекции клиентов и сохранённых паролей появляется единая точка доступа ко всему парку машин через любой браузер. Можно подключиться с планшета в кафе, с чужого ноутбука в командировке, с домашнего компьютера выходного дня - функциональность везде одна и та же. Для команд это значит общий каталог подключений с разграничением прав, прозрачную историю сеансов и единую политику паролей. Apache Guacamole не пытается заменить полноценные RDP или VNC клиенты для тяжёлой работы с графикой, но в большинстве сценариев администрирования его веб-интерфейс справляется превосходно. И самое приятное - вся эта мощь умещается в три Docker-контейнера, которые ставятся на любой сервер за полчаса.