Dockerfile最佳實踐

Dockerfile最佳實踐

本文是原作者對 Docker 官方文檔中 Best practices for writing Dockerfiles 的理解與翻譯。

轉自:附錄四:Dockerfile 最佳實踐

一般性指南和建議

容器應該是短暫的

通過 Dockerfile 構建的鏡像所啟動的容器應該盡可能短暫(生命周期短)。「短暫」意味著可以停止和銷毀容器,并且創建一個新容器并部署好所需的設置和配置工作量應該是極小的。

使用 .dockerignore 文件

使用 Dockerfile 構建鏡像時最好是將 Dockerfile 放置在一個新建的空目錄下。然后將構建鏡像所需要的文件添加到該目錄中。為了提高構建鏡像的效率,你可以在目錄下新建一個 .dockerignore 文件來指定要忽略的文件和目錄。.dockerignore 文件的排除模式語法和 Git 的 .gitignore 文件相似。

使用多階段構建

Docker 17.05 以上版本中,你可以使用 多階段構建 來減少所構建鏡像的大小。

避免安裝不必要的包

為了降低復雜性、減少依賴、減小文件大小、節約構建時間,你應該避免安裝任何不必要的包。例如,不要在數據庫鏡像中包含一個文本編輯器。

一個容器只運行一個進程

應該保證在一個容器中只運行一個進程。將多個應用解耦到不同容器中,保證了容器的橫向擴展和復用。例如 web 應用應該包含三個容器:web應用、數據庫、緩存。

如果容器互相依賴,你可以使用 Docker 自定義網絡 來把這些容器連接起來。

鏡像層數盡可能少

你需要在 Dockerfile 可讀性(也包括長期的可維護性)和減少層數之間做一個平衡。

將多行參數排序

將多行參數按字母順序排序(比如要安裝多個包時)。這可以幫助你避免重復包含同一個包,更新包列表時也更容易。也便于 PRs 閱讀和審查。建議在反斜杠符號 \ 之前添加一個空格,以增加可讀性。

下面是來自 buildpack-deps 鏡像的例子:

RUN apt-get update && apt-get install -y \bzr \cvs \git \mercurial \subversion

構建緩存

在鏡像的構建過程中,Docker 會遍歷 Dockerfile 文件中的指令,然后按順序執行。在執行每條指令之前,Docker 都會在緩存中查找是否已經存在可重用的鏡像,如果有就使用現存的鏡像,不再重復創建。如果你不想在構建過程中使用緩存,你可以在 docker build 命令中使用 --no-cache=true 選項。

但是,如果你想在構建的過程中使用緩存,你得明白什么時候會,什么時候不會找到匹配的鏡像,遵循的基本規則如下:

  • 從一個基礎鏡像開始(FROM 指令指定),下一條指令將和該基礎鏡像的所有子鏡像進行匹配,檢查這些子鏡像被創建時使用的指令是否和被檢查的指令完全一樣。如果不是,則緩存失效。
  • 在大多數情況下,只需要簡單地對比 Dockerfile 中的指令和子鏡像。然而,有些指令需要更多的檢查和解釋。
  • 對于 ADDCOPY 指令,鏡像中對應文件的內容也會被檢查,每個文件都會計算出一個校驗和。文件的最后修改時間和最后訪問時間不會納入校驗。在緩存的查找過程中,會將這些校驗和和已存在鏡像中的文件校驗和進行對比。如果文件有任何改變,比如內容和元數據,則緩存失效。
  • 除了 ADDCOPY 指令,緩存匹配過程不會查看臨時容器中的文件來決定緩存是否匹配。例如,當執行完 RUN apt-get -y update 指令后,容器中一些文件被更新,但 Docker 不會檢查這些文件。這種情況下,只有指令字符串本身被用來匹配緩存。

一旦緩存失效,所有后續的 Dockerfile 指令都將產生新的鏡像,緩存不會被使用。

Dockerfile指令

下面針對 Dockerfile 中各種指令的最佳編寫方式給出建議。

FROM

盡可能使用當前官方倉庫作為你構建鏡像的基礎。推薦使用 Alpine 鏡像,因為它被嚴格控制并保持最小尺寸(目前小于 5 MB),但它仍然是一個完整的發行版。

LABEL

你可以給鏡像添加標簽來幫助組織鏡像、記錄許可信息、輔助自動化構建等。每個標簽一行,由 LABEL 開頭加上一個或多個標簽對。下面的示例展示了各種不同的可能格式。# 開頭的行是注釋內容。

