Docker学习笔记
一、安装
环境:Centos7
1、卸载旧版本
较旧的Docker版本称为docker
或docker-engine
。如果已安装这些程序,请卸载它们以及相关的依赖项。
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
2、使用存储库安装
注意:官方给了三种安装方式,这里我们选择最常用的存储库安装
在新主机上首次安装Docker Engine之前,需要设置Docker存储库。之后可以从存储库安装和更新Docker。
sudo yum install -y yum-utils
# 注意:此处是大坑,可以自行换成阿里的源
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
# 进行安装(如果不换源,速度会很慢)
sudo yum install docker-ce docker-ce-cli containerd.io
3、启动以及验证
systemctl start docker # 启动
systemctl status docker # 查看状态
docker version # 查看版本
docker run hello-world # 测试
4、设置加速器(阿里云)
注意:里面的地址每个人都有专属的地址。
打开阿里云官网->控制台->搜索容器镜像服务
->右下角找到镜像加速器
sudo mkdir -p /etc/docker
tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://isch1uhg.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
docker run hello-world
5、卸载
# 卸载Docker Engine,CLI和Containerd软件包
sudo yum remove docker-ce docker-ce-cli containerd.io
# 主机上的映像,容器,卷或自定义配置文件不会自动删除。要删除所有图像,容器和卷
sudo rm -rf /var/lib/docker
二、Docker三要素
1、仓库(Repository)
仓库:集中存放镜像的场所。
注意:仓库(Repository)和仓库注册服务器(Registry)是有区别的,仓库注册服务器往往存放着多个仓库,每个仓库中又包含多个镜像,每个镜像有不同的标签(tag)
仓库分为公开仓库(Public)和私有仓库(Private)两种形式。
最大的公开仓库是Docker Hub(https://hub.docker.com),存放了数量庞大的镜像供用户下载,国内的公开仓库包括阿里云、网易云等。
2、镜像(Image)
一个只读模板,用来创建Docker容器,一个镜像可以创建很多个容器。
容器与镜像的关系类似于面向对象编程中的对象和类
Docker | 面向对象 |
---|---|
容器 | 对象 |
镜像 | 类 |
3、容器 (Container)
独立运行的一个或一组应用。
容器使用镜像创建的运行实例。
它可以被启动、开始、停止、删除。每个容器都是相互隔离的,保证安全的平台。
可以把容器看作是一个简易版的Linux环境(包括root用户权限,进程空间,用户空间和网络空间等等)和运行在启动的应用程序。
容器的定义和镜像几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的最上面那一层是可读可写的。
三、简单了解底层原理
1、Docker是怎么工作的
Docker是一个Client-Server结构的系统,Docker守护进程运行在主机上,然后通过Socket连接从客户端访问,守护进程从客户端接收命令并管理运行在主机上的容器。
2、Docker为什么比VM快?
(1)Docker有着比虚拟机更少的抽象层,由于Docker不需要Hypervisor实现硬件资源虚拟化,运行在Docker容器上的程序直接使用的都是实际物理机的硬件资源,因此在CPU、内存利用率上Docker将会在效率上有明显的优势。
(2)Docker利用的是宿主机的内核,而不需要Guest OS。因此当新建一个容器时,Docker不需要和虚拟机一样重叠加载一个操作系统内核,从而避免引寻、加载操作系统内核,返回比较费时费资源的过程。当新建一个虚拟机时,虚拟机软件需要加载Guest OS,这个新建过程是分钟级别的,而Docker由于直接利用宿主机的操作系统,因此新建一个Docker容器只需几秒钟。
Docker容器 | 虚拟机(VM) | |
---|---|---|
操作系统 | 与宿主机共享OS | 宿主机OS上运行虚拟机OS |
存储大小 | 镜像小,便于存储与传输 | 镜像庞大(vmdk,vdi等) |
运行性能 | 几乎无额外性能损失 | 操作系统额外的CPU、内存消耗 |
移植性 | 轻便、灵活,适应于Linux | 笨重,与虚拟化技术耦合度高 |
硬件亲和性 | 面向软件开发者 | 面向硬件运维者 |
四、相关命令
1、帮助命令
docker version # 查看docker版本信息
docker info # 详细说明
docker --help # 帮助命令
2、镜像命令
(1)列出本地镜像
docker images [OPTIONS] [REPOSITORY[:TAG]]
# OPTIONS说明:
-a: 列出本地所有的镜像(含中间映像层)
-q: 只显示镜像ID
--digests: 显示镜像的摘要信息
--no-trunc: 显示完整的镜像信息
# 各个选项说明:
# REPOSITORY:表示镜像的仓库源
# TAG:镜像的标签
# IMAGE ID:镜像ID
# CREATED:镜像创建时间
# SIZE:镜像大小
同一仓库源可以有多个TAG,代表这个仓库源的不同版本,我们使用REPOSITORY:TAG来定义不同的镜像,如果不指定一个镜像的版本标签,例如我们只使用ubuntu,docker将默认使用ubuntu:latest镜像。
(2)查找镜像
docker search [options] 某个xxx镜像名字 # 会在https://hub.docker.com上去查找
docker search mysql --filter=STARS=3000 # 搜索星数大于等于3000的镜像
# OPTIONS说明:
# --no-trunc: 显示完整的镜像描述
# -s:列出点赞数不小于指定值的镜像
# --automated: 只列出automated build类型的镜像
(3)获取镜像
docker pull 镜像名字[:TAG] # 如果不写TAG,则默认获取latest,此时从我们配置的阿里云上获取镜像
docker pull mysql # 获取最新版本的mysql
Using default tag: latest
latest: Pulling from library/mysql
852e50cd189d: Pull complete # 分卷下载
29969ddb0ffb: Pull complete
a43f41a44c48: Pull complete
5cdd802543a3: Pull complete
b79b040de953: Pull complete
938c64119969: Pull complete
7689ec51a0d9: Pull complete
a880ba7c411f: Pull complete
984f656ec6ca: Pull complete
9f497bce458a: Pull complete
b9940f97694b: Pull complete
2f069358dc96: Pull complete
Digest: sha256:4bb2e81a40e9d0d59bd8e3dc2ba5e1f2197696f6de39a91e90798dd27299b093
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest
# docker pull mysql 等价于docker pull docker.io/library/mysql:latest
docker pull mysql:5.7 # 下载指定版本的镜像
5.7: Pulling from library/mysql
852e50cd189d: Already exists # 联合文件系统,已经存在的不会去重复下载
29969ddb0ffb: Already exists
a43f41a44c48: Already exists
5cdd802543a3: Already exists
b79b040de953: Already exists
938c64119969: Already exists
7689ec51a0d9: Already exists
36bd6224d58f: Pull complete
cab9d3fa4c8c: Pull complete
1b741e1c47de: Pull complete
aac9d11987ac: Pull complete
Digest: sha256:8e2004f9fe43df06c3030090f593021a5f283d028b5ed5765cc24236c2c4d88e
Status: Downloaded newer image for mysql:5.7
docker.io/library/mysql:5.7
(4)删除镜像
# 删除单个镜像
docker rmi -f 镜像名/ID # 如果是镜像名,后面不带TAG,则默认删除latest
# 删除多个镜像
docker rmi -f 镜像名1 镜像名2 ...
docker rmi -f id1 id2 ... // 注意:两种方式不能混用
# 删除全部镜像
docker rmi -f $(docker images -aq)
3、容器命令
有镜像才能创建容器,这是根本前提(下载一个Centos镜像演示)
(1) 新建并启动容器(交互式)
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
docker run -it --name mycentos centos // 如果不指定别名,系统会自动分配
# OPTIONS说明:
# --name 容器新名字:为容器指定一个名称
# -d:后台运行容器,并返回容器ID,即启动守护式容器
# -i: 以交互模式运行容器,通常与-t同时使用
# -t: 为容器重新分配一个伪输入终端,通常与-i同时使用
# -P: 随机端口映射
# -p: 指定端口映射有以下四种格式
ip:hostPort:containerPort
ip::containerPort
hostPort:containerPort
containerPort
# 测试
[root@iz2zeaj5c9isqt1zj9elpbz ~]# docker run -it centos /bin/bash # 启动并进入容器
[root@783cb2f26230 /]# ls # 在容器内查看
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
[root@783cb2f26230 /]# exit # 退出容器
exit
[root@iz2zeaj5c9isqt1zj9elpbz ~]#
(2)列出当前所有正在运行的容器
docker ps [OPTIONS] # 不带OPTIONS,则只列出正在运行的容器
# OPTIONS说明(常用):
# -a:列出当前所有正在运行的容器以及历史上运行过的
# -l:显示最近创建的容器
# -n: 显示最近n个创建的容器
# -q: 静默模式,只显示容器编号
# --no-trunc:不截断输出
(3)退出容器
exit // 直接关闭并退出容器
重新打开一个终端,执行docker ps -l
,会返回刚才我们创建的容器信息,并且STATUS
会提示已经退出。
那么可不可以在交互式,不关闭容器的情况下,暂时退出,一会又可以回来呢?
Ctrl + P + Q
# 执行后,我们将退出容器,回到宿主机,使用docker ps -l,我们会发现这个刚才退出的容器STATUS是Up状态。
(4)启动容器
docker start [OPTIONS] CONTAINER [CONTAINER...]
# 同时可以启动多个容器,容器名和ID可以混用
# OPTION说明(常用):
# -i : 进入交互式,此时只能进入一个容器
进入上面我们退出但是依然存活的容器
docker start -i 186ae928f07c
(5)重启容器
docker restart [OPTIONS] CONTAINER [CONTAINER...]
# OPTIONS说明:
# -t :在杀死容器之前等待停止的时间(默认为10)
(6) 停止容器
docker stop [OPTIONS] CONTAINER [CONTAINER...]
# OPTIONS说明:
# -t :在杀死前等待停止的时间(默认为10)
docker kill [OPTIONS] CONTAINER [CONTAINER...] // 强制关闭(相当于拔电源)
# OPTIONS说明:
# -s: 发送到容器的信号(默认为“KILL”)
(7)删除容器
docker rm [OPTIONS] CONTAINER [CONTAINER...]
# OPTIONS说明:
# -f :强制删除,不管是否在运行
# 上面的命令可以删除一个或者多个,但是想全部删除所有的容器,那么我们要把全部的容器名或者ID都写一遍吗?
docker rm -f $(docker ps -aq) # 删除所有
docker ps -aq | xargs docker rm -f
(8) 启动守护式容器
docker run -d 容器名/容器ID
说明:我们使用docker ps -a
命令查看,会发现刚才启动的容器以及退出了,这是为什么呢?
很重要的要说明一点:Docker容器后台运行,必须要有一个前台进程,容器运行的命令如果不是那些一直挂起的命令(比如运行top,tail),就会自动退出。
这就是Docker的机制问题,比如我们现在运行WEB容器,以Nginx为例,正常情况下,我们配置启动服务只需要启动响应的service
即可,例如service nginx start
,但是这样做,Nginx为后台进程模式运行,导致Docker前台没有运行的应用。这样的容器后台启动后,会立即自杀因为它觉得它没事可做。所以最佳的解决方案是将要运行的程序以前台进程的形式运行。
那么如何让守护式容器不自动退出呢?我们可以执行一直挂起的命令。
docker run -d centos /bin/sh -c "while true;do echo hello Negan;sleep 2;done"
(9) 查看容器日志
docker logs [OPTIONS] CONTAINER
# OPTION说明:
# -t 加入时间戳
# -f 跟随最新的日志打印
# --tail 显示最后多少条
(10) 查看容器内的进程
docker top CONTAINER
(11) 查看容器内部细节
docker inspect CONTAINER
(12) 进入正在运行的容器并以命令行交互
exec
在容器中打开新的终端,并且可以启动新的进程
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
docker exec -it CONTAINER /bin/bash // 进入容器内部并交互
# 隔山打牛
docker exec -it 3d00a0a2877e ls -l /tmp
# 3d00a0a2877e是我上面跑的守护式容器
# 进入容器,执行ls -l /tmp 命令后将结果返回给我宿主机,而用户的界面还是停留在宿主机,没有进入容器内部
attach
直接进入容器启动命令的终端,不会启动新的进程
docker attach CONTAINER
# 注意,进入上面那个守护式容器后,我们会看到还是每隔两秒打印hello Negan,而且不能退出,只能重新打开一个终端,执行docker kill 命令
(13)容器与主机间的文件拷贝
docker cp CONTAINER:SRC_PATH DEST_PATH # 把容器内的文件拷贝到宿主机
docker cp 90bd03598dd4:123.txt ~
docker cp SRC_PATH CONTAINER:DEST_PATH # 把宿主机上的文件拷贝到容器
docker cp 12345.txt 90bd03598dd4:~
练习
练习一:Docker部署Nginx
# 查找一个nginx镜像
docker search nginx
# 下载镜像
docker pull nginx
# 启动
docker run -d --name nginx01 -p 3344:80 nginx # -p 3344(宿主机):80(容器端口)
# 测试(浏览器访问)
123.56.243.64:3344
五、可视化
1、portainer
Docker图形界面管理工具,提供一个后台面板供我们操作。
docker run -d -p 8088:9000 \
--restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true protainer/portainer
六、Docker镜像
1、镜像是什么
镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时、库、环境变量和配置文件。
所有应用直接打包docker镜像,就可以直接跑起来。
如何获取镜像
- 从远程操作下载
- 朋友拷贝
- 自己制作一个镜像DockerFile
2、镜像加载原理
(1)联合文件系统
UnionFS(联合文件系统):Union文件系统是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层一层叠加,同时可以将不同目录挂在到同一个虚拟文件系统下。联合文件系统是Docker镜像的基础,镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。
(2)Docker镜像加载原理
Docker的镜像实际上是由一层一层的文件系统组成,也就是上面所说的联合文件系统。
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等等。
对于一个精简的OS,rootfs可以很小,只需要包含最近本的命令,工具和程序库就可以,因为底层直接用Host的kernel,自己只需要提供rootfs就可以,由此可见对于不同的Linux发行版,bootfs基本是一致的,rootfs会有差别,因此不同的发行版可以公用bootfs。
3、分层理解
(1)分层的镜像
我们下载一个镜像,注意观察下载的日志输出,可以看到是一层一层在下载。
为什么Docker镜像采用这种分层的结构?
最大的好处,我觉得莫过于资源共享了,比如有多个镜像从相同的Base镜像构建而来,那么宿主机只需要在磁盘上保留一份Base镜像,同时内存中也只需要加载一份Base镜像,这样就可以为所有的容器服务了,而且镜像每一层都可以被共享。
(2)理解
所有的Docker镜像都始于一个基础镜像层,当进行修改或者增加新内容时,就会在当前镜像层之上,创建新的镜像层。假如基于Ubuntu Linux 16.04创建一个新的镜像,这就是新镜像的第一层;如果在该镜像中添加Python包,就会在基础镜像之上创建第二个镜像层,如果继续添加一个安全补丁,就会创建第三个镜像层。
再添加额外的镜像层同时,镜像始终保持是当前所有镜像的组合。
注意:Docker镜像都是只读的,当容器启动时,一个新的可写层被加载到镜像的顶部。
这一层就是我们通常说的容器层,容器之下的都叫镜像层。
4、commit
docker commit 提交容器成为一个新的镜像
docker commit -m="提交的描述信息" -a="作者" 容器id 目标镜像名:[TAG]
七、容器数据卷
如果数据在容器中,那么我们容器删除,数据就会丢失!
需求:数据需要持久化。
MySQL,容器删了,删库跑路?Docker容器中产生的数据,需要同步到本地。
这就是卷技术,目录的挂载,将我们容器内的目录,挂载到Linux上。
1、使用数据卷
- 直接使用命令来挂载
docker run -it -v 主机目录:容器内目录 # 双向绑定,一方改变另一方自动改变
docker run -it -v /home/ceshi:/home centos /bin/bash
docker inspect d2343e9d338a # 进行查看
/*
...
"Mounts": [ # 挂载 -v
{
"Type": "bind",
"Source": "/home/ceshi", # 主机内的路径
"Destination": "/home", # 容器内的路径
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
...
*/
2、实战:安装MySQL
思考:MySQL的数据持久化问题
docker run -d -p 13306:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7
3、具名挂载和匿名挂载
(1) 、匿名挂载
docker run -d -P --name nginx01 -v /etc/nginx nginx # 只指定了容器内的路径
docker volume ls # 查看所有volume的情况
DRIVER VOLUME NAME
local 55050407d8fd052403bbf6ee349aa6268c2ec5c1054dafa678ac5dd31c59217a # 匿名挂载
local fd074ffbcea60b7fe65025ebe146e0903e90d9df5122c1e8874130ced5311049
# 这种就是匿名挂载,我们在-v只写了容器内的路径,没有写容器外的路径。
(2)、具名挂载
docker run -d -P -v juming:/etc/nginx --name nginx02 nginx # 具名挂载
docker volume ls
DRIVER VOLUME NAME
local 55050407d8fd052403bbf6ee349aa6268c2ec5c1054dafa678ac5dd31c59217a
local fd074ffbcea60b7fe65025ebe146e0903e90d9df5122c1e8874130ced5311049
local juming # 我们上面起的名字
# 通过-v 卷名:容器内路径
# 所有的docker容器内的卷,没有指定目录的情况下,都在/var/lib/docker/volumes目录下。
docker inspect juming
[
{
"CreatedAt": "2020-12-09T00:22:54+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/juming/_data",
"Name": "juming",
"Options": null,
"Scope": "local"
}
]
我们通过具名挂载可以方便的找到我们挂载的卷,大多情况下我们都会使用具名挂载。
(3)、拓展
-v 容器内路径 # 匿名挂载
-v 卷名:容器内路径 # 具名挂载
-v /宿主机路径:容器内路径 # 指定路径挂载
# 通过 -V 容器内路径:ro rw改变读写权限
ro readonly # 只读
rw readwrite # 可读可写
# 一旦设置了只读,容器对我们挂载出来的内容就有限定了。文件只能通过宿主机来操作,容器无法操作。
docker run -d -P --name nginx01 -v juming:/etc/nginx:ro nginx
4、数据卷容器
容器间的信息同步。
docker run -it -v /home --name c1 centos /bin/bash # 启动c1,作为一个父容器(指定容器内挂载目录)
docker run -it --name c2 --volumes-from c1 centos /bin/bash # 启动c2,挂载c1,此时在两个容器中/home中的操作就会同步
# 下面的容器是我们自己创建的,可以参考下面dockerfile进行构建
docker run -it --name docker01 negan/centos # 启动一个容器作为父容器(被挂载的容器)
docker run -it --name docker02 --volumes-from docker01 negan/centos #启动容器2,挂载容器1
docker run -it --name docker03 --volumes-from docker02 negan/centos #启动容器3,挂载容器2
# 上面的容器3也可以直接挂载到容器1上,然后我们进入任意一个容器,进入挂载卷volume01/volume02,进行操作,容器间的数据会自动同步。
结论:
容器之间配置信息的传递,数据卷容器的声明周期一直持续到没有容器使用为止。
但是一旦持久到了本地,本地数据不会被删除。
八、DockerFile
1、初识DockerFile
DockerFile就是用来构造docker镜像的构建文件,命令参数脚本。通过这个脚本,可以生成镜像。镜像是一层一层的,脚本是一个个命令,每个命令就是一层。
# dockerfile1内容,所有命令都是大写
FROM centos # 基于centos
VOLUME ["volume01","volume02"] # 挂载数据卷(匿名挂载)
CMD echo "----end-----"
CMD /bin/bash
# 构建
docker build -f dockerfile1 -t negan/centos . # -f 指定路径 -t指定名字,不加tag默认最新
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM centos
---> 0d120b6ccaa8
Step 2/4 : VOLUME ["volume01","volume02"]
---> Running in 0cfe6b5be6bf
Removing intermediate container 0cfe6b5be6bf
---> 396a4a7cfe15
Step 3/4 : CMD echo "----end-----"
---> Running in fa535b5581fa
Removing intermediate container fa535b5581fa
---> 110d9f93f827
Step 4/4 : CMD /bin/bash
---> Running in 557a2bb87d97
Removing intermediate container 557a2bb87d97
---> c2c9b92d50ad
Successfully built c2c9b92d50ad
Successfully tagged negan/centos:latest
docker images # 查看
2、DokcerFile构建过程
(1)、基础知识
每个保留关键字(指令)都必须是大写字母
执行从上到下顺序执行
#
表示注释每一个指令都会创建提交一个新的镜像层,并提交
DockerFile是面向开发的,我们以后要发布项目,做镜像,就需要编写dockerfile文件。
Docker镜像逐渐成为企业交付的标准。
(2)、基本命令
FROM #这个镜像的妈妈是谁?(基础镜像,一切从这里开始构建)
MAINTAINER # 告诉别人谁负责养他?(镜像是谁写的,指定维护者信息,姓名+邮箱)
RUN # 你想让他干啥?(镜像构建的时候需要运行的命令)
ADD # 给他点创业资金(复制文件,会自动解压)
WORKDIR # 镜像的工作目录
VOLUME # 给他一个存放行李的地方(设置卷,容器内挂载到主机的目录,匿名挂载)
EXPOSE # 门牌号是多少?(指定对外端口)
CMD # 指定这个容器启动的时候要运行的命令,只有最后一个会生效,可被替代
ENTRYPOINT # 指定这个容器启动时候要运行的命令,可以追加命令
ONBUILD # 当构建一个被继承DockerFile,这时就会运行ONBUILD指令
COPY # 类似ADD,将我们文件拷贝到镜像中
ENV # 构建的时候设置环境变量
3、实战操作
(1)创建一个自己的CentOS
# vim Dockerfile
FROM centos
MAINTAINER Negan<huiyichanmian@yeah.net>
ENV MYPATH /usr/local
WORKDIR $MYPATH
RUN yum -y install vim
RUN yum -y install net-tools
EXPOSE 80
CMD echo $MYPATH
CMD echo "---end---"
CMD /bin/bash
# 构建
docker build -f Dockerfile -t negan/centos .
# 测试
docker run -it negan/centos
[root@ffae1f9eb97e local]# pwd
/usr/local # 进入的是我们在dockerfile设置的工作目录
# 查看镜像构建过程
docker history 镜像名/ID
(2)CMD与ENTRYPOINT区别
两者都是容器启动的时候要执行的命令,CMD命令只有最后一个会生效,后面不支持追加命令,会被替代。ENTRYPOINT不会被替代,可以追加命令。
CMD
# vim cmd
FROM centos
CMD ["ls","-a"]
# 构建
docker build -f cmd -t cmd_test .
# 运行,发现我们的ls -a命令生效
docker run cmd_test
.
..
.dockerenv
bin
dev
etc
......
# 追加命令运行
docker run cmd_test -l
# 抛出错误,不能追加命令,原来的ls -a命令被-l替换,但是-l不是一个有效的命令
docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"-l\": executable file not found in $PATH": unknown.
# 追加完整的命令
docker run cmd_test ls -al
total 56
drwxr-xr-x 1 root root 4096 Dec 10 14:36 .
drwxr-xr-x 1 root root 4096 Dec 10 14:36 ..
-rwxr-xr-x 1 root root 0 Dec 10 14:36 .dockerenv
lrwxrwxrwx 1 root root 7 Nov 3 15:22 bin -> usr/bin
drwxr-xr-x 5 root root 340 Dec 10 14:36 dev
......
ENTRYPOINT
# vim entrypoint
FROM centos
ENTRYPOINT ["ls","-a"]
# 构建
docker build -f entrypoint -t entrypoint_test .
# 运行
docker run entrypoint_test
.
..
.dockerenv
bin
dev
etc
# 追加命令运行
docker run entrypoint_test -l
total 56
drwxr-xr-x 1 root root 4096 Dec 10 14:41 .
drwxr-xr-x 1 root root 4096 Dec 10 14:41 ..
-rwxr-xr-x 1 root root 0 Dec 10 14:41 .dockerenv
lrwxrwxrwx 1 root root 7 Nov 3 15:22 bin -> usr/bin
drwxr-xr-x 5 root root 340 Dec 10 14:41 dev
drwxr-xr-x 1 root root 4096 Dec 10 14:41 etc
......
4、实战构建tomcat
(1)环境准备
ll
total 166472
-rw-r--r-- 1 root root 11437266 Dec 9 16:22 apache-tomcat-9.0.40.tar.gz
-rw-r--r-- 1 root root 641 Dec 10 23:26 Dockerfile
-rw-r--r-- 1 root root 159019376 Dec 9 17:39 jdk-8u11-linux-x64.tar.gz
-rw-r--r-- 1 root root 0 Dec 10 22:48 readme.txt
(2)构建镜像
# vim Dockerfile (Dockerfile是官方建议命名)
FROM centos
MAINTAINER Negan<huiyichanmian@yeah.net>
COPY readme.txt /usr/local/readme.txt
ADD jdk-8u271-linux-aarch64.tar.gz /usr/local/
ADD apache-tomcat-9.0.40.tar.gz /usr/local/
RUN yum -y install vim
ENV MYPATH /usr/local
WORKDIR $MYPATH
ENV JAVA_HOME /usr/local/jdk1.8.0_11
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.40
ENV CATALINA_BASH /usr/local/apache-tomcat-9.0.40
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin
EXPOSE 8080
CMD /usr/local/apache-tomcat-9.0.40/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.40/bin/logs/catalina.out
# 构建
docker build -t tomcat .
(3)启动容器
docker run -d -P --name tomcat01 -v /home/Negan/tomcat/test:/usr/local/apache-tomcat-9.0.40/webapps/test -v /home/Negan/tomcat/logs:/usr/local/apache-tomcat-9.0.40/logs tomcat
九、发布自己的镜像
1、docker hub
首先需要在DockerHub上注册自己的账号,并且确定这个账号可以登录。
在我们的服务器上进行登录,登录成功后提交我们的镜像。
# 登录
docker login [OPTIONS] [SERVER]
Log in to a Docker 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
# 登录成功后推送我们的镜像
docker push [OPTIONS] NAME[:TAG]
Push an image or a repository to a registry
Options:
--disable-content-trust Skip image signing (default true)
docker tag tomcat huiyichanmian/tomcat # 需要改名,推送改名后的(前面加自己的用户名)
docker push huiyichanmian/tomcat
2、阿里云
登录阿里云,找到容器镜像服务,使用镜像仓库。创建命名空间,创建镜像仓库。选择本地仓库。
具体阿里云上有特别详细的步骤,这里不再赘述了。
十、Docker网络
1、理解docker0
(1)查看本机网卡信息
ip addr
# 本地回环地址
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
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:0c:7b:cb brd ff:ff:ff:ff:ff:ff
inet 172.24.14.32/18 brd 172.24.63.255 scope global dynamic eth0
valid_lft 286793195sec preferred_lft 286793195sec
# docker0地址
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:5e:2b:4c:05 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
(2)查看容器网卡信息
我们获取一个tomcat镜像,用来测试。
docker run -d -P --name t1 tomcat
docker exec -it t1 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
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
# 我们发现容器启动时候会得到一个eth0@ifxxx的网卡,而且ip地址和上面的docker0里的是在同一网段。
233: eth0@if234: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
(3)、再次查看本地网卡信息
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
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:0c:7b:cb brd ff:ff:ff:ff:ff:ff
inet 172.24.14.32/18 brd 172.24.63.255 scope global dynamic eth0
valid_lft 286792020sec preferred_lft 286792020sec
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:5e:2b:4c:05 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
# 我们发现多了一条网卡信息,而且与容器里面的网卡有某种对应关系。(233,234)
234: veth284c2d9@if233: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
我们重复上面的操作,不难发现我们只要安装了docker,本地网卡里就会多一个docker0,而且我们每启动一个容器,docker就会给容器分配一个网卡,而且在本地也会多出一个网卡信息,与容器内的相对应。这就是veth_pair技术,就是一对虚拟设备接口,它们都是成对出现,一段连着协议,一段彼此相连。正因为有了这个特性,我们通常veth_pair充当了一个桥梁,连接各种虚拟网络设备。
所有的容器不指定网络的情况下,都是由docker0路由的,docker会给我们的容器分配一个默认的ip。
Docker使用的是Linux的桥接,宿主机中docker0是一个Docker容器的网桥。所有的网络接口都是虚拟的。
只要容器删除,对应的网桥也响应的被删除。
2、--link
问题:我们每次重启容器,容器的ip地址会变,而我们项目中一些配置使用的固定ip,这时候也需要做相应的改变,那么我们可不可以直接设置服务名呢,下次重启时候,配置直接找服务名。
我们启动两个tomcat,进行测试,互相ping其对应的名字,看是否能通?
docker exec -it t1 ping t2
ping: t2: Name or service not known
# 答案是肯定的,不能识别t2,如何解决?
# 我们使用--link进行连接
docker run -d -P --name t3 --link t2 tomcat
# 我们尝试使用t3来ping t2
docker exec -it t3 ping t2
# 我们发现竟然通了
PING t2 (172.17.0.3) 56(84) bytes of data.
64 bytes from t2 (172.17.0.3): icmp_seq=1 ttl=64 time=0.099 ms
64 bytes from t2 (172.17.0.3): icmp_seq=2 ttl=64 time=0.066 ms
......
那么--link做了什么呢?
docker exec -it t3 cat /etc/hosts # 我们来查看t3的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.3 t2 6bf3c12674c8 # 原因在这了,这里对t2做了标记,当ping t2时候,自动转到172.17.0.3
172.17.0.4 b6dae0572f93
3、自定义网络
(1)查看docker网络
docker network ls
NETWORK ID NAME DRIVER SCOPE
10684d1bfac9 bridge bridge local
19f4854793d7 host host local
afc0c673386f none null local
# bridge 桥接(docker默认)
# host 与主机共享
# none 不配置
(2)容器启动时的默认网络
docker0是我们默认的网络,不支持域名访问,可以使用--link打通。
# 一般我们启动容器是这样子,使用的是默认网络,而这个默认网络就是bridge,所以下面两条命令是相同的
docker run -d -P --name t1 tomcat
docker run -d -P --name t1 --net bridge tomcat
(3)创建网络
# --driver bridge 默认,可以不写
# --subnet 192.168.0.0/16 子网掩码
# --gateway 192.168.0.1 默认网关
docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
docker network ls
docker network ls
NETWORK ID NAME DRIVER SCOPE
10684d1bfac9 bridge bridge local
19f4854793d7 host host local
0e98462f3e8e mynet bridge local # 我们自己创建的网络
afc0c673386f none null local
ip addr
.....
# 我们自己创建的网络
239: br-0e98462f3e8e: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:b6:a7:b1:96 brd ff:ff:ff:ff:ff:ff
inet 192.168.0.1/16 brd 192.168.255.255 scope global br-0e98462f3e8e
valid_lft forever preferred_lft forever
.....
启动两个容器,使用我们自己创建的网络
docker run -P -d --name t1 --net mynet tomcat
docker run -P -d --name t2 --net mynet tomcat
# 查看我们自己创建的网络信息
docker network mynet inspect
# 我们发现我们刚启动的两个容器使用的是我们刚创建的网络
......
"Containers": {
"1993703e0d0234006e1f95e964344d5ce01c90fe114f58addbd426255f686382": {
"Name": "t2",
"EndpointID": "f814ccc94232e5bbc4aaed35022dde879743ad9ac3f370600fb1845a862ed3b0",
"MacAddress": "02:42:c0:a8:00:03",
"IPv4Address": "192.168.0.3/16",
"IPv6Address": ""
},
"8283df6e894eeee8742ca6341bf928df53bee482ab8a6de0a34db8c73fb2a5fb": {
"Name": "t1",
"EndpointID": "e462941f0103b99f696ebe2ab93c1bb7d1edfbf6d799aeaf9a32b4f0f2f08e01",
"MacAddress": "02:42:c0:a8:00:02",
"IPv4Address": "192.168.0.2/16",
"IPv6Address": ""
}
},
.......
那么使用自己创建的网络有什么好处呢?
我们回到我们以前的问题,就是域名ping不通的问题上。
docker exec -it t1 ping t2
PING t2 (192.168.0.3) 56(84) bytes of data.
64 bytes from t2.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.063 ms
......
docker exec -it t2 ping t1
PING t1 (192.168.0.2) 56(84) bytes of data.
64 bytes from t1.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.045 ms
......
我们发现域名可以ping通了,那么说明我们自定义的网络已经帮我们维护好了对应关系。
这样不同的集群使用不同的网络,也能保证集群的安全和健康。
4、网络连通
现在有这样一个需求,t1,t2使用的是我们自己定义的网络,t3,t4使用的默认的docker0网络。那么现在t3和t1或者t2能否通信呢?
我们知道,docker0的默认网关是172.17.0.1,mynet默认网关是192.168.0.1,他们直接属于不同的网段,不能进行通信。那么现在如何解决上面的问题呢?
那么mynet能不能给t3分配一个ip地址呢?如果能分配,那么问题就应该可以得到解决。
docker network connect [OPTIONS] NETWORK CONTAINER
Connect a container to a network
Options:
--alias strings Add network-scoped alias for the container
--driver-opt strings driver options for the network
--ip string IPv4 address (e.g., 172.30.100.104)
--ip6 string IPv6 address (e.g., 2001:db8::33)
--link list Add link to another container
--link-local-ip strings Add a link-local address for the container
# 一个容器两个ip地址
docker network connect mynet t3 # 将t3加入到mynet网络中
# 查看mynet信息
docker network inspect mynet
"Containers": {
......
"d8ecec77f7c1e6d26ad0fcf9107cf31bed4b6dd553321b737d14eb2b497794e0": {
"Name": "t3", # 我们发现了t3
"EndpointID": "8796d63c1dd1969549a2d1d46808981a2b0ad725745d794bd3b824f278cec28c",
"MacAddress": "02:42:c0:a8:00:04",
"IPv4Address": "192.168.0.4/16",
"IPv6Address": ""
}
},
......
这时候,t3就能和t1,t2进行通信了。
5、部署Redis集群
# 创建网络
docker network create redis --subnet 172.38.0.0/16
# 通过脚本创建六个redis配置
for port in $(seq 1 6); \
do \
mkdir -p /mydata/redis/node-${port}/conf
touch /mydata/redis/node-${port}/conf/redis.conf
cat << EOF >/mydata/redis/node-${port}/conf/redis.conf
port 6379
bind 0.0.0.0
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.38.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
appendonly yes
EOF
done
# 启动容器
vim redis.py
import os
for i in range(1, 7):
str = "docker run -p 637{}:6379 -p 1637{}:16379 --name redis-{} \
-v /mydata/redis/node-{}/data:/data \
-v /mydata/redis/node-{}/conf/redis.conf:/etc/redis/redis.conf \
-d --net redis --ip 172.38.0.1{} redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf".format(i,i,i,i,i,i)
os.system(str)
python reidis.py
# 创建集群
docker exec -it redis-1 /bash/sh # 进入redis-1容器
redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1
# 创建集群。。。。
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 172.38.0.15:6379 to 172.38.0.11:6379
Adding replica 172.38.0.16:6379 to 172.38.0.12:6379
Adding replica 172.38.0.14:6379 to 172.38.0.13:6379
M: 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49 172.38.0.11:6379
slots:[0-5460] (5461 slots) master
M: 9d1d33301aea7e4cc9eb41ec5404e2199258e94e 172.38.0.12:6379
slots:[5461-10922] (5462 slots) master
M: d63e90423a034f9c42e72cc562706919fd9fc418 172.38.0.13:6379
slots:[10923-16383] (5461 slots) master
S: a89026d4ea211d36ee04f2f3762c6e3cd9692a28 172.38.0.14:6379
replicates d63e90423a034f9c42e72cc562706919fd9fc418
S: bee27443cd5eb6f031115f19968625eb86c8440b 172.38.0.15:6379
replicates 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49
S: 53d6196c160385181ff23b15e7bda7d4387b2b17 172.38.0.16:6379
replicates 9d1d33301aea7e4cc9eb41ec5404e2199258e94e
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
....
>>> Performing Cluster Check (using node 172.38.0.11:6379)
M: 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49 172.38.0.11:6379
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: a89026d4ea211d36ee04f2f3762c6e3cd9692a28 172.38.0.14:6379
slots: (0 slots) slave
replicates d63e90423a034f9c42e72cc562706919fd9fc418
S: 53d6196c160385181ff23b15e7bda7d4387b2b17 172.38.0.16:6379
slots: (0 slots) slave
replicates 9d1d33301aea7e4cc9eb41ec5404e2199258e94e
M: 9d1d33301aea7e4cc9eb41ec5404e2199258e94e 172.38.0.12:6379
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: bee27443cd5eb6f031115f19968625eb86c8440b 172.38.0.15:6379
slots: (0 slots) slave
replicates 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49
M: d63e90423a034f9c42e72cc562706919fd9fc418 172.38.0.13:6379
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
# 进行测试
redis-cli -c
127.0.0.1:6379> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:160
cluster_stats_messages_pong_sent:164
cluster_stats_messages_sent:324
cluster_stats_messages_ping_received:159
cluster_stats_messages_pong_received:160
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:324
十一、Docker Compose
1、介绍
Compose是Docker官方开源的项目,需要安装。
Compose是用于定义和运行多容器Docker应用程序的工具。通过Compose,可以使用YAML文件来配置应用程序的服务。然后,使用一个命令,就可以从配置中创建并启动所有服务。
使用Compose基本上是一个三步过程:
- Dockerfile保证我们的项目在任何地方运行
- docker-compose文件
- 启动
2、快速开始
(1)安装
curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
docker-compose --version
# 安装成功
# docker-compose version 1.27.4, build 40524192
(2)使用
# 为项目创建目录
mkdir composetest
cd composetest
# 编写一段flask程序
vim app.py
import time
import redis
from flask import Flask
app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379) # 这里主机名直接用“redis”,而不是ip地址
def get_hit_count():
retries = 5
while True:
try:
return cache.incr('hits')
except redis.exceptions.ConnectionError as exc:
if retries == 0:
raise exc
retries -= 1
time.sleep(0.5)
@app.route('/')
def hello():
count = get_hit_count()
return 'Hello World! I have been seen {} times.\n'.format(count)
# 编写requirements.txt文件(不指定版本,下载最新)
flask
redis
# 编写Dockerfile文件
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run"]
# 编写docker-compose.yml文件
# 文件中定义了两个服务,web和redis,web是从Dockerfile开始构建,redis使用的是公共镜像
version: "3.9"
services:
web:
build: .
ports:
- "5000:5000"
volumes:
- .:/code
redis:
image: "redis:alpine"
# 运行
docker-compose up
3、搭建博客
# 创建并进入目录
mkdir wordpress && cd wordpress
# 编写docker-compose.yml文件来启动,并创建了一个单独Mysql实例,具有用于数据持久性的卷挂载
version: "3.3"
services:
db:
image: mysql:5.7
volumes:
- db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: somewordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
wordpress:
depends_on:
- db
image: wordpress:latest
ports:
- "8000:80"
restart: always
environment:
W0RDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
volumes:
db_data: {}
# 启动运行
docker-compose up -d
十二、Docker Swarm
1、环境准备
准备四台服务器。安装docker。
2、swarm集群搭建
docker swarm COMMAND
Commands:
ca Display and rotate the root CA
init Initialize a swarm # 初始化一个节点(管理节点)
join Join a swarm as a node and/or manager # 加入节点
join-token Manage join tokens # 通过节点token加入
leave Leave the swarm # 离开节点
unlock Unlock swarm
unlock-key Manage the unlock key
update Update the swarm #更新
# 首先我们初始化一个节点
docker swarm init --advertise-addr + 自己的ip(这里用内网地址,主要是省钱)
docker swarm init --advertise-addr 172.27.0.4
# 提示我们节点创建成功
Swarm initialized: current node (yamss133bil4gb59fyangtdmm) is now a manager.
To add a worker to this swarm, run the following command:
# 在其他机器上执行,加入这个节点
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-4yyba7377uz9mfabak6pwu4ci 172.27.0.4:2377
# 生成一个管理节点的token,
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
# 我们在机器2上行加入节点的命令
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-4yyba7377uz9mfabak6pwu4ci 172.27.0.4:2377
# 我们在机器1上查看节点信息
docker node ls
# 我们发现一个管理节点,一个工作节点,状态都是就绪状态
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
yamss133bil4gb59fyangtdmm * VM-0-4-centos Ready Active Leader 20.10.0
mfxdgj1pobj0idbl9cesm2xnp VM-0-7-centos Ready Active 20.10.0
# 现在将机器3也加入,同样是work节点
# 到此为止,现在只有机器4没有加入,这时候我们想把它设置成管理节点。
# 在机器1上执行生成管理节点的命令,在机器4上执行
docker swarm join-token manager
# 生成的命令在机器4上执行
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-82pamju7b37aq8e1dcf1xmmng 172.27.0.4:2377
# 此时机器4也是管理节点了
This node joined a swarm as a manager.
# 至此我们可以在机器1或者机器4上进行其他操作了。(只能在管理节点上操作)
3、Raft协议
在前面的步骤,我们已经完成了双主双从集群的搭建。
Raft协议:保证大多数节点存活才能使用。必须大于1,集群至少大于3台。
实验1
将机器1上的docker停止,宕机,现在集群中只有一个管理节点了。集群是否可用。
#我们在机器四上查看节点信息
docker node ls
# 发现我们的集群已经不能用了
Error response from daemon: rpc error: code = DeadlineExceeded desc = context deadline exceeded
# 我们将机器1上的docker重启,发现集群又能用了,但是此时机器1上的docker已经不是leader了,leader自动转给机器4了
实验2
我们将工作节点离开集群,查看集群信息
# 我们在机器2上执行
docker swarm leave
# 在机器一上查看节点信息
docker node ls
# 我们发现机器2状态是Down
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
yamss133bil4gb59fyangtdmm * VM-0-4-centos Ready Active Reachable 20.10.0
mfxdgj1pobj0idbl9cesm2xnp VM-0-7-centos Down Active 20.10.0
u3rlqynazrdiz6oaubnuuyqod VM-0-11-centos Ready Active 20.10.0
im6kk7qd2a3s9g98lydni6udi VM-0-13-centos Ready Active Leader 20.10.0
实验3
现在我们将机器2也设置成管理节点(此时集群有三个管理节点),随机down掉一个管理节点,集群是否正常运行。
# 在机器2上运行
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-82pamju7b37aq8e1dcf1xmmng 172.27.0.4:2377
# 将机器1的docker宕机
systemctl stop docker
# 在机器二上查看节点信息
docker node ls
# 集群正常,且可以看到机器1宕机了
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
yamss133bil4gb59fyangtdmm VM-0-4-centos Ready Active Unreachable 20.10.0
mfxdgj1pobj0idbl9cesm2xnp VM-0-7-centos Down Active 20.10.0
vdwcwr3v6qrn6da40zdrjkwmy * VM-0-7-centos Ready Active Reachable 20.10.0
u3rlqynazrdiz6oaubnuuyqod VM-0-11-centos Ready Active 20.10.0
im6kk7qd2a3s9g98lydni6udi VM-0-13-centos Ready Active Leader 20.10.0
4、弹性创建服务
docker service COMMAND
Commands:
create Create a new service # 创建一个服务
inspect Display detailed information on one or more services # 查看服务信息
logs Fetch the logs of a service or task # 日志
ls List services # 列表
ps List the tasks of one or more services # 查看我们的服务
rm Remove one or more services # 删除服务
rollback Revert changes to a service's configuration
scale Scale one or multiple replicated services # 动态扩缩容
update Update a service # 更新
docker service create -p 8888:80 --name n1 nginx # 创建一个服务,在集群中随机分配
kj0xokbxvf5uw91bswgp1cukf
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
# 给我们的服务增加三个副本
docker service update --replicas 3 n1
# 动态扩缩容
docker service scale n1=10 # 和上面updata一样,这个方便
docker ps # 查看
服务,集群中任意的节点都可以访问,服务可以有多个副本动态扩缩容实现高可用。
5、使用Docker stack部署博客
我们现在需要将上面的博客跑在我们的集群中,并且要开十个副本。
# 编辑docker-compose.yml文件
version: '3.3'
services:
db:
image: mysql:5.7
volumes:
- db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: somewordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
wordpress:
depends_on:
- db
image: wordpress:latest
deploy:
replicas: 10
ports:
- "8000:80"
restart: always
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
volumes:
db_data: {}
# 进行启动
docker stack deploy -c docker-compose.yml wordpress
# 查看
docker service ls