摘要: 本文以構建Haskell應用程式為例, 演示了兩種不同的方法來實現多階段構建。
寫在前面在這篇文章中,Deni Bertovic將向我們展示如何使用Docker來快速構建Haskell應用程式並生成Docker鏡像。
備註: Haskell 是一種標準化的, 通用的純函數程式設計語言, 有非限定性語義和強靜態類型。 作為一門函數程式設計語言, 主要控制結構是函數。 Haskell具有“證明即程式、命題為類型”的特徵。 (摘自維琪百科)
接下來, 我們將從兩個案例入手, 通過對比分析來幫助您理解。 第一個案例, 使用相同的Linux發行版本(ubuntu:16.04)進行開發。 第二個案例, 使用不同的作業系統和發行版本來進行開發。
關於Docker多階段構建功能和“Stack Images Container”指令, 會在該文章的最後有更多介紹。
案例一:在相同的作業系統或發行版本上構建和部署
如果我們在相同的Linux發行版本上構建Haskell應用程式(在本例中是一個Docker鏡像), 那麼構建它的過程就會十分精簡。
在這種情況下,
讓我們看一下將要使用的Makefile, 特別是構建目標:
構建二進位檔案和docker 鏡像
build:
@stack build
@BINARY_PATH=${BINARY_PATH_RELATIVE} docker-compose build
正如我們所看到的, 我們先在本地使用Stack來構建二進位檔案, 然後通過調用docker-compose和提供的Dockerfile構建出最終的docker鏡像。
docker-compose文件如下所示:
version: '2'services: myapp: build: context: . args: - BINARY_PATH image: fpco/myapp command: /opt/myapp/myapp正如我們從docker-compose檔中看到的, 我們在Docker構建時將一個“構建參數”傳遞進去。 具體而言, 我們傳遞的就是位於.stack-work/path/to/myapp中的二進位檔案的相對位置。 接下來, 讓我們觀察一下Dockerfile, 看看如何使用這個構建參數。
FROM ubuntu:16.04RUN mkdir -p /opt/myapp/ARG BINARY_PATHWORKDIR /opt/myappRUN apt-get update && apt-get install -y ca-certificates libgmp-devCOPY "$BINARY_PATH" /opt/myappCOPY static /opt/myapp/staticCOPY config /opt/myapp/configCMD ["/opt/myapp/myapp"]在Dockerfile中, 我們只需要簡單的將.stack-work/path/to/myapp複製到Dockerfile中的預定位置即可。
案例二:在不同的作業系統或發行版本中構建
如果在一個與我們部署的作業系統或發行版本不同的機器上進行開發,
以前, 可以通過下面兩種方式來完成:
將所有構建時相關的檔打包到最終的Docker鏡像中
將整個構建過程分解為Dockerfile.build(包含所有構建時相關的檔)和Dockerfile(包含運行時相關的檔和最終的二進位檔案)兩部分。 這是一個相當繁瑣的過程, 需要一個shell腳本來輔助。 首先構建第一個Docker鏡像, 然後利用這個docker鏡像啟動一個容器, 獲取已編譯的二進位檔案, 之後再啟動第二個容器, 將這個已編譯二進位檔案嵌入其中, 最後將其生成為新的docker鏡像。 您可以在Docker文檔中瞭解到更多相關資訊。
現在, Docker已經採用了一個名為多階段構建的功能, 它使整個構建過程變得簡單化、自動化。 它所帶來的好處就是, 我們不再使用構建工具和構建需求來擴大生產鏡像, 從而保證我們原本的鏡像大小。
Docker多階段構建
在這種情況下, 我們的Dockerfile看起來像以下這樣:
FROM fpco/stack-build:lts-9.9 as buildRUN mkdir /opt/buildCOPY . /opt/buildRUN cd /opt/build && stack build --system-ghcFROM ubuntu:16.04RUN mkdir -p /opt/myappARG BINARY_PATHWORKDIR /opt/myappRUN apt-get update && apt-get install -y ca-certificates libgmp-dev# NOTICE THIS LINECOPY --from=build /opt/build/.stack-work/install/x86_64-linux/lts-9.9/8.0.2/bin .COPY static /opt/myapp/staticCOPY config /opt/myapp/configCMD ["/opt/myapp/myapp"]在Dockerfile中, 我們首先使用fpco / stack-build:lts-9.9鏡像來構建我們的應用程式。 在我們構建完鏡像之後, 我們有另一個FROM 塊使用相同的fpco/stack-build基礎鏡像, 在這裡我們複製了前一個版本的二進位檔案。 這使我們只能交付最終的二進位檔案, 而不需要任何相關的構建檔。
使用“Stack Images Container”
Stack通過一種將應用程式的可執行檔放入其中的方式來構建Docker鏡像。 雖然在Docker引入多階段構建之前, 這種支持是可用的,
首先讓我們看看要將stack.yaml複製到stack-native.yaml所需的更改:
docker:Docker Carrying Haskell.jpg enable: trueimage: container: base: "fpco/myapp-base" name: "fpco/myapp" add: static/: /opt/app/static config/: /opt/app/config正如我們所看到的, 我們正在讓Stack使用docker構建我們的可執行檔(如果您正在構建像OSX或Windows這樣的非Linux平臺, 則需要該檔), 然後指定一些我們想要Stack為我們構建容器的中繼資料。
我們需要將生成鏡像的名稱和本地目錄添加到鏡像中。 Stack將自動為我們添加可執行檔。
讓我們看一下我們的Dockerfile.base, 我們將用它來構建我們的基礎鏡像。
FROM ubuntu:16:04RUN mkdir -p /opt/appWORKDIR /opt/appRUN apt-get update && apt-get install -y ca-certificates libgmp-dev正如我們所看到的, 除了少了複製生成的二進位檔案的部分, 它與我們之前的Dockerfile沒有什麼不同, 因為Stack將會為我們完成這一點。
我們可以使用make build-base來構建鏡像。 然後建立新的鏡像來運行make build-stack-native。
上面的目標是這樣的:
構建用於stack image container的基礎鏡像
build-base: @docker build -t fpco/myapp-base -f Dockerfile.base .使用stack-native.yaml構建應用程式
build-stack-native: build-base @stack --stack-yaml stack-native.yaml build @stack --stack-yaml stack-native.yaml image container現在來測試我們構建的鏡像吧,讓我們運行make run-stack-native試試看吧,如下所示:
**運行由stack image container構建的容器
run-stack-native: @docker run -p 3000:3000 -it -w /opt/app ${IMAGE_NAME} myapp可以在Stack文檔中閱讀關於stack image container的更多資訊。
總結這篇文章演示了如何使用Docker多階段構建來構建Haskell應用程式並生成Docker鏡像,同時保持了鏡像原有的大小。此外,還展示了一種使用“Stack Images Container”的替代方法,同樣它也可以生成類似的docker鏡像,但與前者相比,它缺乏了一些靈活性。
根據您的專案需要,選擇適合您的方法吧!
注:上面的示例代碼可以在Github上找到,並且包含關於進程管理和許可權處理的一些內容,為簡潔起見,這些內容在本文中被省略了。
然後建立新的鏡像來運行make build-stack-native。上面的目標是這樣的:
構建用於stack image container的基礎鏡像
build-base: @docker build -t fpco/myapp-base -f Dockerfile.base .使用stack-native.yaml構建應用程式
build-stack-native: build-base @stack --stack-yaml stack-native.yaml build @stack --stack-yaml stack-native.yaml image container現在來測試我們構建的鏡像吧,讓我們運行make run-stack-native試試看吧,如下所示:
**運行由stack image container構建的容器
run-stack-native: @docker run -p 3000:3000 -it -w /opt/app ${IMAGE_NAME} myapp可以在Stack文檔中閱讀關於stack image container的更多資訊。
總結這篇文章演示了如何使用Docker多階段構建來構建Haskell應用程式並生成Docker鏡像,同時保持了鏡像原有的大小。此外,還展示了一種使用“Stack Images Container”的替代方法,同樣它也可以生成類似的docker鏡像,但與前者相比,它缺乏了一些靈活性。
根據您的專案需要,選擇適合您的方法吧!
注:上面的示例代碼可以在Github上找到,並且包含關於進程管理和許可權處理的一些內容,為簡潔起見,這些內容在本文中被省略了。