如何用Docker成為更高效的數據科學家?
使用 Docker 容器來開發機器學習模型的好處有很多。近日,GitHub 的資深機器學習科學家 Hamel Husain 在 Towards Data Science 上發表了一篇入門級的 Docker 容器教程,文章從基本的概念談起,清楚明白地介紹了 Docker 容器的一些基本的操作方式和注意事項。機器之心對本文進行了編譯介紹。本文所涉及的所有相關代碼請訪問:https://github.com/hamelsmu/Docker_Tutorial
過去五年來,Docker 容器已然成了一個熱門詞匯,似乎我的所有軟件工程師朋友都在使用它們來開發應用。我想搞清楚這種技術可以如何讓我更有效率,但我發現我在網上找到的教程要么過于注重細節(解釋了一些我作為數據科學家絕不會使用的功能),要么就過于淺顯(沒有足夠的信息幫助我理解如何快速有效地使用 Docker)。
所以我寫了這篇快速入門,這樣你不必自己去網上篩選信息就能學習到快速上手 Docker 所需要的一切。
Docker 是什么?你可以把 Docker 看作是輕量級的虛擬機——包含你運行應用所需要的一切。Docker 容器可以獲取你的系統的狀態的快照,這樣其他人就可以使用這個快照快速重建你的計算環境。對于本教程而言,這就是你需要了解的一切。更多詳細介紹可參閱:https://goo.gl/YzUwbc
為什么要使用 Docker?1.重現性:作為專業的數據科學家,讓你的結果能夠重現是非常重要的。重現性不僅有助于同行評議,而且可以確保你創建的模型、應用或分析可以無障礙地運行,這能讓你交付的成果更穩健,更能經受時間的考驗。舉個例子,假如你用 Python 創建了一個模型,只是運行 pip freeze 并將結果得到的 requirements.txt 文件發送給你的同事是不夠的,因為其中只包含特定于 Python 的依賴條件——而實際上的依賴條件不只有 Python,還有操作系統、編譯器、驅動程序、配置文件以及你的代碼成功運行所需的其它數據。就算你只分享 Python 依賴條件也能成功,將所有東西都封裝到一個 Docker 容器中還是能減輕其他人重建你的環境的負擔,并讓他們能更輕松地訪問你的成果。
2.計算環境的可移植性:作為一位數據科學家,尤其是機器學習領域內的數據科學家,快速改變你的計算環境的能力能夠極大地影響你的生產力。數據科學的開始工作常常是原型設計、探索和研究——這些工作并不一定立即就需要特定的計算資源。這個工作往往是在筆記本電腦或個人計算機上完成的。但是在后面某個時候,你往往會需要不同的計算資源來顯著加速你的工作流程——比如使用更多 CPU 或強大的 GPU 來執行深度學習等任務。我看到很多數據科學家由于感受到了在遠程機器上重建他們的本地環境的困難,就將自己局限在了本地計算環境內。而 Docker 能讓你的環境(你的所有庫和文件等等)的移植非常簡單。在 Kaggle 競賽中,快速移植計算環境也是一個巨大的競爭優勢,因為你可以成本高效地利用 AWS 的寶貴計算資源。最后,創建 Docker 文件讓你能移植很多你喜歡的本地環境配置——比如 bash 別名或 vim 插件。
3.強化你的工程能力:熟練使用 Docker 讓你能將模型或分析部署成應用(比如用作提供預測的 REST API),從而讓其他人也能使用你的成果。此外,你在數據科學工作流程中可能需要與存在于 Docker 容器中的其它應用進行交互,比如數據庫。
Docker 術語在我們繼續深入之前,熟悉一下 Docker 的術語會很有幫助:
·鏡像(image):是你想要創建的東西的藍圖。比如:Ubuntu+TensorFlow,帶有英偉達驅動程序和一個運行的 Jupyter 服務器。
·容器(container):是你實現的運行的鏡像的實例化。你可以運行同一個鏡像的多個副本。分清鏡像和容器之間的差異非常重要,因為這是新入門者常?;煜膬蓚€概念。如果你不清楚鏡像和容器的差別,停下來再讀一次。
·Dockerfile:用于創建鏡像的配方。Dockerfile 包含特殊的 Docker 語法。官方文檔說:Dockerfile 是一個文本文檔,其中包含了用戶可以在命令行調用的用來組裝成鏡像的所有命令。
·commit:和 git 類似,Docker 容器提供了版本控制。通過 commit 發生的改變,你在任何時間都可以將你的 Docker 容器的狀態保存為一個新鏡像。
·DockerHub/Image Registry:人們可以發布公開(或私人)Docker 鏡像的地方,用于促進合作與共享。
·層(layer):對已有鏡像的修改,由 Dockerfile 中的一個指令表示。層按次序應用到基礎鏡像上,以創建出最終的鏡像。
本文將使用這些術語,如果你在閱讀時忘記了,一定要回來查看!這些術語很容易混淆,尤其是在鏡像和容器之間——所以你在閱讀時要保持警惕!
安裝 Docker你可以免費下載安裝 Docker 社區版(Docker Community Edition),地址:https://www.docker.com/community-edition
創建你的第一個 Docker 鏡像在創建 Docker 容器之前,創建一個將用于定義鏡像的 Dockerfile 會很有用。我們先慢慢解讀一下下面的 Dockerfile。你也可以在與本教程關聯的 GitHub 庫中找到這個文件:https://goo.gl/iE4Bdr
# reference: https://hub.docker.com/_/ubuntu/
FROM ubuntu:16.04
# Adds metadata to the image as a key value pair example LABEL version="1.0"
LABEL maintainer="Hamel Husain <www.github.com/hamelsmu>"
##Set environment variables
ENV.UTF-8 LC_ALL=C.UTF-8
RUN apt-get update --fix-missing && apt-get install -y wget bzip2 ca-certificates \
build-essential \
byobu \
curl \
git-core \
htop \
pkg-config \
python3-dev \
python-pip \
python-setuptools \
python-virtualenv \
unzip \
&& \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
RUN echo 'export PATH=/opt/conda/bin:$PATH' > /etc/profile.d/conda.sh && \
wget --quiet https://repo.continuum.io/archive/Anaconda3-5.0.0.1-Linux-x86_64.sh -O ~/anaconda.sh && \
/bin/bash ~/anaconda.sh -b -p /opt/conda && \
rm ~/anaconda.sh
ENV PATH /opt/conda/bin:$PATH
RUN pip --no-cache-dir install --upgrade \
multiprocessing \
sklearn-pandas
# Open Ports for Jupyter
EXPOSE 7745
#Setup File System
RUN mkdir ds
ENV HOME=/ds
ENV SHELL=/bin/bash
VOLUME /ds
WORKDIR /ds
ADD run_jupyter.sh /ds/run_jupyter.sh
RUN chmod +x /ds/run_jupyter.sh
# Run the shell
CMD ["./run_jupyter.sh"]
FROM 語句
FROM ubuntu:16.04
FROM 語句包含了 Docker 最神奇的部分。這個語句指定了你想在上面進行創建的基礎鏡像。通過使用 FROM 指定一個基礎鏡像,Docker 將會在你的本地環境中尋找名為 ubuntu:16.04 的鏡像——如果它沒有找到,它就會搜索你指定的 Docker Registry,默認是 DockerHub:https://hub.docker.com/explore/。如果你需要經常在你的 Ubuntu 等操作系統上安裝程序,那么這種分層機制就非常方便。你不必費心從頭開始安裝 Ubuntu,而是可以直接在官方的 Ubuntu 鏡像上開發!DockerHub 上托管著種類繁多的鏡像,包括那些不只是提供了一個操作系統的鏡像,比如如果你想要一個已經安裝了 Anaconda 的容器,你可以選擇在官方的 Anaconda Docker 鏡像上開發,地址:https://hub.docker.com/r/continuumio/anaconda3/。最重要的是,你也可以隨時發布你構建的鏡像,即使該鏡像是通過在其它鏡像上加層得到的!這有無盡的可能性。
在這個案例中,我們指定基礎鏡像為 ubuntu:16.04,它會搜索名叫 ubuntu 的 DockerHub 庫(https://hub.docker.com/_/ubuntu/)。鏡像名之后的部分 16.04 是指定了你想要安裝的基礎鏡像的版本的標簽(tag)。如果你檢索一下 Ubuntu DockerHub 庫,你會注意到不同版本的 Ubuntu 對應于不同的 tag:
2017 年 12 月的官方 Ubuntu DockerHub 庫截屏
比如,ubuntu:16.04、ubuntu:xenial-20171201、ubuntu:xenial 和 ubuntu:latest 全都是指 16.04 版的 Ubuntu,它們全都是同一個鏡像的別名。此外,這里提供的鏈接指向了對應的 Dockerfile,可用于構建每個版本的鏡像。有時候你無法在 DockerHub 中找到 Dockerfile,因為維護者可以自己選擇是否將關于這些鏡像的創建方式的 Dockerfile 包含進來。我個人覺得閱讀一些 Dockerfile 有助于更好地理解 Dockerfile。(但不要急,讀完這篇教程再說!)
你需要特別注意一個標簽,即 :latest 標簽。這也是你在不為 FROM 語句指定標簽時默認 pull 的鏡像。比如說如果你的 FROM 語句是這樣:
FROM ubuntu
然后你就將 pull ubuntu:16.04 鏡像。為什么?——仔細看上面,你可以看到 :latest 關聯的是 16.04.
關于 Docker 鏡像最后需要注意的一點:在從 DockerHub pull 隨機的 Docker 鏡像時要做出明智的判斷。有惡意的人創建的鏡像有可能會包含惡意軟件。
LABEL 語句
這個語句會為你的鏡像添加元數據,而且是完全可選的。我增加這個語句的目的是為了讓別人知道可以聯系誰,同時也方便我搜索我的 Docker 容器,尤其是在一個服務器上同時運行著很多容器時。
LABEL maintainer="Hamel Husain <youremail>"
ENV 語句
ENV.UTF-8 LC_ALL=C.UTF-8
這讓你可以修改環境變量,而且相當直接,相關情況請參閱:https://docs.docker.com/engine/reference/builder/
RUN 語句
這通常是最需要花功夫的地方,給出了你構建該 Docker 鏡像所想要完成的任務。你可以運行 apt-get 和 pip install 等任意的 shell 命令來安裝你需要的軟件包和依賴包。
RUN apt-get update --fix-missing && apt-get install -y wget bzip2
build-essential \
ca-certificates \
git-core \
...
在這里我安裝了一些我喜歡的實用工具,比如 curl、htop、byobu,然后安裝了 Anaconda,之后還安裝了一些基礎 Anaconda 中沒有的其它庫(你可以在完整的 Dockerfile 中查看其它 RUN 語句)。
RUN 語句后的命令與 Docker 沒什么關系,只是一些你在安裝這些軟件包時需要運行的正常 Linux 命令,所以就算你不熟悉這些軟件包或 Linux 命令也不要擔心。另外,再給一個建議:當我最早開始學習 Docker 時,我查看了 GitHub 或 DockerHub 上的其它 Dockerfile,然后將我需要的部分復制粘貼到了我的 Dockerfile。
你可能注意到了 RUN 語句的格式。每個庫或軟件包都整齊地進行了縮進,而且為了可讀性還按字母進行了排序。這是 Dockerfile 的普遍慣例,所以我建議你也這樣做以便合作。
EXPOSE 語句
如果你想公開一個端口,這個語句會很有用——比如,如果你從該容器或某個網絡服務內實施一個 Jupyter Notebook。Docker 的文檔相當好地解釋了 EXPOSE 語句:
EXPOSE 指令實際上并沒有發布該端口。它的功能是作為創建該鏡像的人和運行該容器的人之間的一類文檔,內容是關于打算發布的端口。要實際發布該端口,就要在運行該容器時在 docker run 上使用 -p 標志并且映射一個或多個端口,或者也可以使用 -P 標志發布所有端口并將它們映射到高階端口。
VOLUME 語句
VOLUME /ds
這個語句讓你可以在 Docker 容器和主機計算機之間共享數據。VOLUME 語句讓你可以安裝外部安裝的卷。主機目錄只有在容器運行時才聲明(因為你可能在不同的計算機上運行該容器),而不會在定義鏡像時聲明*。目前你只指定了 Docker 容器內你想與主機容器共享的文件夾的名稱。
Docker 用戶指南解釋說:
主機目錄是在容器運行時聲明的:主機目錄(掛載點)本質上取決于主機。這是為了保證鏡像的可移植性,因為一個給定的主機目錄無法保證在所有主機上都可用。由于這個原因,你不能在 Dockerfile 中掛載主機目錄。VOLUME 指令不支持指定 host-dir 參數。你必須在創建或運行容器時指定掛載點。
此外,這些卷的目的是將數據保存到容器的文件系統之外,當你要操作大量數據而且不希望你的鏡像膨脹得很大時,這會很有用。當你保存一個 Docker 鏡像時,在這個 VOLUME 目錄中的任何數據都不會被保存為該鏡像的一部分,但是在這個容器目錄之外的數據會被保存。
WORKDIR 語句
WORKDIR /ds
這個語句設置了工作目錄,以便你在另一條命令中可以無需使用絕對路徑就能索引特定的文件。例如這個 Dockerfile 中的最后一條語句是:
CMD [“./run_jupyter.sh”]
該語句就默認假設工作目錄是 /ds
ADD 語句
ADD run_jupyter.sh /ds/run_jupyter.sh
這條命令讓你可以在 Docker 容器運行時將文件從主機計算機復制到該 Docker 容器。我使用這個命令來執行 bash 腳本以及將 .bachrc 文件等有用東西導入到容器中。
注意這里的主機容器的路徑并沒有完全指定,因為其主機路徑是你在該容器運行時指定的背景路徑(context directory)的相對路徑(后面會討論)。
在我運行這個容器時,run_jupyter.sh 正好在背景路徑的根目錄內,所以在該源文件之前沒有路徑。
用戶指南中介紹說:
ADD <src>... <dest>
ADD 指令從 <src> 復制新文件、目錄或遠程文件 URL 并將它們添加到路徑 <dest> 的鏡像的文件系統中。
CMD 語句
Docker 容器的設計思想是這些容器是短暫的,能保證運行完你想運行的應用就行了。但在數據科學方面,我們往往希望保持這些容器一直運行,即使它們之中并沒有主動地運行著什么。很多人都通過運行 bash shell 來實現這一點(除非你終止它,否則它就不會停止)。
CMD [“./run_jupyter.sh”]
在上面的命令中,我運行了一個實例化一個 Jupyter Notebook 服務器的 shell 腳本。但是,如果你沒有什么要運行的特定應用而只是想保持你的容器運行(而不退出),你可以直接運行 bash shell,只不過使用以下命令:
CMD ["/bin/bash"]
這種方法是有效的,因為除非你退出,否則 bash shell 就不會終止;因此該容器會一直保持運行。
用戶指南中介紹說:
在一個 Dockerfile 中只能有一個 CMD 指令。如果你列出了不止一個 CMD,那么只有最后一個才有效。
CMD 的主要目的是為正在執行的容器提供默認配置。這些默認配置可能包含一個可執行文件,或者也可以省略可執行文件,在這種情況下你還必須指定一個 ENTRYPOINT 指令。
創建你的 Docker 鏡像Dockerfile 中的信息可真夠多的。不要擔心,后面的內容就相對很簡單了。現在我們已經在 Dockerfile 中創建了我們的配方,是時候創造鏡像了。你可以通過以下命令完成:
GitHub 上也有:https://github.com/hamelsmu/Docker_Tutorial/blob/master/basic_tutorial/build_image.sh
這會創建一個 Docker 鏡像(而不是容器;如果你不記得這兩者之間的差異,請查閱文章前面的術語介紹),你可以在后面運行這個鏡像。
從你的 Docker 鏡像創建和運行容器現在你已經準備好讓這一切工作起來了!我們可以通過執行以下命令來調出環境:
同樣 GitHub 也有:https://github.com/hamelsmu/Docker_Tutorial/blob/master/basic_tutorial/run_container.sh
運行完這個命令之后,你的容器就運行起來了!Jupyter 服務器也運行起來了,因為在該 Dockerfile 最后有這個命令:
CMD [“./run_jupyter.sh”]
現在你應該可以通過其使用的端口訪問你的 Jupyter Notebook 了——在這個案例中可通過 http://localhost:7745/ 訪問,密碼是 tutorial。如果你是通過遠程的方式運行這個 Docker 容器,你還必須設置本地端口轉發,這樣你才能通過你的瀏覽器訪問你的 Jupyter 服務器。端口轉發介紹:https://help.ubuntu.com/community/SSH/OpenSSH/PortForwarding
與你的容器交互一旦容器設置完成并運行起來,下面這些命令就有用了:
·為容器附加一個新的終端會話。如果你需要安裝一些新軟件或使用 shell,這會很有用。
·將你的容器的狀態保存為新鏡像。即使你一開始就在 Dockerfile 中配置了你想安裝的所有庫,隨著時間的推移,你也可能還是需要對容器的狀態進行很大的調整——通過交互來增加更多庫和軟件包。將你的容器的狀態保存為鏡像是很有用的,你后面可以將其分享出去或在上面加層。你可以使用 docker commit CLI 命令將容器狀態保存為新鏡像:
docker commit <container_name> new_image_name:tag_name(可選的)
比如說,如果我想將名為 container1 的容器的狀態保存為名為 hamelsmu/tutorial:v2 的鏡像,我可以直接運行這個命令:
docker commit container_1 hamelsmu/tutorial:v2
你可能會疑惑鏡像名之前的 hamelsmu/ 是什么——這只是為了讓之后將該容器推送到 DockerHub 的工作更輕松,因為 hamelsmu 是我的 DockerHub 用戶名(后面會再談這個問題)。如果你的工作要使用 Docker,那么你的公司很可能有一個內部私有的 Docker 庫,你也可以將你的 Docker 推送到那里。
·列出運行中的容器。當我忘記現在正在運行的容器的名稱時,我就常常使用這個命令:
docker ps -a -f status=running
如果你在使用該命令時沒有加上 status=running,那么你就會看到你系統上的所有容器的列表(即使已經不再運行的容器也在)。這對查找舊容器而言很有用。
·列出你在本地保存的所有鏡像。
docker images
·將你的鏡像推送到 DockerHub(或其它地方)。如果你想與其他人分享你的工作或將鏡像保存到云上,這個命令就會很有用。注意你在做這件事時可不要分享任何私人信息(DockerHub 上也有私有庫)。
首先創建一個 DockerHub 庫并給你的庫起一個適當的名稱,參考這里:https://docs.docker.com/docker-hub/repos/。然后要運行 docker login 命令來連接到你在 DockerHub 或其它注冊位置的賬戶。比如,要推送一個鏡像到這個容器(https://hub.docker.com/r/hamelsmu/tutorial/),我首先必須將我的本地鏡像命令為 hamelsmu/tutorial(我可以選擇任意標簽名)。比如說,這個 CLI 命令就為:
docker push hamelsmu/tutorial:v2
將之前提到的 Docker 鏡像推送到這個庫,其標簽為 v2,參考:https://hub.docker.com/r/hamelsmu/tutorial/tags/。需要指出:如果你公開了你的鏡像,那么其他人就可以直接在你的鏡像上加層,就像本教程中我們在 ubuntu 鏡像上加層一樣。對于想要重現或延展你的研究的其他人來說,這非常有用。
你已經掌握了現在你知道如何操作 Docker 了,你可以執行以下任務:
·與同事和朋友共享可重現的研究。
·通過將你的代碼暫時遷移到所需的更大的計算環境中,無中斷地贏得 Kaggle 競賽。
·在你的筆記本電腦上的 Docker 容器內進行本地的原型開發,然后毫不費力地將同樣的計算過程無縫遷移到服務器上,同時還能保留你喜歡的本地環境配置(你的別名、vim 插件、bash 腳本、自定義提示等)。
·使用 Nvidia-Docker 在 GPU 計算機上快速實例化運行 TensorFlow、PyTorch 或其它深度學習庫所需的所有依賴包。(如果你從頭開始做,這個過程將非常艱辛。)參閱后面的彩蛋。
·將你的模型作為應用發布,比如用作從 Docker 容器提供預測的 REST API。當你的應用 Docker 化了以后,就可以按照需要輕松地隨意復制。
進階閱讀到這里我們也只學到了 Docker 的一點皮毛,前面還有很多東西值得掌握。我很關注 Docker 領域,我認為數據科學家會常常遇到它,希望這篇文章能讓你有足夠的信心開始使用它。下面這些資源曾在我的 Docker 之旅中為我提供過幫助:
·有用的 Docker 命令:https://zaiste.net/posts/removing_docker_containers/
·更有用的 Docker 命令:https://www.digitalocean.com/community/tutorials/how-to-remove-docker-images-containers-and-volumes
·Dockerfile 參考:https://docs.docker.com/engine/reference/builder/
·如何創建和推送到 DockerHub 上的庫:https://docs.docker.com/docker-hub/repos/
彩蛋:Nvidia-Docker我學習 Docker 最早的原因是要在單個 GPU 上做深度學習模型的原型開發,然后在我需要更多計算資源時再遷移到 AWS 上。我當時也在學習 Jeremy Howard 的出色的 Fast.AI 課程(http://www.fast.ai/),并且希望與其他人分享我的原型設計。
但是,要將英偉達 GPU 的驅動程序等所有依賴包都包含以來,你不能使用 Docker,而是要用 Nvidia-Docker(https://github.com/NVIDIA/nvidia-docker)。這比使用 vanilla Docker 要多花一些功夫,但只要你理解了 Docker,做起來就很簡單。
我將我的 Nvidia-Docker 設置放在這里:https://github.com/hamelsmu/Docker_Tutorial/tree/master/gpu_tutorial,你可以用這個來進行練習。