日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

Python3 - Dockerfile 最佳实践

發布時間:2023/12/16 python 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Python3 - Dockerfile 最佳实践 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

    • 一、一般準則和建議
      • 1.1 容器應該是短暫的
      • 1.2 建立上下文
      • 1.3 使用`.dockerignore`文件
      • 1.4 使用多階段構建
      • 1.5 避免安裝不必要的包
      • 1.6 一個容器只專注做一件事情
      • 1.7 最小化鏡像層數
      • 1.8 對多行參數排序
      • 1.9 構建緩存
    • 二、Dockerfile 指令
      • 2.1 FROM
      • 2.2 LABEL
      • 2.3 CMD
      • 2.4 EXPOSE
      • 2.5 ENV
      • 2.6 ADD 和 COPY
      • 2.7 ENTRYPOINT
      • 2.8 VOLUME
      • 2.9 USER
      • 2.10 WORKDIR
      • 2.11 ONBUILD
      • 2.12 官方倉庫示例

Docker官方關于Dockerfile最佳實踐原文鏈接地址:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/

Docker 可以通過從 Dockerfile 包含所有命令的文本文件中讀取指令自動構建鏡像,以便構建給定鏡像。

Dockerfiles 使用特定的格式并使用一組特定的指令。您可以在Dockerfile Reference頁面上了解基礎知識 。如果你是新手寫作Dockerfile,你應該從那里開始。

本文檔介紹了由 Docker,Inc. 和 Docker 社區推薦的用于構建高效鏡像的最佳實踐和方法。要查看許多實踐和建議,請查看Dockerfile for buildpack-deps。

一、一般準則和建議

1.1 容器應該是短暫的

通過 Dockerfile 構建的鏡像所啟動的容器應該盡可能短暫(生命周期短)。「短暫」意味著可以停止和銷毀容器,并且創建一個新容器并部署好所需的設置和配置工作量應該是極小的。我們可以查看下12 Factor(12要素)應用程序方法的進程部分,可以讓我們理解這種無狀態方式運行容器的動機。

1.2 建立上下文

當你發出一個docker build命令時,當前的工作目錄被稱為構建上下文。默認情況下,Dockerfile 就位于該路徑下,當然您也可以使用-f參數來指定不同的位置。無論 Dockerfile 在什么地方,當前目錄中的所有文件內容都將作為構建上下文發送到 Docker 守護進程中去。

下面是一個構建上下文的示例,為構建上下文創建一個目錄并 cd 放入其中。將“hello”寫入一個文本文件hello,然后并創建一個Dockerfile并運行cat。從構建上下文(.)中構建圖像:

mkdir myproject && cd myproject echo "hello" > hello echo -e "FROM busybox\nCOPY /hello /\nRUN cat /hello" > Dockerfile docker build -t helloapp:v1 .

現在移動 Dockerfile 和 hello 到不同的目錄,并建立了圖像的第二個版本(不依賴于緩存中的最后一個版本)。使用-f指向 Dockerfile 并指定構建上下文的目錄:

mkdir -p dockerfiles context mv Dockerfile dockerfiles && mv hello context docker build --no-cache -t helloapp:v2 -f dockerfiles/Dockerfile context

在構建的時候包含不需要的文件會導致更大的構建上下文和更大的鏡像大小。這會增加構建時間,拉取和推送鏡像的時間以及容器的運行時間大小。要查看您的構建環境有多大,請在構建您的系統時查找這樣的消息

Dockerfile: Sending build context to Docker daemon 4.096kB Sending build context to Docker daemon 2.607kB

1.3 使用.dockerignore文件

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

1.4 使用多階段構建

在 Docker 17.05 以上版本中,你可以使用 多階段構建 來減少所構建鏡像的大小。上一節課我們已經重點講解過了。

1.5 避免安裝不必要的包

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

1.6 一個容器只專注做一件事情

應該保證在一個容器中只運行一個進程。將多個應用解耦到不同容器中,保證了容器的橫向擴展和復用。例如一個 web 應用程序可能包含三個獨立的容器:web應用、數據庫、緩存,每個容器都是獨立的鏡像,分開運行。但這并不是說一個容器就只跑一個進程,因為有的程序可能會自行產生其他進程,比如 Celery 就可以有很多個工作進程。雖然“每個容器跑一個進程”是一條很好的法則,但這并不是一條硬性的規定。我們主要是希望一個容器只關注意見事情,盡量保持干凈和模塊化。

