Dockerコンテナからホスト上でシェルスクリプトを実行する方法は?


94

Dockerコンテナからホストを制御する方法は?

たとえば、コピーされたホストbashスクリプトを実行する方法は?


8
これは、ホストをDockerから分離することとは正反対ではないでしょうか。
マーカスミュラー

31
はい。しかし、それは時々必要です。
Alex Ushakov 2015


「コントロールホスト」についてはよくわかりませんが、最近、Dockerを使用してスクリプトを実行して巨大なワークロードを処理し(AWSにマウントされたGPUを使用)、結果をホストに出力しているデータサイエンティストの話を聞きました。非常に興味深いユースケース。dockerのおかげで信頼性の高い実行環境でパッケージ化された基本的なスクリプト
KCD

@KCDそして、なぜ彼らはシステムレベルのコンテナー(LXC)を使用するのではなく、Dockerを介したアプリコンテナー化を好むのですか?
Alex Ushakov 2016年

回答:


28

それは本当にあなたがそのbashスクリプトが何をする必要があるかに依存します!

たとえば、bashスクリプトが出力をエコーするだけの場合は、次のようにすることができます。

docker run --rm -v $(pwd)/mybashscript.sh:/mybashscript.sh ubuntu bash /mybashscript.sh

もう1つの可能性は、bashスクリプトでソフトウェアをインストールすることです。たとえば、docker-composeをインストールするスクリプトです。あなたは次のようなことをすることができます

docker run --rm -v /usr/bin:/usr/bin --privileged -v $(pwd)/mybashscript.sh:/mybashscript.sh ubuntu bash /mybashscript.sh

しかし、この時点で、コンテナー内からホストに必要な特定のアクセス許可を許可するために、スクリプトが何を実行しているかを詳しく知る必要があります。


1
ホストに接続して新しいコンテナーを作成するコンテナーを作成することを考えました。
Alex Ushakov 2015

1
Dockerは相対的なマウントを好まないようです。これはうまくいくはずですdocker run --rm -v $(pwd)/mybashscript.sh:/work/mybashscript.sh ubuntu /work/mybashscript.sh
KCD 2016年

5
最初の行は新しいubuntuコンテナーを開始し、スクリプトを読み取れる場所にマウントします。たとえば、コンテナがホストファイルシステムにアクセスすることは許可されません。2行目は、ホスト/usr/binをコンテナに公開します。どちらの場合も、コンテナはホストシステムへのフルアクセスを持っていません。私は間違っているかもしれませんが、それは悪い質問に対する悪い答えのようです。
ポール

3
十分に公平です-質問はかなり曖昧でした。質問は「ホストシステムへのフルアクセス」を求めていませんでした。説明したように、bashスクリプトが一部の出力をエコーすることだけを目的としている場合は、ホストファイルシステムにアクセスする必要はありません。docker-composeをインストールしていた私の2番目の例では、必要な唯一のアクセス許可は、バイナリが格納されるbinディレクトリへのアクセスです。最初に言ったように、これを行うには、適切なアクセス許可を許可するためにスクリプトが何を行っているかについて、非常に具体的なアイデアが必要になります。
Paul Becotte 2017

1
これを試してみましたが、スクリプトはホストではなくコンテナで実行されます
All2Pie

60

私が使用する解決策は、ホストに接続しSSHて次のようなコマンドを実行することです。

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

更新

この回答は票を取得し続けるように、私は、スクリプトを呼び出すために使用されているアカウントが、まったく権限を持つアカウントであるが、唯一のようにそのスクリプトを実行する必要があることを、思い出させる(と非常にお勧め)したいと思いますsudo(それが可能sudoersファイルから実行)。


別の回避策として、コンテナは出力コマンドのセットとホストは、コンテナの終了後にそれらを実行することができますことができます:evalの$(ドッカ実行を--rm -it container_name_to_outputスクリプト)
parity3

Dockerコンテナ内からホストでコマンドラインを実行する必要がありますが、コンテナに入るsshと見つかりません。他に何か提案はありますか?
ロンローゼンフェルド2018

@RonRosenfeld、どのDockerイメージを使用していますか?debian / ubuntuの場合は、次のコマンドを実行しますapt update && apt install openssh-client
Mohammed Noureldin 2018

SynologyNASにインストールされたものなら何でもかまいません。どうすればわかりますか?
ロンローゼンフェルド2018

@RonRosenfeld、申し訳ありませんが、あなたが何を意味するのかわかりません
Mohammed Noureldin 2018

52

名前付きパイプを使用しました。ホストOSで、コマンドをループして読み取るスクリプトを作成し、その上でevalを呼び出します。

Dockerコンテナにその名前付きパイプを読み取らせます。

パイプにアクセスできるようにするには、ボリュームを介してパイプをマウントする必要があります。

これはSSHメカニズム(または同様のソケットベースの方法)に似ていますが、ホストデバイスに適切に制限されます。これはおそらくより良い方法です。さらに、認証情報を渡す必要はありません。

