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

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

一、交互式创建

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

    图片中,首字母含义:
  • 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与镜像层级的关系


关键字详解:

  • FROM
    每一个Dockerfile文件都从这个关键字开始,它指明从哪个基础镜像开始进行你的镜像构建。通常,我们将docker hub里面提供的官方镜像作为基础镜像,例如:FROM centos:7或者FROM python:2.7,如果你想从空白镜像开始,可以使用FROM scratch
  • RUN
    RUN后面可以跟随任意有效的linux命令,一般用来进行我们在基础镜像上的变更操作,由于每个RUN命令都会在镜像上增加一层,因此,建议将多个linux命令整合成一个命令。可使用类似如下方式:
  • 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,并使用该镜像运行一个容器,然后删除。


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

修改参数

再来看一个使用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 .
    构造过程如下:

以上过程解释如下:

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


多阶段构建

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

#include <stdio.h>
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

镜像的命名空间

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

<registry URL>/<User or Org>/<name>:<tag>
  • <registry URL>
    镜像存放的仓库的名字,通常是一些公用的镜像存储及管理服务器,例如docker hub。
  • <User or Org>
    在前面的仓库中定义的一个用户或者组织。
  • <name>:<tag>
    上面已经介绍过,就是你的镜像名以及标签。
    一些特殊的约定:
  • 如果忽略<registry URL>,那么默认是使用docker hub仓库。
  • 如果忽略<tag>名称,那么默认是latest。
  • 如果是docker hub上的官方镜像,那么镜像名不需要<User or Org>。

常见镜像名称解释:

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 <my secret password>
  3. 推送镜像
    docker image push gnschenker/alpine:1.0
    以上是默认使用docker hub仓库,你需要有相关的账号密码。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容