Docker 005 构建镜像

Docker 005 构建镜像

我们可以创建、修改和更新自己的镜像。构建 docker 镜像有两种方法:

  • 使用 docker commit
  • 使用 docker build命令和 Dockerfile文件: 推荐使用

创建 docker hub 账号

构建镜像的过程中,很重要的一步就是共享和发布镜像,可将自己构建的镜像推送到 docker hub 或者自己的私有 Registry 中,这里以创建 docker hub 为例:打开 https://hub.docker.com/signup 创建自己的账号。

# 下面的命令会登录 docker hub,并将认证信息保存起来,以供后用
$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: resn001
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

# 退出登录
$ docker logout
Removing login credentials for https://index.docker.io/v1/

使用 docker commit 命令创建镜像

这里基于之前的 ubuntu 镜像创建一个新容器,然后安装 nginx,

$ docker run --name ubuntu_c01 -it ubuntu /bin/bash\

#以下命令在容器中执行
$ apt-get update
$ apt-get install nginx
$ exit

我们希望把这个容器作为 web 服务器来运行,需要保存该容器的当前状态,这样就避免了每次创建新容器并安装 nginx 了。完成之后,我们需要将修改后的容器

# 创建镜像我们需要用到 docker commit 命令,其格式如下:
# docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]

# 提交镜像到本地
$ docker commit ubuntu_c01 resn001/ubuntu_c01
sha256:ed048991810e6e3684e8ef106c4be9e8631a8c960b4cf29c406a6d11fb5f6c79

# docker commit 可使用的参数
# -a, --author string    指定作者信息
# -c, --change list      将Dockerfile指令应用于创建的映像
# -m, --message string   提交的描述信息
# -p, --pause            提交期间暂停容器,默认开启

# 查看镜像
$ docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
resn001/ubuntu_c01   latest              ed048991810e        11 minutes ago      152MB

# 查看创建的镜像的详细信息
$ docker inspect resn001/ubuntu_c01

# 使用新容器运行一个容器
$ docker run -it  resn001/ubuntu_c01 /bin/bash

使用 Dockerfile 构建镜像

推荐使用Dockerfile来构建镜像。Dockefile 使用基础的基于 DSL 语法的指令来构建镜像。使用 Dockerfile 构建镜像更具备可重复性、透明性和幂等性。

写好 dockerfile 后,就可以使用 docker build 命令构建一个新镜像。

# 示例
$ mkdir static_web
$ cd static_web
$ touch Dockerfile
$ vim Dockerfile
	# Version 0.0.1
	# 
	FROM  ubuntu:18.04
	MAINTAINER resn "[email protected]"
	
	RUN apt-get update && apt-get install -y nginx
	
	# 下面的指令选择一个,只新建 html 文件的指令, 执行后文的命令时可能会有错误
	# RUN echo "daemon off;" >> /etc/nginx/nginx.conf &&  echo 'Hello world,I am a container' >/usr/share/nginx/html/index.html
	RUN echo 'Hello world,I\'m a container' >/usr/share/nginx/html/index.html

	EXPOSE 80

Dockerfile 由一系列指令和参数构成,每条指令都必须为大写字母,且后面要跟一个参数。

指令会按从上到下的顺序依次执行,所以指令的安排需合理。

扫描二维码关注公众号,回复: 9617537 查看本文章

每条指令都会创建一个新的镜像层并对镜像进程提交,Docker 执行 Dockerfile 中指令的流程大致如下:

  • Docker 从基础镜像运行一个容器
  • 执行一条指令,对容器做出修改
  • 执行类似 docker commit 的操作,提交一个新的镜像层
  • Docker在基于刚提交的镜像运行一个新的容器
  • 执行Dokcerfile 中的下一条指令,知道所有指令都执行完毕

从上面可以看出,如果用户的 Dockerfile 由于某些原因没有正常结束(例如某条执行失败),那么用户还是可以得到一个可用的镜像,这样的好处是,可以基于该失败的镜像运行一个有交互功能的容器,用于排查容器没有正常结束的原因。