注意:如果你的字符串中包含空格,必須將字符串放入引號中或者對空格使用轉義。如果字符串內容本身就包含引號,必須對引號使用轉義。

# Set one or more individual labels
LABEL com.example.version="0.0.1-beta"LABEL vendor="ACME Incorporated"LABEL com.example.release-date="2015-02-12"LABEL com.example.version.is-production=""

一個鏡像可以包含多個標簽,但建議將多個標簽放入到一個 LABEL 指令中。

# Set multiple labels at once, using line-continuation characters to break long lines
LABEL vendor=ACME\ Incorporated \com.example.is-beta= \com.example.is-production="" \com.example.version="0.0.1-beta" \com.example.release-date="2015-02-12"

關于標簽可以接受的鍵值對,參考 Understanding object labels。關于查詢標簽信息,參考 Managing labels on objects。

RUN

為了保持 Dockerfile 文件的可讀性,可理解性,以及可維護性,建議將長的或復雜的 RUN 指令用反斜杠 \ 分割成多行。

apt-get

RUN 指令最常見的用法是安裝包用的 apt-get。因為 RUN apt-get 指令會安裝包,所以有幾個問題需要注意。

不要使用 RUN apt-get upgradedist-upgrade,因為許多基礎鏡像中的「必須」包不會在一個非特權容器中升級。如果基礎鏡像中的某個包過時了,你應該聯系它的維護者。如果你確定某個特定的包,比如 foo,需要升級,使用 apt-get install -y foo 就行,該指令會自動升級 foo 包。

永遠將 RUN apt-get updateapt-get install 組合成一條 RUN 聲明,例如:

RUN apt-get update && apt-get install -y \package-bar \package-baz \package-foo

apt-get update 放在一條單獨的 RUN 聲明中會導致緩存問題以及后續的 apt-get install 失敗。比如,假設你有一個 Dockerfile 文件:

FROM ubuntu:18.04RUN apt-get updateRUN apt-get install -y curl

構建鏡像后,所有的層都在 Docker 的緩存中。假設你后來又修改了其中的 apt-get install 添加了一個包:

FROM ubuntu:18.04RUN apt-get updateRUN apt-get install -y curl nginx

Docker 發現修改后的 RUN apt-get update 指令和之前的完全一樣。所以,apt-get update 不會執行,而是使用之前的緩存鏡像。因為 apt-get update 沒有運行,后面的 apt-get install 可能安裝的是過時的 curlnginx 版本。

使用 RUN apt-get update && apt-get install -y 可以確保你的 Dockerfiles 每次安裝的都是包的最新的版本,而且這個過程不需要進一步的編碼或額外干預。這項技術叫作 cache busting。你也可以顯示指定一個包的版本號來達到 cache-busting,這就是所謂的固定版本,例如:

RUN apt-get update && apt-get install -y \package-bar \package-baz \package-foo=1.3.*

固定版本會迫使構建過程檢索特定的版本,而不管緩存中有什么。這項技術也可以減少因所需包中未預料到的變化而導致的失敗。

下面是一個 RUN 指令的示例模板,展示了所有關于 apt-get 的建議。

