教程地址:datawhale组队学习之Docker教程

1. Docker概述

1.1 Docker为什么出现?

项目带环境一块打包上线,docker就是做这个事情的。避免了不同机器配置环境所带来的麻烦。

打包项目带上环境(镜像机制)——docker仓库——下载镜像直接运行即可!

docker的思想来自于集装箱

**Docker核心思想:隔离,把所有东西打包装箱,每个箱子互相隔离。**再也不用担心环境的问题。

例如一个集装箱装水果,另一个装生化武器,两个互不干扰,并且可以在同一机器上运行。

Docker还通过隔离机制,将服务器性能利用到极致。

1.2 Docker历史

Docker 使用 Google 公司推出的 Go 语言进行开发实现,基于 Linux 内核的 cgroup namespace ,以及 OverlayFS 类的 Union FS 等技术,对进程进行封装隔离,属于 操作系统层面的虚拟化技术 。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器

Docker为什么这么火?轻巧。容器技术出来之前,大家都用虚拟机技术,虚拟机十分笨重。

虚拟机和Docker容器都是虚拟化技术。

VM:Linux centos原生镜像(一个电脑!) 隔离,需要开启多个虚拟机; 几个G

Docker:隔离, 镜像(最核心的环境,很小) 运行镜像就可以了。 几个M

Docker基于Go语言开发,Docker的文档超级详细。

仓库地址:hub.docker.com

文档地址:docs.docker.com

1.3 Docker与虚拟机

虚拟机:

Docker容器:

  • Docker 在容器的基础上,进行了进一步的封装,从文件系统、网络互联到进程隔离等等,极大的简化了容器的创建和维护。使得 Docker 技术比虚拟机技术更为轻便、快捷。
  • 传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;
  • 容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。每个容器间互相隔离,每个容器内都有一个属于自己的文件系统。

Docker为什么比虚拟机快

  1. Docker有比虚拟机更少的抽象层
  2. Docker利用的是宿主机的内核,VM需要GuestOS

所以说新建一个容器时,docker不需要像虚拟机一样重新加载一个操作系统内核,避免引导性的操作。虚拟机是加载GuestOS,分钟级别的,很慢。而Docker是利用宿主机的操作系统,省略了这个复杂的过程,是秒级的。

DevOps(开发,运维)

  1. 更快速的交付和部署。

    传统:一堆帮助文档,安装程序;

    Docker:打包镜像,发布测试,一键运行。

  2. 更便捷的升级和扩缩容

    使用 Docker之后,部署应用就像搭积木一样。

    项目打包为一个镜像。

  3. 更简单的系统运维

    在容器化之后,开发和测试环境高度一致。不会再出现“你的电脑上没问题,我的电脑上出问题了”这种问题。

  4. 更高效的计算资源利用

    Docker是内核级别的虚拟化,可以在一个物理机上运行很多的容器实例。服务器的性能可以被压榨到极致。

2. Docker安装

2.1 Docker的基本组成

Docker架构图

镜像(Image):Docker镜像就像是一个模板,可以通过模板来创建容器服务,

例如:Tomcat镜像——>run——>tomcat01容器(提供服务器),通过这个镜像可以创建多个容器,最终服务运行或者项目运行就是在容器中的。

容器(Container)

Docker利用容器技术,独立运行一个或者一组应用,通过镜像来创建。

基本命令:启动、停止、删除。

仓库(Repository)

存放镜像的地方。分为公有仓库和私有仓库。

2.1.1 Docker镜像

操作系统分为 内核用户空间。对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。而 Docker 镜像Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:18.04 就包含了完整的一套 Ubuntu 18.04 最小系统的 root 文件系统。

Docker 镜像 是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像 不包含 任何动态数据,其内容在构建之后也不会被改变。

分层存储

因为镜像包含操作系统完整的 root 文件系统,体积庞大,因此在 Docker 设计时,就充分利用 Union FS的技术,将其设计为分层存储的架构。所以严格来说,镜像并非是像一个 ISO 那样的打包文件,镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。

镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。因此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。

分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。

2.1.2 Docker容器

镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的 实例 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间 。因此容器可以拥有自己的 root 文件系统、网络配置、进程空间,甚至用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。

镜像使用的是分层存储,容器也是如此。每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为 容器存储层

容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。

按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用 数据卷(Volume)、或者 绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。

数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器删除或者重新运行之后,数据却不会丢失。

2.1.3 Docker仓库

集中的存储、分发镜像的服务。一个 Docker Registry 中可以包含多个 仓库Repository);每个仓库可以包含多个 标签Tag);每个标签对应一个镜像。与Github仓库类似,分为公开仓库和私有仓库。

2.2 安装Docker

需要一台Linux服务器。狂神使用的是阿里云的centos7。

系统版本:

官网centos系统的Docker安装教程:https://docs.docker.com/engine/install/centos/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 1. 卸载旧的docker版本
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine

# 2. 安装需要的安装包
yum install -y yum-utils

# 3. 改镜像仓库,改成阿里云的
sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo # 默认国外,太慢,不用

sudo yum-config-manager \
--add-repo \
http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # 阿里云

# 更新yum软件包索引
yum makecache fast

# 4. 安装Docker相关的依赖 docker-ce社区版 -ee企业版
sudo yum install docker-ce docker-ce-cli containerd.io

# 5. 启动docker
sudo systemctl start docker

# 6. 查看是否安装成功
docker version

# 7. 测试hello world
docker run hello-world

运行官方demo hello-world,没有发现该镜像,于是去官网pull一个hello-world镜像,下载成功后运行该镜像。

1
2
3
4
5
6
7
8
9
# 8. 查看一下下载的hello-world镜像
docker images # 查看所有镜像

# extra:卸载docker
sudo yum remove docker-ce docker-ce-cli containerd.io
sudo rm -rf /var/lib/docker
sudo rm -rf /var/lib/containerd

# /var/lin/docker docker的默认工作路径

2.3 阿里云镜像加速

  1. 阿里云的容器镜像服务
  2. 找到镜像工具-镜像加速器

  1. 配置使用
1
2
3
4
5
6
7
8
9
10
11
sudo mkdir -p /etc/docker

sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://ejhhjy6f.mirror.aliyuncs.com"]
}
EOF

sudo systemctl daemon-reload

sudo systemctl restart docker

2.4 回顾hello-world流程

2.5 底层原理

Docker是怎么工作的?

Docker是一个Client-Server结构的系统,Docker的守护进程运行在主机上,通过Socket从客户端访问。

DockerServer接收到Docker-Client的指令,就会执行这个命令。

学完命令再看这段理论,会很清晰。

3. Docker常用命令

3.1 帮助命令

1
2
3
docker version		# 版本
docker info # 详细信息
docker 命令 --help # 帮助命令

帮助文档地址(可查找所有docker命令):https://docs.docker.com/engine/reference/commandline

3.2 镜像命令

3.2.1 获取/下载镜像

完整命令:

docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]

[Docker Registry 地址[:端口号]/] 默认是Docker Hub(docker.io)

仓库名格式是<用户名>/<软件名>,用户名默认是library

比如说最新版本的mysql的完整命令是:docker.io/library/mysql:latest

docker pull mysql == docker pull docker.io/library/mysql:latest

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
docker pull 镜像名[:tag]  # 下载版本为tag的镜像,不加tag下载最新版