每个 Dockerfile 的第一条指令必须是 FROM,用于指定一个已存在的镜像,后续指令都将基于该镜像进行操作,该镜像被称为基础镜像(base image)。

接着指定 MAINTAINER指令,该指令的用于告诉 docker 该镜像的作者是谁,以及作者的 email。

之后是两条 RUN 指令,RUN 指令会在当前镜像中运行执行的命令,上面的例子中,第一条RUN指令更新了 APT 仓库,并安装了nginx;第二条指令创建了/usr/share/nginx/html/index.html 文件。

注意每条 RUN 指令都会创建一个新的镜像层,如果该指令创建成功,则会将此镜像提交,之后继续执行下一条指令。

默认情况下,RUN 指令会在 shell 中使用命令包装器/bin/sh -c 来执行,如果在不支持 shell 的平台上运行或者不希望使用 shell 运行,也可以使用 exec 格式的RUN 指令

RUN ["apt-get", "install", "-y", "nginx"]

使用 exec 格式的 RUN 指令需要使用一个数组来指定要运行的命令及该命令的参数

接着执行 EXPOSE 指令,改指令告诉 Docker 该容器内的应用程序将会使用容器的指定端口(这里是 80 端口),基于安全方面的考虑,Docker 不会自动打开该端口,而是需要用户在使用 docker run命令时指定需要打开哪些端口。可以指定多个 EXPOSE 指令来向外部公开多个端口。

使用 Dockerfile 构建镜像的过程

执行 docker build 命令,Dockerfile 中的所有指令都会被执行并提交,并在命令成功结束后欧返回一个新镜像,构建镜像的过程如下:

# 构建过程
# 注意命令最后的 点 ,用于指定当前目录
$ docker build -t="t_repo/static_web" .
Sending build context to Docker daemon  2.048kB
Step 1/5 : FROM  ubuntu:18.04
 ---> ccc6e87d482b
Step 2/5 : MAINTAINER resn "[email protected]"
 ---> Running in 37b371f311d4
Removing intermediate container 37b371f311d4
 ---> 334111fe32cb
Step 3/5 : RUN apt-get update && apt-get install -y nginx
 ---> Running in b89fe0db4491
Get:1 http://security.ubuntu.com/ubuntu bionic-security InRelease [88.7 kB]
Get:2 http://archive.ubuntu.com/ubuntu bionic InRelease [242 kB]
......
Fetched 17.6 MB in 45s (396 kB/s)
Reading package lists...
Reading package lists...
Building dependency tree...
Reading state information...
The following additional packages will be installed:
  fontconfig-config fonts-dejavu-core geoip-database iproute2 libatm1 libbsd0
......
Processing triggers for libc-bin (2.27-3ubuntu1) ...
Removing intermediate container b89fe0db4491
 ---> 52c2e58d9708
Step 4/5 : RUN echo 'Hello world,I am a container' >/usr/share/nginx/html/index.html
 ---> Using cache
 ---> bf55e11f0c27
Step 5/5 : EXPOSE 80
 ---> Using cache
 ---> d54272ef62f2
Successfully built d54272ef62f2
Successfully tagged t_repo/static_web:latest

使用 docker build 命令构建镜像时,通过 -t 参数可为镜像指定仓库和名称,上面的例子中,仓库名称为t_repo,镜像名称为static_web。在此命令中还可以为镜像设置一个标签:

# 指定标签方法: “镜像名:标签名”, 不指定标签名时,标签名默认为 latest
# 注意命令最后的 点 ,用于指定当前目录
$ docker build -t="t_repo/static_web:v1" .

还可以通过 git 仓库指定 Dockerfile 文件

# 通过 git 仓库指定 Dockfile 文件
$ docker build -t="t_repo/static_web:v1" git@github:t_repo/static_web

Dcoker1.5.0 之后可通过-f 参数指定指定的 dockerfile 文件,

