Docker 深入学习
1. 命令
1 | $ docker --help |
1.1 镜像命令
1 | docker images # 查询本机上所有的镜像 |
1 | docker search 镜像名 |
1 | docker pull 镜像名[:tag] |
1 | docker imi -f 镜像id # 删除指定镜像 |
1.2 容器命令
1 | docker run [可选参数] image |
1 | docker ps # 列出所有正在运行的容器 |
1 | exit 退出容器并停止 |
1 | dcoker rm 容器ID |
1 | docker start 容器ID |
1.3 常用其他命令
通过 docker run -d 镜像名
来启动,然后通过 docker ps
会发现容器停止了
=> docker 容器使用后台运行,就必须要有一个前台进程,docker 发现没有应用,就会自动停止
eg:nginx 容器启动后发现没有提供服务就会自动停止
1 | docker logs # 查看日志 |
1 | docker top 容器ID # 查看容器中的进程信息,其中pid是进程id,ppid是父进程id |
1 | docker inspect 容器ID/镜像ID # 查看容器/镜像元数据,在下面有一个 layers 里面是镜像文件的每一层文件 |
1 | 很多容器都是后台运行,需要进入容器,修改一些配置 |
1 | docker cp 容器ID:容器内的路径 主机路径 # 将容器中的文件拷贝到主机中 |
1.4 测试安装tomcat
1 | docker pull tomcat |
1.5 测试安装ES
1 | docker stats # 查看各个容器占用内存、CPU的情况 |
2. 可视化 portainer
一个可视化管理方案,直接用docker来安装即可,做好主机端口映射
3. 联合文件系统
镜像是一种轻量级、可执行的独立软件包,包含某个软件所需的所有内容,包括代码、运行时、库、环境变量和配置文件。
UFS(UnionFS) 联合文件系统,是一种分层、轻量级并且高性能的文件系统,支持对文件系统的修改作为一次提交来一层层的叠加。类似于Git版本控制,每一次提交(操作)都记为一层。如果连个软件有共用的某一层文件,比如都基于linux,那么都可以去挂在这一层的linux的文件,就可以复用。
ufs里面包含比如
- bootfs: 包含bootloader和kernel,bootloader主要是引导加载kernal,类似于启动系统的时候会有一个加载过程,当系统启动后,这个加载的程序就可以关闭了 => 加载的这部分就是可以复用的部分
- rootfs: 在bootfs之上,包含的就是典型的linux系统的
/dev, /proc, /bin, /etc
等标准目录和文件 => 比如linux的容器,占的内存很小,是因为镜像文件只需要包含rootfs里最基本的命令、工具即可,可以使用主机的kernel,这样的话可以提高效率
所有的镜像都起始于一个基础镜像层,当进行修改或增加新的内容的时候,就会在当前镜像层之上,创建一个新的镜像层
镜像都是只读的,当容器启动时,一个新的可写层(容器层)就会加载到镜像层的顶部
通过 commit 来将容器层和镜像层进行合并打包,变成一个新的镜像文件
1 | docker commit -m="提交的描述信息" -a="作者" 容器ID 目标镜像名[:tag] |
5. 数据卷
- 每次修改配置文件都需要进入到容器很麻烦
- 数据不应该放在容器中,否则删除容器,数据就丢失了
=> 在容器外部提供一个映射路径,类似于数据共享技术,通过卷技术,也就是目录的挂载,将容器内的目录,挂在到linux上面,来实现容器数据的持久化和同步操作,容器之间也可以数据共享。
容器里面的目录就像是快捷方式,宿主机的文件才是真实的
实现方式:
- 直接使用命令来挂载 -v
1
2
3docker run -it -v 主机目录:容器内目录 镜像名 /bin/bash
docker run --name mysql02 -e MYSQL_ROOT_PASSWORD=123456 -p 3310:3306 -v testconf2:/etc/mysql/conf.d -v /Users/mxxct/mysqldata:/var/lib/mysql -d mysql:5.7
通过 docker inspect 容器ID
查看元数据,在 Mounts
里可以看到挂载的详细信息
- 匿名挂载
不指定主机名,直接写容器内目录-v 容器内目录
- 具名挂载
也可以分为两种- 指定主机目录
-v 主机目录:容器内目录
- 不指定主机目录,只是写一个名字
-v xxx:容器内目录
这时候会默认映射到主机/var/lib/docker/volumes/xxx/_data
这个文件夹下面(在MacOS下面没找到这个路径)
- 指定主机目录
1 | docker volume ls # 查看所有的卷 |
可以在容器内目录后面添加权限控制
eg: -v 主机目录:容器内目录:ro/rw
ro readonly; rw readwrite 默认是rw,如果是ro就表示这个路径只能从主机上来操作,容器内只是读权限
- 通过 DockerFile 在构建镜像是指定 VOLUME
这里是一个匿名挂载
1 | FROM centos |
直接创建文件然后执行 docker build
可能会提示 error checking context:can't stat xxx
错误,这个提示基本是权限不够外加目录结构不对
解决:
1 | mkdir dockerfile |
创建好镜像后,执行
1 | docker run -it ee8398f6597c /bin/bash |
这里有 vol1 和 vol2 两个目录,然后去查看容器的元数据信息,会发现在 Mounts 这里已经把两个卷给挂在出去了
1 | "Mounts": [ |
6. 数据卷容器
容器数据卷是容器实现数据持久化的一种机制,数据卷容器是一种特殊的容器
这个主要是通过类似,用于容器间的数据同步,不是容器和主机之间。
1 | docker run -it --name docker01 imageID |
删除docker01的话,挂载的文件夹数据依然存在,本质是因为他们都挂载到主机的目录下,只要主机对应目录存在,数据就不会丢失
docker01相当于映射主机数据目录的地址(想成一个快捷方式),docker02拿到这个映射地址(快捷方式),放到自己的mount里面
7. DockerFile
通过 docker history imageID 可以查看一个镜像的构建过程
基础知识
- 每个关键字指令都必须是大写
- 按顺序从上到下执行
#
是注释- 每一个指令都会创建并提交一个新的镜像层。(docker run之后会创建一个可写容器)
指令说明
1 | FROM # 指定基础镜像 |
CMD ["ls", "-a"]
这时候的命令相当于 “ls -a”。只有当执行 docker run imageID
的时候才会执行最后一个CMD的命令。(docker run -it imageID
这时候是通过命令行交互,所以不会执行CMD)
如果执行 docker run imageID -l
想执行 ls -al
命令会报错,即CMD命令会被替代,不会追加。
换成 ENTRYPOINT
再执行 docker run imageID -l
就可以执行 ls -al
1 | COPY 主机路径/readme.txt 容器内路径 |
8. Docker 网络
每次启动一个程序,Linux 就会相应创建一个新的网卡地址 => veth-pair 一对虚拟设备接口,一端连着协议,一端彼此相连 => 每启动一个容器,docker会创建一对网卡连接地址,一端在主机,一端在容器
Linux 虚拟网络设别 veth-pair。三个经典实验,直接相连、通过 Bridge 相连和通过 OVS 相连
两个docker容器进行通信,需要先走到docker0,也就是类似于docker的路由器,通过广播或注册的方式,路由器会知道下一个容器的网络地址,然后转发请求连接。Docker0就相当于一个网桥的角色。
1 | docker run --link 通过link可以将两个容器网络连接起来,但是这是单向的,A link B, 只是A可以连接B,想B连接A的话还需要再配置B |
不推荐 --link
。因为 link 用的是 docker0,而docker0不支持容器名称映射,只能写死ip,如果牵扯到ip迁移,就需要重新配置一遍
1 | docker network ls # 查看所有的docker网络 |
网络模式
bridge: 桥接(docker默认)
1
docker run --net bridge # 不写的话也默认有这个bridge
none:不配置
host:和主机共享网络
container:容器网络连通(用得少,局限很大)
自定义创建网络
1 | docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet |
创建的容器基于各自不同的网络,可以保证同一类容器使用的是自己的网络ip。但是如果是两类容器想互相连接呢?网络打通。
1 | docker network connect [option] container network # 将一个容器连接到一个网关上,相当于一个容器两个ip地址。 |
9. Docker Compose
目的:定义运行多个容器,避免一个个的运维容器
步骤:
- DockerFile
- docker-compose.yml
- run
docker-compose up
配置项里有一个 deploy.replicas 可以设置副本,集群部署的时候使用这个参数
单机
10. Docker Swam
相当于是搭建一个小集群,有manager有worker。
集群
1 | docker swarm init # 初始化节点 |
11. Raft 协议
拜占庭将军问题:很久很久以前,拜占庭是东罗马帝国的首都。那个时候罗马帝国国土辽阔,为了防御目的,因此每个军队都分隔很远,将军与将军之间只能靠信使传递消息。在打仗的时候,拜占庭军队内所有将军必需达成一致的共识,才能更好地赢得胜利。但是,在军队内有可能存有叛徒,扰乱将军们的决定。这时候,在已知有成员不可靠的情况下,其余忠诚的将军需要在不受叛徒或间谍的影响下达成一致的协议。莱斯利·兰伯特( Leslie Lamport )通过这个比喻,表达了计算机网络中所存在的一致性问题。这个问题被称为拜占庭将军问题。
二将军问题:白军驻扎在沟渠里,蓝军则分散在沟渠两边。白军比任何一支蓝军都更为强大,但是蓝军若能同时合力进攻则能够打败白军。他们不能够远程的沟通,只能派遣通信兵穿过沟渠去通知对方蓝军协商进攻时间。是否存在一个能使蓝军必胜的通信协议,这就是两将军问题。
二将军问题探讨的是不可靠信道下两方的通信准确性问题,而拜占庭将军问题探讨的是多方通信结果一致性和决策正确性的问题。
准确性是指发送的是什么,接收的就是什么;一致性是在节点更多的情况下的准确性;正确性是无论收到的通信结果是否有干扰(不完全满足一致性),正常工作的节点能够做出正确的决策。
=> 二将军问题可以通过TCP3次握手来解决;拜占庭将军问题可以通过Raft/Paxos(谷哥Chubby)/ZAB(zookeeper)/PBFT(区块链)几种算法解决。另外 PS:raft,zab,paxos解决的是节点故障问题,而非拜占庭问题(节点故意欺骗)。PBFT解决了拜占庭问题。
Raft 协议的具体流程:
- 在最初,还没有一个主节点的时候,所有节点的身份都是Follower。每一个节点都有自己的计时器,当计时达到了超时时间(Election Timeout),该节点会转变为Candidate。
- 成为Candidate的节点,会首先给自己投票,然后向集群中其他所有的节点发起请求,要求大家都给自己投票。
- 其他收到投票请求且还未投票的Follower节点会向发起者投票,发起者收到反馈通知后,票数增加。
- 当得票数超过了集群节点数量的一半,该节点晋升为Leader节点。Leader节点会立刻向其他节点发出通知,告诉大家自己才是老大。收到通知的节点全部变为Follower,并且各自的计时器清零。
这里需要说明一点,每个节点的超时时间都是不一样的。比如A节点的超时时间是3秒,B节点的超时时间是5秒,C节点的超时时间是4秒。这样一来,A节点将会最先发起投票请求,而不是所有节点同时发起。为什么这样设计呢?设想如果所有节点同时发起投票,必然会导致大家的票数差不多,形成僵局,谁也当不成老大。
那么,成为Leader的节点是否就坐稳了老大的位置呢?并不是。Leader节点需要每隔一段时间向集群其他节点发送心跳通知,表明你们的老大还活着。一旦Leader节点挂掉,发不出通知,那么计时达到了超时时间的Follower节点会转变为Candidate节点,发起选主投票,周而复始……
数据同步的流程:
- 由客户端提交数据到Leader节点
- 由Leader节点把数据复制到集群内所有的Follower节点。如果一次复制失败,会不断进行重试
- Follower节点们接收到复制的数据,会反馈给Leader节点
- 如果Leader节点接收到超过半数的Follower反馈,表明复制成功。于是提交自己的数据,并通知客户端数据提交成功。
- 由Leader节点通知集群内所有的Follower节点提交数据,从而完成数据同步流程。
12. 容器服务
1 | docker service create -p ip:端口 --name my-service xxx |
灰度发布:金丝雀发布
命令 -> manager节点调用api -> schedule api -> worker节点创建并维护容器服务
镜像使用的os版本不统一,比如Linux基于的os不一样,版本不一样,命令缺失,阉割版和增肥版都不统一,数据文件可能都修改了位置等等