Docker镜像和容器

Docker镜像和容器

1. 容器(container)

​ 容器是docker的运行环境基础,替代虚拟机功能的就是这个。

​ 容器的运行依靠已经构建好的镜像,镜像中的文件状态就是容器启动后初始的文件状态,系统环境、基础命令、上层应用都已打包在镜像中,应用的使用者只需下载发布者提供的镜像,无须配置环境(镜像已经保证了环境一致性),创建一个容器即可运行。

​ 当容器启动后,容器会按镜像构建者的设置来运行,我们也可以在容器创建和运行的时候修改容器的状态(如同在本机使用变量、配置应用一样)。那么我们来看看如何管理容器。

1.1. 容器,启动!

Hello , Container !


​ 容器,启动!

​ 慢着,在启动之前我们需要有镜像。容器是为了运行应用而存在的,我们要使用应用对应的镜像创建容器。镜像可以从镜像仓库(repository)拉取(pull),docker默认从docker hub(这是docker官方镜像站)上拉取。

拉取镜像使用命令pull,例如,我们要从docker hub上拉取一个 Apache httpd 服务镜像,命令是:

# docker pull httpd
### 这样会拉取`httpd:latest`镜像,下面是屏幕输出的信息
Using default tag: latest
latest: Pulling from library/httpd
a378f10b3218: Already exists 
c20157372e94: Pull complete 
073cbcfef663: Pull complete 
5f75f8a17f40: Pull complete 
39fc6f0c5be2: Pull complete 
Digest: sha256:ed6db4a8c394d075c9c59a3dbd61a3818cd302d9948057f1e19046e5bffec027
Status: Downloaded newer image for httpd:latest
docker.io/library/httpd:latest

​ 现在我们就以运行httpd容器作为测试。输入以下命令运行容器:

# docker run --name test_httpd -d -P httpd
--name <string> # 容器命名
-d  # 后台运行
-P  # 为暴露端口自动分配IP

​ 如果希望创建一个容器,启动后停留在前台并可以操控,应使用-i -t组合选项:

# docker run --name os-test -it centos:7.4.1708 bash
-i  # 连接标准输入
-t  # 分配tty伪终端
# 在镜像后面跟随的参数,将视为容器启动后执行并监视的命令
# 在该状态使用 Ctrl+p - Ctrl+q 可挂至后台
# 也可以和 -d 选项一同使用,创建可交互终端并挂职后台。

列出所有容器使用命令:

# docker ps
### 或加上选项-a可查看包括停止状态的容器
# docker ps -a
CONTAINER ID  IMAGE  COMMAND             CREATED        STATUS        PORTS                  NAMES
ce4633b0b256  httpd  "httpd-foreground"  7 minutes ago  Up 7 minutes  0.0.0.0:32768->80/tcp  test_httpd

# curl 127.0.0.1:32768
<html><body><h1>It works!</h1></body></html>

如果想要停止服务,使用停止命令stop

# docker stop test_httpd
test_httpd

### 后面可以接多个容器作为参数。可用容器ID作为参数:
# docker stop ce4633b0b256
ce4633b0b256

### 若容器中的进程无法停止,或需要立即停止,可用强制停止命令kill:
# docker kill test_httpd

删除容器使用命令rm,需要容器停止后才能删除:

# docker container rm test_httpd

1.2. 定制化容器

创建容器时的常用参数


​ 开始之前先辨别这两个命令

# docker create
# docker run

​ 这两个命令都可以创建容器,且选项基本相同,区别在于create创建容器后不会启动,而run创建容器后会立即启动,选项上的寥寥差别也是因这一点存在的。

1) 网络配置

网络驱动

​ docker容器是如何上网的,是NAT还是是桥接?

​ docker自动配置了三个网络,分别对应了docker的三种同名的网络类型:bridge、host和none。

​ bridge网咯会创建一个虚拟网桥,连接至宿主机以创建一个虚拟网络,这个虚拟网络设有一个地址池,容器从该池分配IP,然后从宿主机转发数据(类似虚拟机的NAT网络,通过内核的iptables的nat表实现),容器stop后会释放IP地址。host网络是复制宿主机的网卡状态,none网络则是没有网络连接。

