Docker 的安装

查看系统信息

目前,CentOS 仅发行版本中的内核支持 Docker。Docker 运行在 CentOS 7 上,要求系统为64位、系统内核版本为 3.10 以上。 查看自己的内核:

uname -r 命令用于打印当前系统相关信息(内核版本号、硬件架构、主机名称和操作系统类型 等)。

1
2
[root@ls-rK8rbuXz ~]# uname -r
3.10.0-1160.62.1.el7.x86_64

查看版本信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@ls-rK8rbuXz ~]# cat /etc/os-release
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:7"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"

CENTOS_MANTISBT_PROJECT="CentOS-7"
CENTOS_MANTISBT_PROJECT_VERSION="7"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="7"

安装步骤

官网安装参考手册: https://docs.docker.com/engine/install/centos/

Centos 安装

  1. 确定你是CentOS7及以上版本,我们已经做过了
  2. yum安装gcc相关环境(需要确保 虚拟机可以上外网 )
  3. 卸载旧版本
1
2
3
4
5
6
7
8
yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
  1. 安装需要的软件包
1
root@ls-rK8rbuXz:~#yum install-y yum-utils
  1. 设置镜像仓库
1
2
3
4
5
6
# 错误
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo ## 报错
[Errno 14] curl#35 - TCP connection reset by peer [Errno 12] curl#35 - Timeout

# 正确推荐使用国内的
yum-config-manager --add-repo http://mirrors.aliyun.com/docker- ce/linux/centos/docker-ce.repo
  1. 更新yum软件包索引
1
root@ls-rK8rbuXz:~# yum makecache fast
  1. 安装 Docker CE(社区版)
1
root@ls-rK8rbuXz:~# yum install docker-ce docker-ce-cli containerd.io
  1. 启动 Docker
1
root@ls-rK8rbuXz:~# systemctl start docker
  1. 测试命令
1
2
3
root@ls-rK8rbuXz:~# docker version
root@ls-rK8rbuXz:~# docker run hello-world
root@ls-rK8rbuXz:~# docker images

image-20230703222148435

  1. 卸载
1
2
3
root@ls-rK8rbuXz:~# systemctl stop docker
root@ls-rK8rbuXz:~# yum -y remove docker-ce docker-ce-cli containerd.io
root@ls-rK8rbuXz:~# rm -rf /var/lib/docker

Ubuntu 安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 添加Docker PGP密钥
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -

# 配置docker apt源 我这里用的国内阿里云的docker下载源
echo 'deb https://mirrors.aliyun.com/docker-ce/linux/debian buster stable'> /etc/apt/sources.list.d/docker.list

# 更新apt源
apt update

# 如果之前安装了docker的话 这里得卸载旧版本docker
apt remove docker docker-engine docker.io

# 安装docker
apt install docker-ce

# 查看版本
docker version

阿里云镜像加速

  1. 介绍: https://www.aliyun.com/product/acr
  2. 注册一个属于自己的阿里云账户(可复用淘宝账号)
  3. 进入管理控制台设置密码,开通
  4. 查看镜像加速器自己的

image-20230703222446543

1
2
3
4
5
6
7
8
9
10
sudo mkdir -p /etc/docker

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

sudo systemctl daemon-reload
sudo systemctl restart docker

测试获取hello-word镜像

  1. 运行 hello-world 镜像
1
docker run hello-world
  1. run 做了什么?
image-20230703223434674

底层原理

Docker是怎么工作的

Docker是一个Client-Server结构的系统,Docker守护进程运行在主机上, 然后通过Socket连接从客户 端访问,守护进程从客户端接受命令并管理运行在主机上的容器。 容器,是一个运行时环境,就是我们 前面说到的集装箱。

image-20230703223550078

为什么 Docker 比 VM 快

1、docker有着比虚拟机更少的抽象层。由亍docker不需要Hypervisor实现硬件资源虚拟化,运行在 docker容器上的程序直接使用的都是实际物理机的硬件资源。因此在CPU、内存利用率上docker将会在 效率上有明显优势。

2、docker利用的是宿主机的内核,而不需要Guest OS。因此,当新建一个容器时,docker不需要和虚拟机 一样重新加载一个操作系统内核。仍而避免引寻、加载操作系统内核返个比较费时费资源的过程,当新建 一个虚拟机时,虚拟机软件需要加载Guest OS,返个新建过程是分钟级别的。而docker由于直接利用宿主 机的操作系统,则省略了返个过程,因此新建一个docker容器只需要几秒钟。

image-20230703223653957

Docker 常用命令

image-20231013161508819

帮助命令

0111

1
2
3
4
5
docker version   # 查看 Docker 的版本
docker version # 简约版的 版本 信息
docker info # 查看 Docker 系统信息,包括镜像和容器数
docker --help # 帮助
docker 命令 --help # 查看某个命令的帮助

镜像命令

docker images

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 列出本地主机上的镜像
[root@ls-rK8rbuXz ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest feb5d9fea6a5 21 months ago 13.3kB

# 解释
REPOSITORY # 镜像的仓库源
TAG # 镜像的标签 一般是版本信息
IMAGE ID # 镜像的ID
CREATED # 镜像创建时间
SIZE # 镜像的大小

# 同一个仓库源可以有多个 TAG,代表这个仓库源的不同版本,我们使用REPOSITORY:TAG 定义不同版本的镜像,如果你不定义镜像的标签版本,docker将默认使用 lastest(最新的) 镜像!

# docker images --help
# 可选项
-a: 列出本地所有镜像
-q: 只显示镜像id
--digests: 显示镜像的摘要信息

docker search

1
2
3
4
5
6
7
8
9
10
11
# 搜索镜像
[root@ls-rK8rbuXz ~]# docker search mysql
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
mysql MySQL is a widely used, open-source relation… 14274 [OK]
mariadb MariaDB Server is a high performing open sou… 5455 [OK]
percona Percona Server is a fork of the MySQL relati… 617 [OK]
phpmyadmin phpMyAdmin - A web interface for MySQL and M… 831 [OK]

# docker search 某个镜像的名称 对应DockerHub仓库中的镜像
# 可选项
--filter=stars=50 # 列出收藏数不小于指定值的镜像。

docker pull

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 下载镜像
[root@ls-rK8rbuXz ~]# docker pull mysql
Using default tag: latest # 不写tag默认是latest(最新的)
latest: Pulling from library/mysql
72a69066d2fe: Pull complete # 分层下载
93619dbc5b36: Pull complete
99da31dd6142: Pull complete
626033c43d70: Pull complete
37d5d7efb64e: Pull complete
ac563158d721: Pull complete
d2ba16033dad: Pull complete
688ba7d5c01a: Pull complete
00e060b6d11d: Pull complete
1c04857f594f: Pull complete
4d7cfa90e6ea: Pull complete
e0431212d27d: Pull complete
Digest: sha256:e9027fe4d91c0153429607251656806cc784e914937271037f7738bd5b8e7709 # 校验hash值
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest # 真实位置

# docker pull mysql 等价于 docker pull docker.io/library/mysql:latest

# 指定版本下载
[root@ls-rK8rbuXz ~]# docker pull mysql:5.7

docker rmi

1
2
3
4
5
# 删除镜像
docker rmi 镜像id或镜像名 # 不能删除运行过的镜像
dokcer rmi -f 镜像id或镜像名 # 强制删除镜像(强制删除正在运行的容器)
docker rmi -f 镜像名(id):tag 镜像名(id):tg # 强制删除多个镜像
docker rmi -f $(docker images -aq) # 强制删除全部镜像

容器命令

说明:有镜像才能创建容器,我们这里使用 centos 的镜像来测试,就是虚拟一个 centos !

1
docker pull 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
# 命令
docker run [OPTIONS] IMAGE [COMMAND][ARG...]

# 常用参数说明
--name="containerName" # 给容器指定一个名字
-d # 以后台方式运行容器并返回容器的ID
-i # 以交互模式运行容器,通常和 -t 一起使用
-t # 给容器重新分配一个终端,通常和 -i 一起使用
-P # 随机端口映射(大写)
-p # 指定端口映射(小结),一般可以有四种写法
ip:主机的端口:容器端口
ip::容器端口
主机端口:容器端口(常用)
容器端口
# 测试
[root@ls-rK8rbuXz ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mysql 5.7 c20987f18b13 18 months ago 448MB
mysql latest 3218b38490ce 18 months ago 516MB
centos latest 5d0da3dc9764 21 months ago 231MB
[root@ls-rK8rbuXz ~]#
# 使用centos进行用交互模式启动容器,在容器内执行/bin/bash命令!
[root@ls-rK8rbuXz ~]# docker run -it centos /bin/bash
[root@ec124b7f2cf0 /]#
[root@ec124b7f2cf0 /]#
[root@ec124b7f2cf0 /]# ls # 注意主机名,已经切换到容器内部了!
bin etc lib lost+found mnt proc run srv tmp var
dev home lib64 media opt root sbin sys usr
[root@ec124b7f2cf0 /]#
[root@ec124b7f2cf0 /]# exit # 使用exit从容器中退出到主机
exit
[root@ls-rK8rbuXz ~]#

列出所有运行的容器

1
2
3
4
5
6
7
8
# 命令
docker ps [OPTIONS]

# 常用参数说明
-a # 列出当前所有正在运行的容器 + 历史运行过的容器
-l # 显示最近创建的容器
-n=number # 显示最近n个创建的容器
-q # 静默模式,只显示容器编号。

退出容器

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

启动停止容器

1
2
3
4
docker start 容器id或容器名       # 启动容器
docker restart 容器id或容器名 # 重启容器
docker stop 容器id或容器名 # 停止容器
docker kill 容器id或容器名 # 强制停止容器

删除容器

1
2
3
4
docker rm 容器id或容器名   # 删除一个停止的容器
docker rm -f 容器id或容器名 # 强制删除一个容器(包括正在运行的容器)
docker rm -f $(docker ps -qa) # 强制删除所有容器(包括正在运行的容器)
dokcer ps -qa | xargs docker rm -f # 强制删除所有容器(包括正在运行的容器)

其他常用命令

后台启动容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 命令
docker run -d 镜像名
# 例子: 后台运行centos
docker run -d centos
# 问题: 使用docker ps 查看,发现容器已经退出了!
[root@ls-rK8rbuXz ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6f655e268f0e centos "/bin/bash" 2 seconds ago Exited (0) 2 seconds ago hungry_hamilton
[root@ls-rK8rbuXz ~]#
# 解释:Docker容器后台运行,就必须有一个前台进程,容器运行的命令如果不是那些一直挂起的命 令,就会自动退出。
# 比如,你运行了nginx服务,但是docker前台没有运行应用,这种情况下,容器启动后,会立即自杀,因为他觉得没有程序了,所以最好的情况是,将你的应用使用前台进程的方式运行启动。
[root@ls-rK8rbuXz ~]# docker run -itd centos /bin/bash
f4911c07c121b70bde41bfcd84128d53640f3416f8e4be71c8c3372242cbea79
[root@ls-rK8rbuXz ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f4911c07c121 centos "/bin/bash" 1 second ago Up 1 second suspicious_jackson

查看日志

1
2
3
4
5
6
7
8
9
10
11
12
13
# docker logs -f -t -n number 容器id

# docker logs --help # 查看帮助
# 参数解释
-f 滚动日志 实时刷新
-t 显示时间
-n 显示的行数 (默认不指定显示所有)
# 例子:我们启动 centos,并编写一段脚本来测试玩玩!最后查看日志
docker run -d centos /bin/sh -c "while true;do echo x1ong;sleep 1;done"
# 测试
[root@ls-rK8rbuXz ~]# docker logs -f -t 22e876fdb6fd
2023-07-04T07:49:48.107583028Z x1ong
2023-07-04T07:49:49.109597644Z x1ong

查看容器中运行的进程信息

1
2
3
4
5
6
# 命令
docker top 容器id
# 测试
[root@ls-rK8rbuXz ~]# docker top dfd8e5a602b9
UID PID PPID C STIME TTY TIME CMD
root 19996 19974 0 15:53 pts/0 00:00:00 /bin/bash

查看容器/镜像的元数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 命令
docker inspect 容器ID
# 测试
[root@ls-rK8rbuXz ~]# docker inspect dfd8e5a602b9
[
.......
"Networks": {
"bridge": {
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
....
}
}
]

进入正在运行的容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 命令1
docker exec -it 容器id bashShell

# 测试1
[root@ls-rK8rbuXz ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dfd8e5a602b9 centos "/bin/bash" 5 minutes ago Up 5 minutes nostalgic_wilson
[root@ls-rK8rbuXz ~]# docker exec -it dfd8e5a602b9 /bin/bash
[root@dfd8e5a602b9 /]# hostname
dfd8e5a602b9

# 命令2
docker attach 容器id

# 测试2
[root@ls-rK8rbuXz ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dfd8e5a602b9 centos "/bin/bash" 6 minutes ago Up 5 minutes nostalgic_wilson
[root@ls-rK8rbuXz ~]# docker attach dfd8e
[root@dfd8e5a602b9 /]# hostname
dfd8e5a602b9
# 区别
# exec 是在容器中打开新的终端,并且可以启动新的进程
# attach 直接进入容器启动命令的终端,不会启动新的进程

从容器内复制文件到宿主机

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

# 测试
# 容器内执行,创建一个文件123.txt并写入内容为helloworld
[root@dfd8e5a602b9 ~]# cd /home
[root@dfd8e5a602b9 home]# echo helloworld > 123.txt
[root@dfd8e5a602b9 home]# cat 123.txt
helloworld
[root@dfd8e5a602b9 home]#

# 宿主机复制查看
[root@ls-rK8rbuXz ~]# docker cp dfd8e5a602b9:/home/123.txt /tmp/123.txt
Successfully copied 2.05kB to /tmp/123.txt
[root@ls-rK8rbuXz ~]#
[root@ls-rK8rbuXz ~]# cat /tmp/123.txt
helloworld
[root@ls-rK8rbuXz ~]#

安装练习

使用 docker 安装 nginx

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
# 1. 搜索镜像
[root@ls-rK8rbuXz ~]# docker search nginx
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
nginx Official build of Nginx. 18715 [OK]
...

# 2. 拉取镜像
[root@ls-rK8rbuXz ~]# docker pull nginx

# 3. 启动容器
[root@ls-rK8rbuXz ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 605c77e624dd 18 months ago 141MB

# 将容器的80端口映射到宿主机的8080端口
[root@ls-rK8rbuXz ~]# docker run -d --name nginx01 -p 8080:80 nginx
a7b3aafe7ae6add22d680848ace975c726523828abd08752867003f376998500
[root@ls-rK8rbuXz ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a7b3aafe7ae6 nginx "/docker-entrypoint.…" 3 seconds ago Up 2 seconds 0.0.0.0:8080->80/tcp, :::8080->80/tcp nginx01

# 4. 测试
[root@ls-rK8rbuXz ~]# curl http://127.0.0.1:8080
...
<h1>Welcome to nginx!</h1>
....

# 5. 进入容器内
[root@ls-rK8rbuXz ~]# docker exec -it a7b3aafe7ae6 /bin/bash
root@a7b3aafe7ae6:/# ls
bin dev docker-entrypoint.sh home lib64 mnt proc run srv tmp var
boot docker-entrypoint.d etc lib media opt root sbin sys usr
root@a7b3aafe7ae6:/# whereis nginx
nginx: /usr/sbin/nginx /usr/lib/nginx /etc/nginx /usr/share/nginx
root@a7b3aafe7ae6:/# cd /usr/share/nginx/
root@a7b3aafe7ae6:/usr/share/nginx# ls
html
root@a7b3aafe7ae6:/usr/share/nginx# cd html/
root@a7b3aafe7ae6:/usr/share/nginx/html# ls
50x.html index.html
root@a7b3aafe7ae6:/usr/share/nginx/html# cat index.html
...
<h1>Welcome to nginx!</h1>
...

使用 docker 安装 tomcat

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
# 1. 拉取tomcat镜像
[root@ls-rK8rbuXz ~]# docker pull tomcat
# 2. 运行镜像
[root@ls-rK8rbuXz ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
tomcat latest fb5657adc892 18 months ago 680MB
[root@ls-rK8rbuXz ~]# docker run -d -p 8081:8080 --name tomcat01 tomcat
1fde810bfc6d5d629c2cd67def8d32bc93fa92eb320b2379e7d59bdcd9507dcb
[root@ls-rK8rbuXz ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1fde810bfc6d tomcat "catalina.sh run" 3 seconds ago Up 2 seconds 0.0.0.0:8081->8080/tcp, :::8081->8080/tcp tomcat01
# 3. 测试
[root@ls-rK8rbuXz ~]# curl http://127.0.0.1:8081
...
<h1>HTTP Status 404 – Not Found</h1>
...
# 4. 进入容器
[root@ls-rK8rbuXz ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1fde810bfc6d tomcat "catalina.sh run" 2 minutes ago Up 2 minutes 0.0.0.0:8081->8080/tcp, :::8081->8080/tcp tomcat01
[root@ls-rK8rbuXz ~]# docker exec -it 1fde810bfc6d /bin/bash
root@1fde810bfc6d:/usr/local/tomcat# ls
BUILDING.txt LICENSE README.md RUNNING.txt conf logs temp webapps.dist
CONTRIBUTING.md NOTICE RELEASE-NOTES bin lib native-jni-lib webapps work
root@1fde810bfc6d:/usr/local/tomcat# cd webapps
root@1fde810bfc6d:/usr/local/tomcat/webapps# ls
# 5. 思考:我们以后要部署项目,还需要进入容器中,是不是十分麻烦,要是有一种技术,可以将容器 内和我们Linux进行映射挂载就好了?我们后面会将数据卷技术来进行挂载操作,也是一个核心内容,这 里大家先听听名词就好,我们很快就会讲到!

docker 镜像

镜像的概念

镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含
运行某个软件所需的所有内容,包括代码、运行时、库、环境变量和配置文件。

docker 镜像加载原理

UnionFS (联合文件系统)

UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统, 它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系 统下(unite several directories into a single virtual filesystem)。Union 文件系统是 Docker 镜像的基 础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。

特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件
系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录

Docker镜像加载原理

docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统UnionFS。

bootfs(boot file system)主要包含bootloader和kernel, bootloader主要是引导加载kernel, Linux刚启 动时会加载bootfs文件系统,在Docker镜像的最底层是bootfs。这一层与我们典型的Linux/Unix系统是 一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已 由bootfs转交给内核,此时系统也会卸载bootfs。

rootfs (root file system) ,在bootfs之上。包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc 等标 准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,Centos等等。

image-20230705150940186

平时我们安装进虚拟机的CentOS都是好几个G,为什么Docker这里才200M?

image-20230705151210236

对于一个精简的OS,rootfs 可以很小,只需要包含最基本的命令,工具和程序库就可以了,因为底层直 接用Host的kernel,自己只需要提供rootfs就可以了。由此可见对于不同的linux发行版, bootfs基本是一 致的, rootfs会有差别, 因此不同的发行版可以公用bootfs

分层理解

分层的镜像

我们可以去下载一个镜像,注意观察下载的日志输出,可以看到是一层一层的在下载!

image-image-20230705151613170

思考:为什么Docker镜像要采用这种分层的结构呢?

最大的好处,我觉得莫过于是资源共享了!比如有多个镜像都从相同的Base镜像构建而来,那么宿主机 只需在磁盘上保留一份base镜像,同时内存中也只需要加载一份base镜像,这样就可以为所有的容器服 务了,而且镜像的每一层都可以被共享。

查看镜像分层的方式可以通过 docker image inspect 镜像名称:tag 命令!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@ls-rK8rbuXz ~]# docker image inspect redis
[
...
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:2edcec3590a4ec7f40cf0743c15d78fb39d8326bc029073b41ef9727da6c851f",
"sha256:9b24afeb7c2f21e50a686ead025823cd2c6e9730c013ca77ad5f115c079b57cb",
"sha256:4b8e2801e0f956a4220c32e2c8b0a590e6f9bd2420ec65453685246b82766ea1",
"sha256:529cdb636f61e95ab91a62a51526a84fd7314d6aab0d414040796150b4522372",
"sha256:9975392591f2777d6bf4d9919ad1b2c9afa12f9a9b4d260f45025ec3cc9b18ed",
"sha256:8e5669d8329116b8444b9bbb1663dda568ede12d3dbcce950199b582f6e94952"
]
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
]

理解:

所有的 Docker 镜像都起始于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之 上,创建新的镜像层。

举一个简单的例子,假如基于 Ubuntu Linux 16.04 创建一个新的镜像,这就是新镜像的第一层;如果 在该镜像中添加 Python包,就会在基础镜像层之上创建第二个镜像层;如果继续添加一个安全补丁,就 会创建第三个镜像层。

该镜像当前已经包含 3 个镜像层,如下图所示(这只是一个用于演示的很简单的例子)。

image-20230705152030373

在添加额外的镜像层的同时,镜像始终保持是当前所有镜像的组合,理解这一点非常重要。下图中举了 一个简单的例子,每个镜像层包含 3 个文件,而镜像包含了来自两个镜像层的 6 个文件。

image-20230705152051307

上图中的镜像层跟之前图中的略有区别,主要目的是便于展示文件。

下图中展示了一个稍微复杂的三层镜像,在外部看来整个镜像只有 6 个文件,这是因为最上层中的文件 7 是文件 5 的一个更新版本。

image-20230705152121953

这种情况下,上层镜像层中的文件覆盖了底层镜像层中的文件。这样就使得文件的更新版本作为一个新
镜像层添加到镜像当中。

Docker 通过存储引擎(新版本采用快照机制)的方式来实现镜像层堆栈,并保证多镜像层对外展示为统 一的文件系统。

Linux 上可用的存储引擎有 AUFS、Overlay2、Device Mapper、Btrfs 以及 ZFS。顾名思义,每种存储 引擎都基于 Linux 中对应的文件系统或者块设备技术,并且每种存储引擎都有其独有的性能特点。

Docker 在 Windows 上仅支持 windowsfilter 一种存储引擎,该引擎基于 NTFS 文件系统之上实现了分 层和 CoW[1]。

下图展示了与系统显示相同的三层镜像。所有镜像层堆叠并合并,对外提供统一的视图。

image-20230705152436430

特点:

Docker镜像都是只读的,当容器启动时,一个新的可写层被加载到镜像的顶部! 这一层就是我们通常说的容器层,容器之下的都叫镜像层!

镜像 commit

docker commit 从容器创建一个新的镜像。

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
docker commit # 提交容器副本使之成为一个新的镜像

# 语法
docker commit -m="镜像的描述信息" -a="镜像的作者" 容器id 要目标镜像名:[标签名]

# 示例
docker commit -m="add webapps content" -a="x1ong" fb5657adc892 tomcat01:1.0

# 测试

# 运行容器 并 修改容器的内容
[root@ls-rK8rbuXz ~]# docker run -itd -p 8080:8080 tomcat
cef950c16d35c2ba6d8df63e374e7397f5a1fc56e023aa77706e823701d515ee
[root@ls-rK8rbuXz ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cef950c16d35 tomcat "catalina.sh run" 4 seconds ago Up 3 seconds 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp epic_bardeen
[root@ls-rK8rbuXz ~]# docker exec -it cef950c16d35 /bin/bash
root@cef950c16d35:/usr/local/tomcat# cp -r webapps.dist/* webapps
root@cef950c16d35:/usr/local/tomcat# exit
exit
[root@ls-rK8rbuXz ~]#

# 将容器打包成镜像
[root@ls-rK8rbuXz ~]# docker commit -m="add webapps content" -a="x1ong" cef950c16d35 tomcat01:0.1
sha256:3a57775fb4c30d47516af74177fbeab4d4753874497996c4e91ef73474a2f42e
[root@ls-rK8rbuXz ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
tomcat01 0.1 3a57775fb4c3 2 seconds ago 684MB
tomcat latest fb5657adc892 18 months ago 680MB
# 将打包成的镜像运行起来查看是否有之前修改的内容
[root@ls-rK8rbuXz ~]# docker run -itd -p 8081:8080 tomcat01:0.1
1422dd48f25e63307038a0137af3f2a5aa96cab2ea99490006c077a5baf51e79
[root@ls-rK8rbuXz ~]# docker exec -it 1422dd48f25e /bin/bash
root@1422dd48f25e:/usr/local/tomcat# cd webapps
root@1422dd48f25e:/usr/local/tomcat/webapps# ls

如果你想要保存你当前的状态,可以通过commit,来提交镜像,方便使用,类似于 VM 中的快照!

容器数据卷

Docker 的理念回顾

将应用和运行的环境打包形成镜像,在 docker 中进行运行,此时数据是存在容器中的,当我们把容器删除,那么容器中的数据就会随之删除。

如果我们想要持久化存储数据,那么当容器被删除的时候,数据也会丢失。

为了能让我们的数据持久化存储,这里就需要用到 Docker 的容器卷技术。

Docker 的容器数据卷

Docker 容器卷技术就是 将容器的某个文件夹或文件挂载到宿主机的某个文件夹或文件当中,当容器的文件夹的内容被修改,那么宿主机中的文件夹内容也会随之修改,保持同步的状态

作用:

卷就是目录或者文件,存在一个或者多个容器中,由docker挂载到容器,但不属于联合文件系统,因此 能够绕过 Union File System , 提供一些用于持续存储或共享数据的特性:

卷的设计目的就是数据的持久化,完全独立于容器的生存周期,因此 Docker不会在容器删除时删除其挂载的数据卷。

特点:

1、数据卷可在容器之间共享或重用数据
2、卷中的更改可以直接生效
3、数据卷中的更改不会包含在镜像的更新中
4、数据卷的生命周期一直持续到没有容器使用它为止

所以: 总结一句话: 就是容器的持久化,以及容器间的继承和数据共享!

使用数据卷

使用 docker 中的 -v 参数可以绑定容器卷

1
2
root@ls-rK8rbuXz:~# docker run --help
-v, --volume list Bind mount a volume

挂载命令的使用:

1
2
3
4
# 语法
docker run -it -d -v 宿主机绝对路径目录:容器内目录 镜像名
# 示例: 将容器的 /home 目录挂载到宿主机的 /home/data 目录下
docker run -it -d -v /home/data:/home centos /bin/bash

查看是否挂载成功:

1
2
3
4
5
6
7
8
9
10
11
12
root@ls-rK8rbuXz:~# docker inspect 97cb407939dd
...
"Mounts": [
{
"Type": "bind",
"Source": "/home/data",
"Destination": "/home",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
...

测试容器和宿主机之间数据共享:可以发现,在容器中创建的,会在宿主机中看到。

image-20231012191903427

测试容器停止退出后,主机修改数据是否会同步:

  1. 停止容器
  2. 在宿主机上修改文件,增加些内容
  3. 启动刚才停止的容器
  4. 然后查看对应的文件,发现数据依旧同步!ok

image-20231012192144377

实战

使用 docker 安装 mysql

要求:将容器内 mysql数据库的数据 使用容器数据卷技术 挂载到宿主机中。

1
2
3
4
5
6
# 1. 搜索镜像
root@ls-rK8rbuXz:~# docker search mysql
# 2. 拉取镜像
root@ls-rK8rbuXz:~# docker pull mysql
# 3. 启动容器
root@ls-rK8rbuXz:/home# docker run -d -p 3310:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql mysql

此时容器中的 /etc/mysql/conf.d/var/lib/mysql 目录就分别挂载到了宿主机的 /home/mysql/conf/home/mysql/data 目录下。

image-20231012194005238

DockerFile

前置知识

这里我们思考一个问题,我们常常使用 docker pull 去拉取一个镜像的时候,那么这个镜像是怎么来的?答案是官方有内置的镜像,比如 apache、nginx、redis、tomcat 等。

当然,这里我们普通用户其实也是可以上传直接的 docker 镜像的。

从编写 DockFile 到上传镜像 和 启动运行 流程大概是这样的:

开始应用 => 编写DockerFile => 打包为镜像 => 上传到仓库(私有仓库 or 公有仓库) => 下载镜像 => 启动运行。

DockerFile 的概念

DockerFile 是用来构建 Docker 镜像的文件,是由一系列命令和参数构成的脚本。

构建步骤:

  1. 编写 DockerFile 文件
  2. docker build 构建镜像
  3. docker run 启动运行

这里我们来看下 centos 镜像的 DockerFile 文件:https://hub.docker.com/_/centos

image-20231012222831312

image-20231012222914795

DockerFile 构建过程

基础知识

  • 每条保留字指令都必须为大写 且 后面要跟随至少一个参数
  • 指令按照从上到下,顺序执行。
  • # 表示注释
  • 每条指令都会创建一个新的镜像层,并对镜像进行提交

流程

  1. docker从基础镜像运行一个容器
  2. 执行一条指令并对容器做出修改
  3. 执行类似 docker commit 的操作提交一个新的镜像层
  4. docker 在基于刚提交的镜像运行一个新的容器
  5. 执行DockerFile中的下一条指令直到所有指令都执行完成!

说明

从软件开发的角度来看,DockerFile、Docker镜像、Docker容器分别代表了三个不同的阶段:

  1. DockerFile 就是软件的原材料(代码)
  2. Docker 镜像则是软件的交付品(.apk)
  3. Docker 容器则是软件的运行状态(客户下载安装执行)

image-20231012223715571

构建命令

  1. 编写Dockerfile:文件名随意,但是官方推荐命令为:Dockerfle
  2. 构建镜像:docker build -t 镜像名:TAG -f Dockerfile文件名 如果是默认文件名则为:docker build -t 镜像名:TAG .
  3. 运行镜像:docker run -itd -p 宿主机端口:容器端口 镜像名:TAG /bin/bash

DockerFile 指令

1
2
3
4
5
6
7
8
9
10
11
12
FROM # 基础镜像,当前新镜像是基于哪个镜像的 
MAINTAINER # 镜像维护者的姓名混合邮箱地址
RUN # 容器构建时需要运行的命令
EXPOSE # 当前容器对外保留出的端口
WORKDIR # 指定在创建容器后,终端默认登录的进来工作目录,一个落脚点
ENV # 用来在构建镜像过程中设置环境变量
ADD # 将宿主机目录下的文件拷贝进镜像且ADD命令会自动处理URL和解压tar压缩包
COPY # 类似ADD,拷贝文件和目录到镜像中!
VOLUME # 容器数据卷,用于数据保存和持久化工作
CMD # 指定一个容器启动时要运行的命令,dockerFile中可以有多个CMD指令,但只有最后一个生效!
ENTRYPOINT # 指定一个容器启动时要运行的命令!和CMD一样
ONBUILD # 当构建一个被继承的DockerFile时运行命令,父镜像在被子镜像继承后,父镜像的 ONBUILD被触发

image-20231012223826406

实战测试

Docker Hub 中99% 的镜像都是通过在base镜像 (Scratch) 中安装和配置需要的软件构建出来的。

image-20231012224041263

构建 Centos

要求该容器安装 vimnet-toolsapache 软件。

编写 Dockerfile

在当前目录下创建文件 Dockerfile 内容如下:

1
2
3
4
5
6
7
8
9
10
FROM centos:7
MAINTAINER x1ongsec<x1ongsec@163.com>
ENV wwwroot /var/www/html/
WORKDIR $wwwroot
RUN /bin/bash
RUN yum install vim -y
RUN yum install net-tools -y
RUN yum install httpd -y
EXPOSE 80
CMD ["/bin/bash"]

构建镜像

1
root@ls-rK8rbuXz:~# docker build -t centos_base .

image-20231013084432503

接着使用 docker images 查看镜像会发现多个 centos_base

1
2
3
4
5
6
7
root@ls-rK8rbuXz:~# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos_base latest df0c8ace1444 9 hours ago 904MB
mysql latest c138801544a9 2 months ago 577MB
centos latest 5d0da3dc9764 2 years ago 231MB
medicean/vulapps base_lamp f4e50ddb88a4 5 years ago 602MB
root@ls-rK8rbuXz:~#

镜像运行测试

1
2
root@ls-rK8rbuXz:~# docker run -itd -p 8081:80 centos_base /bin/bash
d4577993b09202dac661946fc798c7029ae6ab7fe2a5222d8fc837f87cb0966a

进入到容器:

1
2
3
4
5
6
7
8
9
10
11
12
root@ls-rK8rbuXz:~# docker exec -it d4577993b09202dac661946fc798c7029ae6ab7fe2a5222d8fc837f87cb0966a /bin/bash

# 查看vim net-tools httpd 命令是否安装:
[root@d4577993b092 html]# rpm -qa |grep -E "vim|net-tools|httpd"
vim-common-7.4.629-8.el7_9.x86_64
vim-enhanced-7.4.629-8.el7_9.x86_64
httpd-tools-2.4.6-99.el7.centos.1.x86_64
vim-minimal-7.4.629-7.el7.x86_64
vim-filesystem-7.4.629-8.el7_9.x86_64
net-tools-2.0-0.25.20131004git.el7.x86_64
httpd-2.4.6-99.el7.centos.1.x86_64
[root@d4577993b092 html]#

列出镜像的变更历史

1
2
# 命令语法:
docker history 镜像名 or 镜像ID

image-20231013085644775

构建 Tomcat

要求:使用 Centos7 基础镜像,并在该镜像上安装必要的vimnet-tools以及程序 tomcat 和其依赖环境 jdk

准备工作

这里我们需要在tomcat的官网以及jdk的官网下载两者的二进制执行文件:

Tomcat 官网:https://tomcat.apache.org

image-20231013114445517

Jdk 官网:https://www.oracle.com/java/technologies/downloads/#java8

image-20231013114609329

准备文件如下,其中 README.txt 则是对本镜像的说明,内容自己填写。

image-20231013114639578

编写 Dockerfile

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
FROM centos:7
MAINTAINER x1ongsec<x1ongsec@163.com>
# 把宿主机的README.txt复制到/root路径下
COPY README.txt /root
# 把java和jdk安装包添加到容器中(与复制不同的是这里自动解压)
ADD apache-tomcat-9.0.81.tar.gz /usr/local
ADD jdk-8u381-linux-x64.tar.gz /usr/local
# 切换当前SHELL环境为/bin/bash
RUN /bin/bash
# 安装必要软件
RUN yum install vim -y
RUN yum install net-tools -y
# 设置工作目录以及环境变量
ENV MYPATH /usr/local
WORKDIR $MYPATH
# 配置Java与tomcat的环境变量
ENV JAVA_HOME /usr/local/jdk1.8.0_381
ENV CLASSPATH ${JAVA_HOME}/lib/dt.jar:${JAVA_HOME}/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.81
ENV CATALINA_BASE /usr/local/apache-tomcat-9.0.81
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin
#容器运行时监听的端口
EXPOSE 8080
# 启动时运行tomcat
ENTRYPOINT ["/usr/local/apache-tomcat-9.0.81/bin/catalina.sh", "run"]

构建镜像

1
root@ls-rK8rbuXz:~/tomcat# docker build -t tomcat_base .

镜像运行测试

1
2
root@ls-rK8rbuXz:~# docker run -d -p 8090:8080 tomcat_base
fc5dbd0c5ee8b65f008fd93786baff5899e451b5f00369923094927c102d47f8

访问测试

image-20231013114907648

CMD 与ENTRYPOINT 的区别

CMD

CMD 指令为启动的容器指定默认要运行的程序,程序运行结束,容器也就结束。CMD 指令指定的程序可被 docker run 命令行参数中指定要运行的程序所覆盖。
类似于 RUN 指令,用于运行程序,但二者运行的时间点不同: CMD 在docker run 时运行,RUN 是在 docker build时运行。
注意:如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效。

ENTRYPOINT

类似于 CMD 指令,但其不会被 docker run 的命令行参数指定的指令所覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。

但是, 如果运行 docker run 时使用了 –entrypoint 选项,将覆盖 ENTRYPOINT 指令指定的程序。
优点:在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数。
注意:如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。

CMD测试

CMD什么时候执行

建一个简单的 Dockerfile 文件,内容为下面的2行内容:

1
2
FROM centos:7
CMD ["ls","-s"]

使用如下的命令,建造一个镜像并使用镜像建一个容器:

1
2
root@ls-rK8rbuXz:~# docker build .
root@ls-rK8rbuXz:~# docker run dc3b387f634e

image-20231013115802437

可以看到,容器运行起来后直接运行了CMD中的命令。也就是说CMD在镜像运行的时候执行。

覆盖CMD的命令

使用下面的命令运行一个容器,可以看到已经覆盖了CMD设置的命令

image-20231013120020358

ENTRYPOINT测试

ENTRYPOINT什么时候执行

建一个简单的 Dockerfile 文件,内容为下面的2行内容:

1
2
FROM centos:7
ENTRYPOINT ["ls","-s"]

建造一个镜像并使用镜像建一个容器

1
root@ls-rK8rbuXz:~# docker build .

image-20231013120455350

运行后和默认的和CMD是一样的

ENTRYPOINT设置的指令后面追加指令

使用 docker run 377bed257c25 -l 看到 -l 已经追加到dockerfile 设置的指令后面并执行了。

image-20231013120711486

总结

  • CMD设置的指令在镜像运行时自动运行,无法追加指令,只能把指令全部覆盖。
  • ENTRYPOINT设置的指令在镜像运行时与CMD一样,可以在新建镜像时设置的指令后追加新的指令,也可以使用 –entrypoint 覆盖指令。

发布镜像到DockerHub

注册DockerHub https://hub.docker.com/signup,需要有一个账号

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
# 1. 查看登陆帮助命令
root@ls-rK8rbuXz:~# docker login --help
Usage: docker login [OPTIONS] [SERVER]
Log in to a registry.
If no server is specified, the default is defined by the daemon.
Options:
-p, --password string Password
--password-stdin Take the password from stdin
-u, --username string Username
# 2. 登陆
root@ls-rK8rbuXz:~# docker login -u x1ongsec
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

# 3. 将镜像发布到DockerHub
root@ls-rK8rbuXz:~# docker push tomcat_base:latest
The push refers to repository [docker.io/library/tomcat_base]
5f70bf18a086: Preparing
9d3e7e13e895: Preparing
108c449d4d0d: Preparing
44754636ad15: Preparing
85d5b7f528b0: Preparing
aca9902b61fd: Waiting
b63e96353fcf: Waiting
174f56854903: Waiting
# 问题1: 提示 请求的对资源的访问被拒绝
denied: requested access to the resource is denied
# 解决问题: 本地镜像名无帐号信息,解决加 tag即可
root@ls-rK8rbuXz:~# docker tag 1019c470437f x1ongsec/tomcat_base:1.0
# 再次推送
root@ls-rK8rbuXz:~# docker push x1ongsec/tomcat_base:1.0
The push refers to repository [docker.io/x1ongsec/tomcat_base]
5f70bf18a086: Pushed
9d3e7e13e895: Pushed
108c449d4d0d: Pushed
44754636ad15: Pushed
85d5b7f528b0: Pushed
aca9902b61fd: Pushed
b63e96353fcf: Pushed
174f56854903: Pushed
1.0: digest: sha256:538c343e5d96a01cfeae635a98a1dd29dc5759384629d326b826ed53199c7260 size: 1997

Docker 网络

Docker 网络讲解

理解 Docker0

准备工作:清空所有的容器,清空所有的镜像

1
2
docker rm -f $(docker ps -a -q) # 删除所有容器
docker rmi -f $(docker images -qa) # 删除全部镜像

通信测试

这里我们先做一个测试

使用 ip addr 命令查看 Linux 服务器的网络信息:

image-20231013204350188

这里我们分析可得,有三个网络:

127.0.0.1 本机回环地址

192.168.0.4 百度云服务器的内网IP

172.17.0.1 Docker 网桥

当我们创建一个容器的时候,docker会自动的为该容器分配一个IP地址,而该IP地址与Linux服务器的网桥IP同在一个网段。

1
2
# 创建容器
root@ls-rK8rbuXz:~# docker run -d -P tomcat_base

image-20231013204738118

这里思考一个问题:Linux服务器是否可以ping通容器?

image-20231013204827937

答案是可以的。

第二个问题:容器是否可以ping通Linux网桥的IP以及Linux服务器的其他网卡?

image-20231013204953929

答案也是可以的。

通信原理

  1. 每一个安装了 Docker 的 linux 主机都有一个 docker0 的虚拟网卡。这是个桥接网卡,使用了veth-pair技术。

当我们运行一个容器之后,我们再次在Linux服务器中查看IP地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
root@ls-rK8rbuXz:~# 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
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether fa:20:20:24:5d:41 brd ff:ff:ff:ff:ff:ff
inet 192.168.0.4/20 brd 192.168.15.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::f820:20ff:fe24:5d41/64 scope link
valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:0a:b3:91:36 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:aff:feb3:9136/64 scope link
valid_lft forever preferred_lft forever
241: vethd2cd09c@if240: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 96:55:a0:df:46:a6 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::9455:a0ff:fedf:46a6/64 scope link
valid_lft forever preferred_lft forever

在没有开启任何容器之前我们有三个网络,分别是 loeth0docker0,但是当我们开启容器之后会多了个 vethd2cd09c@if240 的网络。

2、每启动一个容器,linux主机就会多了一个虚拟网卡。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 我们启动了一个tomcat01,主机的ip地址多了一个 123: vethc8584ea@if122 
# 然后我们在tomcat01容器中查看容器的ip是 122: eth0@if123

# 我们再启动一个tomcat02观察
[root@kuangshen ~]# docker run -d -P --name tomcat02 tomcat_base
# 然后发现linux主机上又多了一个网卡 125: veth021eeea@if124:
# 我们看下tomcat02的容器内ip地址是 124: eth0@if125:
[root@kuangshen ~]# docker exec -it tomcat02 ip addr
# 观察现象:
# tomcat --- linux主机 vethc8584ea@if122 ---- 容器内 eth0@if123
# tomcat --- linux主机 veth021eeea@if124 ---- 容器内 eth0@if125

# 相信到了这里,大家应该能看出点小猫腻了吧!只要启动一个容器,就有一对网卡
# veth-pair 就是一对的虚拟设备接口,它都是成对出现的。一端连着协议栈,一端彼此相连着。
# 正因为有这个特性,它常常充当着一个桥梁,连接着各种虚拟网络设备!
# “Bridge、OVS 之间的连接”,“Docker 容器之间的连接” 等等,以此构建出非常复杂的虚拟网络 结构,比如 OpenStack Neutron。

3、我们来测试下tomcat01和tomcat02容器间是否可以互相ping通

1
2
3
4
5
6
7
root@ls-rK8rbuXz:~# docker exec -it 587dd636f5cc ping 172.17.0.3
PING 172.17.0.3 (172.17.0.4) 56(84) bytes of data.
64 bytes from 172.17.0.4: icmp_seq=1 ttl=64 time=0.031 ms
64 bytes from 172.17.0.4: icmp_seq=2 ttl=64 time=0.037 ms
64 bytes from 172.17.0.4: icmp_seq=3 ttl=64 time=0.033 ms
64 bytes from 172.17.0.4: icmp_seq=4 ttl=64 time=0.033 ms
# 容器和容器之间都是可以相互访问的

4、我们来绘制一个网络模型图

image-20231013212810747

结论:tomcat1和tomcat2共用一个路由器。是的,他们使用的一个,就是docker0。任何一个容器启动 默认都是docker0网络。
docker默认会给容器分配一个可用ip。

小结

Docker使用Linux桥接,在宿主机虚拟一个Docker容器网桥(docker0),Docker启动一个容器时会根据 Docker网桥的网段分配给容器一个IP地址,称为Container-IP,同时Docker网桥是每个容器的默认网关。因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的Container-IP直接通信。

image-20231013212922415

Docker容器网络就很好的利用了Linux虚拟网络技术,在本地主机和容器内分别创建一个虚拟接口,并让他们彼此联通(这样一对接口叫veth pair);

Docker中的网络接口默认都是虚拟的接口。虚拟接口的优势就是转发效率极高(因为Linux是在内核中 进行数据的复制来实现虚拟接口之间的数据转发,无需通过外部的网络设备交换),对于本地系统和容器系统来说,虚拟接口跟一个正常的以太网卡相比并没有区别,只是他的速度快很多。

思考一个场景,我们编写一个微服务,数据库连接地址原来是使用ip的,如果ip变化就不行了,那我们能不能使用服务名访问呢?

jdbc:mysql://mysql:3306,这样的话哪怕mysql重启,我们也不需要修改配置了! docker提供了 –link 的操作!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 我们使用tomcat01,通过容器名 ping tomcat02 此时可以通吗? 答案是不可以的
root@ls-rK8rbuXz:~# docker exec -it 880ef9bc449e ping tomcat02
ping: tomcat: Name or service not known

# 我们这里再启动一个tomcat3,但是在启动的时候,我们使用 --link 参数连接 tomcat02
root@ls-rK8rbuXz:~# docker run -d -P --name tomcat03 --link tomcat02 tomcat_base
d87cac2a282dbde98b2fb112d697e6e427611bb1ed87cd275d2ffb2617b34aff
# 此时是可以通的,因为tomcat03在run的时候连接了tomcat02
root@ls-rK8rbuXz:~# docker exec -it d87cac2a282d ping tomcat02
PING tomcat02 (172.17.0.4) 56(84) bytes of data.
64 bytes from tomcat02 (172.17.0.4): icmp_seq=1 ttl=64 time=0.126 ms
64 bytes from tomcat02 (172.17.0.4): icmp_seq=2 ttl=64 time=0.067 ms

# 再来测试,tomcat02 是否可以ping tomcat03 反向也ping不通
root@ls-rK8rbuXz:~# docker exec -it 83cad5d30841 ping tomcat03
ping: tomcat03: Name or service not known
# 反向是ping不通的

思考,这个原理是什么呢?我们进入tomcat03中查看下host配置文件

1
2
3
4
5
6
7
8
9
10
11
root@ls-rK8rbuXz:~# docker exec -it d87cac2a282d cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.4 tomcat02 83cad5d30841
172.17.0.5 d87cac2a282d
# 所以这里其实就是配置了一个 hosts 地址而已!
# 原因:--link的时候,直接把需要link的主机的域名和ip直接配置到了hosts文件中了。

--link 早都过时了,我们不推荐使用! 我们可以使用自定义网络的方式。

自定义网络

内置网络模式分析

基本命令查看

image-20231013214118743

查看所有网络

image-20231013214150547

所有网络模式

网络模式 配置 说明
bridge 模式 –net=bridge 默认值 在Docker网桥docker0上为容器创建新的网络
none模式 –net=none 不配置网络 用户可以稍后进入容器,自行配置
container 模式 –net=container:name/id 容器和另外一个容器共享Network namespace。 kubernetes中的pod就是多个容器共享一个Network namespace。
host模式 –net=host 容器和宿主机共享Network namespace
用户自定义 –net=自定义网络 创建容器的时候可以指定为自己定义的网络

查看一个具体的网络的详细信息

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
root@ls-rK8rbuXz:~# docker network inspect cf9cefcfc6ef
[
{
"Name": "bridge",
"Id": "cf9cefcfc6ef13ca3dd44adc5e8e6110d9e591aba98633f03bac60be7477f281",
"Created": "2023-10-09T23:40:27.43579885+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
# 默认docker0是管理这个子网范围内的。0~16,也就是 255*255,去掉0个255,我们有65534可以分配的ip
# docker0网络默认可以支持创建6万多个容器ip不重复
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
# 以下当前网络模式下的所有容器以及IP
"83cad5d308415f1eef98441b0335e2dec47b92e91f02427ee74c56dee8f13bb1": {
"Name": "tomcat02",
"EndpointID": "05190a8bd7cef85325fd5f4029f10521ca2e9a8cf90df48b91b199e84775e47e",
"MacAddress": "02:42:ac:11:00:04",
"IPv4Address": "172.17.0.4/16",
"IPv6Address": ""
},
"880ef9bc449ee449d87f041e6cdf15d8f9c6db9ef2e5100c8d7b22f169013a34": {
"Name": "tomcat01",
"EndpointID": "84761ecb21030854c0f1911e4bc4504aa3439c90e90c4e2351474e4dd7292e75",
"MacAddress": "02:42:ac:11:00:03",
"IPv4Address": "172.17.0.3/16",
"IPv6Address": ""
},
"97cb407939ddd7e7293aab51074c758f250f7b3404dbf98168e9395a31793739": {
"Name": "LAMP",
"EndpointID": "c0bd47b0292df6f8c56383fc2f83f78e9c00e25f852e8539a6bab29cd38fe67b",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
},
"d87cac2a282dbde98b2fb112d697e6e427611bb1ed87cd275d2ffb2617b34aff": {
"Name": "tomcat03",
"EndpointID": "ed4c6920c59d49412acec8ba2a369e81d642dcc725f808dd8437e5a8842beee9",
"MacAddress": "02:42:ac:11:00:05",
"IPv4Address": "172.17.0.5/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]

自定义网卡

当我们在创建容器的时候,如果没有使用 –net 指定网络模式,则docker会使用默认的 bridge 模式(网卡docker0)。

1
2
3
4
5
6
root@ls-rK8rbuXz:~# docker run -d -P --name tomcat04 --net bridge tomcat_base
45edbc42a24e2a70a575452e15851d124ad962b1a02816f6fc0ed82a2cab3acb
# docker0网络的特点
1. 它是默认的
2. 域名访问不通
3. --link 域名通了,但是删了又不行

我们可以让容器创建的时候,使用我们自定义的网络,接下来我们来创建自定义的网络:

语法:

1
docker network create --driver 网络模式 --subnet 子网地址 --gateway 网关地址 自定义的网络名

image-20231013215918429

1
2
3
4
5
6
7
8
root@ls-rK8rbuXz:~# docker network create -d bridge --subnet 192.168.1.0/24 --gateway 192.168.1.1 mynet
26092e185426d8afbc5008968fb01b16bf9055b4572df03dfda33d8cdf21cb69
root@ls-rK8rbuXz:~# docker network ls
NETWORK ID NAME DRIVER SCOPE
cf9cefcfc6ef bridge bridge local
85ac0b9f4846 host host local
26092e185426 mynet bridge local
2a42c8845944 none null local

查看创建的网络模式信息

1
root@ls-rK8rbuXz:~# docker network inspect 26092e185426

image-20231013220216534

这里我们创建两个容器,tomcat01 和 tomcat02 并使用我们自定义的网络 mynet 观察他们两者之间是否可以相互ping通。

1
2
3
4
5
root@ls-rK8rbuXz:~# docker run -d -P --name tomcat01 --net mynet tomcat_base
c70152d0343779de381f7d54da5b7b296a1141e48a93209995c8a0c840f39091
root@ls-rK8rbuXz:~#
root@ls-rK8rbuXz:~# docker run -d -P --name tomcat02 --net mynet tomcat_base
e551d2ee39511125f19e520e30ff8e0c0b5cbfaebf585b99f556fa5f6f7e29bb

image-20231013220639308

经过测试,他们两者之间都可以相互通信,并且支持容器名称访问。

不通网段之间的网络连通

image-20231013221341320

docker0 和自定义网络 mynet 肯定不通,我们使用自定义网络的好处就是网络隔离:

大家公司项目部署的业务都非常多,假设我们有一个商城,我们会有订单业务(操作不同数据),会有 订单业务购物车业务(操作不同缓存)。如果在一个网络下,有的程序猿的恶意代码就不能防止了,所 以我们就在部署的时候网络隔离,创建两个桥接网卡,比如订单业务(里面的数据库,redis,mq,全 部业务 都在order-net网络下)其他业务在其他网络。

那关键的问题来了,如何让 tomcat01 访问 tomcat-net01 或者说是 mynet 网络中的容器? 我们只需要让 tomcat01 连接到 mynet 网络即可。

容器准备:

1
2
3
4
5
6
7
8
9
10
11
# 启动默认的容器在docker0下:
root@ls-rK8rbuXz:~# docker run -d -P --name tomcat01 tomcat_base
ea45b17c27744b10bf120a1ef3c3dc46700f5871dde1d0f2d12440d7289d3542
root@ls-rK8rbuXz:~# docker run -d -P --name tomcat02 tomcat_base
0e38073fbb1de5a5e6455144c0225e45d6339aa8425ae7ce857d8e81f5d3de5c

# 启动 tomcat-net01 以及 tomcat-net02 并在 自定义网络中
root@ls-rK8rbuXz:~# docker run -d -P --name tomcat-net01 --net mynet tomcat_base
4c75594da8bb227813f03f549a0e8143b937b81b4e5330c2ee036d4c621ec7a4
root@ls-rK8rbuXz:~# docker run -d -P --name tomcat-net02 --net mynet tomcat_base
171e8fd959aed00797202ad28fc6c2be8939615c4bef402f34a8485af92e1df7

网络通信准备:

docker network 提供了 connect 命令,用于将一个容器连接到一个网络中。也就是一个容器可以有多个网卡。

image-20231013221950328

将一个容器连接到一个网络中语法:

1
docker network connect <network> <container_name>

命令:

1
root@ls-rK8rbuXz:~# docker network connect mynet tomcat01

此时,tomcat01容器就连接到了 mynet 网络中:

1
root@ls-rK8rbuXz:~# docker network inspect mynet

image-20231013222334997

此时 tomcat01 就有了双IP,可以与 mynet 网络中的容器进行通信。

image-20231013222625236

进入到 tomcat01 容器,发现该容器有了双网卡。

image-20231013222724995

结论: 如果要跨网络操作别人,就需要使用 docker network connect <network> <container_name> 连接。