Làm cách nào để kiểm soát máy chủ từ vùng chứa docker?
Ví dụ, làm thế nào để thực thi được sao chép vào tập lệnh bash của máy chủ lưu trữ?
Làm cách nào để kiểm soát máy chủ từ vùng chứa docker?
Ví dụ, làm thế nào để thực thi được sao chép vào tập lệnh bash của máy chủ lưu trữ?
Câu trả lời:
Điều đó THỰC SỰ phụ thuộc vào những gì bạn cần tập lệnh bash đó để làm!
Ví dụ: nếu tập lệnh bash chỉ lặp lại một số đầu ra, bạn có thể làm
docker run --rm -v $(pwd)/mybashscript.sh:/mybashscript.sh ubuntu bash /mybashscript.sh
Một khả năng khác là bạn muốn tập lệnh bash để cài đặt một số phần mềm - giả sử tập lệnh để cài đặt docker-comp. bạn có thể làm một cái gì đó như
docker run --rm -v /usr/bin:/usr/bin --privileged -v $(pwd)/mybashscript.sh:/mybashscript.sh ubuntu bash /mybashscript.sh
Nhưng tại thời điểm này, bạn thực sự phải biết rõ tập lệnh đang làm gì để cho phép các quyền cụ thể mà nó cần trên máy chủ của bạn từ bên trong vùng chứa.
docker run --rm -v $(pwd)/mybashscript.sh:/work/mybashscript.sh ubuntu /work/mybashscript.sh
/usr/bin
với vùng chứa. Trong cả hai trường hợp, vùng chứa đều không có quyền truy cập đầy đủ vào hệ thống máy chủ. Có lẽ tôi sai, nhưng nó có vẻ là một câu trả lời tồi cho một câu hỏi tồi.
Giải pháp tôi sử dụng là kết nối với máy chủ lưu trữ SSH
và thực hiện lệnh như sau:
ssh -l ${USERNAME} ${HOSTNAME} "${SCRIPT}"
Vì câu trả lời này tiếp tục nhận được phiếu bầu, tôi muốn nhắc nhở (và đặc biệt khuyên bạn) rằng tài khoản đang được sử dụng để gọi tập lệnh phải là tài khoản không có quyền nào cả, mà chỉ thực thi tập lệnh đó với tư cách là sudo
(có thể thực hiện từ sudoers
tệp).
ssh
không tìm thấy. Bạn có bất cứ lời đề nghị khác?
apt update && apt install openssh-client
.
Đã sử dụng một đường ống được đặt tên. Trên hệ điều hành máy chủ, tạo một tập lệnh để lặp và đọc các lệnh, sau đó bạn gọi eval trên đó.
Yêu cầu bộ chứa docker đọc đến đường ống được đặt tên đó.
Để có thể truy cập đường ống, bạn cần phải gắn nó qua một ổ đĩa.
Điều này tương tự như cơ chế SSH (hoặc một phương pháp dựa trên ổ cắm tương tự), nhưng hạn chế bạn thích hợp với thiết bị chủ, có lẽ tốt hơn. Thêm vào đó, bạn không cần phải chuyển thông tin xác thực.
Cảnh báo duy nhất của tôi là hãy thận trọng về lý do tại sao bạn đang làm điều này. Đó là điều hoàn toàn cần làm nếu bạn muốn tạo một phương thức để tự nâng cấp với đầu vào của người dùng hoặc bất cứ điều gì, nhưng có thể bạn không muốn gọi một lệnh để lấy một số dữ liệu cấu hình, vì cách thích hợp sẽ là chuyển nó vào dưới dạng args / volume vào docker. Cũng nên thận trọng về thực tế là bạn đang đánh giá, vì vậy chỉ cần suy nghĩ về mô hình cấp phép.
Một số câu trả lời khác như chạy script. Dưới một ổ đĩa sẽ không hoạt động chung vì chúng sẽ không có quyền truy cập vào toàn bộ tài nguyên hệ thống, nhưng nó có thể phù hợp hơn tùy thuộc vào cách sử dụng của bạn.
Nếu bạn không lo lắng về bảo mật và chỉ đơn giản là bạn đang tìm cách khởi động một bộ chứa docker trên máy chủ từ bên trong một bộ chứa docker khác như OP, bạn có thể chia sẻ máy chủ docker đang chạy trên máy chủ với bộ chứa docker bằng cách chia sẻ ổ cắm lắng nghe của nó.
Vui lòng xem https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface và xem khả năng chấp nhận rủi ro cá nhân của bạn có cho phép điều này đối với ứng dụng cụ thể này hay không.
Bạn có thể thực hiện việc này bằng cách thêm các vòng khối lượng sau vào lệnh bắt đầu của mình
docker run -v /var/run/docker.sock:/var/run/docker.sock ...
hoặc bằng cách chia sẻ /var/run/docker.sock trong tệp soạn thảo docker của bạn như sau:
version: '3'
services:
ci:
command: ...
image: ...
volumes
- /var/run/docker.sock:/var/run/docker.sock
Khi bạn chạy lệnh bắt đầu docker trong vùng chứa docker của mình, máy chủ docker đang chạy trên máy chủ của bạn sẽ thấy yêu cầu và cung cấp vùng chứa anh chị em.
tín dụng: http://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/
/usr/bin/docker:/usr/bin/docker
:).
Câu trả lời này chỉ là một phiên bản chi tiết hơn của giải pháp của Bradford Medeiros , đối với tôi nó cũng hóa ra là câu trả lời tốt nhất, vì vậy công lao sẽ thuộc về anh ta.
Trong câu trả lời của mình, anh ấy giải thích LÀM GÌ ( đường ống được đặt tên ) nhưng không chính xác LÀM THẾ NÀO để làm điều đó.
Tôi phải thừa nhận rằng tôi không biết những gì được đặt tên là ống dẫn tại thời điểm tôi đọc giải pháp của mình. Vì vậy, tôi đã cố gắng thực hiện nó (trong khi nó thực sự đơn giản), nhưng tôi đã thành công, vì vậy tôi rất vui được giúp đỡ bằng cách giải thích cách tôi đã làm. Vì vậy, quan điểm của câu trả lời của tôi là chỉ nêu chi tiết các lệnh bạn cần chạy để nó hoạt động, nhưng một lần nữa, tín dụng sẽ thuộc về anh ta.
Trên máy chủ chính, chọn thư mục mà bạn muốn đặt tệp ống dẫn có tên của mình, ví dụ: /path/to/pipe/
và tên ống dẫn mypipe
, sau đó chạy:
mkfifo /path/to/pipe/mypipe
Đường ống được tạo ra. Kiểu
ls -l /path/to/pipe/mypipe
Và kiểm tra các quyền truy cập bắt đầu bằng "p", chẳng hạn như
prw-r--r-- 1 root root 0 mypipe
Bây giờ chạy:
tail -f /path/to/pipe/mypipe
Thiết bị đầu cuối hiện đang đợi dữ liệu được gửi vào đường ống này
Bây giờ hãy mở một cửa sổ đầu cuối khác.
Và sau đó chạy:
echo "hello world" > /path/to/pipe/mypipe
Kiểm tra thiết bị đầu cuối đầu tiên (thiết bị đầu cuối có tail -f
), nó sẽ hiển thị "hello world"
Trên vùng chứa máy chủ, thay vì chạy tail -f
chỉ xuất bất cứ thứ gì được gửi dưới dạng đầu vào, hãy chạy lệnh này sẽ thực thi nó dưới dạng các lệnh:
eval "$(cat /path/to/pipe/mypipe)"
Sau đó, từ thiết bị đầu cuối khác, hãy thử chạy:
echo "ls -l" > /path/to/pipe/mypipe
Quay lại terminal đầu tiên và bạn sẽ thấy kết quả của ls -l
lệnh.
Bạn có thể nhận thấy rằng trong phần trước, ngay sau khi ls -l
đầu ra được hiển thị, nó sẽ dừng nghe lệnh.
Thay vì eval "$(cat /path/to/pipe/mypipe)"
chạy:
while true; do eval "$(cat /path/to/pipe/mypipe)"; done
(bạn có thể nohup điều đó)
Giờ đây, bạn có thể gửi lần lượt số lượng lệnh không giới hạn, tất cả chúng sẽ được thực thi, không chỉ lệnh đầu tiên.
Cảnh báo duy nhất là nếu máy chủ phải khởi động lại, vòng lặp "while" sẽ ngừng hoạt động.
Để xử lý khởi động lại, đây là những gì tôi đã làm:
Đưa while true; do eval "$(cat /path/to/pipe/mypipe)"; done
vào một tệp execpipe.sh
có #!/bin/bash
tiêu đề
Đừng quên chmod +x
nó
Thêm nó vào crontab bằng cách chạy
crontab -e
Và sau đó thêm
@reboot /path/to/execpipe.sh
Tại thời điểm này, hãy kiểm tra nó: khởi động lại máy chủ của bạn và khi nó được sao lưu, hãy lặp lại một số lệnh vào đường dẫn và kiểm tra xem chúng có được thực thi hay không. Tất nhiên, bạn không thể thấy đầu ra của các lệnh, vì vậy ls -l
sẽ không giúp ích được gì, nhưng touch somefile
sẽ hữu ích.
Một tùy chọn khác là sửa đổi tập lệnh để đưa đầu ra vào một tệp, chẳng hạn như:
while true; do eval "$(cat /path/to/pipe/mypipe)" &> /somepath/output.txt; done
Bây giờ bạn có thể chạy ls -l
và đầu ra (cả stdout và stderr sử dụng &>
trong bash) phải ở trong output.txt.
Nếu bạn đang sử dụng cả docker compile và dockerfile như tôi, đây là những gì tôi đã làm:
Giả sử bạn muốn gắn kết thư mục mẹ của mypipe như /hostpipe
trong vùng chứa của bạn
Thêm điều này:
VOLUME /hostpipe
trong tập tin docker của bạn để tạo điểm gắn kết
Sau đó thêm cái này:
volumes:
- /path/to/pipe:/hostpipe
trong tệp soạn thảo docker của bạn để mount / path / to / pipe as / hostpipe
Khởi động lại bộ chứa docker của bạn.
Thực thi vào vùng chứa docker của bạn:
docker exec -it <container> bash
Vào thư mục mount và kiểm tra, bạn có thể thấy đường dẫn:
cd /hostpipe && ls -l
Bây giờ hãy thử chạy một lệnh từ bên trong vùng chứa:
echo "touch this_file_was_created_on_main_host_from_a_container.txt" > /hostpipe/mypipe
Và nó sẽ hoạt động!
CẢNH BÁO: Nếu bạn có máy chủ OSX (Mac OS) và vùng chứa Linux, nó sẽ không hoạt động (giải thích tại đây https://stackoverflow.com/a/43474708/10018801 và sự cố tại đây https://github.com/docker / for-mac / issue / 483 ) vì cách triển khai đường dẫn không giống nhau, vì vậy những gì bạn viết vào đường dẫn từ Linux chỉ có thể được đọc bởi Linux và những gì bạn viết vào đường dẫn từ Mac OS chỉ có thể được đọc bởi một Mac OS (câu này có thể không chính xác lắm, nhưng chỉ cần lưu ý rằng có một vấn đề đa nền tảng).
Ví dụ: khi tôi chạy thiết lập docker trong DEV từ máy tính Mac OS của mình, đường ống có tên như đã giải thích ở trên không hoạt động. Nhưng trong phần dàn dựng và sản xuất, tôi có máy chủ Linux và bộ chứa Linux, và nó hoạt động hoàn hảo.
Đây là cách tôi gửi một lệnh từ vùng chứa nút js của tôi đến máy chủ chính và truy xuất đầu ra:
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);
Viết một máy chủ python máy chủ đơn giản đang lắng nghe trên một cổng (giả sử 8080), liên kết cổng -p 8080: 8080 với vùng chứa, thực hiện một yêu cầu HTTP tới localhost: 8080 để yêu cầu máy chủ python chạy các tập lệnh shell với popen, chạy một cuộn hoặc viết mã để tạo một yêu cầu 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()
Sự lười biếng của tôi đã khiến tôi tìm ra giải pháp dễ dàng nhất mà chưa được công bố như một câu trả lời ở đây.
Nó dựa trên bài báo tuyệt vời của luc juggery .
Tất cả những gì bạn cần làm để có được một trình bao đầy đủ cho máy chủ linux của bạn từ bên trong vùng chứa docker của bạn là:
docker run --privileged --pid=host -it alpine:3.8 \
nsenter -t 1 -m -u -n -i sh
Giải trình:
--privileged: cấp quyền bổ sung cho vùng chứa, nó cho phép vùng chứa có quyền truy cập vào các thiết bị của máy chủ (/ dev)
--pid = host: cho phép các vùng chứa sử dụng cây quy trình của máy chủ Docker (máy ảo mà daemon Docker đang chạy) Tiện ích nsenter: cho phép chạy một tiến trình trong các không gian tên hiện có (các khối xây dựng cung cấp cách ly cho các vùng chứa)
nsenter (-t 1 -m -u -n -i sh) cho phép chạy quá trình sh trong cùng bối cảnh cách ly với quá trình với PID 1. Toàn bộ lệnh sau đó sẽ cung cấp một trình bao tương tác trong VM
Thiết lập này có ý nghĩa bảo mật chính và nên được sử dụng cẩn thận (nếu có).
docker run --detach-keys="ctrl-p" -it -v /:/mnt/rootdir --name testing busybox
# chroot /mnt/rootdir
#
Tôi có một cách tiếp cận đơn giản.
Bước 1: Mount /var/run/docker.sock:/var/run/docker.sock (Vì vậy, bạn sẽ có thể thực hiện các lệnh docker bên trong vùng chứa của mình)
Bước 2: Thực hiện điều này bên dưới bên trong vùng chứa của bạn. Phần quan trọng ở đây là ( - máy chủ mạng vì nó sẽ thực thi từ ngữ cảnh máy chủ)
docker run -i --rm --network host -v /opt/test.sh:/test.sh alpine: 3.7 sh /test.sh
test.sh nên chứa một số lệnh (ifconfig, netstat, v.v.) bất cứ thứ gì bạn cần. Bây giờ bạn sẽ có thể nhận được đầu ra ngữ cảnh máy chủ.
Như Marcus đã nhắc, về cơ bản docker là quá trình cô lập. Bắt đầu với docker 1.8, bạn có thể sao chép tệp theo cả hai cách giữa máy chủ và vùng chứa, hãy xem tài liệu củadocker cp
https://docs.docker.com/reference/commandline/cp/
Sau khi một tệp được sao chép, bạn có thể chạy nó cục bộ
myvalue=$(docker run -it ubuntu echo $PATH)
và kiểm tra nó thường xuyên trong một trình bao tập lệnh (tất nhiên, bạn sẽ sử dụng một cái gì đó khác ngoài $ PATH, chỉ là một ví dụ), khi nó được một số giá trị cụ thể, bạn khởi động kịch bản của bạn
Bạn có thể sử dụng khái niệm ống dẫn, nhưng sử dụng tệp trên máy chủ và fswatch để thực hiện mục tiêu thực thi một tập lệnh trên máy chủ từ bộ chứa docker. Như vậy (Sử dụng có rủi ro của riêng bạn):
#! /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
Trong ví dụ này, bên trong vùng chứa gửi lệnh tới / dev / command_pipe, như sau:
/home/test # echo 'docker network create test2.network.com' > /dev/command_pipe
Trên máy chủ, bạn có thể kiểm tra xem mạng đã được tạo chưa:
$ docker network ls | grep test2
8e029ec83afe test2.network.com bridge local
Để mở rộng phản hồi của người dùng2915097 :
Ý tưởng của sự cô lập là có thể hạn chế những gì một ứng dụng / quy trình / vùng chứa (bất kể góc độ của bạn ở đây là gì) có thể làm với hệ thống máy chủ rất rõ ràng. Do đó, có thể sao chép và thực thi một tệp sẽ thực sự phá vỡ toàn bộ khái niệm.
Đúng. Nhưng nó đôi khi cần thiết.
Không. Không phải vậy, hoặc Docker không phải là thứ thích hợp để sử dụng. Những gì bạn nên làm là khai báo một giao diện rõ ràng cho những gì bạn muốn làm (ví dụ: cập nhật cấu hình máy chủ), và viết một máy khách / máy chủ tối thiểu để thực hiện chính xác điều đó và không cần thêm gì nữa. Nói chung, tuy nhiên, điều này có vẻ không được mong muốn cho lắm. Trong nhiều trường hợp, bạn chỉ nên nghĩ lại cách tiếp cận của mình và loại bỏ nhu cầu đó. Docker ra đời khi về cơ bản mọi thứ đều là một dịch vụ có thể truy cập được bằng một số giao thức. Tôi không thể nghĩ ra bất kỳ cách sử dụng thích hợp nào của vùng chứa Docker có được quyền thực thi các nội dung tùy ý trên máy chủ.
A
(src trên github). Trong A
repo, tôi tạo các hook thích hợp mà sau lệnh 'git pull', tạo hình ảnh docker mới và chạy chúng (tất nhiên là xóa vùng chứa cũ). Tiếp theo: github có các web-hook cho phép tạo yêu cầu POST đến liên kết điểm cuối tùy ý sau khi push on master. Vì vậy, tôi sẽ không tạo dịch vụ dày đặc B sẽ là điểm cuối đó và sẽ chỉ chạy 'git pull' trong repo A trong máy HOST (quan trọng: lệnh 'git pull' phải được thực hiện trong môi trường HOST - không phải trong môi trường B vì B không thể chạy container mới A bên trong B ...)