Docker之构建镜像

发布于:2024-04-26 ⋅ 阅读:(28) ⋅ 点赞:(0)

一、基于容器生成镜像

        容器启动后是可写的,所有写操作都保存在顶部的可写层中。可以通过docker commit命令将现有的容器进行提交来生成新的镜像。

        具体的实现原理是通过对可写层的修改生成新的镜像,因为联合文件系统所允许的层数是有限的,建议通过 Dockerfile构建镜像

        docker commit命令用于从容器中创建一个新的镜像,其语法

 docker commit [选项]容器[仓库[:标签]]

-a选项指定提交的镜像作者;-c选项表示使用Dockerfile指令来创建镜像;-p选项表示在执行提交命令commit时将容器暂停。

二、Dockerfile

        Dockerfile 可以定义镜像内容,其是由一系列指令和参数构成的脚本,每一条指令构建一层,因此每一条指令的内容就是描述该层应当如何构建,一个Dockerfile包含了构建镜像的完整指令。Docker 通过读取一系列Dockerfile指令自动构建镜像。

        镜像的定制实际上就是定制每一层所添加的配置文件。将每一层修改、安装、构建、操作的命令都写入一个Dockerfile脚本,使用该脚本构建、定制镜像,可以解决基于容器生成镜像无法重复、构建缺乏透明性和体积偏大的问题。创建Dockerfile之后,当需要定制自己额外的需求时,只需在Dockerfile上添加或者修改指令,重新生成镜像即可。

三、Dockerfile的基本语法

使用docker build命令,其基本语法

 docker commit [选项]容器[仓库[:标签]]

 # 使用当前目录作为构建上下文的简单构建命令
 docker build .
 Sending build context to Docker daemon 6.51 MB
 ...

        大多数情况下,最好将Dockerfile 和所需文件复制到一个空的目录中,再以这个目录为构建上下文进行构建。

        一定要注意不要将多余的文件放到构建上下文中,特别是不要把/、/usr路径作为构建上下文,否则构建过程会相当缓慢甚至失败。

        按照习惯,将Dockerfile文件直接命名为“Dockerfile”,并置于构建上下文的根位。否则,执行镜像构建时就需要使用-f选项指定Dockerfile文件的具体位置

 docker build -f Dockerfile 文件路径 .
 # 点号(.)表示当前路径
 ​
 # 通过-t(--taq)选项指定构建的新镜像的仓库名和标签
 docker build -t shykes/myapp .
 ​
 # 在执行build命令时添加多个-t选项(带参数)将镜像标记为多个仓库
 docker build -t shykes/myapp:1.0.2-tshykes/myapp:latest .

Docker 守护进程逐一执行Dockerfile中的指令。

        Docker 将重用过程中的中间镜像(缓存),以加速构建过程。构建缓存仅会使用本地生成链上的镜像,如果不想使用本地缓存的镜像,也可以通过--cache-from 选项指定缓存。如果通过--no-cache选项禁用缓存,则将不再使用本地生成的镜像链,而是从镜像仓库中下载。

构建成功后,可以将所生成的镜像推送到Docker注册中心。

四、Dockerfile格式

 # 注释  

指令 参数

  • 指令不区分大小写,但建议大写。指令可以指定若干参数。
  • Dockerfile文件必须以FROM指令开头,该指令定义构建镜像的基础镜像。FROM指令之前唯一允许的是ARG指令(用于定义变量)。
  • 以“#”符号开头的行都将被视为注释,除非是解析器指令(Parser Directive)。行中其他位置的“#符号将被视为参数的一部分。
  • 解析器指令是“#指令 =值”格式的一种特殊类型的注释,单个指令只能使用一次。
  • 一旦注释、空行或构建器指令被处理,Docker 就不再搜寻解析器指令,而是将格式化解析器指令的地 任何内容都作为注释,并且判断解析器指令。因此,所有解析器指令都必须位于Dockerfile的首部。
  • Docker 可使用解析器指令 escape 设置用于转义字符的字符。如果未指定,则默认转义字符为反斜杠“”。转义字符既用于转义行中的字符,也用于转义一个新的行,这让Dockerfile指令能跨越多行。
 # escape=\
 或者
 # escape=`

将转义字符设置为反引号()在Windows系统中特别有用,默认转义字符“\”是目录路径分隔符。

五、.dockerignore 文件

