Dockerfile 的一些实践经验
date
Mar 23, 2021
slug
practice-experience-for-dockerfile
status
Published
summary
去年一个人把公司内部分老项目和新项目添加了
Docker
支持,重新设计了 CD/CI 流程;同时由于基本是单机服务,编排容器用的 Docker Compose
,因此在这个过程中,也积累了一些经验。tags
Docker
type
Post
去年一个人把公司内部分老项目和新项目添加了
Docker
支持,重新设计了 CD/CI 流程;同时由于基本是单机服务,编排容器用的 Docker Compose
,因此在这个过程中,也积累了一些经验。一、减少镜像大小
1.1 Alpine
使用
Alpine
或者是带有 alpine
版本的镜像作为基础镜像 下图是这个博客分别用
node:14.13.0
和 node:14.13.0-alpine3.12
构建后的镜像大小对比
具体关于
Alpine
的介绍可以看看这个链接1.2 多阶段构建
在构建一些运行文件和源代码分离的项目时(
Vue.js
、React.js
、Go
等),可以考虑使用多阶段构建让最后生成的镜像只含有运行文件1.2.1 Vue.js
FROM node:13.14.0-alpine3.11 as base # 构建镜像 FROM base as builder WORKDIR /app COPY ./package.json ./package.json RUN npm install -g cross-env RUN npm install && \ npm install -g cnpm --registry=https://registry.npm.taobao.org && \ cnpm install node-sass sass-loader COPY . . RUN npm run build:h5 # 运行镜像 FROM caddy:2.1.1-alpine as runner RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo "Asia/Shanghai" > /etc/timezone && \ apk del tzdata COPY ./Caddyfile /etc/caddy/Caddyfile COPY --from=builder /app/dist/build/h5 /app/dist COPY --from=builder /app/public/pc.html /app/dist/public/pc.html CMD caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
1.3 如果希望所有容器都通过端口访问,可以考虑使用 Caddy
我个人比较偏向于所有的容器之间都通过端口访问,那么就会遇到一些服务本身没有映射功能端口的情——例如
PHP
——因此,可以考虑使用 Caddy
在容器内部代理端口Caddyfile
::8989 { root * /app/public file_server php_fastcgi 127.0.0.1:9000 }
Dockerfile
:COPY ./Caddyfile /etc/caddy/Caddyfile CMD caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
二、减少 Layer 层的数量
减少层数有利于提高 Push 和 Pull 的速度,提高部署效率;在
Dockerfile
中,每一行的命令都会生成一个新层,因此可以通过命令合并的方式降低层数,比如使用 && \ 分隔运行多个命令RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo "Asia/Shanghai" > /etc/timezone && \ apk del tzdata
三、提高构建速度
3.1 容易产生变动的命令放在最后执行
虽然
docker build
的过程中可以使用缓存提高速度,但是如果某一行 Dockerfile
内容发生了变动,那么它以及之后所有的命令都需要重新执行(不包括多阶段构建中的其他阶段)。3.2 多阶段构建
多阶段构建不仅能够帮助降低镜像大小,也可以在一定程度上提高构建的速度,比如说在
go build
的时候,运行镜像的基础环境已经完成了;同时,每个部分之间的缓存判断是独立的,因此就算修改了构建部分的 Dockerfile,运行部分的构建依然可以使用缓存。
3.2.1 Go
# syntax = docker/dockerfile:experimental FROM golang:1.14.2-alpine3.11 as base FROM base as module ENV GOPROXY https://mirrors.aliyun.com/goproxy/ ENV GO111MODULE on WORKDIR /app ADD go.mod . ADD go.sum . RUN --mount=type=cache,target=/go/pkg/mod,id=go,sharing=locked \ sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ apk update && \ apk add git && \ go mod download -x FROM module as builder WORKDIR /app COPY . . RUN --mount=type=cache,target=/go/pkg/mod,id=go,sharing=locked \ go build FROM alpine:3.12.0 as runner WORKDIR /app RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ apk update && \ apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo "Asia/Shanghai" > /etc/timezone && \ apk del tzdata COPY --from=builder /app/go /app/go CMD ["./go"] # syntax = docker/dockerfile:experimental
3.3 使用 BuildKit
四、成功率和稳定性
基础镜像尽量不使用类似
latest
这种非固定镜像的 Tag,尽量使用精确的版本号比如说:node:14.13.0-alpine3.12
五、时区问题
由于大部分镜像的时区都不是东八区,因此在构建成镜像时时间判断、Log 时间标记可能都会出现问题,因此时区也是需要考虑的问题
通过
Dockerfile
解决(Alpine
):RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo "Asia/Shanghai" > /etc/timezone && \ apk del tzdata
运行时解决(通过同步服务器的宿主机时间):
# docker run -v /etc/localtime:/etc/localtime # docker-compose volumes: - /etc/localtime:/etc/localtime:ro