本文前面的知识点介绍容器命令的时候,说过一个cp命令,可以把宿主机和容器的文件相互拷贝,但是这种偏向于比较独立完整的单一文件,有没有想过遇到成体系的数据怎么办,比如整个数据库的数据base路径要落盘,此时直接cp命令移动,一定会出现问题,那怎么办?就算不出现问题,一个数据库的数据就靠cp命令,现实吗?此时就用到了数据卷。它可以让宿主机的一个路径以共享文件夹的方式开发给多个容器
因此数据卷的特点如下:
1:数据卷可在容器之间共享或重用数据
2:卷中的更改可以直接生效,且持久化的生效,不会由于容器的断开而消失
3;数据卷中的更改不会包含在镜像的更新中,也就是不会被commit打包进去,因为它也属于运行数据的一种,本质上是宿主机的文件
4:数据卷的生命周期一直持续到没有容器使用它为止,但是当没有容器使用时,也不会消失,只是再次的成为了宿主机的一个普通路径
容器添加数据卷有两种方式
方式一:在创建容器的时候,你可以用-v参数添加容器卷
bash
docker run -it -v 宿主机路径:容器路径 镜像名
比如把安装Oracle的包共享到容器去
bash
[root@hdp3 opt] cd wy/
[root@hdp3 wy] ll
总用量 2295604
drwxr-xr-x. 8 root root 4096 8月 21 2009 database
-rw-r--r--. 1 root root 1239269270 10月 6 19:05 linux.x64_11gR2_database_1of2.zip
-rw-r--r--. 1 root root 1111416131 10月 6 20:27 linux.x64_11gR2_database_2of2.zip
[root@hdp3 wy] docker run -it -v /opt/wy:/opt/wy centos
[root@6f1543b2044e /] ls -l
total 48
lrwxrwxrwx 1 root root 7 Nov 3 2020 bin -> usr/bin
drwxr-xr-x 5 root root 360 Dec 6 10:31 dev
drwxr-xr-x 1 root root 4096 Dec 6 10:31 etc
drwxr-xr-x 2 root root 4096 Nov 3 2020 home
lrwxrwxrwx 1 root root 7 Nov 3 2020 lib -> usr/lib
lrwxrwxrwx 1 root root 9 Nov 3 2020 lib64 -> usr/lib64
drwx------ 2 root root 4096 Sep 15 2021 lost+found
drwxr-xr-x 2 root root 4096 Nov 3 2020 media
drwxr-xr-x 2 root root 4096 Nov 3 2020 mnt
drwxr-xr-x 1 root root 4096 Dec 6 10:31 opt
dr-xr-xr-x 142 root root 0 Dec 6 10:31 proc
dr-xr-x--- 2 root root 4096 Sep 15 2021 root
drwxr-xr-x 11 root root 4096 Sep 15 2021 run
lrwxrwxrwx 1 root root 8 Nov 3 2020 sbin -> usr/sbin
drwxr-xr-x 2 root root 4096 Nov 3 2020 srv
dr-xr-xr-x 13 root root 0 Dec 6 10:31 sys
drwxrwxrwt 7 root root 4096 Sep 15 2021 tmp
drwxr-xr-x 12 root root 4096 Sep 15 2021 usr
drwxr-xr-x 20 root root 4096 Sep 15 2021 var
[root@6f1543b2044e /] cd /opt/
[root@6f1543b2044e opt] ls
wy
[root@6f1543b2044e opt] cd wy/
[root@6f1543b2044e wy] ls
database linux.x64_11gR2_database_2of2.zip
linux.x64_11gR2_database_1of2.zip
添加共享卷的时候并不一定需要容器内部和宿主机两者存在对应的路径,其实就和插进去一块盘一样,且默认对数据卷拥有读写权限
你还可以通过查看容器结构,来确定数据卷。


