Docker学习笔记

一、安装

环境:Centos7

1、卸载旧版本

较旧的Docker版本称为dockerdocker-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容器的网桥。所有的网络接口都是虚拟的。

只要容器删除,对应的网桥也响应的被删除。

问题:我们每次重启容器,容器的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基本上是一个三步过程:

  1. Dockerfile保证我们的项目在任何地方运行
  2. docker-compose文件
  3. 启动

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
posted @ 2020-12-11 17:24  李大鹅  阅读(288)  评论(0编辑  收藏  举报