Как запустить сценарий оболочки на хосте из контейнера докеров?


94

Как управлять хостом из контейнера докеров?

Например, как выполнить скопированный на хост bash скрипт?


8
разве это не будет полной противоположностью изоляции хоста от докера?
Маркус Мюллер

31
Да. Но иногда это необходимо.
Alex


Не уверен насчет «управляющего хоста», но недавно я был на выступлении специалистов по данным, которые используют докер для запуска скриптов для обработки огромных рабочих нагрузок (с использованием установленных на AWS графических процессоров) и вывода результата на хост. Очень интересный вариант использования. По сути, сценарии упакованы в надежную среду выполнения благодаря docker
KCD

@KCD И почему они предпочитают контейнеризацию приложений через докеры вместо использования контейнеров системного уровня (LXC)?
Alex Ushakov

Ответы:


28

Это ДЕЙСТВИТЕЛЬНО зависит от того, что вам нужно для этого сценария 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

Но на этом этапе вы действительно начинаете понимать, что делает скрипт, чтобы предоставить определенные разрешения, необходимые для вашего хоста, изнутри контейнера.


1
У меня возникла идея сделать контейнер, который подключается к хосту и создает новые контейнеры.
Alex

1
Докеру, похоже, не нравится ваше относительное крепление. Это должно сработатьdocker run --rm -v $(pwd)/mybashscript.sh:/work/mybashscript.sh ubuntu /work/mybashscript.sh
KCD

5
Первая строка запускает новый контейнер ubuntu и монтирует сценарий, где его можно прочитать. Например, он не разрешает контейнеру доступ к файловой системе хоста. Вторая строка предоставляет /usr/binконтейнеру доступ к хосту . Ни в том, ни в другом случае контейнер не имеет полного доступа к хост-системе. Может я ошибаюсь, но это похоже на плохой ответ на плохой вопрос.
Пол

3
Достаточно справедливо - вопрос был довольно расплывчатым. Вопрос не требовал «полного доступа к хост-системе». Как описано, если сценарий bash предназначен только для вывода некоторого вывода, ему не потребуется доступа к файловой системе хоста. Для моего второго примера, который устанавливал docker-compose, единственное необходимое разрешение - это доступ к каталогу bin, где хранится двоичный файл. Как я сказал в начале, для этого у вас должны быть очень конкретные представления о том, что делает скрипт, чтобы предоставить правильные разрешения.
Пол Бекотт

1
Пробовал, скрипт выполняется в контейнере, а не на хосте
All2Pie

60

Решение, которое я использую, - подключиться к хосту SSHи выполнить такую ​​команду:

ssh -l ${USERNAME} ${HOSTNAME} "${SCRIPT}"

ОБНОВИТЬ

Поскольку этот ответ продолжает набирать голоса, я хотел бы напомнить (и настоятельно рекомендовать), что учетная запись, которая используется для вызова скрипта, должна быть учетной записью без каких-либо разрешений вообще, но только для выполнения этого скрипта как sudo(это может быть сделано из sudoersфайла).


В качестве другого обходного пути контейнер может выводить набор команд, и хост может запускать их после выхода из контейнера: eval $ (docker run --rm -it container_name_to_output script)
parity3

Мне нужно запустить командную строку на хосте из контейнера Docker, но когда я вхожу в контейнер, sshне обнаруживается. Есть ли у вас другие предложения?
Рон Розенфельд

@RonRosenfeld, какой образ Docker вы используете? в случае Debian / Ubuntu, запустите это: apt update && apt install openssh-client.
Мохаммед Нурелдин

Это будет то, что установлено на моем Synology NAS. Как я могу сказать?
Рон Розенфельд

@RonRosenfeld, извините, я не понимаю, что вы имеете в виду
Мохаммед Нурелдин

52

Использовал именованный канал. В операционной системе хоста создайте сценарий для цикла и чтения команд, а затем вызовите для него eval.