如果容器互相依賴,你可以使用Docker 容器網絡來把這些容器連接起來,我們前面已經跟大家講解過 Docker 的容器網絡模式了。

1.7 最小化鏡像層數

在 Docker 17.05 甚至更早 1.10之 前,盡量減少鏡像層數是非常重要的,不過現在的版本已經有了一定的改善了:

  • 在 1.10 以后,只有 RUN、COPY 和 ADD 指令會創建層,其他指令會創建臨時的中間鏡像,但是不會直接增加構建的鏡像大小了。
  • 上節課我們也講解到了 17.05 版本以后增加了多階段構建的支持,允許我們把需要的數據直接復制到最終的鏡像中,這就允許我們在中間階段包含一些工具或者調試信息了,而且不會增加最終的鏡像大小。

當然減少RUN、COPY、ADD的指令仍然是很有必要的,但是我們也需要在 Dockerfile 可讀性(也包括長期的可維護性)和減少層數之間做一個平衡。

1.8 對多行參數排序

只要有可能,就將多行參數按字母順序排序(比如要安裝多個包時)。這可以幫助你避免重復包含同一個包,更新包列表時也更容易,也更容易閱讀和審查。建議在反斜杠符號 \ 之前添加一個空格,可以增加可讀性。 下面是來自 buildpack-deps 鏡像的例子:

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

1.9 構建緩存

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

如果你想在構建的過程中使用了緩存,那么了解什么時候可以什么時候無法找到匹配的鏡像就很重要了,Docker中緩存遵循的基本規則如下:

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

二、Dockerfile 指令

下面是一些常用的 Dockerfile 指令,我們也分別來總結下,根據上面的建議和下面這些指令的合理使用,可以幫助我們編寫高效且易維護的 Dockerfile 文件。

2.1 FROM

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

2.2 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="2022-04-13" LABEL com.example.version.is-production=""

一個鏡像可以包含多個標簽,在 1.10 之前,建議將所有標簽合并為一條LABEL指令,以防止創建額外的層,但是現在這個不再是必須的了,以上內容也可以寫成下面這樣:

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

關于標簽可以接受的鍵值對,參考Understanding object labels。

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

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

  • 不要使用 RUN apt-get upgrade 或 dist-upgrade,如果基礎鏡像中的某個包過時了,你應該聯系它的維護者。如果你確定某個特定的包,比如 foo,需要升級,使用 apt-get install -y foo 就行,該指令會自動升級 foo 包。

  • 永遠將 RUN apt-get update 和 apt-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:14.04 RUN apt-get update RUN apt-get install -y curl

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

FROM ubuntu:14.04 RUN apt-get update RUN apt-get install -y curl nginx

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

使用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。

2.3 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 的工作方式十分熟悉。

2.4 EXPOSE

EXPOSE指令用于指定容器將要監聽的端口。因此,你應該為你的應用程序使用常見的端口。

例如,提供 Apache web 服務的鏡像應該使用 EXPOSE 80,而提供 MongoDB 服務的鏡像使用 EXPOSE 27017。

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

2.5 ENV

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

ENV 指令也可用于為你想要容器化的服務提供必要的環境變量,比如 Postgres 需要的 PGDATA。 最后,ENV 也能用于設置常見的版本號,比如下面的示例:

ENV PG_MAJOR 9.3 ENV PG_VERSION 9.3.4 RUN 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 指令來自動的改變容器中的軟件版本。

2.6 ADD 和 COPY

雖然ADD和COPY功能類似,但一般優先使用 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.txt COPY . /tmp/

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

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

ADD http://example.com/big.tar.xz /usr/src/things/ RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things RUN 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。

2.7 ENTRYPOINT

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

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

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

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

docker run s3cmd

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

docker run s3cmd ls s3://mybucket

這樣鏡像名可以當成命令行的參考。ENTRYPOINT 指令也可以結合一個輔助腳本使用,和前面命令行風格類似,即使啟動工具需要不止一個步驟。

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

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

注意:該腳本使用了 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

2.8 VOLUME

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

2.9 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 來回切換用戶。

2.10 WORKDIR

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

2.11 ONBUILD

