这篇文章继续给大家介绍Docker,主要讲解Dockerfile的内容,包含常用指令详解,多阶段构建,Dockerfile,玩好dockerfile,对k8s的学习也会有帮助的。
目录
参考链接:https://docs.docker.com/engine/reference/builder/
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
1、指定基础镜像,必须放在首行。(注意,scratch是保留字,并不是镜像!如果使用了该关键字表示不依赖于任何镜像)
2、导入ONBUILD指令代码
3、起别名,用做多阶段构建
在容器里运行指令
将宿主机的文件拷贝到容器中,如果容器目录不存在,则会自动创建,也可以在多阶段拷贝数据,使用--from指定源
与COPY作用不同的是,可以解压tar包文件并删除源文件
将变量传递给容器,容器运行时会有该变量,在容器启动或创建时,可以使用-e,--env选项替换默认值。
对容器的指定路径生成随机存储卷
露容器内的端口,外部使用随机端口映射时,可以暴露此处定义的服务端口,通常建议暴露的端口和服务有关,将来用户使用-P时,就可以根据容器暴露的端口自动做映射
声明作者信息,但是有局限,官方已经弃用
为镜像打标签,基于KEY=VALUE方式打标签
如果用户启动容器时没有指定COMMAND,则使用该指令作为默认的COMMAND,如果用户启动容器时指定了COMMAND,则该指令会被无视,换句话说,该指令可以在容器启动时会被覆盖,如果CMD和ENTRYPOINT都不指定,则默认为FROM指令的COMMAND。
如果FROM的值为scratch,则没有COMMAND,启动的时候需要用户自己指定。
指定容器的工作目录,当用户连接到容器时,就默认在该目录,若不指定,则默认在/路径,相当于docker run -w /path/
[root@Docker01 ~]# cat dockerfile
# 指定基础镜像
FROM centos:7
# 声明作者信息,官方已经弃用,只能声明一行,建议使用LABEL
MAINTAINER koten
# LABEL可以基于key=value方式指定信息
LABEL author=koten \
subject=linux \
hobby=docker
# 修改centos镜像的yum源为国内地址,并安装nginx,sshd服务(运行时,不能阻塞RUN指令,否则终止编译)
RUN sed -e 's|^mirrorlist=|#mirrorlist=|g' \
-e 's|^#baseurl=http://mirror.centos.org/centos|baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos|g' \
-i.bak \
/etc/yum.repos.d/CentOS-*.repo && \
yum -y install epel-release && \
sed -e 's!^metalink=!#metalink=!g' \
-e 's!^#baseurl=!baseurl=!g' \
-e 's!https\?://download\.fedoraproject\.org/pub/epel!https://mirrors.tuna.tsinghua.edu.cn/epel!g' \
-e 's!https\?://download\.example/pub/epel!https://mirrors.tuna.tsinghua.edu.cn/epel!g' \
-i /etc/yum.repos.d/epel*.repo && \
yum -y install nginx openssh-server initscripts && \
rm -rf /var/cache/yum
# 指定工作目录,若不指定则默认路径为"/"
WORKDIR /usr/local/nginx/html
# 将宿主机的文件拷贝到容器的指定路径
COPY config/games.conf /etc/nginx/conf.d/
COPY scripts/start.sh /
# 如果文件为tar包,会自动解压并删除源文件。
ADD softwares/koten-games.tar.gz /usr/local/nginx/html/
# 暴露容器的80端口
EXPOSE 80 22
# 将容器的指定路径进行持久化,会产生随机(匿名)存储卷
VOLUME /usr/local/nginx/html/
# 向容器传递环境变量,基于key=value的语法格式,当容器运行时,这些变量是存在的
# 在启动容器时,可以使用"-e"来替换此处定义的变量。
ENV author=koten \
subject=linux
# 在构建阶段声明的环境变量,当镜像构建结束后,该变量的生命周期结束!当容器运行时,看不到以下变量哟~
# 在构建阶段,可以使用"--build-arg"来替换此处定义的变量。
ARG AUTHOR=koten \
CLASS=1
RUN mkdir -pv /${AUTHOR}/${CLASS}
# 容器启动时的命令,若用户在启动容器时指定了启动命令,则该指令会被覆盖。
CMD ["tail","-f","/etc/hosts"]
# CMD ["nginx","-g","daemon off;"]
# CMD ["bash","-x","/start.sh"]
# ENTRYPOINT ["bash","-x","/start.sh"]
# CMD ["20"]
# 容器启动时的命令,当其和CMD指令结合使用时,CMD将作为参数传递给ENTRYPOINT。
# ENTRYPOINT指令是无法在容器运行时被替换的,容器运行时的命令会覆盖CMD指令并作为参数传递给ENTRYPOINT指令。
# ENTRYPOINT ["sleep"]
CMD是容器启动时的命令,写在Dockerfile中,当容器启动,后面跟指令的时候,CMD内容会被覆盖;
ENTRRYPOINT也是容器启动时的命令,写在Dockerfile中,当容器启动,后面跟指令的时候,指令会成为ENTRRYPOINT命令的参数;
若容器启动,后面不跟指令,Dockerfile中既写了CMD也写了ENTRRYPOINT,此时CMD内容作为ENTRRYPOINT内容的参数。
若容器启动时,后面跟了指令或内容,Dockerfile中也写了CMD也写了ENTRRYPOINT,此时后面跟的指令内容会代替CMD内容作为ENTRRYPOINT内容的参数。
ENV是直接将dockerfile编写的环境变量作为镜像中的环境变量,在启动容器的时候可以-e去修改dockerfile定义的变量内容;ARG也是定义变量,但是最终不会传递到环境变量中,而是在dockerfile中使用,可以用--build-arg来替换dockerfile中定义的变量。
ARG应用场景:dockerfile中定义了版本号,已经编译成了镜像,我们不能每次都重复编译镜像,所以我们可以通过修改ARG定义的版本号,实现指定版本号的安装部署。
ENV应用场景,例如我们的镜像中服务的端口号,我们可以用ENV定义在运行的时候通过修改ENV,来修改服务的端口。
健康状态检查是查的镜像中服务的状态,当容器指定了健康检查时,除了正常状态外,它还具有健康状态。此状态最初为 starting ,每当健康检查通过时,它就会变成 healthy ,在连续失败一定次数后,就变成了 unhealthy 。底层通常是curl服务的url,判断是否curl通,看$?是0则没有问题非0就是服务有问题。
--interval=DURATION (default: 30s)
容器启动后,间隔多长时间进行健康检查,默认时30秒。也可以是分钟,比如"--interval=5m"。
--timeout=DURATION (default: 30s)
如果单次运行检查花费的时间超过指定的秒数,则认为检查失败,默认超时时间时30秒。
--start-period=DURATION (default: 0s)
失败状态开始计数的时间,即在这个时间段内检查失败将不记录最大的重试次数。默认为0,表示不延迟检查。
(1)若在这个时间段内检查服务是健康的,则其状态会直接变更为"healthy",
(2)若在这个时间段内检查不健康,也不记录最大的重试次数。
--retries=N (default: 3)
重试检查失败的次数,默认值为3。
若失败状态开始计数时间为第10秒,间隔检查的时间为3秒,重试检查失败的次数为3次。
若容器启动延迟了30秒,那么它检查的时间以此是3,6,9,12,15,18......
当他在第12秒检查时,由于已经过了失败状态开始计数时间,所以12,15,18会被记录三次,当第18秒检查的时候,状态会显示失败,当第30秒容器启动后,那么可能在第30秒或者第33秒(容器启动需要时间)状态显示健康
指定容器的运行用户,若不指定,默认为root用户,比如ES服务可能会用到。
值得注意的是,运行的服务使用普通用户运行的话,要注意权限问题,修改属主属组,要注意端口的问题,0 到 1023 的端口号被称为系统端口或保留端口,这些端口号通常只能由系统管理员(root 用户)使用。?
[root@Docker01 dockerfile_ubuntu_nginx]# cat build.sh
#!/bin/bash
docker build -t koten-games:v1.$1 .
docker run -d koten-games:v1.$1
docker ps -l
[root@Docker01 dockerfile_ubuntu_nginx]# cat dockerfile
FROM ubuntu:20.04
RUN useradd -r -s /sbin/nologin -u 2023 koten
RUN apt-get update && \
apt-get -y install nginx openssh-server curl && \
rm -rf cache
RUN sed -ri 's/(user )www-data/\1koten/g' /etc/nginx/nginx.conf && \
sed -i 's/80/8080/g' /etc/nginx/sites-enabled/default && \
chmod 777 -R /var/ /run/
# 指定运行服务的用户,该用户必须对服务的相关文件或目录有读或者写的权限
USER koten
# 配置健康检查
HEALTHCHECK --interval=3s --timeout=1s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8080/ || exit 1
CMD ["nginx","-g","daemon off;"]
当启动多个服务时,例如java和mysql,mysql还没有启动,java启动了,此时若没有健康状态检查就会报错,就只能先手动启动java,再启动mysql了
?? ?基础镜像触发器,构建镜像会构建触发器的相关指令,即当有其他镜像引用该镜像时会调用触发器。简单讲就是父镜像定义了ONBUILD,不管后面是ADD也好,RUN也好,这个镜像在构建容器的时候都不会执行,但是当有子镜像FROM该父镜像,那么子镜像会依次执行父镜像中定义的命令。父镜像中的ONBUILD会优于自己的dockerfile中的命令执行,这也就意味着,如果子镜像中存在与父镜像中的ONBUILD命令相同或者相似的指令,那么这些指令可能会被父镜像中的ONBUILD 命令所覆盖或修改,注意不要冲突了。
? ? 父镜像是否有ONBUILD可以通过docker inspect 查看详细信息查看,注意有的镜像会利用此操作留后门。
首先,创建一个基于 Python 的带有 ONBUILD 指令的 Dockerfile。我们将其命名为Dockerfile_onbuild
# 使用官方 Python 基础镜像
FROM python:3.8-slim
# 设置工作目录
WORKDIR /app
# 定义需要执行的操作
ONBUILD COPY requirements.txt ./
ONBUILD RUN pip install --no-cache-dir -r requirements.txt
ONBUILD COPY . .
将该镜像打包名为PYTHON:v1.0,接下来,在一个新的项目中,您可以使用以下 Dockerfile 调用刚才创建的带有 ONBUILD 指令的镜像。我们将其命名为 `Dockerfile`:
# 使用刚才创建的带有 ONBUILD 指令的镜像
FROM PYTHON:v1.0
# 设置镜像元数据
LABEL maintainer="Your Name <your.email@example.com>"
LABEL version="1.0"
LABEL description="Example project using an ONBUILD base image."
# 开放端口
EXPOSE 8080
# 设置运行时命令
CMD ["python", "app.py"]
此时构建并运行子镜像将触发 ONBUILD 指令,从而安装依赖项并复制应用代码到工作目录。
调用系统命令的shell。这对Windows很有用,Linux无需使用,默认就是linux的shell。
如果有需要可以指定Windows的shell哟,如下所示,支持powershell和CMD。
调用windows的powershell解释器为:SHELL ["powershell", "-command"]
调用windows的cmd解释器为:SHELL ["cmd", "/S", "/C"]
多阶段构建可以减小镜像大小,因为每个阶段只保留了必要的文件和依赖项,而不是将所有的构建环境和依赖项都打包到最终的镜像中。此外,它还可以提高构建速度,因为能够重复使用已经构建好的镜像层。
在dockerfile中可以指定多个FROM ,每个 FROM 指令都可以作为一个独立的构建阶段。在每个阶段中,我们可以安装所需的软件包、编译代码等操作,最终将需要的文件复制到最终的镜像中。
在dockerfile中理论可以指定无数个from,但是实际也就是用2-3个,用多了没有意义,可读性差。
如果不指定别名,那么第一个阶段引用的时候就引用0,第二个就为1,以此类推。
[root@centos201 alpine]# cat Dockerfile
# FROM alpine
FROM alpine AS haha
RUN mkdir -pv /koten-linux && \
cp /etc/hosts /koten-linux
RUN mkdir /koten-haha
RUN dd if=/dev/zero of=/koten-linux/bigfile.log bs=1M count=1024
# FROM alpine
FROM alpine AS xixi
RUN mkdir /kotenxixi
RUN touch /koten-xixi/apps.log
FROM alpine
RUN mkdir /koten-hehe
# COPY --from=0 /koten-linux/hosts /koten-hehe
# COPY --from=1 /koten-xixi/apps.log /koten-hehe
COPY --from=haha /koten-linux/hosts /koten-hehe
COPY --from=xixi /koten-xixi/apps.log /koten-hehe
CMD ["tail","-f","/etc/hosts"]
[root@centos201 alpine]#
下面Dockerfile 中,第一阶段使用 Node.js 构建应用程序并生成静态资源,第二阶段使用 Nginx 镜像作为基础镜像,并将第一阶段生成的静态资源复制到 Nginx 的默认 HTML 目录中,最终运行 Nginx 容器。
# 构建阶段一
FROM node:14.17-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 构建阶段二
FROM nginx:1.21-alpine
COPY --from=builder /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
简而言之是要求构建速度快,镜像体积小。
1、将不常更改的指令放在前面,充分利用缓存镜像;
2、在不影响功能前提下合并多条指令,减少镜像层数,从而减少中间层(中间容器)的启动;
3、使用 .dockerignore 来忽略不需要发送给docker daemon进程的文件,例如开发环境中的缓存文件、日志文件、临时文件等。可以忽略这些文件。
4、修改软件源地址,比如yum,apt,apk源,建议使用国内的。
5、可以使用多阶段构建,每一个 Dockerfile 中的指令都会创建一个新的中间层,前面的阶段生成的镜像层可以被后面的阶段直接复用。
1、删除缓存文件,例如,rm -rf /var/cache/yum。
2、卸载无用的软件包,比如编译安装后的编译器,下载的软件包等都可以被卸载。
3、使用较小的基础镜像,比如alpine。
4、使用多阶段构建,只将必要的文件和依赖放入最后的镜像。
?? ??? ?
注意:1、大多数开源的Linux镜像默认都是用了标准C语言编译器glibC,这会占用很大一部分空间,而alpine使用musl libc和BusyBox构建的Linux发行版;
2、alpine和其它linux发行版相比就是体积小,但也可能会存在部分软件不兼容的情况,如果alpine无法兼容一些软件时,可以考虑使用Ubuntu20.04镜像,因为其72.8MB,最后再考虑使用centos镜像,因为其镜像大小超过200MB。
我是koten,10年运维经验,持续分享运维干货,感谢大家的阅读和关注!