Docker
Docker
一、基本概念
- 镜像(
Image
) - 容器(
Container
) - 仓库(
Repository
)
理解以上三个概念,就能理解docker的生命周期
1.镜像
Docker镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件,以及一些运行时所需的配置参数。镜像不包含任何动态数据,其内容在插件之后也不会被改变
分层存储,镜像采用了分层存储的架构,由一组文件系统组成的(多层文件系统联合组成)。在构建镜像时,会一层一层构建,后一层依赖于上一层,后一层上的任何改变都只会发生在本层,不会干涉到上一层。因此构建镜像的时候,需要对每层需要添加的东西尽量加最少最有必要的东西,减少额外的东西
分层存储的特征还使得镜像的复用,定制更为容易
2.容器
容器是镜像运行时的实体,可以被创建、启动、停止、删除暂停等
镜像(Image)和容器(container)的关系,就像是面向对象程序设计中的
类
和实例
一样
容器的实质是进程,运行于属于自己的独立的命名空间。因此容器可以拥有自己的root
文件系统,网络配置、进程空间等,运行在一个隔离的环境。这样的隔离特性,使得容器封装的应用比直接在宿主运行更加安全
容器也是分层存储,是以镜像为基础层,在其上创建一个当前容器的存储层,这个层是为容器运行时进行读写而准备的,称为容器存储层
容器存储层的生命周期跟容器一样,当容器消亡时,容器存储层也随之消亡,因此任何保存于容器存储层的信息都会随着容器的删除而丢失
Dokcer最佳实践的要求,容器不应该向其存储层写入任何数据,容器存储层保存无状态化,所有的文件写入操作,都应该使用数据卷、或者绑定宿主目录
数据卷独立于容器,使用容器卷,容器的删除或者重写运行之后,数据都不会丢失
3.仓库
Docker Register:提供一个集中的存储、分发镜像的服务
一个Docker Register可以包含多个**仓库(Repository);每个仓库可以包含多个标签(Tag),**每个标签对应一个镜像
可以通过<Repository Name>:<Tag Name>
的格式来指定具体的软件是那个版本的镜像
仓库名以两段路径形式出现,比如jwilder/nginx-proxy
前者是Docker Registry多用户环境下的用户名,后者是对应的软件名
Docker Registry 公开服务
Docker Registry公开服务是开放给用户使用、允许用户管理镜像的Registry服务。
最常见的是Docker Registry公开服务是官方的hub.docker.com,也是默认的Registry
也可以使用国内的镜像网站
私有Docker Registry
用户可以在本地搭建私有的Docker Registry。Docker提供了Docker Registry镜像,可以直接使用搭建私有Registry服务
二、镜像
1.获取镜像
从Docker镜像仓库获取镜像的命令是 docker pull
docker pull [选项] [Docker Registry 地址[:端口号]/] 仓库名[:标签]
具体选项可以从docker pull --help
命令查看,
Docker镜像仓库地址:地址格式一般为
<域名/IP>[:端口号]
。默认地址是 Docker Hub仓库名:仓库名是两段式,即
<用户名>/<软件名>
.对于Docker Hub,如果不给出用户名,默认为library
,也就是官方镜像
$ docker pull ubuntu:18.04
上面命令没有给出Docker镜像仓库地址,默认从Docker Hub
获取镜像。而镜像名称是ubuntun:18.04
,因此会获取官方镜像 library/ubuntun
仓库中标签为18.04
的镜像
2.运行
有了镜像后,我们就能够以这个镜像为基础启动并运行一个容器。以上面的ubuntu:18.04
为例,如果我们打算启动ubuntu>>bash
并且进行交互式操作的话,可以执行下面命令
$ docker run -it --rm \
ubuntu:18.04 \
bash
docker run
就是运行容器的命令
-it
:是两个参数,一个是-i
:交互式操作、一个是-t
:终端。这里打算进入bash
执行命令并查看返回结果,--rm
:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动docker rm
。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用--rm
可以避免浪费空间。ubuntu:18.04
:这是指用ubuntu:18.04
镜像为基础来启动容器。bash
:放在镜像名后的是 命令,这里我们希望有个交互式 Shell,因此用的是bash
。 通过exit
退出了这个容器。
列出镜像
使用docker image ls
命令,可以列出已经下载下来的镜像
$ docker image ls
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/library/ubuntu 18.04 e28a50f651f9 3 weeks ago 65.5 MB
列表包含了仓库名
、标签
、镜像ID
、创建时间
、所占用的空间
镜像 ID 则是镜像的唯一标识,一个镜像可以对应多个 标签。
1.镜像体积
docker image ls
列表中的镜像体积总和并非是所有镜像实际硬盘消耗。由于 Docker 镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能会因为使用相同的基础镜像,从而拥有共同的层。由于 Docker
使用 Union FS
,相同的层只需要保存一份即可,因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多。
可以使用 docker system df
命令来查看镜像、容器、数据卷所占用的空间
2.虚悬镜像
一个特殊的镜像,这个镜像既没有仓库名,也没有标签,均为 <none>
。:
<none> <none> 00285df0df87 5 days ago 342 MB
这个镜像原本是有镜像名和标签的,原来为 mongo:3.2
,随着官方镜像维护,发布了新版本后,重新 docker pull mongo:3.2
时,mongo:3.2
这个镜像名被转移到了新下载的镜像身上,而旧的镜像上的这个名称则被取消,从而成为了 <none>
。除了 docker pull
可能导致这种情况,docker build
也同样可以导致这种现象。由于新旧镜像同名,旧镜像名称被取消,从而出现仓库名、标签均为 <none>
的镜像。这类无标签镜像也被称为 虚悬镜像(dangling image) ,可以用下面的命令专门显示这类镜像:
$ docker image ls -f dangling=true
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 00285df0df87 5 days ago 342 MB
一般来说,虚悬镜像已经失去了存在的价值,是可以随意删除的,可以用下面的命令删除。
$ docker image prune
3.中间层镜像
为了加速镜像构建、重复利用资源,Docker会利用中间层镜像。使用在使用一段时间过后,可能会看到一些依赖的中间层镜像。默认的 docker image ls
列表中只会显示顶层镜像,如果希望显示包括中间层镜像所在内的所有镜像的话,需要加-a
参数
$ docker image ls -a
4.列出部分镜像
不加任何参数的情况下,docker image ls
会列出所有顶层镜像,但是有时候我们只希望列出部分镜像。docker image ls
有好几个参数可以帮助做到这个事情。
根据仓库名列出镜像
$ docker image ls ubuntu
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 18.04 f753707788c5 4 weeks ago 127 MB
ubuntu latest f753707788c5 4 weeks ago 127 MB
列出特定的某个镜像,也就是说指定仓库名和标签
$ docker image ls ubuntu:18.04
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 18.04 f753707788c5 4 weeks ago 127 MB
除此以外,docker image ls
还支持强大的过滤器参数 --filter
,或者简写 -f
。之前我们已经看到了使用过滤器来列出虚悬镜像的用法,它还有更多的用法。比如,我们希望看到在 mongo:3.2
之后建立的镜像,可以用下面的命令:
$ docker image ls -f since=mongo:3.2
REPOSITORY TAG IMAGE ID CREATED SIZE
redis latest 5f515359c7f8 5 days ago 183 MB
nginx latest 05a60462f8ba 5 days ago 181 MB
想查看某个位置之前的镜像也可以,只需要把 since
换成 before
即可。
此外,如果镜像构建时,定义了 LABEL
,还可以通过 LABEL
来过滤。
$ docker image ls -f label=com.example.version=0.1
...
5.以特定格式显示
默认情况下,docker image ls
会输出一个完整的表格,但是我们并非所有时候都会需要这些内容。比如,刚才删除虚悬镜像的时候,我们需要利用 docker image ls
把所有的虚悬镜像的 ID 列出来,然后才可以交给 docker image rm
命令作为参数来删除指定的这些镜像,这个时候就用到了 -q
参数。
Dockerfile - 学习
学习自
- 一篇文章带你吃透 Dockerfile - 掘金 (juejin.cn)
- Dockerfile reference
- 全网最详细的Docker-Compose详细教程 - 掘金 (juejin.cn)
- docker compose 配置文件 .yml 全面指南 - 知乎 (zhihu.com)
- compose-spec/spec.md at master · compose-spec/compose-spec · GitHub
学习Dockers前期,通过Docker的官方镜像仓库拉取里面的镜像,根据这些镜像创建出容器并运行
实际上,Docker官方镜像也是通过一定的方式构建出来的,只要弄清其中的逻辑,我们也可以仿照官方镜像的构建过程,构建出自己的镜像
Dockerfile
就是这样一个用于描述Docker镜像构建过程的文本文件,dockerfile可以包含多条构建指令,以及相关的描述
1.什么是容器
容器是计算机上的沙盒进程,与主机上的其它进程隔离,这种隔离利用了内核命名空间和cgroups
。简而言之容器是:
是
image
的可运行实例可以在本地计算机、虚拟机上运行或部署到云中
是可移植的
与其它容器隔离,并运行自己的软件,二进制文件和配置
2.什么是容器映射
当容器运行时,它使用了隔离的文件系统。这个自定义的文件系统由容器映像container image
提供。因为image包含了容器的问价系统,使用image必须包含所有的运行应用程序所必须的所有东西——依赖项、配置、脚本、二进制文件等等。
沙盒进程是指在计算机系统中,为了保障安全和隔离性而采用的一种技术,将应用程序运行在一个受限制的环境中,限制它们能访问的资源和操作范围,从而避免恶意程序和授权程序对系统的破坏
3.容器是怎么运行的
当一个容器运行时,它为其文件系统使用来image的各个层。每个容器都有自己的命名空间来创建/更新/删除文件。在另一个容器中不会看到任何更改,即使它们使用相同的image
4.容器卷[container volumes]
每个容器启动时都是从容器的定义开始的。在容器中可以创建、更新和删除文件,但当容器被删除时,这些改变将回丢失,所有更变都被隔离在各个容器中
卷:提供了将容器的特定文件系统路径链路到主机的能力。如果在主机上的某个文件被挂载,那么当容器中该文件路径下的文件发送更改时,我们在主机上同样也可以看到更改。同样的,启动另一个挂载了同一个文件目录的容器,它也可以访问到相同的文件
镜像构建原理
1.Docker架构模式
docker
使用了client/server的架构模式。构建镜像时,用户在dockers client输入构建命令。docker引擎以 REST API
的形式,像 docker daemon发送构建请求,如何dockers daemon就根据构建请求的内容,开始镜像构建的工作,并向Client持续放回构建过程的信息。
2.镜像分层模型
docker镜像是用于创建容器的只读模板,是通过 Dockerfile中定义的指令构建而成的,构建结束后,会在原有的镜像层上生成一个新的镜像层,如下所示
在 tomcat 镜像创建一个容器后,会在tomcat镜像之上新创建一个可写的容器层,在容器中写文件时,会保存到这个容器层中
3.基础镜像与父级镜像
用于构建基础镜像的 Dockerfile 不指定父级镜像,Docker约定使用如下形式基础镜像
FROM scratch
这里的 scratch
是一个空镜像,可以从零开始构建镜像,常用来构建最小镜像,如busybox
,debian
,alpine
等镜像,省去很多linux命令,因此很小。一般,不需要自己去构建基础镜像。
构建自定义镜像时,通过FROM指定使用说明父级镜像。例如,官方的tomcat命令没有yum,vim等命令,但是我们可以将tomcat镜像作为父级镜像,然后安装想要的命令,这样在容器中就可以使用了。
4.构建上下文 / build context
Client 向 Docker daemon 发送的构架请求包含两部分,第一部分是 Dockerfile文件,第二部分是构建上下文
构建上下文是一些文件集合,这些文件可以是指定路径下的文件,也可以是远程资源中指定路径下的文件,在构建过程中,Docker daemon 可以访问这些文件,并执行相应的操作[理解:访问配置文件]
- 路径上下文
构建命令中指定具体路径,该路径下的所有文件即为构建上下文,这些文件被打包送给Docker daemon中,然后被解压
假使一个项目的文件结构如下
demo
|--Dockerfile
|--src
|--test
|--node_modules
在项目根目录执行下面的构建命令
docker build -t img-tag .
构建请求的第一部分是Dockerfile,这个文件在当前目录下,文件是默认名称,因此省略,
相当于默认加上了 -f Dockerfile, 该Dockerfile内容如下
FROM busybox
WORKDIR /src
COPY src .
构建请求的第二部分是 .
这个点代表当前,此时当前目录就是此次的构建的上下文,Docker引擎会整理该目录下的所有文件,把不被 .dockerignore
中的规则所的文件都发送到Docker daemon中,如下所示
如果此时位于项目根目录的上一级目录,构建命令如下
docker build -t img-tag -f ./demo/Dockerfile ./demo/
- URL上下文
Docker 还支持利用远程仓库URL构建镜像,此时指定的远程仓库目录就充当了构建上下文
docker build https://gitee.com:user/my-repo.git#master:docker
以上构建命令指定了一个 Gitee 项目的 master 分支,冒号(:)之前是 Git 检出的目标 URL, 冒号之后的 docker 是远程仓库根目录下的一个子目录,此时这个子目录就是构建上下文
Docker client 执行构建命令时,Docker 引擎首先会将远程仓库的 master 分支拉取到本地的一个临时目录中,然后将其中的 docker 目录下的文件作为构建上下文发送到 Docker daemon 中。拉取远程文件之后,又回到了路径上下文的步骤,如下图所示
- 省略上下文
如果 Dockerfile 中的指令不需要对任何文件进行操作,可以省略构建上下文,此时不会向 Docker daemon 发送额外的文件,这可以提高构建速度
docker build -t my-hello-world:latest -<<EOF
FROM busybox
RUN echo "hello world"
EOF
5.构建缓存
迭代过程中,Dockerfile对于的资源会被经常修改,因此需要频繁重新构建镜像,Docker为了提高构建速度,设计了多种优化方案,其中最重要的是构建缓存
示例:说明构建缓存是如何工作的,Dockerfile如下
# syntax=docker/dockerfile:1
FROM ubuntu:latest
RUN apt-get update && apt-get install -y build-essentials
COPY main.c Makefile /src/
WORKDIR /src/
RUN make build
镜像构建过中,dockerfile 中的指令会从上往下执行,每一个构建步骤的结果都会被缓存起来,例如
此时再次构建,会直接使用缓存中的结果(Using cache)
这里假设修改了main.c 中的代码,再次构建时,从 COPY main Makefile /src/
这条指令开始,后续构建缓存都会失效,如下图所示
如果不想使用构建缓存,执行构建命令时,可以传入 --no-cahe
6.镜像构建过程
Docker Client 执行构建命令后,会经过以下步骤构建出最终镜像
确定构建上下文,如果构建上下文中有 .dockerignore 文件,解析该文件的匹配规则,将构建上下文中被匹配的文件资源排除
将 Dockerfile 和构建上下文发送给 Docker daemon
Docker daemon 收到构建请求。以下的步骤都由 Docker daemon 完成,省略主语
逐条校验 Dockerfile 中的指令是否合法,如果不合法,立即结束构建。这一步可以确定一共有多少个构建步骤,便于后续分步构建时显示当前步骤,如 Step 1/2
逐条执行 Dockerfile 中的指令,每条指令都新创建一层。会生成临时 container 用于执行命令,该步骤结束后删除临时容器
生成最终镜像
.dockerignore
这个文件需要遵循一定的语法规则
以 # 开头的行是备注,不会被解析为匹配规则
支持 ? 通配符,匹配单个字符
支持 * 通配符,匹配多个字符,只能匹配单级目录
支持 ** 通配符,可匹配多级目录
支持 ! 匹配符,声明某些文件资源不需要被排除
可以用 .dockerignore 排除 Dockerfile 和 .dockerignore 文件。Docker Client 仍然会将这两个文件发送到 Docker daemon,因为 Docker 底层需要。但 ADD 和 COPY 指令就无法操作这两个文件了
示例:
# this is a .dockerignore demo
*/demo*
*/*/demo*
demo?
**/mydemo*
Dockerfile
Dockerfile时一个用于描述Docekr镜像构建过程的文本文件,包含多条构建指令,以及相关的描述
Dockerfile的构建指令需要遵循如下的语法
# Comment
INSTRUCTION arguments
以 #
开头的行绝大部分是注释,还有一小部分是解析器指令
构建指令分两个部分,第一部分是指令,第二部分是指令参数。
1.解析器指令 / parse directive
解析器指令是以 #
开始,用来提示解释器对 Dockerfile进行特殊处理,构建过程中它不会增加镜像层,也不会出现在构建过程
解析器指令是可选的
# directive=value
# 解析器指令需要在空行,注释,构建指令之前
注意事项
同一解析器指令不能重复
不区分大小写,按照惯例,推荐小写
空行、注释、构建指令之后,Docker 不再查找解析器指令,都当成注释
按照惯例,解析器指令位于 Dockerfile 的第一行,在后面添加空行
行内的空格被忽略,不支持跨行
Docker 目前支持两种解析器指令
syntax
escape
syntax 解析器指令,只有使用 BuildKit 作为构建器时才生效
escape 解析器指令,用于指定在 Dockerfile 中使用转义字符
在 Dockerfile 中,escape 默认为 \
# escape=\
复制代码
但 Windows 系统中的 \ 是路径分隔符,推荐将 escape 替换为 `,这和 PowerShell 是一致的
# escape=`
2.常见指令解析
序号 | 指令名 | 功能描述 |
1 | FROM | 指定基础镜像或者父级镜像 |
2 | LABEL | 为镜像添加元数据 |
3 | ENV | 设置环境变量 |
4 | WORKDIR | 指定后续指令的工作目录,类似于Linux中的cd命令 |
5 | USER | 指定当前构建阶段以及容器运行时的默认用户,以及可选的用户组 |
6 | VOLUME | 创建具有指定名称的挂载数据卷,用于数据持久化 |
7 | ADD | 将构建上下文中指定目录下的文件复制到镜像文件按系统的指定位置 |
8 | COPY | 功能与语法与ADD类似,但是不会自动解压文件,也不能访问网络资源 |
9 | EXPOSE | 约定容器运行时监听的端口,通常用于容器与外界之间的通信 |
10 | RUN | 用于构建镜像过程中执行目录 |
11 | CMD | 构建镜像成功后,所创建的容器启动时执行的命令,常与ENTRYPOINT结合使用 |
12 | ENTRYPOINT | 用于配置容器以可执行的方式运行,常与CMD结合使用 |
FROM
指定基础镜像或父级镜像
FORM [--platform=<platform>] <image> [AS <name>]
FORM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FORM [--platform=<platform>] <image>[@<digest>] [AS <name>]
tag
或digest
是可选项,默认为latest版本为基础镜像如果不以任何镜像为基础,使用:
FORM scratch
.scratch是一个空镜像,用于构建最小镜像
LABEL
为镜像添加元数据
LABEL <key>=<value> <key>=<value> <key>=<value>...
示例:
LABEL auth="ch" \
version="1.0.0" \
decription="Dockerfile"
使用
LABEL
定义键值对结构的元数据,一个LABEL可指定多个元数据定义元数据值时,尽量使用双引号
当前镜像可以继承继承镜像或者父级镜像中的元数据时,可以覆盖
可使用以下命令查看元数据
docker image inspect -f='{{json .ContainerConfig.Labels}}' my-image
ENV
设置环境变量
ENV <key>=<value>...
ENV <key> <value>
当前镜像可以继承基础镜像或者父级镜像中的环境变量,也可以覆盖
使用
ENV
指定定义的环境变量,最终会持久化到容器中运行容器时,可以通过
--env =
或者-e =
覆盖镜像定义中的环境变量对只使用在镜像构建过程中的变量,推荐使用
ARG
,或者内环境变量,这样不会被持久化到最终的镜像中
内环境变量示例:
RUN TEMP="no persisit"
- 查看最终镜像中的环境变量
docker image inspect -f='{{json .ContainerConfig.Env}}' my-image
WORKDIR
指定后续指令的工作目录,类似linux中的cd命令
WORKDIR /path/to/workdir
使用Dockerfile中设置的环境变量
ENV DIR_PATH=/demo
WORKDIR $DIR_PATH/$DIR_NAME
RUN pwd
构建镜像时,pwd 的输出结果是 /demo,因为 $DIR_NAME 未显示指定,直接忽略
默认的工作目录是
/
可以使用Dockerfile中显示指定的环境变量,包括父级镜像中的环境变量
父级镜像可能设置工作目录,最佳实践是显示设置当前镜像的工作目录
USER
指定当前构建阶段以及容器运行时的默认用户,以及可选的用户组
USER <user>[:<group>]
USER <user>[:<GID>]
USER <UID>[:<group>]
USER <UID>[:<GID>]
使用USER指定用户后,Dockerfile中构建镜像的
RUN
,CMD
,ENTRYPOINT
指令都会使用该用户,同时这个用户也是容器运行时的默认用户不指定用户组,使用默认用户组root
运行容器时,可以使用
-u
参数覆盖Dockerfile中默认的用户
VOLUME
创建具有指定名称的挂载数据卷,用于数据持久化
VOLUME ["volume1","volume2",...]
VOLUME volume1 volume2 ...
数据卷的特征以及作用:
数据持久化,避免容器重启后丢失重要数据
修改数据卷时不会对容器产生影响,防止容器不断膨胀
有利于多个容器共享数据
ADD
将构建上下文中指定目录下的文件**(src)复制到镜像文件系统的指定位置(dest)**
ADD [--chown=<user>:<group>][--checksum=<checksum>]<src>... <dest>
ADD [--chown=<user>:<group>]["<src>", ..."<dest>"]
ADD <git ref> <dir>
如果
ADD
指令对应的src资源有变更,Dockerfile中这条指令后的构建缓存都会失效Dockerfile中
--chown
特性只有在linux下才有效,windows是无效的src支持通配符
dest必须是文件夹,用以存放文件
如果src是压缩资源,将会被解压为一个文件
如果 src 是远程 URL, 并且 dest 不以 / 结尾,Docker 从 URL 下载文件,存到 dest 中
如果 src 是远程 URL,URL 中含有非空路径,并且 dest 以 / 结尾,Docker 会推断文件名,根据 URL 中的路径,在目标位置创建相同路径,将下载的文件放入其中
dest 可以是镜像文件系统下的绝对路径,或者是 WORKDIR 下的相对路径
如果 dest 不是以 / 结尾,Docker 会把它当成普通文件,src 中的内容会被写入这个文件中
如果目标位置下的某些目录不存在,会自动创建
ADD 添加网络资源时不支持身份认证,可以使用 RUN wget 或者 RUN curl 实现这个功能
COPY
功能与ADD类似,但是不会自动解压文件,也不能访问网络资源
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
EXPOSE
约定容器运行时监听的端口,通常用于容器与外界之间的通信
EXPOSE <port> [<port>/<protocol>...]
支持 TCP 或者 UDP 协议,如果不显式指定协议,默认使用 TCP 协议
需要同时以 TCP 和 UDP 协议的方式暴露同一个端口时,需要分别指定
EXPOSE 并不会真正将端口发布到宿主机,而是作为一种约定,让镜像使用者在运行容器时,用 -p 分别发布约定端口,或者 -P 发布所有约定端口
如果没有暴露端口,运行容器是也可以通过 -p 的方式映射端口
RUN
用于构建镜像过程中执行命令,有两种执行方式
第一种,以shell方式运行
RUN <command>
RUN echo "Hello Dockerfile"
第二种,以exec的方式运行
RUN ["executable","param1","param2"...]
CMD
构建镜像成功后,所创建的容器启动时执行的命令
CMD command param1 param2 #shell
CMD ["executable","param1","param2"] #exec
CMD ["param1","param2"] #作为ENTRYPOINT的默认参数,是exec方式的特殊形式
Docker种只有一条
CMD
指令生效,在多条CMD指令存在的情况下,只有最后一条生效虽然Dockerfile中只有一条CMD生效,但每一条CMD指令会新增一个镜像层,所有推荐只定义一条CMD指令,使用
&&
连接多个指令exec方式是通过JSON数组的方式进行解析的,因此需要双引号
与RUN指令不同,RUN指令是在构建指令的过程中执行,CMD命令是在容器启动时执行
docker run
后的命令行参数会覆盖CMD中的命令
ENTRYPOINT
用于配置容器以可执行的方式运行。有两种形式
ENTRYPOINT ["executable","param1","param2"] #推荐
ENTRYPOINT command param1 param2
Dockerfile中只有最后一条
ENTRYPOINT
指令生效运行容器时,docker run --entrypoint 覆盖该指令
shell 形式的 ENTRYPOINT 会使 CMD 命令 和 docker run
中的命令行参数失效。它有一个缺点,ENTRYPOINT 命令将作为 /bin/sh -c 的子命令,不会传递信号。比如,停止容器时,容器内接收不到 SIGTERM 信号,这并不是预期的效果,可以在命令前添加 exec 来解决,如 ENTRYPOINT exec top -b
指定 ENTRYPOINT 后,CMD 的内容将作为默认参数传给 ENTRYPOINT 指令,形如
如果 CMD 是在基础镜像中定义的,当前镜像定义的 ENTRYPOINT 会将 CMD 的值重置为空值,这种情况下,需要重新定义 CMD
Docker-Compose
docker-compose通过一个声明式的配置文件描述整个应用,从而使用一条命令即可完成部署
docker-compose同使用YAML文件来定义多级服务,在使用时默认使用文件名docker-compose.yml
,也可以在docker compose运行时使用-f
参数来指定具体文件
compose的优点
在单主机上建立多个隔离环境,Compose使用项目名称将环境彼此隔离,可以在多个不同的上下文中使用此项目名称
创建容器时保留卷数据
仅重新创建以更改的容器,当服务没有更改时,Compose会使用现有的容器
变量在环境之间组合重复使用
多个配置文件
可以为用一个项目配置多个compose文件,使用多个compose文件能够针对不同的环境或者不同的工作流自定义compose应用程序
默认情况下,compose读取两个文件,docker-compose.yml
和一个可选docker-compose.override.yml
文件
如果在两个文件中都定义了服务,compose会使用override进行合并配置
当配置文件的名称非默认情况时,可以使用-f
指定Compose文件
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
yaml文件级
Docker compose的YAML文件包含有4个一级key:version
,services
,networks
,volumes
version
:指定版本信息,通常位于文件的第一行。其定义了Compose文件格式的版本。services
:用于定义不用的应用服务。Docker Compose会将每个服务部署在各种的容器中。networks
:用于指引Docker创建新的网络。默认情况下,Docker Compose会创建bridge网络,这个是一个单主机网络,只能实现同一主机上容器的连接。可以使用driver属性指定不同的网络类型volumes
用于指引Docker来创建新的卷
docker-compose.yml的具体配置:
1.build
指定构建镜像的dockerfile的上下文路径,或者详细配置文件
version: "3.9"
services:
webapp:
build: ./dir
或者更详细的写法
version: "3.9"
services:
webapp:
build:
context: ./dir
dockerfile: Dockerfile-alternate
args:
buildno: 1
context 上下文路径,可以是文件路径,也可以是到链接到 git 仓库的 url。当是相对路径时,它被解释为相对于 Compose 文件的位置。
dockerfile 指定构建镜像的 Dockerfile 文件名
args 构建参数,只能在构建过程中访问的环境变量
cache_from 缓存解析镜像列表
labels 设置构建镜像的元数据
network 设置网络容器连接,
none
表示在构建期间禁用网络shm_size 设置
/dev/shm
此构建容器的分区大小target 多阶段构建,可以指定构建哪一层
2.network
...累了,下次再写
version: '3.9'
services:
mysql:
build:
context: ./mysql
environment:
MYSQL_ROOT_PASSWORD: admin
restart: always
container_name: mysql
volumes:
- /data/edu-bom/mysql/test:/var/lib/mysql
image: mysql/mysql:5.7
ports:
- 3306:3306
networks:
net:
eureka:
build:
context: ./edu-eureka-boot
restart: always
ports:
- 8761:8761
container_name: edu-eureka-boot
hostname: edu-eureka-boot
image: edu/edu-eureka-boot:1.0
depends_on:
- mysql
networks:
net:
networks:
net:
volumes:
vol:
docker compose常用命令
构建并启动服务——
docker-compose up -d
停止运行并删除服务——
docker-compose down
列出所有运行容器——
docker-compose ps
查看服务日志——
docker-compose logs
构建或重建——
docker-compose build
启动服务——
docker-compose start
停止运行中的服务——
docker-compose stop
重启服务——
docker-compose restart
使用Docker 以及Docker Compose部署Go程序
使用docker的主要目标是其容器化。可以为应用程序提供一致的环境,而不依赖它运行的主机
部署示例
1.准备代码
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", hello)
server := &http.Server{
Addr: ":8888",
}
fmt.Println("server startup...")
if err := server.ListenAndServe(); err != nil {
fmt.Printf("server startup failed, err:%v\n", err)
}
}
func hello(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("hello liwenzhou.com!"))
}
这里是简单代码
2.创建Docker镜像
镜像(image)包含运行应用程序所需的所有东西——代码/二进制文件、运行时、依赖项以及所需的任何其它人间系统对象
简单讲,镜像是定义应用程序以及运行所需的一切
3.编写Dockerfile
要创建Docker镜像(image)必须在配置文件中的指定步骤,这个文件默认称为Dockerfile
FROM golang:alpine
# 为我们的镜像设置必要的环境变量
ENV GO111MODULE=on \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64
# 移动到工作目录:/build
WORKDIR /build
# 将代码复制到容器中
COPY . .
# 将我们的代码编译成二进制可执行文件app
RUN go build -o app .
# 移动到用于存放生成的二进制文件的 /dist 目录
WORKDIR /dist
# 将二进制文件从 /build 目录复制到这里
RUN cp /build/app .
# 声明服务端口
EXPOSE 8888
# 启动容器时运行的命令
CMD ["/dist/app"]
4.Dockerfile解析
- From
使用了基础镜像 golang:alpine
来创建镜像。这个镜像运行的是alpine Linux发行版,该发行版的大小很小并内置了Go。有大量公开可用的Docker镜像,请查看https://hub.docker.com/_/golang
- Env
用来设置编译阶段需要的环境变量
WORKDIR,COPY,RUN
EXPORT,CMD
声明服务端口,应用程序监听这个端口并通过这个端口对外提供服务。还定义了运行镜像执行的默认执行命令CMD ["/dist/app"]
构建镜像
在项目目录下面,在终端输入下面的命令创建镜像,并指定镜像名称为go_app
docker build . -t go_app
等待构建结束,输出 Successfully
等输出 Successfully
后,此时镜像已经准备好了,但是目前什么项目都没有,需要运行下面的代码来运行镜像。注:运行中的镜像称为镜像
docker run -p 8888:8888 go_app
标志位-p
来定义端口绑定,由于容器中的应用程序在端口8888上运行,这里绑定的主机端口也是8888。如果要绑定另一个端口,则可以使用 -p $HOST_PORT:8888
到这里就可以测试我们的程序是否工作正常,打开 http://127.0.0.1:8888 查看事先定义的响应内容。
分阶段构建示例
Go程序编译之后可得到一个可执行的二进制文件,在最终的镜像中不需要go编译器,也就是说我们只需要一个运行最终二进制文件的容器即可。
Docker的最佳实践之一是通过仅保留二进制文件来减小镜像大小,为此,我们将使用一种称为多阶段构建的技术
FROM golang:alpine AS builder
# 为我们的镜像设置必要的环境变量
ENV GO111MODULE=on \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64
# 移动到工作目录:/build
WORKDIR /build
# 将代码复制到容器中
COPY . .
# 将我们的代码编译成二进制可执行文件 app
RUN go build -o app .
###################
# 接下来创建一个小镜像
###################
FROM scratch
# 从builder镜像中把/dist/app 拷贝到当前目录
COPY --from=builder /build/app /
# 需要运行的命令
ENTRYPOINT ["/app"]
使用这种技术,我们剥离了使用golang:alpine
作为编译镜像来编译得到二进制可执行文件的过程,并基于scratch
生成一个简单的、非常小的新镜像。我们将二进制文件从命名为builder
的第一个镜像中复制到新创建的scratch
镜像中。有关scratch镜像的更多信息,请查看https://hub.docker.com/_/scratch
附带其他文件的部署示例
web项目(前后端不分离)一般会有静态文件或者配置文件,需要拷贝到最终的镜像文件中
例如 templates
| static
| conf
三个文件的内容拷贝到镜像文件中
FROM golang:alpine AS builder
# 为我们的镜像设置必要的环境变量
ENV GO111MODULE=on \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64
# 移动到工作目录:/build
WORKDIR /build
# 复制项目中的 go.mod 和 go.sum文件并下载依赖信息
COPY go.mod .
COPY go.sum .
RUN go mod download
# 将代码复制到容器中
COPY . .
# 将我们的代码编译成二进制可执行文件 bubble
RUN go build -o bubble .
###################
# 接下来创建一个小镜像
###################
FROM scratch
COPY ./templates /templates
COPY ./static /static
COPY ./conf /conf
# 从builder镜像中把/dist/app 拷贝到当前目录
COPY --from=builder /build/bubble /
# 需要运行的命令
ENTRYPOINT ["/bubble", "conf/config.ini"]
Tips: 这里把COPY静态文件的步骤放在上层,把COPY二进制可执行文件放在下层,争取多使用缓存。
关联其他容器
项目中使用了MySQL,可以选择使用如下命令启动一个MySQL容器,它的别名为mysql8019
;root用户的密码为root1234
;挂载容器中的/var/lib/mysql
到本地的/Users/docker/mysql
目录;内部服务端口为3306,映射到外部的13306端口。
docker run --name mysql8019 -p 13306:3306 -e MYSQL_ROOT_PASSWORD=root1234 -v /Users/q1mi/docker/mysql:/var/lib/mysql -d mysql:8.0.19
这里需要修改一下我们程序中配置的MySQL的host地址为容器别名,使它们在内部通过别名(此处为mysql8019)联通。
[mysql]
user = root
password = root1234
host = mysql8019
port = 3306
db = bubble
修改后记得重新构建bubble_app
镜像:
docker build . -t bubble_app
我们这里运行bubble_app
容器的时候需要使用--link
的方式与上面的mysql8019
容器关联起来,具体命令如下:
docker run --link=mysql8019:mysql8019 -p 8888:8888 bubble_app
Docker Compose模式
除了像上面一样使用--link
的方式来关联两个容器之外,我们还可以使用Docker Compose
来定义和运行多个容器。
Compose
是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,你可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务。
使用Compose基本上是一个三步过程:
使用
Dockerfile
定义你的应用环境以便可以在任何地方复制。定义组成应用程序的服务,
docker-compose.yml
以便它们可以在隔离的环境中一起运行。执行
docker-compose up
命令来启动并运行整个应用程序。
我们的项目需要两个容器分别运行mysql
和bubble_app
,我们编写的docker-compose.yml
文件内容如下:
# yaml 配置
version: "3.7"
services:
mysql8019:
image: "mysql:8.0.19"
ports:
- "33061:3306"
command: "--default-authentication-plugin=mysql_native_password --init-file /data/application/init.sql"
environment:
MYSQL_ROOT_PASSWORD: "root1234"
MYSQL_DATABASE: "bubble"
MYSQL_PASSWORD: "root1234"
volumes:
- ./init.sql:/data/application/init.sql
bubble_app:
build: .
command: sh -c "./wait-for.sh mysql8019:3306 -- ./bubble ./conf/config.ini"
depends_on:
- mysql8019
ports:
- "8888:8888"
这个 Compose 文件定义了两个服务:bubble_app
和 mysql8019
。其中:
bubble_app
使用当前目录下的Dockerfile
文件构建镜像,并通过depends_on
指定依赖mysql8019
服务,声明服务端口8888并绑定对外8888端口。
mysql8019
mysql8019 服务使用 Docker Hub 的公共 mysql:8.0.19 镜像,内部端口3306,外部端口33061。
这里需要注意一个问题就是,我们的bubble_app
容器需要等待mysql8019
容器正常启动之后再尝试启动,因为我们的web程序在启动的时候会初始化MySQL连接。这里共有两个地方要更改,第一个就是我们Dockerfile
中要把最后一句注释掉:
# Dockerfile
...
# 需要运行的命令(注释掉这一句,因为需要等MySQL启动之后再启动我们的Web程序)
# ENTRYPOINT ["/bubble", "conf/config.ini"]
第二个地方是在bubble_app
下面添加如下命令,使用提前编写的wait-for.sh
脚本检测mysql8019:3306
正常后再执行后续启动Web应用程序的命令:
command: sh -c "./wait-for.sh mysql8019:3306 -- ./bubble ./conf/config.ini"
当然,因为我们现在要在bubble_app
镜像中执行sh命令,所以不能在使用scratch
镜像构建了,这里改为使用debian:stretch-slim
,同时还要安装wait-for.sh
脚本用到的netcat
,最后不要忘了把wait-for.sh
脚本文件COPY到最终的镜像中,并赋予可执行权限哦。更新后的Dockerfile
内容如下:
FROM golang:alpine AS builder
# 为我们的镜像设置必要的环境变量
ENV GO111MODULE=on \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64
# 移动到工作目录:/build
WORKDIR /build
# 复制项目中的 go.mod 和 go.sum文件并下载依赖信息
COPY go.mod .
COPY go.sum .
RUN go mod download
# 将代码复制到容器中
COPY . .
# 将我们的代码编译成二进制可执行文件 bubble
RUN go build -o bubble .
###################
# 接下来创建一个小镜像
###################
FROM debian:stretch-slim
COPY ./wait-for.sh /
COPY ./templates /templates
COPY ./static /static
COPY ./conf /conf
# 从builder镜像中把/dist/app 拷贝到当前目录
COPY --from=builder /build/bubble /
RUN set -eux; \
apt-get update; \
apt-get install -y \
--no-install-recommends \
netcat; \
chmod 755 wait-for.sh
# 需要运行的命令
# ENTRYPOINT ["/bubble", "conf/config.ini"]
所有的条件都准备就绪后,就可以执行下面的命令跑起来了:
docker-compose up