จะควบคุมโฮสต์จากคอนเทนเนอร์นักเทียบท่าได้อย่างไร
ตัวอย่างเช่นวิธีดำเนินการคัดลอกไปยังโฮสต์ bash script?
จะควบคุมโฮสต์จากคอนเทนเนอร์นักเทียบท่าได้อย่างไร
ตัวอย่างเช่นวิธีดำเนินการคัดลอกไปยังโฮสต์ bash script?
คำตอบ:
นั่นขึ้นอยู่กับสิ่งที่คุณต้องการให้สคริปต์ทุบตีทำ!
ตัวอย่างเช่นหากสคริปต์ bash เพียงแค่สะท้อนเอาต์พุตบางส่วนคุณก็ทำได้
docker run --rm -v $(pwd)/mybashscript.sh:/mybashscript.sh ubuntu bash /mybashscript.sh
ความเป็นไปได้อีกประการหนึ่งคือคุณต้องการให้สคริปต์ทุบตีติดตั้งซอฟต์แวร์บางตัว - พูดสคริปต์เพื่อติดตั้งนักเทียบท่าเขียน คุณสามารถทำสิ่งที่ชอบ
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
ให้ Docker container อ่านไปป์ที่มีชื่อนั้น
เพื่อให้สามารถเข้าถึงท่อได้คุณต้องติดตั้งผ่านโวลุ่ม
สิ่งนี้คล้ายกับกลไก SSH (หรือวิธีการใช้ซ็อกเก็ตที่คล้ายกัน) แต่ จำกัด คุณไว้ที่อุปกรณ์โฮสต์ซึ่งน่าจะดีกว่า นอกจากนี้คุณไม่จำเป็นต้องส่งผ่านข้อมูลการรับรองความถูกต้อง
คำเตือนเดียวของฉันคือระมัดระวังว่าทำไมคุณถึงทำเช่นนี้ เป็นสิ่งที่ต้องทำโดยสิ้นเชิงหากคุณต้องการสร้างวิธีการอัปเกรดด้วยตนเองด้วยการป้อนข้อมูลของผู้ใช้หรืออะไรก็ตาม แต่คุณอาจไม่ต้องการเรียกคำสั่งเพื่อรับข้อมูลการกำหนดค่าบางอย่างเนื่องจากวิธีที่เหมาะสมคือการส่งผ่านเป็น args / volume เป็นนักเทียบท่า นอกจากนี้โปรดระมัดระวังเกี่ยวกับความจริงที่ว่าคุณกำลังหลบหลีกดังนั้นเพียงแค่คิดแบบจำลองการอนุญาต
บางส่วนของคำตอบอื่น ๆ เช่นการเรียกใช้สคริปต์ภายใต้ไดรฟ์ข้อมูลจะไม่ทำงานโดยทั่วไปเนื่องจากจะไม่สามารถเข้าถึงทรัพยากรระบบทั้งหมดได้ แต่อาจเหมาะสมกว่าขึ้นอยู่กับการใช้งานของคุณ
หากคุณไม่กังวลเกี่ยวกับความปลอดภัยและคุณต้องการเริ่มต้น Docker container บนโฮสต์จากภายใน docker container อื่นเช่น OP คุณสามารถแชร์เซิร์ฟเวอร์ Docker ที่ทำงานบนโฮสต์กับ Docker container ได้โดยแชร์ซ็อกเก็ตสำหรับฟัง
โปรดดูhttps://docs.docker.com/engine/security/security/#docker-daemon-attack-surfaceและดูว่าการยอมรับความเสี่ยงส่วนบุคคลของคุณอนุญาตให้ใช้กับแอปพลิเคชันนี้หรือไม่
คุณสามารถทำได้โดยเพิ่ม Volume args ต่อไปนี้ในคำสั่ง 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
เมื่อคุณรันคำสั่ง docker start ภายในคอนเทนเนอร์นักเทียบท่าของคุณเซิร์ฟเวอร์นักเทียบท่าที่ทำงานบนโฮสต์ของคุณจะเห็นคำขอและจัดเตรียมคอนเทนเนอร์พี่น้อง
เครดิต: http://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/
/usr/bin/docker:/usr/bin/docker
)
คำตอบนี้เป็นเพียงโซลูชันของ Bradford Medeiros ที่มีรายละเอียดมากขึ้นซึ่งสำหรับฉันแล้วก็กลายเป็นคำตอบที่ดีที่สุดดังนั้นเครดิตจึงไปที่เขา
ในคำตอบของเขาเขาอธิบายว่าต้องทำอะไร ( ตั้งชื่อไปป์ ) แต่ไม่ใช่ว่าจะทำอย่างไร
ฉันต้องยอมรับว่าฉันไม่รู้ว่าท่อชื่ออะไรในตอนที่ฉันอ่านวิธีแก้ปัญหาของเขา ดังนั้นฉันจึงพยายามที่จะนำมันไปใช้ (แม้ว่ามันจะง่ายมาก) แต่ฉันก็ทำได้สำเร็จดังนั้นฉันจึงยินดีที่จะช่วยอธิบายว่าฉันทำได้อย่างไร ดังนั้นประเด็นของคำตอบของฉันก็คือการให้รายละเอียดคำสั่งที่คุณต้องใช้เพื่อให้มันใช้งานได้ แต่อีกครั้งเครดิตจะไปที่เขา
บนโฮสต์หลักให้เลือกโฟลเดอร์ที่คุณต้องการใส่ไฟล์ไปป์ที่มีชื่อของคุณเช่น/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
) ควรแสดงคำว่า "hello world"
บนคอนเทนเนอร์โฮสต์แทนที่จะเรียกใช้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
ใน dockerfile ของคุณเพื่อสร้างจุดเชื่อมต่อ
จากนั้นเพิ่มสิ่งนี้:
volumes:
- /path/to/pipe:/hostpipe
ในไฟล์นักเทียบท่าของคุณเขียนเพื่อติดตั้ง / path / to / pipe as / hostpipe
รีสตาร์ทคอนเทนเนอร์นักเทียบท่าของคุณ
ดำเนินการในคอนเทนเนอร์นักเทียบท่าของคุณ:
docker exec -it <container> bash
เข้าไปในโฟลเดอร์ mount และตรวจสอบว่าคุณสามารถเห็นท่อ:
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 / issue / 483 ) เนื่องจากการใช้งานไปป์ไม่เหมือนกันดังนั้นสิ่งที่คุณเขียนลงในไพพ์จาก Linux สามารถอ่านได้โดย Linux เท่านั้นและสิ่งที่คุณเขียนลงในไพพ์จาก Mac OS สามารถอ่านได้โดย a Mac OS (ประโยคนี้อาจไม่ถูกต้องมากนัก แต่โปรดทราบว่ามีปัญหาข้ามแพลตฟอร์มอยู่)
ตัวอย่างเช่นเมื่อฉันเรียกใช้การตั้งค่านักเทียบท่าใน DEV จากคอมพิวเตอร์ Mac OS ไปป์ที่มีชื่อตามที่อธิบายไว้ข้างต้นไม่ทำงาน แต่ในการจัดเตรียมและการผลิตฉันมีโฮสต์ Linux และคอนเทนเนอร์ Linux และทำงานได้อย่างสมบูรณ์
นี่คือวิธีที่ฉันส่งคำสั่งจาก node js container ไปยังโฮสต์หลักและดึงเอาท์พุท:
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 ที่เรียกใช้เชลล์สคริปต์ด้วยป๊อปเพนเรียกใช้ 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()
ความขี้เกียจของฉันทำให้ฉันพบวิธีแก้ปัญหาที่ง่ายที่สุดที่ไม่ได้เผยแพร่เป็นคำตอบที่นี่
มันจะขึ้นอยู่กับบทความดีดีจากjuggery luc
สิ่งที่คุณต้องทำเพื่อให้ได้เชลล์เต็มไปยังโฮสต์ linux ของคุณจากภายในคอนเทนเนอร์นักเทียบท่าของคุณคือ:
docker run --privileged --pid=host -it alpine:3.8 \
nsenter -t 1 -m -u -n -i sh
คำอธิบาย:
--privileged: ให้สิทธิ์เพิ่มเติมกับคอนเทนเนอร์ทำให้คอนเทนเนอร์สามารถเข้าถึงอุปกรณ์ของโฮสต์ (/ dev)
--pid = host: อนุญาตให้คอนเทนเนอร์ใช้แผนผังกระบวนการของโฮสต์ Docker (VM ที่ Docker daemon กำลังทำงานอยู่) ยูทิลิตี้ nsenter: อนุญาตให้รันกระบวนการในเนมสเปซที่มีอยู่ (บล็อคส่วนประกอบที่ให้การแยกกับคอนเทนเนอร์)
nsenter (-t 1 -m -u -n -i sh) อนุญาตให้รันกระบวนการ sh ในบริบทการแยกเดียวกันกับกระบวนการที่มี PID 1 จากนั้นคำสั่งทั้งหมดจะจัดเตรียมเชลล์ sh แบบโต้ตอบใน VM
การตั้งค่านี้มีผลกระทบด้านความปลอดภัยที่สำคัญและควรใช้ด้วยความระมัดระวัง (ถ้ามี)
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: ดำเนินการด้านล่างนี้ภายในคอนเทนเนอร์ของคุณ ส่วนสำคัญที่นี่คือ (- โฮสต์เครือข่ายเนื่องจากจะดำเนินการจากบริบทโฮสต์)
นักเทียบท่าวิ่ง -i --rm - โฮสต์เครือข่าย -v /opt/test.sh:/test.sh อัลไพน์: 3.7 sh /test.sh
test.sh ควรมีคำสั่งบางอย่าง (ifconfig, netstat ฯลฯ ... ) สิ่งที่คุณต้องการ ตอนนี้คุณจะสามารถรับเอาต์พุตบริบทของโฮสต์ได้
ตามที่มาร์คัสเตือนนักเทียบท่านั้นโดยพื้นฐานแล้วการแยกกระบวนการ เริ่มต้นด้วยนักเทียบท่า 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
หากต้องการขยายการ ตอบสนองของผู้ใช้ 2915097 :
แนวคิดของการแยกคือการสามารถ จำกัด สิ่งที่แอปพลิเคชัน / กระบวนการ / คอนเทนเนอร์ (ไม่ว่าคุณจะอยู่ที่มุมใดก็ตาม) สามารถทำกับระบบโฮสต์ได้อย่างชัดเจน ดังนั้นความสามารถในการคัดลอกและเรียกใช้ไฟล์จะทำลายแนวคิดทั้งหมด
ใช่. แต่บางครั้งก็จำเป็น
ไม่เป็นเช่นนั้นหรือ Docker ไม่ใช่สิ่งที่ถูกต้องที่จะใช้ สิ่งที่คุณควรทำคือการประกาศอินเตอร์เฟซที่ชัดเจนสำหรับสิ่งที่คุณต้องการจะทำ (เช่นการปรับปรุงการกำหนดค่าโฮสต์) และเขียนลูกค้าน้อยที่สุด / เซิร์ฟเวอร์ที่จะทำตรงนั้นและไม่มีอะไรเพิ่มเติม อย่างไรก็ตามโดยทั่วไปสิ่งนี้ดูเหมือนจะไม่เป็นที่ต้องการมากนัก ในหลาย ๆ กรณีคุณควรคิดใหม่และกำจัดความต้องการนั้นให้สิ้นซาก นักเทียบท่าเกิดขึ้นเมื่อโดยพื้นฐานแล้วทุกอย่างเป็นบริการที่สามารถเข้าถึงได้โดยใช้โปรโตคอลบางอย่าง ฉันไม่สามารถนึกถึงกรณีการใช้งานที่เหมาะสมของคอนเทนเนอร์ Docker ที่ได้รับสิทธิ์ในการดำเนินการตามอำเภอใจบนโฮสต์
A
(src บน github) ในA
repo ฉันสร้าง hooks ที่เหมาะสมซึ่งหลังจากคำสั่ง 'git pull' สร้างอิมเมจนักเทียบท่าใหม่และเรียกใช้ (และลบคอนเทนเนอร์เก่าแน่นอน) ถัดไป: github มี web-hooks ซึ่งอนุญาตให้สร้างคำขอ POST ไปยังลิงก์ปลายทางโดยพลการหลังจากกดบน master ดังนั้นฉันจึงไม่ต้องการสร้างบริการ Dockerized B ซึ่งจะเป็นจุดสิ้นสุดนั้นและจะเรียกใช้ 'git pull' ใน repo A ในเครื่อง HOST เท่านั้น (สำคัญ: คำสั่ง 'git pull' ต้องดำเนินการในสภาพแวดล้อม HOST ไม่ใช่ในสภาพแวดล้อม B เพราะ B ไม่สามารถเรียกใช้คอนเทนเนอร์ใหม่ A ภายใน B ... )