私の唯一の警告は、なぜあなたがこれをしているのかについて注意することです。ユーザー入力などで自己アップグレードするメソッドを作成したい場合は、完全に何かを行う必要がありますが、コマンドを呼び出して構成データを取得することはおそらく望ましくありません。適切な方法は、それを引数として渡すことです。 / volumeをdockerに入れます。また、回避しているという事実にも注意してください。許可モデルについて考えてみてください。

スクリプトの実行など、ボリュームの下でのその他の回答の一部は、システムリソース全体にアクセスできないため、一般的には機能しませんが、使用状況によってはより適切な場合があります。


14
注意:これは正しい/最良の答えであり、もう少し賞賛が必要です。他のすべての答えは、「あなたが何をしようとしているのか」を尋ねたり、物事の例外を作ったりすることをいじっています。私にはこれを実行できるようにする必要がある非常に特殊なユースケースがあり、これが唯一の良い答えです。上記のSSHは、セキュリティ/ファイアウォールの標準を下げる必要があり、dockerrunのものは完全に間違っています。これをありがとう。これは単純なコピー/貼り付けの答えではないため、賛成票はそれほど多くないと思いますが、これが答えです。できれば私から+100ポイント
Farley

3
さらに詳しい情報をお探しの場合は、ホストマシンで実行されている次のスクリプトを使用できます。unix.stackexchange.com / a / 369465もちろん、「nohup」を指定して実行し、何らかのスーパーバイザラッパーを作成する必要があります。生きている、それを維持するために(多分、cronジョブを使用:Pを)
sucotronic

7
これは良い答えかもしれません。ただし、詳細とコマンドラインの説明を追加すると、はるかに良いでしょう。詳述することは可能ですか?
MohammedNoureldin19年

5
賛成、これはうまくいきます!ボリュームがマウントされている「mkfifohost_executor_queue」を使用して名前付きパイプを作成します。次に、ホストのシェルとしてキューに入れられるコマンドを実行するコンシューマーを追加するには、 'tail -f host_executor_queue |を使用します。sh& '。最後の&は、バックグラウンドで実行されます。最後に、コマンドをキューにプッシュするには、「echo touch foo> host_executor_queue」を使用します。このテストでは、ホームディレクトリに一時ファイルfooを作成します。コンシューマーをシステムの起動時に開始する場合は、 '@ reboot tail -f host_executor_queue |を入力します。crontabのsh& '。host_executor_queueに相対パスを追加するだけです。
skybunk

1
誰かがサンプルコードで答えを編集できますか?
ルーカスポッタースキー

6

セキュリティについて心配する必要がなく、OPなどの別のDockerコンテナー内からホスト上でDockerコンテナーを起動することを検討している場合は、ホストで実行されているDockerサーバーをそのリスンソケットを共有することでDockerコンテナーと共有できます。

https://docs.docker.com/engine/security/security/#docker-daemon-attack-surfaceを参照してくださいし、あなたの個人的なリスク許容度は、この特定のアプリケーションのためにこれを可能にするかどうかを確認します。

これを行うには、次のボリューム引数を開始コマンドに追加します

docker run -v /var/run/docker.sock:/var/run/docker.sock ...

または、次のようにdockercomposeファイル内で/var/run/docker.sockを共有します。

version: '3'

services:
   ci:
      command: ...
      image: ...
      volumes
         - /var/run/docker.sock:/var/run/docker.sock

Dockerコンテナ内でdockerstartコマンドを実行すると、ホストで実行されているDockerサーバーがリクエストを確認し、兄弟コンテナをプロビジョニングします。

クレジット:http//jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/


1
Dockerをコンテナーにインストールする必要があることを考慮してください。インストールしない場合は、Dockerバイナリ用のボリュームもマウントする必要があります(例/usr/bin/docker:/usr/bin/docker)。
ジェリー


@DatGuyKajありがとう、私はあなたのリソースによって概説された問題を反映するために私の答えを編集しました。
マットブッチ

これは、コンテナではなくホストでスクリプトを実行することに関する質問には答えません
Brandon

5

この回答は、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、「helloworld」と表示されます。

パート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

(あなたはそれをnohupすることができます)

これで、無制限の数のコマンドを次々に送信できます。最初のコマンドだけでなく、すべてのコマンドが実行されます。

パート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が、役に立ちます。

もう1つのオプションは、スクリプトを変更して、次のような出力をファイルに入れることです。

while true; do eval "$(cat /path/to/pipe/mypipe)" &> /somepath/output.txt; done

これで実行できls -l、出力(&>bashで使用するstdoutとstderrの両方)がoutput.txtにあるはずです。

パート5-dockerで動作させる

私のようにdockercomposeとdockerfileの両方を使用している場合は、次のようにします。

/hostpipeコンテナのようにmypipeの親フォルダをマウントするとします。

これを追加:

VOLUME /hostpipe

マウントポイントを作成するためにdockerfileで

次に、これを追加します。

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

/ path / to / pipeを/ hostpipeとしてマウントするために、dockercomposeファイルで

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でのみ読み取ることができ、MacOSからパイプに書き込んだものは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);

