docker入门(3)--创建和管理镜像

有三种创建自定义镜像的方式

一、交互式创建

  1. 选取基础镜像,运行容器,并进入交互窗口
    docker container run -it --name sample alpine /bin/sh
  2. 进行你所需要的修改,变更。这里假设需要安装ping命令所需的相关软件。
    / # apk update && apk add iputils
    docker入门(3)--创建和管理镜像_第1张图片
  3. 核实ping是否可用了,退出容器,查看容器状态
    / # ping 114.114.114.114
    / # exit
    docker container ls -a | grep sample
    docker入门(3)--创建和管理镜像_第2张图片
  4. 查看我们的容器与基础镜像有什么不同
    docker container diff sample
    docker入门(3)--创建和管理镜像_第3张图片

    图片中,首字母含义:
  • A代表新增文件
  • C代表修改过的文件
  • D代表被删除的文件
  1. 将修改过的容器导出为自己的镜像
    docker container commit sample my-alpine
  2. 查看镜像
    docker image ls
  3. 查看自定义的镜像的创建过程
    docker image history my-alpine

    通过IMAGE的id可以看到最后的一层是我们添加的,并且是在alpine这个镜像的基础上。
    这种方式创建镜像的优缺点:

Manually creating custom images as shown in the previous section of this chapter is very helpful when doing exploration, creating prototypes, or making feasibility studies. But it has a serious drawback: it is a manual process and thus is not repeatable or scalable. It is also as error-prone as any task executed manually by humans.

二、通过Dockerfile创建镜像

一个简单的Dockerfile示例如下:

FROM python:2.7
RUN mkdir -p /app
WORKDIR /app
COPY ./requirements.txt /app/
RUN pip install -r requirements.txt
CMD ["python", "main.py"]

Dockerfile与镜像层级的关系


docker入门(3)--创建和管理镜像_第4张图片

关键字详解:

  • FROM
    每一个Dockerfile文件都从这个关键字开始,它指明从哪个基础镜像开始进行你的镜像构建。通常,我们将docker hub里面提供的官方镜像作为基础镜像,例如:FROM centos:7或者FROM python:2.7,如果你想从空白镜像开始,可以使用FROM scratch
  • RUN
    RUN后面可以跟随任意有效的linux命令,一般用来进行我们在基础镜像上的变更操作,由于每个RUN命令都会在镜像上增加一层,因此,建议将多个linux命令整合成一个命令。可使用类似如下方式:
    docker入门(3)--创建和管理镜像_第5张图片
  • COPY 和 ADD
    COPYADD都是用于将主机中的文件拷贝到镜像中的,不同之处在于,ADD可以拷贝和解压tar文件,还可以通过URL来拷贝网络上的文件到容器中。
    一些示例如下:
COPY . /app        #将上下文目录中所有文件或目录递归拷贝到容器的/app目录下
COPY ./web /app/web        #将上下文目录中的web目录下的内容拷贝到容器的/app/web目录下
COPY sample.txt /data/my-sample.txt        #拷贝单个文件并重命名
ADD sample.tar /app/bin/        #解压tar包内的文件到指定目录
ADD http://example.com/sample.txt /data/        #拷贝远程文件到指定目录
COPY ./sample* /mydir/        #支持源路径中使用通配符

默认,通过COPYADD拷贝到镜像内的文件的UID和GID都为0,如果你需要改变,可以使用如下命令:
ADD --chown=11:22 ./data/files* /app/data/
除了指定UID和GID,你也可以指定用户的用户名称组名称,但是这些名称必须存在与镜像的/etc/passwd 和 /etc/group中,否则Dockerfile构建过程会失败。

  • WORKDIR
    WORKDIR用来定义工作目录或者上下文目录。常见的问题:
RUN cd /app/bin
RUN touch sample.txt

由于每执行一次RUN,都是在原有镜像上添加一个新层,因此上面的命令只是在root目录下新建了sample.txt文件。
正确的切换至一个目录下,并新建文件,命令如下:

WORKDIR /app/bin
RUN touch sample.txt
  • CMD 和 ENTRYPOINT
    CMDENTRYPOINT命令比较特殊,因为除了这两个命令以外的其他命令,都是在构建镜像时就会运行并生效,而这两个命令是在使用镜像启动容器时,才会执行的命令。这两个命令用于告诉Docker在启动容器时,应该执行什么命令,并以何种方式去执行(指定一些参数)。
    两者的区别
    先来看一个典型的Dockerfile示例