​ 在未设置的情况下,容器会自动连接到bridge网络。

​ 将容器连接到指定的docker网络使用选项--net=NET_NAME,不使用选项容器默认连接bridge网络。如下示例会使容器连接none网络(即无连接):

# docker run --net=none busybox

​ 具体还有其他的连接方式以及网络创建方法详见网络章节。

DNS

​ 在创建容器时使用--dns选项,可以为容器单独指定DNS。若不指定,则会在容器启动时将容器docker主机上的/etc/resolv.conf文件的有效内容复制到容器内。

网络别名

--network-alias选项可使其他同一网络中的容器可以用别名与该容器通信。该选项尽仅支持在用户自定义网络中使用,因为容器名通信也只能在用户自定义网络使用。

端口映射

​ 将容器上的端口映射到宿主机,使用选项-P-p,区别在于-P是为容器的暴露端口随机分配本地端口,-p是手动指定本地端口和容器端口。

​ 例如下面的命令将为新容器映射两个端口,一个是为容器暴露的80端口随机分配,一个是手动指定:

# docker run -dp 10000:10000 -P --name httpd httpd:latest
-p 主机端口:容器端口
### 容器创建好后可用 docker port 命令查看容器端口映射列表
# docker port httpd
80/tcp -> 0.0.0.0:32776
80/tcp -> [::]:32776
10000/tcp -> 0.0.0.0:10000
10000/tcp -> [::]:10000

​ 端口映射只能在创建容器时指定,如果要更改,有两种方法:

  1. 修改容器配置文件的端口部分,需要重启docker服务。
  2. docker commit命令生成镜像,用新镜像创建容器时重新指定端口。

​ 端口映射会使防火墙中加入一个DNAT,可用iptables -t nat -nL查看。

2) 存储配置

​ 容器的运行会产生数据,包括镜像原有的数据在内,都被存放在叫做 Docker area 的空间(默认是/var/lib/docker目录)。容器的文件系统分层叠放,镜像中的数据是只读层叫做镜像层,容器运行后产生的数据存在于可读可写层叫做容器层,镜像层和容器层叠加后的联合文件系统(或叫融合层)就是容器中看到的内容。容器层和融合层以及容器初始化数据会在容器删除后一同被移除(即便不移除也难以管理),因此docker官方另外提供了持久化存储功能。

绑定挂载 bind mount

​ 将本地路径映射到容器内,用这样的方法使容器可以读写宿主机的内容(反过来也行),以实现将容器中的文件持久化。

​ 在创建容器时,使用选项-v 宿主机路径:容器内路径[:挂载选项]挂载。其中宿主机路径不存在时会自动创建一个目录;容器内路径需使用绝对路径,路径不存在时也会自动创建。绑定挂载的方式允许多个容器挂载宿主机同一位置。

docker run \
  -v /var/www/html:/var/www/html \
  --name test_httpd httpd

------------------------------------------------------
-v的长选项格式为--volume
在容器内目标路径后可添加选项,即参数格式为SRC:DST:OPTION,可选选项有ro、z或Z,多个选项用逗号连接

​ 或使用--mount选项挂载。

​ 但是注意--mount选项和-v选项存在区别,除语法的区别外更重要的区别是--mount选项不支持使用selinux选项,以及当docker主机中的路径不存在时不会自动创建目录。

--mount语法比较冗长,是这样的:

docker run  \
  --mount type=bind,source=/var/www/html,destination=/var/www/html  \
  --name test_httpd httpd
------------------------------------------------------
"source"键可替换为"src"
"destination"键可替换为"dst"或"target"
可选字段"readonly"
--mount不支持selinux选项

​ 官方更鼓励使用--mount,实际是因为--mount选项更精确直观,且支持更精细的挂载选项。详见docker存储章节。

容器卷volume

​ 卷只需要名称,适合不需要映射特定路径的持久化场景。使用时指定卷的名称(甚至不用定名称,docker会创建新的匿名卷)和挂载到容器内的路径即可,删除容器时除非指定选项否则容器卷不会一同删除。同一个卷允许挂载到多个容器。