(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker pull mysql
Using default tag: latest # 不写tag,默认最新版
latest: Pulling from library/mysql
a076a628af6f: Pull complete # 分层下载,docker image 的核心 联合文件系统
f6c208f3f991: Pull complete
88a9455a9165: Pull complete
406c9b8427c6: Pull complete
7c88599c0b25: Pull complete
25b5c6debdaf: Pull complete
43a5816f1617: Pull complete
1a8c919e89bf: Pull complete
9f3cf4bd1a07: Pull complete
80539cea118d: Pull complete
201b3cad54ce: Pull complete
944ba37e1c06: Pull complete
Digest: sha256:feada149cb8ff54eade1336da7c1d080c4a1c7ed82b5e320efb5beebed85ae8c # 签名防伪标志
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest # 真实地址

# 下载指定版本
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker pull mysql:5.7
5.7: Pulling from library/mysql
a076a628af6f: Already exists # 前面已经下载过,就不用再下载了,分层共用的好处
f6c208f3f991: Already exists
88a9455a9165: Already exists
406c9b8427c6: Already exists
7c88599c0b25: Already exists
25b5c6debdaf: Already exists
43a5816f1617: Already exists
1831ac1245f4: Pull complete
37677b8c1f79: Pull complete
27e4ac3b0f6e: Pull complete
7227baa8c445: Pull complete
Digest: sha256:b3d1eff023f698cd433695c9506171f0d08a8f92a0c8063c1a4d9db9a55808df
Status: Downloaded newer image for mysql:5.7
docker.io/library/mysql:5.7

3.2.2 列出镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
docker images [options]  # 查看所有本地主机的镜像
docker image ls # 查看所有本地主机的镜像

(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest d1165f221234 3 weeks ago 13.3kB

# 解释
REPOSITORY 镜像的仓库源(名字)
TAG 镜像标签
IMAGE ID id
CREATED 镜像创建时间
SIZE 镜像大小

ID是镜像的唯一标识,一个镜像可对应多个标签。

# options可选项
-a, -all # 列出所有镜像(包括隐藏镜像)
-q, -quiet # 只显示镜像id
-aq # 显示所有镜像的id

3.2.3 删除镜像

docker image rm [options] <IMAGE1> [<IMAGE2>...]

docker rmi [options] <IMAGE1> [<IMAGE2> ...]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
-f  # 强制移除镜像

docker rmi -f 镜像id # 删除指定镜像
docker rmi -f 镜像id1 镜像id2 镜像id3 # 删除多个镜像
docker rmi -f $(docker images -aq) # 递归删除所有镜像(Linux的骚操作)
docker rmi -f $(docker image ls -q redis) # 删除所有仓库名为redis的镜像

$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
centos latest 0584b3d2cf6d 3 weeks ago 196.5 MB
redis alpine 501ad78535f0 3 weeks ago 21.03 MB
docker latest cf693ec9b5c7 3 weeks ago 105.1 MB
nginx latest e43d811ce2f4 5 weeks ago 181.5 MB

# 用镜像ID删除镜像,可以只取前几位,只要能与其他镜像区分开就可以
$ docker image rm 501 # 删除redis
# 用镜像名删除镜像
$ docker iamge rm centos
Untagged: centos:latest
Untagged: centos@sha256:b2f9d1c0ff5f87a4743104d099a3d561002ac500db1b9bfa02a783a46e0d366c
Deleted: sha256:0584b3d2cf6d235ee310cf14b54667d889887b838d3f3d3033acd70fc3c48b8a
Deleted: sha256:97ca462ad9eeae25941546209454496e1d66749d53dfa2ee32bf1faabd239d38
# 用镜像摘要删除镜像
$ docker image ls --digests
REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE
node slim sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228 6e0c4c8e3913 3 weeks ago 214 MB

$ docker image rm node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228
Untagged: node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228

UntaggedDeleted

因为每个镜像都有一个TAG,而一个TAG可能对应多个镜像,我们的删除命令实际上是要求删除某个标签的镜像,所以首先需要做的是将满足我们要求的所有镜像标签都取消,这就是我们看到的 Untagged 的信息。因为一个镜像可以对应多个标签,因此当我们删除了所指定的标签后,可能还有别的标签指向了这个镜像,如果是这种情况,那么 Delete 行为就不会发生。所以并非所有的 docker image rm 都会产生删除镜像的行为,有可能仅仅是取消了某个标签而已。

当镜像的所有标签都被取消,该镜像很可能失去了存在的意义,因此触发删除行为。镜像是多层存储结构,因此在删除的时候也是从上层向基础层方向依次进行判断删除。镜像的多层结构让镜像复用变得非常容易,因此很有可能某个其它镜像正依赖于当前镜像的某一层。这种情况,依旧不会触发删除该层的行为。直到没有任何层依赖当前层时,才会真实的删除当前层。这就是为什么,有时候会奇怪,为什么明明没有别的标签指向这个镜像,但是它还是存在的原因,也是为什么有时候会发现所删除的层数和自己 docker pull 看到的层数不一样的原因。

还有就是如果有容器正在依赖该镜像,那么该镜像也不可删除。

3.2.4 搜索镜像

1
2
3
4
5
6
7
8
9
10
docker search [options]  # 在docker hub上搜索镜像

(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker search mysql
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
mysql MySQL is a widely used, open-source relation… 10680 [OK]
mariadb MariaDB Server is a high performing open sou… 4011 [OK]
......

# 可选项,通过搜索来过滤
--filter=STARS=3000 # 搜stars为3000以上的

3.2.5 Dockerfile构建镜像

Dockerfile 是一个文本文件,其内包含了一条条的 指令(Instruction)每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。

空白目录建立文本文件,并命名为Dockerfile

1
2
3
$ mkdir mynginx
$ cd mynginx
$ touch Dockerfile

内容为:

1
2
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

FROM指定基础镜像

构建镜像必然是以一个镜像为基础,在其上定制。FROM的作用就是指定基础镜像。所以在Dockerfile中FROM是必备指令,且是第一条指令。

除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。

1
2
FROM scratch
...

如果你 scratch 为基础镜像的话,意味着不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见,对于 Linux 下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接 FROM scratch 会让镜像体积更加小巧。

RUN执行命令

RUN是用来执行命令行命令的。格式有两种:

  • shell格式:RUN <命令>,就像我们刚才使用的那样。
  • exec格式:RUN ["可执行文件", "参数1", "参数2"]

比如

1
2
3
4
5
6
7
8
9
FROM debian:stretch

RUN apt-get update
RUN apt-get install -y gcc libc6-dev make wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install

Dockerfile 中每一个指令都会建立一层,RUN 也不例外。每一个 RUN 的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,commit 这一层的修改,构成新的镜像。

上面的这种写法,创建了 7 层镜像。这是完全没有意义的,而且很多运行时不需要的东西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。

正确写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FROM debian:stretch

RUN set -x; buildDeps='gcc libc6-dev make wget' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps

牢记:这不是在写shell脚本,而是在定义每一层该如何构建。

此外,别忘了最后添加清理工作的命令,删除为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了 apt 缓存文件。

构建镜像

我们继续构建nginx镜像

进入Dockerfile文件所在的mynginx目录,执行以下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 这里我们指定了镜像的名称 -t nginx:v3
# 最后的 . 表示当前目录
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ mynginx]# docker build -t nginx:v3 .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM nginx
---> f6d0b4767a6c
Step 2/2 : RUN echo '<h1>Hello, Dokcer!</h1>' > /usr/share/nginx/html/index.html
---> Running in a0b6b23016fd
Removing intermediate container a0b6b23016fd
---> bad85166b231
Successfully built bad85166b231
Successfully tagged nginx:v3

# 第一个就是我们构建的镜像
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ mynginx]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx v3 bad85166b231 2 minutes ago 133MB
tomcat 9.0 040bdb29ab37 2 months ago 649MB

镜像构建命令:

docker build [选项] <上下文路径/URL/->

镜像构建上下文(Context)

docker build命令最后的.表示的是当前目录,而不是Dockerfile文件所在路径。准确的说,.是在指定上下文路径

docker build工作原理如下:Docker在运行时分为Docker引擎(服务端守护进程)和客户端工具。Docker引擎提供了一组REST API,如docker命令这样的客户端工具,通过这组API与引擎交互。因此,表面看我们是在本机执行docker各种功能,实际上是远程调用Docker引擎来完成的。

那么构建镜像时,远程服务端怎么获得本地文件呢?答案就是通过上下文文件,构建时用户指定上下文路径,docker build会将该路径所有文件打包,上传给docker引擎。

3.3 容器命令

说明:有了镜像才能创建容器,下载一个centos镜像来测试学习

1
docker pull centos
命令 含义
docker run 新建容器
docker ps 列出容器
exit 退出容器
docker rm 删除容器
docker start 容器id 启动容器
docker restart 容器id 重启容器
docker stop 容器id 停止当前运行容器
docker kill 容器id 强制停止容器

3.3.1 新建并启动容器

当执行docker run来创建容器时,Docker在后台运行的标准操作包括:

  • 检查本地是否有该镜像,不存在就从registry下载;
  • 利用镜像创建并启动一个容器;
  • 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层;
  • 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去;
  • 从地址池配置一个 ip 地址给容器;
  • 执行用户指定的应用程序;
  • 执行完毕后容器被终止。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
docker run [options] imageName

# 参数说明
--name Name # 容器名字,用来区分容器
-d # 后台方式运行
-it # 交互方式运行,进入容器查看内容
-t # Dockers分配一个伪终端并绑定到容器的标准输入上
-i # 让容器的标准输入保持打开
-p # 指定容器端口 -p 8080:8080
-p ip:主机端口:容器端口
-p 主机端口:容器端口 (常用)
-p 容器端口
容器端口
-P # 随机指定端口

# 启动镜像centos并进入容器
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker run -it centos /bin/bash
[root@5dfc69f1db45 /]#

[root@5dfc69f1db45 /]# ls # 查看容器内的centos(基础版本,很多命令都不完善)
bin etc lib lost+found mnt proc run srv tmp var
dev home lib64 media opt root sbin sys usr

[root@5dfc69f1db45 /]# exit # 从容器中退出
exit

容器是否会长久运行,是和 docker run 指定的命令有关,和 -d 参数无关,只要命令不结束,容器也就不会退出。比如命令中如果while语句是死循环,不让bash退出,那么容器就不会退出。

3.3.2 启动已终止容器

利用docker container start 容器id,直接将一个已经终止(exited)的容器启动运行。

3.3.3 停止容器

1
2
docker stop 容器id           # 停止当前运行的容器
docker kill 容器id # 强制停止当前容器

3.3.4 重启容器

docker start 容器id会保留容器的第一次启动时的所有参数。

docker restart 容器id作用是依次执行docker stopdocker start

容器可能会因某些错误意外停止运行。对于服务类容器,通常希望它能够自动重启。启动容器时设置–restart就可以达到效果。–restart=always意味着无论容器因何种原因退出(包括正常退出),都立即重启;

3.3.5 列出运行容器

1
2
3
4
5
6
7
8
9
10
11
12
13
docker ps [options]  # 默认列出当前正在运行的容器
docker container ls [options] # 默认列出当前正在运行的容器
-a # 列出当前正在运行和曾经运行过的容器
-n=数量 # 显示最近创建的几个容器
-q # 只显示容器id

(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker ps # 查看当前正在运行的容器
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker ps -a # 查看曾经运行过的容器
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5dfc69f1db45 centos "/bin/bash" 4 minutes ago Exited (130) About a minute ago admiring_euclid
0f243e550191 d1165f221234 "/hello" 15 hours ago Exited (0) 15 hours ago crazy_napier

3.3.6 进入容器

attach命令

1
2
3
4
5
6
7
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker run -dit centos
b2a51a873e9389bc5cee88e052ddeeea555f887b1fb5186a610eaa1b7e284c22
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b2a51a873e93 centos "/bin/bash" 11 seconds ago Up 9 seconds relaxed_sutherland
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker attach b2a
[root@b2a51a873e93 /]#

如果从这个stdin中exit回到host端,容器会停止。

exec命令

docker exec后可以跟多个参数。只用 -i 参数时,由于没有分配伪终端,界面没有我们熟悉的 Linux 命令提示符,但命令执行结果仍然可以返回。当 -i -t 参数一起使用时,则可以看到我们熟悉的 Linux 命令提示符。

1
2
3
4
5
6
7
8
9
10
11

(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker run -dit centos
8b1861aba38b41b463c68c5ee021327e41513a2623dad3cb797481d67905e9c3
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8b1861aba38b centos "/bin/bash" 4 seconds ago Up 3 seconds infallible_neumann
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker exec -i 8b18 bash

(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker exec -it 8b18 bash
[root@8b1861aba38b /]#

从这个 stdin 中 exit回到host端,不会导致容器的停止。

attach和exec的区别:(1)attach直接进入容器启动命令的终端,不会启动新的进程; (2)exec则是在容器中打开新的终端,并且可以启动新的进程; (3)如果想直接在终端中查看命令的输出,用attach,其他情况使用exec;

3.3.7 退出容器

1
2
exit  # 容器停止并退出
Ctrl + P + Q # 容器不停止并退出

3.3.8 删除容器

1
2
3
4
5
docker rm 容器id                  # 删除指定容器(不能删除正在运行的容器,除非用rm -f)
docker rm -f $(docker ps -aq) # 删除所有容器
docker ps -a -q|xargs docker rm # 删除所有容器
docker container prune # 删除所有处于终止状态的容器
docker rm -v $(docker ps -aq -f status=exited) # 批量删除所有已经退出的容器

3.3.9 暂停容器

docker pause 容器id

3.3.10 导出容器

1
2
3
4
5
6
7

(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8b1861aba38b centos "/bin/bash" 6 minutes ago Up 6 minutes infallible_neumann
b2a51a873e93 centos "/bin/bash" 8 minutes ago Exited (130) 6 minutes ago relaxed_sutherland
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker export 8b18 > /mnt/zhoucz/centos.bar

3.3.11 导入容器快照

从容器快照文件中导入为镜像

1
2
3
4
5
6
7
8
9
10
11
12

(base) [root@iZ2ze2m65fn6to8rcr3rjaZ /]# cat /mnt/zhoucz/centos.bar | docker import - test/centos:v9.999
sha256:25a6a65ad0f4e9b29ec12d85dcec35a98a985152638c7086b0058170b61b8b3c
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ /]# docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
test/centos v9.999 25a6a65ad0f4 6 seconds ago 209MB
nginx v3 bad85166b231 2 days ago 133MB
tomcat 9.0 040bdb29ab37 3 months ago 649MB
tomcat latest 040bdb29ab37 3 months ago 649MB
python 3.6 6b0219e0ed75 3 months ago 874MB
nginx latest f6d0b4767a6c 3 months ago 133MB
centos latest 300e315adb2f 4 months ago 209MB

也可通过指定URL或某个目录来导入:

1
$ docker import http://example.com/exampleimage.tgz example/imagerepo

用户既可以使用 docker load 来导入镜像存储文件到本地镜像库,也可以使用 docker import 来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。

3.4 常用其他命令

后台启动容器

1
2
3
4
5
6
7
8
9
# 启动容器:docker run -d 镜像名
docker run -d centos

# 问题:使用docker ps, 发现centos停止了
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

# 常见的坑:docker使用后台运行,就必须要有一个前台进程。docker反向没有应用,就会自动停止
# nginx, 容器启动后,发现自己没有提供服务,就会立刻停止,就是没有程序了

查看日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
docker logs

# 编写一段shell脚本,不停地打印zcz
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker run -d centos /bin/sh -c "while true;do echo zcz;sleep 1;done"
e597be3ca6febe4246bf7c57d9fd9d2e633b8c643453aa1d9cd8e35d4b6d770a

(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e597be3ca6fe centos "/bin/sh -c 'while t…" 33 seconds ago Up 32 seconds vibrant_dijkstra

# 显示日志
-tf # 显示日志
--tail num # 要显示的日志条数
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker logs -f -t --tail 5 e597be3ca6fe # 显示指定行数的日志
2021-04-02T05:10:28.332572202Z zcz
2021-04-02T05:10:29.335579873Z zcz
2021-04-02T05:10:30.338541980Z zcz
2021-04-02T05:10:31.341479563Z zcz
2021-04-02T05:10:32.344343431Z zcz

查看容器中的进程信息

1
2
3
4
5
6
7
docker top 容器id

(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker top e597be3ca6fe
UID PID PPID C STIME TTY TIME CMD
root 30451 30431 0 13:08 ? 00:00:00 /bin/sh -c while true;do echo zcz;sleep 1;done
root 31111 30451 0 13:13 ? 00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 1

查看容器的元数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
docker inspect 容器id    # 查看容器所有信息,很多

# 测试
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker inspect e597be3ca6fe
[
{
"Id": "e597be3ca6febe4246bf7c57d9fd9d2e633b8c643453aa1d9cd8e35d4b6d770a",
"Created": "2021-04-02T05:08:13.56414271Z",
"Path": "/bin/sh",
"Args": [
"-c",
"while true;do echo zcz;sleep 1;done"
],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 30451,
"ExitCode": 0,
"Error": "",
"StartedAt": "2021-04-02T05:08:13.966405181Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
"Image": "sha256:300e315adb2f96afe5f0b2780b87f28ae95231fe3bdd1e16b9ba606307728f55",
"ResolvConfPath": "/var/lib/docker/containers/e597be3ca6febe4246bf7c57d9fd9d2e633b8c643453aa1d9cd8e35d4b6d770a/resolv.conf",
"HostnamePath": "/var/lib/docker/containers/e597be3ca6febe4246bf7c57d9fd9d2e633b8c643453aa1d9cd8e35d4b6d770a/hostname",
"HostsPath": "/var/lib/docker/containers/e597be3ca6febe4246bf7c57d9fd9d2e633b8c643453aa1d9cd8e35d4b6d770a/hosts",
"LogPath": "/var/lib/docker/containers/e597be3ca6febe4246bf7c57d9fd9d2e633b8c643453aa1d9cd8e35d4b6d770a/e597be3ca6febe4246bf7c57d9fd9d2e633b8c643453aa1d9cd8e35d4b6d770a-json.log",
"Name": "/vibrant_dijkstra",
"RestartCount": 0,
"Driver": "overlay2",
"Platform": "linux",
"MountLabel": "",
"ProcessLabel": "",
"AppArmorProfile": "",
"ExecIDs": null,
"HostConfig": {
"Binds": null,
"ContainerIDFile": "",
"LogConfig": {
"Type": "json-file",
"Config": {}
},
"NetworkMode": "default",
"PortBindings": {},
"RestartPolicy": {
"Name": "no",
"MaximumRetryCount": 0
},
"AutoRemove": false,
"VolumeDriver": "",
"VolumesFrom": null,
"CapAdd": null,
"CapDrop": null,
"CgroupnsMode": "host",
"Dns": [],
"DnsOptions": [],
"DnsSearch": [],
"ExtraHosts": null,
"GroupAdd": null,
"IpcMode": "private",
"Cgroup": "",
"Links": null,
"OomScoreAdj": 0,
"PidMode": "",
"Privileged": false,
"PublishAllPorts": false,
"ReadonlyRootfs": false,
"SecurityOpt": null,
"UTSMode": "",
"UsernsMode": "",
"ShmSize": 67108864,
"Runtime": "runc",
"ConsoleSize": [
0,
0
],
"Isolation": "",
"CpuShares": 0,
"Memory": 0,
"NanoCpus": 0,
"CgroupParent": "",
"BlkioWeight": 0,
"BlkioWeightDevice": [],
"BlkioDeviceReadBps": null,
"BlkioDeviceWriteBps": null,
"BlkioDeviceReadIOps": null,
"BlkioDeviceWriteIOps": null,
"CpuPeriod": 0,
"CpuQuota": 0,
"CpuRealtimePeriod": 0,
"CpuRealtimeRuntime": 0,
"CpusetCpus": "",
"CpusetMems": "",
"Devices": [],
"DeviceCgroupRules": null,
"DeviceRequests": null,
"KernelMemory": 0,
"KernelMemoryTCP": 0,
"MemoryReservation": 0,
"MemorySwap": 0,
"MemorySwappiness": null,
"OomKillDisable": false,
"PidsLimit": null,
"Ulimits": null,
"CpuCount": 0,
"CpuPercent": 0,
"IOMaximumIOps": 0,
"IOMaximumBandwidth": 0,
"MaskedPaths": [
"/proc/asound",
"/proc/acpi",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/proc/scsi",
"/sys/firmware"
],
"ReadonlyPaths": [
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger"
]
},
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/939dc725bed830236316fea321e04af162744f44072827372669df9ff2c2f1b2-init/diff:/var/lib/docker/overlay2/12942808314c9217ac8aedd99858712a95279513321ac740530096373f482e4b/diff",
"MergedDir": "/var/lib/docker/overlay2/939dc725bed830236316fea321e04af162744f44072827372669df9ff2c2f1b2/merged",
"UpperDir": "/var/lib/docker/overlay2/939dc725bed830236316fea321e04af162744f44072827372669df9ff2c2f1b2/diff",
"WorkDir": "/var/lib/docker/overlay2/939dc725bed830236316fea321e04af162744f44072827372669df9ff2c2f1b2/work"
},
"Name": "overlay2"
},
"Mounts": [],
"Config": {
"Hostname": "e597be3ca6fe",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh",
"-c",
"while true;do echo zcz;sleep 1;done"
],
"Image": "centos",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {
"org.label-schema.build-date": "20201204",
"org.label-schema.license": "GPLv2",
"org.label-schema.name": "CentOS Base Image",
"org.label-schema.schema-version": "1.0",
"org.label-schema.vendor": "CentOS"
}
},
"NetworkSettings": {
"Bridge": "",
"SandboxID": "e73698db27cfa3b73ff76cf34aadef82912668506c4bc79852c2c4c3e75937ac",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {},
"SandboxKey": "/var/run/docker/netns/e73698db27cf",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "727544846d92aa83705be3a7e0dfaf27381411eb9019e1d1b090053d96e51864",
"Gateway": "172.18.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.18.0.3",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"MacAddress": "02:42:ac:12:00:03",
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "16525c1ecff55e1fd07c6cd7dc6e3b9d1fc29d46ec296d16db3dd0d96f3a3b3f",
"EndpointID": "727544846d92aa83705be3a7e0dfaf27381411eb9019e1d1b090053d96e51864",
"Gateway": "172.18.0.1",
"IPAddress": "172.18.0.3",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:12:00:03",
"DriverOpts": null
}
}
}
}
]


进入当前正在运行的容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 我们通常容器都是使用后台方式运行,需要进入容器,修改一些配置

# 命令
docker exec -it 容器id bashShell
# 测试
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e597be3ca6fe centos "/bin/sh -c 'while t…" 10 minutes ago Up 10 minutes vibrant_dijkstra
fd178f773e6d centos "/bin/bash" 23 minutes ago Up 23 minutes mystifying_poincare
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker exec -it e597be3ca6fe /bin/bash
[root@e597be3ca6fe /]# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 05:08 ? 00:00:00 /bin/sh -c while true;do echo zcz;sleep 1;done
root 663 0 0 05:19 pts/0 00:00:00 /bin/bash
root 681 1 0 05:19 ? 00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 1
root 682 663 0 05:19 pts/0 00:00:00 ps -ef

# 方式二
docker attach 容器id
# 测试 正在执行当前的代码(这里写的是死循环,出不来了,只能断掉了服务器)
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker attach e597be3ca6fe
zcz
zcz
zcz
zcz
zcz

# docker exec # 进入容器后开启一个新的终端,可以在里面操作(常用)
# dockerattach # 进入容器正在执行的终端,不会启动新的进程

从容器内拷贝文件到主机上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
docker cp 容器id:容器内路径 目的主机路径

# 测试,在docker内的centos容器中新建一个文件,并拷贝到主机的home目录下
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ home]# docker attach 2760718a4b8a # 进入容器
[root@2760718a4b8a /]# cd /home
[root@2760718a4b8a home]# touch test.docker # 新建文件
[root@2760718a4b8a home]# exit # 退出容器
exit
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ home]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2760718a4b8a centos "/bin/bash" About a minute ago Exited (0) 10 seconds ago focused_almeida
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ home]# docker cp 2760718a4b8a:/home/test.docker /home # 拷贝
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ home]# ls
test.docker www zcz.docker zhoucz zzz