FROM alpine:latest
ENTRYPOINT ["ping"]
CMD ["8.8.8.8", "-c", "3"]

ENTRYPOINT来指定需要执行的命令,用CMD来指定该命令所需的参数。
这是首选的推荐用法,也被称为exec格式。我们使用这个Dockerfile来构建一个镜像,命名为pinger,并使用该镜像运行一个容器,然后删除。

docker入门(3)--创建和管理镜像_第6张图片

通过这种方式创建的镜像,我们还可以按照如下方式运行,可以实现修改Dockerfile中 ENTRYPOINT的命令,也可以修改Dockerfile中 CMD的参数,非常灵活。
如下:
修改命令

docker入门(3)--创建和管理镜像_第7张图片
修改参数

再来看一个使用CMD,并且没有ENTRYPOINT的Dockerfile示例:

FROM alpine:latest
CMD wget -O - http://www.baidu.com

当没有明确定义ENTRYPOINT参数时,其默认参数为/bin/sh -c,而CMD内的参数将以字符串形式,传递给该命令,因此以上Dockerfile最终执行的就是:
/bin/sh -c "wget -O - http://www.baidu.com"

一个Dockerfile文档示例

FROM node:9.4        #指定基础镜像
RUN mkdir -p /app        #在镜像的文件系统内,创建一个/app目录
WORKDIR /app        #切换上下文(工作目录)至/app目录下
COPY package.json /app/        #从主机上Dockerfile文件所在目录下,将package.json文件拷贝到镜像中的/app目录下
RUN npm install        #npm会安装package.json内Node.js所需的依赖包
COPY . /app        #与第四步一样,拷贝主机内文件(这里是应用程序文件)到镜像的/app目录下
ENTRYPOINT ["npm"]        #与最后一行的命令结合,启动Node.js服务
CMD ["start"]

通过Dockerfile构造镜像的过程及相关解释

常用的通过Dockerfile构造镜像的过程:

  1. 创建一个空目录cmd_entrypoint。
  2. 进入目录中,创建一个Dockerfile文件,写入如下内容:
FROM alpine:latest
ENTRYPOINT ["ping"]
CMD ["8.8.8.8", "-c", "3"]
  1. 在目录下执行命令:
    docker image build -t pinger .
    当你的配置文件不是默认的名字时,可以使用-f参数指定:
    docker image build -t pinger -f my-Dockerfile .
    构造过程如下:
    docker入门(3)--创建和管理镜像_第8张图片

以上过程解释如下:

  1. 拉取基础镜像alpine,它的id为196d
  2. 执行ENTRYPOINT,会通过第一步的基础镜像生成一个容器9ad9,在容器内执行ENTRYPOINT所需的修改或变更操作等,操作完成后删除容器9ad9,然后新加一层成为新的镜像0669。
  3. 这个镜像将作为下一个操作的基础镜像,然后重复上面的过程,直至最后成为一个你所需的镜像。
    流程也可以参考:


    docker入门(3)--创建和管理镜像_第9张图片

多阶段构建

示例如下:
C程序源文件hello.c:

#include 
int main (void)
{
printf ("Hello, world!\n");
return 0;
}

常用的Dockerfile编写方式如下:

FROM alpine:3.7
RUN apk update &&
    apk add --update alpine-sdk
RUN mkdir /app
WORKDIR /app
COPY . /app
RUN mkdir bin
RUN gcc -Wall hello.c -o bin/hello
CMD /app/bin/hello

这种方式构造的镜像文件如下:

$ docker image ls | grep hello-world
hello-world latest e9b... 2 minutes ago 176MB

多阶段构建Dockerfile如下:

FROM alpine:3.7 AS build
RUN apk update && \
    apk add --update alpine-sdk
RUN mkdir /app
WORKDIR /app
COPY . /app
RUN mkdir bin
RUN gcc hello.c -o bin/hello

FROM alpine:3.7
COPY --from=build /app/bin/hello /app/hello
CMD /app/hello

得到的镜像文件如下:

$ docker image ls | grep hello-world
hello-world-small latest f98... 20 seconds ago 4.16MB
hello-world       latest 469... 10 minutes ago 176MB

多阶段构建优点:减少黑客可攻击的范围、节省内存和磁盘空间、更少的启动容器时间、减少下载镜像所需的带宽。

Dockerfile编写最佳实践