​ 当创建容器时指定的容器卷不存在,会自动创建该卷。容器卷有专门的管理命令docker volume

​ 容器创建时挂载存储卷同样使用-v--mount选项。先看-v选项,语法为-v 卷名:容器内路径

docker run  \
  -v html_vol:/var/www/html  \
  --name test_httpd httpd
------------------------------------------------------
可选选项ro

​ 接下来看--mount选项方法

docker run  \
  --mount src=html_vol,dst=/var/www/html  \
  --name test_httpd httpd
------------------------------------------------------
在挂载卷时--mount选项可以省略type键
与绑定挂载不同,当指定卷不存在时--mount也会自动创建卷
可选字段"readonly"

​ 以上是命名卷,还有匿名卷的手动指定:

#--volume
docker run -v /var/www/html \
  --name test_httpd httpd
#--mount
docker run  \
  --mount dst=/var/www/html  \
  --name test_httpd httpd

​ 匿名卷也有名称,该名称由docker自动生成而无须手动指定,因此匿名卷不方便新的容器再次挂载。

​ 以上是创建容器时的挂载卷的方法,简单看一下docker volume的常用管理命令。

##列出所有存储卷
docker volume ls

##创建存储卷
docker volume create [VOLUME]
# 创建卷时可以不写卷名(即匿名卷),且一次只能创建一个卷

##删除存储卷
docker volume rm VOLUME [VOLUME...]

##删除未使用的存储卷
docker volume prune
# 删除所有未挂载的本地卷
 -f, --force # 无须交互确认

##查看卷的详细信息
docker volume inspect VOLUME [VOLUME...]
临时挂载 tmpfs mount

tmpfs的数据存储在内存中,容器停止后数据将立即消失,因此这实际上不是数据持久化。这样的特性使其有独特的使用场景(tmpfs 有更快的读写速度,且数据不会被轻易读取到)。

​ 临时挂载可以使用--mount--tmpfs两种选项挂载。--tmpfs使用非常简单:

# docker run \
> --tmpfs /DESTINATION/PATH \
> --name test_httpd httpd

--mount

# docker run \
> --mount type=tmpfs,destination=/DESTINATION/PATH \
> --name test_httpd httpd

3) 容器名和主机名

--name <CONTAINER>设置容器名称,若不指定,则docker会为这个容器自动分配一个随机但可读的名称。该名称适用于各种docker命令行工具中指代容器。

-h <HOSTNAME>设置容器的主机名,若不指定,则docker自动为容器设置主机名为容器ID的前12位。容器之间可用主机名通信。

4) 后台运行

​ 容器内的应用一般前台运行,将一些信息输出到标准输出;而使用docker run创建的容器也将运行于前台占用标准输出,我们需要将容器挂到后台来继续其他的操作。使用docker run命令时指定-d(或--detach)选项即可。

​ 使用docker create创建的容器不会立即启动,启动后在后台运行,所以没有该选项。

​ 不论容器在前台或后台,容器默认的标准输出内容都可以用docker logs命令查看。

5) 自动删除和重启策略

--rm true|false自动删除选项,很简单就是在容器退出时删除该容器,但跟另一个选项不兼容。

--restart <POLICY>自动重启选项,在容器退出时自动重启,该选项有不同的策略,且与--rm选项不兼容使用(否则容器停止时是该重启还是该删除?)。

policy 说明
no 容器未指定--restart选项时的默认策略,即不会自动重启
no_failure[:MAX-RETRIES] 仅当容器的退出状态码不为0时自动重启,选填尝试次数
always 任何退出状态都会自动重启容器,只要docker进程在
unless-stopped 任何退出状态都会自动重启容器,
但docker进程启动前就已停止的容器不会在启动docker进程时启动

1.3. 容器的管理

​ 除了1.1中提到的创建和停止,我们对容器还能做什么?


默认运行命令

