怎麼創建高效Docker鏡像?

1.概述

在過去的幾年中,Docker已成為Linux上容器化的事實上的標準。 Docker易於使用,並提供輕量級虛擬化,使其成為構建應用程序和微服務的理想選擇,因為越來越多的服務在雲中運行。

儘管創建我們的第一張圖像可能相對容易,但是構建有效的圖像仍需要深思熟慮。在本教程中,我們將看到有關如何編寫有效的Docker映像的示例以及每個建議背後的原因的示例。

讓我們從使用官方圖片開始。

2.以官方形象為基礎

2.1 什麼是官方鏡像?

官方Docker映像是由Docker贊助的團隊創建或維護的,或者至少由他們批准的映像。他們在GitHub項目上公開管理Docker映像。當發現漏洞時,它們也會進行更改,並確保映像是最新的並遵循最佳實踐。

讓我們通過使用Nginx官方圖像的示例更清楚地看到這一點。網絡服務器的創建者維護此鏡像。

假設我們要使用Nginx託管我們的靜態網站。我們可以創建我們的Dockerfile並基於官方映像:

FROM nginx:1.19.2

COPY my-static-website/ /usr/share/nginx/html

然後我們可以建立我們的形象:

$ docker build -t my-static-website .

最後,運行它:

$ docker run -p 8080:80 -d my-static-website

我們的Dockerfile只有兩行。基本官方映像負責Nginx服務器的所有詳細信息,例如默認配置文件和應公開的端口。

更具體地說,基本映像阻止Nginx成為守護程序並結束初始過程。在其他環境中也會出現這種行為,但是在Docker中,這被解釋為應用程序的結尾,因此容器終止。解決方案是將Nginx配置為不成為守護程序。這是官方映像中的配置:

CMD ["nginx", "-g", "daemon off;"]

當我們將圖像基於官方鏡像時,我們避免了難以調試的意外錯誤。官方的鏡像維護者是Docker和我們要使用的軟件的專家,因此我們將從他們的全部知識中受益,並可能節省時間。

2.2 由其創作者維護的鏡像

儘管從前面解釋的意義上講不是官方的,但Docker Hub中還有其他鏡像,這些鏡像也由應用程序的創建者維護。

讓我們用一個例子來說明這一點。 EMQX是MQTT消息代理。假設我們要將此代理用作應用程序中的微服務之一。我們可以基於他們的映像並添加我們的配置文件。甚至更好的是,我們可以使用它們的配置通過環境變量配置EMQX。

例如,要更改EMQX監聽的默認端口,我們可以添加EMQX_LISTENER__TCP__EXTERNAL環境變量:

$ docker run -d -e EMQX_LISTENER__TCP__EXTERNAL=9999 -p 9999:9999 emqx/emqx:v4.1.3

作為特定軟件背後的社區,他們處於提供其軟件Docker映像的最佳位置

在某些情況下,對於我們要使用的應用程序,我們找不到任何形式的官方圖像。即使在那種情況下,我們也會從Docker Hub中搜索可以用作參考的鏡像而受益。

讓我們以H2為例。 H2是用Java編寫的輕量級關係數據庫。儘管沒有H2的官方圖片,但第三方創建了一個並對其進行了很好的記錄。我們可以使用他們的GitHub項目來學習如何將H2用作獨立服務器,甚至可以協作以使項目保持最新狀態。

即使我們僅將Docker映像項目用作構建映像的起點,我們也可能會從頭開始學習更多。

3.盡可能避免構建新映像

在使用Docker時,即使與基本映像相比變化不大,我們仍可能養成始終創建新映像的習慣。對於這些情況,我們可以考慮將我們的配置直接添加到正在運行的容器中,而不是構建一個image

每次更改時,都需要重新構建自定義Docker映像。之後還需要將它們上傳到註冊倉庫。如果鏡像包含私有信息,我們可能需要將其存儲在私有存儲庫中。在某些情況下,通過使用基本映像並動態配置它們,而不是每次都構建自定義映像,我們可能會獲得更多好處。

讓我們以HAProxy為例來說明這一點。與Nginx一樣,HAProxy可以用作反向代理。 Docker社區維持其官方形象

假設我們需要配置HAProxy以將請求重定向到應用程序中的適當微服務。所有這些邏輯都可以寫在一個配置文件中,例如my-config.cfg 。 Docker映像要求我們將配置放置在特定路徑上。

讓我們看看如何通過在運行容器上安裝的自定義配置來運行HAProxy:

$ docker run -d -v my-config.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro haproxy:2.2.2

這樣,即使升級HAProxy,也變得更加簡單,因為我們只需要更改標籤即可。當然,我們還需要確認我們的配置仍適用於新版本。

如果我們要構建一個由許多容器組成的解決方案,則可能已經在使用編排器,例如Docker Swarm或Kubernetes。它們提供了存儲配置並將其鏈接到正在運行的容器的方法。 Swarm稱它們為Configs ,而Kubernetes稱它們為ConfigMaps

編排工具已經考慮過,我們可以在我們使用的鏡像之外存儲一些配置。在某些情況下,最好將配置保留在映像之外。

4.創建精簡版的鏡像

鏡像大小很重要,原因有兩個。首先,較小的鏡像傳輸速度更快。當我們在開發機器中構建映像時,它看起來可能不會改變遊戲規則。但是,當我們在CI / CD管道上構建多個映像並部署到多個服務器時,每次部署節省的總時間可能是可感知的。

其次,要實現圖像的精簡版,我們需要刪除鏡像未使用的額外軟件包。這將有助於我們減少攻擊面,從而提高圖像的安全性。

讓我們看兩種減小Docker映像大小的簡單方法。

4.1 可用時使用精簡版版本

在這裡,我們有兩個主要選擇:精簡版的Debian和Alpine Linux發行版。

Slim版本是Debian社區的一項工作,旨在從標準映像中刪除不必要的文件。許多Docker映像已經是基於Debian的精簡版本

例如,HAProxy和Nginx映像基於Debian發行版debian:buster-slim 。因此,這些映像從數百MB變為只有幾十MB。

在某些其他情況下,該圖像提供了標準版本的標準版本和精簡版本。例如,最新的Python映像提供了一個苗條的版本,當前為python:3.7.9-slim ,它幾乎比標準映像小十倍。

另一方面,許多圖像都提供Alpine版本,就像我們之前提到的Python圖像一樣。基於Alpine的圖像通常約為10 MB

Alpine Linux在設計之初就考慮了資源效率和安全性。這使其非常適合基礎Docker映像。

需要牢記的一點是,Alpine Linux幾年前選擇將系統庫從更常見的glibc更改為musl 。儘管大多數軟件都可以正常運行,但是如果我們選擇Alpine作為基礎映像,那麼我們可以很好地對我們的應用程序進行徹底的測試。

4.2 使用多階段構建

多階段構建功能允許在同一Dockerfile中的多個階段中構建映像,通常使用下一個階段中的上一個階段的結果。讓我們看看它如何有用。

假設我們要使用HAProxy並通過其REST API(即Data Plane API)動態地對其進行配置。由於該API二進製文件在基礎HAProxy映像中不可用,因此我們需要在構建期間下載它。

我們可以在一個階段中下載HAProxy API二進製文件,並將其提供給下一階段:

FROM haproxy:2.2.2-alpine AS downloadapi

 RUN apk add --no-cache curl

 RUN curl -L https://github.com/haproxytech/dataplaneapi/releases/download/v2.1.0/dataplaneapi_2.1.0_Linux_x86_64.tar.gz --output api.tar.gz

 RUN tar -xf api.tar.gz

 RUN cp build/dataplaneapi /usr/local/bin/



 FROM haproxy:2.2.2-alpine

 COPY --from=downloadapi /usr/local/bin/dataplaneapi /usr/local/bin/dataplaneapi

 ...

第一階段, downloadapi ,下載最新的API並解壓縮tar文件。第二階段複製二進製文件,以便HAProxy稍後可以使用它。我們不需要卸載curl或刪除下載的tar文件,因為第一階段已被完全丟棄,並且不會出現在最終映像中。

在其他一些情況下,多階段圖像的好處更加明顯。例如,如果我們需要從源代碼進行構建,則最終映像將不需要任何構建工具。第一階段可以安裝所有構建工具並構建二進製文件,而下一階段將僅複製那些二進製文件。

即使我們不總是使用此功能,也很高興知道它的存在。在某些情況下,精簡版我們的鏡像可能是最佳選擇。

5.結論

容器化將繼續存在,而Docker是開始使用它的最簡單方法。它的簡單性可以幫助我們快速提高生產力,儘管有些經驗教訓只能通過經驗來學習。

在本教程中,我們回顧了一些構建更強大和安全的圖像的技巧。由於容器是現代應用程序的基礎,因此它們越可靠,我們的應用程序就越強大。