一些建议:

  • 首先,我们要意识到容器的生命周期是短暂的。短暂的,意味着一个运行着的容器可能很快就会被停止以及销毁,而一个增加了新功能的容器,需要我们能够快速的构建和启用。我们需要尽可能的保证能够快速的启动、停止容器内的应用。
  • 我们可能会针对同一个Dockerfile不停的构建自己的镜像。我们应该合理的组织Dockerfile内的每个命令,以尽可能的利用容器构建过程中的缓存功能。例如:
FROM node:9.4
RUN mkdir -p /app
WORKIR /app
COPY . /app
RUN npm install
CMD ["npm", "start"]

修改为:

FROM node:9.4
RUN mkdir -p /app
WORKIR /app
COPY package.json /app/
RUN npm install
COPY . /app
CMD ["npm", "start"]

以上两个Dockerfile一般来说,都会在通过npm install安装依赖包的时候花费大量时间,而针对一个开发项目来说,依赖包的变动的情况是比较少的,大部分时间都是应用程序的变动而导致重新构建镜像,因此将安装依赖包的过程独立出来,利用缓存的功能,可以加速下一次的构建过程。

  • 另一个最佳实践就是尽可能减少你镜像的layer数。通常来说,layer越少,镜像越小,启动速度越快。而最好的减少layer数量的办法就是整合你的命令,因为,在dokcerfile中,每一个以FROM、COPY、RUN这样的关键字开头的行,就意味着一个新的layer。最常见的办法就是整合多个RUN行为一行。例如:
RUN apt-get update
RUN apt-get install -y ca-certificates
RUN rm -rf /var/lib/apt/lists/*

整合为:

RUN apt-get update \
    && apt-get install -y ca-certificates \
    && rm -rf /var/lib/apt/lists/*
  • 通过.dockerignore文件,除去那些不需要拷贝到镜像内的文件,达到减小镜像体积的作用。用法与git中.gitignore一样。
  • 在镜像的文件系统内,减少安装不必要的软件。
  • 合理利用多阶段构建。

三、保存和加载镜像

第三种制作一个容器镜像的方法是,通过一个文件进行导入或加载。因为,一个容器镜像本质上就是一个tar包的文件。请看以下示例:
docker image save -o ./backup/my-alpine.tar my-alpine
上面的命令,将一个镜像保存为指定的tar包。
docker image load -i ./backup/my-alpine.tar
上面的命令,将一个tar包加载为可用的镜像。

管理镜像(分享和运送镜像)

为了能够把我们自定义的镜像运送到我们的生产环境中,我们需要给镜像一个全球唯一的名称,通常这个动作称为:tagging。通常,我们把镜像存放在一个集中管理的地方,以方便他人获取,这个地方称为:仓库(image registries)。

镜像的标签

每个镜像都有一个标签,通常用来指定镜像的版本号。

docker image pull alpine        #没有指定tag时,默认为lates
docker image pull alpine:3.5        #指定tag

镜像的命名空间

一个完整的镜像命名如下:

//:

  • 镜像存放的仓库的名字,通常是一些公用的镜像存储及管理服务器,例如docker hub。

  • 在前面的仓库中定义的一个用户或者组织。
  • :
    上面已经介绍过,就是你的镜像名以及标签。
    一些特殊的约定:
  • 如果忽略,那么默认是使用docker hub仓库。
  • 如果忽略名称,那么默认是latest。
  • 如果是docker hub上的官方镜像,那么镜像名不需要

常见镜像名称解释:

Image Description
alpine Official alpine image on Docker Hub with the latest tag.
ubuntu:16.04 Official ubuntu image on Docker Hub with the 16.04 tag or version.
microsoft/nanoserver nanoserver image of Microsoft on Docker Hub with the latest tag.
acme/web-api:12.0 web-api image version 12.0 associated with the acme org.The image is on Docker Hub.
gcr.io/gnschenker/sampleapp:1.1 sample-app image with the 1.1 tag belonging to an individual with the gnschenker ID on Google's container registry.

推送镜像到一个仓库

  1. 将需要推送的镜像取一个自定义的tag
    docker image tag alpine:latest gnschenker/alpine:1.0
  2. 登录到你的仓库
    docker login -u gnschenker -p
  3. 推送镜像
    docker image push gnschenker/alpine:1.0
    以上是默认使用docker hub仓库,你需要有相关的账号密码。

相关参考:Docker镜像上传到阿里云

你可能感兴趣的:(docker入门(3)--创建和管理镜像)