​ 在容器启动时,会自动执行一个命令并监视之。若命令执行后立即退出,将按照退出状态码判断容器结束状态(正确退出或错误退出);若命令持续运行,容器也持续运行直至手动结束容器或命令意外退出,容器的结束状态也是命令退出状态码。每次启动容器的时候都会运行这个命令,而这个命令是在镜像中事先指定好的。

​ 我们可以在创建容器时手动指定运行的命令和参数,以替代镜像中指定的命令,并在每次启动容器时运行:

# docker run -it --name test httpd bash
[root@6e2485efd9c2 /]#
########################
这样就会进入容器的命令行界面中,而容器中的httpd则没有运行,因为默认命令被换成了bash
我们可以用这样的方法进入容器内进行操作
-i选项保持标准输入连接
-t选项创建一个tty
这两个选项通常会搭配使用,所以短选项结合写为-it

​ 如果后续使用docker start启动该容器,则容器的标准输入输出被置于后台。

连接容器

​ 所以当容器挂于后台的情况下,总有个回到前台的办法吧?当然有。不管是用docker run-d选项指定还是docker start启动而在后台运行的容器,都可以使用docker attache命令连接容器的标准输出(-d选项的长选项为--detach)。

# docker create -it --name test httpd bash && docker start test
# docker attach test
root@30d1e83e1482:/usr/local/apache2#
执行容器内的命令

​ 如果容器没有运行bash的状态下,也可以使用docker exec命令运行一个bash从而进入容器的命令行。

# docker run -d --name test_httpd httpd
# docker exec -it test_httpd bash
root@41e20434cd0d:/usr/local/apache2#
########################
 -e, --env list #环境变量列表,格式为键=值,键=值
 -i, --interactive
 -t, --tty
 -u, --user string #用户名或uuid
 -w, --workdir string
容器改名

​ 用docker rename为容器重命名

查看容器端口

​ 用docker port命令可以查看容器映射到主机上的端口。

# docker port httpd
80/tcp -> 0.0.0.0:32776
80/tcp -> [::]:32776
10000/tcp -> 0.0.0.0:10000
10000/tcp -> [::]:10000
查看容器日志

docker logs,日志实质上是被监控的进程的标准输出内容。

对比文件系统

docker diff命令可以对比不同容器之间的文件列表差异。

2. 镜像(Image)

​ 容器需要依靠构建好的镜像来创建和运行,那么这个镜像究竟是什么,我们能否自己轻松做一个镜像。

​ 我们之所以把image称为为镜像,是因为最初这类文件是对光盘内容的刻录,后来这个名称的使用范围扩大到一切把零散对象封装好的、使用时以只读方式打开、不对该文件本体产生影响的对象,docker的镜像就是这样的对象。

​ docker镜像就像是建楼一样,一个镜像由多层构建,从下往上构建,每一层都在低层的基础上改变。

   |===========|
   |  CMD ...  |
   |-----------|
   |  CMD ...  |
   |-----------|
   |  ENV ...  |
   |-----------|
   |  ADD ...  |
___|___________|___         

​ docker镜像允许引用镜像,以构建好的镜像为父镜像继续构建。

   |===========|
   |  CMD ...  |
   |-----------|
   |  ENV ...  |
   |-----------|
   |  ADD ...  |
   |-----------|
   |  FROM ... |
   |===========|
   |    ...    |
   |-----------|
   |    ...    |

​ 而运行容器,就会在镜像上产生写时复制层(在容器之外并不与镜像真实关联),这一层存放容器运行后文件系统发生的改变。这些改变的内容也可以进行保存(使用命令docker image commit),生成新的镜像。

   | 容器运行时  |
   >~~~~~~~~~~~<
   |===========|
   |    ...    |

​ 我们来看看镜像如何管理镜像,以及如何自己制作镜像。

2.1. 管理镜像 docker image

​ 我们开篇提到过镜像的拉取命令docker pull,如下例命令从网络中拉取httpd镜像:

# docker pull httpd

​ 开篇中也提到了,该镜像是从docker hub上拉取,且拉取对象全称为httpd:latest

​ docker hub是由docker官方维护的注册中心,其中有许多官方和民间维护的项目仓库,镜像存放于仓库之中。我们默认从这里拉取镜像。我们自己也可以注册账号建立自己的仓库在这里存放镜像(docker hub的基础使用免费)。更具体的信息在2.3提供。

​ 镜像完整名称分为两部分,用:隔开,左边是仓库名称NAME,右边是镜像标签TAG

NAME实际是仓库所在路径,仓库可以在docker hub,也可以是其他的注册中心,详见注册中心和仓库TAG是字符串,按照镜像发布者的需求,可以是版本号、日期、代号,或者在仅有一个仓库可用的拮据情况下,将真正的镜像名放在TAG而不在NAME

​ 在官方建议下,仓库中最新版本的镜像的标签为latest,用户在未提供TAG的情况下,docker命令行将默认补充latest作为标签来寻找镜像。

​ 镜像下载好后,在本地的路径默认为/usr/lib/docker,可以通过修改docker的daemon文件修改该位置。

1) 镜像的增删改查

docker images 列出镜像

# docker images
REPOSITORY         TAG            IMAGE ID             CREATED             SIZE
test               latest         03ddec397cf7         3 days ago          197MB
httpd              latest         75a48b16cd56         4 weeks ago         168MB
nginx              latest         bc649bab30d1         5 weeks ago         187MB
ubuntu             18.04          5a214d77f5d7         2 years ago         63.1MB
hello-world        latest         feb5d9fea6a5         2 years ago         13.3kB
centos             7.4.1708       9f266d35e02c         4 years ago         197MB

docker pull 拉取镜像

# docker pull mysql
latest: Pulling from library/mysql
8e0176adc18c: Pull complete 
2d2c52718f65: Pull complete 
d88d03ce139b: Pull complete 
4a7d7f11aa1e: Pull complete 
ce5949193e4c: Pull complete 
f7f024dfb329: Pull complete 
5fc3c840facc: Pull complete 
509068e49488: Pull complete 
cbc847bab598: Pull complete 
942bef62a146: Pull complete 
Digest: sha256:1773f3c7aa9522f0014d0ad2bbdaf597ea3b1643c64c8ccc2123c64afd8b82b1
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest

docker tag 新增镜像标签

# docker tag hello-world:latest hello-world:1.0
# docker images | grep hello
hello-world         1.0            feb5d9fea6a5         2 years ago         13.3kB
hello-world         latest         feb5d9fea6a5         2 years ago         13.3kB

docker rmi 删除镜像

# docker rmi hello-world:1.0
Untagged: hello-world:1.0
# docker images | grep hello
hello-world         latest         feb5d9fea6a5         2 years ago         13.3kB
### 会发现另一个标签的镜像并未移除,仅仅是移除一个命名
### 但如果镜像只有一个标签,此时进行删除操作将真正删除镜像
# docker rmi test
Untagged: test:latest
Deleted: sha256:03ddec397cf7e436ee0abdefada6da9ca59a473c63349af52303ae972c0b95ae
Deleted: sha256:4157bd8244fb2711feb96d9d204c9711674065d501c6c1d262a6b7bd93bc4d59
Deleted: sha256:5e4c9582ce91f389d74d240287bcf9948e7d568d95aa28fb172e24181f99e3fd
Deleted: sha256:43c4654821e2c70045b09dc98de6f1c0f9e6599b6ea24cb4d72eab5275f905bb
Deleted: sha256:169e9d3119b64ec64cd72c33331633b475ebd2a972e8d171d2ef1694a526e7b6

docker commit 提交镜像

### docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
### docker commit实际是docker container的命令,该命令能将容器的更改内容提交为新的镜像。

docker history 镜像构建历史

# docker history hello-world:latest 
IMAGE         CREATED       CREATED BY                                     SIZE                COMMENT
feb5d9fea6a5  2 years ago   /bin/sh -c #(nop)  CMD ["/hello"]              0B                  
<missing>     2 years ago   /bin/sh -c #(nop) COPY file:50563a97010fd7ce…  13.3kB
2) 镜像的分发

