Contents
  1. 1. 使用docker镜像和仓库
    1. 1.1. 列出镜像
    2. 1.2. 拉取镜像
    3. 1.3. 查找镜像
    4. 1.4. 构建镜像
    5. 1.5. 创建Docker Hub账号
    6. 1.6. 使用commit 命令创建镜像
    7. 1.7. 用Dockerfile构建镜像
    8. 1.8. 基于Dockerfile构建新镜像
    9. 1.9. 指令失败时会怎样
    10. 1.10. Dockefile和构建缓存
    11. 1.11. 基于构建缓存的Dockerfile模板
    12. 1.12. 查看新镜像
    13. 1.13. 从新镜像中启动容器
    14. 1.14. 将镜像推送到docker hub
    15. 1.15. 删除镜像
    16. 1.16. 从容器中运行Registry
      1. 1.16.1. 运行基于容器的Registry
      2. 1.16.2. 测试新Registry

《第一本Docker书》第4章读书笔记

构建自己的Docker镜像,以及如何使用Docker仓库和Docker Registry

使用docker镜像和仓库

列出镜像

查看所有的镜像

1
2
3
[root@iz0201z3qoku45z ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest a2a15febcdf3 7 days ago 64.2MB

镜像保存在docker宿主机的/var/lib/docker目录下

值查看指定的镜像(查看fedora相关的镜像)

在docker images 后面直接跟镜像名字即可

1
docker images fedora

为了区分一个仓库中的不同镜像,Docker提供了一种称为标签(Tag)的东西,每个镜像在列出来的时候都带有一个标签,如12.04、12.10、quantal或者precise等,每个标签对醉成特定镜像的一些镜像层进行标记,这种机制使得在同一个仓库中可以存储多个镜像。

可以通过在仓库名后面加上冒号和标签来指定仓库中的某一镜像,

1
docker run -t -i --name new_container ubuntu:12.04 /bin/bash

同一个镜像可以有多个标签,使得我们可以方便的对镜像进行打标签,并很容易找到标签。

dockerhub 上的仓库分为两种:用户仓库(user repository)和顶层仓库(top-level repository),用户仓库的镜像都是由Docker用户创建的,顶层仓库则是由Docker内部的人员来管理的

用户仓库的命名由用户名和仓库名两部分组成,如jamtur01/puppet(使用用户镜像,需要自己承担相应的风险)

  • 用户名:jamtur01
  • 仓库名:puppet

顶层仓库只包含仓库名部分,如ubuntu仓库,顶层仓库中的镜像是架构良好、安全且最新的

拉取镜像

使用docker run命令从镜像启动一个容器时,如果镜像不在本地,Docker会先从docker hub下载该镜像,如果没有指定具体的标签,docker会子弟哦那个下载latest标签的镜像

可以通过docker pull命令将镜像拉取回本地,会节省从一个新奖项启动一个容器所需要的时间

拉取fedora:20基础镜像

1
docker pull fedora:20

查找镜像

通过docker search 来查找所有Docker hub上公共的可用镜像

1
2
3
4
5
6
[root@iz0201z3qoku45z ~]# docker search puppet
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
puppet/puppetserver A Docker Image for running Puppet Server. Wi… 80
alekzonder/puppeteer GoogleChrome/puppeteer image and screenshots… 58 [OK]
puppet/puppetserver-standalone An image for running a Puppet Server standal… 32
devopsil/puppet Dockerfile for a container with puppet insta… 30 [OK]

返回了在Docker hub上查找到的所有puppet的镜像,该命令会完成镜像查找工作,并返回如下信息

  • 仓库名(NAME)
  • 镜像描述(DESCRIPTION)
  • 用户评价(STARS)—反应出一个镜像的受欢迎程度
  • 是否官方(OFFICIAL)—由上游开发者管理的镜像
  • 自动构建(Automated)—表示这个镜像是由Docker Hub的自动构建流程(Automated Build)创建成的

拉取macadmins/puppetmaster镜像

1
docker pull macadmins/puppetmaster

从puppet镜像创建一个容器

1
docker run -i -t macadmins/puppetmaster /bin/bash

构建镜像

构建docker镜像的两种方法

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

推荐使用dockerfile来构建docker镜像,

一般来说,我们不是真正的创建新镜像,而是继续一个已有的基础镜像,如ubuntu等,构建新镜像

创建Docker Hub账号

构建镜像中很重要的一环就是如何共享和发布镜像,可以将镜像推送到Docker Hub或者用户自己的私有Registry中,为了完成这项工作需要在Docker hub上创建一个账号

https://hub.docker.com/signup

注册成功后进行登陆

登陆docker hub

1
2
3
4
5
6
7
8
9
[root@iz0201z3qoku45z ~]# 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: flag0
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

用户的个人认证信息会保存到$HOME/.docker/config.json

登出docekr hub

1
docker loginout

使用commit 命令创建镜像

可以将使用commit命令想象成在往版本控制系统里面提交变更,先创建一个新的容器,并在容器中做出修改,就像修改代码一样,最后再将修改提交为一个新镜像

从创建一个新容器开始,这个容器基于ubuntu镜像

创建一个需要进行修改的定制容器

1
2
docker run -i -t ubuntu /bin/bash
root@85b5b40294be:/#

在容器中安装Apache软件包

1
2
3
4
root@85b5b40294be:/# apt-get -yqq update
...
root@85b5b40294be:/# apt-get -y install apache2
...
  • -yqq 软件安装时会有提问是否继续,需要输入 yes ,使用yqq参数,自动 yes 。

我们启动了一个容器,在里面安装了apache,想要将它的当前状态保存下来,需要先使用exit命令从容器里退出,之后再运行docker commit命令

提交定制容器

1
docker commit 85b5b40294be flag0/apache2

docker commit 中指定了需要提交的容器的id(可以通过 docker ps -l -q 获得刚刚创建的容器的id),以及一个目标镜像仓库和镜像名。

docker commit提交的只是创建容器的镜像与容器的当前状态之间有差异的部分,这使得该更新非常轻量

查看新创建的镜像

1
2
3
docker images flag0/apache2
REPOSITORY TAG IMAGE ID CREATED SIZE
flag0/apache2 latest ff47a37f3204 3 minutes ago 188MB

可以在提交竟像是指定更多的数据(包括标签)来详细描述所作的修改。

提交另一个新的定制容器

1
docker commit -m "A new custom image" -a "flag0" 85b5b40294be flag0/apache2:webserver
  • -m 选项用来指定新创建的镜像的提交信息
  • -a 指定该镜像的作者信息
  • flag0/apache2:webserver 指定了镜像的用户名和仓库名为该镜像增加了一个webserver标签

查看提交的新镜像的详细信息

1
2
3
4
5
docker inspect flag0/apache2:webserver
{{
"Author": "flag0",
"Comment": "A new custom image",
...

从提交的镜像运行一个新容器

1
docker run -t -i flag0/apache2:webserver /bin/bash

用Dockerfile构建镜像

dockerfile使用基本的基于DSL(Domain Specific Language)语法的指令来构建一个docker镜像,推荐使用Dockerfile构建镜像更具有可重复性、透明性以及幂等性

接下来,我们将创建一个包含简单web服务器的Docker镜像

创建一个示例仓库

1
2
3
mkdir static_web
cd static_web
touch Dockerfile

我们创建了一个名为static_web的目录用来保存Dockerfile,这个目录就是我们的构建环境(build environment),Docker会在构建镜像时将构建上下文和该上下文中的文件和目录上传到Docker守护进程,这样Docker守护进程就能够直接访问用户想在镜像中存储的任何代码、文件或者其他数据

创建第一个Dockerfile

通过dockerfile构建一个能够作为web服务器的Docker镜像

1
2
3
4
5
FROM ubuntu:14.04
MAINTAINER flag0 "root@flag0.com"
RUN apt-get -yqq update && apt-get install -y ngix
RUN echo 'Hi,I am in your container' > /usr/share/ngix/html/index.html
EXPOSE 80

Dockerfile的每条指令,如FROM,都必须为大写字母且后面要跟随一个参数:FROM ubuntu:14.04,Dockerfile中的指令会按顺序从上到下执行,所以应该根据需要合理安排指令的顺序

每条指令都会创建一个新的镜像层并对镜像进行提交。Docker大体上按照如下流程执行Dockerfile中的命令

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

当Dockerfile因为某些原因失败了,没有正常执行,用户可以得到一个使用正常的镜像,可以基于该镜像运行一个具备交互功能的容器,使用最后创建的镜像进行调试 失败的指令。

dockerfile中以#开头的行都会被认为成注释

  • FROM 每个Dockerfile的第一条命令必须是FROM,FROM指令指定一个已经存在的镜像,后续指令都将继续该进行进行,这个镜像被称为基础镜像(base images)
  • MAINTAINER 用于标识镜像的所有者和联系方式
  • RUN 会在当前镜像中运行指定的命令,每条RUN命令都会创建一个新的镜像层,如果该指令执行成功,就会将此镜像层提交,之后继续执行Dockerfile中的下一条指令
  • EXPOSE告诉Docker该容器内的应用程序将会使用容器的指定端口,由于安全原因Docker并不会自动打开该端口,而是需要用户在使用docker run运行容器时来指定需要打开哪些端口。可以多次使用EXPOSE指令来向外部公开多个端口

exec格式的RUN命令

默认情况下,RUN指令会在shell里使用命令包装器/bin/sh -c 来执行,在不支持shell的平台上运行或者不希望在shell中运行(比如避免shell字符串篡改),也可以使用exec格式的run命令

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

在这种方式中,我们使用一个数组来指定要运行的命令和传递给该命令的每个参数

基于Dockerfile构建新镜像

执行docker build命令时,Dockerfile中的所有指令都会被执行并且提交,在该命令成功结束后会返回一个新镜像。

运行Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@iz0201z3qoku45z static_web]# cd static_web
[root@iz0201z3qoku45z static_web]# docker build -t="flag0/static_web" .
Sending build context to Docker daemon 2.048kB
Step 1/5 : FROM ubuntu:14.04
---> 2c5e00d77a67
Step 2/5 : MAINTAINER flag0 "root@flag0.com"
---> Using cache
---> 42de39438f65
Step 3/5 : RUN apt-get -yqq update && apt-get install -y nginx
---> Using cache
---> 6e6348290412
Step 4/5 : RUN echo "Hi,I am in your container" > /usr/share/nginx/html/index.html
---> Running in dc0d1396f60a
Removing intermediate container dc0d1396f60a
---> 27575b451fe0
Step 5/5 : EXPOSE 80
---> Running in 9b941e50b42b
Removing intermediate container 9b941e50b42b
---> 009c6a5fa17b
Successfully built 009c6a5fa17b
Successfully tagged flag0/static_web:latest
  • -t 为新镜像设置仓库和名称(本例中仓库为jamtur01,镜像名为static_web),也可以为镜像设置一个标签,使用方法为镜像名:标签(如果没有指定任何标签,docker会为镜像设置一个latest标签)

从Git仓库构建Docker镜像

上面命令最后的. 告诉docker到本地目录中去找Dockerfile文件,也可以指定一个Git仓库的源地址来指定Dockerfile的位置

1
docker build -t="flag0/static_web:latest" git@github:jamtur01/docker-static_web

这里假设在这个Git仓库的根目录下存在Dockerfile文件

可以使用-f 标志指定一个区别与标准Dockerfile的构建源的位置

1
docker build -t="flag0/static_web" -f path/to/file

这个文件可以不必命名为Dockerfile,但是必须要位于构建上下文之中

将构建上下文上传到Docker守护进程

1
2
[root@iz0201z3qoku45z static_web]# docker build -t="flag0/static_web"  .
Sending build context to Docker daemon 2.048kB

在构建上下文的根目录下存在以.dockerignore命名的文件的话,那么该文件的内容将被按行进行分割,每一行都是一条文件过滤匹配模式,这非常像.gitignore文件,该文件用来设置哪些文件不会被当作构建上下文的一部分,因此可以防止他们被上传到Docker守护进程里去。

指令失败时会怎样

假设在在编写Dockerfile的把软件包的名字弄错了,比如写了ngix

运行一遍构建过程,并看下指令失败时会怎么样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cd static_web
docker build -t="jamtur01/static_web" .
[root@iz0201z3qoku45z static_web]# docker build -t="flag0/static_web" .
Sending build context to Docker daemon 2.048kB
Step 1/5 : FROM ubuntu:14.04
---> 2c5e00d77a67
Step 2/5 : MAINTAINER flag0 "root@flag0.com"
---> Using cache
---> 42de39438f65
Step 3/5 : RUN apt-get -yqq update && apt-get install -y ngix
---> Running in d40e9918b765
Reading package lists...
Building dependency tree...
Reading state information...
E: Unable to locate package ngix
The command '/bin/sh -c apt-get -yqq update && apt-get install -y ngix' returned a non-zero code: 100

这是需要调试一个这次失败,可以用docker run命令来基于这次构建到目前为止已经成功的最后一步创建一个容器,在这个例子里,使用的镜像id是42de39438f65

基于最后的成功步骤创建新容器

1
2
docker run -t -i 42de39438f65 /bin/bash
root@19f53703e528:/#

在这里可以再次运行RUN apt-get -yqq update && apt-get install -y ngix来进一步调试找出出错的原因,一旦解决了这个问题,就可以退出容器,使用正确的报名修改Dockerfile文件,之后再尝试进行构建。

Dockefile和构建缓存

Docker会把每一步的构建过程都提交为镜像,会将之前的镜像层都看作缓存,当再次构建时,Docker则会从第一条发生变化的指令开始,当之前的构建步骤没有变化时,会节省大量的时间

当需要全部重新构建时,使用--no-cache参数

忽略Dockerfile的构建缓存

1
docker build --no-cache -t="flag0/static_web" .

基于构建缓存的Dockerfile模板

构建缓存可以让我们实现简单的Dockerfile模板,可以节省镜像构建时间(比如在Dockerfile文件顶部增加包仓库或者更新包,从而尽可能确保缓存命中)尽可能的在自己的Dockerfile文件顶部使用相同的指令集模板。

ubuntu系统的Dockerfile模板

1
2
3
4
FROM ubuntu:14.04
MAINTAINER flag0 "root@flag.com"
ENV REFERSHED_AT 2014-07-01
RUN apt-get -qq update
  • -ENV 指令在镜像中设置环境变量(通过ENV指令来设置一个名为REFRESHED_AT的环境变量,这个环境变量用来表名该镜像模板最后的更新时间)
  • -RUN 指令来运行apt-get -qq update命令,该指令运行时,会刷新APT包的缓存,用来确保我们能将要安装的每个软件包都更新到最新版本

有了这个模板,如果想刷新一个构建,只需要修改ENV指令中的日期,使得Docker在执行到ENV指令是开始重置这个缓存,运行后续指令而无须依赖该缓存。RUN apt-get update这条指令将会被再次执行,包缓存也将会被刷新为最新内容

Fedora Dockerfile模板

1
2
3
4
FROM fedora:20
MAINTAINER flag0 "root@flag0.com"
ENV REFRESHED_AT 2014-07-01
RUN yum -q makecache

在Fedora中使用yum实现了与上面的ubuntu例子中非常类似的功能

查看新镜像

列出新镜像

1
2
3
[root@iz0201z3qoku45z ~]# docker images flag0/static_web
REPOSITORY TAG IMAGE ID CREATED SIZE
flag0/static_web latest 009c6a5fa17b 5 hours ago 222MB

使用docker history命令

如果想深入探求镜像是如何构建出来的,可以使用docker history命令

1
2
3
4
5
6
7
8
root@iz0201z3qoku45z ~]# docker history 009c6a5fa17b
IMAGE CREATED CREATED BY SIZE COMMENT
009c6a5fa17b 5 hours ago /bin/sh -c #(nop) EXPOSE 80 0B
27575b451fe0 5 hours ago /bin/sh -c echo "Hi,I am in your container" … 26B
6e6348290412 5 hours ago /bin/sh -c apt-get -yqq update && apt-get in… 34.3MB
42de39438f65 5 hours ago /bin/sh -c #(nop) MAINTAINER flag0 "root@fl… 0B
2c5e00d77a67 3 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
...

从上面的结果可以看到新构建的jamtur01/static_web镜像的每一层,以及创建这些层的Dockerfile命令

从新镜像中启动容器

从新镜像启动一个容器

1
docker run -d -p 80 --name static_web flag0/static_web nginx -g "daemon off"
  • nginx -g “daemon off”将以前台运行的方式启动Nginx,来作为我们的Web服务器
  • -p 用来控制Docker在运行时应该公开哪些网络端口给外部(宿主机)。

运行一个容器时,Docker可以通过两种方法来宿主机上分配端口

  • Docker可以在宿主机上随机选择一个位于32768~61000的一个比较大的端口号来映射到容器的80端口上
  • 可以在Docker宿主机中指定一个具体的端口号来映射到容器中的80端口上

上述命令将在Docker宿主机上随机打开一个端口,这个端口会连接到容器中的80端口上。

查看Docker端口映射情况

1
2
3
docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2d157174a62f flag0/static_web "nginx -g 'daemon of…" 25 seconds ago Up 24 seconds 0.0.0.0:32769->80/tcp static_web1

可以看到容器中的80端口被映射到了宿主机的49154上,我们也可以通过docker port命令来查看容器的端口映射情况

docker port命令

1
2
docker port 2d157174a62f 80
0.0.0.0:32769

除了id外,也可以使用容器名,也可以不指定端口号,会列出所有映射端口号

1
2
docker port static_web1
80/tcp -> 0.0.0.0:32769

通过-p选项映射到特定端口

1
docker run -d -p 8080:80 --name static_web3 flag0/static_web nginx -g "daemon off;"
  • -p 可以指定将容器中的端口映射到Docker宿主机的某一特定端口上,上述命令会将容器内的80端口绑定到本地宿主机的8080端口上

宿主机的一个端口,只能绑定一个容器,为了避免这个问题可以将容器内的端口绑定到不同的宿主机端口上去

绑定到特定的网络接口

也可以将端口绑定限制在特定的网络接口(即IP地址)上

1
docker run -d -p 127.0.0.1:8081:80 --name static_web4 flag0/static_web nginx -g "daemon off;"

上述命令将容器内的80端口绑定到了本地宿主机的127.0.0.1这个IP的80端口上,我们也可以使用类似的方式将容器内的80端口绑定到一个宿主机的随机端口上

绑定特定的网络接口的随机端口

1
docker run -d -p 127.0.0.1::80 --name static_web5 flag0/static_web nginx -g "daemon off;"

可以使用docker inspect 或者 docker port命令来查看容器内的80端口具体被绑定到了宿主机的哪个端口上。

也可以通过端口绑定时使用/udp 后缀来指定UDP端口

使用docker run命令对外公开端口

Docker还提供了一个更简单的方式,即可-P 参数,可以用来对外公开在Dockerfile中通过EXPOSE指令公开的所有端口

1
docker run -d -P --name static_web6 flag0/static_web nginx -g "daemon off;"

该命令会将容器内的80端口对本地宿主机公开,并且绑定到一个随机端口上。该命令会将用来构建该镜像的Dockerfile文件中EXPOSE指令指定的其他端口也一并公开。

使用curl连接到容器

1
2
curl localhost:32770
Hi,I am in your container

得到了一个非常简单的基于Docker的Web服务器

将镜像推送到docker hub

推送Docker镜像

1
docker push flag0/apache2-1

我们使用了一个名为flag0/apache2-1的用户仓库,成功得将镜像推送到了docker hub

可以在docker hub上看到我们上传的镜像

1566834435611

自动构建

docker hub允许我们定义自动构建,只需要将github或bitbucket中含有dockerfile文件的仓库连接到docker hub即可。

。。。明天补上

删除镜像

删除docker镜像

1
2
3
docker rmi -f flag0/static_web1
Untagged: flag0/static_web1:latest
Deleted: sha256:4403ea0272ba374367b5eaee5900fb1ce19c0834b979f6403d6f6c4b7266445c
  • -f 强制删除

每一个Deleted:行都代表一个镜像层被删除

该操作只会将本地的镜像删除,如果之前已经将该镜像推送到Dockerhub上,那么他在docker hub上依旧存在

如果想删除一个Docker hub上的镜像仓库,在登陆Docker hub后使用delete repository 来删除

1566836933369

同时删除多个Docker镜像

1
docker rmi flag0/apache2 flag0/apache2-1

删除所有镜像

1
docker rmi `docker images -a -q`

从容器中运行Registry

运行基于容器的Registry

1
docker run -p 5000:5000 registry:2

在启动Docker守护进程的命令中添加-insecure-registry localhost:5000,并重启守护进程,才能继续使用本地Registry

测试新Registry

使用新的Registy为镜像打标签

1
docker tag 5c42817d9f34 localhost:5000/flag0/apache2-1

将镜像推送到新Registry

1
2
3
4
5
6
7
8
docker push localhost:5000/flag0/apache2-1
The push refers to repository [localhost:5000/flag0/apache2-1]
c1d7d627d405: Pushed
66285ac4bf24: Pushed
48334332ed8d: Pushed
46c1a22ffea5: Pushed
b057ab380990: Pushed
latest: digest: sha256:3b86cc40923125ca9c66475f350014a365e8fbf365b240f656c1c1aa05f13dee size: 1364

这个镜像被提交到了本地的Registry中,并且可以将其用于使用docker run命令构建新容器

从本地Registry构建新容器

1
docker run -t -i localhost:5000/flag0/apache2-1 /bin/bash

这是在防火墙后部署自己的Docker Registry最简单的方式

Contents
  1. 1. 使用docker镜像和仓库
    1. 1.1. 列出镜像
    2. 1.2. 拉取镜像
    3. 1.3. 查找镜像
    4. 1.4. 构建镜像
    5. 1.5. 创建Docker Hub账号
    6. 1.6. 使用commit 命令创建镜像
    7. 1.7. 用Dockerfile构建镜像
    8. 1.8. 基于Dockerfile构建新镜像
    9. 1.9. 指令失败时会怎样
    10. 1.10. Dockefile和构建缓存
    11. 1.11. 基于构建缓存的Dockerfile模板
    12. 1.12. 查看新镜像
    13. 1.13. 从新镜像中启动容器
    14. 1.14. 将镜像推送到docker hub
    15. 1.15. 删除镜像
    16. 1.16. 从容器中运行Registry
      1. 1.16.1. 运行基于容器的Registry
      2. 1.16.2. 测试新Registry