Як керувати хостом з контейнера докера?
Наприклад, як виконати скопійований в хост скрипт 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
.
Використовував іменовану трубу. На хост-OS створіть скрипт для циклу та читання команд, після чого ви викликаєте eval на цьому.
Нехай докер-контейнер прочитаний у цій іменованій трубі.
Щоб мати доступ до труби, вам потрібно змонтувати її через том.
Це схоже на механізм SSH (або подібний метод на основі сокета), але належним чином обмежує вас на хост-пристрої, що, ймовірно, краще. Крім того, вам не потрібно передавати інформацію про автентифікацію.
Моє єдине попередження - бути обережними щодо того, чому ви це робите. Це цілком щось робити, якщо ви хочете створити метод для самостійного оновлення за допомогою введення користувачем або що завгодно, але ви, мабуть, не хочете викликати команду, щоб отримати деякі дані конфігурації, оскільки правильним способом буде передавати це як аргументи / том у докер. Також будьте обережні щодо того, що ви оцінюєте, тому просто подумайте про модель дозволу.
Деякі з інших відповідей, таких як запуск скрипта. Під томом, не працюватимуть загалом, оскільки вони не матимуть доступу до повних системних ресурсів, але це може бути більш доречним залежно від вашого використання.
Якщо вас не турбує безпека, і ви просто хочете запустити контейнер докера на хості з іншого контейнера докера, такого як OP, ви можете поділитися сервером докера, що працює на хості, з контейнером докера, поділившись його сокетом прослуховування.
Будь ласка, перегляньте https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface і перевірте, чи дозволяє ваша особиста толерантність до ризику це для цього конкретного додатка.
Це можна зробити, додавши наступні аргументи обсягу до команди start
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
Коли ви запускаєте команду запуску докера в контейнері докера, сервер докера, запущений на вашому хості, побачить запит та надасть контейнер для братів і сестер.
кредит: http://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/
/usr/bin/docker:/usr/bin/docker
).
Ця відповідь - лише більш детальний варіант рішення Бредфорда Медейроса , який, як і для мене, виявився найкращою відповіддю, тому заслуга в ньому.
У своїй відповіді він пояснює, ЩО робити (з іменами труби ), але не точно ЯК це робити.
Треба визнати, що я не знав, як називаються труби, на той час, коли я читав його рішення. Тож я намагався його реалізувати (хоча насправді це дуже просто), але мені це вдалося, тому я радий допомогти, пояснивши, як я це зробив. Тож суть моєї відповіді полягає лише у деталізації команд, які потрібно виконати, щоб це працювало, але знову ж таки, заслуга в ньому.
На головному хості виберіть папку, куди ви хочете помістити свій файл із іменем pipe, наприклад, /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
у вашому файлі докера, щоб створити файл / шлях / до / труби як / hostpipe
Перезапустіть контейнери докера.
Виконайте в контейнері докера:
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 node на основний хост і отримую вихідні дані:
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, запустити curl або написання коду для створення запиту 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 - це, в основному, ізоляція процесу. Починаючи з docker 1.8, ви можете копіювати файли в обох напрямках між хостом і контейнером, див. Документdocker cp
https://docs.docker.com/reference/commandline/cp/
Після копіювання файлу ви можете запустити його локально
myvalue=$(docker run -it ubuntu echo $PATH)
і регулярно тестувати її в оболонці сценарію (звичайно, ви будете використовувати щось інше, ніж $ PATH, це лише приклад), коли це - це якесь конкретне значення, ви запускаєте свій скрипт
Ви можете використовувати концепцію pipe, але використовуйте файл на хості та 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, який отримував би права виконувати довільні речі на хості.
A
(src на github). У A
репо я створюю належні хуки, які після команди 'git pull' створюють новий образ докера і запускають їх (і звичайно видаляють старий контейнер). Далі: github має веб-хуки, які дозволяють створювати запит POST до довільного посилання на кінцеву точку після натискання на master. Тому я не хочу створювати докеризований сервіс B, який буде цією кінцевою точкою і який буде запускати лише 'git pull' у репо A на машині HOST (важливо: команда 'git pull' повинна виконуватися в середовищі HOST - а не в середовищі B, оскільки B не може запустити новий контейнер A всередині B ...)