​ 镜像化的一大意义就是利于分发,本机上费好大劲部署好环境和应用后,打包发送到其他机器可以直接docker run运行是非常方便的。

​ 镜像有两种分发方法:上传到云端供下载,和拷贝到存储介质,分别对应命令docker pushdocker save——前一种将镜像推送到注册中心,后一种将镜像保存为tar文件。

​ 不过docker push需要前往dockerhub或指定的注册中心注册账号,然后在本机上登录。

docker save只需要本地有足够的空间即可。

# docker save test > ./test.tar
### 默认以数据流的方式,用输出重定向指定文件,也可以用-o选项指定
# docker save test -o ./test.tar

docker push则需要登录注册中心的账号再操作,详见注册中心和仓库

2.2. 构建镜像(docker build)

​ 前面提到过我们可以在改变容器后用commit命令提交来产生新的镜像,但这样的方式有几个问题:需要基于其他镜像的容器运行,操作冗余效率低,需另编写命令行脚本,不方便快速和自动化构建。我们需要一种更简单的容器原生的镜像构建方式,因此有了通过编写dockerfile和使用docker builder自动完成镜像构建的方式。

Docker Build 官方文档:https://docs.docker.com/build/

1) docker builder

​ 不知道你学没学过编译,我是没学过的,只知道皮毛。零散的源文件集中于一个目录中,目录外还有库文件为依赖,这个目录中还有个makefile文件用来描述如何处理这些文件,最后对着这个目录使用make命令进行编译。

​ 使用docker builder构建镜像与用make编译有些类似,同样是将文件集中于一个目录,目录中有一个描述构建过程的文件,可能会引用外部资源。在构建镜像的过程中,外部引用的库就是父镜像,makefile就是dockerfile。

​ 构建过程其实是由docker创建一个临时容器,然后根据dockerfile中的指令来运行相应的命令(即docker builder是一层对命令解释器的抽象23333) ,每进行一个指令就创建写时复制层,完成指令就封装为只读层,直至完成所有指令后将所有层链接为一个镜像整体。

​ dockerfile指令包括引用镜像、定义环境变量、导入文件、运行命令、暴露端口等等操作,这使镜像可自定义程度非常高,且这些指令的使用非常简单。

2) Dockerfile

​ 接下来我们来了解dockerfile的语法和指令。

编写规则

​ dockerfile的文件名必须为Dockerfile,存放在项目的根目录。

​ 语法方面dockerfile非常简单,因为dockerfile没有复杂的分支语句,也就没有层级关系,一行一个指令。

​ 支持变量,引用变量同shell中类似,${变量名},变量使用ENVARG指令指定。

​ 注释方面,dockerfile的注释同shell、python经典注释方法一样,用#注释一行。#开头行会在解释指令时直接跳过(除非是解析器指令)。

​ 每一行构建指令都对应一层镜像,因此构建指令应尽量少以优化构建过程的执行效率。

构建指令

​ 指令方面,使用大写字母且前面无空格或缩进(虽然小写可以识别,空格会被忽略,但这属于官方不建议的写法)。指令一行完成,除了RUN命令,该命令可用\换行,因为shell可以用\换行。

  • FROM 指定父镜像
FROM NAME[:TAG]
FROM NAME@DIGEST
#选择已有的镜像作为父镜像
#作为dockerfile的第一个指令,往往选择一个系统镜像作为基础环境,且FROM只能是第一个指令
  • WORKDIR 切换工作目录
WORKDIR PATH
#切换编译时容器内工作目录
  • COPY 复制到容器
COPY SRC DST
COPY ["SRC",..."DST"]
#将项目目录下的文件或目录复制到构建时容器的某个路径
  • ADD 添加到容器
ADD SRC DST
ADD ["SRC",..."DST"]
#类似COPY指令,但如果source是归档文件则会在复制同时释放它们
  • VOLUME 挂载卷
VOLUME ["path"]
#创建容器时自动创建持久化存储,挂载到指定路径
  • RUN 运行命令
RUN COMMAND \
 && COMMAND
RUN ["COMMAND", "parament1"...]
#执行命令行操作
#第一种格式,是用`/bin/sh -c`运行命令
#第二种格式,直接指定可执行程序
  • ENV 环境变量
