此前在公司實習時,平常的開發、工做按規定都必須使用Windows操做系統,可是項目實際的測試、上線環境都是基於Linux的,因此每次只能在本地編寫某一功能的代碼後經過「跳板機」將項目代碼傳送到服務器上進行測試。出現問題也只能在服務器上修改,運行經過後再將修改後的代碼傳送到本地而後繼續下一階段的工做。因爲我本身不太喜歡直接在服務器上使用vim編輯代碼,也受不了每次都要經過"跳板機"將代碼傳來傳去的繁複操做,本着生命在於折騰的真理,開啓了此次的折騰之旅。html
爲了既能夠愉快地在Windows下編碼、交流、玩耍,又能夠一鍵在Linux下快速部署、測試代碼,本文經過Docker for Windows在Windows系統中佈置與服務器上一致的Linux測試環境,不用再將咱們的代碼拷貝到遠程服務器上,只佔用較小的資源就能夠一鍵在本地以相同的運行環境測試咱們的代碼。python
下載地址:Download Docker Desktoplinux
安裝前提:64bit Windows 10 Pro and Microsoft Hyper-Vgit
開啓Hyper-V功能:github
安裝Docker;web
設置國內鏡像倉庫地址:docker
啓動Docker後右擊Docker的運行圖標選擇"setting"進入設置界面;shell
選擇設置面板左側的"Daemon"選項,在"Registry mirrors"中填入國內的鏡像倉庫地址(推薦使用阿里雲的,在我這邊測試的速度較快,可是可能須要註冊帳號);bootstrap
點擊"Apply";vim
設置共享目錄:
進入Docker的設置面板,選擇"Shared Drives"選項;
勾選想要共享的盤符,點擊"Apply",可能須要輸入Windows帳戶的密碼;
在Docker的安裝和設置過程當中,最可能遇到問題的地方多是「設置共享目錄」的時候,可能會出現勾選了盤符並點擊"Apply"後選項又被取消的問題。因爲掛載Windows的盤符失敗會在加入"-v"選項啓動Docker容器時會出現卡死的狀況。查看Docker的日誌文件(C:\ProgramData\DockerDesktop\service.txt)會發現以下錯誤:
[Error] Unable to mount C drive: C:\Program Files\Docker\Docker\Resources\bin\docker.exe: Error response from daemon: OCI runtime create failed: container_linux.go:344: starting container process caused "exec: \"/usr/bin/nsenter1\": stat /usr/bin/nsenter1: no such file or directory": unknown.
複製代碼
網絡上關於該問題的解決方案主要分爲如下幾種:
我嘗試了第一、3個方法,第1個方法對個人Docker不起做用,而後使用第3中方法從新安裝後就能夠了,比較奇怪。
首先須要設置Windows與docker的Linux測試環境的共享目錄爲:D:\pc_share\
,而該目錄下的目錄樹以下所示:
.
|-- apps # 放置咱們的項目
| |-- test_demo1 # 示例項目1
| | |-- app.log
| | `-- app.py
| `-- test_demo2 # 示例項目2
| |-- app.log
| `-- app.py
`-- etc # 放置咱們的配置文件
|-- DockerFile # 鏡像的配置文件,用於構建docker鏡像
|-- requirements.txt # 用於記錄、安裝python項目的依賴庫
`-- supervisor # 放置示例項目的運行配置文件
|-- test_demo1.conf # 記錄了示例項目1的啓動信息,經過軟件supervisor啓動、監控
`-- test_demo2.conf # 記錄了示例項目2的啓動信息,經過軟件supervisor啓動、監控
複製代碼
以上就是咱們此次實驗的全部文件的目錄樹示意圖,將相關項目都放在"D:\pc_share\apps"下,爲了方便演示,在這裏建立了兩個基於tornado的Web項目test_demo1
,test_demo1
,每一個項目中只包含了一個啓動文件app.py
:
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, from test_demo_1")
def make_app():
return tornado.web.Application([
(r"/", MainHandler), # 路由
])
if __name__ == "__main__":
app = make_app()
app.listen(8888) # 監聽8888端口,示例項目2的監聽改成另外一個端口,不然有一個將因爲端口占用沒法成功啓動
tornado.ioloop.IOLoop.current().start() # 開啓Web服務
複製代碼
如上所示是一段十分簡單的Web程序,該程序監聽'8888'端口,若是訪問"http:\\localhost:8888"就能夠獲得回覆:"Hello, from test_demo_1"。
由章節2.1可知,運行上述項目至少須要三個環境:操做系統
、python
、tornado框架
。爲了與項目上線的環境保持一致,這裏假設三個環境條件分別爲:Centos7
、python2.7
、tornado5.1.1
。因此咱們須要使用Dockerfile配置相應的運行環境,不熟悉Dockerfile的同窗能夠瀏覽下教程:Dockerfile教程。
爲了更方便地修改運行環境,Dockerfile主要分爲兩部分:配置下層運行環境
、配置上層運行環境
。
如上圖所示爲底層運行環境的配置過程示意圖,對應的Dockerfile的部分爲:
FROM centos:7 as centos_python2 # 基於鏡像:"centos:7"
MAINTAINER mileskong <xiangqian_kong@126.com> # 做者信息
# 環境變量,反斜槓表示換行
ENV PYPI_REPO=https://mirrors.aliyun.com/pypi/simple/ \
PYTHONIOENCODING=UTF-8 \
SHARE_PATH=/mnt/share # docker容器的掛載點,對應Windows中的"D:\pc_share\"路徑(啓動容器時能夠設置共享路徑與容器中的掛載點)
# 爲Centos7安裝必要的軟件,這裏只安裝了三個經常使用的軟件,能夠按需修改
RUN set -ex \ && yum -y install epel-release wget net-tools \ && yum clean all # 清空緩存,精簡鏡像
# 安裝pip軟件(這裏省略了python2.7的安裝,由於Centos7中自帶了python2.7.5,如需安裝其餘版本的同窗在此以前加入python的安裝過程)
ENV PIP_VERSION 19.1.1
RUN set -ex; \ \ wget -O get-pip.py 'https://bootstrap.pypa.io/get-pip.py'; \ \ python get-pip.py \ --disable-pip-version-check \ --no-cache-dir \ "pip==$PIP_VERSION" \ ; \ pip --version; \ \ find /usr/local -depth \ \( \ \( -type d -a \( -name test -o -name tests \) \) \ -o \ \( -type f -a \( -name '*.pyc' -o -name '*.pyo' \) \) \ \) -exec rm -rf '{}' +; \ rm -f get-pip.py 複製代碼
經過以上Dockerfile中的內容,咱們已經完成了一個鏡像的配置,該鏡像中主要包含了:Centos7
、python2.7
、pip
。
將以上內容保存爲:D:\pc_share\etc\DockerFile
,而後在Windows的命令行軟件cmd
中鍵入以下命令經過Dockerfile構建鏡像centos_python2:1.0(centos_python2爲鏡像名,1.0爲tag,也即版本號):
cd D:\pc_share\etc # 進入配置文件的目錄
# docker的鏡像構建命令,
# -t [鏡像名:tag]:設置鏡像名與tag;
# -f [文件名]:指定Dockerfile;
# --target=[階段名]:用於多階段構建;
docker build -t centos_python2:1.0 -f DockerFile --target=centos_python2 .
複製代碼
經過上述命令能夠構建出包含Centos7
、python2.7
、pip
的鏡像:centos_python2:1.0
。在cmd
中鍵入docker images
命令能夠獲得以下信息:
如上圖中紅框中即爲對應的鏡像,其大小僅爲237Mb,若是使用alpine
代替Centos7
甚至能夠將鏡像控制在30Mb之內。
下層運行環境提供了一個較爲通用的基於Centos的python開發平臺,是全部python項目運行的通用基礎,在後續的開發、測試過程當中基本不須要修改。可是,不一樣的python項目可能會面臨着不一樣的問題,主要能夠分爲如下幾項:
因爲docker鏡像是隻讀的而且構建過程較爲耗時,在開發過程當中咱們會常常修改項目文件、配置文件、依賴庫等信息。咱們不可能每修改一次就從新構建一個新的鏡像。爲了解決這一問題,咱們將常常變更的過程放置到另外一個鏡像中,也即上層運行環境
。
爲了解決上述第一個問題,咱們在開發python項目時經常使用requestments.txt
文件記錄並控制項目所依賴python第三方庫,經過如下命令安裝requestments.txt
中記錄的第三方庫:
pip install -r /&{path}/requirements.txt
複製代碼
本文示例項目的第三方庫配置文件放置在:D:\pc_share\etc\requestments.txt
:
tornado==5.1.1 # 依賴的Web框架,版本號爲5.1.1
supervisor==4.0.2 # 使用supervisor監管python項目的運行
複製代碼
當項目依賴的第三方庫發生變化時直接修改requestments.txt
文件並基於下層運行環境
從新構建上層運行環境
的鏡像就能夠了,減小了沒必要要的操做。
爲了解決第2、三個問題,這裏使用了supervisor
管理python項目,關於其詳細的教程能夠參見「淡水網誌」的博客:supervisor使用教程。按照supervisor
的格式爲python項目編寫啓動配置文件並將其放置在supervisor
監控的文件夾中,supervisor
就能夠按照咱們的配置文件很方便地對項目進行啓動、監控。例如示例項目1的啓動配置文件放在:D:\pc_share\etc\supervisor\test_demo1.conf
:
[program:test_demo1] # 項目名稱,惟一
directory=/mnt/share/apps/test_demo1 # 項目所在路徑,由於是在docker的Linux中執行,因此須要填項目在Linux中的路徑,這裏假設將Windows中的共享路徑掛載到"/mnt/share/"下
command=python app.py # 項目的啓動命令
stdout_logfile=/mnt/share/apps/test_demo1/app.log # 項目的日誌文件
priority=1 # 優先級
numprocs=1 # 進程數
autostart=true # 自動啓動
autorestart=true # 自動重啓
startretries=5 # 啓動嘗試次數
複製代碼
以上就是示例項目1的啓動配置文件,修改supervisor
的監控路徑爲/mnt/share/etc/supervisor/
,由於docker能夠將Windows的共享目錄D:\pc_share\
掛載到/mnt/share/
下,因此當supervisor
啓動時就會在/mnt/share/etc/supervisor/
(也即Windows中的D:\pc_share\etc\supervisor
)下尋找項目的配置文件,並按照項目的配置文件啓動對應的項目。
FROM centos_python2:1.0 as centos_python2_supervisor # 上層運行環境的鏡像基於下層運行環境的鏡像:"centos_python2:1.0"
ENV SUPERVISOR_PATH=$SHARE_PATH/etc/supervisor # 放置項目的啓動配置文件的路徑,下面會配置supervisor的設置文件將其讀取配置文件的目錄改成"$SUPERVISOR_PATH"所指向的目錄
# 經過pip按照requirements.txt記錄的依賴庫列表安裝python的第三方庫
COPY requirements.txt /tmp/ # 將 D:\pc_share\etc\ 目錄下的requirements.txt複製到鏡像的/tmp/中 RUN set -ex \ && pip install -r /tmp/requirements.txt -i $PYPI_REPO \ && rm -rf ~/.cache/pip/*
# 配置supervisor的設置文件"/etc/supervisord.conf"
RUN set -ex \ && echo_supervisord_conf > /etc/supervisord.conf \ && mkdir /etc/supervisord.d/ \ && echo "[include]" >> /etc/supervisord.conf \ && echo "files = $SUPERVISOR_PATH/*.conf" >> /etc/supervisord.conf \ # 監控"$SUPERVISOR_PATH"目錄中的項目啓動配置文件 && sed -i '/nodaemon/s/false/true/' /etc/supervisord.conf # 【十分重要】將supervisord.conf中的nodaemon變量改由false改成true,也即將supervisor改成前臺運行,後面做詳細解釋
EXPOSE 8888 8889 # 聲明暴露的端口
CMD ["supervisord", "-c", "/etc/supervisord.conf"] # 容器(不是鏡像)啓動時執行的命令 複製代碼
如上所示爲上層運行環境
的鏡像配置文件。首先,該鏡像基於剛剛構建的下層運行環境
鏡像:centos_python2:1.0
,在此基礎上,使用pip工具依照requirements.txt
中記錄的依賴項爲項目安裝運行時必要的python第三方庫。而後,生成、配置supervisor
的設置文件/etc/supervisord.conf
,將supervisor
讀取項目的啓動配置文件的目錄改成$SUPERVISOR_PATH/
,其中$SUPERVISOR_PATH
爲前文設置的環境變量,記錄了Windows中項目的啓動配置文件在docker鏡像中的掛載點。則經過$SUPERVISOR_PATH
讀取的配置文件實際就是Windows中D:\pc_share\etc\supervisor\
下的配置文件。
最後,CMD
後的內容表示容器啓動時會被執行的語句,一般能夠用來啓動項目。因爲咱們使用了supervisor
來監控項目,因此文件中直接開啓了supervisor
的服務。
supervisor
的設置過程當中有一行命令爲sed -i '/nodaemon/s/false/true/' /etc/supervisord.conf
,這是我在不瞭解docker運行機制的狀況下踩過的坑。Docker鏡像使用run命令運行起來後就會產生一個容器,能夠簡單地把容器看成一個進程,進程在執行完它的指令後就會退出、中止。因此當咱們的指令爲啓動supervisor
時,而supervisor
默認是後臺運行的,因此咱們的容器啓動時執行命令啓動supervisor
:supervisord -c /etc/supervisord.conf
,這是supervisor
進入了後臺運行,該命令執行結束,docker容器也認爲它完成了本身的任務,順利退出、中止運行。形成的結果就是每次啓動docker容器都會馬上中止運行,沒法使容器按咱們的想法持續的運行。爲了解決這個問題,咱們能夠將supervisor
改成在前臺運行,也即修改設置文件/etc/supervisord.conf
中的"nodaemon"爲"true"。這時再啓動容器,supervisor
會一直佔據着前臺,使容器不能"完成"本身的指令,咱們的容器就能夠保持着持續的運行狀態。
經過2.2節咱們完成了docker鏡像的配置文件Dockerfile的編寫,在這一份Dockerfile中,爲了方便修改、加速鏡像的構建過程,咱們將其分爲兩個部分,分別對應着下層運行環境鏡像與上層運行環境鏡像。爲了區分,根據docker鏡像的命名規範,咱們將前者命名爲:centos_python2:1.0,後者命名爲:centos_python2_supervisor:1.0。
綜上所述,整個Dockerfile的內容爲(D:\pc_share\etc\DockerFile):
FROM centos:7 as centos_python2
MAINTAINER mileskong <xiangqian_kong@126.com>
ENV PYPI_REPO=https://mirrors.aliyun.com/pypi/simple/ \
PYTHONIOENCODING=UTF-8 \
SHARE_PATH=/mnt/share
# install software
RUN set -ex \ && yum -y install epel-release wget net-tools \ && yum clean all
# install pip
ENV PIP_VERSION 19.1.1
RUN set -ex; \ \ wget -O get-pip.py 'https://bootstrap.pypa.io/get-pip.py'; \ \ python get-pip.py \ --disable-pip-version-check \ --no-cache-dir \ "pip==$PIP_VERSION" \ ; \ pip --version; \ \ find /usr/local -depth \ \( \ \( -type d -a \( -name test -o -name tests \) \) \ -o \ \( -type f -a \( -name '*.pyc' -o -name '*.pyo' \) \) \ \) -exec rm -rf '{}' +; \ rm -f get-pip.py
FROM centos_python2:1.0 as centos_python2_supervisor
ENV SUPERVISOR_PATH=$SHARE_PATH/etc/supervisor
# install python libs by pip
COPY requirements.txt /tmp/ RUN set -ex \ && pip install -r /tmp/requirements.txt -i $PYPI_REPO \ && rm -rf ~/.cache/pip/*
# build supervisord.conf
RUN set -ex \ && echo_supervisord_conf > /etc/supervisord.conf \ && mkdir /etc/supervisord.d/ \ && echo "[include]" >> /etc/supervisord.conf \ && echo "files = $SUPERVISOR_PATH/*.conf" >> /etc/supervisord.conf \ && sed -i '/nodaemon/s/false/true/' /etc/supervisord.conf
EXPOSE 8888 8889
CMD ["supervisord", "-c", "/etc/supervisord.conf"] 複製代碼
下面則是根據Dockerfile構建鏡像並運行的步驟:
cd D:\pc_share\etc # 進入配置文件的目錄
docker build -t centos_python2:1.0 -f DockerFile --target=centos_python2 . # 構建下層運行環境鏡像:"centos_python2:1.0"
docker build -t centos_python2_supervisor:1.0 -f DockerFile --target=centos_python2_supervisor . # 基於"centos_python2:1.0",構建上層運行環境鏡像:"centos_python2_supervisor:1.0"
docker run -itd -v d:/pc_share/:/mnt/share -p 8888:8888 -p 8889:8889 ${image_id} # 運行鏡像"centos_python2:1.0"
複製代碼
上述命令首先構建了下層運行環境的鏡像centos_python2:1.0
,而後在此基礎上構建了上層運行環境的鏡像centos_python2_supervisor:1.0
,最後經過docker的run命令啓動鏡像。其中比較重要的是run命令中的一些參數,下面做詳細介紹:
docker run [options] [image_id]
# options:
-v [宿主機的共享目錄]:[docker容器中的掛載點]:將宿主機中的共享目錄掛載到docker容器的某個掛載點下,若是掛載點不存在則啓動容器時自動建立;
-p [宿主機端口]:[docker容器端口]:將宿主機的端口映射到docker容器的端口上;
複製代碼
根據run的參數能夠看出,咱們在運行鏡像時將Windows系統下的目錄:d:/pc_share/
掛載到docker容器中的/mnt/share
下;將宿主機的8888端口映射到docker容器的8888端口,將8889映射到docker容器的8889端口。
經過上述步驟啓動鏡像centos_python2_supervisor:1.0
後,經過命令docker ps -a
能夠查看經過該鏡像開啓的容器:
當咱們在本地瀏覽器中分別輸入"localhost:8888"和"localhost:8889"後就能夠訪問運行在docker中的示例項目了。
在第2章節中詳細介紹了怎麼使用docker自定義項目的運行環境,整個過程基本能夠劃分爲如下幾部分:
在開發過程當中,前兩個步驟是配置環境的過程,而第三個步驟是咱們常常重複進行的,每當咱們在Windows中修改了項目代碼或者配置文件後,都須要重啓docker容器驗證代碼的運行狀況,而run命令的參數也較爲複雜,因此頻繁的重啓容器也多是比較煩人的。
因爲我日常比較喜歡使用vscode
做爲開發的IDE工具,偶爾中發現了vscode
也支持docker插件,十分方便,下面簡要爲你們介紹下如何經過vscode
的docker插件實現「一鍵啓動」。
vscode
的擴展插件商店中搜索"docker"並安裝該插件,重啓後會發現vscode
的側邊欄多出了一個docker的圖標:如上圖中所示,這個插件能夠直接顯示出本機中存在的鏡像、容器和倉庫信息。由圖能夠看到咱們以前建立的全部鏡像。
centos_python2_supervisor:1.0
,右擊並在選項列表中選擇Run
,會發現咱們的容器列表中新增了一個運行的容器:Restart Container
,就能夠重啓咱們的容器,從新啓動咱們的項目:因此,當咱們經過編寫Dockerfile、構建鏡像後,基本的運行環境就搭建成功了。以後每次須要測試修改後的代碼時只須要在docker的插件界面中選中對應容器選擇重啓就能夠了。若是測試想要了解項目運行更詳細的信息,右擊選擇Attach Shell
就能夠進入容器中經過熟悉的終端查看相關日誌: