444 Stimmen

Die Verwendung der RUN-Anweisung in einem Dockerfile mit 'source' funktioniert nicht.

Ich habe ein Dockerfile, das ich zusammenstelle, um eine einfache Python-Umgebung zu installieren (in die ich später eine App installieren werde).

FROM ubuntu:12.04

# erforderlich zum Bauen bestimmter Python-Bibliotheken
RUN apt-get install python-dev -y

# installiere pip - kanonische Installationsanweisungen von pip-installer.org
# http://www.pip-installer.org/en/latest/installing.html
ADD https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py /tmp/ez_setup.py
ADD https://raw.github.com/pypa/pip/master/contrib/get-pip.py /tmp/get-pip.py
RUN python /tmp/ez_setup.py
RUN python /tmp/get-pip.py
RUN pip install --upgrade pip 

# installiere und konfiguriere virtualenv
RUN pip install virtualenv 
RUN pip install virtualenvwrapper
ENV WORKON_HOME ~/.virtualenvs
RUN mkdir -p $WORKON_HOME
RUN source /usr/local/bin/virtualenvwrapper.sh

Der Build läuft OK, bis zur letzten Zeile, wo ich die folgende Ausnahme erhalte:

[vorherige Schritte 1-9 für Klarheit entfernt]
...
virtualenvwrapper virtualenv-clone stevedore erfolgreich installiert
Aufräumen...
 ---> 1fc253a8f860
Schritt 10 : ENV WORKON_HOME ~/.virtualenvs
 ---> In 8b0145d2c80d ausgeführt
 ---> 0f91a5d96013
Schritt 11 : RUN mkdir -p $WORKON_HOME
 ---> In 9d2552712ddf ausgeführt
 ---> 3a87364c7b45
Schritt 12 : RUN source /usr/local/bin/virtualenvwrapper.sh
 ---> In c13a187261ec ausgeführt
/bin/sh: 1: source: nicht gefunden

Wenn ich in dieses Verzeichnis ls mache (nur um zu testen, ob die vorherigen Schritte ausgeführt wurden), sehe ich, dass die Dateien wie erwartet vorhanden sind:

$ docker run 3a87 ls /usr/local/bin
easy_install
easy_install-2.7
pip
pip-2.7
virtualenv
virtualenv-2.7
virtualenv-clone
virtualenvwrapper.sh
virtualenvwrapper_lazy.sh

Wenn ich versuche, nur den source Befehl auszuführen, erhalte ich den gleichen 'nicht gefunden' Fehler wie oben. Wenn ich jedoch eine interaktive Shell-Sitzung mit RUN starte, funktioniert source:

$ docker run 3a87 bash
source
bash: Zeile 1: source: Dateiname Argument erforderlich
source: Verwendung: source Dateiname [Argumente]

Ich kann das Skript von hier aus ausführen und danach problemlos auf workon, mkvirtualenv usw. zugreifen.

Ich habe ein wenig recherchiert, und anfangs schien das Problem möglicherweise in der Unterscheidung zwischen bash als Ubuntu login shell und dash als Ubuntu system shell zu liegen, wobei dash den source Befehl nicht unterstützt.

Die Antwort darauf scheint jedoch zu sein, stattdessen '.' anstelle von source zu verwenden, doch das führt nur dazu, dass die Docker-Runtime mit einer go panic Ausnahme ausfällt.

Wie kann ein Shell-Skript aus einer Dockerfile RUN-Anweisung heraus ausgeführt werden, um dieses Problem zu umgehen (läuft auf dem Standard-Basisimage für Ubuntu 12.04 LTS)?

242voto

Anubhav Sinha Punkte 2549

Ursprüngliche Antwort

FROM ubuntu:14.04
RUN rm /bin/sh && ln -s /bin/bash /bin/sh

Dies sollte für jedes Ubuntu-Docker-Basisimage funktionieren. Im Allgemeinen füge ich diese Zeile für jedes Dockerfile hinzu, das ich schreibe.

Bearbeitung durch einen besorgten Zuschauer

Wenn Sie den Effekt von "verwenden Sie bash anstelle von sh in diesem gesamten Dockerfile" erzielen möchten, ohne das Betriebssystem im Container zu verändern und möglicherweise zu beschädigen*, können Sie Docker einfach mitteilen, was Sie beabsichtigen. Das wird wie folgt gemacht:

SHELL ["/bin/bash", "-c"]

* Der mögliche Schaden besteht darin, dass viele Skripte in Linux (bei einer frischen Ubuntu-Installation gibt grep -rHInE '/bin/sh' / über 2700 Ergebnisse zurück) einen vollständigen POSIX-Shell bei /bin/sh erwarten. Die Bash-Shell ist nicht nur POSIX plus zusätzliche eingebaute Befehle. Es gibt eingebaute Befehle (und mehr), die sich völlig anders verhalten als die in POSIX. Ich unterstütze voll und ganz die Vermeidung von POSIX (und die veraltete Vorstellung, dass jedes Skript, das Sie nicht auf einer anderen Shell getestet haben, funktioniert, weil Sie denken, dass Sie Bashismen vermieden haben) und stattdessen nur Bashismen zu verwenden. Sie tun dies jedoch mit einem ordnungsgemäßen Shebang in Ihrem Skript. Nicht indem Sie die POSIX-Shell unter dem gesamten Betriebssystem herausziehen. (Es sei denn, Sie haben Zeit, alle über 2700 Skripte zu überprüfen, die mit Linux geliefert werden, sowie alle in den von Ihnen installierten Paketen enthaltenen Skripte.)

Weitere Details in dieser Antwort unten. https://stackoverflow.com/a/45087082/117471

183voto

Ahmad Abdelghany Punkte 9491

Die Standard-Shell für die RUN Anweisung ist ["/bin/sh", "-c"].

RUN "source file"      # übersetzt zu: RUN /bin/sh -c "source file"

Mit der SHELL Anweisung kannst du die Standard-Shell für nachfolgende RUN Anweisungen im Dockerfile ändern:

SHELL ["/bin/bash", "-c"] 

Jetzt wurde die Standard-Shell geändert und du musst sie nicht mehr explizit in jeder RUN Anweisung definieren

RUN "source file"    # übersetzt zu: RUN /bin/bash -c "source file"

Zusätzliche Anmerkung: Du könntest auch die Option --login hinzufügen, die eine Anmeldungsshell starten würde. Das bedeutet, dass zum Beispiel die Datei ~/.bashrc gelesen wird und du sie nicht mehr explizit vor deinem Befehl sourcen musst

80voto

mixja Punkte 6309

Der einfachste Weg ist die Verwendung des Punktoperators anstelle von source, der das sh-Äquivalent des bash source-Befehls ist:

Statt:

RUN source /usr/local/bin/virtualenvwrapper.sh

Verwende:

RUN . /usr/local/bin/virtualenvwrapper.sh

59voto

Mithril Punkte 11476

Wenn Sie Docker 1.12 oder neuer verwenden, verwenden Sie einfach SHELL!

Kurze Antwort:

Allgemein:

SHELL ["/bin/bash", "-c"] 

für Python virtualenv:

SHELL ["/bin/bash", "-c", "source /usr/local/bin/virtualenvwrapper.sh"]

Lange Antwort:

von https://docs.docker.com/engine/reference/builder/#shell

SHELL ["executable", "parameters"]

Die SHELL-Anweisung ermöglicht es, die Standardshell, die für die Shell-Form von Befehlen verwendet wird, zu überschreiben. Die Standardshell auf Linux ist ["bin/sh", "-c"], und auf Windows ist ["cmd", "/S", "/C"]. Die SHELL-Anweisung muss im JSON-Format in einer Docker-Datei geschrieben werden.

Die SHELL-Anweisung ist besonders nützlich unter Windows, wo zwei häufig verwendete und recht unterschiedliche native Shells existieren: cmd und powershell sowie alternative verfügbare Shells wie sh.

Die SHELL-Anweisung kann mehrmals erscheinen. Jede SHELL-Anweisung überschreibt alle vorherigen SHELL-Anweisungen und betrifft alle nachfolgenden Anweisungen. Zum Beispiel:

FROM microsoft/windowsservercore

# Ausgeführt als cmd /S /C echo default
RUN echo default

# Ausgeführt als cmd /S /C powershell -command Write-Host default
RUN powershell -command Write-Host default