要提高构建性能,可通过将.dockerignore文件添加到构建上下文中来定义要排除的文件和目录

 #注释
     */temp*
     */*/temp*
     temp?

除了以#开头的注释行,其他3 行分别表示在根的任何直接子目录中排除名称以 temp 开头的文件和目录从根目录下两级的任何子目录中排除以 temp 开头的文件和目录排除根目录中名称为 temp 的单字符扩展名的文件和目录

Docker 还支持一个特殊的通配符字符串“**”,它匹配任何数量的目录(包括零)。例如,*/.go将排除所有目录中以.go结尾的所有文件,包括构建上下文的根。

六、 Dockerfile 常用指令

1、FROM—设置基础镜像

FROM指令可以使用以下3种格式。

 FROM <镜像> [AS<名称>]  

 FROM <镜像>[:<标签>][AS<名称]  

 FROM <镜像>[@<摘要值>][AS<名称>]

  • FROM为后续指令设置基础镜像。“镜像”参数可以指定任何有效的镜像,特别是可以从公开仓库下载的镜像。
  • FROM可以在同一个Dockerfile文件中多次出现,以创建多个镜像层。
  • 通过添加“AS <名称>”来为此构建阶段构建的镜像指定一个名称,这个名称可用于在后续的FROM指令和COPY--from=<name|index>指令中引用此阶段构建的镜像。
  • “标签”“摘要值”参数是可选的。如果省略其中任何一个,构建器将默认使用“latest”作为要生成的镜像的标签。

2、RUN—运行指令

RUN指令可以使用以下两种格式。

RUN <命令>

RUN ["可执行程序","参数1","参数2"]

  • 第1种是shell格式,命令在shell环境中运行,在Linux系统中默认为/bin/sh-c命令,在Windows系统中为cmd/S/C命令。
  • 第2种是exec格式,不会启动shell环境。

RUN指令将在当前镜像顶部创建新的层,在其中执行所定义的命令并提交结果。提交结果产生的镜像将用于Dockerfile的下一步处理。

分层的RUN指令和生成的提交结果符合Docker的核心理念。

exec格式可以避免shell字符串转换,能够使用不包含指定shell可执行文件的基本镜像来运行RUN命令。

在shell格式中,可以使用反斜杠“\”将单个RUN指令延续到下一行

RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'

# 也可以将这两行指令并到一行中
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME

# 如果不使用/bin/sh,改用其他shell,则需要使用exec格式并以参数形式传入所要使用的shell
RUN ["/bin/bash", "-c","echo hello"]

3、CMD—指定容器启动时默认执行的命令

CMD指令可以使用以下3种格式。

CMD ["可执行程序","参数1","参数2"]              # exec格式

CMD ["参数1","参数2"]                                    # 提供ENTRYPOINT指令的默认参数

CMD 命令 参数 1 参数 2                                 # shell格式

一个Dockerfile文件中只能有一个CMD指令,如果列出多个CMD指令,则只有最后一个CMD指令有效。

CMD的主要作用是为运行中的容器提供默认值。

CMD一般是整个Dockerfile的最后一条指令,当Dockerfile完成了所有环境的安装和配置后,使用CMD指示docker run命令运行镜像时要执行的命令。

CMD指令使用shell 或exec格式设置运行镜像时要执行的命令。

# shell格式
FROM ubuntu
CMD echo "This is a test."| wc -

