Docker鏡像層級設計指南-構建完美鏡像

一個鏡像的層級
一個Docker鏡像也可以繼承另外一個鏡像。通常的使用場景是Docker鏡像去繼承
一個基礎的操作系統鏡像。你可以將它理解爲面向對象中的像類層級一樣。一個Docker鏡像從另外一個鏡像進行繼承或者”擴展”,它就可以擁有這個鏡像的所有功能。同時,它也可以替換或者覆蓋這個基礎鏡像的功能。
利用Docker鏡像繼承的好處可以參考面向對象繼承的概念

  • 複用性 – 給基礎鏡像添加功能對所有繼承的鏡像都可用
  • 擴展性 – 可以在維護繼承功能的同時向鏡像添加附加功能
  • 疊加性 – 基礎鏡像功能可以被替換
  • 結構一致性 – 基礎鏡像的文件系統佈局跟所有繼承它的的鏡像佈局一致
  • 成熟性 – 隨着基礎映像的發展,繼承的映像也會隨着發展。解決安全漏洞就是一個很好的例子
    Docker鏡像層級設計指南-構建完美鏡像

從操作系統開始

如上所述,有很多官方並且被認證的Linux發行版。最常見的方法是從Docker Hub裏查看有哪些可用鏡像。特別需要關注的是那些官方並且被認證的Linux發行版。可以點擊這裏。
在一個鏡像的底層,操作系統的層級往往從標準的Alpine Linux發行版擴展而來。在Docker社區已經贏得很好的口碑,他是非盈利,高效資源以及安全的。
擴展一個Linux發行版可以讓你自定義一些功能以至於滿足你們組織的需求。下面例子展示了基於標準的操作系統鏡像做的一些修改。這個Dockerfile可以在GitHub找到。

FROM alpine:3.10.2
LABEL relenteny.repository.url=https://github.com/relenteny/alpine
LABEL relenteny.repository.tag=3.10.2
LABEL relenteny.alpine.version=3.10.2
RUN set -x && \
addgroup -g 1000 -S alpine && \
adduser -u 1000 -G alpine -h /home/alpine -D alpine && \
apk add –no-cache curl bind-tools
USER alpine
WORKDIR /home/alpine
ENTRYPOINT [“/bin/sh”, “-l”]
CMD []

這個例子簡單明瞭的說明了:
它創建了一個用戶alpine,並將其設置爲實例化容器的運行用戶。按照配置,用戶alpine不能作爲特權用戶運行。這是保護Docker鏡像的一個重要方面。實例化容器將啓動的目錄被設置爲alpine用戶的主目錄。
作爲一個例子, curl以及bind-tools都會被添加到基礎鏡像中。當需要其他額外的包時, 這是一個構建基礎鏡像的博弈,關於這個鏡像是否可以被後續鏡像廣泛使用與鏡像大小,容器將簡單地啓動一個登錄shell。這裏不需要CMD[]指令,但是在Dockerfile中定義’ ENTRYPOINT ‘和’ CMD ‘通過指定完整的調用定義提供了一定程度的文檔
實例化後, 容器直接執行啓動命令. 我們在Dockerfile中並不需要定義CMD []指令, 但是如果定義了ENTRYPOINT與CMD,它通過指定完整的調用定義提供了一定程度的文檔說明(譯者補充: 可以認爲CMD作爲ENTRYPOINT的參數)。
LABEL可以有多種用途。它們純粹是提供信息,但是在通過開發和部署過程跟蹤鏡像非常有用。
例子: 支持Python版本化
不管運行時平臺是什麼,都會出現平臺版本的問題。隨着時間的推移,預計不同的應用程序和服務將使用不同(或特定的)運行時平臺版本。Python應用程序也不例外。
操作系統發行版將支持運行時平臺版本的一部分。然而依賴於操作系統發行版提供的特定版本的運行時平臺,會產生對操作系統版本的依賴,隨着時間的推移,這種依賴會變得難以管理。爲了與公共Docker鏡像保持一致,運行時平臺應該使用相同的父鏡像進行構建。
幸運的是,大多數Linux版本都廣泛支持多版本的應用程序運行時平臺。需要設計一個模式來解決一些細節,通過這個模式在維護鏡像層次結構的同時,多版本的運行時平臺可以輕鬆並且快速的被組裝。
運行時平臺通常有多種安裝和配置方式。對於Python運行時鏡像,我的選擇是使用pyenv。pyenv通常用於管理安裝在單個系統上的多個Python版本,並且pyenv在安裝和配置特定版本一般用途的Python很出色。
擴展剛纔討論的操作系統鏡像,在接下來的Docker鏡像層級中安裝跟配置pyenv。Dockerfile同樣可以在GitHub上找到。