# Ausgeführt als powershell -command Write-Host hello
SHELL ["powershell", "-command"]
RUN Write-Host hello

# Ausgeführt als cmd /S /C echo hello
SHELL ["cmd", "/S"", "/C"]
RUN echo hello

Die folgenden Anweisungen können von der SHELL-Anweisung betroffen sein, wenn ihre Shell-Form in einer Docker-Datei verwendet wird: RUN, CMD und ENTRYPOINT.

Das folgende Beispiel ist ein häufiges Muster unter Windows, das durch Verwendung der SHELL-Anweisung optimiert werden kann:

...
RUN powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"
...

Der von Docker aufgerufene Befehl wird sein:

cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"

Dies ist aus zwei Gründen ineffizient. Erstens wird ein unnötiger cmd.exe-Befehlsprozessor (auch als Shell bezeichnet) aufgerufen. Zweitens erfordert jede RUN-Anweisung in der Shell-Form ein zusätzliches powershell -command, das dem Befehl vorangestellt ist.

Um dies effizienter zu gestalten, können eines von zwei Mechanismen verwendet werden. Einer besteht darin, die JSON-Form des RUN-Befehls zu verwenden, wie zum Beispiel:

...
RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 \"c:\\foo.txt\""]
...

Obwohl die JSON-Form eindeutig ist und cmd.exe nicht verwendet, erfordert sie mehr Verbose durch doppeltes Anführungszeichen und Escapen. Der alternative Mechanismus ist die Verwendung der SHELL-Anweisung und der Shell-Form, was eine natürlichere Syntax für Windows-Benutzer ermöglicht, insbesondere in Kombination mit der Escape-Parser-Anweisung:

# escape=`

FROM microsoft/nanoserver
SHELL ["powershell","-command"]
RUN New-Item -ItemType Directory C:\Example
ADD Execute-MyCmdlet.ps1 c:\example\
RUN c:\example\Execute-MyCmdlet -sample 'hello world'

Ergebnis:

PS E:\docker\build\shell> docker build -t shell .
Sending build context to Docker daemon 4.096 kB
Step 1/5 : FROM microsoft/nanoserver
 ---> 22738ff49c6d
Step 2/5 : SHELL powershell -command
 ---> Running in 6fcdb6855ae2
 ---> 6331462d4300
Removing intermediate container 6fcdb6855ae2
Step 3/5 : RUN New-Item -ItemType Directory C:\Example
 ---> Running in d0eef8386e97

    Directory: C:\

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       10/28/2016  11:26 AM                Example

 ---> 3f2fbf1395d9
Removing intermediate container d0eef8386e97
Step 4/5 : ADD Execute-MyCmdlet.ps1 c:\example\
 ---> a955b2621c31
Removing intermediate container b825593d39fc
Step 5/5 : RUN c:\example\Execute-MyCmdlet 'hello world'
 ---> Running in be6d8e63fe75
hello world
 ---> 8e559e9bf424
Removing intermediate container be6d8e63fe75
Successfully built 8e559e9bf424
PS E:\docker\build\shell>

Die SHELL-Anweisung kann auch verwendet werden, um die Arbeitsweise einer Shell zu ändern. Beispielsweise könnten unter Windows mit SHELL cmd /S /C /V:ON|OFF die Semantik der verzögerten Umgebungsvariablenexpansion geändert werden.

Die SHELL-Anweisung kann auch unter Linux verwendet werden, wenn eine alternative Shell wie zsh, csh, tcsh und andere erforderlich ist.

Das SHELL-Feature wurde in Docker 1.12 hinzugefügt.

54voto

Andrea Grandi Punkte 855

Ich hatte das gleiche Problem und um pip install innerhalb von virtualenv auszuführen, musste ich diesen Befehl verwenden:

RUN pip install virtualenv virtualenvwrapper
RUN mkdir -p /opt/virtualenvs
ENV WORKON_HOME /opt/virtualenvs
RUN /bin/bash -c "source /usr/local/bin/virtualenvwrapper.sh \
    && mkvirtualenv myapp \
    && workon myapp \
    && pip install -r /mycode/myapp/requirements.txt"

Ich hoffe, es hilft.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X