# JSON数组表示命令
FROM ubuntu
CMD ["/usr/bin/wc","--help"

如果希望容器每次运行同一可执行文件,则应考虑组合使用ENTRYPOINT和CMD指令

如果用户执行dockerrun命令时指定了参数,则该参数会覆盖CMD指令中的默认定义。

RUN实际执行命令并提交结果;CMD 在构建镜像时不执行任何命令,只是为镜像定义想要执行的命令。

4、LABEL—向镜像添加标记

LABEL <键>=<值> <键>=<值> <键>=<值>...

LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-valie="foo"
LABEL version="1.0"
LABEL description=”这段文本表明 \
标记可以使用多行“

一个镜像可以有多个标记。要指定多个标记,Docker建议尽可能将它们合并到单个LABEL指令中。

5、EXPOSE—声明容器运行时监听的网络端口

EXPOSE <端口>[<端口>...]

EXPOSE指令通知容器在运行时监听指定的网络端口。可以指定TCP或UDP端口,默认是TCP端口。

EXPOSE不会发布该端口,只是起到声明作用。要发布端口,必须在运行容器时使用-p选项以发布一个或多个端口,或者使用-P选项发布所有暴露的端口。

6、ENV——指定环境变量

ENV <键> <值>                        # 将单个变量设置为一个值

ENV <键>=<值> ...                   # 允许一次设置多个变量

ENV 指令以键值对的形式定义环境变量。该值会存在于构建镜像阶段的所有后续指令环境中,也可以在运行时被指定的环境变量替换。

7、COPY—将源文件复制到容器

COPY [--chown=<用户>:<组>]<源>…<目的>

COPY [--chown=<用户>:<组>]["<源>",...,"<目的>"]

--chown选项只能用于构建Linux容器,而不能在Windows 容器上工作。因为用户和组的所有权概念不能在Linux和Windows之间转换,所以对于路径中包含空白字符的情形,必须采用第2种格式。

COPY指令将指定源路径的文件或目录复制到容器文件系统指定的目的路径中。

COPY指令可以指定多个源路径,但文件和目录的路径将被视为相对于构建上下文的源路径。

COPY hom* /mydir/ 			# 添加所有以"hom"开头的文件
COPY hom?.txt /mydir/	 	#?用于替换任何单字符,如"home.txt"

目的路径可以是绝对路径,也可以是相对于工作目录

COPY test relativeDir/ 		# 将“test”添加到相对路径'WORKDIR'/relativeDir/
COPY test /absoluteDird		# 将“test”添加到绝对路径/absoluteDir/

COPY指令遵守以下复制规则。

  • 源路径必须位于构建上下文中,不能使用指令 COPY ./something/something,因为 docker build 命令的第1步是发送上下文目录及其子目录到Docker守护进程中。
  • 如果源是目录,则复制目录的整个内容,包括文件系统元数据。注意,目录本身不会被复制,被复制的只是其内容。
  • 如果源是任何其他类型的文件,则它会与其元数据被分别复制。在这种情形下,如果目的路径以斜杠(/)结尾,则它将被认为是一个目录,源内容将被写到“<目的>/base(<源>)”路径中。
  • 如果直接指定多个源,或者源中使用了通配符,则目的路径必须是目录,并且必须以斜杠(/)结尾。
  • 如果目的路径不以斜杠结尾,则它将被视为常规文件,源内容将被写入目录路径。
  • 如果目的路径不存在,则其会与其路径中所有缺少的目录一起被创建。

复制过来的源文件在容器中作为新文件和目录,它们都以 UID 和GID 为 0的用户和组账号的身份被创建,除非使用--chown选项明确指定用户名、组名或UID/GID组合。

还可以使用-from=<namelindex>选项将源位置设置为之前构建阶段

8、ADD—将源文件复制到容器

ADD [--chown=<用户>:<组>] <源>...<目的>

ADD [--chown=<用户>:<组>]["<源>",..."<目的>"]

ADD指令的可以使用URL地址指定

ADD指令的归档文件在复制过程中能够被自动解压缩。

9、ENTRYPOINT—配置容器的默认入口点

ENTRYPOINT ["可执行文件","参数1","参数2"]            # exec格式(首选)

ENTRYPOINT 命令 参数1 参数2                                 # shell格式

# 将使用Nginx 镜像的默认内容启动nginx监听端口80
docker run -i -t --rm -p 80:80 nginx

        docker run <镜像>的命令行参数将附加在exec格式的ENTRYPOINT指令所定义的所有元素之后,并将覆盖使用 CMD 指令所指定的所有元素。这种方式允许参数被传递给入口点,即 docker run <镜像>-d 命令将-d 参数传递给入口点。用户可以使用 docker run--entrypoint 命令覆盖ENTRYPOINT指令。

        shell格式的ENTRYPOINT 指令防止使用任何CMD或run命令行参数,其缺点是ENTRYPOINT指令将作为/bin/sh -c 的子命令启动,不传递任何其他信息。这就意味着可执行文件将不是容器的第 1个进程(PID1),并且不会接收UNIX信号,因此可执行文件将不会从docker stop<容器>命令中接收到SIGTERM(中止信号)。

在Dockerfile中只有最后一个ENTRYPOINT 指令会起作用。

10、VOLUME—创建挂载点

VOLUME ["挂载点路径”]

VOLUME 指令创建具有指定名称的挂载点,并将其标记为从本地主机或其他容器可访问的外部挂载。挂载点路径可以是JSON数组VOLUME ["/var/log/"]或具有多个参数的纯字符串,如VOLUME/var/log 或VOLUME/war/log/var/db。

11、WORKDIR—配置工作目录

WORKDIR 工作目录路径

WORKDIR指令为Dockerfile中的任何RUN、CMD、ENTRYPOINT、COPY和ADD指令设置工作目录,如果该目录不存在,则将被自动创建,即使它没有在任何后续的Dockerfile指令中被使用。可以在一个Dockerfile文件中多次使用WORKDIR指令。如果提供了相对路径,则该路径将相对于前面WORKDIR指令的路径。

WORKDIR /a
WORKDIR b
WORKDIRc
RUN pwd

# 在此Dockerfile中,最终pwd命令的输出是/a/b/c。

12、其他指令

USER 指令设置运行镜像时使用的用户名(或UID)和可选的用户组(或GID),Dockerfile 中的任何RUN、CMD和ENTRYPOINT指令也会使用这个被设置的身份。

ARG指令定义一个变量,用户可以在使用--build-arg <varname>=<value>标志执行dockerbuild命令构建时将其传递给构建器。如果用户指定了一个未在Dockerfile 中定义的构建参数,构建将输出错误。

SHELL指令用于指定shell格式以覆盖默认的shell。

七、操作

7.1、用docker commit命令基于容器构建镜像

这种方法的基本步骤是:运行容器-修改容器-将容器保存为新的镜像。

1、以交互方式启动CentOS容器

[root@docker-a ~]# docker run -it centos /bin/bash
Unable to find image 'centos:latest' locally
latest: Pulling from library/centos
a1d0c7532777: Pull complete 
Digest: sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177
Status: Downloaded newer image for centos:latest
[root@39314c4bf9c7 /]# 

2、在容器中执行以下命令,编辑用于安装Nginx软件包的yum源定义文件

# 配置nginx软件包的yum源定义文件,将原来的yum文件打包或全部删除(防止影响),下面使用全部删除
[root@4d31f1ee0762 ~]# rm -f ./CentOS-Linux-*
[root@4d31f1ee0762 ~]# vi /etc/yum.repos.d/nginx.repo
[root@4d31f1ee0762 /]# cat /etc/yum.repos.d/nginx.repo 
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=0
enabled=1
# 安装nginx
[root@4d31f1ee0762 ~]# yum install nginx

3、基于该容器生成新的镜像

# 退出容器
[root@4d31f1ee0762 ~]# exit
exit

# 查看容器ID
[root@docker-a ~]# docker ps -a
CONTAINER ID   IMAGE     ...    STATUS                      PORTS     NAMES
4d31f1ee0762   centos    ...   Exited (0) 13 seconds ago     musing_engelbart

# 执行docker commit命令,将该容器提交并在本地生成新的镜像
[root@docker-a ~]# docker commit 4d31f1ee0762 centos-with-nginx
sha256:00e543806f6a7705fc39c5b41a679dcd4da674b52636399be6a79d0bd38676c1

# 查看该镜像的基本信息
[root@docker-a ~]# docker images centos-with-nginx
REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
centos-with-nginx   latest    00e543806f6a   20 seconds ago   248MB

# 通过docker history命令进一步验证镜像的构建过程和镜像的分层结构
[root@docker-a ~]# docker history centos-with-nginx
IMAGE         CREATED         CREATED BY         SIZE                      COMMENT
00e543806f6a  35 seconds ago  /bin/bash          16.7MB    
5d0da3dc9764   2 years ago      /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B  
<missing>      2 years ago      /bin/sh -c #(nop)  LABEL org.label-schema.sc…   0B   
<missing>      2 years ago      /bin/sh -c #(nop) ADD file:805cb5e15fb6e0bb0…  231MB 

4、基于新的镜像启动新容器

# 基于新的镜像启动一个容器
[root@docker-a ~]# docker run -it centos-with-nginx /bin/bash
[root@670d26e56e72 /]# nginx		# 启动Nginx
[root@670d26e56e72 /]# ps -aux		# 查看相关进程
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0  12052  3336 pts/0    Ss   05:20   0:00 /bin/bash
root          15  0.0  0.0  27076   876 ?        Ss   05:20   0:00 nginx: master process nginx
nginx         16  0.0  0.0  45792  4472 ?        S    05:20   0:00 nginx: worker process
root          17  0.0  0.0  47588  3564 pts/0    R+   05:20   0:00 ps -aux

5、根据需要将镜像推送到Docker Hub或其他注册中心

7.2、查看官方镜像的Dockerfile

可前往Docker Hub网站或其他网站参考官方镜像的Dockerfile。

7.3、使用docker build命令基于Dockerfile构建镜像

基本步骤是:准备构建上下文--编写Dockerfile---构建镜像。多数情况下使基于一个已有的基础镜像构建新的镜像。

下面以在CentOS镜像的基础上安装Nginx服务器软件构建新的镜像为例

1、准备构建上下文

# 建立一个目录,用作构建上下文,并准备所需的文件
[root@docker-a ~]# mkdir dockerfile-test && cd dockerfile-test
[root@docker-a dockerfile-test]# touch nginx.repo
[root@docker-a dockerfile-test]# touch Dockerfile
[root@docker-a dockerfile-test]# vi nginx.repo 
[root@docker-a dockerfile-test]# cat nginx.repo 
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=0
enabled=1

2、编写Dockerfile文件

# 第一层镜像:基于centos
FROM centos:latest

LABEL auther xxx

# 定义环境变量
ENV pkg nginx
# 定义工作的环境路径
WORKDIR /etc/yum.repos.d

# 第二层:配置centos的软件仓库源
RUN rm -f ./CentOS-Linux-*

# 第三层:将docker主机上的文件拷贝到镜像第三层文件系统中
# COPY ./nginx.repo /etc/yum.repos.d/
RUN curl -o ./CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo

# 第四层
RUN yum clean all && yum makecache

# 第五层:安装nginx-注意,所有操作都免交互
RUN yum install $pkg -y


# 第六层:修改Dockerfile,添加一个修改首页内容的信息:
RUN echo "Hello! This is nginx server" > /usr/share/nginx/html/index.html

# 声明这个镜像使用的是80端口
EXPOSE 80

# 第七层 启动nginx,容器启动时执行的命令,如果有多个CMD,在容器启动时就只执行最后一个
CMD nginx -g "daemon off;"  # 这句不会退出容器
CMD echo "123456"			  # 若加上这句,容器在执行完后会自动退出
# 或者:
# ENTRYPOINT 跟CMD是同一个意思,如果同时用,CMD就变成ENTRYPOINT的一个参数
# ENTRYPOINT ["ls", "-s", "-l"]

3、使用docker build命令构建镜像

[root@docker-a dockerfile-test]# docker build -t centos-with-nginx:1.0 .	# 最后的点号表示构建上下文为当前目录
...
Building 0.0s (10/10) FINISHED                                         
...      

# 查看刚构建的镜像信息
[root@docker-a dockerfile-test]# docker images centos-with-nginx:1.0 
REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
centos-with-nginx   1.0       cac33bbb93a5   26 minutes ago   343MB

4、基于该镜像启动容器进行测试

[root@docker-a dockerfile-test]# docker run --rm -d -p 8000:80 --name my-nginx centos-with-nginx:1.0 
6744c52a9e0859b7542cc60be808c6dfffd73e20bbe45c3f01b3d00b3434e685

# 列出正在运行的容器来验证该容器
[root@docker-a dockerfile-test]# docker ps
CONTAINER ID   IMAGE    ...     STATUS             PORTS                   NAMES
ceb0c4687474   centos-with-...  ... Up 2 seconds   0.0.0.0:8000->...   my-nginx

# 访问Nginx官网进行测试
[root@docker-a dockerfile-test]# curl 127.0.0.1:8000
<!DOCTYPE html P
...
# 还可以使用浏览器访问进行实际测试

5、实验完毕,停止该容器,该容器会被自动删除

[root@docker-s dockerfile-test]# docker stop my-nginx
my-nginx
[root@docker-s dockerfile-test]# docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

7.4、构建缓存测试

在构建过程中,每次生成一层新的镜像时这个进行就会被缓存。即使是后面的某个步骤构建失败,再次构建时也会从失败的那层镜像的前一条指令继续往下执行。

# 修改Dockerfile,修改首页内容的信息:
Hello Pleace test the nginx server

[root@docker-adockerfile-test]# docker build -t centos-with-nginx:2.0 .
...   	# 此处执行使用了缓存,没有重新执行命令,而是从修改处执行     
 => [7/7] RUN echo "Hello Pleace test the nginx server" > /usr/share/nginx/html/index.html        0.4s
 => exporting to image          		0.1s
 => => exporting layers         		`0.1s
 => => writing image sha256:3eea5ebd876ec132bbb7b0c1ca7dc8cb5fc68c3f6b5
 ...
 
# 同上、基于该镜像启动容器,访问Nginx首页,可以发现输出了Hello Pleace test the nginx server

如果不想使用这种缓存功能,可以在执行构建命令时加上--no-cache选项

docker build --no-cache -t centos-with-nginx:2.0 .