藉助Docker,在win10下編碼,一鍵在Linux下測試

kk_miles發表於2019-05-16

此前在公司實習時,日常的開發、工作按規定都必須使用Windows作業系統,但是專案實際的測試、上線環境都是基於Linux的,所以每次只能在本地編寫某一功能的程式碼後通過“跳板機”將專案程式碼傳送到伺服器上進行測試。出現問題也只能在伺服器上修改,執行通過後再將修改後的程式碼傳送到本地然後繼續下一階段的工作。由於我自己不太喜歡直接在伺服器上使用vim編輯程式碼,也受不了每次都要通過"跳板機"將程式碼傳來傳去的繁複操作,本著生命在於折騰的真理,開啟了這次的折騰之旅。

為了既可以愉快地在Windows下編碼、交流、玩耍,又可以一鍵在Linux下快速部署、測試程式碼,本文通過Docker for Windows在Windows系統中佈置與伺服器上一致的Linux測試環境,不用再將我們的程式碼拷貝到遠端伺服器上,只佔用較小的資源就可以一鍵在本地以相同的執行環境測試我們的程式碼。

效果演示

  1. 一鍵執行專案:

藉助Docker,在win10下編碼,一鍵在Linux下測試

  1. 模擬修改專案後的一鍵重新部署、測試:

藉助Docker,在win10下編碼,一鍵在Linux下測試


1. Docker for Windows的安裝與設定

下載地址:Download Docker Desktop

安裝前提:64bit Windows 10 Pro and Microsoft Hyper-V

1.1 安裝步驟:

  1. 開啟Hyper-V功能:

    • 控制皮膚 -> 程式和功能 -> 啟動或關閉Windows功能 -> 勾選"Hype-V" -> 重啟
  2. 安裝Docker;

  3. 設定國內映象倉庫地址:

    • 啟動Docker後右擊Docker的執行圖示選擇"setting"進入設定介面;

    • 選擇設定皮膚左側的"Daemon"選項,在"Registry mirrors"中填入國內的映象倉庫地址(推薦使用阿里雲的,在我這邊測試的速度較快,但是可能需要註冊賬號);

    • 點選"Apply";

  4. 設定共享目錄:

    • 進入Docker的設定皮膚,選擇"Shared Drives"選項;

    • 勾選想要共享的碟符,點選"Apply",可能需要輸入Windows賬戶的密碼;

1.2 可能遇到的問題:

在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.
複製程式碼

​ 網路上關於該問題的解決方案主要分為以下幾種:

​ 我嘗試了第1、3個方法,第1個方法對我的Docker不起作用,然後使用第3中方法重新安裝後就可以了,比較奇怪。


2. 正式開始

2.1 建立工作目錄

首先需要設定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_demo1test_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.2 使用Docker建立程式執行環境

​由章節2.1可知,執行上述專案至少需要三個環境:作業系統pythontornado框架。為了與專案上線的環境保持一致,這裡假設三個環境條件分別為:Centos7python2.7tornado5.1.1。所以我們需要使用Dockerfile配置相應的執行環境,不熟悉Dockerfile的同學可以瀏覽下教程:Dockerfile教程

​為了更方便地修改執行環境,Dockerfile主要分為兩部分:配置下層執行環境配置上層執行環境

2.2.1 配置下層執行環境

藉助Docker,在win10下編碼,一鍵在Linux下測試

如上圖所示為底層執行環境的配置過程示意圖,對應的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中的內容,我們已經完成了一個映象的配置,該映象中主要包含了:Centos7python2.7pip

將以上內容儲存為: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 .
複製程式碼

​通過上述命令可以構建出包含Centos7python2.7pip的映象:centos_python2:1.0。在cmd中鍵入docker images命令可以得到如下資訊:

藉助Docker,在win10下編碼,一鍵在Linux下測試

​如上圖中紅框中即為對應的映象,其大小僅為237Mb,如果使用alpine代替Centos7甚至可以將映象控制在30Mb以內。

2.2.2 配置上層執行環境

藉助Docker,在win10下編碼,一鍵在Linux下測試

下層執行環境提供了一個較為通用的基於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檔案並基於下層執行環境重新構建上層執行環境的映象就可以了,減少了不必要的操作。

為了解決第二、三個問題,這裡使用了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預設是後臺執行的,所以我們的容器啟動時執行命令啟動supervisorsupervisord -c /etc/supervisord.conf,這是supervisor進入了後臺執行,該命令執行結束,docker容器也認為它完成了自己的任務,順利退出、停止執行。造成的結果就是每次啟動docker容器都會立刻停止執行,無法使容器按我們的想法持續的執行。為了解決這個問題,我們可以將supervisor改為在前臺執行,也即修改設定檔案/etc/supervisord.conf中的"nodaemon"為"true"。這時再啟動容器,supervisor會一直佔據著前臺,使容器不能"完成"自己的指令,我們的容器就可以保持著持續的執行狀態。

2.3 構建映象並執行

通過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可以檢視通過該映象開啟的容器:

藉助Docker,在win10下編碼,一鍵在Linux下測試

當我們在本地瀏覽器中分別輸入"localhost:8888"和"localhost:8889"後就可以訪問執行在docker中的示例專案了。

3. 一鍵啟動

在第2章節中詳細介紹了怎麼使用docker自定義專案的執行環境,整個過程基本可以劃分為以下幾部分:

  1. 編寫Dockerfile,自定義專案的執行環境和啟動配置;
  2. 通過Dockerfile構建docker映象;
  3. 通過run命令啟動docker,生成容器。

在開發過程中,前兩個步驟是配置環境的過程,而第三個步驟是我們經常重複進行的,每當我們在Windows中修改了專案程式碼或者配置檔案後,都需要重啟docker容器驗證程式碼的執行情況,而run命令的引數也較為複雜,所以頻繁的重啟容器也可能是比較煩人的。

由於我平常比較喜歡使用vscode作為開發的IDE工具,偶爾中發現了vscode也支援docker外掛,十分方便,下面簡要為大家介紹下如何通過vscode的docker外掛實現“一鍵啟動”。

3.1 docker外掛的使用方法

  1. vscode的擴充套件外掛商店中搜尋"docker"並安裝該外掛,重啟後會發現vscode的側邊欄多出了一個docker的圖示:

藉助Docker,在win10下編碼,一鍵在Linux下測試

如上圖中所示,這個外掛可以直接顯示出本機中存在的映象、容器和倉庫資訊。由圖可以看到我們之前建立的所有映象。

  1. 選擇我們的映象centos_python2_supervisor:1.0,右擊並在選項列表中選擇Run,會發現我們的容器列表中新增了一個執行的容器:

藉助Docker,在win10下編碼,一鍵在Linux下測試

  1. 如果我們修改了程式碼後需要重新測試的話,選擇容器右擊選擇Restart Container,就可以重啟我們的容器,重新啟動我們的專案:

藉助Docker,在win10下編碼,一鍵在Linux下測試

所以,當我們通過編寫Dockerfile、構建映象後,基本的執行環境就搭建成功了。之後每次需要測試修改後的程式碼時只需要在docker的外掛介面中選中對應容器選擇重啟就可以了。如果測試想要了解專案執行更詳細的資訊,右擊選擇Attach Shell就可以進入容器中通過熟悉的終端檢視相關日誌:

藉助Docker,在win10下編碼,一鍵在Linux下測試

多謝觀看!

相關文章