如何更好地優化容器的建立?這些技巧你務必收藏

在你得到正確的容器配置時須要進行幾回迭代?你每次迭代須要多長時間?好吧,若是回答「太屢次且時間太長」,那麼個人經歷與你很類似。從表面上看,建立配置文件彷佛很簡單:在配置文件中實現與手動安裝系統時要執行的步驟相同。不幸的是,我發現這種方法一般沒法正常工做,而且一些「技巧」對於此類DevOps練習很是有用。python


在本文中,我將分享一些新發現的技術。這些技術能夠幫助你最大程度地減小迭代次數和迭代時間。另外,我將概述一些標準作法之外的調優作法。docker

節省時間在容器映像構建迭代上ubuntu

若是Dockerfile涉及下載並安裝5GB的文件,則即便具備良好的網絡速度,每次Docker 映像構建迭代均可能會花費大量時間。忘記包含要安裝的項目可能意味着在此以後重建全部層。bash

解決這一難題的一種方法是使用本地HTTP服務器,以免在Docker映像構建迭代期間屢次從互聯網下載大文件。爲了舉例說明,假設您須要在Ubuntu 18.04下使用Anaconda 3建立一個容器映像。Anaconda 3安裝程序的文件大小約爲0.5GB,所以在此示例中稱爲「大」文件。服務器

請注意,若是不想使用COPY指令,由於它會建立一個新層。使用大型安裝程序後,還應刪除它,以最小化容器映像的大小。您可使用多階段構建,可是我發現如下方法足夠有效。網絡

其基本思想是使用基於Python的HTTP服務器在本地,以服務大文件(S),並有Dockerfile wget的,從這個本地服務器的大文件(S)。讓咱們探索如何有效設置它的細節。工具

在此示例存儲庫中,文件夾tutorial2_docker_tricks /的必要內容是:ui

tutorial2_docker_tricks/├── build_docker_image.sh# builds the docker image├── run_container.sh# instantiates a container from the image├── install_anaconda.dockerfile# Dockerfile for creating our target docker image├── .dockerignore# used to ignore contents of the installer/ folder from the docker context├── installer# folder with all our large files required for creating the docker image│  └── Anaconda3-2019.10-Linux-x86_64.sh# from https://repo.anaconda.com/archive/Anaconda3-2019.10-Linux-x86_64.sh└── workdir# example folder used as a volume in the running container編碼

該方法的關鍵步驟是:設計

步驟1:將大文件放在安裝程序/文件夾中。在此示例中,我具備大型Anaconda安裝程序文件Anaconda3-2019.10-Linux-x86_64.sh。若是克隆個人Git存儲庫,則找不到該文件,由於只有您(做爲容器映像建立者)須要此源文件。圖像的最終用戶沒有。下載安裝程序以跟隨示例。

步驟2:建立.dockerignore文件,並使其忽略installer /文件夾,以免Docker將全部大文件複製到構建上下文中。

步驟3:在終端中,cd進入tutorial2_docker_tricks /文件夾,並以./build_docker_image.sh執行構建腳本。

步驟4:在build_docker_image.sh中,啓動Python HTTP服務器以提供來自installer /文件夾的任何文件:

cdinstallerpython3-mhttp.server--bind10.0.2.158888 &cd..

步驟5:若是您想知道奇怪的Internet協議(IP)地址,我正在使用VirtualBox Linux VM,當我運行ifconfig時,10.0.2.15顯示爲以太網適配器的地址。該IP彷佛是VirtualBox使用的約定。若是設置不一樣,則須要更新此IP地址以匹配您的環境,而後更新build_docker_image.sh和install_anaconda.dockerfile。在此示例中,服務器的端口號設置爲8888。請注意,IP和端口號能夠做爲構建參數傳遞,可是爲了簡潔起見,我對其進行了硬編碼。

步驟6:因爲HTTP服務器設置爲在後臺運行,請 使用我發現的一種簡潔的方法使用kill -9命令在腳本末尾附近中止服務器:

kill-9`ps -ef | grep http.server | grep8888| awk'{print$2}'

步驟7:請注意, 在腳本的前面(啓動HTTP服務器以前)也使用了一樣的kill -9。一般,當我迭代可能會故意中斷的任何構建腳本時,這能夠確保每次HTTP服務器都乾淨啓動。

步驟8:在Dockerfile中,有一條RUN wget 指令,該指令從本地HTTP服務器下載Anaconda安裝程序。它還會刪除安裝程序文件並在安裝後進行清理。最重要的是,全部這些操做都在同一層中執行,以將圖像大小保持爲最小:

# install Anaconda by downloading the installer via the local http serverARGANACONDARUNwget --no-proxy http://10.0.2.15:8888/${ANACONDA} -O ~/anaconda.sh \&&/bin/bash ~/anaconda.sh -b -p /opt/conda \&&rm ~/anaconda.sh \&&rm -fr /var/lib/apt/lists/{apt,dpkg,cache,log} /tmp/* /var/tmp/*

步驟9:該文件運行包裝程序腳本anaconda.sh,並經過使用rm刪除大型文件來清理它們

步驟10:構建完成後,您應該看到圖像anaconda_ubuntu1804:v1。(您可使用docker image ls列出圖像。)

步驟11:您可使用終端中的./run_container.sh從該映像實例化一個容器,而該文件夾位於tutorial2_docker_tricks /文件夾中。您能夠驗證Anaconda已安裝:

$./run_container.sh$python --versionPython 3.7.5$conda --versionconda 4.8.0$anaconda --versionanaconda Command line client (version 1.7.2)

步驟12:您會注意到,run_container.sh設置了一個卷workdir。在此示例存儲庫中,文件夾workdir /爲空。這是我用來設置卷的約定,能夠在其中使個人Python和其餘腳本獨立於容器映像。

最小化容器圖像大小

每一個RUN命令等效於執行一個新的Shell,每一個RUN命令建立一個層。用單獨的RUN命令模仿安裝指令的幼稚方法最終可能會在一個或多個相互依賴的步驟中中斷。若是碰巧能夠正常工做,一般會產生較大的圖像。在一個RUN命令中連接多個安裝步驟,包括autoremove,autoclean和rm命令(以下面的示例所示)對於最小化每一個圖層的大小頗有用。根據所安裝的內容,可能不須要其中一些步驟。可是,因爲這些步驟花費的時間很少,所以,在調用apt-get的RUN命令結束時,我老是將它們投入適當的時間:

RUN apt-getupdate\    && DEBIAN_FRONTEND=noninteractive \apt-get-y--quiet --no-install-recommends install \# list of packages being installed go here \&& apt-get-y autoremove \&& apt-getclean autoclean \&& rm -fr /var/lib/apt/lists/{apt,dpkg,cache,log} /tmp/* /var/tmp/*

另外,請確保您有一個.dockerignore文件,以忽略不須要發送到Docker構建上下文的項目(例如前面示例中的Anaconda安裝程序文件)。

組織構建工具I/O

對於軟件構建系統,構建輸入和輸出(配置和調用工具的全部腳本)應在映像和最終運行的容器以外。容器自己應保持無狀態,以便不一樣的用戶能夠獲得相同的結果。在上一篇文章中,我對此進行了普遍的介紹,但因爲它對個人工做很是有用,所以我想強調一下。最好經過設置容器體積來訪問這些輸入和輸出。

我不得不使用一個容器映像,該映像以源代碼和大型預構建二進制文件的形式提供數據。做爲軟件開發人員,我但願在容器中編輯代碼。這是有問題的,由於容器默認狀況下是無狀態的:它們不被保存在容器內,由於它們被設計爲可拋棄的。

可是我一直在努力,在天天結束時,我中止了容器,而且必須當心不要將其卸下,由於必須保持狀態,以便我次日才能繼續工做。這種方法的缺點是,若是有不止一我的在項目上工做,那麼開發狀態就會有分歧。這種方法在開發人員中擁有相同的構建系統的價值在某種程度上已經喪失了。

以非root用戶身份生成輸出

I / O的重要方面涉及在容器中運行工具時生成的輸出文件的全部權。默認狀況下,因爲Docker以root身份運行,所以輸出文件將歸root擁有,這是不愉快的。您一般但願以非root用戶身份工做。生成構建輸出後更改全部權可使用腳原本完成,但這是一個額外且沒必要要的步驟。

最好儘早在Dockerfile中設置USER參數:

ARGUSERNAME# other commands...USER${USERNAME}

該USERNAME能夠做爲構建參數(在傳遞--build精氨酸)執行時搬運工圖像構建。您能夠在示例Dockerfile和相應的構建腳本中看到一個示例。

工具的某些部分可能還須要以非root用戶身份安裝。所以,若是要直接在Linux下手動安裝,則Dockerfile中的安裝順序可能須要與安裝順序不一樣。

非交互式安裝

交互性與容器自動化相反。我發現了

DEBIAN_FRONTEND=noninteractive apt-get -y --quiet --no-install-recommends

防止安裝程序打開對話框所必需的apt-get安裝說明選項(如上例所示)。注意,這些選項應做爲RUN指令的一部分使用。正如本常見問題解答所解釋的那樣,不該在Dockerfile中將DEBIAN_FRONTEND = noninteractive設置爲環境變量(ENV),由於它將由容器繼承。

記錄您的構建並運行輸出

調試構建失敗的緣由是一項常見的任務,而日誌則是完成此任務的好方法。使用Bash腳本中的tee工具保存在容器映像構建或容器運行會話期間發生的全部事情的TypeScript 。換句話說,將|&tee $ BASH_SOURCE.log添加到Docker 映像構建的末尾,而且在腳本中運行docker image運行命令。請參閱映像構建和容器運行腳本中的示例。

這種發球技術的做用是生成一個與Bash腳本同名的文件,但 附加一個.log擴展名,以便您知道其起源於哪一個腳本。運行腳本時,您在終端上看到的全部內容都將以相似的名稱記錄到該文件中。

這對於容器映像的用戶在沒法解決問題時向您報告問題特別有價值。您能夠要求他們向您發送日誌文件以幫助診斷問題。許多工具生成的輸出太多,以致於很容易使終端緩衝區的默認大小不堪重負。僅依靠終端的緩衝區容量來複制粘貼錯誤消息可能不足以診斷問題,由於之前的錯誤可能已經丟失。

我發現即便在容器映像構建腳本中,這也頗有用,尤爲是在使用上面討論的基於Python的HTTP服務器時。服務器在下載過程當中生成了不少行,一般使終端的緩衝區不堪重負。

優雅地處理代理

在個人工做環境中,須要代理訪問Internet才能下載RUN apt-get和RUN wget命令中的資源。一般從環境變量http_proxy或https_proxy推斷代理。雖然可使用ENV命令在Dockerfile中對此類代理設置進行硬編碼,可是直接將ENV用於代理存在多個問題。

若是您是惟一能夠構建容器的人,那麼也許可使用。

可是,其餘位置不一樣的代理設置的其餘人沒法使用Dockerfile。另外一個問題是IT部門可能會在某個時候更改代理,從而致使Dockerfile再也不起做用。此外,Dockerfile是一個精確的文檔,指定了配置控制的系統,而且每一項更改都將經過質量保證進行審查。

一種避免對代理進行硬編碼的簡單方法是,將本地代理設置做爲docker image build 命令中的build參數傳遞:

docker image build \

    --build-arg MY_PROXY=http://my_local_proxy.proxy.com:xx

而後,在Dockerfile中,根據build參數設置環境變量。在此處顯示的示例中,您仍然能夠設置默認的代理值,該值能夠被上面的build參數覆蓋:

# set a default proxyARGMY_PROXY=MY_PROXY=http://my_default_proxy.proxy.com:nn/ENV http_proxy=$MY_PROXYENV https_proxy=$MY_PROXY

總結

這些技術幫助我大大減小了建立容器映像並在出現錯誤時對其進行調試所需的時間。我會繼續尋找其餘最佳作法,以添加到個人列表中。但願以上技巧對您有所幫助。

相關文章
相關標籤/搜索