# 这里的路径汇总的 file 可不比命名为 Dockerfile,但必须要位于构建上下文中
$ docker build -t="t_repo/static_web:v1" -f path/to/file

构建时忽略指定的文件

如果在构建上下文的根目录指定 .dockerignore 文件,那么该文件内容会被按行进行过滤匹配,类似于 .gitignore 文件。该文件用来设置哪些文件不被当做构建上下文的一部分,因此可以防止他们被上传到 docker 守护进程中去,该文件的匹配规则采用了 Go 语言的 filepath

在构建镜像的过程中,Dockerfile 中的每条指令都会被依次执行,并作为构建过程的最终结果返回了新的镜像 ID。构建的每一步机器对应指令都会地理运行,并且在输出最终镜像ID 之前,Docker 会提交每部的构建结果。

构建失败时

以上面的构建过程为例,这里简单描述,假设 第三步指令为 “RUN apt-get update && apt-get install -y ngin” (nginx 少了个 x),那么在构建时,会在此处报错,这个时候,我们只需要使用上一步的镜像 ID:334111fe32cb,新建一个容器,将第三步的指令在新容器中执行一次 进行排错即可,错误排除后,更正第三步的指令即可再次构建。

# 使用第二步中生产的镜像来排错
$ docker run -it 334111fe32cb /bin/bash	

Dockerfile 和构建缓存

Docker 构建镜像的过程是非常聪明的,每一步命令的构建结果都会被提交为镜像,下次构建时,直接从有问题的那一步直接开始,这样可以节省大量时间。前面构建步骤中的镜像层相当于缓存了。

每次构建时,Docker 都就会从第一条发生了变动的指令开始。

如果希望构建时,忽略缓存功能,那么可以使用--no-cache 参数:

$ docker build --no-cache -t -t="t_repo/static_web:v1"

构建缓存的好处——Dockerfile 模板

先看下面的 Dockerfile 内容

	FROM  ubuntu:18.04
	MAINTAINER resn "[email protected]"
	ENV REFERSHED_AT 2020-02-02
	RUN apt-get -qq update

这里通过 ENV 指令指定了变量REFERSHED_AT的值为2020-02-02,基于该模板,如果想刷新一个构建,只需要修改 ENV 指令中的日期即可,

查看镜像

使用 docker images 命令可查看已有的镜像:

# 查看指定镜像
$ docker images t_repo/static_web
# 查看镜像构建历史
# 可查指定镜像的每一层,以及每一层的指令
$ docker history t_repo/static_web
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
5e48955e883e        2 days ago          /bin/sh -c #(nop)  EXPOSE 80                    0B
03055ca4743a        2 days ago          /bin/sh -c echo 'Hello world,I am a containe…   29B
52c2e58d9708        2 days ago          /bin/sh -c apt-get update && apt-get install…   88.3MB
334111fe32cb        2 days ago          /bin/sh -c #(nop)  MAINTAINER resn "[email protected]…   0B
ccc6e87d482b        7 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
<missing>           7 weeks ago         /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B
<missing>           7 weeks ago         /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   745B
<missing>           7 weeks ago         /bin/sh -c [ -z "$(apt-get indextargets)" ]     987kB
<missing>           7 weeks ago         /bin/sh -c #(nop) ADD file:08e718ed0796013f5…   63.2MB

从新镜像启动容器

可使用新建的镜像启动一个容器来验证镜像是否构建正常:

# 看的书中使用的下面的命令,但我这里一直报错,我就改了一下 
# docker run -d -p 80 --name static_web  t_repo/static_web  nginx -g "daemon off"

# 改成了 dockerfile 中指令改为
# RUN echo "daemon off;" >> /etc/nginx/nginx.conf &&  echo 'Hello world,I am a container' >/usr/share/nginx/html/index.html
# 之后重新构建镜像
# docker build -t="web01" .
# 最后使用下面的命令执行成功
$ docker run -d -p 80 --name cweb02  web01:latest  nginx
3c6361a2c550ce660aa6ea5e9767bd3100266b99811f1f735a3349b26a8b267b

