Как управлять хостом из контейнера докеров?
Например, как выполнить скопированный на хост bash скрипт?
Как управлять хостом из контейнера докеров?
Например, как выполнить скопированный на хост bash скрипт?
Ответы:
Это ДЕЙСТВИТЕЛЬНО зависит от того, что вам нужно для этого сценария bash!
Например, если сценарий bash просто повторяет какой-то вывод, вы можете просто сделать
docker run --rm -v $(pwd)/mybashscript.sh:/mybashscript.sh ubuntu bash /mybashscript.sh
Другая возможность состоит в том, что вы хотите, чтобы сценарий bash установил какое-то программное обеспечение, скажем, сценарий для установки docker-compose. ты мог бы сделать что-то вроде
docker run --rm -v /usr/bin:/usr/bin --privileged -v $(pwd)/mybashscript.sh:/mybashscript.sh ubuntu bash /mybashscript.sh
Но на этом этапе вы действительно начинаете понимать, что делает скрипт, чтобы предоставить определенные разрешения, необходимые для вашего хоста, изнутри контейнера.
docker run --rm -v $(pwd)/mybashscript.sh:/work/mybashscript.sh ubuntu /work/mybashscript.sh
/usr/bin
контейнеру доступ к хосту . Ни в том, ни в другом случае контейнер не имеет полного доступа к хост-системе. Может я ошибаюсь, но это похоже на плохой ответ на плохой вопрос.
Решение, которое я использую, - подключиться к хосту SSH
и выполнить такую команду:
ssh -l ${USERNAME} ${HOSTNAME} "${SCRIPT}"
Поскольку этот ответ продолжает набирать голоса, я хотел бы напомнить (и настоятельно рекомендовать), что учетная запись, которая используется для вызова скрипта, должна быть учетной записью без каких-либо разрешений вообще, но только для выполнения этого скрипта как sudo
(это может быть сделано из sudoers
файла).
ssh
не обнаруживается. Есть ли у вас другие предложения?
apt update && apt install openssh-client
.
Использовал именованный канал. В операционной системе хоста создайте сценарий для цикла и чтения команд, а затем вызовите для него eval.
Попросите контейнер докеров прочитать этот именованный канал.
Чтобы получить доступ к трубе, вам необходимо смонтировать ее через том.
Это похоже на механизм SSH (или аналогичный метод на основе сокетов), но ограничивает вас должным образом хост-устройством, что, вероятно, лучше. Кроме того, вам не нужно передавать информацию для аутентификации.
Мое единственное предупреждение - будьте осторожны с тем, почему вы это делаете. Это совершенно то, что нужно сделать, если вы хотите создать метод для самостоятельного обновления с пользовательским вводом или чем-то еще, но вы, вероятно, не хотите вызывать команду для получения некоторых данных конфигурации, так как правильным способом было бы передать это как аргументы / volume в докер. Также будьте осторожны с тем фактом, что вы избегаете, поэтому просто подумайте о модели разрешений.
Некоторые из других ответов, такие как запуск сценария на томе, не будут работать в целом, поскольку у них не будет доступа ко всем системным ресурсам, но это может быть более подходящим в зависимости от вашего использования.
Если вас не беспокоит безопасность, и вы просто хотите запустить контейнер докеров на хосте из другого контейнера докеров, такого как OP, вы можете поделиться сервером докеров, запущенным на хосте, с контейнером докеров, поделившись с ним слушающим сокетом.
См. Https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface и узнайте, позволяет ли ваша устойчивость к личному риску это для этого конкретного приложения.
Вы можете сделать это, добавив следующие аргументы тома в свою команду запуска
docker run -v /var/run/docker.sock:/var/run/docker.sock ...
или путем совместного использования /var/run/docker.sock в вашем файле компоновки докеров следующим образом:
version: '3'
services:
ci:
command: ...
image: ...
volumes
- /var/run/docker.sock:/var/run/docker.sock
Когда вы запускаете команду docker start в своем контейнере докеров, сервер докеров, работающий на вашем хосте, увидит запрос и подготовит родственный контейнер.
кредит: http://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/
/usr/bin/docker:/usr/bin/docker
).
Этот ответ - всего лишь более подробная версия решения Брэдфорда Медейроса , которое для меня также оказалось лучшим ответом, так что заслуга ему.
В своем ответе он объясняет, ЧТО делать ( именованные каналы ), но не совсем КАК это делать.
Я должен признать, что не знал, что такое именованные каналы, когда читал его решение. Поэтому я изо всех сил пытался реализовать это (хотя на самом деле это действительно просто), но мне это удалось, поэтому я рад помочь, объяснив, как я это сделал. Итак, мой ответ - это просто подробное описание команд, которые вам нужно запустить, чтобы заставить его работать, но, опять же, ему заслуга.
На главном хосте выберите папку, в которую вы хотите поместить файл именованного канала, например, /path/to/pipe/
и имя канала, например mypipe
, а затем запустите:
mkfifo /path/to/pipe/mypipe
Труба создана. Тип
ls -l /path/to/pipe/mypipe
И проверьте права доступа, начинающиеся с "p", например
prw-r--r-- 1 root root 0 mypipe
Теперь запустите:
tail -f /path/to/pipe/mypipe
Теперь терминал ожидает отправки данных в этот канал.
Теперь откройте другое окно терминала.
А затем запустите:
echo "hello world" > /path/to/pipe/mypipe
Проверьте первый терминал (тот, на котором tail -f
), он должен отображать "привет, мир"
В хост-контейнере вместо запуска, tail -f
который просто выводит все, что отправлено в качестве входных данных, запустите эту команду, которая будет выполнять ее как команды:
eval "$(cat /path/to/pipe/mypipe)"
Затем с другого терминала попробуйте запустить:
echo "ls -l" > /path/to/pipe/mypipe
Вернитесь к первому терминалу, и вы должны увидеть результат ls -l
команды.
Возможно, вы заметили, что в предыдущей части сразу после ls -l
отображения вывода он перестает прислушиваться к командам.
Вместо eval "$(cat /path/to/pipe/mypipe)"
запуска:
while true; do eval "$(cat /path/to/pipe/mypipe)"; done
(вы не можете этого сделать)
Теперь вы можете отправлять неограниченное количество команд одну за другой, будут выполнены все, а не только первая.
Единственное предостережение: если хост должен перезагрузиться, цикл while перестанет работать.
Вот что я сделал для перезагрузки:
Поместите while true; do eval "$(cat /path/to/pipe/mypipe)"; done
в файл execpipe.sh
с #!/bin/bash
заголовком
Не забывай об chmod +x
этом
Добавьте его в crontab, запустив
crontab -e
А затем добавив
@reboot /path/to/execpipe.sh
На этом этапе протестируйте его: перезагрузите сервер и, когда он восстановится, введите несколько команд в канал и проверьте, выполняются ли они. Конечно, вы не можете видеть вывод команд, поэтому ls -l
не поможет, но touch somefile
поможет.
Другой вариант - изменить сценарий, чтобы поместить результат в файл, например:
while true; do eval "$(cat /path/to/pipe/mypipe)" &> /somepath/output.txt; done
Теперь вы можете запустить, ls -l
и вывод (как stdout, так и stderr, использующийся &>
в bash) должен быть в output.txt.
Если вы используете как docker compose, так и dockerfile, как я, вот что я сделал:
Предположим, вы хотите смонтировать родительскую папку mypipe, как /hostpipe
в вашем контейнере.
Добавь это:
VOLUME /hostpipe
в вашем файле докеров, чтобы создать точку монтирования
Затем добавьте это:
volumes:
- /path/to/pipe:/hostpipe
в вашем файле docker compose, чтобы смонтировать / path / to / pipe как / hostpipe
Перезагрузите докер-контейнеры.
Exec в свой док-контейнер:
docker exec -it <container> bash
Зайдите в папку монтирования и убедитесь, что вы видите трубу:
cd /hostpipe && ls -l
Теперь попробуйте запустить команду из контейнера:
echo "touch this_file_was_created_on_main_host_from_a_container.txt" > /hostpipe/mypipe
И должно работать!
ВНИМАНИЕ: Если у вас есть хост OSX (Mac OS) и контейнер Linux, он не будет работать (объяснение здесь https://stackoverflow.com/a/43474708/10018801 и выпуск здесь https://github.com/docker / for-mac / issues / 483 ), потому что реализация канала отличается, поэтому то, что вы пишете в канал из Linux, может быть прочитано только Linux, а то, что вы пишете в канал из Mac OS, может быть прочитано только Mac OS (это предложение может быть не очень точным, но помните, что существует проблема кроссплатформенности).
Например, когда я запускаю настройку докера в DEV со своего компьютера Mac OS, именованный канал, как описано выше, не работает. Но при постановке и производстве у меня есть хост Linux и контейнеры Linux, и они отлично работают.
Вот как я отправляю команду из контейнера js моего узла на основной хост и получаю результат:
const pipePath = "/hostpipe/mypipe"
const outputPath = "/hostpipe/output.txt"
const commandToRun = "pwd && ls-l"
console.log("delete previous output")
if (fs.existsSync(outputPath)) fs.unlinkSync(outputPath)
console.log("writing to pipe...")
const wstream = fs.createWriteStream(pipePath)
wstream.write(commandToRun)
wstream.close()
console.log("waiting for output.txt...") //there are better ways to do that than setInterval
let timeout = 10000 //stop waiting after 10 seconds (something might be wrong)
const timeoutStart = Date.now()
const myLoop = setInterval(function () {
if (Date.now() - timeoutStart > timeout) {
clearInterval(myLoop);
console.log("timed out")
} else {
//if output.txt exists, read it
if (fs.existsSync(outputPath)) {
clearInterval(myLoop);
const data = fs.readFileSync(outputPath).toString()
if (fs.existsSync(outputPath)) fs.unlinkSync(outputPath) //delete the output file
console.log(data) //log the output of the command
}
}
}, 300);
Напишите простой серверный сервер python, прослушивающий порт (скажем, 8080), свяжите порт -p 8080: 8080 с контейнером, сделайте HTTP-запрос к localhost: 8080, чтобы запросить сервер python, выполняющий сценарии оболочки с помощью popen, запустить завиток или написание кода для выполнения HTTP-запроса curl -d '{"foo": "bar"}' localhost: 8080
#!/usr/bin/python
from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer
import subprocess
import json
PORT_NUMBER = 8080
# This class will handles any incoming request from
# the browser
class myHandler(BaseHTTPRequestHandler):
def do_POST(self):
content_len = int(self.headers.getheader('content-length'))
post_body = self.rfile.read(content_len)
self.send_response(200)
self.end_headers()
data = json.loads(post_body)
# Use the post data
cmd = "your shell cmd"
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
p_status = p.wait()
(output, err) = p.communicate()
print "Command output : ", output
print "Command exit status/return code : ", p_status
self.wfile.write(cmd + "\n")
return
try:
# Create a web server and define the handler to manage the
# incoming request
server = HTTPServer(('', PORT_NUMBER), myHandler)
print 'Started httpserver on port ' , PORT_NUMBER
# Wait forever for incoming http requests
server.serve_forever()
except KeyboardInterrupt:
print '^C received, shutting down the web server'
server.socket.close()
Моя лень заставила меня найти самое простое решение, которое не было опубликовано здесь в качестве ответа.
Он основан на большую статью по LUC juggery .
Все, что вам нужно сделать, чтобы получить полную оболочку для вашего хоста Linux из вашего контейнера докеров, это:
docker run --privileged --pid=host -it alpine:3.8 \
nsenter -t 1 -m -u -n -i sh
Пояснение:
--privileged: предоставляет контейнеру дополнительные разрешения, он позволяет контейнеру получить доступ к устройствам хоста (/ dev)
--pid = host: позволяет контейнерам использовать дерево процессов узла Docker (виртуальная машина, в которой работает демон Docker). Утилита nsenter: позволяет запускать процесс в существующих пространствах имен (строительные блоки, обеспечивающие изоляцию контейнеров)
nsenter (-t 1 -m -u -n -i sh) позволяет запускать процесс sh в том же контексте изоляции, что и процесс с PID 1. Вся команда затем предоставит интерактивную оболочку sh в виртуальной машине.
Эта установка имеет серьезные последствия для безопасности, и ее следует использовать с осторожностью (если таковые имеются).
docker run --detach-keys="ctrl-p" -it -v /:/mnt/rootdir --name testing busybox
# chroot /mnt/rootdir
#
У меня простой подход.
Шаг 1. Смонтируйте /var/run/docker.sock:/var/run/docker.sock (чтобы вы могли выполнять команды докеров внутри своего контейнера)
Шаг 2: Выполните это ниже в своем контейнере. Ключевая часть здесь ( --network host, поскольку это будет выполняться из контекста хоста)
docker run -i --rm --network host -v /opt/test.sh:/test.sh alpine: 3.7 sh /test.sh
test.sh должен содержать некоторые команды (ifconfig, netstat и т. д.), которые вам нужны. Теперь вы сможете получить вывод контекста хоста.
Как напоминает Маркус, докер - это, по сути, изоляция процессов. Начиная с docker 1.8, вы можете копировать файлы в обе стороны между хостом и контейнером, см. Документdocker cp
https://docs.docker.com/reference/commandline/cp/
После копирования файла вы можете запустить его локально.
myvalue=$(docker run -it ubuntu echo $PATH)
и регулярно тестировать его в оболочке скрипта (конечно, вы будете использовать что-то еще, кроме $ PATH, это просто пример), когда он - какое-то конкретное значение, вы запускаете свой скрипт
Вы можете использовать концепцию конвейера, но использовать файл на хосте и fswatch для достижения цели по выполнению сценария на хост-машине из контейнера докеров. Примерно так (используйте на свой страх и риск):
#! /bin/bash
touch .command_pipe
chmod +x .command_pipe
# Use fswatch to execute a command on the host machine and log result
fswatch -o --event Updated .command_pipe | \
xargs -n1 -I "{}" .command_pipe >> .command_pipe_log &
docker run -it --rm \
--name alpine \
-w /home/test \
-v $PWD/.command_pipe:/dev/command_pipe \
alpine:3.7 sh
rm -rf .command_pipe
kill %1
В этом примере внутри контейнера отправьте команды в / dev / command_pipe, например:
/home/test # echo 'docker network create test2.network.com' > /dev/command_pipe
На хосте вы можете проверить, создана ли сеть:
$ docker network ls | grep test2
8e029ec83afe test2.network.com bridge local
Чтобы развернуть ответ user2915097 :
Идея изоляции состоит в том, чтобы иметь возможность очень четко ограничить то, что приложение / процесс / контейнер (независимо от вашего угла зрения) может делать с хост-системой. Следовательно, возможность копировать и выполнять файл действительно нарушит всю концепцию.
Да. Но иногда это необходимо.
Нет. Это не так, или Docker не подходит. Что вам нужно сделать, так это объявить понятный интерфейс для того, что вы хотите сделать (например, обновить конфигурацию хоста), и написать минимальный клиент / сервер, чтобы делать именно это и не более того. Однако, как правило, это не очень желательно. Во многих случаях вам следует просто переосмыслить свой подход и устранить эту потребность. Docker появился на свет, когда практически все было сервисом, доступным по некоторому протоколу. Я не могу придумать ни одного правильного варианта использования контейнера Docker, получающего права на выполнение произвольных вещей на хосте.
A
(src на github). В A
репо я создаю правильные хуки, которые после команды git pull создают новый образ докера и запускают их (и, конечно, удаляют старый контейнер). Далее: в github есть веб-хуки, которые позволяют создавать POST-запрос на произвольную ссылку на конечную точку после нажатия на мастер. Поэтому я не хочу создавать dockerized службу B, которая будет этой конечной точкой и будет запускать только 'git pull' в репо A на машине HOST (важно: команда 'git pull' должна выполняться в среде HOST, а не в среде B, потому что B не может запустить новый контейнер A внутри B ...)