ENV KEY=VAL KEY=VAL ...
ENV KEY VAL
#设置环境变量,容器运行时也生效
  • ARG 构建时变量
### ARG是唯一可以在FROM前就可以使用的指令 ###
ARG KEY=VAL
#由ARG设置的变量仅在构建阶段生效
#ARG一次只能设置一个环境变量
  • EXPOSE 暴露端口
EXPOSE PORT PORT ...
#向容器引擎和用户告知这是需要映射的端口
  • CMD 容器运行命令
CMD COMMAND
CMD ["COMMAND","parament1"...]
CMD ["parament1","parament2"...]
#容器启动后运行并监视的命令
#第三种风格是在有ENTRYPOINT的情况下指定ENTRYPOINT的参数
#若有多个CMD指令,以最后一个为准
  • ENTRYPOINT 进入点
ENTRYPOINT COMMAND
ENTRYPOINT ["COMMAND","parament1"...]
#作用同CMD,区别在于命令使用绝对路径,
#且无法用创建容器命令的尾随参数修改指定运行的命令(但可以修改参数)
#CMD 和 ENTRYPOINT 是可以同时使用,CMD 会成为 ENTRYPOINT 的尾随参数
#想要修改用ENTRYPOINT指定命令的方法,是在创建容器时使用选项--entrypoint
  • HEALTHCHECK 健康检查
HEALTHCHECK [options] CMD command
#与docker create的healthcheck选项一样,其参数作为这里的选项使用
HEALTHCHECK NODE
#禁止从父镜像(FROM)继承的HEALTHCHECK生效
  • 其他构建指令
SHELL SHELL_PATH
#用指定shell覆盖默认的shell(/bin/sh),作用于RUN、CMD、ENTRYPOINT和命令行末尾

MAINTAINER STRING
#镜像作者信息,现已 用LABEL替代

LABEL KEY=VAL KEY=VAL
#以键值对的形式,添加镜像元数据

USER USERNAME|UID
#以指定用户运行程序

ONBUILD BUILDCMD ...
#该构建指令只在其他镜像以该镜像作父镜像时,才生效

STOPSINGAL SIGNAL
命令形式

​ dockerfile中,RUN、CMD、ENTRYPOINT都有两种形式,shell和exec形式。

shell形式

  • 直接以字符串命令格式,实际执行命令为/bin/sh -c STRING/bin/sh -c是默认SHELL,可以使用SHELL指令修改)
  • 可以直接执行脚本、解析变量
  • 可以使用&&||等连接符,可用\换行

exec形式

  • 以JSON序列格式解析,需要每个元素为字符串(应当用""包含),如果格式不正确或元素非字符串,则不会被解析为exec形式,而判断为shell形式
  • 格式为["CMD","parament1","parament2"...]
  • CMD必须为可执行程序,否则会提示格式错误
  • 如果没有路径则会在$PATH标识的路径中查找可执行程序,仍未找到则会提示未找到可执行文件
  • 官方推荐使用exec形式,如果有需要可以使用[""/bin/sh","-c"...]以解决脚本和变量等的使用问题
  • 但即使是官方镜像,很多时候RUN指令都是用的shell形式

3) 多阶段构建(Multi-stage builds)

​ 多阶段构建对于那些在优化 Dockerfile 的同时又努力使其易于阅读和维护的人来说非常有用。

​ 例如,有时候我们希望编译过程在镜像构建过程中同时进行,因此需要在构建时引入编译工具,但是最终编译工具仍会占据镜像的空间,除非将编译过程摘到构建前。那就使用多阶段构建吧。

多阶段构建官方文档:https://docs.docker.com/build/building/multi-stage/

​ 对于多阶段构建,你可以在 Dockerfile 中使用多个FROM指令,每条FROM指令都可以使用不同的基础镜像,而且每条FROM指令都会开始一个新的构建阶段。你可以有选择性地将产物从一个阶段复制到另一个阶段,在最终镜像中丢掉你不想要的东西。

多阶段、阶段别名