这里说一个注意点,老版本的docker中,容器拥有数据卷的写权限时,容器对数据卷的主路径是可以删除的,就比如本例中的/opt/wy这个文件夹,在容器里是可以删除的,但是在新版本的docker中最多只能删除其内部的所有文件,而不能删除主路径本身
bash
[root@6f1543b2044e opt] cd wy/
[root@6f1543b2044e wy] ls
database linux.x64_11gR2_database_2of2.zip
linux.x64_11gR2_database_1of2.zip
[root@6f1543b2044e wy] cd /opt/
[root@6f1543b2044e opt] ls
wy
[root@6f1543b2044e opt] rm -rf *
rm: cannot remove 'wy': Device or resource busy ###不让你删除主路径###
[root@6f1543b2044e opt] ls
wy
[root@6f1543b2044e opt] cd wy/
[root@6f1543b2044e wy] ls -l
total 0
言归正传,有的时候,数据卷对应的宿主机路径不会让你有写的权限,因此可以运行如下命令,:ro表示只读,默认:rw
bash
docker run -it -v 宿主机路径:容器路径:ro 镜像名
如果你需要挂载多个数据卷,则要输入多个-v,而不是在一个-v后写
此时要注意老版本docker这一步有个bug,极端的时候,run命令-v挂载数据卷后访问会报错cannot open directory:Permission denied,这就需要加上--privileged=true这个参数,但新版本我还没有遇到出现这个问题
方式二:DockerFile方式添加数据卷
bash
[root@hdp3 wy] vi myfile
FROM centos
VOLUME ["/opt/myfile/test01","/opt/myfile/test02"]
CMD echo "OK!!!!"
CMD /bin/bash
~
"myfile" [New] 4L, 95C written
[root@hdp3 wy] ll
总用量 4
-rw-r--r-- 1 root root 95 12月 6 21:02 myfile
[root@hdp3 wy] docker build -f /opt/wy/myfile -t wy/centos .
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM centos
---> 5d0da3dc9764
Step 2/4 : VOLUME ["/opt/myfile/test01","/opt/myfile/test02"]
---> Running in 4b6880c91d0f
Removing intermediate container 4b6880c91d0f
---> 4bffb4a9ad2c
Step 3/4 : CMD echo "OK!!!!"
---> Running in 44edd9f7322e
Removing intermediate container 44edd9f7322e
---> 51a76bdc475f
Step 4/4 : CMD /bin/bash
---> Running in 8295fe0b4243
Removing intermediate container 8295fe0b4243
---> f605952bf91a
Successfully built f605952bf91a
Successfully tagged wy/centos:latest
[root@hdp3 wy] docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
wy/centos latest f605952bf91a 34 seconds ago 231MB
atguigu/mytomcat 1.2 4f6fc450f49c 4 hours ago 680MB
tomcat latest fb5657adc892 11 months ago 680MB
centos latest 5d0da3dc9764 14 months ago 231MB
特别说明!DockerFile方式添加数据卷有个很鬼畜的事情,老版本你在dockerfile中写的都会是容器内部会生成的路径,至于宿主机上的路径docker会给一个默认路径,需要通过查看容器的结构信息才知道挂载哪里了。而新版本中当在容器中进入数据卷路径时,会提示你,该目录打不开,这是因为新版本docker中已经不再是去生成数据卷路径,然后挂载一个宿主机默认路径了,而是只生成一个挂载点,你想用它,只能是在run的时候-v。同时拥有数据卷的容器,通常被称作是数据卷容器
此时,一定有人在想,run是新生成一个容器,那怎么给已有的容器添加数据卷,很抱歉的告诉你,截至本文目前操作的最新版,通通不支持给已有容器添加数据卷。已有的容器添加数据卷,本文尝试去改容器的配置文件,发现行不通,随着容器的重启,配置文件的相关内容就复原了
说完了数据卷,本章的下一个知识点叫容器直连。前面已经介绍过如何安装docker以及docker如何使用,但各位读者在跟随知识点学习时一定发现一个小问题,无论在KVM中,还是VMware,默认的网络NAT模式对于宿主机来讲虚拟化结果物是可见的,当然需要是直接归属的宿主机,Windows-》VMware-》KVM 这种最外层的Windows任然是访问不了虚拟化结果物的。外部想要发现结果物需要虚拟化技术的配置支持,也就是桥接,VMware内置了桥接基础,页面点一点选中后,就可以去配置相应的ip了,到了KVM要自己创建一个桥接用的网卡路由,后面的操作流程和VMware差不多。但是到了docker上,各位读者想一想大家实际操作过网络模式吗?除了前面知识点零星点点的遇到相关问题提到过网络相关的配置
其实,这是因为docker本身的立意,是容器的即用即插,从最开始老版本的默认只有回环网卡路由,到后来宿主机内部局域网,视容器为一个消耗品,所以在网络策略上几乎可以说是没有自带的其他模式。但是它给开发者预留了自定义网络策略的能力
此外,给各位读者分享一个网络策略的技术经验。由于docker这种网络策略现状,因此在其他架构以它为基础做容器平台时,就能同时看出一家公司的技术实力怎么样,就以K8s来讲,原生版本下的k8s配合docker的效果是访问任何服务,客户端都要去访问服务在k8s主节点上映射的端口,K8s内部负责服务发现、负载均衡、灾难修复(就是极短时间内重启一个容器)这一套东西。但是在大体量的公司内部这种访问方式是不能满足使用的,因此像百度、阿里这种企业,都是通过二次开发K8s插件,使得底层的容器能够像一个正常的服务器实例那样连接使用。此时,肯定有人说K8s不是弃用Docker了吗?不要被误导,K8s只是从15版本开始不在默认内置对Docker的对接协议,而是和对待其他虚拟化技术一样,需要开发者自行安装协议插件,到了18版本才实际删除了内置的Docker-CRI协议
回归正题,在各位读者学习Docker阶段,因当知道如何通过自定义网络策略,来实现容器可以让外部当做普通的服务器实例来连接。但本文在此强调一点,有看过前面KVM的读者,不要把下面的操作和kvm知识点中的桥接混淆,docker严格意义上来讲没有桥接、nat的分别,它和VMware有点像,将不同网络策略需要的支持封装在了自带的网络模块中,用户只需当做是配了不同ip段范围的可选网络配置即可
首先,docker容器的网络配置自带了三类,但其实相当于没有。自带的bridge,相当于VMware的NAT,但注意,由于该模式采用的网络模块是一个老版本docker的内置模块,并沿用至今,底层实现的缺陷导致它创建出来的容器没有DNS功能。host模式,很少使用,它是指容器完全没有自己的网络配置,直接使用宿主机的网卡,这种网络模式相当于让容器默认走了IP映射,但还需创建容器时显示配置IP映射,考虑到灵活的使用方式,通常根本不考虑这种网络模式。最后一个nono是预留的空网络无法使用。
bash
[root@hdp3 ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
e8e4f692235d bridge bridge local
4d563e307658 host host local
7bfee5177ee6 none null local
自定义网络模式的时候用的是docker自带的macvlan模块(DRIVER),这是docker后期版本中提出的一种新型的网络模块,可以提供更加灵活的网络。配置的时候,先运行下面的命令,创建一个网络模式。由于本文的目的时容器直连,所以网络策略和宿主机相同,做一个桥接效果的网络模式。从实现上来说,完全可以使用自定义网络模式,配置其他网段,这个就看实际需求了
bash
docker network create clu --driver macvlan --subnet=192.168.88.0/24 --gateway=192.168.88.2 -o macvlan_mode=bridge -o parent=ens33
clu:创建的网络模式名字,自定义
--driver macvlan:使用macvlan网络模块,这个通常不变
--gateway:该网络模式使用的网关
--subnet:CIDR格式的IP范围表达书,下面会介绍怎么写这个配置。这里如果各位读者和本文书写时操作的环境同样时基于Vmware的一个虚拟机,则改动前三位符合你的网关即可
-o:其他详情配置,上面命令中是指基于bridge模式,并继承宿主机的网卡,ens33是宿主机的网卡名
创建好后,查询一下,重点关注 DRIVER 采用的网络模块,现在创建的和自带的不一样
bash
[root@hdp3 ~] docker network ls
NETWORK ID NAME DRIVER SCOPE
e8e4f692235d bridge bridge local
2cd5c6656669 clu macvlan local
4d563e307658 host host local
7bfee5177ee6 none null local
随后,当创建容器的时候就可使用--net参数使用这个新的网络模式
bash
docker run -it --net clu --ip 192.168.88.191 centos:7.8.2003
--net:指定网络模式
--ip:是给容器一个IP,其实可以不指定用随机的,还可以在创建网络模式的时候用--ip-range参数限制随机的ip范围,但是正式使用都是静态IP
上面这种命令创建容器之后,该容器就和你的宿主机所在同一网段,并且可以被其他的设备直接发现,比如我在本地Windows上ping一下

此时容器存在了,并且可以外部网络发现,但无法外部连接,因为结合前面做镜像的知识点,应该不难明白现在这个容器没有SSH软件,毕竟本来就是即插即用的容器,考虑到SSH软件不必要,所以就没自带,至少centos的docker镜像是这样的,只有最基本的rootfs文件。并且前面介绍docker命令知识点时,留下一个特权模式的小尾巴,就在这里统一介绍。因为要安装服务并且启动等等,要用到系统管理命令,所以直连的容器自身必然是一个特权容器
bash
#新建一个特权容器
docker run -d --net clu --ip 192.168.88.190 --privileged=true centos:7.8.2003 /usr/sbin/init
#进入容器
docker exec -it f063be0bd990 /bin/bash
进入容器后,你需要保证有正常且可用的用户身份和资源,这里通过passwd命令重置root密码,并自动生成对应的资源
bash
这里要明白一个问题,你进入容器,其实是进入了一个shell命令行终端!!终端这两个字非常重要
在终端中 passwd 命令后的井号部分 意思是你当前是就是root用户,你要对自己的密码进行重置,这就可以不用经过输入旧密码的那一套
这个涉及到系统层面的东西比较复杂,这里不要多想无脑用就行
[root@f063be0bd990 /] passwd # add root
Changing password for user root.
New password:
BAD PASSWORD: The password is shorter than 8 characters
Retype new password:
passwd: all authentication tokens updated successfully.
调整yum源这里不展示了,自己网上搜一下,随后安装SSH,并启动它
bash
yum install -y openssh-* initscripts
[root@test01 /] /etc/init.d/sshd start
Starting sshd (via systemctl): [ OK ]
[root@test01 /] /etc/init.d/sshd status
● sshd.service - OpenSSH server daemon
Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)
Active: active (running) since Sat 2022-12-10 09:25:10 UTC; 20s ago
Docs: man:sshd(8)
man:sshd_config(5)
Main PID: 368 (sshd)
CGroup: /system.slice/docker-2441cd39fce5d3de267a18ff2076c970eb7e61505db88c4bcf9b72e9ab5b17e0.scope/system.slice/sshd.service
└─368 /usr/sbin/sshd -D
‣ 368 /usr/sbin/sshd -D
Dec 10 09:25:10 test01 systemd[1]: Starting OpenSSH server daemon...
Dec 10 09:25:10 test01 sshd[368]: Server listening on 0.0.0.0 port 22.
Dec 10 09:25:10 test01 sshd[368]: Server listening on :: port 22.
Dec 10 09:25:10 test01 systemd[1]: Started OpenSSH server daemon.
此时就可以用SSH连接工具直接连接了。要说明的是,之所以上面日志中计算机名称位置叫test01,是因为本文把容器的主机名也改了,看的更像一个单独的系统

