基于Dockerfile構(gòu)建容器鏡像的最佳實(shí)踐
目錄
1、背景概述
2、為什么鏡像會(huì)這么大
2.1 基礎(chǔ)鏡像過(guò)大
2.2 基礎(chǔ)鏡像過(guò)大,而且找不到了
2.3 .git 目錄(非必要目錄)
2.4 Dockerfile 本身有其他問(wèn)題
3、Dockerfile 如何優(yōu)化
3.1 從哪里入手
3.2 方案
3.3 樣例
4、除了這些優(yōu)化還可以做什么
4.1 設(shè)置字符集
4.2 時(shí)區(qū)校正
4.3 進(jìn)程管理
4.4 降權(quán)啟動(dòng)
4.5 底層庫(kù)依賴
5、小結(jié)

1、背景概述
容器鏡像是容器化落地轉(zhuǎn)型的第一步,總結(jié)幾點(diǎn)需要做鏡像優(yōu)化的原因
隨著應(yīng)用容器化部署的大規(guī)模遷移以及版本迭代的加快,優(yōu)化基礎(chǔ)設(shè)施之docker鏡像主要有以下目的
縮短部署時(shí)的鏡像下載時(shí)間
提升安全性,減少可供攻擊的目標(biāo)
減少故障恢復(fù)時(shí)間
節(jié)省存儲(chǔ)開銷
2、為什么鏡像會(huì)這么大
這里簡(jiǎn)要分析了幾個(gè)典型的Repo,總結(jié)了現(xiàn)有Docker鏡像較大的幾個(gè)原因
2.1 基礎(chǔ)鏡像過(guò)大
舉例:倉(cāng)庫(kù)A,制作出來(lái)的鏡像大小9.67GB
用到的基礎(chǔ)鏡像:鏡像大小8.72GB
逆向分析了一下,為啥基礎(chǔ)鏡像還這么大?結(jié)果就不用多說(shuō)了 0.0
2.2 基礎(chǔ)鏡像過(guò)大,而且找不到了
舉例:倉(cāng)庫(kù)B,制作出來(lái)的鏡像大小22.7GB
用到的基礎(chǔ)鏡像:404 not found,沒錯(cuò),找不到了 0.0
2.3 .git 目錄(非必要目錄)
這個(gè)問(wèn)題更多內(nèi)容可以參考我之前的文章?Git目錄為什么這么大?[1]
舉例:倉(cāng)庫(kù)C,代碼大小795MB
其中.git目錄大小225MB ,dockerfile中的指令如下(全部添加到了鏡像中)
ADD?.?/app/startapp/
其中還包含了d目錄大小約300MB,是否需要使用不得而知,但目測(cè)不需要使用,僅為測(cè)試數(shù)據(jù)
d
├──?[?503]??test_421.json
├──?[?483]??test_havalB9.json
...
├──?[?484]??test_144.json
├──?[?104]??.gitmodules
├──?[?122]??.idea
├──?[???0]??__init__.py
├──?[?11M]??164103.zip
├──?[108M]??test_180753.csv
├──?[?68M]??test_180753.txt
...
└──?[?335]??README.md
以上其實(shí)都不需要提交到鏡像中制作成鏡像
2.4 Dockerfile 本身有其他問(wèn)題
這個(gè)原因不言而喻,不是專業(yè)的人寫的Dockerfile可能都有一定的優(yōu)化空間,只是暫時(shí)沒關(guān)注這些細(xì)節(jié)而已
例如,放任各路repo研發(fā)自行寫Dockerfile,沒有一定的標(biāo)準(zhǔn),前期可能無(wú)所謂,到后期問(wèn)題就慢慢浮現(xiàn)了
正所謂《能用就行》~
3、Dockerfile 如何優(yōu)化
3.1 從哪里入手
優(yōu)化docker鏡像應(yīng)該從鏡像分層概念入手
3.1.1 舉個(gè)栗子
一個(gè)實(shí)際的例子
nginx:alpine 鏡像 23.2MB
#?docker?history?nginx:alpine
IMAGE??????????CREATED???????CREATED?BY??????????????????????????????????????SIZE??????COMMENT
b46db85084b8???9?days?ago????/bin/sh?-c?#(nop)??CMD?["nginx"?"-g"?"daemon…???0B
??????9?days?ago????/bin/sh?-c?#(nop)??STOPSIGNAL?SIGQUIT???????????0B
??????9?days?ago????/bin/sh?-c?#(nop)??EXPOSE?80????????????????????0B
??????9?days?ago????/bin/sh?-c?#(nop)??ENTRYPOINT?["/docker-entr…???0B
??????9?days?ago????/bin/sh?-c?#(nop)?COPY?file:09a214a3e07c919a…???4.61kB
??????9?days?ago????/bin/sh?-c?#(nop)?COPY?file:0fd5fca330dcd6a7…???1.04kB
??????9?days?ago????/bin/sh?-c?#(nop)?COPY?file:0b866ff3fc1ef5b0…???1.96kB
??????9?days?ago????/bin/sh?-c?#(nop)?COPY?file:65504f71f5855ca0…???1.2kB
??????9?days?ago????/bin/sh?-c?set?-x?????&&?addgroup?-g?101?-S?…???17.6MB
??????9?days?ago????/bin/sh?-c?#(nop)??ENV?PKG_RELEASE=1????????????0B
??????9?days?ago????/bin/sh?-c?#(nop)??ENV?NJS_VERSION=0.7.0????????0B
??????9?days?ago????/bin/sh?-c?#(nop)??ENV?NGINX_VERSION=1.21.4?????0B
??????9?days?ago????/bin/sh?-c?#(nop)??LABEL?maintainer=NGINX?Do…???0B
??????10?days?ago???/bin/sh?-c?#(nop)??CMD?["/bin/sh"]??????????????0B
??????10?days?ago???/bin/sh?-c?#(nop)?ADD?file:762c899ec0505d1a3…???5.61MB
python:alpine 鏡像 45.5MB
#?docker?history?python:alpine
IMAGE??????????CREATED???????CREATED?BY??????????????????????????????????????SIZE??????COMMENT
382a63bb2f25???10?days?ago???/bin/sh?-c?#(nop)??CMD?["python3"]??????????????0B
??????10?days?ago???/bin/sh?-c?set?-ex;???wget?-O?get-pip.py?"$P…???8.31MB
??????10?days?ago???/bin/sh?-c?#(nop)??ENV?PYTHON_GET_PIP_SHA256…???0B
??????10?days?ago???/bin/sh?-c?#(nop)??ENV?PYTHON_GET_PIP_URL=ht…???0B
??????10?days?ago???/bin/sh?-c?#(nop)??ENV?PYTHON_SETUPTOOLS_VER…???0B
??????10?days?ago???/bin/sh?-c?#(nop)??ENV?PYTHON_PIP_VERSION=21…???0B
??????10?days?ago???/bin/sh?-c?cd?/usr/local/bin??&&?ln?-s?idle3…???32B
??????10?days?ago???/bin/sh?-c?set?-ex??&&?apk?add?--no-cache?--…???29.8MB
??????10?days?ago???/bin/sh?-c?#(nop)??ENV?PYTHON_VERSION=3.10.0????0B
??????10?days?ago???/bin/sh?-c?#(nop)??ENV?GPG_KEY=A035C8C19219B…???0B
??????10?days?ago???/bin/sh?-c?set?-eux;??apk?add?--no-cache???c…???1.82MB
??????10?days?ago???/bin/sh?-c?#(nop)??ENV?LANG=C.UTF-8?????????????0B
??????10?days?ago???/bin/sh?-c?#(nop)??ENV?PATH=/usr/local/bin:/…???0B
??????10?days?ago???/bin/sh?-c?#(nop)??CMD?["/bin/sh"]??????????????0B
??????10?days?ago???/bin/sh?-c?#(nop)?ADD?file:762c899ec0505d1a3…???5.61MB
實(shí)際存儲(chǔ)
#?docker?inspect?nginx:alpine|?jq?'.[0]|{GraphDriver}'
{
??"GraphDriver":?{
????"Data":?{
??????"LowerDir":?"/data/docker-overlay2/overlay2/3d.../diff:/data/docker-overlay2/overlay2/ae.../diff:/data/docker-overlay2/overlay2/ea.../diff:/data/docker-overlay2/overlay2/29.../diff:/data/docker-overlay2/overlay2/5e.../diff",
??????"MergedDir":?"/data/docker-overlay2/overlay2/b7.../merged",
??????"UpperDir":?"/data/docker-overlay2/overlay2/b7.../diff",
??????"WorkDir":?"/data/docker-overlay2/overlay2/b7.../work"
????},
????"Name":?"overlay2"
??}
}
分層概念的描述
鏡像解決了應(yīng)用運(yùn)行及環(huán)境的打包問(wèn)題,實(shí)際應(yīng)用中應(yīng)用都是基于同一個(gè)rootfs來(lái)打包和迭代的,但并不是每個(gè)rootfs都會(huì)多份,實(shí)際上docker利用了存儲(chǔ)驅(qū)動(dòng)AUFS,devicemapper,overlay,overlay2的存儲(chǔ)技術(shù)實(shí)現(xiàn)了分層
例如上面查看一個(gè)docker鏡像會(huì)發(fā)現(xiàn)這些層
LowerDir:鏡像層
MergedDir:整合了 lower 層和 upper 讀寫層顯示出來(lái)的視圖
UpperDir:讀寫層
WorkDir:中間層,對(duì) Upper 層的寫入,先寫入 WorkDir,再移入 UpperDir
3.1.2 Copy on write
當(dāng)Docker第一次啟動(dòng)一個(gè)容器時(shí),初始的讀寫層是空的,當(dāng)文件系統(tǒng)發(fā)生變化時(shí),這些變化都會(huì)應(yīng)用到這一層之上。比如,如果想修改一個(gè)文件,這個(gè)文件首先會(huì)從該讀寫層下面的只讀層復(fù)制到該讀寫層。由此,該文件的只讀版本依然存在于只讀層,只是被讀寫層的該文件副本所隱藏,該機(jī)制則被稱之為寫時(shí)復(fù)制
3.1.3 UnionFS
把多個(gè)目錄(也叫分支)內(nèi)容聯(lián)合掛載到同一個(gè)目錄下,而目錄的物理位置是分開的
一個(gè)直觀的效果,第一次拉取一個(gè)nginx:1.15版本鏡像,再次拉取nginx:1.16鏡像,速度要快很多
3.2 方案
了解了鏡像大小的主要構(gòu)成之后,就很容易知道從哪些方向入手減少鏡像大小了
3.2.1 減少鏡像層數(shù)
鏡像層數(shù)的增加,對(duì)Dockerfile來(lái)說(shuō)主要在于RUN指令出現(xiàn)的次數(shù),因此,合并RUN指令可以大大減少鏡像層數(shù)
舉個(gè)栗子:
合并前,三層
RUN?apk?add?tzdata
RUN?cp?/usr/share/zoneinfo/Asia/Shanghai?/etc/localtime
RUN?echo?"Asia/Shanghai"?>?/etc/timezone
合并后,一層
RUN?apk?add?tzdata?\
????&&?cp?/usr/share/zoneinfo/Asia/Shanghai?/etc/localtime?\
????&&?echo?"Asia/Shanghai"?>?/etc/timezone
3.2.2 減少每層鏡像大小
3.2.2.1 選用更小的基礎(chǔ)鏡像
scratch:空鏡像,又叫鏡像之父!任何鏡像都需要有一個(gè)基礎(chǔ)鏡像,那么問(wèn)題來(lái)了,就好比是先有雞還是先有蛋的問(wèn)題,基礎(chǔ)鏡像的“祖宗”是什么呢?能不能在構(gòu)建時(shí)不以任何鏡像為基礎(chǔ)呢?答案是肯定的,可以選用 scratch,具體就不展開了,可以參考:baseimages[2],使用scratch鏡像的例子pausebusybox:對(duì)比 scratch,多了常用的linux工具等alpine:多了包管理工具 apk等
3.3.2.2 多階段構(gòu)建
多階段構(gòu)建非常適用于編譯性語(yǔ)言,簡(jiǎn)單來(lái)說(shuō)就是允許一個(gè)Dockerfile中出現(xiàn)多條FROM指令,只有最后一條FROM指令中指定的基礎(chǔ)鏡像作為本次構(gòu)建鏡像的基礎(chǔ)鏡像,其它的階段都可以認(rèn)為是只為中間步驟
FROM … AS …和COPY --from組合使用
例如java鏡像,鏡像大小812MB
FROM?centos?AS?jdk
COPY?jdk-8u231-linux-x64.tar.gz?/usr/local/src
RUN?cd?/usr/local/src?&&?\
????tar?-xzvf?jdk-8u231-linux-x64.tar.gz?-C?/usr/local
使用多階段構(gòu)建,鏡像大小618MB
FROM?centos?AS?jdk
COPY?jdk-8u231-linux-x64.tar.gz?/usr/local/src
RUN?cd?/usr/local/src?&&?\
????tar?-xzvf?jdk-8u231-linux-x64.tar.gz?-C?/usr/local
FROM?centos
COPY?--from=jdk?/usr/local/jdk1.8.0_231?/usr/local
3.3.2.3 忽略文件
構(gòu)建上下文build context,“上下文” 意為和現(xiàn)在這個(gè)工作相關(guān)的周圍環(huán)境
docker build時(shí)當(dāng)前的工作目錄,不管構(gòu)建時(shí)有沒有用到當(dāng)前目錄下的某些文件及目錄,默認(rèn)情況下這個(gè)上下文中的文件及目錄都會(huì)作為構(gòu)建上下文內(nèi)容發(fā)送給Docker Daemon
當(dāng)docker build開始執(zhí)行時(shí),控制臺(tái)會(huì)輸出Sending build context to Docker daemon xxxMB,這就表示將當(dāng)前工作目錄下的文件及目錄都作為了構(gòu)建上下文
前面提到可以在RUN指令中添加--no-cache不使用緩存,同樣也可以在執(zhí)行docker build命令時(shí)添加該指令以在鏡像構(gòu)建時(shí)不使用緩存
構(gòu)建上下文中,使用.dockerignore 文件在構(gòu)建時(shí)就可以避免將本地模塊以及調(diào)試日志被拷貝進(jìn)入到Docker鏡像中,這和git版本控制的.gitignore很類似
3.3.2.4 遠(yuǎn)程下載
使用遠(yuǎn)程下載代替ADD可以減少鏡像大小
RUN?curl?-s?http://192.168.1.1/repository/tools/jdk-8u241-linux-x64.tar.gz?|?tar?-xC?/opt/
3.3.2.5 拆分 COPY
例如一個(gè)COPY指令的目錄下A有4個(gè)子目錄AA/BB/CC/DD被COPY,但常變化的只有一個(gè) BB
這個(gè)時(shí)候拆分COPY會(huì)更快
COPY?A/AA?/app/A/AA
COPY?A/BB?/app/A/BB
COPY?A/CC?/app/A/CC
COPY?A/DD?/app/A/DD
3.3.2.6 構(gòu)建時(shí)掛載
構(gòu)建時(shí)掛載(擴(kuò)展功能[3])
配置
修改 docker 啟動(dòng)參數(shù),添加 --experimentaldockerfile 頭部添加 # syntax=docker/dockerfile:1.1.1-experimental
使用
掛載本地 golang 緩存
#?syntax?=?docker/dockerfile:experimental
FROM?golang
...
RUN?--mount=type=cache,target=/root/.cache/go-build?go?build?...
掛載 cache 目錄
#?syntax?=?docker/dockerfile:experimental
FROM?ubuntu
RUN?rm?-f?/etc/apt/apt.conf.d/docker-clean;?echo?'Binary::apt::APT::Keep-Downloaded-Packages?"true";'?>?/etc/apt/apt.conf.d/keep-cache
RUN?--mount=type=cache,target=/var/cache/apt?--mount=type=cache,target=/var/lib/apt?\
??apt?update?&&?apt?install?-y?gcc
掛載某些憑據(jù)
#?syntax?=?docker/dockerfile:experimental
FROM?python:3
RUN?pip?install?awscli
RUN?--mount=type=secret,id=aws,target=/root/.aws/credentials?aws?s3?cp?s3://...?...
等等
3.3.2.7 構(gòu)建后清理
刪除壓縮包 清理安裝緩存 --no-cache rm -rf /var/lib/apt/lists/* rm -rf /var/cache/yum/*
3.3.2.8 鏡像壓縮
export和import組合進(jìn)行壓縮鏡像(壓縮效果不是很明顯)
這種方法不好的就是會(huì)丟失一部分鏡像信息
#?docker?run?-d?--name?nginx?nginx:alpine
#?docker?export?nginx?|docker?import?-?nginx:alpine2
sha256:dd6a3cf822ac3c3ad3e7f7b31675cd8cd99a6f80e360996e04da6fc2f3b98cb5
#?docker?history?nginx:alpine
IMAGE??????????CREATED???????CREATED?BY??????????????????????????????????????SIZE??????COMMENT
b46db85084b8???10?days?ago???/bin/sh?-c?#(nop)??CMD?["nginx"?"-g"?"daemon…???0B
??????10?days?ago???/bin/sh?-c?#(nop)??STOPSIGNAL?SIGQUIT???????????0B
??????10?days?ago???/bin/sh?-c?#(nop)??EXPOSE?80????????????????????0B
??????10?days?ago???/bin/sh?-c?#(nop)??ENTRYPOINT?["/docker-entr…???0B
??????10?days?ago???/bin/sh?-c?#(nop)?COPY?file:09a214a3e07c919a…???4.61kB
??????10?days?ago???/bin/sh?-c?#(nop)?COPY?file:0fd5fca330dcd6a7…???1.04kB
??????10?days?ago???/bin/sh?-c?#(nop)?COPY?file:0b866ff3fc1ef5b0…???1.96kB
??????10?days?ago???/bin/sh?-c?#(nop)?COPY?file:65504f71f5855ca0…???1.2kB
??????10?days?ago???/bin/sh?-c?set?-x?????&&?addgroup?-g?101?-S?…???17.6MB
??????10?days?ago???/bin/sh?-c?#(nop)??ENV?PKG_RELEASE=1????????????0B
??????10?days?ago???/bin/sh?-c?#(nop)??ENV?NJS_VERSION=0.7.0????????0B
??????10?days?ago???/bin/sh?-c?#(nop)??ENV?NGINX_VERSION=1.21.4?????0B
??????10?days?ago???/bin/sh?-c?#(nop)??LABEL?maintainer=NGINX?Do…???0B
??????10?days?ago???/bin/sh?-c?#(nop)??CMD?["/bin/sh"]??????????????0B
??????10?days?ago???/bin/sh?-c?#(nop)?ADD?file:762c899ec0505d1a3…???5.61MB
#?docker?history?nginx:alpine2
IMAGE??????????CREATED??????????CREATED?BY???SIZE??????COMMENT
dd6a3cf822ac???40?seconds?ago????????????????23MB??????Imported?from?-
#?docker?images|grep?nginx
nginx???????????????????????????????????????????????????????????????????????????????????????????????????????????????alpine2?????????????????????dd6a3cf822ac???54?seconds?ago???23MB
nginx???????????????????????????????????????????????????????????????????????????????????????????????????????????????alpine??????????????????????b46db85084b8???10?days?ago??????23.2MB
3.3 樣例
3.3.1 go 樣例
樣例一
kubeadm安裝的k8s集群,kube-apiserver鏡像的Dockerfile是利用bazel編譯工具編譯的
bazel?build?...
LABEL?maintainers=Kubernetes?Authors
LABEL?description=go?based?runner?for?distroless?scenarios
WORKDIR?/
COPY?/workspace/go-runner?.?#?buildkit
ENTRYPOINT?["/go-runner"]
COPY?file:2e904ea733ba0ded2a99947847de31414a19d83f8495dd8c1fbed3c70bf67a22?in?/usr/local/bin/kube-apiserver
代碼目錄 28M(包含.git 目錄 20.5M)
鏡像大小 122MB
樣例二
開源編排引擎Cadence的Dockerfile
ARG?TARGET=server
#?Can?be?used?in?case?a?proxy?is?necessary
ARG?GOPROXY
#?Build?tcheck?binary
FROM?golang:1.17-alpine3.13?AS?tcheck
WORKDIR?/go/src/github.com/uber/tcheck
COPY?go.*?./
RUN?go?build?-mod=readonly?-o?/go/bin/tcheck?github.com/uber/tcheck
#?Build?Cadence?binaries
FROM?golang:1.17-alpine3.13?AS?builder
ARG?RELEASE_VERSION
RUN?apk?add?--update?--no-cache?ca-certificates?make?git?curl?mercurial?unzip
WORKDIR?/cadence
#?Making?sure?that?dependency?is?not?touched
ENV?GOFLAGS="-mod=readonly"
#?Copy?go?mod?dependencies?and?build?cache
COPY?go.*?./
RUN?go?mod?download
COPY?.?.
RUN?rm?-fr?.bin?.build
ENV?CADENCE_RELEASE_VERSION=$RELEASE_VERSION
#?bypass?codegen,?use?committed?files.??must?be?run?separately,?before?building?things.
RUN?make?.fake-codegen
RUN?CGO_ENABLED=0?make?copyright?cadence-cassandra-tool?cadence-sql-tool?cadence?cadence-server?cadence-bench?cadence-canary
#?Download?dockerize
FROM?alpine:3.11?AS?dockerize
RUN?apk?add?--no-cache?openssl
ENV?DOCKERIZE_VERSION?v0.6.1
RUN?wget?https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz?\
????&&?tar?-C?/usr/local/bin?-xzvf?dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz?\
????&&?rm?dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz?\
????&&?echo?"****?fix?for?host?id?mapping?error?****"?\
????&&?chown?root:root?/usr/local/bin/dockerize
#?Alpine?base?image
FROM?alpine:3.11?AS?alpine
RUN?apk?add?--update?--no-cache?ca-certificates?tzdata?bash?curl
#?set?up?nsswitch.conf?for?Go's?"netgo"?implementation
#?https://github.com/gliderlabs/docker-alpine/issues/367#issuecomment-424546457
RUN?test?!?-e?/etc/nsswitch.conf?&&?echo?'hosts:?files?dns'?>?/etc/nsswitch.conf
SHELL?["/bin/bash",?"-c"]
#?Cadence?server
FROM?alpine?AS?cadence-server
ENV?CADENCE_HOME?/etc/cadence
RUN?mkdir?-p?/etc/cadence
COPY?--from=tcheck?/go/bin/tcheck?/usr/local/bin
COPY?--from=dockerize?/usr/local/bin/dockerize?/usr/local/bin
COPY?--from=builder?/cadence/cadence-cassandra-tool?/usr/local/bin
COPY?--from=builder?/cadence/cadence-sql-tool?/usr/local/bin
COPY?--from=builder?/cadence/cadence?/usr/local/bin
COPY?--from=builder?/cadence/cadence-server?/usr/local/bin
COPY?--from=builder?/cadence/schema?/etc/cadence/schema
COPY?docker/entrypoint.sh?/docker-entrypoint.sh
COPY?config/dynamicconfig?/etc/cadence/config/dynamicconfig
COPY?config/credentials?/etc/cadence/config/credentials
COPY?docker/config_template.yaml?/etc/cadence/config
COPY?docker/start-cadence.sh?/start-cadence.sh
WORKDIR?/etc/cadence
ENV?SERVICES="history,matching,frontend,worker"
EXPOSE?7933?7934?7935?7939
ENTRYPOINT?["/docker-entrypoint.sh"]
CMD?/start-cadence.sh
#?All-in-one?Cadence?server
FROM?cadence-server?AS?cadence-auto-setup
RUN?apk?add?--update?--no-cache?ca-certificates?py-pip?mysql-client
RUN?pip?install?cqlsh
COPY?docker/start.sh?/start.sh
CMD?/start.sh
#?Cadence?CLI
FROM?alpine?AS?cadence-cli
COPY?--from=tcheck?/go/bin/tcheck?/usr/local/bin
COPY?--from=builder?/cadence/cadence?/usr/local/bin
ENTRYPOINT?["cadence"]
#?Cadence?Canary
FROM?alpine?AS?cadence-canary
COPY?--from=builder?/cadence/cadence-canary?/usr/local/bin
COPY?--from=builder?/cadence/cadence?/usr/local/bin
CMD?["/usr/local/bin/cadence-canary",?"--root",?"/etc/cadence-canary",?"start"]
#?Cadence?Bench
FROM?alpine?AS?cadence-bench
COPY?--from=builder?/cadence/cadence-bench?/usr/local/bin
COPY?--from=builder?/cadence/cadence?/usr/local/bin
CMD?["/usr/local/bin/cadence-bench",?"--root",?"/etc/cadence-bench",?"start"]
#?Final?image
FROM?cadence-${TARGET}
代碼目錄 85.4M(包含.git 目錄 57.7M)
鏡像大小 135.69MB
3.3.2 py 樣例
FROM?python:3.4
RUN?apt-get?update?\
????&&?apt-get?install?-y?--no-install-recommends?\
????????postgresql-client?\
????&&?rm?-rf?/var/lib/apt/lists/*
WORKDIR?/usr/src/app
COPY?requirements.txt?./
RUN?pip?install?-r?requirements.txt
COPY?.?.
EXPOSE?8000
CMD?["python",?"manage.py",?"runserver",?"0.0.0.0:8000"]
代碼目錄 275M(包含.git 目錄 222M)
鏡像大小 436MB
4、除了這些優(yōu)化還可以做什么
4.1 設(shè)置字符集
在Dockerfile中設(shè)置通用的字符集
#?Set?lang
ENV?LANG?"en_US.UTF-8"
4.2 時(shí)區(qū)校正
這個(gè)問(wèn)題更多內(nèi)容可以參考我之前的文章?k8s環(huán)境下處理容器時(shí)間問(wèn)題的多種姿勢(shì)?[4]
在Dockerfile中設(shè)置通用的時(shí)區(qū)
#?Set?timezone
RUN?ln?-sf?/usr/share/zoneinfo/Asia/Shanghai?/etc/localtime?\
???&&?echo?"Asia/Shanghai"?>?/etc/timezone
4.3 進(jìn)程管理
docker容器運(yùn)行時(shí),默認(rèn)會(huì)以Dockerfile中的ENTRYPOINT或CMD作為PID為1的主進(jìn)程,這個(gè)進(jìn)程存在的目的,通俗來(lái)說(shuō)需要做的就是將容器"夯住",一旦這個(gè)進(jìn)程不存在了,那么容器就會(huì)退出
除此之外,這個(gè)主進(jìn)程還有一個(gè)重要的作用就是管理“僵尸進(jìn)程”
一個(gè)比較官方的定義,“僵尸進(jìn)程”是指完成執(zhí)行(通過(guò)exit系統(tǒng)調(diào)用,或運(yùn)行時(shí)發(fā)生致命錯(cuò)誤或收到終止信號(hào)所致),但在操作系統(tǒng)的進(jìn)程表中仍然存在其進(jìn)程控制塊,處于"終止?fàn)顟B(tài)"的進(jìn)程。
清理“僵尸進(jìn)程”的思路主要有
將父進(jìn)程中對(duì) SIGCHLD信號(hào)的處理函數(shù)設(shè)為SIG_IGN(忽略信號(hào));fork兩次并殺死一級(jí)子進(jìn)程,令二級(jí)子進(jìn)程成為孤兒進(jìn)程而被init所“收養(yǎng)”、清理
目前可以實(shí)現(xiàn)的開源方案
Tini
tini容器init是一個(gè)最小化的init系統(tǒng),運(yùn)行在容器內(nèi)部,用于啟動(dòng)一個(gè)子進(jìn)程,并等待進(jìn)程退出時(shí)清理僵尸和執(zhí)行信號(hào)轉(zhuǎn)發(fā)
優(yōu)點(diǎn)
tini可以避免應(yīng)用程序生成僵尸進(jìn)程tini可以處理Docker進(jìn)程中運(yùn)行的程序的信號(hào),通過(guò)Tini,SIGTERM可以終止進(jìn)程,不需要你明確安裝一個(gè)信號(hào)處理器
示例
#?Add?Tini
ENV?TINI_VERSION?v0.19.0
ADD?https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini?/tini
RUN?chmod?+x?/tini
ENTRYPOINT?["/tini",?"--"]
#?Run?your?program?under?Tini
CMD?["/your/program",?"-and",?"-its",?"arguments"]
#?or?docker?run?your-image?/your/program?...
dumb-init
dumb-init會(huì)向子進(jìn)程的進(jìn)程組發(fā)送其收到的信號(hào)。例如 bash 接收到信號(hào)之后,不會(huì)向子進(jìn)程發(fā)送信號(hào)
dumb-init也可以通過(guò)設(shè)置環(huán)境變量DUMB_INIT_SETSID=0來(lái)控制只向它的直接子進(jìn)程發(fā)送信號(hào)
另外dumb-init也會(huì)接管失去父進(jìn)程的進(jìn)程,確保其能正常退出
示例
FROM?alpine:3.11.5
RUN?sed?-i?"s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g"?/etc/apk/repositories?\
????&&?apk?add?--no-cache?dumb-init
#?Runs?"/usr/bin/dumb-init?--?/my/script?--with?--args"
ENTRYPOINT?["dumb-init",?"--"]
#?or?if?you?use?--rewrite?or?other?cli?flags
#?ENTRYPOINT?["dumb-init",?"--rewrite",?"2:3",?"--"]
CMD?["/my/script",?"--with",?"--args"]
4.4 降權(quán)啟動(dòng)
很多情況下,容器中的進(jìn)程需要降權(quán)啟動(dòng)以保證安全性,這就和我們?cè)?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(150, 84, 181);">vm上運(yùn)行一個(gè)nginx服務(wù)一樣,最好通過(guò)特定的降權(quán)用戶去運(yùn)行
舉例,tomcat鏡像
...
USER?tomcat
WORKDIR?/usr/local/tomcat
EXPOSE?8080
ENTRYPOINT?["catalina.sh","run"]
如果在某些情況下需要使用sudo權(quán)限,在docker官方避免安裝或使用sudo,sudo因?yàn)樗哂胁豢深A(yù)測(cè)的TTY和可能導(dǎo)致問(wèn)題的信號(hào)轉(zhuǎn)發(fā)行為。如果必須,例如將守護(hù)進(jìn)程初始化為 root但將其作為非運(yùn)行root,推薦使用gosu
例如,Postgres 官方鏡像[5] 使用以下腳本作為其ENTRYPOINT
#!/bin/bash
set?-e
if?[?"$1"?=?'postgres'?];?then
????chown?-R?postgres?"$PGDATA"
????if?[?-z?"$(ls?-A?"$PGDATA")"?];?then
????????gosu?postgres?initdb
????fi
????exec?gosu?postgres?"$@"
fi
exec?"$@"
4.5 底層庫(kù)依賴
很多時(shí)候,服務(wù)依賴一些底層庫(kù)的支持,這里以基于alpine基礎(chǔ)鏡像構(gòu)建java鏡像舉個(gè)栗子
alpine為了精簡(jiǎn)本身并沒有安裝太多的常用軟件,所以如果要使用jdk/jre的話就需要glibc,而glibc需要先得到ca-certificates證書服務(wù)(安裝glibc前置依賴)才能安裝
用alpine跑了jdk8的鏡像結(jié)果發(fā)現(xiàn)jdk無(wú)法執(zhí)行。究其原因,java是基于GUN Standard C library(glibc),alpine是基于MUSL libc(mini libc),所以alpine需要安裝glibc的庫(kù)
5、小結(jié)
本文簡(jiǎn)要分析了Dockerfile為什么這么大的幾個(gè)主要原因,并且根據(jù)生產(chǎn)經(jīng)驗(yàn)羅列了一些優(yōu)化鏡像大小的措施以及其他方面常用的處理辦法,很多技巧性的內(nèi)容,比較雜亂,就不一一提及了 ~
See you ~
參考資料
https://www.ssgeek.com/post/git-mu-lu-wei-shi-me-zhe-me-da/
[2]https://docs.docker.com/develop/develop-images/baseimages/
[3]https://docs.docker.com/engine/reference/commandline/dockerd/#description
[4]https://www.ssgeek.com/post/k8s-huan-jing-xia-chu-li-rong-qi-shi-jian-wen-ti-de-duo-chong-zi-shi/
[5]https://hub.docker.com/_/postgres/
歡迎進(jìn)群一起進(jìn)行技術(shù)交流
加群方式:公眾號(hào)消息私信“加群”或加我好友再加群均可
