概述
這篇文章主要是我日常工作中的制作鏡像的實(shí)踐, 同時(shí)結(jié)合我學(xué)習(xí)到的關(guān)于鏡像制作的相關(guān)文章總結(jié)出來的. 包括通用的容器最佳實(shí)踐, java, nginx, python 容器最佳實(shí)踐. 最佳實(shí)踐的目的一方面保證鏡像是可復(fù)用的, 提升 DevOps 效率, 另一方面是為了提高安全性. 希望對(duì)各位有所幫助.
本文分為四部分內(nèi)容, 分別是:
- 通用容器鏡像最佳實(shí)踐
- Java 容器鏡像最佳實(shí)踐
- NGINX 容器鏡像最佳實(shí)踐
- 以及 Python 容器最佳實(shí)踐
通用容器鏡像最佳實(shí)踐
使用 LABEL maintainer
LABEL maintainer
指令設(shè)置鏡像的作者姓名和郵箱字段糯笙。示例如下:
LABEL maintainer="cuikaidong@foxmail.com"
復(fù)用鏡像
建議盡量使用 FROM
語句復(fù)用合適的上游鏡像沸移。這可確保鏡像在更新時(shí)可以輕松從上游鏡像中獲取安全補(bǔ)丁捣染,而不必直接更新依賴項(xiàng)鼻弧。
此外唠梨,在 FROM
指令中使用標(biāo)簽 tag(例如 alpine:3.13
)很泊,使用戶能夠清楚地了解鏡像所基于的上游鏡像版本囱嫩。
:exclamation: 禁止使用 latest
tag以確保鏡像不會(huì)受到 latest
上游鏡像版本的重大更改的影響。
保持標(biāo)簽 TAGS 的兼容性
給自己的鏡像打標(biāo)簽時(shí)儿捧,注意保持向后兼容性荚坞。例如,如果制作了一個(gè)名為example
的鏡像纯命,并且它當(dāng)前為 1.0 版西剥,那么可以提供一個(gè) example:1
標(biāo)簽。后續(xù)要更新鏡像時(shí)亿汞,只要它繼續(xù)與原始鏡像兼容瞭空,就可以繼續(xù)標(biāo)記新鏡像為 example:1
,并且該 tag 的下游消費(fèi)者將能夠在不中斷的情況下獲得更新疗我。
如果后續(xù)發(fā)布了不兼容的更新咆畏,那么應(yīng)該切換到一個(gè)新 tag,例如 example:2
吴裤。那么下游消費(fèi)者可以按照自身實(shí)際情況升級(jí)到新版本旧找,而不會(huì)因?yàn)樾碌牟患嫒葭R像而造成事故。但是任何使用 example:latest
的下游消費(fèi)者都會(huì)承擔(dān)引入不兼容更改的風(fēng)險(xiǎn), 所以這也是前面我強(qiáng)烈建議不要使用 latest
tag 的原因.
避免多個(gè)進(jìn)程
建議不要在一個(gè)容器內(nèi)啟動(dòng)多個(gè)服務(wù)麦牺,例如 nginx 和 后端 app钮蛛。因?yàn)槿萜魇禽p量級(jí)的,可以很容易地通過 Docker Compose 或 Kubernetes 鏈接在一起剖膳。Kubernetes 或基于此的 TKE 容器平臺(tái)通過將相關(guān)鏡像調(diào)度到單個(gè) pod 中魏颓,輕松地對(duì)它們進(jìn)行集中管理。
在封裝腳本中使用 EXEC
指令
許多鏡像會(huì)通過在啟動(dòng)應(yīng)用程序之前使用封裝腳本進(jìn)行一些設(shè)置吱晒。如果您的鏡像使用這樣的腳本甸饱,那么該腳本最后應(yīng)該使用 exec
啟動(dòng)應(yīng)用程序,以便用應(yīng)用程序的進(jìn)程替換該腳本的進(jìn)程仑濒。如果不使用 exec
叹话,那么容器運(yùn)行時(shí)發(fā)送的信號(hào)(比如 TERM
或 SIGKILL
)將轉(zhuǎn)到封裝腳本,而不是應(yīng)用程序的進(jìn)程墩瞳。這不是我們所期望的驼壶。
清除臨時(shí)文件
應(yīng)刪除在生成過程中創(chuàng)建的所有臨時(shí)文件。這還包括使用 ADD
指令添加的任何文件喉酌。例如辅柴,?? 我們強(qiáng)烈建議您在執(zhí)行apt-get install
操作之后運(yùn)行 rm -rf /var/lib/apt/lists/*
命令箩溃。
通過如下創(chuàng)建 RUN
語句,可以防止 apt-get
緩存存儲(chǔ)在鏡像層中:
RUN apt-get update && apt-get install -y \
curl \
s3cmd=1.1.* \
&& rm -rf /var/lib/apt/lists/*
請(qǐng)注意碌嘀,如果您改為:
RUN apt-get install curl -y
RUN apt-get install s3cmd -y && rm -rf /var/lib/apt/lists/*
那么,第一個(gè) apt-get
調(diào)用會(huì)在這一層 (image layer) 中留下額外的文件歪架,并且在稍后運(yùn)行rm -rf ...
操作時(shí)股冗,無法刪除這些文件。額外的文件在最終鏡像中不可見和蚪,但它們會(huì)占用空間止状。
另外,在一條 RUN
語句中執(zhí)行多個(gè)命令可以減少鏡像中的層數(shù)攒霹,從而縮短下載和安裝時(shí)間怯疤。
yum
的例子如下:
RUN yum -y install curl && yum -y install s3cmd && yum clean all -y
:notebook: 備注:
RUN
、COPY
和ADD
步驟會(huì)創(chuàng)建鏡像層催束。- 每個(gè)層包含與前一層的差異項(xiàng)集峦。
- 鏡像層會(huì)增加最終鏡像的大小。
:notebook: 提示:
- 將相關(guān)命令(
apt-get install
)放入同一RUN
步驟抠刺。- 在同一
RUN
步驟中刪除創(chuàng)建的文件塔淤。- 避免使用
apt-get upgrade
或yum upgrade all
,因?yàn)樗鼤?huì)把所有包升級(jí)到最新版本.
按正確的順序放置指令
容器構(gòu)建過程中, 讀取 dockerfile
并從上到下運(yùn)行指令速妖。成功執(zhí)行的每一條指令都會(huì)創(chuàng)建一個(gè)層高蜂,在下次構(gòu)建此鏡像或另一個(gè)鏡像時(shí)可以重用該層。建議在 Dockerfile 的頂部放置很少更改的指令罕容。這樣做可以確保同一鏡像的下一次構(gòu)建速度非潮感簦快,因?yàn)樯蠈痈牡木彺孢€在, 可以復(fù)用锦秒。
例如露泊,如果正在處理一個(gè) dockerfile
,其中包含一個(gè)用于安裝正在迭代的文件的 ADD
指令脂崔,以及一個(gè)用于 apt-get install
包的 RUN
指令滤淳,那么最好將ADD
命令放在最后:
FROM alpine:3.11
RUN apt-get -y install curl && rm -rf /var/lib/apt/lists/*
ADD app /app
這樣,每次編輯 app 并重新運(yùn)行 docker build
時(shí)砌左,系統(tǒng)都會(huì)為 apt-get
命令復(fù)用緩存層脖咐,并且只為 ADD
操作生成新層。
如果反過來, dockerfile 如下:
FROM alpine:3.11
ADD app /app
RUN apt-get -y install curl && rm -rf /var/lib/apt/lists/*
那么汇歹,每次更改 app 然后再次運(yùn)行 docker build
時(shí)屁擅,ADD
操作都會(huì)使鏡像層的緩存失效,因此必須重新運(yùn)行 apt-get
操作产弹。
標(biāo)記重要端口
EXPOSE
指令使容器中的端口對(duì)主機(jī)系統(tǒng)和其他容器可用派歌。雖然可以指定使用 docker run -p
調(diào)用公開端口弯囊,但在dockerfile
中使用 EXPOSE
指令可以通過顯式聲明應(yīng)用程序需要運(yùn)行的端口,使人和應(yīng)用程序更容易使用您的鏡像:
- 暴露的端口將顯示在
docker ps
下胶果。 -
docker inspect
返回的鏡像的元數(shù)據(jù)中也會(huì)顯示暴露的端口匾嘱。 - 當(dāng)將一個(gè)容器鏈接到另一個(gè)容器時(shí),會(huì)鏈接暴露的端口早抠。
設(shè)置環(huán)境變量
??? 使用 ENV
指令設(shè)置環(huán)境變量是很好的實(shí)踐霎烙。一個(gè)例子是設(shè)置項(xiàng)目的版本。這使得人們?cè)诓徊榭?dockerfile
的情況下很容易找到版本蕊连。另一個(gè)例子是在公布一條可以被另一個(gè)進(jìn)程使用的路徑悬垃,比如 JAVA_HOME
.
避免默認(rèn)密碼
:exclamation: 最好避免設(shè)置默認(rèn)密碼。許多人會(huì)擴(kuò)展基礎(chǔ)鏡像甘苍,但是忘記刪除或更改默認(rèn)密碼尝蠕。如果為生產(chǎn)中的用戶分配了一個(gè)眾所周知的密碼,這可能會(huì)導(dǎo)致安全問題载庭。??? 應(yīng)該使用環(huán)境變量, secret 或其他 K8s 加密方案來配置密碼看彼。
如果確實(shí)選擇設(shè)置默認(rèn)密碼,請(qǐng)確保在容器啟動(dòng)時(shí)顯示適當(dāng)?shù)木嫦⒚两荨O?yīng)該通知用戶默認(rèn)密碼的值闲昭,并說明如何更改,例如設(shè)置什么環(huán)境變量靡挥。
禁用SSHD
:exclamation: 禁止在鏡像中運(yùn)行 sshd序矩。可以使用 docker exec
命令訪問本地主機(jī)上運(yùn)行的容器跋破◆さ恚或者,可以使用 kubectl exec
命令來訪問在 K8s 或 TKE 容器平臺(tái)上運(yùn)行的容器毒返。在鏡像中安裝和運(yùn)行sshd 會(huì)遭受潛在攻擊, 需要額外的安全補(bǔ)丁修復(fù)租幕。
將 VOLUMES(卷) 用于持久數(shù)據(jù)
鏡像應(yīng)使用卷來存儲(chǔ)持久數(shù)據(jù)。這樣拧簸,Kubernetes 或 TKE 將網(wǎng)絡(luò)存儲(chǔ)掛載到運(yùn)行容器的節(jié)點(diǎn)劲绪,如果容器移動(dòng)到新節(jié)點(diǎn),則存儲(chǔ)將重新連接到該節(jié)點(diǎn)盆赤。通過將卷用于所有持久化存儲(chǔ)的需求贾富,即使重新啟動(dòng)或移動(dòng)容器,也會(huì)保留持久化內(nèi)容牺六。如果鏡像將數(shù)據(jù)寫入容器內(nèi)的任意位置颤枪,則可能數(shù)據(jù)會(huì)丟失。
此外淑际,在 Dockerfile 中顯式定義卷使鏡像的消費(fèi)者很容易理解在運(yùn)行鏡像時(shí)必須定義哪些卷畏纲。
有關(guān)如何在 K8s 或 TKE 容器平臺(tái)中使用卷的更多信息扇住,請(qǐng)參閱 Kubernetes documentation.
使用非 root 用戶運(yùn)行容器進(jìn)程
默認(rèn)情況下,Docker 用容器內(nèi)部的 root 運(yùn)行容器進(jìn)程盗胀。這是一個(gè)不安全的做法艘蹋,因?yàn)槿绻粽咴O(shè)法突破容器,他們可以獲得對(duì)Docker 宿主機(jī)的 root 權(quán)限票灰。
:exclamation: 注意:
如果容器中是 root簿训,那么逃逸出來就是主機(jī)上的 root。
使用多階段構(gòu)建
利用多階段構(gòu)建來創(chuàng)建一個(gè)用于構(gòu)建工件的臨時(shí)鏡像米间,該工件將被復(fù)制到生產(chǎn)鏡像上。臨時(shí)構(gòu)建鏡像將與與該映像關(guān)聯(lián)的原始文件膘侮、文件夾和依賴項(xiàng)一起丟棄携栋。
這會(huì)產(chǎn)生了一個(gè)精益竿开,生產(chǎn)就緒的鏡像。
一個(gè)用例是使用非Alpine基礎(chǔ)鏡像來安裝需要編譯的依賴項(xiàng)。然后可以將 wheel 文件復(fù)制到最終鏡像乒验。
Python 示例如下:
FROM python:3.6 as base
COPY requirements.txt /
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /wheels -r requirements.txt
FROM python:3.6-alpine
COPY --from=base /wheels /wheels
COPY --from=base requirements.txt .
RUN pip install --no-cache /wheels/* # flask, gunicorn, pycrypto
WORKDIR /app
COPY . /app
使用前大小: 705MB, 使用后大小: 103MB
:exclamation: 禁止在容器中存儲(chǔ)機(jī)密信息
禁止在容器中存儲(chǔ)機(jī)密信息, 包括:
- 敏感信息
- 數(shù)據(jù)庫(kù)憑據(jù)
- ssh 密鑰
- 用戶名和密碼
- api 令牌等
以上信息可以通過:
- 環(huán)境變量 ENV 傳遞
- 卷 VOLUME 掛載
避免將文件放入 /tmp
中
對(duì)于一些應(yīng)用程序(如: python 的 gunicorn ), 會(huì)將某些緩存信息或心跳檢測(cè)信息寫入 /tmp
中, 這對(duì) /tmp
的讀寫性能有較高要求, 如果 /tmp
掛載的是普通磁盤, 可能導(dǎo)致嚴(yán)重的性能問題.
在某些Linux發(fā)行版中,/tmp
通過 tmpfs
文件系統(tǒng)存儲(chǔ)在內(nèi)存中曙博。但是畔塔,Docker容器默認(rèn)情況下沒有為 /tmp
打開 tmpfs
:
$ docker run --rm -it ubuntu:18.04 df
Filesystem 1K-blocks Used Available Use% Mounted on
overlay 31263648 25656756 3995732 87% /
tmpfs 65536 0 65536 0% /dev
tmpfs 4026608 0 4026608 0% /sys/fs/cgroup
/dev/mapper/root 31263648 25656756 3995732 87% /etc/hosts
shm 65536 0 65536 0% /dev/shm
如上所示,/tmp
正在使用標(biāo)準(zhǔn)的 Docker overlay 文件系統(tǒng):它由普通的塊設(shè)備或計(jì)算機(jī)正在使用的硬盤驅(qū)動(dòng)器支持所袁。這可能導(dǎo)致性能問題 .
針對(duì)這類應(yīng)用程序, 通用的解決方案是將其臨時(shí)文件存儲(chǔ)在其他地方盏档。特別是,如果你看上面你會(huì)看到 /dev/shm
使用 shm
文件系統(tǒng)共享內(nèi)存和內(nèi)存文件系統(tǒng)燥爷。所以你需要做的就是使用 /dev/shm 而不是 /tmp
使用 Alpine Linux基礎(chǔ)鏡像 (謹(jǐn)慎采納)
使用基于Alpine Linux 的鏡像蜈亩,因?yàn)樗惶峁┍匾陌? 生成的鏡像更小。
收益有:
- 減少了主機(jī)成本前翎,因?yàn)槭褂玫拇疟P空間更少
- 更快的構(gòu)建稚配、下載和運(yùn)行時(shí)間
- 更安全(因?yàn)榘蛶?kù)更少)
- 更快的部署
示例如下:
FROM python:3.6-alpine
WORKDIR /app
COPY requirements.txt /
RUN pip install -r /requirements.txt # flask and gunicorn
COPY . /app
使用前大小: 702MB, 使用后大小: 102MB
:exclamation: 注意:
謹(jǐn)慎使用 alpine, 我看到過使用 Alpine Linux 產(chǎn)生的一大堆問題,因?yàn)樗⒃?musl libc 之上港华,而不是大多數(shù) Linux 發(fā)行版使用的GNU libc(glibc)道川。問題有: 日期時(shí)間格式的錯(cuò)誤, 由于堆棧較小導(dǎo)致的崩潰等等。
使用 .dockerignore
排除無關(guān)文件
要排除與構(gòu)建無關(guān)的文件立宜,請(qǐng)使用 .dockerignore
文件冒萄。此文件支持與 .gitignore
文件類似的排除模式。具體請(qǐng)參閱 .dockerignore文件赘理。
不要安裝不必要的包
為了降低復(fù)雜性宦言,依賴性,文件大小和構(gòu)建時(shí)間商模,請(qǐng)避免安裝額外的或不必要的應(yīng)用程序包奠旺。例如蜘澜,不需要在數(shù)據(jù)庫(kù)鏡像中包含文本編輯器。
解耦應(yīng)用程序
每個(gè)容器應(yīng)該只有一個(gè)進(jìn)程响疚。將應(yīng)用程序分離到多個(gè)容器中可以更容易地水平擴(kuò)展和重用容器鄙信。例如,Web 應(yīng)用程序堆棧 LNMP 可能包含三個(gè)獨(dú)立的容器忿晕,每個(gè)容器都有自己獨(dú)特的映像装诡,以分離的方式管理 Web 服務(wù)器, 應(yīng)用程序,緩存數(shù)據(jù)庫(kù)和數(shù)據(jù)庫(kù)践盼。
將每個(gè)容器限制為一個(gè)進(jìn)程是一個(gè)很好的經(jīng)驗(yàn)法則鸦采,但它不是一個(gè)硬性規(guī)則。例如咕幻,可以 使用 init 進(jìn)程生成容器 渔伯,另外某些程序可能會(huì)自行生成其他子進(jìn)程 (如: nginx)。
根據(jù)自己的經(jīng)驗(yàn)進(jìn)行判斷肄程,盡可能保持容器簡(jiǎn)潔和模塊化锣吼。如果容器彼此依賴,則可以使用 容器網(wǎng)絡(luò) 或 K8s Sidecar 來確保這些容器可以進(jìn)行通信蓝厌。
對(duì)多行參數(shù)進(jìn)行排序
建議通過按字母順序排序多行參數(shù)來方便后續(xù)的更改玄叠。這有助于避免重復(fù)包并使列表更容易更新。這也使 PR 更容易閱讀和審查拓提。在反斜杠(\
)之前添加空格也有幫助读恃。
下面是來自一個(gè)示例openjdk
圖像:
...
apt-get update; \
apt-get install -y --no-install-recommends \
dirmngr \
gnupg \
wget \
; \
rm -rf /var/lib/apt/lists/*; \
...
JAVA 容器鏡像最佳實(shí)踐
IDE插件推薦
- idea - 轉(zhuǎn)到“首選項(xiàng)”、“插件”崎苗、“安裝JetBrains插件…”狐粱,搜索“Docker”并單擊“安裝”
- Eclipse
:notebook: 備注:
設(shè)置內(nèi)存限制相關(guān)參數(shù)
:notebook: 備注:
指定
-Xmx=1g
將告訴 JVM 分配一個(gè) 1 GB 堆, 但是它并沒有告訴 JVM 將其整個(gè)內(nèi)存使用量限制為 1 GB。除了對(duì)內(nèi)存, 還會(huì)有 card tables胆数、code cache 和各種其他堆外數(shù)據(jù)結(jié)構(gòu)肌蜻。用于指定總內(nèi)存使用量的參數(shù)是-XX:MaxRAM
。請(qǐng)注意必尼,使用-XX:MaxRam=500m
時(shí)蒋搜,堆將大約為 250 MB。
JVM 在歷史上查找/proc
以確定有多少可用內(nèi)存判莉,然后根據(jù)該值設(shè)置其堆大小豆挽。不幸的是,像 docker 這樣的容器在/proc
中不提供特定于容器的信息券盅。2017年之后有一個(gè)補(bǔ)丁帮哈,提供了一個(gè) -XX:+UseCGroupMemoryLimitForHeap
命令行參數(shù),它告訴 jvm 查找 /sys/fs/cgroup/memory/memory.limit_in_bytes
锰镀,以確定有多少可用內(nèi)存娘侍。如果這個(gè)補(bǔ)丁在運(yùn)行的 OpenJDK 版本中不可用咖刃,可以通過顯式設(shè)置 -XX:MaxRAM=n
來代替。
總結(jié), 設(shè)置內(nèi)存限制相關(guān)參數(shù):
- Openjdk 8 的新版本, 添加:
-XX:+UseCGroupMemoryLimitForHeap
- 如果沒有上邊的參數(shù), 設(shè)置:
-XX:MaxRAM=n
- 建議設(shè)置 JVM Heap 約為 memory limit 的 50% - 80%
- 建議設(shè)置 JVM MaxRAM 接近 K8s pod 的 memory limit
設(shè)置GC策略
OpenJDK8 中有一個(gè)補(bǔ)丁憾筏,它將使用 cgroup 可用的信息來計(jì)算適當(dāng)數(shù)量的并行 GC 線程嚎杨。但是,如果這個(gè)補(bǔ)丁在您用的 OpenJDK 版本中不可用氧腰,假設(shè)您的容器宿主機(jī)有 8 個(gè) CPU, 但是容器中 CPU limit 為 2 個(gè) CPU, 那么您最終可能會(huì)得到 8 個(gè)并行 GC 線程枫浙。解決方法是顯式指定并行GC線程的數(shù)量: -XX:ParallelGCThreads=2
。
如果您的容器中 cpu limit 設(shè)置為只有一個(gè) CPU古拴,強(qiáng)烈建議使用 -XX:+UseSerialGC
運(yùn)行箩帚,來完全避免并行GC。
JAVA 啟動(dòng)階段調(diào)優(yōu)
JAVA 程序都有一個(gè)啟動(dòng)階段黄痪,它需要大量的堆膏潮,之后可能會(huì)進(jìn)入一個(gè)安靜的循環(huán)階段,在這個(gè)階段它就不需要太多的堆满力。
對(duì)于串行 GC 策略, 您可以通過配置使它更具侵略性, 如: -XX:MinHeapFreeRatio=20
(當(dāng)堆占用率大于 80%,此值默認(rèn)增大轻纪。)
XX:MaxHeapFreeRatio=40
(堆占用率小于60%時(shí)收縮)
對(duì)于并行 - parallel GC策略, 推薦如下配置:
-XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90
JAVA 容器全局建議資源請(qǐng)求和資源限制
JAVA 程序都有一個(gè)啟動(dòng)階段油额,啟動(dòng)階段也會(huì)大量消耗 CPU, CPU 使用越多, 啟動(dòng)階段越短.
下面是一個(gè)表,總結(jié)了不同CPU限制下的 spring boot 示例應(yīng)用啟動(dòng)時(shí)間(CPU 以 millicore 為單位):
- 500m - 80 seconds
- 1000m - 35 seconds
- 1500m - 22 seconds
- 2500m - 17 seconds
- 3000m - 12 seconds
根據(jù)以上情況, K8s 或 TKE 容器平臺(tái)管理員可以考慮對(duì) JAVA 容器做如下限制:
- 使用CPU requests, 不設(shè)置 cpu limit
- 使用 memory limit 且等于 memory request
示例如下:
resources:
requests:
memory: "1024Mi"
cpu: "500m"
limits:
memory: "1024Mi"
使用 ExitOnOutOfMemoryError
而非 HeapDumpOnOutOfMemoryError
(謹(jǐn)慎評(píng)估)
我們都知道, 在傳統(tǒng)的虛擬機(jī)上部署的 Java 實(shí)例. 為了更好地分析問題, 一般都是要加上: -XX:+HeapDumpOnOutOfMemoryError
這個(gè)參數(shù)的, 加這個(gè)參數(shù)后, 如果遇到內(nèi)存溢出, 就會(huì)自動(dòng)生成 HeapDump , 后面我們可以拿到這個(gè) HeapDump 來更精確地分析問題.
但是, 容器技術(shù)的應(yīng)用, 帶來了一些不同, 在使用容器平臺(tái)后, 我們更傾向于:
- 遇到故障快速失敗
- 遇到故障快速恢復(fù)
- 盡量做到用戶對(duì)故障"無感知"
所以, 針對(duì) Java 應(yīng)用容器, 我們也要優(yōu)化以滿足這種需求, 以 OutOfMemoryError
故障為例:
- 遇到故障快速失敗, 即盡可能"快速退出, 快速終結(jié)"
-XX:+ExitOnOutOfMemoryError
就正好滿足這種需求:
傳遞此參數(shù)時(shí)刻帚,拋出 OutOfMemoryError
時(shí) JVM 將立即退出潦嘶。 如果您想盡快終止異常應(yīng)用程序,則可以傳遞此參數(shù)崇众。
NGINX 容器鏡像最佳實(shí)踐
如果您直接在基礎(chǔ)硬件或虛擬機(jī)上運(yùn)行 NGINX掂僵,通常需要一個(gè) NGINX 實(shí)例來使用所有可用的CPU。由于NGINX 是多進(jìn)程模式顷歌,通常你會(huì)啟動(dòng)多個(gè) worker processes锰蓬,每個(gè)工作進(jìn)程都是不同的進(jìn)程,以便利用所有CPU眯漩。
但是芹扭,在容器中運(yùn)行時(shí),如果將 worker_processes
設(shè)置為 auto
, 會(huì)根據(jù)容器所在宿主機(jī)的 CPU 核數(shù)啟動(dòng)相應(yīng)進(jìn)程數(shù). 比如, 我之前在物理機(jī)上運(yùn)行 NGINX 容器使用 auto
參數(shù), 盡管 CPU limit 設(shè)置為2, 但是 NGINX 會(huì)啟動(dòng) 64 (物理機(jī) CPU 數(shù)) 個(gè)進(jìn)程.
因此赦抖,???建議根據(jù) 實(shí)際需求或 CPU limit 的設(shè)置配置 nginx.conf
, 如下:
worker_processes 2;
Python 容器鏡像最佳實(shí)踐
??Warning:
隨著時(shí)間的遷移, 以及實(shí)踐的深入, 最佳實(shí)踐也在發(fā)生著變化, 以下部分內(nèi)容已經(jīng)不能作為 Python 容器鏡像的最佳實(shí)踐.
最新的 Python 容器鏡像最佳實(shí)踐可以參見這篇文章: https://EWhisper.cn/posts/25776/
示例如下:
# 基于官方基礎(chǔ)鏡像
FROM python:3.7-alpine
# 設(shè)置工作目錄
WORKDIR /app
# 設(shè)置環(huán)境變量
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV DEBUG 0
# install psycopg2
RUN apk update \
&& apk add --virtual build-deps gcc musl-dev python3-dev \
&& apk add postgresql-dev \
&& pip install psycopg2 \
&& apk del build-deps
# install dependencies
COPY ./requirements.txt .
RUN pip install -r requirements.txt
# copy project
COPY . .
# 切換到非 root 用戶
RUN adduser -D myuser
USER myuser
# run gunicorn
CMD gunicorn hello_django.wsgi:application --bind 0.0.0.0:$PORT
△ 示例 Dockerfile
IDE插件推薦
- PyCharm - 同Idea
- VSCode - Visual Studio Code Remote - Containers 插件
建議配置的環(huán)境變量
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV DEBUG 0
-
PYTHONDONTWRITEBYTECODE
: 防止 python 將 pyc 文件寫入硬盤 -
PYTHONUNBUFFERED
: 防止 python 緩沖 stdout 和 stderr -
DEBUG
: 方便根據(jù)環(huán)境類型的不同(測(cè)試/生產(chǎn))調(diào)整是否開啟debug
安裝數(shù)據(jù)庫(kù)驅(qū)動(dòng)包的方法
以 postgredb 的驅(qū)動(dòng) psycopg2 為例, 可能需要安裝額外的基礎(chǔ)組件:
# install psycopg2
RUN apk update \
&& apk add --virtual build-deps gcc musl-dev python3-dev \
&& apk add postgresql-dev \
&& pip install psycopg2 \
&& apk del build-deps
參考鏈接
- Docker documentation - Best practices for writing Dockerfiles
- “Docker和PID 1 zombie reaping問題”
- “揭開init系統(tǒng)(PID 1)的神秘面紗”
- Blog article - Resource management in Docker
- Docker documentation - Runtime Metrics
- Blog article - Memory inside Linux containers
- Docker documentation - Docker basics
- Docker documentation - Dockerfile reference
- Docker documentation - 自定義元數(shù)據(jù)舱卡。
- testdriven.io - Deploying Django to Heroku With Docker
- testdriven.io - Dockerizing Django with Postgres, Gunicorn, and Nginx
- dockercon-2018 - Docker for Python Developers
- Docker documentation - 多階段構(gòu)建
- Red Hat Developer - OpenJDK and Containers
- Java Application Optimization on Kubernetes on the Example of a Spring Boot Microservice
- Python Speed - Faster Docker builds with pipenv, poetry, or pip-tools
- Python Speed - Configuring Gunicorn for Docker
- Docker documentation - Docker and Eclipse
- Docker documentation - Docker and IntelliJ IDEA
- Developing inside a Container
2個(gè)問題
- 您是否有制作其他語言鏡像的最佳實(shí)踐呢?
- 您是否嘗試通過 GraalVM 制作 原生可執(zhí)行 Java 鏡像? 體驗(yàn)如何?
三人行, 必有我?guī)? 知識(shí)共享, 天下為公. 本文由東風(fēng)微鳴技術(shù)博客 EWhisper.cn 編寫.