示例:

## 为go-hello项目创建同名目录,查看项目主文件 hello.go
[root@ubuntu2004 go-hello]# cat hello.go
package main
import (
    "fmt"
    "time"
)
func main() {
   for {
     fmt.Println("hello,world!")
     time.Sleep(time.Second)
   }
}
## 查看该项目的构建脚本
[root@ubuntu2004 go-hello]# cat build.sh
#!/bin/bash
## 从命令行参数设置版本。
## 注意shell脚本的相对路径,起点当前工作目录,因此该脚本应当进入compose项目目录后再运行
docker build -t go-hello:$1 .
# 查看该项目的Dockerfile
[root@ubuntu2004 go-hello]# cat Dockerfile
## 以golang环境镜像为父镜像,设置构建阶段别名builder
FROM golang:1.18-alpine AS builder
## 一顿操作后,该阶段上层有了/opt/hello文件
COPY hello.go /opt
WORKDIR /opt
RUN go build hello.go 

## 第二个(最后一个)FROM命令设置父镜像
#FROM alpine:3.15.0
FROM scratch
## COPY命令的 --from 选项指定源文件来源构建阶段
## --from=0 表示来自上一阶段
#COPY --from=0 /opt/hello /hello
COPY --from=builder /opt/hello /hello
CMD ["/hello"]
## 构建
[root@ubuntu2004 go-hello]# bash build.sh v3.0

## 运行镜像
[root@ubuntu2004 go-hello]# docker run --name hello2 go-hello:v3.0
hello, world
在指定阶段结束构建

​ 在不修改 Dockerfile 的情况下,通过指定阶段别名,使构建结束在某一阶段。一般在需要对某阶段进行调试时使用。

docker build --target build -t hello .
使用外部镜像作为阶段以复制

​ 将一个已有的镜像作为阶段进行文件复制

COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
使用前面的阶段作为新阶段

​ 如下例,builder、build1、build2分别为三个不同阶段。

# syntax=docker/dockerfile:1

FROM alpine:latest AS builder
RUN apk --no-cache add build-base

FROM builder AS build1
COPY source1.cpp source.cpp
RUN g++ -o /binary source.cpp

FROM builder AS build2
COPY source2.cpp source.cpp
RUN g++ -o /binary source.cpp

4) 构建过程中的变量

2.3. 注册中心(registry)和仓库(repository)

​ 镜像并不只是归档文件,集中放在某个目录即可,而是具有文件树结构和元数据的对象,因此应该有一种存放和组织管理的方式。镜像存放在镜像仓库中。

​ 镜像仓库分为网络仓库和本地仓库。

​ 本地仓库就是我们用docker images命令查看的那个,其实际存放的在由

1) 仓库与镜像名称

​ 镜像资源不是放在注册中心的账号下,而是存放在仓库中。仓库与账号共同在注册中心管理,账号与仓库相关联,而使用户对仓库有使用和管理的权限。

​ 而镜像的名称是由NAMETAG组合,NAME实际是仓库路径,仓库下的资源以TAG做为识别标记。而TAG一般是镜像的版本号,其中,在拉取镜像的时候如果没有指定TAG,会自动填写latest(最近)作为TAG

​ 因此镜像的存放路径是 注册中心url/仓库名称:TAG ,这也是完整的镜像名称。

2) 登录dockerhub

​ 2023年,hub.docker.com页面已经不能裸连了,很遗憾。但是只是网页前端不能访问,仓库还是可以通信的,因此我们只是拉取镜像是没问题的。

​ 假如已经注册好了了dockerhub账号,可以使用docker login命令来进行登录。

# docker login [-u USERNAME] [-p PASSWORD] [SERVER]
###如果没有使用-u和-p选项,则使用交互的方法登录
###SERVER是注册中心网址,不指定时默认登录dockerhub

3) 第三方注册中心(未完成)

阿里云等云平台提供的个人仓库

4) 搭建私有化注册中心

见自建Docker注册中心章节。

posted @ 2025-03-09 17:51  好坏会红平个  阅读(41)  评论(0)    收藏  举报