Иногда бывает нужно запустить рабочие программы так, чтобы отделить их от ОС (не устанавливать поверх системы, использовать другие библиотеки, сформировать portable пакет и т.д.).
Например, защитить от нежелательного обновления, или наоборот, потестировать обновление перед тем как применить его в работу.
Простейшее решение - устанавливать и запускать их внутри контейнера docker.
docker run -ti \ --name test \ debian
и устанавливай что хочешь.
Если контейнер будет многоразовый - полезно сразу добавить в него скрипт инициализации, например так:
docker run -ti \ --name test \ debian \ /etc/startup
После создания контейнер не запустится, потому что скрипта /etc/startup нет. Его можно добавить, хотя бы минимальный:
#!/bin/sh /bin/bash
chmod 700 startup docker cp startup test:/etc/startup docker start test
В работающий контейнер можно зайти рутом, и подготовить всё к установке:
docker exec -ti test /bin/bash apt update apt upgrade apt install ...
Так как запускать прикладные программы от рута - не самая лучшая идея, лучше создать отдельного юзера, и работать под ним:
apt install adduser adduser myuser su -l myuser
В этом случае пользователь входит так, как если бы выполнялся нормальный логин в систему, с обработкой всяких там .profile
Можно сразу логиниться в контейнер юзером:
docker exec -ti test su -l myuser
Есть похожий способ, но это не логин в систему, это просто запуск конкретной программы под конкретным пользователем.
Это другое, переменные окружения юзера не устанавливаются:
docker exec -ti --user myuser test /bin/bash
В любом случае - это консольный вход. Текстовый редактор, работа с файлами, скрипты, обработка данных - вполне удобно.
Но в случае с графическими программами есть две дополнительные сложности:
Во-первых, нужен графический режим. Это не очень сложно, потому что X Window System изначально разработана с возможностью работы через сеть или через UNIX-socket.
Но во-вторых, некоторые программы требуют еще и работу с чем-то типа MESA/OpenGL.
Для проброса Иксов будем использовать UNIX-socket, часто он находится как /tmp/.X11-unix/X0
Для этого при создании контейнера добавим проброс файла:
docker run -ti \ -v /tmp/.X11-unix:/tmp/.X11-unix \ --name test \ debian \ /etc/startup
В типовом случае, если вы единственный пользователь на компьютере, и программы запускаются под единственным пользователем в контейнере - скорее всего будет достаточно просто указать переменную DISPLAY и запустить программу:
su -l myuser export DISPLAY=:0 progname
Удобнее просто добавить "export DISPLAY=:0" в .profile, именно поэтому и используется вход через su -l myuser.
Пояснение, почему так:
X-сервер на хосте проверяет подключение через сокет по списку разрешений.
Посмотреть текущий список на хосте можно так:
xhost access control enabled, only authorized clients can connect SI:localuser:myuser
В данном случае доступ разрешен локальному юзеру myuser, причем юзер тут определяется по UID соответствующему UID myuser в таблице пользователей.
Что это значит:
Если в контейнере создать нового пользователя с UID, соответствующим UID пользователя myuser хоста - у него будет доступ к X-серверу.
Имя пользователя роли не играет, хоть lopata - лишь бы UID совпадал.
Но кроме того, права на файл UNIX-сокета обычно также выдаются на текущего юзера хоста:
ls -l /tmp/.X11-unix/X0 srwxr-xr-x 1 myuser myuser 0 Mar 15 17:31 /tmp/.X11-unix/X0
То есть, кроме списка авторизации проверяются обычные права на файл.
Поскольку права также привязаны на UID/GID - для доступа к сокету в этом случае у пользователя в контейнере должен быть такой же UID как у пользователя myuser хоста.
Как правило, первый пользовательский UID - 1000, поэтому по умолчанию всё работает, так как UID и там и там совпадает.
Но если на хосте или в контейнере почему-то несколько пользователей, то для того чтобы дать возможность работать с графикой им всем - нужно внести их в списки разрешенных и дать нужные права на сокет.
Для этого на хосте выполнить команду:
xhost +local: или xhost +SI:localuser:other_user
(причем other_user должен существовать на хосте, с таким же UID, который будет у нужного юзера внутри контейнера).
А права на сокет поменять либо на "доступ для всех":
chmod 777 /tmp/.X11-unix/X0
либо на "доступ для группы" и внести нужного пользователя в группу, владеющую сокетом
ls -l /tmp/.X11-unix/X0 srwxr-xr-x 1 myuser myuser 0 Mar 6 17:31 /tmp/.X11-unix/X0 chmod 775 /tmp/.X11-unix/X0 usermod -a -G myuser other_user
и сделать это можно прямо внутри контейнера.
Причем есть нюанс: сокет может пересоздаваться при рестарте хоста, соответственно права на него будут возвращаться к первоначальному состоянию, именно поэтому может пригодиться скрипт /etc/startup:
#!/bin/sh chmod 775 /tmp/.X11-unix/X0 /bin/bash
Но это только обычные Иксы, без аппаратного ускорения. Для работы графических ускорителей нужно подключить устройство, которое за это отвечает.
Оно может находиться в системе примерно тут:
ls -l /dev/dri total 0 drwxr-xr-x 2 root root 100 Mar 15 16:17 by-path crw-rw---- 1 root video 226, 0 Mar 15 16:17 card0 crw-rw---- 1 root video 226, 1 Mar 15 16:17 card1 crw-rw---- 1 root render 226, 128 Mar 15 16:17 renderD128
Кроме того, может потребоваться доступ к shared memory. Чтобы всё это сделать - изменяем скрипт создания контейнера:
docker run -ti \ --device /dev/dri \ --ipc host \ -v /tmp/.X11-unix:/tmp/.X11-unix \ --name test \ debian \ /etc/startup
Но прежде чем можно будет этим пользоваться - нужно проверить доступы внутри контейнера:
ls -l /dev/dri total 0 drwxr-xr-x 2 root root 100 Mar 15 16:17 by-path crw-rw---- 1 root video 226, 0 Mar 15 16:17 card0 crw-rw---- 1 root video 226, 1 Mar 15 16:17 card1 crw-rw---- 1 root 105 226, 128 Mar 15 16:17 renderD128
Например, тут можно видеть, что группа video распозналась, т.к. она есть в системе контейнера, а группа с GID 105 - не распознана.
Значит, ее надо создать (возьмем то же имя render, но это непринципиально):
groupadd -a 105 render ls -l /dev/dri total 0 drwxr-xr-x 2 root root 100 Mar 15 16:17 by-path crw-rw---- 1 root video 226, 0 Mar 15 16:17 card0 crw-rw---- 1 root video 226, 1 Mar 15 16:17 card1 crw-rw---- 1 root render 226, 128 Mar 15 16:17 renderD128
Теперь остается включить нужного пользователя в группы video и render:
usermod -a -G video myuser usermod -a -G render myuser
Теперь, если приложение корректно установить внутри контейнера - оно будет работать в том числе и с 3D-ускорителем (если конечно он есть).
Итак, оно запускается, работает, но работает почти как в виртуальной машине, в своей изолированной файловой системе.
А что, если нужно обмениваться документами с хостом? Перекидывать их по сети неудобно, поэтому нужно добавить разделяемый каталог, еще немного усложнив создание контейнера:
docker run -ti \ --device /dev/dri \ --ipc host \ -v /tmp/.X11-unix:/tmp/.X11-unix \ -v /home/myuser/shared:/shared \ --name test \ debian \ /etc/startup
Вот теперь программы на хосте и в контейнере работают с одним и тем же каталогом shared.
Остается только сделать простейший скрипт входа в контейнер под нужным пользователем, чтобы не заморачиваться с вводом ключей:
#!/bin/sh docker exec -ti test su -l myuser
или запускать окно терминала примерно так:
xterm -e 'docker exec -ti test su -l myuser'
тогда окно терминала будет сразу открываться в контексте контейнера.
Настроенный контейнер с установленными программами можно сохранить, на случай переноса на другую машину или для восстановления:
docker commit test test_image docker save test_image | gzip > test_image.gz
Ненужный больше контейнер можно удалить:
docker stop test docker rm test
При этом удаляются все зависимости и изменения в ОС внутри контейнера.
Такую песочницу, например, удобно использовать, когда для выполнения конкретной работы требуется несколько программ, работающих вместе, и их можно таскать с собой в виде пакета с компьютера на компьютер, не беспокоясь о том, что после очередного обновления ОС хоста изменятся версии, нарушится совместимость, или программа вообще пропадет из репозитария по какой-то причине.