Попросите контейнер докеров прочитать этот именованный канал.

Чтобы получить доступ к трубе, вам необходимо смонтировать ее через том.

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

Мое единственное предупреждение - будьте осторожны с тем, почему вы это делаете. Это совершенно то, что нужно сделать, если вы хотите создать метод для самостоятельного обновления с пользовательским вводом или чем-то еще, но вы, вероятно, не хотите вызывать команду для получения некоторых данных конфигурации, так как правильным способом было бы передать это как аргументы / volume в докер. Также будьте осторожны с тем фактом, что вы избегаете, поэтому просто подумайте о модели разрешений.

Некоторые из других ответов, такие как запуск сценария на томе, не будут работать в целом, поскольку у них не будет доступа ко всем системным ресурсам, но это может быть более подходящим в зависимости от вашего использования.


14
ВНИМАНИЕ: это правильный / лучший ответ, и за него нужно немного больше похвалы. Любой другой ответ - это возня с вопросом «что вы пытаетесь сделать» и создание исключений для разных вещей. У меня есть очень конкретный вариант использования, который требует, чтобы я мог это сделать, и это единственный хороший ответ, imho. SSH, описанный выше, потребует снижения стандартов безопасности / брандмауэра, а запуск докеров просто неправильный. Спасибо за это. Я предполагаю, что это не получит столько голосов, потому что это не простой ответ копирования / вставки, но это ответ. +100 очков от меня, если бы я мог
Фарли

3
Для тех, кто ищет дополнительную информацию, вы можете использовать следующий скрипт, запущенный на хост-машине: unix.stackexchange.com/a/369465 Конечно, вам придется запустить его с помощью nohup и создать какую-то оболочку супервизора. чтобы поддерживать его в рабочем состоянии (возможно, используйте задание cron: P)
Sucotronic

7
Это может быть хорошим ответом. Однако было бы намного лучше, если бы вы предоставили более подробную информацию и некоторые дополнительные объяснения в командной строке. Можно ли уточнить?
Мохаммед

5
Проголосовали, это работает! Создайте именованный канал с помощью mkfifo host_executor_queue, где установлен том. Затем, чтобы добавить потребителя, который выполняет команды, помещенные в очередь в качестве оболочки хоста, используйте 'tail -f host_executor_queue | ш & '. Знак & в конце заставляет его работать в фоновом режиме. Наконец, чтобы поместить команды в очередь, используйте 'echo touch foo> host_executor_queue' - этот тест создает временный файл foo в домашнем каталоге. Если вы хотите, чтобы потребитель запускался при запуске системы, укажите '@reboot tail -f host_executor_queue | sh & 'в crontab. Просто добавьте относительный путь к host_executor_queue.
скайбанк

1
может кто-нибудь отредактировать ответ с помощью примера кода?
Лукас Поттерски,

6

Если вас не беспокоит безопасность, и вы просто хотите запустить контейнер докеров на хосте из другого контейнера докеров, такого как 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/


1
Учтите, что докер должен быть установлен в контейнере, иначе вам также потребуется смонтировать том для двоичного файла докера (например, /usr/bin/docker:/usr/bin/docker).
Джерри

1
Будьте осторожны при установке сокета докера в свой контейнер, это может быть серьезной проблемой безопасности: docs.docker.com/engine/security/security/…
DatGuyKaj

@DatGuyKaj, спасибо, я отредактировал свой ответ, чтобы отразить проблемы, указанные на вашем ресурсе.
Мэтт Буччи,

Это не отвечает на вопрос о запуске скрипта на хосте, а не в контейнере
Брэндон,

5

Этот ответ - всего лишь более подробная версия решения Брэдфорда Медейроса , которое для меня также оказалось лучшим ответом, так что заслуга ему.

В своем ответе он объясняет, ЧТО делать ( именованные каналы ), но не совсем КАК это делать.