# 拷贝是手动的,未来我们使用-v卷技术来自动实现

3.5 小结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ home]# docker --help

Usage: docker [OPTIONS] COMMAND

A self-sufficient runtime for containers

Options:
--config string Location of client config files (default "/root/.docker")
-c, --context string Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var and default context set with "docker
context use")
-D, --debug Enable debug mode
-H, --host list Daemon socket(s) to connect to
-l, --log-level string Set the logging level ("debug"|"info"|"warn"|"error"|"fatal") (default "info")
--tls Use TLS; implied by --tlsverify
--tlscacert string Trust certs signed only by this CA (default "/root/.docker/ca.pem")
--tlscert string Path to TLS certificate file (default "/root/.docker/cert.pem")
--tlskey string Path to TLS key file (default "/root/.docker/key.pem")
--tlsverify Use TLS and verify the remote
-v, --version Print version information and quit

Management Commands:
app* Docker App (Docker Inc., v0.9.1-beta3)
builder Manage builds
buildx* Build with BuildKit (Docker Inc., v0.5.1-docker)
config Manage Docker configs
container Manage containers
context Manage contexts
image Manage images
manifest Manage Docker image manifests and manifest lists
network Manage networks
node Manage Swarm nodes
plugin Manage plugins
secret Manage Docker secrets
service Manage services
stack Manage Docker stacks
swarm Manage Swarm
system Manage Docker
trust Manage trust on Docker images
volume Manage volumes