これはうまく機能します。セキュリティはどうですか?これを使用して、実行中のコンテナー内からDockerコンテナーを開始/停止しますか?dockerコマンドの実行以外の権限なしでdockeruserを作成するだけですか?
Kristof van Woensel

4

ポート(たとえば8080)をリッスンする単純なサーバーpythonサーバーを作成し、ポート-p 8080:8080をコンテナーにバインドし、localhost:8080にHTTPリクエストを送信して、popenでシェルスクリプトを実行している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()

IMOこれが最良の答えです。ホストマシンで任意のコマンドを実行するには、何らかのAPI(RESTなど)を使用する必要があります。これは、セキュリティを適用し、実行中のプロセスを適切に制御できる唯一の方法です(たとえば、強制終了、stdin、stdout、終了コードの処理など)。もちろん、このAPIをDocker内で実行できれば問題ありませんが、個人的には、ホスト上で直接実行してもかまいません。
barney765

2

私の怠惰は、ここで答えとして公開されていない最も簡単な解決策を見つけることにつながりました。

lucjuggeryによる素晴らしい記事に基づいています。

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シェルが提供されます。

この設定はセキュリティに大きな影響を与えるため、注意して使用する必要があります(ある場合)。


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

3
この回答はOPの質問を解決する可能性がありますが、OPがどのように機能し、なぜ問題が解決するのかを説明することをお勧めします。これは、新しい開発者が何が起こっているのか、そしてこれや同様の問題を自分で修正する方法を理解するのに役立ちます。貢献してくれてありがとう!
CalebKleveter18年

1

私には簡単なアプローチがあります。

ステップ1:/var/run/docker.sock:/var/run/docker.sockをマウントします(これにより、コンテナー内でdockerコマンドを実行できるようになります)

ステップ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
ピーターMutisya

0

マーカスが思い出させるように、dockerは基本的にプロセスの分離です。docker 1.8以降、ホストとコンテナ間でファイルを双方向にコピーできます。のドキュメントを参照してください。docker cp

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

ファイルがコピーされると、ローカルで実行できます


1
私はそれを知っている。つまり、Dockerコンテナ内からこのスクリプトを実行するにはどうすればよいですか?
Alex Ushakov 2015


2
@AlexUshakov:まさか。これを行うと、dockerの多くの利点が損なわれます。それをしないでください。試さないでください。あなたがする必要があることを再考してください。
マーカスミュラー

Vladのトリックforums.docker.com/t/…
user2915097

1
ホストでは、いつでもコンテナ内の変数の値を取得myvalue=$(docker run -it ubuntu echo $PATH)して、スクリプトシェルで定期的にテストできます(もちろん、$ PATH以外のものを使用しますが、これは単なる例です)。は特定の値であり、スクリプトを起動します
user2915097 2015

0

パイプの概念を使用できますが、ホスト上のファイルとfswatchを使用して、Dockerコンテナーからホストマシン上でスクリプトを実行するという目標を達成します。そのように(あなた自身の責任で使用してください):

#! /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コンテナーの適切なユースケースは考えられません。


ユースケースがあります:ドッキングされたサービスA(githubのsrc)があります。ではAレポ私は「gitのプル」コマンドの後に新しいドッキングウィンドウのイメージを作成し、それらを実行します(もちろん、古いコンテナを除く)適切なフックを作成します。次へ:githubには、マスターをプッシュした後に任意のエンドポイントリンクへのPOSTリクエストを作成できるWebフックがあります。そのため、そのエンドポイントになり、HOSTマシンのリポジトリAでのみ「gitpull」を実行するドッキングされたサービスBを作成しません(重要:コマンド「gitpull」はHOST環境で実行する必要があります-B環境ではなくB B内で新しいコンテナAを実行できません...)
KamilKiełczewski2017年

1
問題:Linux、git、docker以外はHOSTに何も入れたくない。そして、私はdockerizetサービスAとサービスB(実際には誰かがマスターでgit pushを行った後にリポジトリAでgitpullを実行するgit-pushハンドラーです)が欲しいです。だから、gitの自動デプロイ問題のユースケースである
カミルKiełczewski

@KamilKiełczewski私はまったく同じことをしようとしています、あなたは解決策を見つけましたか?
user871784 2017年

1
「いいえ、そうではありません」と言うのは気が狭く、世界中のすべてのユースケースを知っていることを前提としています。私たちのユースケースはテストの実行です。環境を正しくテストするには、コンテナーで実行する必要がありますが、テストの性質上、ホスト上でスクリプトを実行する必要もあります。
セニカゴンザレス2018

1
なぜ私が-7の答えを残すのか疑問に思っている人のために: a)間違いを犯しても大丈夫です。私は間違っていた。これがここに文書化されていても問題ありません。b)コメントは実際に価値をもたらします。答えを削除すると、それらも削除されます。c)それでも、検討するのが賢明かもしれない視点に貢献します(必要がない場合は、隔離を解除しないでください。ただし、必要な場合もあります)。
マーカスミュラー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.