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 "resn@a.com" 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 由一系列指令和参数构成,每条指令都必须为大写字母,且后面要跟一个参数。
指令会按从上到下的顺序依次执行,所以指令的安排需合理。
每条指令都会创建一个新的镜像层并对镜像进程提交,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 "resn@a.com" ---> 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 "resn@a.com" 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 "resn@a.c… 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 指令公开的端口对外公布,会将容器的端口绑定到宿主机的一个随机端口上。
来源:https://www.cnblogs.com/resn/p/12425644.html