Я должен признать, что не знал, что такое именованные каналы, когда читал его решение. Поэтому я изо всех сил пытался реализовать это (хотя на самом деле это действительно просто), но мне это удалось, поэтому я рад помочь, объяснив, как я это сделал. Итак, мой ответ - это просто подробное описание команд, которые вам нужно запустить, чтобы заставить его работать, но, опять же, ему заслуга.

ЧАСТЬ 1. Тестирование концепции именованного канала без докера

На главном хосте выберите папку, в которую вы хотите поместить файл именованного канала, например, /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), он должен отображать "привет, мир"

ЧАСТЬ 2 - Запуск команд через трубу

В хост-контейнере вместо запуска, tail -fкоторый просто выводит все, что отправлено в качестве входных данных, запустите эту команду, которая будет выполнять ее как команды:

eval "$(cat /path/to/pipe/mypipe)"

Затем с другого терминала попробуйте запустить:

echo "ls -l" > /path/to/pipe/mypipe

Вернитесь к первому терминалу, и вы должны увидеть результат ls -lкоманды.

ЧАСТЬ 3 - Заставьте его слушать вечно

Возможно, вы заметили, что в предыдущей части сразу после ls -lотображения вывода он перестает прислушиваться к командам.

Вместо eval "$(cat /path/to/pipe/mypipe)"запуска:

while true; do eval "$(cat /path/to/pipe/mypipe)"; done

(вы не можете этого сделать)

Теперь вы можете отправлять неограниченное количество команд одну за другой, будут выполнены все, а не только первая.

ЧАСТЬ 4 - Сделайте так, чтобы он работал даже при перезагрузке

Единственное предостережение: если хост должен перезагрузиться, цикл 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.

ЧАСТЬ 5 - Заставьте его работать с докером

Если вы используете как docker compose, так и dockerfile, как я, вот что я сделал:

Предположим, вы хотите смонтировать родительскую папку mypipe, как /hostpipeв вашем контейнере.

Добавь это:

VOLUME /hostpipe

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

Затем добавьте это:

volumes:
   - /path/to/pipe:/hostpipe

в вашем файле docker compose, чтобы смонтировать / path / to / pipe как / hostpipe

Перезагрузите докер-контейнеры.

ЧАСТЬ 6 - Тестирование

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, и они отлично работают.

ЧАСТЬ 7 - Пример из контейнера Node.JS

Вот как я отправляю команду из контейнера 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);

Это прекрасно работает. А что насчет безопасности? Я хочу использовать это для запуска / остановки контейнеров докеров из работающего контейнера? Могу ли я просто сделать dockeruser без каких-либо привилегий, кроме выполнения команд docker?
Кристоф ван Венсель,

4

Напишите простой серверный сервер 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()

ИМО, это лучший ответ. Выполнение произвольных команд на хост-машине ДОЛЖНО выполняться через какой-либо API (например, REST). Это единственный способ обеспечения безопасности и надлежащего управления запущенными процессами (например, уничтожение, обработка stdin, stdout, кода выхода и т. Д.). Конечно, было бы неплохо, если бы этот API мог работать внутри Docker, но лично я не возражаю, чтобы запускать его на хосте напрямую.
barney765

2

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

Он основан на большую статью по 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 в виртуальной машине.

Эта установка имеет серьезные последствия для безопасности, и ее следует использовать с осторожностью (если таковые имеются).


1
docker run --detach-keys="ctrl-p" -it -v /:/mnt/rootdir --name testing busybox
# chroot /mnt/rootdir
# 

3
Хотя этот ответ может решить вопрос OP, предлагается объяснить, как он работает и почему решает проблему. Это помогает новым разработчикам понять, что происходит, и как исправить эту и подобные проблемы самостоятельно. Спасибо за участие!
Калеб Клветер

1

У меня простой подход.

Шаг 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 и т. д.), которые вам нужны. Теперь вы сможете получить вывод контекста хоста.


