構建 Docker 鏡像的 N 個小技巧,運維工程師看過來,學到了~
構建鏡像的幾個小技巧
構建上下文
執行?docker build
?命令時,當前的工作目錄被稱為構建上下文。默認情況下,Dockerfile 就位于該路徑下。也可以通過?-f
?參數來指定 dockerfile ,但 docker 客戶端會將當前工作目錄下的所有文件發送到 docker 守護進程進行構建。
所以來說,當執行 docker build 進行構建鏡像時,當前目錄一定要?干凈
?,切記不要在家里錄下創建一個 Dockerfile 緊接著?docker build
?一把梭 ??。
正確做法是為項目建立一個文件夾,把構建鏡像時所需要的資源放在這個文件夾下。比如這樣:
mkdir project
cd !$
vi Dockerfile
# 編寫 Dockerfile
也可以通過?
.dockerignore
?文件來忽略不需要的文件發送到 docker 守護進程
基礎鏡像
使用體積較小的基礎鏡像,比如?alpine
?或者?debian:buster-slim
,像 openjdk 可以選用?openjdk:xxx-slim
,由于 openjdk 是基于 debian 的基礎鏡像構建的,所以向 debian 基礎鏡像一樣,后面帶個?slim
?就是基于?debian:xxx-slim
鏡像構建的。
REPOSITORY TAG IMAGE ID CREATED SIZE
debian buster-slim e1af56d072b8 4 days ago 69.2MB
alpine latest cc0abc535e36 8 days ago 5.59MB
不過需要注意的是,alpine 的 c 庫是?musl libc
?,而不是正統的?glibc
,另外對于一些依賴?glibc
?的大型項目,像 openjdk 、tomcat、rabbitmq 等都不建議使用 alpine 基礎鏡像,因為 musl libc 可能會導致 JVM 一些奇怪的問題。這也是為什么 tomcat 官方沒有給出基礎鏡像是 alpine 的 Dockerfile 的原因。
國內軟件源
- 對于 alpine 基礎鏡像修改軟件源
echo "http://mirrors.huaweicloud.com/alpine/latest-stable/main/" > /etc/apk/repositories ;\
echo "http://mirrors.huaweicloud.com/alpine/latest-stable/community/" >> /etc/apk/repositories ;\
apk?update?;
- debian 基礎鏡像修改默認原件源碼
sed -i 's/deb.debian.org/mirrors.huaweicloud.com/g' /etc/apt/sources.list ;\
sed -i 's|security.debian.org/debian-security|mirrors.huaweicloud.com/debian-security|g' /etc/apt/sources.list ;\
apt update ;\
- Ubuntu 基礎鏡像修改默認原件源碼
sed -i 's/archive.ubuntu.com/mirrors.huaweicloud.com/g' /etc/apt/sources.list
apt update ;\
- 對于 CentOS ???
你確定要用 220MB 大小的基礎鏡像?
REPOSITORY TAG IMAGE ID CREATED SIZE
centos latest 0f3e07c0138f 3 months ago 220MB
時區設置
由于絕大多數基礎鏡像都是默認采用 UTC 的時區,與北京時間相差 8 個小時,這將會導致容器內的時間與北京時間不一致,因而會對一些應用造成一些影響,還會影響容器內日志和監控的數據。
-e TZ=Asia/Shanghai
?來設定容器內的時區。alpine
-
但對于 alpine 基礎鏡像無法通過 TZ 環境變量的方式設定時區,需要安裝 tzdata 來配置時區。
root@ubuntu:~/docke/alpine# docker run --rm -it -e TZ=Asia/Shanghai alpine date
Thu Jan 2 03:37:44 UTC 2020
-
對于 alpine 基礎鏡像,可以在 RUN 指令后面追加上以下命令
apk add --no-cache tzdata ;\
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime ;\
echo "Asia/Shanghai" > /etc/timezone ;\
apk del tzdata ;\
- 通過 tzdate 設定時區
root@ubuntu:~/docke/alpine# docker build -t alpine:tz2 .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM alpine
---> cc0abc535e36
Step 2/2 : RUN set -xue ; echo "http://mirrors.huaweicloud.com/alpine/latest-stable/main/" > /etc/apk/repositories ; echo "http://mirrors.huaweicloud.com/alpine/latest-stable/community/" >> /etc/apk/repositories ; apk update ; apk add --no-cache tzdata ; cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime ; echo "Asia/Shanghai" > /etc/timezone ; apk del tzdata
---> Running in 982041a34dbf
+ echo http://mirrors.huaweicloud.com/alpine/latest-stable/main/
+ echo http://mirrors.huaweicloud.com/alpine/latest-stable/community/
+ apk update
fetch http://mirrors.huaweicloud.com/alpine/latest-stable/main/x86_64/APKINDEX.tar.gz
fetch http://mirrors.huaweicloud.com/alpine/latest-stable/community/x86_64/APKINDEX.tar.gz
v3.11.2-11-gd5cdcefa20 [http://mirrors.huaweicloud.com/alpine/latest-stable/main/]
v3.11.2-14-g973431591e [http://mirrors.huaweicloud.com/alpine/latest-stable/community/]
OK: 11261 distinct packages available
+ apk add --no-cache tzdata
fetch http://mirrors.huaweicloud.com/alpine/latest-stable/main/x86_64/APKINDEX.tar.gz
fetch http://mirrors.huaweicloud.com/alpine/latest-stable/community/x86_64/APKINDEX.tar.gz
(1/1) Installing tzdata (2019c-r0)
Executing busybox-1.31.1-r8.trigger
OK: 9 MiB in 15 packages
+ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+ echo Asia/Shanghai
+ apk del tzdata
(1/1) Purging tzdata (2019c-r0)
Executing busybox-1.31.1-r8.trigger
OK: 6 MiB in 14 packages
Removing intermediate container 982041a34dbf
---> 3ec89f3e824d
Successfully built 3ec89f3e824d
Successfully tagged alpine:tz2
root@ubuntu:~/docke/alpine# docker run --rm -it alpine:tz2 date
Thu Jan 2 11:12:23 CST 2020
debian
- 通過啟動時設定環境變量指定時區
root@ubuntu:~/docke/alpine# docker run --rm -it -e TZ=Asia/Shanghai debian date
Thu Jan 2 11:38:56 CST 2020
- 也可以再構建鏡像的時候復制時區文件設定容器內時區
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime ;\
echo "Asia/shanghai" > /etc/timezone ;\
ubuntu
-
通過啟動時設定環境變量指定時區,發射失敗 ??,只能通過時區文件來設定時區了。
root@ubuntu:~/docke/alpine# docker run --rm -it -e TZ=Asia/Shanghai debian date
Thu Jan 2 11:38:56 CST 2020
root@ubuntu:~/docke/alpine# ^debian^ubuntu
docker run --rm -it -e TZ=Asia/Shanghai ubuntu date
Thu Jan 2 03:44:13 Asia 2020
在這里有個命令執行的小技巧,通過脫字符?
^
?來替換上一條命令中的 debian 為 ubuntu 然后執行相同的命令。
- 通過時區文件來設定時區
apt update ;\
apt install tzdata -y ;\
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime ;\
echo "Asia/shanghai" > /etc/timezone ;\
盡量使用 URL 添加源碼
git & wget source
?然后?build
,最后?rm -rf source/
?這三部放在一條 RUN 指令中,這樣就能避免源碼添加到鏡像中而增大鏡像體積啦。-
項目官方的 Dockerfile
# centos 7
FROM centos:7
# 添加配置文件
# add profiles
ADD conf/client.conf /etc/fdfs/
ADD conf/http.conf /etc/fdfs/
ADD conf/mime.types /etc/fdfs/
ADD conf/storage.conf /etc/fdfs/
ADD conf/tracker.conf /etc/fdfs/
ADD fastdfs.sh /home
ADD conf/nginx.conf /etc/fdfs/
ADD conf/mod_fastdfs.conf /etc/fdfs
# 添加源文件
# add source code
ADD source/libfastcommon.tar.gz /usr/local/src/
ADD source/fastdfs.tar.gz /usr/local/src/
ADD source/fastdfs-nginx-module.tar.gz /usr/local/src/
ADD source/nginx-1.15.4.tar.gz /usr/local/src/
# Run
RUN yum install git gcc gcc-c++ make automake autoconf libtool pcre pcre-devel zlib zlib-devel openssl-devel wget vim -y \
&& mkdir /home/dfs \
&& cd /usr/local/src/ \
&& cd libfastcommon/ \
&& ./make.sh && ./make.sh install \
&& cd ../ \
&& cd fastdfs/ \
&& ./make.sh && ./make.sh install \
&& cd ../ \
&& cd nginx-1.15.4/ \
&& ./configure --add-module=/usr/local/src/fastdfs-nginx-module/src/ \
&& make && make install \
&& chmod +x /home/fastdfs.sh
# export config
VOLUME /etc/fdfs
EXPOSE 22122 23000 8888 80
ENTRYPOINT ["/home/fastdfs.sh"]
- 經過本人優化后的 Dockerfile
FROM alpine:3.10
RUN set -x \
&& echo "http://mirrors.huaweicloud.com/alpine/latest-stable/main/" > /etc/apk/repositories \
&& echo "http://mirrors.huaweicloud.com/alpine/latest-stable/community/" >> /etc/apk/repositories \
&& apk update \
&& apk add --no-cache --virtual .build-deps gcc libc-dev make perl-dev openssl-dev pcre-dev zlib-dev git \
&& mkdir -p /usr/local/src \
&& cd /usr/local/src \
&& git clone https://github.com/happyfish100/libfastcommon.git --depth 1 \
&& git clone https://github.com/happyfish100/fastdfs.git --depth 1 \
&& git clone https://github.com/happyfish100/fastdfs-nginx-module.git --depth 1 \
&& wget http://nginx.org/download/nginx-1.15.4.tar.gz \
&& tar -xf nginx-1.15.4.tar.gz \
&& cd /usr/local/src/libfastcommon \
&& ./make.sh \
&& ./make.sh install \
&& cd /usr/local/src/fastdfs/ \
&& ./make.sh \
&& ./make.sh install \
&& cd /usr/local/src/nginx-1.15.4/ \
&& ./configure --add-module=/usr/local/src/fastdfs-nginx-module/src/ \
&& make && make install \
&& apk del .build-deps \
&& apk add --no-cache pcre-dev bash \
&& mkdir -p /home/dfs \
&& mv /usr/local/src/fastdfs/docker/dockerfile_network/fastdfs.sh /home \
&& mv /usr/local/src/fastdfs/docker/dockerfile_network/conf/* /etc/fdfs \
&& chmod +x /home/fastdfs.sh \
&& rm -rf /usr/local/src*
VOLUME /home/dfs
E XPOSE 22122 23000 8888 8080
CMD ["/home/fastdfs.sh"]
-
構建之后的對比
使用項目默認的 Dockerfile 進行構建的話,鏡像大小接近 500MB,而經過一些的優化,將所有的 RUN 指令合并為一條,最終構建出來的鏡像大小為 30MB。
REPOSITORY TAG IMAGE ID CREATED SIZE
fastdfs alpine e855bd197dbe 10 seconds ago 29.3MB
fastdfs debian e05ca1616604 20 minutes ago 103MB
fastdfs centos c1488537c23c 30 minutes ago 483MB
使用虛擬編譯環境
對于只在編譯過程中使用到的依賴,我們可以將這些依賴安裝在虛擬環境中,編譯完成之后可以一并刪除這些依賴,比如 alpine 中可以使用?apk add --no-cache --virtual .build-deps
,后面加上需要安裝的相關依賴。
apk add --no-cache --virtual .build-deps gcc libc-dev make perl-dev openssl-dev pcre-dev zlib-dev git
構建完成之后可以使用 apk del .build-deps 命令,一并將這些編譯依賴全部刪除。
需要注意的是,.build-deps 后面接的是編譯時以來的軟件包,并不是所有的編譯依賴都可以刪除,不要把運行時的依賴包接在后面,最好單獨 add 一下。
最小化層數
docker 在 1.10 以后,只有?RUN、COPY 和 ADD
?指令會創建層,其他指令會創建臨時的中間鏡像,但是不會直接增加構建的鏡像大小了。
前文提到了建議使用 git 或者 wget 的方式來將文件打入到鏡像當中,但如果我們必須要使用 COPY 或者 ADD 指令呢?
還是拿 FastDFS 為例:
# centos 7
FROM centos:7
# 添加配置文件
# add profiles
ADD conf/client.conf /etc/fdfs/
ADD conf/http.conf /etc/fdfs/
ADD conf/mime.types /etc/fdfs/
ADD conf/storage.conf /etc/fdfs/
ADD conf/tracker.conf /etc/fdfs/
ADD fastdfs.sh /home
ADD conf/nginx.conf /etc/fdfs/
ADD conf/mod_fastdfs.conf /etc/fdfs
# 添加源文件
# add source code
ADD source/libfastcommon.tar.gz /usr/local/src/
ADD source/fastdfs.tar.gz /usr/local/src/
ADD source/fastdfs-nginx-module.tar.gz /usr/local/src/
ADD source/nginx-1.15.4.tar.gz /usr/local/src/
src.tar.gz
?然后通過 ADD 的方式把文件添加到當中去,然后在 RUN 指令后使用?mv
命令把文件移動到指定的位置。這樣僅僅一條 ADD 和 RUN 指令取代掉了 12 個 ADD 指令。FROM alpine:3.10
COPY src.tar.gz /usr/local/src.tar.gz
RUN set -xe \
&& apk add --no-cache --virtual .build-deps gcc libc-dev make perl-dev openssl-dev pcre-dev zlib-dev tzdata \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& tar -xvf /usr/local/src.tar.gz -C /usr/local \
&& mv /usr/local/src/conf/fastdfs.sh /home/fastdfs/ \
&& mv /usr/local/src/conf/* /etc/fdfs \
&& chmod +x /home/fastdfs/fastdfs.sh \
&& rm -rf /usr/local/src/* /var/cache/apk/* /tmp/* /var/tmp/* $HOME/.cache
VOLUME /var/fdfs
其他最小化層數無非就是把構建項目的整個步驟弄成一條 RUN 指令,不過多條命令合并可以使用?&&
?或者?;
?這兩者都可以,不過據我在 docker hub 上的所見所聞,使用?;
?的居多,尤其是官方的 Dockerfile。文章略有精簡。