格式:ONBUILD <其它指令>。 ONBUILD是一個特殊的指令,它后面跟的是其它指令,比如 RUN, COPY 等,而這些指令,在當前鏡像構建時并不會被執行。只有當以當前鏡像為基礎鏡像,去構建下一級鏡像的時候才會被執行。Dockerfile 中的其它指令都是為了定制當前鏡像而準備的,唯有 ONBUILD 是為了幫助別人定制自己而準備的。

假設我們要制作 Node.js 所寫的應用的鏡像。我們都知道 Node.js 使用 npm 進行包管理,所有依賴、配置、啟動信息等會放到 package.json 文件里。在拿到程序代碼后,需要先進行 npm install 才可以獲得所有需要的依賴。然后就可以通過 npm start 來啟動應用。因此,一般來說會這樣寫 Dockerfile:

FROM node:slim RUN mkdir /app WORKDIR /app COPY ./package.json /app RUN [ "npm", "install" ] COPY . /app/ CMD [ "npm", "start" ]

把這個 Dockerfile 放到 Node.js 項目的根目錄,構建好鏡像后,就可以直接拿來啟動容器運行。但是如果我們還有第二個 Node.js 項目也差不多呢?好吧,那就再把這個 Dockerfile 復制到第二個項目里。那如果有第三個項目呢?再復制么?文件的副本越多,版本控制就越困難,讓我們繼續看這樣的場景維護的問題:

如果第一個 Node.js 項目在開發過程中,發現這個 Dockerfile 里存在問題,比如敲錯字了、或者需要安裝額外的包,然后開發人員修復了這個 Dockerfile,再次構建,問題解決。第一個項目沒問題了,但是第二個項目呢?雖然最初 Dockerfile 是復制、粘貼自第一個項目的,但是并不會因為第一個項目修復了他們的 Dockerfile,而第二個項目的 Dockerfile 就會被自動修復。

那么我們可不可以做一個基礎鏡像,然后各個項目使用這個基礎鏡像呢?這樣基礎鏡像更新,各個項目不用同步 Dockerfile 的變化,重新構建后就繼承了基礎鏡像的更新?好吧,可以,讓我們看看這樣的結果。那么上面的這個 Dockerfile 就會變為:

FROM node:slim RUN mkdir /app WORKDIR /app CMD [ "npm", "start" ]

這里我們把項目相關的構建指令拿出來,放到子項目里去。假設這個基礎鏡像的名字為 my-node 的話,各個項目內的自己的 Dockerfile 就變為:

FROM my-node COPY ./package.json /app RUN [ "npm", "install" ] COPY . /app/

基礎鏡像變化后,各個項目都用這個 Dockerfile 重新構建鏡像,會繼承基礎鏡像的更新。

那么,問題解決了么?沒有。準確說,只解決了一半。如果這個 Dockerfile 里面有些東西需要調整呢?比如 npm install 都需要加一些參數,那怎么辦?這一行 RUN 是不可能放入基礎鏡像的,因為涉及到了當前項目的 ./package.json,難道又要一個個修改么?所以說,這樣制作基礎鏡像,只解決了原來的 Dockerfile 的前4條指令的變化問題,而后面三條指令的變化則完全沒辦法處理。

ONBUILD 可以解決這個問題。讓我們用 ONBUILD 重新寫一下基礎鏡像的 Dockerfile:

FROM node:slim RUN mkdir /app WORKDIR /app ONBUILD COPY ./package.json /app ONBUILD RUN [ "npm", "install" ] ONBUILD COPY . /app/ CMD [ "npm", "start" ]

這次我們回到原始的 Dockerfile,但是這次將項目相關的指令加上 ONBUILD,這樣在構建基礎鏡像的時候,這三行并不會被執行。然后各個項目的 Dockerfile 就變成了簡單地:

FROM my-node

是的,只有這么一行。當在各個項目目錄中,用這個只有一行的 Dockerfile 構建鏡像時,之前基礎鏡像的那三行 ONBUILD 就會開始執行,成功的將當前項目的代碼復制進鏡像、并且針對本項目執行 npm install,生成應用鏡像。

2.12 官方倉庫示例

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

好啦🌶🌶, Dockerfile的最佳使用就到這里, 喜歡就點個贊吧~ ???

總結

以上是生活随笔為你收集整理的Python3 - Dockerfile 最佳实践的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。