如果需要像本文中这样,改已有容器的主机名部分,需要关闭容器、关闭 docker 服务!!!!一定要把服务也关闭,之后进入容器在宿主机上的配置文件中,就在本地docker仓库的containers文件夹下,根据容器id进入对应的文件夹找到,搜索含有你要修改的容器id的配置文件
注意,在新创建容器时,可以指定--hostname参数改变容器的主机名称
bash
[root@hdp3 2441cd39fce5d3de267a18ff2076c970eb7e61505db88c4bcf9b72e9ab5b17e0]# pwd
/opt/docker/containers/2441cd39fce5d3de267a18ff2076c970eb7e61505db88c4bcf9b72e9ab5b17e0
[root@hdp3 2441cd39fce5d3de267a18ff2076c970eb7e61505db88c4bcf9b72e9ab5b17e0]# ll
总用量 32
-rw-r----- 1 root root 0 12月 10 16:55 2441cd39fce5d3de267a18ff2076c970eb7e61505db88c4bcf9b72e9ab5b17e0-json.log
drwx------ 2 root root 4096 12月 10 16:55 checkpoints
-rw------- 1 root root 2959 12月 10 16:59 config.v2.json
-rw-r--r-- 1 root root 1201 12月 10 16:59 hostconfig.json
-rw-r--r-- 1 root root 13 12月 10 16:55 hostname
-rw-r--r-- 1 root root 150 12月 10 16:59 hosts
drwx------ 3 root root 4096 12月 10 16:55 mounts
-rw-r--r-- 1 root root 53 12月 10 16:55 resolv.conf
-rw-r--r-- 1 root root 71 12月 10 16:55 resolv.conf.hash
修改两个文件分别是config.v2.json、hostname,比如本例中要修改的容器id是2441cd39fce5,就在配置文件中用id搜下面的内容
config.v2.json