2
Согласно официальной документации docker по сети с использованием хост-сети: «Однако во всех остальных случаях, таких как хранилище, пространство имен процессов и пространство имен пользователей, процесс изолирован от хоста». Отъезд - docs.docker.com/network/network-tutorial-host
Питер Мутися

0

Как напоминает Маркус, докер - это, по сути, изоляция процессов. Начиная с docker 1.8, вы можете копировать файлы в обе стороны между хостом и контейнером, см. Документdocker cp

https://docs.docker.com/reference/commandline/cp/

После копирования файла вы можете запустить его локально.


1
Я знаю это. Иными словами, как запустить этот скрипт из контейнера докеров?
Alex


2
@AlexUshakov: никак. Это нарушит многие преимущества докера. Не делай этого. Не пытайся. Еще раз подумайте, что вам нужно делать.
Маркус Мюллер,

См. Также трюк Влада forum.docker.com/t/…
user2915097

1
вы всегда можете на хосте получить значение какой-либо переменной в вашем контейнере, что-то вроде того, myvalue=$(docker run -it ubuntu echo $PATH)и регулярно тестировать его в оболочке скрипта (конечно, вы будете использовать что-то еще, кроме $ PATH, это просто пример), когда он - какое-то конкретное значение, вы запускаете свой скрипт
user2915097

0

Вы можете использовать концепцию конвейера, но использовать файл на хосте и 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

-7

Чтобы развернуть ответ user2915097 :

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

Да. Но иногда это необходимо.

Нет. Это не так, или Docker не подходит. Что вам нужно сделать, так это объявить понятный интерфейс для того, что вы хотите сделать (например, обновить конфигурацию хоста), и написать минимальный клиент / сервер, чтобы делать именно это и не более того. Однако, как правило, это не очень желательно. Во многих случаях вам следует просто переосмыслить свой подход и устранить эту потребность. Docker появился на свет, когда практически все было сервисом, доступным по некоторому протоколу. Я не могу придумать ни одного правильного варианта использования контейнера Docker, получающего права на выполнение произвольных вещей на хосте.


У меня есть вариант использования: у меня есть dockerized сервис A(src на github). В Aрепо я создаю правильные хуки, которые после команды git pull создают новый образ докера и запускают их (и, конечно, удаляют старый контейнер). Далее: в github есть веб-хуки, которые позволяют создавать POST-запрос на произвольную ссылку на конечную точку после нажатия на мастер. Поэтому я не хочу создавать dockerized службу B, которая будет этой конечной точкой и будет запускать только 'git pull' в репо A на машине HOST (важно: команда 'git pull' должна выполняться в среде HOST, а не в среде B, потому что B не может запустить новый контейнер A внутри B ...)
Камил Келчевски

1
Проблема: я хочу, чтобы в HOST ничего не было, кроме linux, git и docker. И я хочу иметь службу Dockerizet A и службу B (которая на самом деле является обработчиком git-push, который выполняет git pull на репо A после того, как кто-то сделает git push на master). Итак, git auto-deploy - проблемный вариант использования,
Камил Келчевски

@ KamilKiełczewski Я пытаюсь сделать то же самое, вы нашли решение?
user871784

1
Сказать: «Нет, это не так» - это узкий кругозор и предполагает, что вы знаете все варианты использования в мире. Наш вариант использования - это запуск тестов. Они должны работать в контейнерах, чтобы правильно протестировать среду, но, учитывая характер тестов, им также необходимо выполнять сценарии на хосте.
Сеника Гонсалес

1
Просто для тех, кто задается вопросом, почему я оставляю -7 ответ: а) ошибаться - это нормально. Я был неправ. Это нормально, что это задокументировано здесь. б) комментарии действительно вносят свой вклад; удаление ответа приведет к их удалению. c) Он по-прежнему вносит свой вклад в точку зрения, которую было бы разумно рассмотреть (не нарушайте свою изоляцию, если вам это не нужно. Однако иногда вам нужно).
Маркус Мюллер,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.