遇到的错误有两个:

第一个是镜像构建完成后欧 nginx 无法启动,尝试登录到容器内排错时无法启动容器,报错信息如下

$ docker run -it 24e4642806e8 /bin/bash
Unable to find image '24e4642806e8:latest' locally
docker: Error response from daemon: pull access denied for 24e4642806e8, repository does not exist or may require 'docker login': denied: requested access to the resource is denied.
See 'docker run --help'.

在网上查了之后说是镜像要加标签才可以,就把docker run 命令中的镜像加了标签

# 该命令有错误
$ docker run -d -p 80 --name cweb02  web01:latest  nginx  -g "daemon off"

但执行后查看容器依旧为退出状态,

使用 docker logs a7f0d120458d 后,显示的错误信息为: nginx: [emerg] unexpected end of parameter, expecting ";" in command line

使用 docker run -it web01:latest /bin/bash 登录容器后发现配置文件没有错误,且在容器中可以正常启动 nginx,因此怀疑为 docker 命令问题。

最后修改了命令后,执行成功

docker run -d -p 80 --name cweb02 web01:latest nginx

参数含义:

--name : 指定容器的名称

-d:已 detached 方式在后台运行,适用nginx 守护进程这类需要长时间运行的进程

-p:告诉 docker 需要对外公布的端口, docker 可以通过两种方法在宿主机上分配端口,

  • 第一种在宿主机上随机选择一个位于 32768 和 61000 之间的端口,来映射到容器的 80 端口上
  • 第二种是在宿主机上指定一个具体来端口来映射到容器的 80 端口上
# 查看容器端口分配情况
$ docker ps -l
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                   NAMES
3c6361a2c550        web01:latest        "nginx"             22 minutes ago      Up 22 minutes       0.0.0.0:32769->80/tcp   cweb02

docker port 3c6361a2c550
80/tcp -> 0.0.0.0:32769

docker port 3c6361a2c550 80
0.0.0.0:32769

可以看到 容器的 80 端口映射到了宿主机的 32679 端口。

通过-p参数还可以将容器的端口映射到宿主机的指定端口上:

$ docker run -d -p 80:80 --name cweb03  web01:latest  nginx
056d07bfd4c7b46d8d55236875ab5ad19239b12fc65fec61f5dd995f57028291
$ docker port 056d07bfd4c7
80/tcp -> 0.0.0.0:80
$ docker port 056d07bfd4c7 80
0.0.0.0:80

这里有个需要注意的问题:

当将多个容器的端口都映射到宿主机的某一个端口时,只有一个容器能成功的将端口映射到宿主机的段鸥。

# 将容器的80端口映射到宿主机的8080
$ docker run -d -p 8080:80 --name cweb04  web01:latest  nginx

还可以将端口映射到指定网路接口(IP 地址)的端口上:

# 绑定到指定 IP 的指定端口上
$ docker run -d -p 127.0.0.1:8080:80 --name cweb05  web01:latest  nginx

# 绑定到指定 IP 的随机端口上
$ docker run -d -p 127.0.0.1::80 --name cweb06  web01:latest  nginx
24377ba29740dee11637c68861fd893686c66acd0e86090805c18bb8c40a122d
$ docker port 24377ba29740
80/tcp -> 127.0.0.1:32768
$

对外公布端口的更简单的方式:

$ docker run -d -P --name cweb07  web01:latest  nginx
a0c2041a3a5930d8b713e59f47b4029b9368c121aecbd1abe3887f2c70f0fa71

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                   NAMES
a0c2041a3a59        web01:latest        "nginx"             2 seconds ago       Up 2 seconds        0.0.0.0:32770->80/tcp   cweb07

通过-P参数,可直接将 Dockerfile 中 EXPOSE 指令公开的端口对外公布,会将容器的端口绑定到宿主机的一个随机端口上。

猜你喜欢

转载自www.cnblogs.com/resn/p/12425644.html