hostname

最后重启docker服务和容器,进入容器你就会发现主机名成功修改
bash
[root@hdp3 2441cd39fce5d3de267a18ff2076c970eb7e61505db88c4bcf9b72e9ab5b17e0] docker exec -it 2441cd39fce5 /bin/bash
[root@test01 /]#
最后说一下创建网络模式时的CIDR取值问题。
CIDR的格式是IP/网络ID,前面的IP是一个网络IP段的表达式,重点是网络ID,这个ID是让人迷糊的罪魁祸首。如果使用的网络是IPV4的时候,它最大有效值是32,本质作用是用来表示网络策略所用的IP类型
传统的意义上,网络IPV4类型分为三类ABC,总共是四组二进制,但用十进制展示的数字来表示,A类网络的默认起始掩码是255.0.0.0,B类默认起始是255.255.0.0,C类默认起始是255.255.255.0。翻译成二进制来看,也就是说,整个传统网络IPV4策略中,从a到c类的子网默认掩码的范围是11111111.00000000.00000000.00000000到11111111.11111111.11111111.11111111而这里面,网络ID就是所处网段所用掩码翻译成二进制后,有几个1
理论上来讲,id值的大小决定了该IP策略所处的网段类型,A类所能容纳的IP最多,所能配置的网络也就越因此灵活,反之C类最次,所以一般公司里面使用的都是A类地址,所以子网掩码,会遇到10开头,甚至更靠前的ip。
有很多人看到这里,很可能会以为子网掩码,可以从四个1开始一直往后枚举到四个255,打住这个想法,因为并不是所有的子网掩码都是有效的子网掩码,一个有效的子网掩码,它的二进制一定是一串1开头,并以一串0结尾,或者反过来,如果中间出现混杂则此掩码无效
当然这只是为了让大家明白这里用到的网络ID是什么,真正的要说了解网络掩码的原理,是一个很复杂庞大的东西,大家如非需要,则没必要了解太深。上文中,因为用的是vmware虚拟机做宿主机,网络策略是192的一个c类地址,所以子网掩码应该是255.255.255.0,换算成CIDR网络ID就是24