Докер под Windows — это постоянные приключения. То ему нужно обновить операционку, иначе последние версии не ставятся, то он забывает, как подключаться к сети. В общем, каждый день от него новости. «Поставил и забыл» — это не про Docker Desktop for Windows. Особенно, когда он используется не совсем так, как рекомендуют его разработчики. А они почему-то не одобряют подключение внешних windows сетевых дисков в качестве локальных. И совсем не одобряют доступ к к таким сетевым папкам, которые расположены ещё и на host машине. Пишут, что это ужас-ужас с точки зрения безопасности, требуют всяких ключей типа:
cap_add:
- SYS_ADMIN
- DAC_READ_SEARCH
для работы команды mount в контейнере и прочая, и прочая.
В общем, когда в очередной раз после выгрузки контейнеров на сервер заказчика сервисы перестали видеть сетевые диски, я не особо удивился. Так уже бывало, и даже была написана пошаговая инструкция для группы поддержки, как и что перезагружать, когда ломаются сетевые настройки докера.
Так что я открываю свою инструкцию и начинаю действовать. Перезапускаю контейнеры — не помогает. Перезапускаю через docker-compose с пересозданием инфраструктуры — не помогает. Сбрасываю настройки Docker к заводским, восстанавливаю параметры виртуалки, загружаю заново образы, запускаю через docker-compose — опять всё по старому — не видит сеть. Точнее не подключается к сетевым шарам, хотя пинг из контейнера до SMB сервера проходит нормально. Последний пункт — перезагрузку сервера и переустановку Docker, пока пропускаю, так как перезагружать сервер очень не хочется. На этом инструкция кончилась.
Ок, перехожу на свою домашнюю машину, тут у меня тоже Docker под Windows, но чуть более новой версии. Проверяю на нём. Те же яйца:
Ага. Ну неужели, думаю, Docker накатил обновление с какой-то безопасностью и теперь мои скрипты из-за этого не запускаются? Последняя проверка — начисто удалить Docker с машины, и поставить заново. Это должно быть круче сброса к заводским настройкам. Проделываю весь перечень из предыдущего шага, только в дополнение к этому ещё и перезагружаю свою машину, чтобы уж совсем железно. Ставлю Docker c нуля, заливаю образы, запускаю docker-compose — ёпрст! Все сервисы как не видели сетевых шар, так и продолжают писать при загрузке «mount error(22): Invalid argument»
Пробую запустить скрипт по строкам из командной строки: подключаюсь к контейнеру, запускаю по очереди команды и вижу, что всё подключается и работает как надо:
mkdir -p $SMB_MOUNT_POINT
mount -t cifs $SMB_SHARE $SMB_MOUNT_POINT -o user=$SMB_USER,password=$SMB_PASSWORD,vers=2.0
ls $SMB_MOUNT_POINT
То есть, это что же, какая-то хрень с передачей параметров в скрипт при запуске контейнера?
Ищем ещё идеи. Все варианты с перезагрузкой докера отмели, остались варианты с возможными изменениями в родительском образе. У меня образ собирается на основе openjdk:8-jdk-alpine, конкретной версии не указано, так что какие-нибудь улучшения безопасности могли сломать мои скрипты. Может поменяли что-то в OpenJDK или дочернем Alpine?
Проверяю логи проекта, пробую выбрать более старые openjdk:8-jdk-alpine-3.8, openjdk:8-jdk-alpine-3.7 и т.д. — каждый раз пересобираю контейнер, проверяю — всё по-старому.
Чёрт подери! Может я что-то всё-таки поменял в своей сборке? Выгружаю из GIT'а версию проекта месячной давности, собираю — те же глюки. Трёхмесячной давности — проблема всё ещё тут. Как же так? Что изменилось? Конфигурация докера к настоящему моменту гарантировано рабочая, конфигурация образа — тоже не поменялась, исходники проекта те же самые (GIT всё сохраняет). Чудес не бывает — надо понять, где всё-таки появились изменения. В проде вручную запускаю команды подключения к шарам — так до перезапуска сервисы будут работать нормально и иду спать. Утро вечера мудренее.
Связи нет, но вы держитесь!
Наутро приходит идея — что пора, видимо, узнать, а что собственно говоря не нравится скрипту при выполнении.
Сообщение «mount error(22): Invalid argument» — это не сильно информативно. Нахожу, что есть волшебный ключик -х для баша с которой он выводит отладочную инфу при выполнениии скриптов.
Начинаем отладку внутри sh:
/ # sh -x /entry-point.sh
' echo 'Mount //<private_ip>/Exchange/Pipeline to /pipeline
Mount //<private_ip>/Exchange/Pipeline to /pipeline
' mkdir -p '/pipeline
' mount -t cifs //<private_ip>/Exchange/Pipeline /pipeline -o 'user=<private_user>,password=<private_password>,vers=2.0
mount error(22): Invalid argument
Refer to the mount.cifs(8) manual page (e.g. man mount.cifs)
mount: mounting //<private_ip>/Exchange/Pipeline on /pipeline failed: Invalid argument
' ls '/pipeline
+ exec
И тут появляются какие-то непонятные моменты — строка начинается с кавычек, потом кавычки в конце… Откуда кавычки?
Идея — может запуск с помощью настоящего bash будет информативнее?
Инсталлирую в контейнер BASH:
apk add bash
Запускаю отладку:
/ # bash -x /entry-point.sh
' echo 'Mount //<private_ip>/Exchange/Pipeline to /pipeline
Mount //<private_ip>/Exchange/Pipeline to /pipeline
+ mkdir -p $'/pipeline\r'
+ mount -t cifs //<private_ip>/Exchange/Pipeline /pipeline -o $'user=<private_user>,password=<private_password>,vers=2.0\r'
mount error(22): Invalid argument
Refer to the mount.cifs(8) manual page (e.g. man mount.cifs)
mount: mounting //<private_ip>/Exchange/Pipeline on /pipeline failed: Invalid argument
+ ls $'/pipeline\r'
+ exec
Блин, тут вроде, когда строка начинается с плюса — это хорошо, но появились какие-то \r и параметры $'...'
Ставим Midnight Commander, чтобы уж экспериментировать с удобствами
apk add mc
и открываем скрипт на редактирование, а там:Оппа! ^M в конце каждой строки. Ну-ка, ну-ка, смотрим в локальном проекте — а что у нас с окончаниями строк???? CRLF. Работаем под Windows, однако.
Меняем в этом конкретно файле CRLF на LF (да здравствует Notepad++!), собираем проект — бинго! Работает как надо.
Почему раньше было ок, а сейчас всё полетело? Смотрю по коммитам — не было никаких перемен. И тут вспоминаю, что GIT умеет на лету править символы перевода строк текстовых файлов. А я на днях подключил новый репозитарий, и возможно выгрузил оттуда все файлы с конвертацией в CRLF.
В итоге добавляем в проект файл .gitattributes, с указанием, что в отдельных файлах надо-таки сохранять символы конца строк как в UNIX:
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
# Declare files that will always have LF line endings on checkout.
*.sh text eol=lf
Мораль — иногда виновник даже не попадает в круг первоначальных подозреваемых.
Постскриптум
После обновления на последнюю версию Docker Desktop for Windows 2.2.0.3 всё обратно перестало работать. На этот раз я сразу пошёл внутрь контейнеров, и начал отладку. Оказалось, что перестал работать IP адрес 10.0.75.1 для доступа к хост машине, DockerNat теперь удалён из системы, а виртуалка DockerDesktopVM даже не имеет внешнего сетевого адаптера, т.е. связь с ней идёт не через стандартную сеть, а как-то иначе. И для доступа к хосту надо пользоваться host.docker.internal. Товарищи разработчики так и написали, что
DockerNAT has been removed from Docker Desktop 2.2.0.0 as using an IP address to communicate from the host to a container is not a supported feature. To communicate from a container to the host, you must use the special DNS name host.docker.internal.
Ок, поправил конфиги для тестового окружения, база данных подцепилась, пинг из контейнера до host.docker.internal проходит, а вот сетевые диски не подключаются. Пробую запустить mount вручную из шелла, и получаю знакомую ошибку «mount error(22): Invalid argument».
Убираю по очереди аргументы — запускаю просто «mount -t cifs //host.docker.internal/playground /pipeline» — вроде работает, но стучится от пользователя «root».
Добавляю пользователя: mount -t cifs //host.docker.internal/playground /pipeline -o user=smbuser — спрашивает пароль и подключается!
Полный вариант тоже работает:
mount -t cifs //host.docker.internal/playground /pipeline -o user=smbuser,password=smbpassword
а вот "mount -t cifs //host.docker.internal/playground /pipeline -o user=smbuser,password=smbpassword,vers=2.0" не пашет.
Меняю последний параметр на vers=2.1 — ура, работает!
Похоже, что Docker в последней версии сделал свою собственную имплементацию SMB сервера с блекджеком, но без поддержки 2.0. Ерунда, конечно, по сравнению с другими новостями.
Всем добра, сил и упорства!
Иллюстрация для статьи взята с сайта blog.eduonix.com/software-development/learn-debug-docker-containers
W001fer
По части NAT — наступил на те же грабли. Полез смотреть, что за host.docker.internal — оказывается, оно в файл hosts тупо прописывает адрес хостовой машины. Ну и еще пару алиасов туда же: gateway.docker.internal на тот же хостовой адрес и kubernetes.docker.internal на loopback 127.0.0.1
asorkin Автор
Интересно, а как всё-таки они умудрились сделать сеть без сетевого адаптера у виртуалки?
f1inx
Сначала нужно убедиться, что это именно так ifconfig -a в помощь. Для поддержки сети вовсе не обязательна эмуляция железа реального сетевого адаптера или виртуальный сетевой адаптер для общения с хост системой можно и в user space весь data link уровень сделать через tun/tap и подобные механизмы. Но конечно существуют и инвазивные методы встраивания в реализацию ipc4/ipv6 в ядре.
asorkin Автор
Это tun/tap так работает?