Docker 컨테이너에서 호스트를 제어하는 방법은 무엇입니까?
예를 들어, 호스트 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
그러나이 시점에서 컨테이너 내부에서 호스트에 필요한 특정 권한을 허용하기 위해 스크립트가 수행하는 작업을 자세히 알아야합니다.
답변
내가 사용하는 해결책은 호스트에 연결하고 다음 SSH
과 같은 명령을 실행하는 것입니다.
ssh -l ${USERNAME} ${HOSTNAME} "${SCRIPT}"
최신 정보
이 답변이 계속 투표를 받기 때문에 스크립트를 호출하는 데 사용되는 계정은 권한이 전혀없는 계정이어야하지만 해당 스크립트를 다음과 같이 실행해야한다는 점을 상기시키고 싶습니다 sudo
. sudoers
파일 에서 완료 ).
답변
명명 된 파이프를 사용했습니다. 호스트 os에서 명령을 반복하고 읽는 스크립트를 만든 다음 eval을 호출합니다.
Docker 컨테이너가 명명 된 파이프를 읽도록합니다.
파이프에 액세스하려면 볼륨을 통해 마운트해야합니다.
이것은 SSH 메커니즘 (또는 유사한 소켓 기반 방법)과 유사하지만 호스트 장치로 적절하게 제한하므로 아마도 더 좋을 것입니다. 또한 인증 정보를 전달할 필요가 없습니다.
나의 유일한 경고는 당신이 이것을하는 이유에 대해 조심하라는 것입니다. 사용자 입력 등으로 자체 업그레이드하는 방법을 만들고 싶다면 완전히해야 할 일이지만, 적절한 방법은 args로 전달하는 것이기 때문에 일부 구성 데이터를 가져 오는 명령을 호출하고 싶지 않을 것입니다. / 볼륨을 도커에 넣습니다. 또한 회피하고 있다는 사실에주의해야하므로 허가 모델에 대해 생각해보십시오.
볼륨에서 스크립트를 실행하는 것과 같은 다른 답변 중 일부는 전체 시스템 리소스에 액세스 할 수 없기 때문에 일반적으로 작동하지 않지만 사용량에 따라 더 적절할 수 있습니다.
답변
보안에 대해 걱정하지 않고 단순히 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 컨테이너 내에서 docker start 명령을 실행하면 호스트에서 실행중인 Docker 서버가 요청을보고 형제 컨테이너를 프로비저닝합니다.
크레딧 : http://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/
답변
이 답변은 Bradford Medeiros 솔루션의 더 자세한 버전 일 뿐이며 저에게도 가장 좋은 답변으로 판명되었으므로 그에게 신용이 돌아갑니다.
그의 대답에서 그는 무엇을해야하는지 ( 파이프 명명 ) 설명하지만 정확히 어떻게해야하는지 설명하지 않습니다.
나는 그의 해결책을 읽을 당시에 명명 된 파이프가 무엇인지 몰랐다는 것을 인정해야한다. 그래서 구현하는 데 어려움을 겪었지만 (실제로는 정말 간단하지만) 성공 했으므로 어떻게했는지 설명함으로써 기꺼이 도와 드리겠습니다. 그래서 내 대답의 요점은 작동하기 위해 실행해야 할 명령을 자세히 설명하는 것입니다.
1 부-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
“hello world”가 표시되어야합니다.
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와 함께 작동하도록 만들기
나처럼 docker compose와 dockerfile을 모두 사용하는 경우 다음 작업을 수행했습니다.
/hostpipe
컨테이너에서 와 같이 mypipe의 상위 폴더를 마운트한다고 가정 해 보겠습니다.
이거 추가 해봐:
VOLUME /hostpipe
마운트 지점을 만들기 위해 dockerfile에서
그런 다음 이것을 추가하십시오.
volumes:
- /path/to/pipe:/hostpipe
도커에서 / path / to / pipe를 / hostpipe로 마운트하기 위해 파일을 작성하십시오.
Docker 컨테이너를 다시 시작하십시오.
6 부-테스트
Docker 컨테이너로 실행하십시오.
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 (이 문장은 정확하지 않을 수 있지만 크로스 플랫폼 문제가 존재한다는 점만 기억하십시오).
예를 들어, Mac OS 컴퓨터에서 DEV에서 Docker 설정을 실행하면 위에서 설명한대로 명명 된 파이프가 작동하지 않습니다. 그러나 스테이징 및 프로덕션에서는 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);
답변
포트 (예 : 8080)에서 수신 대기하는 간단한 서버 파이썬 서버를 작성하고, -p 8080 : 8080 포트를 컨테이너에 바인딩하고, localhost : 8080에 HTTP 요청을 수행하여 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 의 위대한 기사 를 기반으로합니다 .
Docker 컨테이너 내에서 Linux 호스트에 대한 전체 셸을 얻으려면 다음을 수행하면됩니다.
docker run --privileged --pid=host -it alpine:3.8 \
nsenter -t 1 -m -u -n -i sh
설명:
–privileged : 컨테이너에 추가 권한을 부여하고 컨테이너가 호스트 (/ dev)의 장치에 액세스 할 수 있도록합니다.
–pid = host : 컨테이너가 Docker 호스트 (Docker 데몬이 실행중인 VM)의 프로세스 트리를 사용할 수 있도록합니다. nsenter 유틸리티 : 기존 네임 스페이스 (컨테이너에 격리를 제공하는 빌딩 블록)에서 프로세스를 실행할 수 있습니다.
nsenter (-t 1 -m -u -n -i sh)를 사용하면 PID 1이있는 프로세스와 동일한 격리 컨텍스트에서 sh 프로세스를 실행할 수 있습니다. 그러면 전체 명령이 VM에 대화 형 sh 셸을 제공합니다.
이 설정은 보안에 중요한 영향을 미치며주의하여 사용해야합니다 (있는 경우).