Commands:
attach Attach local standard input, output, and error streams to a running container
build Build an image from a Dockerfile
commit Create a new image from a container's changes
cp Copy files/folders between a container and the local filesystem
create Create a new container
diff Inspect changes to files or directories on a container's filesystem
events Get real time events from the server
exec Run a command in a running container
export Export a container's filesystem as a tar archive
history Show the history of an image
images List images
import Import the contents from a tarball to create a filesystem image
info Display system-wide information
inspect Return low-level information on Docker objects
kill Kill one or more running containers
load Load an image from a tar archive or STDIN
login Log in to a Docker registry
logout Log out from a Docker registry
logs Fetch the logs of a container
pause Pause all processes within one or more containers
port List port mappings or a specific mapping for the container
ps List containers
pull Pull an image or a repository from a registry
push Push an image or a repository to a registry
rename Rename a container
restart Restart one or more containers
rm Remove one or more containers
rmi Remove one or more images
run Run a command in a new container
save Save one or more images to a tar archive (streamed to STDOUT by default)
search Search the Docker Hub for images
start Start one or more stopped containers
stats Display a live stream of container(s) resource usage statistics
stop Stop one or more running containers
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
top Display the running processes of a container
unpause Unpause all processes within one or more containers
update Update configuration of one or more containers
version Show the Docker version information
wait Block until one or more containers stop, then print their exit codes

Run 'docker COMMAND --help' for more information on a command.

To get more help with docker, check out our guides at https://docs.docker.com/go/guides/

4. Docker数据管理

4.1 数据卷

数据卷是一个可供一个或多个容器使用的特殊目录,它绕过 UFS (UNIX File System) ,可以提供很多有用的特性:

  • 数据卷可以在容器之间共享和重用
  • 对数据卷的修改会立马生效
  • 对数据卷的更新,不会影响镜像
  • 数据卷默认会一直存在,即使容器被删除

docker的镜像是由多个只读的文件系统叠加在一起形成的。当我们在我启动一个容器的时候,docker会加载这些只读层并在这些只读层的上面(栈顶)增加一个读写层。这时如果修改正在运行的容器中已有的文件,那么这个文件将会从只读层复制到读写层。该文件的只读版本还在,只是被上面读写层的该文件的副本隐藏。当删除docker,或者重新启动时,之前的更改将会消失。在Docker中,只读层及在顶部的读写层的组合被称为Union File System(联合文件系统)。

为了很好的实现数据保存和数据共享,Docker提出了Volume这个概念,简单的说就是绕过默认的联合文件系统,而以正常的文件或者目录的形式存在于宿主机上。又被称作数据卷。

4.1.1 创建数据卷

docker volume create xxx

4.1.2 查看数据卷

查看所有数据卷

docker volume ls

查看指定数据卷

1
2
3
4
5
6
7
8
9
10
11
12
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ share]# docker volume inspect datawhale
[
{
"CreatedAt": "2021-04-16T14:12:15+08:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/datawhale/_data",
"Name": "datawhale",
"Options": {},
"Scope": "local"
}
]

查看数据卷的具体信息

1
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ share]# docker inspect web

数据卷信息在mounts中:

1
2
3
4
5
6
7
8
9
10
11
12
"Mounts": [
{
"Type": "volume",
"Name": "datawhale",
"Source": "/var/lib/docker/volumes/datawhale/_data",
"Destination": "/usr/share/nginx/html",
"Driver": "local",
"Mode": "z",
"RW": true,
"Propagation": ""
}
],

4.1.3 启动一个挂载数据卷的容器

--mount标记可以将数据卷挂载到容器中。一次docker run中可以挂载多个数据卷。

下面创建一个名为 web 的容器,并加载一个数据卷到容器的 /usr/share/nginx/html 目录。

1
2
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ share]# docker run -d -P --name test --mount source=datawhale,target=/usr/share/nginx/html nginx
66a8f9b7aa1e47eb5cd08827e607ba319d535cfeeba59086beb924d6ba2ddd6b

–mount参数说明:

​ source:数据卷

​ target:容器内部文件系统挂载点

注:可以不提前创建好数据卷,直接在运行容器的时候mount 这时如果不存在指定的数据卷,docker会自动创建,自动生成。

4.1.4 删除数据卷

docker volume rm datawhale #datawhale为卷名

数据卷是被设计用来持久化数据的,它的生命周期独立于容器,Docker 不会在容器被删除后自动删除 数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的 数据卷。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用 docker rm -v 这个命令。

清除无用的数据卷

docker volume prune

4.2 挂载主机目录

4.2.1 挂载主机目录作为数据卷

1
2
3
4
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ www]# docker run -d --name web2 --mount type=bind,source=/www,target=/usr/share/nginx/html nginx
d7ee4773a9c6d492b12c7e4a7e9fc87db19ac9582b6b5cfb111ac81e0e0ae6af

# 加载主机的www文件夹到容器的/usr/share/nginx/html目录。本地主机目录必须是绝对路径且必须存在,否则会报错

这个功能在进行测试的时候十分方便,比如用户可以放置一些程序到本地目录中,来查看容器是否正常工作。Docker 挂载主机目录的默认权限是 读写,用户也可以通过增加 readonly 指定为 只读

4.2.2 挂载主机单个文件作为数据卷

1
2
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ /]# docker run -d --name web3 --mount type=bind,source=/.test,target=/root/.test centos
37a0e2129636ff67b446048cdde2964b507c4fb6393630eba380ad6fd64b2dda

5. Docker网络

5.1 基础网络介绍

5.1.1 外部访问容器

Docker容器内部端口映射到外部宿主机端口 - 运维笔记

一篇很不错的docker端口映射的笔记

3.3节docker run命令中提到过,-p, -P可以指定端口映射。-P随机指定,-p特指某端口。

映射所有接口地址

使用hostPort:containerPort格式本地的 80 端口映射到容器的 80 端口

docker run -d -p 80:80 nginx

此时默认会绑定本地所有接口上的所有地址。

映射到指定地址的指定端口

使用ip:hostPort:containerPort格式

比如docker run -d -p 127.0.0.1:80:80 nginx

映射到指定地址的任意端口

使用ip::containerPort绑定localhost的任意端口到容器的80端口,本地主机会自动分配一个端口。

docker run -d -p 127.0.0.0::80 nginx

可以使用udp来指定udp端口

docker run -d -p 127.0.0.1:80:80/udp nginx

查看映射端口配置

docker port 容器id 端口查看已绑定的地址

5.1.2 容器互联

新建网络

1
2
3
4
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker network create -d bridge mynet
ce82c22db3246994b9f902d418ebb81b81bfe80f4299243f4b9b520a28835d2d

-d 指定docker网络类型

连接容器

运行一个容器并连接到新建的mynet网络

$ docker run -it --rm --name busybox1 --network my-net busybox sh

再开一个终端,再运行一个容器并加入到mynet网络

$ docker run -it --rm --name busybox2 --network my-net busybox sh

再开一个终端查看容器信息

1
2
3
4
5
$ docker container ls

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b47060aca56b busybox "sh" 11 minutes ago Up 11 minutes busybox2
8720575823ec busybox "sh" 16 minutes ago Up 16 minutes busybox1

在第一个容器中ping第二个容器,来证明互联

1
2
3
4
/ # ping busybox2
PING busybox2 (172.19.0.3): 56 data bytes
64 bytes from 172.19.0.3: seq=0 ttl=64 time=0.072 ms
64 bytes from 172.19.0.3: seq=1 ttl=64 time=0.118 ms

ping来测试连接busybox2容器,它会解析成 172.19.0.3。

5.1.3 配置DNS

Docker利用虚拟文件来挂载容器的 3个相关配置文件,来自定义配置容器的主机名和DNS。

1
2
3
4
5
6
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker run -it centos /bin/bash
[root@c896492cf579 /]# mount
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/SSEB54NI6MD56XYER3WGEIVCO7:/var/lib/docker/overlay2/l/AVOXTTPYDQARCCBYY6GIVC7NMZ,upperdir=/var/lib/docker/overlay2/be32cb0ee2b3f1c91a9d2906812f704c261ae8bc1e82d25784f72a012b30fdab/diff,workdir=/var/lib/docker/overlay2/be32cb0ee2b3f1c91a9d2906812f704c261ae8bc1e82d25784f72a012b30fdab/work)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev type tmpfs (rw,nosuid,size=65536k,mode=755)
.....

这种机制可以让宿主主机 DNS 信息发生更新后,所有Docker容器的 DNS 配置通过 /etc/resolv.conf文件立刻得到更新。

配置全部容器的 DNS ,也可以在主机的 /etc/docker/daemon.json 文件中增加以下内容来设置。

1
2
3
4
5
6
{
"dns" : [
"114.114.114.114",
"8.8.8.8"
]
}

这样每次启动的容器 DNS 自动配置为 114.114.114.114 和8.8.8.8。使用以下命令来证明其已经生效。

设置完一定要重启才能生效

重启docker服务 systemctl restart docker

5.2 Docker的网络模式

1
2
3
4
5
6
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker network ls  # 查看网络
NETWORK ID NAME DRIVER SCOPE
e78c44b9ed15 bridge bridge local
12fdc8dfd506 host host local
ce82c22db324 mynet bridge local
e5b08a153008 none null local
网络模式 简介
Bridge 为每一个容器分配、设置 IP 等,并将容器连接到一个 docker0 虚拟网桥,默认为该模式。
Host 容器将不会虚拟出自己的网卡,配置自己的 IP 等,而是使用宿主机的 IP 和端口。
None 容器有独立的 Network namespace,但并没有对其进行任何网络设置,如分配 veth pair 和网桥连接,IP 等。
Container 新创建的容器不会创建自己的网卡和配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。

Bridge模式

Docker进程启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的Docker容器会连接到这个虚拟网桥上,附加在其上的任何网卡之间都能自动转发数据包。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。从docker0子网中分配一个 IP 给容器使用,并设置 docker0 的 IP 地址为容器的默认网关。在主机上创建一对虚拟网卡veth pair设备,Docker veth pair 设备的一端放在新创建的容器中,并命名为eth0(容器的网卡),另一端放在主机中,以vethxxx这样类似的名字命名,并将这个网络设备加入到 docker0 网桥中。可以通过brctl show命令查看。

运行一个基于busybox镜像构建的容器bbox01,查看ip addr

busybox 被称为嵌入式 Linux 的瑞士军刀,整合了很多小的 unix 下的通用功能到一个小的可执行文件中。

1
2
3
4
5
6
7
8
9
10
11
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker run -it --name bbox01 busybox
/ # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
107: eth0@if108: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.18.0.2/16 brd 172.18.255.255 scope global eth0
valid_lft forever preferred_lft forever
# 可以看到eth0

宿主机执行ip addr命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:16:3e:00:37:72 brd ff:ff:ff:ff:ff:ff
inet 172.17.152.19/20 brd 172.17.159.255 scope global dynamic eth0
valid_lft 293187274sec preferred_lft 293187274sec
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:74:be:3a:4e brd ff:ff:ff:ff:ff:ff
inet 172.18.0.1/16 brd 172.18.255.255 scope global docker0
valid_lft forever preferred_lft forever
84: br-ce82c22db324: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:d6:d6:e5:d5 brd ff:ff:ff:ff:ff:ff
inet 172.19.0.1/16 brd 172.19.255.255 scope global br-ce82c22db324
valid_lft forever preferred_lft forever
108: vethc7d10d1@if107: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether ce:d4:31:fa:13:bd brd ff:ff:ff:ff:ff:ff link-netnsid 0
#可以看到vethc7d...

所以说守护进程会创建一对对等的虚拟设备接口veth pair,将容器的接口设置为eth0接口(容器的网卡),另一个接口放置在宿主机的命名空间,以类似 vethxxx 这样的名字命名。

同时,守护进程还会从网桥 docker0 的私有地址空间中分配一个 IP 地址和子网给该容器,并设置 docker0 的 IP 地址为容器的默认网关。也可以安装 yum install -y bridge-utils 以后,通过 brctl show 命令查看网桥信息。

1
2
3
4
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# brctl show
bridge name bridge id STP enabled interfaces
br-ce82c22db324 8000.0242d6d6e5d5 no
docker0 8000.024274be3a4e no vethc0e3def

对于每个容器的 IP 地址和 Gateway 信息,可以通过 docker inspect 容器名称|ID 进行查看,在 NetworkSettings 节点中可以看到详细信息。

可以通过 docker network inspect bridge 查看所有 bridge 网络模式下的容器,在 Containers 节点中可以看到容器名称。

关于 bridge 网络模式的使用,只需要在创建容器时通过参数 --net bridge 或者 --network bridge 指定即可,当然这也是创建容器默认使用的网络模式,也就是说这个参数是可以省略的。

image-20210419093917459

Host模式

  • host 网络模式需要在创建容器时通过参数 --net host 或者 --network host 指定;
  • 采用 host 网络模式的 Docker Container,可以直接使用宿主机的 IP 地址与外界进行通信,若宿主机的 eth0 是一个公有 IP,那么容器也拥有这个公有 IP。同时容器内服务的端口也可以使用宿主机的端口,无需额外进行 NAT 转换;
  • host 网络模式可以让容器共享宿主机网络栈,这样的好处是外部主机与容器直接通信,但是容器的网络缺少隔离性。

image-20210419101703706

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker run -it --name bbox02 --net host busybox
/ # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast qlen 1000
link/ether 00:16:3e:00:37:72 brd ff:ff:ff:ff:ff:ff
inet 172.17.152.19/20 brd 172.17.159.255 scope global dynamic eth0
valid_lft 293183500sec preferred_lft 293183500sec
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue
link/ether 02:42:74:be:3a:4e brd ff:ff:ff:ff:ff:ff
inet 172.18.0.1/16 brd 172.18.255.255 scope global docker0
valid_lft forever preferred_lft forever
84: br-ce82c22db324: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue
link/ether 02:42:d6:d6:e5:d5 brd ff:ff:ff:ff:ff:ff
inet 172.19.0.1/16 brd 172.19.255.255 scope global br-ce82c22db324
valid_lft forever preferred_lft forever
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:16:3e:00:37:72 brd ff:ff:ff:ff:ff:ff
inet 172.17.152.19/20 brd 172.17.159.255 scope global dynamic eth0
valid_lft 293183473sec preferred_lft 293183473sec
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:74:be:3a:4e brd ff:ff:ff:ff:ff:ff
inet 172.18.0.1/16 brd 172.18.255.255 scope global docker0
valid_lft forever preferred_lft forever
84: br-ce82c22db324: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:d6:d6:e5:d5 brd ff:ff:ff:ff:ff:ff
inet 172.19.0.1/16 brd 172.19.255.255 scope global br-ce82c22db324
valid_lft forever preferred_lft forever

可以看到容器与宿主机的br-ce82c22db324一模一样。

如果启动容器的时候使用host模式,那么这个容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。但是,容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的。

None模式

  • none 网络模式是指禁用网络功能,只有 lo 接口 local 的简写,代表 127.0.0.1,即 localhost 本地环回接口。在创建容器时通过参数 --net none 或者 --network none 指定;
  • none 网络模式即不为 Docker Container 创建任何的网络环境,容器内部就只能使用 loopback 网络设备,不会再有其他的网络资源。可以说 none 模式为 Docke Container 做了极少的网络设定,但是俗话说得好“少即是多”,在没有网络配置的情况下,作为 Docker 开发者,才能在这基础做其他无限多可能的网络定制开发。这也恰巧体现了 Docker 设计理念的开放。
1
2
3
4
5
6
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# docker run -it --name bbox03 --net none busybox
/ # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever

可以通过 docker network inspect none 查看所有 none 网络模式下的容器。

使用none模式,Docker 容器拥有自己的 Network Namespace,但是,并不为Docker 容器进行任何网络配置。也就是说,这个 Docker 容器没有网卡、IP、路由等信息。需要自己为 Docker 容器添加网卡、配置 IP 等

Container模式

  • Container 网络模式是 Docker 中一种较为特别的网络的模式。在创建容器时通过参数 --net container:已运行的容器名称|ID 或者 --network container:已运行的容器名称|ID 指定;
  • 处于这个模式下的 Docker 容器会共享一个网络栈,这样两个容器之间可以使用 localhost 高效快速通信。

image-20210419102659865

Container 网络模式即新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。同样两个容器除了网络方面相同之外,其他的如文件系统、进程列表等还是隔离的。

这个模式指定新创建的容器和已经存在的一个容器共享一个Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过lo网卡设备通信。

5.3 高级网络配置

5.3.1 相关命令

Docker网络相关命令。

其中有些命令选项只有在 Docker 服务启动的时候才能配置,而且不能马上生效。

  • -b BRIDGE--bridge=BRIDGE 指定容器挂载的网桥
  • --bip=CIDR定制 docker0 的掩码
  • -H SOCKET...--host=SOCKET... Docker 服务端接收命令的通道
  • --icc=true|false 是否支持容器之间进行通信
  • --ip-forward=true|false 请看下文容器之间的通信
  • --iptables=true|false 是否允许 Docker 添加 iptables 规则
  • --mtu=BYTES 容器网络中的 MTU

下面2个命令选项既可以在启动服务时指定,也可以在启动容器时指定。在 Docker服务启动的时候指定则会成为默认值,后面执行 docker run 时可以覆盖设置的默认值。

  • --dns=IP_ADDRESS... 使用指定的DNS服务器
  • --dns-search=DOMAIN... 指定DNS搜索域

最后这些选项只有在 docker run 执行时使用,因为它是针对容器的特性内容。

  • -h HOSTNAME--hostname=HOSTNAME 配置容器主机名
  • --link=CONTAINER_NAME:ALIAS 添加到另一个容器的连接
  • --net=bridge|none|container:NAME_or_ID|host 配置容器的桥接模式
  • -p SPEC 或 --publish=SPEC` 映射容器端口到宿主主机
  • -P or --publish-all=true|false 映射容器所有端口到宿主主机

5.3.2 容器访问控制

容器的访问控制,主要通过 Linux 上的 iptables 防火墙来进行管理和实现。

容器访问外部网络

需要本地系统的转发支持。

1
2
3
# 检查转发是否打开
(base) [root@iZ2ze2m65fn6to8rcr3rjaZ ~]# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1 # 已打开

手动打开

sysctl -w net.ipv4.ip_forward=1

如果在启动 Docker 服务的时候设定 --ip-forward=true, Docker 就会自动设定系统的 ip_forward 参数为 1。

容器之间访问

需要两方面的支持

  • 容器的网络拓扑是否已经互联。默认情况下,所有容器都会被连接到 docker0 网桥上。
  • 本地系统的防火墙软件 -- iptables 是否允许通过。

访问所有端口

当启动 Docker 服务(即 dockerd)的时候,默认会添加一条转发策略到本地主机 iptables 的 FORWARD 链上。策略为通过(ACCEPT)还是禁止(DROP)取决于配置--icc=true(缺省值)还是 --icc=false。当然,如果手动指定 --iptables=false 则不会添加 iptables 规则。

可见,默认情况下,不同容器之间是允许网络互通的。如果为了安全考虑,可以在 /etc/docker/daemon.json 文件中配置 {"icc": false} 来禁止它。

访问指定端口

在通过 -icc=false 关闭网络访问后,还可以通过 --link=CONTAINER_NAME:ALIAS 选项来访问容器的开放端口。

例如,在启动 Docker 服务时,可以同时使用 icc=false --iptables=true 参数来关闭允许相互的网络访问,并让 Docker 可以修改系统中的 iptables 规则。

此时,系统中的 iptables 规则可能是类似

1
2
3
4
5
6
$ sudo iptables -nL
...
Chain FORWARD (policy ACCEPT)
target prot opt source destination
DROP all -- 0.0.0.0/0 0.0.0.0/0
...

5.3.3 端口映射实现

默认情况下,容器可以主动访问到外部网络的连接,但是外部网络无法访问到容器。

容器访问外部实现

容器所有到外部网络的连接,源地址都会被 NAT (网络地址转换协议)成本地系统的 IP 地址。这是使用 iptables 的源地址伪装操作实现的。

查看主机的 NAT 规则。

1
2
3
4
5
6
$ sudo iptables -t nat -nL
...
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 !172.17.0.0/16
...

其中,上述规则将所有源地址在 172.17.0.0/16 网段,目标地址为其他网段(外部网络)的流量动态伪装为从系统网卡发出。MASQUERADE 跟传统 SNAT 的好处是它能动态从网卡获取地址。

外部访问容器实现

容器允许外部访问,可以在 docker run 时候通过 -p-P 参数来启用。

不管用那种办法,其实也是在本地的 iptable 的 nat 表中添加相应的规则。

5.3.4 配置docker0网桥

Docker 服务默认会创建一个 docker0 网桥(其上有一个 docker0 内部接口),它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。

Docker 默认指定了 docker0 接口 的 IP 地址和子网掩码,让主机和容器之间可以通过网桥相互通信,它还给出了 MTU(接口允许接收的最大传输单元),通常是 1500 Bytes,或宿主主机网络路由上支持的默认值。这些值都可以在服务启动的时候进行配置。

  • --bip=CIDR IP 地址加掩码格式,例如 192.168.1.5/24
  • --mtu=BYTES 覆盖默认的 Docker mtu 配置

也可以在配置文件中配置 DOCKER_OPTS,然后重启服务。

由于目前 Docker 网桥是 Linux 网桥,用户可以使用 brctl show 来查看网桥和端口连接信息。

1
2
3
4
$ sudo brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.3a1d7362b4ee no veth65f9
vethdda6

brctl 命令在 Debian、Ubuntu 中可以使用 sudo apt-get install bridge-utils 来安装。

每次创建一个新容器的时候,Docker 从可用的地址段中选择一个空闲的 IP 地址分配给容器的 eth0 端口。使用本地主机上 docker0 接口的 IP 作为所有容器的默认网关。

5.3.5 自定义网桥

在启动 Docker 服务的时候,使用 -b BRIDGE--bridge=BRIDGE 来指定使用的网桥。

如果服务已经运行,那需要先停止服务,并删除旧的网桥。

1
2
3
$ sudo systemctl stop docker
$ sudo ip link set dev docker0 down
$ sudo brctl delbr docker0

创建一个网桥 bridge0

1
2
3
$ sudo brctl addbr bridge0
$ sudo ip addr add 192.168.5.1/24 dev bridge0
$ sudo ip link set dev bridge0 up

查看确认网桥创建并启动。

1
2
3
4
5
$ ip addr show bridge0
4: bridge0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state UP group default
link/ether 66:38:d0:0d:76:18 brd ff:ff:ff:ff:ff:ff
inet 192.168.5.1/24 scope global bridge0
valid_lft forever preferred_lft forever

在 Docker 配置文件 /etc/docker/daemon.json 中添加如下内容,即可将 Docker 默认桥接到创建的网桥上。

1
2
3
{
"bridge": "bridge0",
}

启动 Docker 服务。

新建一个容器,可以看到它已经桥接到了 bridge0 上。

可以继续用 brctl show 命令查看桥接的信息。另外,在容器中可以使用 ip addrip route 命令来查看 IP 地址配置和路由信息。

6. Docker Compose

6.1 Docker Compose简介

我们使用 Docker 的时候,定义 Dockerfile 文件,然后使用 docker build、docker run 等命令操作容器。然而微服务架构的应用系统一般包含若干个微服务,每个微服务一般都会部署多个实例,如果每个微服务都要手动启停,那么效率之低,维护量之大可想而知

使用 Docker Compose 可以轻松、高效的管理容器,它是一个用于定义和运行多容器 Docker 的应用程序工具它允许用户通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。

6.2 如何使用Docker Compose

Compose 中有两个重要的概念:

  • 服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。
  • 项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。

按照教程运行compose项目之后,Linux命令行不能直接访问网站,可以使用wget间接访问。

  • wget -help查看是否有wget
  • Centos安装wgetyum -y install wget
  • Ubuntu安装wget apt-get update; apt-get install wget
  • 执行wget http://localhost:5000,会将网页下载到本地index.html文件中,可用vim打开查看网页。
  • 执行wget http://localhost:5000一次,就相当于刷新了一次页面。

6.3 Docker Compose基本使用

6.3.1 启动服务

在docker compose中,我们习惯把每一个容器叫做service。

在创建好docker-compose.yml文件后,可以通过命令docker-compose up将文件中定义的容器都启动起来。

命令自动连接默认文件docker-compose.yml,也可给文件起其他名字,这样在使用时需要指定文件名。

docker-compose up -f docer-test.yml

但是直接通过这种方式的话会直接将启动时的输出打印到终端,所以我们常会加上-d参数。

1
docker-compose up -d

6.3.2 查看服务状态

查看一下创建的service状态:docker-compose ps

查看所有service的状态:docker-compose ps -a

6.3.3 停止或删除服务

docker-compose stop;

docker-compose down;

stop时直接停止services,down时停止并删除创建的service,volume和network

6.3.4 进入服务

docker-compose exec mysql bash

exec后面接的就是我们要进入具体的service的名字,名字后面就是我们要执行的命令。

6.3.5 查看服务输出日志

docker-compose logs

6.4 Compose模板文件

Compose模板文件.

7. 综合实践

7.1 挂载部署

这种方式类似于常规部署,通过数据卷的方式将宿主机的jar包挂载到容器中,然后执行jar包的jdk选择容器中的而非采用本地的。

  • 将jar包上传到服务器的指定目录,比如/root/docker/jar
  • 通过docker pull openjdk:8获取镜像
  • 编写docker-compose.yml文件
  • 执行命令docker-compose up -d启动jar包,可以通过docker ps查看容器是否在运行,需要注意的是默认查看所有运行中的容器。
  • 如果容器启动失败或者状态异常,可以通过docker logs查看日志
  • 通过docker inspect myopenjdk查看容器详细信息,可以看到容器ip已经设置成功
  • 在虚拟机中打开浏览器输入jar包项目的访问地址,就可以看到运行的项目,需要注意访问端口是映射过的端口而非项目实际端口

7.2 构建镜像部署

  • 将jar包上传到服务器的指定目录,比如/root/docker/jar。
  • 在该目录下创建Dockerfile文件,通过vim等编辑工具在Dockerfile中编辑内容
  • Dockerfile构建完成以后可以通过命令docker build构建镜像,然后再运行容器,这里咱们用docker-compose命令直接编排构建镜像和运行容器。
  • 编写docker-compose.yml文件
  • 执行docker-compose up –d直接启动基于文件构建的自定义镜像,如果镜像不存在会自动构建,如果已存在那么直接启动。如果想重新构建镜像,则执行docker-compose build。如果想在执行compose文件的时候重构,则执行docker-compose up –d –build。
  • 在浏览器中输入访问路径可以看到项目已经正常运行