RUN apt-get update && apt-get install -y \aufs-tools \automake \build-essential \curl \dpkg-sig \libcap-dev \libsqlite3-dev \mercurial \reprepro \ruby1.9.1 \ruby1.9.1-dev \s3cmd=1.1.* \&& rm -rf /var/lib/apt/lists/*

其中 s3cmd 指令指定了一個版本號 1.1.*。如果之前的鏡像使用的是更舊的版本,指定新的版本會導致 apt-get udpate 緩存失效并確保安裝的是新版本。

另外,清理掉 apt 緩存 var/lib/apt/lists 可以減小鏡像大小。因為 RUN 指令的開頭為 apt-get udpate,包緩存總是會在 apt-get install 之前刷新。

注意:官方的 Debian 和 Ubuntu 鏡像會自動運行 apt-get clean,所以不需要顯式的調用 apt-get clean。

CMD

CMD 指令用于執行目標鏡像中包含的軟件,可以包含參數。CMD 大多數情況下都應該以 CMD ["executable", "param1", "param2"...] 的形式使用。因此,如果創建鏡像的目的是為了部署某個服務(比如 Apache),你可能會執行類似于 CMD ["apache2", "-DFOREGROUND"] 形式的命令。我們建議任何服務鏡像都使用這種形式的命令。

多數情況下,CMD 都需要一個交互式的 shell (bash, Python, perl 等),例如 CMD ["perl", "-de0"],或者 CMD ["PHP", "-a"]。使用這種形式意味著,當你執行類似 docker run -it python 時,你會進入一個準備好的 shell 中。CMD 應該在極少的情況下才能以 CMD ["param", "param"] 的形式與 ENTRYPOINT 協同使用,除非你和你的鏡像使用者都對 ENTRYPOINT 的工作方式十分熟悉。

EXPOSE

EXPOSE 指令用于指定容器將要監聽的端口。因此,你應該為你的應用程序使用常見的端口。例如,提供 Apache web 服務的鏡像應該使用 EXPOSE 80,而提供 MongoDB 服務的鏡像使用 EXPOSE 27017

對于外部訪問,用戶可以在執行 docker run 時使用一個標志來指示如何將指定的端口映射到所選擇的端口。

ENV

為了方便新程序運行,你可以使用 ENV 來為容器中安裝的程序更新 PATH 環境變量。例如使用 ENV PATH /usr/local/nginx/bin:$PATH 來確保 CMD ["nginx"] 能正確運行。

ENV 指令也可用于為你想要容器化的服務提供必要的環境變量,比如 Postgres 需要的 PGDATA

最后,ENV 也能用于設置常見的版本號,比如下面的示例:

ENV PG_MAJOR 9.3ENV PG_VERSION 9.3.4RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

類似于程序中的常量,這種方法可以讓你只需改變 ENV 指令來自動的改變容器中的軟件版本。

ADD 和 COPY

雖然 ADDCOPY 功能類似,但一般優先使用 COPY。因為它比 ADD 更透明。COPY 只支持簡單將本地文件拷貝到容器中,而 ADD 有一些并不明顯的功能(比如本地 tar 提取和遠程 URL 支持)。因此,ADD 的最佳用例是將本地 tar 文件自動提取到鏡像中,例如 ADD rootfs.tar.xz

如果你的 Dockerfile 有多個步驟需要使用上下文中不同的文件。單獨 COPY 每個文件,而不是一次性的 COPY 所有文件,這將保證每個步驟的構建緩存只在特定的文件變化時失效。例如:

COPY requirements.txt /tmp/RUN pip install --requirement /tmp/requirements.txtCOPY . /tmp/

如果將 COPY . /tmp/ 放置在 RUN 指令之前,只要 . 目錄中任何一個文件變化,都會導致后續指令的緩存失效。

為了讓鏡像盡量小,最好不要使用 ADD 指令從遠程 URL 獲取包,而是使用 curlwget。這樣你可以在文件提取完之后刪掉不再需要的文件來避免在鏡像中額外添加一層。比如盡量避免下面的用法:

ADD http://example.com/big.tar.xz /usr/src/things/RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/thingsRUN make -C /usr/src/things all

而是應該使用下面這種方法:

RUN mkdir -p /usr/src/things \&& curl -SL http://example.com/big.tar.xz \| tar -xJC /usr/src/things \&& make -C /usr/src/things all

上面使用的管道操作,所以沒有中間文件需要刪除。

對于其他不需要 ADD 的自動提取功能的文件或目錄,你應該使用 COPY

ENTRYPOINT

ENTRYPOINT 的最佳用處是設置鏡像的主命令,允許將鏡像當成命令本身來運行(用 CMD 提供默認選項)。

例如,下面的示例鏡像提供了命令行工具 s3cmd:

ENTRYPOINT ["s3cmd"]CMD ["--help"]

現在直接運行該鏡像創建的容器會顯示命令幫助:

docker run s3cmd

或者提供正確的參數來執行某個命令:

docker run s3cmd ls s3://mybucket

這樣鏡像名可以當成命令行的參考。

ENTRYPOINT 指令也可以結合一個輔助腳本使用,和前面命令行風格類似,即使啟動工具需要不止一個步驟。

例如,Postgres 官方鏡像使用下面的腳本作為 ENTRYPOINT

#!/bin/bash
set -eif [ "$1" = 'postgres' ]; thenchown -R postgres "$PGDATA"if [ -z "$(ls -A "$PGDATA")" ]; thengosu postgres initdbfiexec gosu postgres "$@"
fiexec "$@"

注意:該腳本使用了 Bash 的內置命令 exec,所以最后運行的進程就是容器的 PID 為 1 的進程。這樣,進程就可以接收到任何發送給容器的 Unix 信號了。

該輔助腳本被拷貝到容器,并在容器啟動時通過 ENTRYPOINT 執行:

COPY ./docker-entrypoint.sh /ENTRYPOINT ["/docker-entrypoint.sh"]

該腳本可以讓用戶用幾種不同的方式和 Postgres 交互。

你可以很簡單地啟動 Postgres

docker run postgres

也可以執行 Postgres 并傳遞參數:

$ docker run postgres postgres --help

最后,你還可以啟動另外一個完全不同的工具,比如 Bash

$ docker run --rm -it postgres bash

VOLUME

VOLUME 指令用于暴露任何數據庫存儲文件,配置文件,或容器創建的文件和目錄。強烈建議使用 VOLUME 來管理鏡像中的可變部分和用戶可以改變的部分。

USER

如果某個服務不需要特權執行,建議使用 USER 指令切換到非 root 用戶。先在 Dockerfile 中使用類似 RUN groupadd -r postgres && useradd -r -g postgres postgres 的指令創建用戶和用戶組。

注意:在鏡像中,用戶和用戶組每次被分配的 UID/GID 都是不確定的,下次重新構建鏡像時被分配到的 UID/GID 可能會不一樣。如果要依賴確定的 UID/GID,你應該顯式的指定一個 UID/GID。

你應該避免使用 sudo,因為它不可預期的 TTY 和信號轉發行為可能造成的問題比它能解決的問題還多。如果你真的需要和 sudo 類似的功能(例如,以 root 權限初始化某個守護進程,以非 root 權限執行它),你可以使用 gosu。

最后,為了減少層數和復雜度,避免頻繁地使用 USER 來回切換用戶。

WORKDIR

為了清晰性和可靠性,你應該總是在 WORKDIR 中使用絕對路徑。另外,你應該使用 WORKDIR 來替代類似于 RUN cd ... && do-something 的指令,后者難以閱讀、排錯和維護。

官方鏡像示例

這些官方鏡像的 Dockerfile 都是參考典范:https://github.com/docker-library/docs

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/532489.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532489.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532489.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux內存背后的那些神秘往事

Linux內存背后的那些神秘往事 作者:大白斯基(公眾號:后端研究所) 轉自:https://mp.weixin.qq.com/s/l_YdpyHht5Ayvrc7LFZNIA 前言 大家好,我的朋友們! CPU、IO、磁盤、內存可以說是影響計算機…

mmdeploy快速上手

mmdeploy快速上手 若要將使用 openmmlab 的框架(如mmdet、mmcls)等訓練的模型進行快速部署,同樣來自 openmmlab 的 mmdeploy 無疑是最合適的選擇,本文將簡單地完成一個 Faster RCNN 模型的部署。 配置 本文基于如下軟硬件配置&…

精簡CUDA教程——CUDA Driver API

精簡CUDA教程——CUDA Driver API tensorRT從零起步邁向高性能工業級部署(就業導向) 課程筆記,講師講的不錯,可以去看原視頻支持下。 Driver API概述 CUDA 的多級 API CUDA 的 API 有多級(下圖)&#xff…

CUDA編程入門極簡教程

CUDA編程入門極簡教程 轉自:CUDA編程入門極簡教程 作者:小小將 前言 2006年,NVIDIA公司發布了CUDA,CUDA是建立在NVIDIA的CPUs上的一個通用并行計算平臺和編程模型,基于CUDA編程可以利用GPUs的并行計算引擎來更加高效地…

精簡CUDA教程——CUDA Runtime API

精簡CUDA教程——CUDA Runtime API tensorRT從零起步邁向高性能工業級部署(就業導向) 課程筆記,講師講的不錯,可以去看原視頻支持下。 Runtime API 概述 環境 圖中可以看到,Runtime API 是基于 Driver API 之上開發的…

Python并發——concurrent.futures梳理

Python并發——concurrent.futures梳理 參考官方文檔: concurrent.futures — 啟動并行任務 Executor對象 class concurrent.funtures.Executor該抽象類是 ThreadPoolExecutor 和 ProcessPoolExecutor 的父類,提供異步執行調用方法。要通過它的子類調用…

TensorRT ONNX 基礎

TensorRT ONNX 基礎 tensorRT從零起步邁向高性能工業級部署(就業導向) 課程筆記,講師講的不錯,可以去看原視頻支持下。 概述 TensorRT 的核心在于對模型算子的優化(合并算子、利用當前 GPU 特性選擇特定的核函數等多種…

回文子串、回文子序列相關題目

回文子串、回文子序列相關題目 回文子串是要連續的,回文子序列可不是連續的。 516. 最長回文子序列 dp數組含義:dp[i][j]dp[i][j]dp[i][j] 表示子序列 s[i,j]s[i,j]s[i,j] 中的最長回文子序列的長度。 dp數組初始化:子序列長度為 1 時&am…

mmdetection tools工具梳理

mmdetection tools工具梳理 mmdetection 是一個非常好用的開源目標檢測框架,我們可以用它方便地訓練自己的目標檢測模型,mmdetection 項目倉庫提供許多實用的工具來實現幫助我們進行各種測試。本篇將梳理以下 mmdetection 項目倉庫 tools 目錄下的各種實…

TensorRT ONNX 基礎(續)

TensorRT ONNX 基礎(續) PyTorch正確導出ONNX 幾條推薦的原則,可以減少潛在的錯誤: 對于任何使用到 shape、size 返回值的參數時,例如 tensor.view(tensor.size(0), -1) 這類操作,避免直接使用 tensor.s…

frp實現內網穿透極簡教程

frp實現內網穿透極簡教程 本文是內網穿透極簡教程,為求簡潔,我們不介紹為什么內網穿透也不介紹其原理,這里假設各位讀者都已經明確的知道自己的目的,本文僅介紹如何安裝配置 frp 實現內網穿透。 簡單來說,內網穿透就…

圖像預處理之warpaffine與雙線性插值及其高性能實現

圖像預處理之warpaffine與雙線性插值及其高性能實現 視頻講解:https://www.bilibili.com/video/BV1ZU4y1A7EG 代碼Repo:https://github.com/shouxieai/tensorRT_Pro 本文為視頻講解的個人筆記。 warpaffine矩陣變換 對于坐標點的變換,我們通…

LeetCode-10 正則表達式匹配

LeetCode-10 正則表達式匹配 動態規劃 10. 正則表達式匹配 dp數組含義:dp[i][j]dp[i][j]dp[i][j] 表示 s[0:i?1]s[0:i-1]s[0:i?1] 能否被 p[0:j?1]p[0:j-1]p[0:j?1] 成功匹配。 狀態轉移方程 : 如果 s[i?1]p[j?1]s[i-1]p[j-1]s[i?1]p[j?1] …

shell if判斷和for循環常見寫法

shell if判斷和for循環常見寫法 轉自: Shell中for循環的幾個常用寫法 Shell中if 條件判斷總結 if常見寫法 一、if的基本語法: if [ command ];then符合該條件執行的語句 elif [ command ];then符合該條件執行的語句 else符合該條件執行的語句 fibash shell會按順序…

關于pytorch使用多個dataloader并使用zip和cycle來進行循環時出現的顯存泄漏的問題

關于pytorch使用多個dataloader并使用zip和cycle來進行循環時出現的顯存泄漏的問題 如果我們想要在 Pytorch 中同時迭代兩個 dataloader 來處理數據,會有兩種情況:一是我們按照較短的 dataloader 來迭代,長的 dataloader 超過的部分就丟棄掉…

neovim及coc.nvim自動補全初探

neovim及coc.nvim自動補全初探 安裝 # mac # 安裝 brew install neovim # 查看neovim安裝路徑 brew list nvim# ubuntu apt install neovim習慣了打開 vi/vim 的方式,可以用個 alias 在 ~/.zshrc 中設置一下: alias vi"nvim"插件 vim-plug…

sed 簡明教程

sed 簡明教程 轉自:https://coolshell.cn/articles/9104.html awk于1977年出生,今年36歲本命年,sed比awk大2-3歲,awk就像林妹妹,sed就是寶玉哥哥了。所以 林妹妹跳了個Topless,他的哥哥sed坐不住了&#xf…

awk 簡明教程

awk 簡明教程 轉自:https://coolshell.cn/articles/9070.html 有一些網友看了前兩天的《Linux下應該知道的技巧》希望我能教教他們用awk和sed,所以,出現了這篇文章。我估計這些80后的年輕朋友可能對awk/sed這類上古神器有點陌生了&#xff0c…

應該知道的LINUX技巧

應該知道的LINUX技巧 轉自:https://coolshell.cn/articles/8883.html 這篇文章來源于Quroa的一個問答《What are some time-saving tips that every Linux user should know?》—— Linux用戶有哪些應該知道的提高效率的技巧。我覺得挺好的,總結得比較好…

[深度][PyTorch] DDP系列第一篇:入門教程

[深度][PyTorch] DDP系列第一篇:入門教程 轉自:[原創][深度][PyTorch] DDP系列第一篇:入門教程 概覽 想要讓你的PyTorch神經網絡在多卡環境上跑得又快又好?那你definitely需要這一篇! No one knows DDP better than I…