FROM relenteny/alpine:3.10.2
LABEL relenteny.repository.url=https://github.com/relenteny/python-pyenv
LABEL relenteny.repository.tag=1.2.14
LABEL relenteny.pyenv.version=1.2.14
LABEL relenteny.pyenv.virtualenv.version=1.1.5
COPY build /opt/build
USER root
RUN set -x && \
apk add –no-cache git bash build-base libffi-dev openssl-dev bzip2-dev zlib-dev readline-dev sqlite-dev && \
cp -r /opt/build/home/alpine/* /home/alpine && \
chmod +x /home/alpine/bin/*.sh && \
chown -R alpine.alpine /home/alpine && \
rm -rf /opt/build
USER alpine
RUN set -x && \
cd /home/alpine && \
git clone https://github.com/pyenv/pyenv.git /home/alpine/.pyenv && \
cd /home/alpine/.pyenv && \
git branch pyenv-1.2.14 v1.2.14 && \
git checkout pyenv-1.2.14 && \
cd /home/alpine && \
git clone https://github.com/pyenv/pyenv-virtualenv.git /home/alpine/.pyenv/plugins/pyenv-virtualenv && \
cd /home/alpine/.pyenv/plugins/pyenv-virtualenv && \
git branch virtualenv-1.1.5 v1.1.5 && \
git checkout virtualenv-1.1.5 && \
cd /home/alpine && \
echo ‘export PYENV_ROOT=”$HOME/.pyenv”‘ >> /home/alpine/.profile && \
echo ‘export PATH=”$PYENV_ROOT/bin:$PATH”‘ >> /home/alpine/.profile && \
echo ‘eval “$(pyenv init -)”‘ >> /home/alpine/.profile && \
echo ‘eval “$(pyenv virtualenv-init -)”‘ >> /home/alpine/.profile

這個Dockerfile參考pyenv的安裝說明。還預裝了一組將接下來要討論的腳本。以下是一些關於這個Dockerfile的注意事項:
pyenv的安裝指南可以從這裏獲取。針對在Alpine Linux上安裝所需的包可以從pyenv的Wiki文檔找到.
安裝pyenv-virtualenv插件的細節可以從這裏獲取.
爲了兼容以前的image版本,pyenv跟pyenv-virtualenv都指定了特定版本。更簡潔的方法是通過版本標記創建本地分支。
用戶alpine的環境變量文件.profile被修改,是爲了支持pyenv環境。

  • COPY指令從源目錄build複製文件到鏡像目錄/opt/build。在過去的幾年中,我一直使用這種方式將文件複製到鏡像中。在構建過程中,源目錄包含一個或多個目標的子目錄。對於這個鏡像,源有一個home/alpine/bin的子目錄結構,這個目錄中的文件將被放入鏡像目錄/home/alpine/bin。
    添加易用腳本是爲了後續的鏡像:
  • install-python.sh用於配置特定版本的Python。其目的是通過執行該腳本去創建一個新鏡像。例如,下面的Dockerfile代碼片段將構建一個預裝Python 3.7.4併爲內置用戶alpine提供python環境的鏡像。
FROM relenteny/pyenv:1.2.14
RUN /home/alpine/bin/install-python.sh 3.7.4
通常,雖然不是必需的,但生成的鏡像將會被存儲在registry中,作爲一個通用的python運行時鏡像。使用上面的示例,鏡像名稱和標記是myregistry/python:3.7.4。
install-requirements.sh – 是一個工具腳本,它通過requirements.txt來安裝額外的python模塊。這個腳本只能在安裝python的環境下執行。調用時,必須將包含要安裝的模塊的requirements.txt當成參數傳遞給這個腳本。如下所示/home/alpine/bin/install-requirments.sh /home/alpine/requirements.txt
在這裏,requirements.txt被放在內置用戶alpine的home目錄上。當然,這取決於實際的Dockerfile是如何編寫的。下面是它的用法示例。 

構建一個標準規範的Python鏡像
如前一節所討論的,pyenv鏡像的設計目的是構建後續的Python鏡像。通過配置一個標準的操作系統鏡像,以及一個安裝和配置多版本Python的鏡像,可以非常容易地構建可用於多種用途的Python鏡像。
在我們層次結構中,接下來的鏡像是安裝Python 3.7.4。Dockerfile可以在GitHub上找到。

FROM relenteny/pyenv:1.2.14
LABEL relenteny.repository.url=https://github.com/relenteny/python
LABEL relenteny.repository.tag=3.7.4
LABEL relenteny.python.version=3.7.4
RUN /home/alpine/bin/install-python.sh 3.7.4

就是這樣。在Dockerfile中只需幾個簡單的指令,就可以通過之前的基礎Python鏡像創建出新版本。
添加一個應用程序框架
在任何語言平臺中,僅僅使用平臺提供的構造器從頭搭建應用程序是很少見的。通常,在構建應用程序時會添加額外的框架、工具。
下面的案例示範是如何安裝流行的web框架Flask。Dockerfile可以在GitHub上找到。

FROM relenteny/python:3.7.4
LABEL relenteny.repository.url=https://github.com/relenteny/flask
LABEL relenteny.repository.tag=1.1.1
LABEL relenteny.flask.version=1.1.1
COPY build /opt/build
USER root
RUN set -x && \
cp -r /opt/build/home/alpine/* /home/alpine && \
chown -R alpine.alpine /home/alpine/* && \
rm -rf /opt/build
USER alpine
RUN set -x && \
cd /home/alpine && \
bin/install-requirements.sh /home/alpine/requirements.txt && \
rm /home/alpine/requirements.txt
除了LABEL指令外,在這個Dockerfile中沒有任何東西可以說明Flask正在被安裝。這是一個可重複的模式。無論您使用的是Flask、Django、TensorFlow、Ansible,還是衆多Python框架中的任何一個,都需要添加一個requirements.txt文件,然後調用install-requirements.sh。執行最終的自定義腳本,您就擁有了具有可以共享的基礎鏡像,它可以跨組織共享。
這裏使用的複製指令有一點需要注意。源目錄build的內容被複制到鏡像中的/opt/build。此外,COPY如果沒有指定任何UID,用戶就是root。這可以簡單的通過使用--chown的選項的方式調用COPY將源構建目錄的內容複製到/home/alpine。這就消除了用戶在root和alpine之間進行切換。如之前所說,我發現這個模式很有用。它最適用於更復雜的鏡像構建過程,其中文件可能需要安裝在不同的文件系統位置(例如/等),在這些位置用戶必須是root用戶才能執行操作。所以,雖然這不是必須的,它仍然是構建鏡像的一個既定模式。

最後,一個應用
現在我們已經做到了這裏,需要在這個鏡像層次結構中構建包含實際應用程序的鏡像。至少是一個簡單的例子。我們似乎走了很長的路才走到這一步。雖然我希望你能夠理解爲跨組織重用而設計鏡像層次結構的價值,但是達到這一點還有一個額外的好處。構建Docker鏡像需要一些時間。無論是在開發人員的工作站上或CI/CD pipeline, 鏡像花幾分鐘去構建會阻礙生產力。或者說,作爲團隊成員在等待他們的測試鏡像時候會感到焦慮。通過繼承已經完成了大部分組裝工作的鏡像,最終鏡像構建步驟落地和配置應用程序組件,理論上應該只需要幾秒鐘。
借用Miguel Grinberg寫的Flask傑出教程 第一部分: Hello, World!,下面的鏡像來自於本教程,並將其配置爲一個Docker鏡像來執行。Dockerfile可以在GitHub上找到。

FROM relenteny/flask:1.1.1
LABEL relenteny.repository.url=https://github.com/relenteny/flask-helloworld
LABEL relenteny.repository.tag=1.0.0
RUN set -x && \
cd /home/alpine && \
git clone https://github.com/miguelgrinberg/microblog.git && \
cd microblog && \
git checkout v0.1 && \
echo “FLASK_APP=microblog.py” > .flaskenv
WORKDIR /home/alpine/microblog
ENTRYPOINT [ “/bin/sh”, “-lc”, “flask run –host 0.0.0.0”]
如果你構建了這個鏡像並且給它打上”myrepository/flask-helloworld:1.0.0″的標記,用命令docker run -it -p 5000:5000 myrepository/flask-helloworld:1.0.0去實例化這個容器,然後使用curl命令或者一個瀏覽器,讀取http://localhost:5000或者http://localhost:5000/index的URL,它會返回”Hello World”. 

本文轉載自:k8s中文社區

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章