Docker 复杂应用安装

MySQL 主从复制

主从搭建步骤:

  • 新建主服务器容器实例 3307

1
2
3
4
5
6
7
BASH
docker run -p 3307:3306 --name mysql-master \
-v /mydata/mysql-master/log:/var/log/mysql \
-v /mydata/mysql-master/data:/var/lib/mysql \
-v /mydata/mysql-master/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:5.7
  1. 进入/mydata/mysql-master/conf 目录下新建 my.cnf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
BASH

[root@88231 conf]# vim my.cnf
[mysqld]
## 设置server_id,同一局域网中需要唯一
server_id=101
## 指定不需要同步的数据库名称
binlog-ignore-db=mysql
## 开启二进制日志功能
log-bin=mall-mysql-bin
## 设置二进制日志使用内存大小(事务)
binlog_cache_size=1M
## 设置使用的二进制日志格式(mixed,statement,row)
binlog_format=mixed
## 二进制日志过期清理时间。默认值为0,表示不自动清理。
expire_logs_days=7
## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。
## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致
slave_skip_errors=1062
  1. 修改完配置后重启 master 实例
1
2
3
AUTOIT
[root@88231 conf]# docker restart mysql-master
mysql-master
  1. 进入 mysql-master 容器
1
2
3
4
ELIXIR
[root@88231 conf]# docker exec -it mysql-master /bin/bash
root@c84fa378812d:/# mysql -uroot -p
Enter password:
  1. master 容器实例内创建数据同步用户
1
2
3
4
5
6
BASH
mysql> CREATE USER 'slave'@'%' IDENTIFIED BY '123456';
Query OK, 0 rows affected (0.01 sec)

mysql> GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'slave'@'%';
Query OK, 0 rows affected (0.01 sec)
  • 新建从服务器容器实例 3308

1
2
3
4
5
6
7
BASH
docker run -p 3308:3306 --name mysql-slave \
-v /mydata/mysql-slave/log:/var/log/mysql \
-v /mydata/mysql-slave/data:/var/lib/mysql \
-v /mydata/mysql-slave/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:5.7
  1. 进入/mydata/mysql-slave/conf 目录下新建 my.cnf
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
BASH

[root@88231 conf]# vim my.cnf
[mysqld]
## 设置server_id,同一局域网中需要唯一
server_id=102
## 指定不需要同步的数据库名称
binlog-ignore-db=mysql
## 开启二进制日志功能,以备Slave作为其它数据库实例的Master时使用
log-bin=mall-mysql-slave1-bin
## 设置二进制日志使用内存大小(事务)
binlog_cache_size=1M
## 设置使用的二进制日志格式(mixed,statement,row)
binlog_format=mixed
## 二进制日志过期清理时间。默认值为0,表示不自动清理。
expire_logs_days=7
## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。
## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致
slave_skip_errors=1062
## relay_log配置中继日志
relay_log=mall-mysql-relay-bin
## log_slave_updates表示slave将复制事件写进自己的二进制日志
log_slave_updates=1
## slave设置为只读(具有super权限的用户除外)
read_only=1
  1. 修改完配置后重启 slave 实例
1
2
3
BASH
[root@88231 conf]# docker restart mysql-slave
mysql-slave
  1. 在主数据库中查看主从同步状态
1
2
3
4
5
6
7
8
BASH
mysql> show master status;
+-----------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+-----------------------+----------+--------------+------------------+-------------------+
| mall-mysql-bin.000001 | 617 | | mysql | |
+-----------------------+----------+--------------+------------------+-------------------+
1 row in set (0.01 sec)
  1. 进入 mysql-slave 容器
1
2
3
4
BASH
[root@88231 conf]# docker exec -it mysql-slave /bin/bash
root@820edd47f326:/# mysql -uroot -p
Enter password:
  1. 在从数据库中配置主从复制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BASH

change master to master_host='宿主机ip', master_user='slave', master_password='123456', master_port=3307, master_log_file='mall-mysql-bin.000001', master_log_pos=617, master_connect_retry=30;

mysql> change master to master_host='192.168.88.231', master_user='slave', master_password='123456', master_port=3307, master_log_file='mall-mysql-bin.000001', master_log_pos=617, master_connect_retry=30;
Query OK, 0 rows affected, 2 warnings (0.15 sec)

#主从复制命令参数说明
master_host:主数据库的IP地址;
master_port:主数据库的运行端口;
master_user:在主数据库创建的用于同步数据的用户账号;
master_password:在主数据库创建的用于同步数据的用户密码;
master_log_file:指定从数据库要复制数据的日志文件,通过查看主数据的状态,获取File参数;
master_log_pos:指定从数据库从哪个位置开始复制数据,通过查看主数据的状态,获取Position参数;
master_connect_retry:连接失败重试的时间间隔,单位为秒。
  1. 在从数据库中查看主从同步状态
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
BASH

mysql> show slave status \G;
*************************** 1. row ***************************
Slave_IO_State:
Master_Host: 192.168.88.231
Master_User: slave
Master_Port: 3307
Connect_Retry: 30
Master_Log_File: mall-mysql-bin.000001
Read_Master_Log_Pos: 617
Relay_Log_File: mall-mysql-relay-bin.000001
Relay_Log_Pos: 4
Relay_Master_Log_File: mall-mysql-bin.000001
# NO -- 还没开始
Slave_IO_Running: No
Slave_SQL_Running: No
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 617
Relay_Log_Space: 154
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: NULL
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 0
Master_UUID:
Master_Info_File: /var/lib/mysql/master.info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State:
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 0
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
1 row in set (0.00 sec)
  1. 在从数据库中开启主从同步
1
2
3
BASH
mysql> start slave;
Query OK, 0 rows affected (0.00 sec)
  1. 查看从数据库状态发现已经同步
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
BASH

mysql> show slave status \G;
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.88.231
Master_User: slave
Master_Port: 3307
Connect_Retry: 30
Master_Log_File: mall-mysql-bin.000001
Read_Master_Log_Pos: 617
Relay_Log_File: mall-mysql-relay-bin.000002
Relay_Log_Pos: 325
Relay_Master_Log_File: mall-mysql-bin.000001
# Yes -- 已开始
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 617
Relay_Log_Space: 537
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 101
Master_UUID: 25cb5d93-e4df-11ec-86da-0242ac110003
Master_Info_File: /var/lib/mysql/master.info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 0
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
1 row in set (0.00 sec)
  1. 主从复制测试
1
2
3
ASCIIDOC
- 主机新建库-使用库-新建表-插入数据,ok
- 从机使用库-查看记录,ok

Redis 集群

搭建 Redis 集群

3 主 3 从 redis 集群扩缩容配置案例架构说明

https://www.processon.com/view/link/629e20255653bb03f2cc0a14

  1. 新建 6 个 docker 容器 redis 实例
1
2
3
4
5
6
7
8
9
10
11
12
BASH
docker run -d --name redis-node-1 --net host --privileged=true -v /data/redis/share/redis-node-1:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6381

docker run -d --name redis-node-2 --net host --privileged=true -v /data/redis/share/redis-node-2:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6382

docker run -d --name redis-node-3 --net host --privileged=true -v /data/redis/share/redis-node-3:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6383

docker run -d --name redis-node-4 --net host --privileged=true -v /data/redis/share/redis-node-4:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6384

docker run -d --name redis-node-5 --net host --privileged=true -v /data/redis/share/redis-node-5:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6385

docker run -d --name redis-node-6 --net host --privileged=true -v /data/redis/share/redis-node-6:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6386

如果运行成功,效果如下:
image-20220606234533562

命令分步解释:

  • docker run:创建并运行 docker 容器实例
  • –name redis-node-6:容器名字
  • –net host:使用宿主机的 IP 和端口,默认
  • –privileged=true:获取宿主机 root 用户权限
  • -v /data/redis/share/redis-node-6:/data:容器卷,宿主机地址:docker 内部地址
  • redis:6.0.8:redis 镜像和版本号
  • –cluster-enabled yes:开启 redis 集群
  • –appendonly yes:开启持久化
  • –port 6386:redis 端口号
  1. 进入容器 redis-node-1 并为 6 台机器构建集群关系
1
2
3
4
5
6
7
8
BASH
# 进入容器
[root@88231 ~]# docker exec -it redis-node-1 /bin/bash

# 构建主从关系
# 注意,进入docker容器后才能执行一下命令,且注意自己的真实IP地址
redis-cli --cluster create 192.168.88.231:6381 192.168.88.231:6382 192.168.88.231:6383 192.168.88.231:6384 192.168.88.231:6385 192.168.88.231:6386 --cluster-replicas 1
# --cluster-replicas 1 表示为每个master创建一个slave节点

image-20220606235150386

image-20220606235242475

image-20220606235332969

一切 OK 的话,3 主 3 从搞定

  1. 链接进入 6381 作为切入点,查看集群状态
1
2
3
4
5
ACCESSLOG
root@88231:/data# redis-cli -p 6381
127.0.0.1:6381> keys *
127.0.0.1:6381> cluster info
127.0.0.1:6381> cluster nodes

image-20220607000338016

image-20220607000524306

主从容错切换迁移

数据读写存储
  1. 启动 6 个 redis 构成的集群并通过 exec 进入
  2. 对 6381 新增两个 key
  3. 防止路由失效加参数 -c 并新增两个 key

image-20220609222228049
image-20220609222420357

  1. 查看集群信息
1
2
BASH
redis-cli --cluster check 192.168.88.231:6381

image-20220609222613143

容错切换迁移
  1. 主 6381 和从机切换,先停止主机 6381
  2. 6381 主机停了,对应的真实从机上位
  3. 6381 作为 1 号主机分配的从机以实际情况为准,具体是几号机器就是几号
  4. 再次查看集群信息

image-20220609223136321

==6381 宕机了,6385 上位成了新的 master==

备注:本次操作 6381 为主节点,对应的从节点是 6385,对应关系是随机的,每次操作以实际情况为准

  1. 启动 6381 节点
1
2
BASH
docker start redis-node-1

image-20220609224042839

  1. 再停 6385 节点
1
2
BASH
docker stop redis-node-5

image-20220609224750744

  1. 再启 6385 节点
1
2
BASH
docker start redis-node-5

==发现主从节点又恢复之前的状态了==

  1. 查看集群状态
1
2
3
4
SMALI
redis-cli --cluster check 自己IP:6381

可以看到主节点分配的

image-20220609230337692

主从扩容案例
  1. 新建 6387、6388 两个节点+新建后启动+查看是否是 8 节点
1
2
3
4
5
6
BASH
docker run -d --name redis-node-7 --net host --privileged=true -v /data/redis/share/redis-node-7:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6387

docker run -d --name redis-node-8 --net host --privileged=true -v /data/redis/share/redis-node-8:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6388

docker ps
  1. 进入 6387 容器实例内部
1
2
BASH
docker exec -it redis-node-7 /bin/bash
  1. 将新增的 6387 节点(空槽号)作为 master 节点加入集群
1
2
3
4
5
SMALI
redis-cli --cluster add-node 自己实际IP地址:6387 自己实际IP地址:6381
redis-cli --cluster add-node 192.168.88.231:6387 192.168.88.231:6381
6387 就是将要作为master新增节点
6381 就是原来集群节点里面的领路人,相当于6387拜拜6381的码头从而找到组织加入集群

image-20220609231034192

  1. 检查集群情况第 1 次
1
2
3
4
SMALI
redis-cli --cluster check 真实ip地址:6381

redis-cli --cluster check 192.168.88.231:6381

image-20220609231423755

  1. 重新分派槽号
1
2
3
4
5
6
STATA
重新分派槽号

命令:redis-cli --cluster reshard IP地址:端口号

redis-cli --cluster reshard 192.168.88.231:6381

image-20220609232741435

  1. 检查集群情况第 2 次
1
2
3
4
BASH
redis-cli --cluster check 真实ip地址:6381

redis-cli --cluster check 192.168.88.231:6381

image-20220609233201196

==槽号分派说明==

为什么 6387 是 3 个新的区间,以前的还是连续?

重新分配成本太高,所以前 3 家各自匀出来一部分,从 6381/6382/6383 三个旧节点分别匀出 1364 个坑位给新节点 6387

image-20220609233346240

  1. 为主节点 6387 分配从节点 6388
1
2
3
4
5
SMALI
命令:redis-cli --cluster add-node ip:新slave端口 ip:新master端口 --cluster-slave --cluster-master-id 新主机节点ID

redis-cli --cluster add-node 192.168.88.231:6388 192.168.88.231:6387 --cluster-slave --cluster-master-id 7206137ce4e66c0464fa0fa00472202ce5b16792
-------这个是6387的编号,按照自己实际情况

image-20220609233720722

  1. 检查集群第 3 次
1
2
3
4
SMALI
redis-cli --cluster check 192.168.88.231:6382

4 主 4 从

image-20220609233902550

主从缩容案例

目的:6387 和 6388 下线

  1. 检查集群情况 - 获得 6388 的节点 ID
1
2
BASH
redis-cli --cluster check 192.168.88.231:6382

image-20220610223609176

  1. 将 6388 删除 从集群中将 4 号从节点 6388 删除
1
2
3
4
BASH
命令:redis-cli --cluster del-node ip:从机端口 从机6388节点ID

redis-cli --cluster del-node 192.168.88.231:6388 1fedf6a6f9acfbdba6951a532cd2d68e4546898e

image-20220610223709085

1
2
BASH
redis-cli --cluster check 192.168.88.231:6382

==检查一下发现,6388 被删除了,只剩下七台机器了。==

image-20220610223900954

  1. 将 6387 的槽号清空,重新分配,本例将清出来的槽号都给 6381
1
2
BASH
redis-cli --cluster reshard 192.168.88.231:6381

image-20220610224116496

image-20220610224657149

将 6387 节点的槽号都分配给 6381

  1. 检查集群情况
1
2
3
4
BASH
redis-cli --cluster check 192.168.88.231 6382

4096 个槽位都指给 6381,它变成了 8192 个槽位,相当于全部都给 6381了

image-20220610225027291

  1. 删除 6387 节点
1
2
3
4
BASH
命令:redis-cli --cluster del-node ip:端口 6387节点ID

redis-cli --cluster del-node 192.168.88.231:6387 7206137ce4e66c0464fa0fa00472202ce5b16792

image-20220610225144956

  1. 再次检查集群情况
1
2
3
4
BASH
redis-cli --cluster check 192.168.88.231 6382

恢复之前的 3 主 3 从,缩容成功!

image-20220610225344237

DockerFile

Dockerfile 介绍

Dockerfile 是用来构建 Docker 镜像的文本文件,是由一条条构建镜像所需的指令和参数构成的脚本。

官网:https://docs.docker.com/engine/reference/builder/

构建步骤:

  1. 编写一个 Dockerfile 文件
  2. docker bulid 构建为一个镜像
  3. docker run 运行镜像
  4. docker push 发布镜像(DockerHub. 阿里云镜像仓库)

image-20220611202748238

Dockerfile 构建过程

Dockerfile 基础知识

  1. 每个保留关键字(指令)都==必须是大写字母==且后面要跟随至少一个参数
  2. 指令按照从上到下,顺序执行
  3. # 表示注释
  4. 每条指令都会创建一个新的镜像层并对镜像进行提交

image-20211230224659329

Docker 执行 Dockerfile 的大致流程

  1. docker 从基础镜像运行一个容器
  2. 执行一条指令并对容器做出修改
  3. docker 再基于刚提交的镜像运行一个新容器
  4. 执行 Dockerfile 中的下一条指令知道所有指令都执行完成

总结

从应用软件的角度看,DockerfileDocker镜像Docker容器分别代表软件的三个不同阶段:

  • Dockerfile是软件的原材料
  • Docker镜像是软件的交付品
  • Docker容器则可以认为是软件镜像的运行态,也即依照镜像运行的容器实例

==Dockerfile 面向开发,Docker 镜像成为交付标准,Docker 容器则涉及部署与运维,三者缺一不可,合力充当 Docker 体系的基石==

image-20220611204149366

  1. Dockerfile,需要定义一个 Dockerfile,Dockerfile 定义了进程需要的一切东西。Dockerfile 涉及的内容包括执行代码或者是文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版、服务进程和内核进程(当应用进程需要和系统服务和内核进程打交道,这时需要考虑如何设计 namespace 的权限控制)等等;
  2. Docker 镜像,在用 Dockerfile 定义一个文件之后,docker build 时会产生一个 Docker 镜像,当运行 Docker 镜像时会真正开始提供服务;
  3. Docker 容器,容器是直接提供服务的。

DockerFile 的保留字指令

  • FROM:基础镜像,当前新镜像是基于哪个镜像的,指定一个已经存在的镜像作为模板,第一条必须是 FROM

  • MAINTAINER:镜像维护者的姓名和邮箱地址

  • RUN:容器构建时需要运行的命令,包含两种格式:

    • shell 格式:

    image-20220611204952312

    • exec 格式:

    image-20220611205003786

    • RUN 是在 docker build 时运行
  • EXPOSE:当前容器对外暴露出的端口

  • WORKDIR:指定在创建容器后,终端默认登陆的进来工作目录,一个落脚点

  • USER:指定该镜像以什么样的用户去执行,如果都不指定,默认是 root

  • ENV:用来在构建镜像过程中设置环境变量

    • ```

      ENV MY_PATH /usr/mytest

      这个环境变量可以在后续的任何 RUN 指令中使用,这就如同在命令前面指定了环境变量前缀一样;

      也可以在其它指令中直接使用这些环境变量,比如:WORKDIR $MY_PATH

      1
      DOCKERFILE
      • ADD:将宿主机目录下的文件拷贝进镜像且会自动处理 URL 和解压 tar 压缩包

      • COPY:类似 ADD,拷贝文件和目录到镜像中

        • # 将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置
          
          COPY src dest
          COPY ["src", "dest"]
          
          # <src源路径>:源文件或者源目录
          # <dest目标路径>:容器内的指定路径,该路径不用事先建好,路径不存在的话,会自动创建。
          
          1

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11

      - `VOLUME`:容器数据卷,用于数据保存和持久化工作

      - `CMD`:指定容器启动后的要干的事情

      - ![image-20220611210408046](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220611210408.png)

      - **注意:Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效,CMD 会被 docker run 之后的参数替换**

      - 它和前面

      RUN

      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

      命令的区别:

      - `CMD` 是在 `docker run` 时运行
      - `RUN` 是在 `docker build` 时运行

      - `ENTRYPOINT`:也是用来指定一个容器启动时要运行的命令

      - 类似于 CMD 指令,**但是 ENTRYPOINT 不会被 docker run 后面的命令覆盖**, 而且这些命令行参数**会被当作参数送给 ENTRYPOINT 指令指定的程序**
      - `ENTRYPOINT ["<executeable>","<param1>","<param2>"...]`
      - ENTRYPOINT 可以和 CMD 一起用,一般是**变参**才会使用 CMD,这里的 CMD 等于是在给 ENTRYPOINT 传参。当指定了 ENTRYPOINT 后,CMD 的含义就发生了变化,不再是直接运行其命令而是将 CMD 的内容作为参数传递给 ENTRYPOINT 指令,他两个组合会变成 `<ENTRYPOINT> "<CMD>"`
      - **优点**:在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数
      - **注意**:如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效

      - 小总结

      - ![image-20220618230538863](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220618230538.png)

      ### 实战案例

      自定义镜像 ==> `mycentosjava8`

      **要求:**

      - Centos7 镜像具备 vim+ifconfig+jdk8
      - 准备 jdk8 的安装包(`jdk-8u251-linux-x64.tar.gz`)

      1. **准备编写 Dockerfile 文件**

      在 `/home` 目录下建一个 `myfile` 文件夹,并将 `jdk8` 的安装包放进去:

      ![image-20220619202508648](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220619202515.png)

      `Dockerfile` 文件内容如下:

DOCKERFILE

FROM centos:centos7
MAINTAINER Kyire6<kyire666.outlook.com>

ENV MYPATH /usr/local
WORKDIR $MYPATH

#安装vim编辑器
RUN yum -y install vim
#安装ifconfig命令查看网络IP
RUN yum -y install net-tools
#安装java8及lib库
RUN yum -y install glibc.i686
RUN mkdir /usr/local/java
#ADD 是相对路径jar,把jdk-8u251-linux-x64.tar.gz添加到容器中,安装包必须要和Dockerfile文件在同一位置
ADD jdk-8u251-linux-x64.tar.gz /usr/local/java/
#配置java环境变量
ENV JAVA_HOME /usr/local/java/jdk1.8.0_251
ENV JRE_HOME $JAVA_HOME/jre
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
ENV PATH $JAVA_HOME/bin:$PATH

EXPOSE 80

CMD echo $MYPATH
CMD echo “success————–ok”
CMD /bin/bash

1
2
3

1. **执行构建命令**

BASH
docker build -t 新镜像名:TAG .

例如

docker build -t centosjava8:1.0 .

1
2
3
4
5
6
7
8
9

> **注意:命令要在 `Dockerfile` 的同级目录下执行,不要忘了命令结尾的 `.`**

![image-20220619203258616](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220619203258.png)

![image-20220619204718396](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220619204718.png)

1. **运行容器**

BASH
docker run -it centosjava8:1.0 /bin/bash

1
2
3
4
5
6
7
8
9

![image-20220619212100997](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220619212101.png)

### 虚悬镜像

虚悬镜像就是仓库名、标签都是 `<none>` 的镜像,也称为 `dangling image`

**用 Dockerfile 生成一个**

DOCKERFILE

编写 Dockerfile 文件

vim Dockerfile
from ubuntu
CMD echo ‘action is success’

build 镜像

docker build .

1
2
3
4
5

![image-20220619214708171](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220619214708.png)

**查看虚悬镜像**

BASH
docker images -f dangling=true

1
2
3
4
5
6
7

![image-20220619214819128](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220619214819.png)

**删除所有虚悬镜像**

虚悬镜像已经失去存在价值,可以删除

BASH
docker image prune

1
2
3
4
5
6
7
8
9
10
11
12
13

![image-20220619215102621](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220619215102.png)

### 发布自己的镜像

> DockerHub

1. 地址 https://hub.docker.com/ 注册自己的账号!

2. 确保这个账号可以登录

3. 在服务器上提交自己的镜像

SHELL

[root@ouwen ~]# docker login –help

Usage: 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
[root@ouwen ~]# docker login -u ouwen666
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

1
2
3

4. 使用 `docker login` 登录之后就可以提交镜像了


SHELL

1. 使用 docker tag 命令修改镜像版本

[root@ouwen ~]# docker tag 352abc3918b1 ouwen666/tomcat:1.0

2. 使用 docker push 命令提交镜像到 DockerHub

[root@ouwen ~]# docker push ouwen666/tomcat:1.0

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

![image-20220103135111272](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220103135111.png)

> 发现:提交的时候也是按照镜像的层级来的!

> 阿里云镜像

1. 登录阿里云

2. 找到容器镜像服务

3. 创建镜像仓库

![image-20220103140835560](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220103140835.png)

4. 浏览仓库信息

![image-20220103140926596](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220103140926.png)

### 总结

![image-20220103142038290](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220103142038.png)

## Docker 网络

### `docker0` 网卡

docker 服务启动后,会产生一个名为 `docker0` 的虚拟网桥,使用 `ip addr` 查看本机 ip

![image-20220103235821684](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220104001129.png)

### docker 网络常用命令

#### All 命令


BASH
docker network –help

1
2
3
4
5

![image-20220625214854077](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220625214901.png)

#### 查看网络

BASH
docker network ls

1
2
3
4
5

![image-20220625215035376](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220625215035.png)

#### 查看网络源数据

BASH
docker network inspect xxx

1
2
3
4
5

![image-20220626002156711](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626002156.png)

#### 创建网络

BASH
docker network create xxx

1
2
3

#### 删除网络

BASH
docker network rm xxx

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

![image-20220626002407749](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626002407.png)

> **docker 网络能干嘛?**
>
> - 容器间的互联和通信以及端口映射
> - 容器 IP 变动时可以通过服务名直接进行网络通信,进而不受到影响

### 网络模式

![image-20220626002547570](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626002547.png)

- bridge 模式:使用 –network bridge 指定,默认使用 docker0
- host 模式:使用 –network host 指定
- none 模式:使用 –network none 指定
- container 模式:使用 –network container:NAME 或者容器 ID 指定

**容器实例内默认网络 IP 生产规则**

1. 先启动两个 Ubuntu 容器实例

![image-20220626002940194](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626002940.png)

1. `docker inspect 容器 ID or 容器名字`

![image-20220626003252589](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626003252.png)

1. 关闭 u2 实例,新建 u3,查看 IP 变化

![image-20220626003453087](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626003453.png)

> 结论:
>
> **docker 容器内部的 IP 是有可能会发生变化的**

#### bridge

Docker 服务默认会创建一个 docker0 网桥(其上有一个 docker0 内部接口),该桥接网络的名称为 docker0,它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。Docker 默认指定了 docker0 接口 的 IP 地址和子网掩码,让主机和容器之间可以通过网桥相互通信。

GRADLE

查看 bridge 网络的详细信息,并通过 grep 获取名称项

docker network inspect bridge | grep name

1
2
3

![image-20220626003931893](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626003931.png)

1C
ifconfig | grep docker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

![image-20220626004014292](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626004014.png)

> **说明:**
>
> 1. Docker 使用 Linux 桥接,在宿主机虚拟一个 Docker 容器网桥(docker0),Docker 启动一个容器时会根据 Docker 网桥的网段分配给容器一个 IP 地址,称为 Container-IP,同时 Docker 网桥是每个容器的默认网关。因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的 Container-IP 直接通信
> 2. docker run 的时候,没有指定 network 的话默认使用的网桥模式就是 bridge,使用的就是 docker0。在宿主机 ifconfig,就可以看到 docker0 和自己 create 的 network(后面讲)eth0,eth1,eth2……代表网卡一,网卡二,网卡三……,lo 代表 127.0.0.1,即 localhost,inet addr 用来表示网卡的 IP 地址
> 3. 网桥 docker0 创建一对对等虚拟设备接口一个叫 veth,另一个叫 eth0,成对匹配
> 1. 整个宿主机的网桥模式都是 docker0,类似一个交换机有一堆接口,每个接口叫 veth,在本地主机和容器内分别创建一个虚拟接口,并让他们彼此联通(这样一对接口叫 veth pair)
> 2. 每个容器实例内部也有一块网卡,每个接口叫 eth0
> 3. docker0 上面的每个 veth 匹配某个容器实例内部的 eth0,两两配对,一一匹配
>
> **通过上述,将宿主机上的所有容器都连接到这个内部网络上,两个容器在同一个网络下,会从这个网关下各自拿到分配的 ip,此时两个容器的网络是互通的。**

![image-20220626004158705](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626004158.png)
**两两匹配验证**

BASH
docker run -d -p 8081:8080 –name tomcat81 billygoo/tomcat8-jdk8

docker run -d -p 8082:8080 –name tomcat82 billygoo/tomcat8-jdk8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

![image-20220626004536998](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626004537.png)

![image-20220626004743381](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626004743.png)

#### host

直接使用宿主机的 IP 地址与外界进行通信,不再需要额外进行 NAT 转换。

> **说明:**
>
> 容器将不会获得一个独立的 Network Namespace, 而是和宿主机共用一个 Network Namespace。容器将不会虚拟出自己的网卡而是使用宿主机的 IP 和端口。

![image-20220626004852511](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626004852.png)

**验证**

BASH
docker run -d -p 8083:8080 –network host –name tomcat83 billygoo/tomcat8-jdk8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

![image-20220626005212574](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626005212.png)
**问题:**

docker 容器启动时出现了警告

**原因:**

docker 启动时指定 –network=host 或 -net=host,如果还指定了 -p 映射端口,那这个时候就会有此警告,并且通过-p 设置的参数将不会起到任何作用,端口号会以主机端口号为主,重复时则递增。

**解决:**

解决的办法就是使用 docker 的其他网络模式,例如 –network=bridge,这样就可以解决问题,或者直接无视…

**正确做法:**

BASH
docker run -d –network host –name tomcat83 billygoo/tomcat8-jdk8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

这样就不会出现之前的警告了,查看容器实例内部:

![image-20220626005512017](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626005512.png)

> 没有设置-p 的端口映射了,如何访问启动的 tomcat83?
>
> http://宿主机 IP:8080/
>
> **在 CentOS 里面用默认的火狐浏览器访问容器内的 tomcat83 看到访问成功,因为此时容器的 IP 借用主机的,所以容器共享宿主机网络 IP,这样的好处是外部主机与容器可以直接通信。**

#### none

在 none 模式下,并不为 Docker 容器进行任何网络配置。 也就是说,这个 Docker 容器没有网卡、IP、路由等信息,只有一个 lo,需要我们自己为 Docker 容器添加网卡、配置 IP 等。禁用网络功能,只有 lo 标识(就是 127.0.0.1 表示本地回环)

**验证**

BASH
docker run -d -p 8084:8080 –network none –name tomcat84 billygoo/tomcat8-jdk8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

进入容器内部查看

![image-20220626005846474](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626005846.png)
在容器外部查看

![image-20220626005935898](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626005936.png)

#### container

新建的容器和已经存在的一个容器共享一个网络 IP 配置而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。

![image-20220626010017092](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626010017.png)
**验证**

BASH
docker run -d -p 8085:8080 –name tomcat85 billygoo/tomcat8-jdk8

docker run -d -p 8086:8080 –network container:tomcat85 –name tomcat86 billygoo/tomcat8-jdk8

1
2
3
4
5
6
7
8
9
10
11
12
13

运行结果:

![image-20220626010148299](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626010148.png)

相当于 tomcat86 和 tomcat85 公用同一个 ip 同一个端口,导致端口冲突!

**换一个镜像进行验证**

> Alpine 操作系统是一个面向安全的轻型 Linux 发行版
>
> Alpine Linux 是一款独立的、非商业的通用 Linux 发行版,专为追求安全性、简单性和资源效率的用户而设计。 可能很多人没听说过这个 Linux 发行版本,但是经常用 Docker 的朋友可能都用过,因为他小,简单,安全而著称,所以作为基础镜像是非常好的一个选择,可谓是麻雀虽小但五脏俱全,镜像非常小巧,不到 6M 的大小,所以特别适合容器打包。

BASH
docker run -it –name alpine1 alpine /bin/sh

docker run -it –network container:alpine1 –name alpine2 alpine /bin/sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

![image-20220626010511330](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626010511.png)

假如此时关闭 alpine1,再看看 alpin2

![image-20220626010625716](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626010625.png)

发现 `107: eth0@if108` 已经消失

![image-20220626010751877](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626010751.png)

### 自定义网络

#### 过时的 link

![image-20220626010828576](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626010828.png)

#### 使用自定义网络的好处

**before**

BASH
docker run -d -p 8081:8080 –name tomcat81 billygoo/tomcat8-jdk8
docker run -d -p 8082:8080 –name tomcat82 billygoo/tomcat8-jdk8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

启动成功使用 docker exec 进入到容器内部

**按照 IP 地址 ping 是 OK 的**

![image-20220626011951406](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626011951.png)

但是无法按照服务名 ping

![image-20220626012135798](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626012135.png)

**after**

自定义桥接网络,自定义网络默认使用的就是桥接网络 – bridge

1. 新建自定义网络

![image-20220626012308361](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626012308.png)

1. 新建容器并加入上一步新建的自定义网络

BASH
docker run -d -p 8081:8080 –network my_network –name tomcat81 billygoo/tomcat8-jdk8

docker run -d -p 8082:8080 –network my_network –name tomcat82 billygoo/tomcat8-jdk8

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. 互相 ping 测试

![image-20220626012536459](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626012536.png)

> **结论**
>
> **\*自定义网络本身就维护好了主机名和 ip 的对应关系(ip 和域名都能通)\***

## Docker 平台架构图解

### 整体说明

从其架构和运行流程来看,Docker 是一个 C/S 模式的架构,后端是一个松耦合架构,众多模块各司其职。

Docker 运行的基本流程为:

1. 用户是使用 Docker Client 与 Docker Daemon 建立通信,并发送请求给后者。
2. Docker Daemon 作为 Docker 架构中的主体部分,首先提供 Docker Server 的功能使其可以接受 Docker Client 的请求。
3. Docker Engine 执行 Docker 内部的一系列工作,每一项工作都是以一个 Job 的形式的存在。
4. Job 的运行过程中,当需要容器镜像时,则从 Docker Registry 中下载镜像,并通过镜像管理驱动 Graph driver 将下载镜像以 Graph 的形式存储。
5. 当需要为 Docker 创建网络环境时,通过网络管理驱动 Network driver 创建并配置 Docker 容器网络环境。
6. 当需要限制 Docker 容器运行资源或执行用户指令等操作时,则通过 Execdriver 来完成。
7. Libcontainer 是一项独立的容器管理包,Network driver 以及 Exec driver 都是通过 Libcontainer 来实现具体对容器进行的操作。

### 整体架构

![image-20220626012732507](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220626012732.png)

## Docker Compose

> 官方文档:https://docs.docker.com/compose/

### 什么是 Docker Compose

`Docker Compose` 是一个用于定义和运行多容器 `Docker` 应用程序的工具。使用 `Compose`,您可以使用 `YAML` 文件来配置应用程序的服务。然后,使用单个命令,从配置创建并启动所有服务

使用 `Docker Compose` 基本上有以下三步:

1. 使用 定义应用的环境,以便可以在任何位置重现它。`Dockerfile`
2. 定义构成应用的服务,以便它们可以在隔离的环境中一起运行。`docker-compose.yml`
3. 运行[Docker Compose](https://docs.docker.com/compose/cli-command/)将启动并运行整个应用。您也可以使用 docker-compose 二进制文件运行。` docker compose up``docker-compose up `

`docker-compose.yml` 示例:

YAML

version: “3.9” # optional since v1.27.0
services:
web:
build: .
ports:
- “8000:5000”
volumes:
- .:/code
- logvolume01:/var/log
links:
- redis
redis:
image: redis
volumes:
logvolume01: {}

1
2
3
4
5

### 安装 Docker Compose

1. 下载

BASH

官网地址

sudo curl -L “https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)” -o /usr/local/bin/docker-compose

国内镜像

sudo curl -L “https://get.daocloud.io/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)” -o /usr/local/bin/docker-compose

1
2
3
4
5

![image-20220404124103270](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220404124103.png)

1. 给 `docker-compose` 文件授可执行权限

BASH
sudo chmod +x /usr/local/bin/docker-compose

1
2
3

1. 测试安装是否成功

BASH
docker-compose –version

1
2
3

1. 卸载 `docker-compose`

AWK
rm /usr/local/bin/docker-compose

1
2
3
4
5
6
7

![image-20220404124418121](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220404124418.png)

### Compose 核心概念

**一文件**

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

**两要素**

- 服务(service)

一个个应用容器实例,比如订单微服务、库存微服务、mysql 容器、nginx 容器或者 redis 容器

- 工程(project)

由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义

### Compose 使用的三个步骤

1. 编写 Dockerfile 定义各个微服务应用并构建出对应的镜像文件
2. 使用 docker-compose.yml 定义一个完整业务单元,安排好整体应用中的各个容器服务
3. 最后,执行 docker-compose up 命令 来启动并运行整个应用程序,完成一键部署上线

### Compose 常用命令

**查看帮助信息**

BASH
docker-compose -h|help

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

**命令选项**

- -f, –file FILE 指定使用的 Compose 模板文件,默认为 docker-compose.yml,可以多次指定;
- -p, –project-name NAME 指定项目名称,默认将使用所在目录名称作为项目名;
- –x-networking 使用 Docker 的可拔插网络后端特性;
- –x-network-driver DRIVER 指定网络后端的驱动,默认为 bridge;
- –verbose 输出更多调试信息;
- -v, –version 打印版本并退出;
- -H, –host HOST,远程操作 docker,被操作的 docker 需要开放 2375 端口

**常用命令**

- **up**:启动所有 docker-compose 服务

BASH
docker-compose up

启动所有docker-compose服务并后台运行

docker-compose up -d

1
2
3

- **down**:停止并删除容器、网络、卷

BASH
docker-compose down

1
2
3

- **exec**:进入容器实例内部

BASH
docker-compose exec docker-compose.yml文件中写的服务id /bin/bash

1
2
3

- **ps**:展示当前 docker-compose 编排过的运行的所有容器

BASH
docker-compose ps

1
2
3

- **top**:展示当前 docker-compose 编排过的容器进程

BASH
docker-compose top

1
2
3

- **logs**:查看容器输出日志

BASH
docker-compose logs yml里面的服务id

1
2
3

- **build**:构建或者重新构建服务

BASH
docker-compose build

1
2
3

- **start**:启动服务

BASH
docker-compose start yml里面的服务id

1
2
3

- **stop**:停止服务

BASH
docker-compose stop yml里面的服务id

1
2
3

- **restart**:重启服务

BASH
docker-compose restart yml里面的服务id

1
2
3

- **config**:检查配置

BASH
docker-compose config

有问题才输出

docker-compose config -q
1
2
3

- **scale**:设置指定服务运行容器的个数,以 `service=num` 形式指定


BASH
docker-compose scale yml里面的服务id=实例个数
1
2
3
4
5
6
7
8
9

### docker-compose.yml 文件规则

官网地址:https://docs.docker.com/compose/compose-file/compose-file-v3/

- **version**:指定 docker-compose.yml 文件的写法格式
- **services**:服务,多个容器集合
- **build**:配置构建时,Compose 会利用它自动构建镜像,该值可以是一个路径,也可以是一个对象,用于指定 Dockerfile 参数


YAML
build: ./dir

build:
context: ./dir
dockerfile: Dockerfile
args:
buildno: 1
1
2
3

- **command**:覆盖容器启动后默认执行的命令


YAML
command: bundle exec thin -p 3000

command: [bundle,exec,thin,-p,3000]
1
2
3

- **dns**:配置 dns 服务器,可以是一个值或列表


YAML
dns: 8.8.8.8

dns:
- 8.8.8.8
- 9.9.9.9
1
2
3

- **dns_search**:配置 DNS 搜索域,可以是一个值或列表


YAML
dns_search: example.com

dns_search:
- dc1.example.com
- dc2.example.com
1
2
3

- **environment**:环境变量配置,可以用数组或字典两种方式


YAML
environment:
RACK_ENV: development
SHOW: ‘ture’

environment:
- RACK_ENV=development
- SHOW=ture
1
2
3

- **env_file**:从文件中获取环境变量,可以指定一个文件路径或路径列表,其优先级低于 environment 指定的环境变量


YAML
env_file: .env

env_file:
- ./common.env

1
2
3

- **expose**:暴露端口,只将端口暴露给连接的服务,而不暴露给主机

YAML
expose:
- “3000”
- “8000”

1
2
3

- **image**:指定服务所使用的镜像

YAML
image: java

1
2
3

- **network_mode**:设置网络模式

YAML
network_mode: “bridge”
network_mode: “host”
network_mode: “none”
network_mode: “service:[service name]”
network_mode: “container:[container name/id]”

1
2
3

- **ports**:对外暴露的端口定义,和 expose 对应

YAML
ports: # 暴露端口信息 - “宿主机端口:容器暴露端口”

  • “8763:8763”
  • “8763:8763”
    1
    2
    3

    - **links**:将指定容器连接到当前连接,可以设置别名,避免 ip 方式导致的容器重启动态改变的无法连接情况

    YAML
    links: # 指定服务名称:别名
    • docker-compose-eureka-server:compose-eureka
      1
      2
      3

      - **volumes**:卷挂载路径

      YAML
      volumes:
    • /lib
    • /var
      1
      2
      3
      4
      5
      6
      7

      ## Docker 轻量级可视化工具 Portainer

      Portainer 是一款轻量级的应用,它提供了图形化界面,用于方便地管理 Docker 环境,包括单机环境和集群环境。

      docker 命令安装

      BASH

官方版本

docker run -d -p 8000:8000 -p 9010:9000 –name portainer –restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer

中文版本

docker run -d –restart=always –name=”portainer” -p 9010:9000 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data 6053537/portainer-ce

EE 商业版本(public 为中文汉化资源)

docker run -d -p 8000:8000 -p 9010:9000 -p 9443:9443 –name=portainer –restart=always -v /var/run/docker.sock:/var/run/docker.sock -v /u01/portainer/portainer_data:/data -v /u01/portainer/public:/public portainer/portainer-ee:2.10.0

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211

第一次登录需要创建 admin 用户,访问地址:`xxx.xxx.xxx.xxx:9010`

![image-20211218203604987](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20211218203605.png)

选择 local 选项卡后本地 docker 详细信息展示

![image-20211218203652466](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20211218203652.png)
管理本地环境

![image-20211218203840461](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20211218203840.png)

## Docker Swarm

> 官方文档:https://docs.docker.com/engine/swarm/

### 什么是 Docker Swarm

Swarm 是 [Docker](https://www.docker.com/) 官方提供的一款集群管理工具,其主要作用是把若干台 Docker 主机抽象为一个整体,并且通过一个入口统一管理这些 Docker 主机上的各种 Docker 资源。

Docker Swarm 和 Docker Compose 一样,都是 Docker 官方容器编排项目,但不同的是,Docker Compose 是一个在单个服务器或主机上创建多个容器的工具,而 Docker Swarm 则可以在多个服务器或主机上创建容器集群服务。

### Docker Swarm 架构图

![image-20220405181654649](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220405181654.png)

上图可以看出,Swarm 是典型的 master-slave 结构,通过发现服务来选举 manager。manager 是中心管理节点,各个 node 上运行 agent 接受 manager 的统一管理,集群会自动通过 Raft 协议分布式选举出 manager 节点,无需额外的发现服务支持,避免了单点的瓶颈问题,同时也内置了 DNS 的负载均衡和对外部负载均衡机制的集成支持。

### Swarm 关键概念

- **Swarm(群)**

嵌入在 Docker 引擎中的集群管理和编排功能是使用[swarmkit 构建的](https://github.com/docker/swarmkit/)。`Swarmkit` 是一个单独的项目,它实现了 Docker 的编排层并直接在 Docker 中使用。

一个 swarm 由多个 Docker 主机组成,它们以**swarm 模式**运行并充当管理器(管理成员资格和委托)和工作人员(运行 [swarm 服务](https://docs.docker.com/engine/swarm/key-concepts/#services-and-tasks))。给定的 Docker 主机可以是管理员、工作人员或同时执行这两种角色。创建服务时,您需要定义其最佳状态(副本数量、可用的网络和存储资源、服务向外界公开的端口等等)。Docker 致力于维护所需的状态。例如,如果一个工作节点变得不可用,Docker 会将该节点的任务安排在其他节点上。*任务*是一个 正在运行的容器,它是一个集群服务的一部分,由一个集群管理器管理,而不是一个独立的容器。

swarm 服务相对于独立容器的主要优势之一是您可以修改服务的配置,包括它所连接的网络和卷,而无需手动重新启动服务。Docker 将更新配置,停止具有过期配置的服务任务,并创建与所需配置匹配的新任务。

当 Docker 在 swarm 模式下运行时,您仍然可以在任何参与 swarm 的 Docker 主机以及 swarm 服务上运行独立容器。独立容器和 swarm 服务之间的一个关键区别在于,只有 `swarm manager` 可以管理 swarm,而独立容器可以在任何守护进程上启动。Docker 守护进程可以作为管理者、工作者或两者兼而有之。

就像您可以使用[Docker Compose](https://docs.docker.com/compose/)定义和运行容器一样,您可以定义和运行[Swarm 服务](https://docs.docker.com/engine/swarm/services/)堆栈。

- **Node(节点)**

**节点**是参与 swarm 的 Docker 引擎的一个实例。您也可以将其视为 Docker 节点。您可以在单个物理计算机或云服务器上运行一个或多个节点,但生产群部署通常包括分布在多个物理和云计算机上的 Docker 节点。

要将应用程序部署到 swarm,您需要向 **管理器节点**提交服务定义。管理节点将称为 [任务](https://docs.docker.com/engine/swarm/key-concepts/#services-and-tasks)的工作单元分派给工作节点。

管理器节点还执行维护集群所需状态所需的编排和集群管理功能。管理器节点选举一个领导者来执行编排任务。

**工作节点**接收并执行从管理节点分派的任务。默认情况下,管理器节点也将服务作为工作节点运行,但您可以将它们配置为专门运行管理器任务并成为仅管理器节点。代理在每个工作节点上运行并报告分配给它的任务。`worker` 节点将其分配的任务的当前状态通知给 `manager` 节点,以便 `manager` 可以保持每个 `worker` 的期望状态。

- **Service(服务)**

**服务**是要在管理节点或工作节点上执行的任务的定义。它是 swarm 系统的**中心结构**,也是用户与 swarm 交互的主要根源。

创建服务时,您需要指定要使用的容器映像以及在运行的容器中执行的命令。

在**复制服务**模型中,群管理器根据您在所需状态中设置的规模在节点之间分配特定数量的副本任务。

对于**全局服务**,swarm 在集群中的每个可用节点上为服务运行一个任务。

- **Task(任务)**

一个**任务**携带一个 Docker 容器和在容器内运行的命令。它是 swarm 的**原子调度单元**。Manager 节点根据服务规模中设置的副本数将任务分配给工作节点。一旦任务被分配给一个节点,它就不能移动到另一个节点。它只能在分配的节点上运行或失败。

- **Load balancing(负载均衡)**

swarm 管理器使用**入口负载平衡**将您希望在外部提供给 swarm 的服务公开。swarm manager 可以自动为服务分配一个**PublishedPort**,或者您可以为该服务配置一个 PublishedPort。您可以指定任何未使用的端口。如果不指定端口,swarm manager 会为服务分配一个 `30000-32767` 范围内的端口。

外部组件(例如云负载均衡器)可以访问集群中任何节点的 PublishedPort 上的服务,无论该节点当前是否正在运行该服务的任务。swarm 中的所有节点将入口连接路由到正在运行的任务实例。

Swarm 模式有一个内部 `DNS` 组件,它自动为 swarm 中的每个服务分配一个 DNS 条目。swarm manager 使用**内部负载平衡**根据服务的 DNS 名称在集群内的服务之间分配请求。

### Swarm 工作原理

#### 节点如何工作

Swarm 集群由管理节点(Manager)和工作节点(Work)构成。

![image-20220716220722605](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220716220722.png)

##### 管理节点

Manager 节点处理集群管理任务:

- 维护集群状态
- 调度服务
- 服务群模式[HTTP API 端点](https://docs.docker.com/engine/api/)

使用[Raft](https://raft.github.io/raft.pdf)实现,管理器维护整个 swarm 和在其上运行的所有服务的一致内部状态。出于测试目的,可以使用单个管理器运行 swarm。如果单管理器集群中的管理器发生故障,您的服务将继续运行,但您需要创建一个新集群才能恢复。

为了利用 swarm 模式的容错特性,Docker 建议您根据组织的高可用性要求实现奇数个节点。当您有多个管理器时,您可以从管理器节点的故障中恢复而无需停机。

- 一个三管理器群最多可以容忍一名管理器的损失。
- 一个五管理器群最多可以同时丢失两个管理器节点。
- 一个`N`管理器集群最多可以容忍丢失 `(N-1)/2`管理器。
- Docker 建议一个 swarm 最多使用七个管理器节点。

> **重要提示**:添加更多管理器并不意味着增加可扩展性或提高性能。一般来说,情况正好相反。

##### 工作节点

工作节点也是 Docker 引擎的实例,其唯一目的是执行容器。Worker 节点不参与 Raft 分布式状态,不做调度决策,也不服务于 swarm 模式的 HTTP API。

您可以创建一个由一个管理器节点组成的集群,但如果没有至少一个管理器节点,您就不能拥有一个工作程序节点。默认情况下,所有 manager 也是 worker。在单个管理节点集群中,您可以运行类似 `docker service create` 的命令,并且调度程序将所有任务放在本地引擎上。

要防止调度程序将任务放置在多节点集群中的管理器节点上,请将管理器节点的可用性设置为 `Drain`。调度器优雅地停止 `Drain` 模式节点上的任务,并在一个 `Active` 节点上调度任务。调度程序不会将新任务分配给 `Drain` 可用的节点。

请参阅[docker node update](https://docs.docker.com/engine/reference/commandline/node_update/) 命令行参考以了解如何更改节点可用性。

#### 服务如何运作

要在 Docker 引擎处于 swarm 模式时部署应用程序映像,您需要创建一个服务。通常,服务是某个更大应用程序上下文中微服务的映像。服务的示例可能包括 HTTP 服务器、数据库或您希望在分布式环境中运行的任何其他类型的可执行程序。

创建服务时,您需要指定要使用的容器映像以及在运行的容器中执行的命令。您还可以定义服务的选项,包括:

- swarm 使服务在 swarm 外部可用的端口
- 服务连接到集群中的其他服务的覆盖网络
- CPU 和内存限制和预留
- 滚动更新策略
- 在 swarm 中运行的图像的副本数

##### 服务、任务和容器

当您将服务部署到 swarm 时,swarm manager 接受您的服务定义作为服务的所需状态。然后,它将集群中的节点上的服务安排为一个或多个副本任务。这些任务在 swarm 中的节点上彼此独立运行。

例如,假设您想在 nginx 的三个实例之间进行负载平衡。下图显示了具有三个副本的 nginx 服务。nginx 的三个实例中的每一个都是 swarm 中的一个任务。

![image-20220717111642619](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220717111642.png)

容器是一个独立的进程。在 swarm 模式模型中,每个任务只调用一个容器。任务类似于调度程序放置容器的“槽”。一旦容器处于活动状态,调度程序就会识别出任务处于运行状态。如果容器未通过健康检查或终止,则任务终止。

##### 任务和调度

任务是 swarm 中调度的原子单元。当您通过创建或更新服务来声明所需的服务状态时,编排器通过调度任务来实现所需的状态。例如,您定义了一个服务,该服务指示协调器始终保持三个 HTTP 侦听器实例运行。编排器通过创建三个任务来响应。每个任务都是调度程序通过生成容器来填充的槽。容器是任务的实例化。如果 HTTP 侦听器任务随后未能通过其健康检查或崩溃,编排器将创建一个新的副本任务来生成一个新容器。

任务是一种单向机制。它通过一系列状态单调地进行:分配、准备、运行等。如果任务失败,编排器将删除任务及其容器,然后根据服务指定的所需状态创建一个新任务来替换它。

Docker swarm 模式的底层逻辑是一个通用的调度器和编排器。服务和任务抽象本身并不知道它们实现的容器。假设您可以实现其他类型的任务,例如虚拟机任务或非容器化流程任务。调度器和编排器不知道任务的类型。但是,当前版本的 Docker 只支持容器任务。

下图显示了 swarm 模式如何接受服务创建请求并将任务调度到工作节点。

![image-20220717231629076](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220717231629.png)

##### 待办服务(pending)

一个服务可以这样配置,使得当前在 swarm 中的任何节点都不能运行它的任务。在这种情况下,服务保持状态`pending`。以下是服务可能保持状态的几个示例`pending`。

> **注意**:如果您的唯一目的是阻止部署服务,请将服务缩放到 0,而不是尝试将其配置为保留在`pending`.

- 如果所有节点都已暂停或耗尽,并且您创建了一项服务,则该服务将处于挂起状态,直到节点可用为止。实际上,第一个可用的节点会获得所有任务,因此这在生产环境中不是一件好事。
- 您可以为服务保留特定数量的内存。如果 swarm 中没有节点具有所需的内存量,则服务将保持挂起状态,直到可以运行其任务的节点可用。如果您指定一个非常大的值,例如 500 GB,则该任务将永远保持挂起状态,除非您确实有一个可以满足它的节点。
- 您可以对服务施加放置约束,并且这些约束可能无法在给定时间得到遵守。

这种行为说明您的任务的要求和配置与当前的 swarm 状态并没有紧密联系。作为 swarm 的管理员,您声明了您的 swarm 所需的状态,并且管理器与 swarm 中的节点一起创建该状态。您不需要对 swarm 上的任务进行微观管理。

##### 部署模式

有两种类型的服务部署,复制的(`replicated`)和全局的(`global`)。

对于复制服务,您指定要运行的相同任务的数量。例如,您决定部署具有三个副本的 HTTP 服务,每个副本提供相同的内容。

全局服务是在每个节点上运行一个任务的服务。没有预先指定的任务数量。每次将节点添加到 swarm 时,编排器都会创建一个任务,调度器会将任务分配给新节点。全局服务的良好候选者是监控代理、防病毒扫描程序或您希望在集群中的每个节点上运行的其他类型的容器。

下图显示了黄色的三服务副本和灰色的全局服务。

![image-20220717232121078](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220717232121.png)

#### Swarm 任务状态

Docker 允许创建可以启动任务的服务。服务是对所需状态的描述,而任务完成工作。工作按以下顺序安排在 swarm 节点上:

1. 通过使用创建服务`docker service create`。
2. 请求转到 Docker 管理器节点。
3. Docker 管理器节点安排服务在特定节点上运行。
4. 每个服务可以启动多个任务。
5. 每个任务都有一个生命周期,其状态包括 `NEW`、`PENDING` 和 `COMPLETE`。

任务是运行一次即可完成的执行单元。当一个任务停止时,它不会再次执行,但一个新的任务可能会取代它。

任务通过多个状态前进,直到它们完成或失败。任务在 `NEW` 状态中初始化。任务通过多个状态向前推进,并且其状态不会后退。例如,任务永远不会从 `COMPLETE` 到 `RUNNING`。

任务按以下顺序通过状态:

| 任务状态 | 描述 |
| ----------- | ---------------------------------------------------------------------- |
| `NEW` | 任务已初始化。 |
| `PENDING` | 分配了任务的资源。 |
| `ASSIGNED` | Docker 将任务分配给节点。 |
| `ACCEPTED` | 该任务已被工作节点接受。如果工作节点拒绝任务,则状态更改为`REJECTED`。 |
| `PREPARING` | Docker 正在准备任务。 |
| `STARTING` | Docker 正在启动任务。 |
| `RUNNING` | 任务正在执行。 |
| `COMPLETE` | 任务退出,没有错误代码。 |
| `FAILED` | 任务以错误代码退出。 |
| `SHUTDOWN` | Docker 请求关闭任务。 |
| `REJECTED` | 工作节点拒绝了该任务。 |
| `ORPHANED` | 节点关闭时间过长。 |
| `REMOVE` | 该任务不是终端,但相关服务已被删除或缩小。 |

> **查看任务状态**
>
> 运行 docker service ps 以获取任务的状态。该 `CURRENT STATE` 字段显示任务的状态以及它在那里的时间。

### 常用命令

#### 创建 Swarm

1. 运行以下命令来创建一个新的 swarm:

BASH
$ docker swarm init –advertise-addr

1
2
3

1. 例如:

BASH
$ docker swarm init –advertise-addr 192.168.88.230
Swarm initialized: current node (uo7m7w01d5wfdjt0qn9m3aau4) is now a manager.

To add a worker to this swarm, run the following command:

docker swarm join --token SWMTKN-1-63d99h3uln0k8qfne7w39qt6f1tv4yddvevyzwh3uzhhfl73wf-3abelxhlyvhm54klavpbr06r5 192.168.88.230:2377

To add a manager to this swarm, run ‘docker swarm join-token manager’ and follow the instructions.

1
2
3
4
5
6
7

> 该 `--advertise-addr` 标志将管理节点配置为将其地址发布为 `192.168.88.230`。 swarm 中的其他节点必须能够访问该 IP 地址的 manager。
>
> 输出包括将新节点加入 swarm 的命令。根据 `--token` 标志的值,节点将作为 manager 或 worker 加入。

1. 运行`docker info`查看 swarm 的当前状态:

BASH

$ docker info
Containers: 3
Running: 3
Paused: 0
Stopped: 0
Images: 4
…snip…
Swarm: active
NodeID: uo7m7w01d5wfdjt0qn9m3aau4
Is Manager: true
ClusterID: ym3kr78wgx258lawn3iclgxwq
Managers: 1
Nodes: 3
…snip…

1
2
3

1. 运行`docker node ls`命令查看节点信息:

BASH
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
uo7m7w01d5wfdjt0qn9m3aau4 * 88230 Ready Active Leader 20.10.13
p4tlkfxa4d2ne1diq0mtlifs9 88233 Ready Active 20.10.17
oawv9cs597brq8h5hzis3ynlc 88237 Ready Active 20.10.17

1
2
3
4
5
6
7

#### 将节点添加到 Swarm

当你创建了一个带有管理节点的 swarm 时,你就可以添加工作节点了。

1. 运行创建 Swarm 中 `docker swarm init` 步骤生成的命令,以创建一个加入现有 swarm 的工作节点:

BASH
$ docker swarm join –token SWMTKN-1-63d99h3uln0k8qfne7w39qt6f1tv4yddvevyzwh3uzhhfl73wf-3abelxhlyvhm54klavpbr06r5 192.168.88.230:2377

1
2
3

1. 如果没有可用的命令,可以在管理节点上运行以下命令来生成工作节点的加入命令:

BASH
$ docker swarm join-token worker
To add a worker to this swarm, run the following command:

docker swarm join --token SWMTKN-1-63d99h3uln0k8qfne7w39qt6f1tv4yddvevyzwh3uzhhfl73wf-3abelxhlyvhm54klavpbr06r5 192.168.88.230:2377
1
2
3

1. 生成管理节点的加入命令:

BASH
$ docker swarm join-token manager
To add a manager to this swarm, run the following command:

docker swarm join --token SWMTKN-1-63d99h3uln0k8qfne7w39qt6f1tv4yddvevyzwh3uzhhfl73wf-0so9mhkgy8btr579ho4pfza9n 192.168.88.230:2377
1
2
3
4
5

#### 删除节点

先在要删除的节点上运行:

BASH
docker swarm leave

1
2
3

在 `manage` 节点上运行:

BASH
docker node rm NODE

1
2
3
4
5

#### 节点提升

**用法**

BASH
docker node promote NODE [NODE…]

1
2
3
4
5
6
7
8
9
10
11
12
13

**说明**

将节点提升为 `manager`。该命令只能在 `manage node` 上执行。

> **笔记**
>
> 这是一个集群管理命令,必须在 swarm manager 节点上执行。

#### 节点降级

**用法**

BASH
docker node demote NODE [NODE…]

1
2
3
4
5
6
7
8
9
10
11
12
13

**说明**

降级现有 `manager`,使其不再是 `manager`。

> **笔记**
>
> 这是一个集群管理命令,必须在 swarm manager 节点上执行。

#### 部署服务

1. 在管理节点上运行以下命令:

BASH
$ docker service create –replicas 1 –name helloworld alpine ping docker.com
ktcjndh4nj4nbwp461idnob0y

1
2
3
4
5
6
7
8

- 该 `docker service create` 命令创建服务。
- 该 `--name` 标志命名服务 `helloworld`。
- 该 `--replicas` 标志指定 1 个正在运行的实例的所需状态。
- 参数 `alpine ping docker.com` 将服务定义为执行命令的 Alpine Linux 容器 `ping docker.com`。

1. 运行 `docker service ls` 查看正在运行的服务列表:

BASH
$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
ktcjndh4nj4n helloworld replicated 1/1 alpine:latest

1
2
3
4
5
6
7

#### 检查服务

将服务部署到 swarm 后,可以使用 `Docker CLI` 查看有关在 swarm 中运行的服务的详细信息。

1. 运行 `docker service inspect --pretty <SERVICE-ID>` 以易于阅读的格式显示有关服务的详细信息。

YAML

$ docker service inspect –pretty helloworld

ID: ktcjndh4nj4nbwp461idnob0y
Name: helloworld
Service Mode: Replicated
Replicas: 1
Placement:
ContainerSpec:
Image: alpine
Args: ping docker.com
Init: false
Resources:
Endpoint Mode: vip

1
2
3

> **提示**:要以 json 格式返回服务详细信息,请运行不带 `--pretty` 标志的相同命令。

BASH

$ docker service inspect helloworld
[
{
“ID”: “ktcjndh4nj4nbwp461idnob0y”,
“Version”: {
“Index”: 28
},
“CreatedAt”: “2022-04-05T15:00:31.72873489Z”,
“UpdatedAt”: “2022-04-05T15:00:31.72873489Z”,
“Spec”: {
“Name”: “helloworld”,
“Labels”: {},
“TaskTemplate”: {
“ContainerSpec”: {
“Image”: “alpine”,
“Args”: [
“ping”,
“docker.com”
],
“Init”: false,
“StopGracePeriod”: 10000000000,
“DNSConfig”: {},
“Isolation”: “default”
},
“Resources”: {
“Limits”: {},
“Reservations”: {}
},
“RestartPolicy”: {
“Condition”: “any”,
“Delay”: 5000000000,
“MaxAttempts”: 0
},
“Placement”: {},
“ForceUpdate”: 0,
“Runtime”: “container”
},
“Mode”: {
“Replicated”: {
“Replicas”: 1
}
},
“EndpointSpec”: {
“Mode”: “vip”
}
},
“Endpoint”: {
“Spec”: {}
}
}
]

1
2
3

1. 运行 `docker service ps <SERVICE-ID>` 查看哪些节点正在运行该服务:

BASH
$ docker service ps helloworld
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
3s1myvuaoc0k helloworld.1.3s1myvuaoc0kdvlaz4fgz3usk alpine:latest 8886 Running Running 31 minutes ago

1
2
3
4
5
6
7
8
9

在这种情况下,服务的一个实例正在节点 `helloworld` 上运行 。`8886` 您可能会看到该服务在您的管理器节点上运行。默认情况下,swarm 中的管理节点可以像工作节点一样执行任务。

Swarm 还向您显示服务任务的 `DESIRED STATE` 和 `CURRENT STATE`,以便您可以查看任务是否根据服务定义运行。

1. 在运行 `docker ps` 任务的节点上运行以查看有关任务容器的详细信息。

> **提示**:如果`helloworld`在管理节点以外的节点上运行,则必须 ssh 到该节点。

BASH
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3128610843c3 alpine:latest “ping docker.com” 38 minutes ago Up 38 minutes helloworld.1.3s1myvuaoc0kdvlaz4fgz3usk

1
2
3
4
5
6
7

#### 扩展服务

将服务部署到 swarm 后,您就可以使用 `Docker CLI` 扩展服务中的容器数量了。在服务中运行的容器称为“任务”。

1. 运行以下命令以更改在 swarm 中运行的服务的所需状态

BASH
$ docker service scale =

1
2
3

例如:

BASH
$ docker service scale helloworld=5
hellworld scaled to 5

1
2
3

1. 运行 `docker service ps <SERVICE-ID>` 查看更新的任务列表:

BASH
$ docker service ps helloworld
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
3s1myvuaoc0k helloworld.1 alpine:latest 8886 Running Running 43 minutes ago
sub0vfkz17t1 helloworld.2 alpine:latest 88235 Running Running 53 seconds ago
qiiwkq8eaw4c helloworld.3 alpine:latest 88236 Running Running about a minute ago
272jo44c63qd helloworld.4 alpine:latest 88236 Running Running about a minute ago
vx61pv6vzm9y helloworld.5 alpine:latest 8886 Running Running 2 minutes ago

1
2
3
4
5
6
7

您可以看到 swarm 创建了 4 个新任务以扩展到总共 5 个正在运行的 Alpine Linux 实例。任务分布在 swarm 的三个节点之间。

#### 删除服务

1. 运行 `docker service rm helloworld` 以删除 `helloworld` 服务。

BASH
$ docker service rm helloworld
helloworld

1
2
3
4
5

#### 应用滚动更新

1. 将您的 Redis 标签部署到 swarm 并为 swarm 配置 10 秒的更新延迟。请注意,以下示例显示了较旧的 Redis 标记:

BASH
$ docker service create
–replicas 3
–name redis
–update-delay 10s
redis:3.0.6
pvud31bgvl7e5ljf6xuxcn7dh

1
2
3
4
5
6
7
8
9
10
11

您在服务部署时配置滚动更新策略。

该 `--update-delay` 标志配置更新服务任务或任务集之间的时间延迟。您可以将时间描述 `T` 为秒数 `Ts`、分钟数 `Tm` 或小时数的组合 `Th`。所以 `10m30s` 表示延迟 10 分 30 秒。

默认情况下,调度程序一次更新 1 个任务。您可以传递该 `--update-parallelism` 标志来配置调度程序同时更新的最大服务任务数。

默认情况下,当单个任务的更新返回状态为 时 `RUNNING`,调度程序会安排另一个任务进行更新,直到所有任务都更新完毕。如果在更新期间的任何时间任务返回 `FAILED`,调度程序会暂停更新。您可以使用或 的 `--update-failure-action` 标志来控制行为 。`docker service create` `docker service update`

1. 检查`redis`服务:

BASH

$ docker service inspect –pretty redis

ID: pvud31bgvl7e5ljf6xuxcn7dh
Name: redis
Service Mode: Replicated
Replicas: 3
Placement:
UpdateConfig:
Parallelism: 1
Delay: 10s
ContainerSpec:
Image: redis:3.0.6
Init: false
Resources:
Endpoint Mode: vip

1
2
3

1. 现在您可以更新 `redis`, swarm manager 根据 `UpdateConfig` 策略将更新应用到节点:

BASH
$ docker service update –image redis:3.0.7 redis
redis

1
2
3
4
5
6
7
8
9
10
11

默认情况下,调度程序按如下方式应用滚动更新:

- 停止第一个任务。
- 为已停止的任务安排更新。
- 启动更新任务的容器。
- 如果一个任务的更新返回`RUNNING`,等待指定的延迟时间然后开始下一个任务。
- 如果在更新期间的任何时间,任务返回`FAILED`,则暂停更新。

1. 运行 `docker service inspect --pretty redis` 以查看处于所需状态的新图像:

BASH

$ docker service inspect –pretty redis

ID: pvud31bgvl7e5ljf6xuxcn7dh
Name: redis
Service Mode: Replicated
Replicas: 3
Placement:
UpdateConfig:
Parallelism: 1
Delay: 10s
ContainerSpec:
Image: redis:3.0.7
Init: false
Resources:
Endpoint Mode: vip

1
2
3

1. 运行 `docker service ps <SERVICE-ID>` 以观看滚动更新:

BASH
$ docker service ps redis
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
s3uwdrtssguv redis.1 redis:3.0.7 88236 Running Running 3 minutes ago
9nav9b094si0 _ redis.1 redis:3.0.6 88236 Shutdown Shutdown 5 minutes ago
tdy6tqfhweo4 redis.2 redis:3.0.7 88235 Running Running 2 minutes ago
t9vr0kt1h8av _ redis.2 redis:3.0.6 88235 Shutdown Shutdown 3 minutes ago
xjc6v95dayhv redis.3 redis:3.0.7 8886 Running Running 3 minutes ago
biy701m9tvat _ redis.3 redis:3.0.6 8886 Shutdown Shutdown 4 minutes ago

1
2
3
4
5
6
7
8
9

#### 排空一个节点

有时,例如计划的维护时间,您需要将节点设置为 `DRAIN` 可用。`DRAIN` 可用性阻止节点从集群管理器接收新任务。这也意味着管理器停止在节点上运行的任务,并在可用的节点上启动副本任务 `ACTIVE`。

> **重要**:将节点设置为 `DRAIN` 不会从该节点删除独立容器,例如使用 Docker 引擎 API 创建的 `docker run` 容器 `docker-compose up`。一个节点的状态,包括 `DRAIN`,只影响节点调度 swarm 服务工作负载的能力。

1. 运行 `docker node update --availability drain <NODE-ID>` 以排空已分配任务的节点:

BASH
$ docker node update –availability drain worker1
work1

1
2
3

1. 检查节点其可用性:

BASH
$ docker node inspect –pretty worker1
AVAILABILITY属性值为Drain

1
2
3
4
5
6
7

## Docker Stack

### deploy

指定与服务的部署和运行有关的配置。只在 swarm 模式下才会有用。

NESTEDTEXT

version: “3.7”
services:
redis:
image: redis:alpine
deploy:
sssssssss
endpoint_mode: dnsrr
labels:
description: “This redis service label”
resources:
limits:
cpus: ‘0.50’
memory: 50M
reservations:
cpus: ‘0.25’
memory: 20M
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
window: 120s

1
2
3
4
5

可以选参数:

**endpoint_mode**:访问集群服务的方式。

AVRASM
endpoint_mode: vip

Docker 集群服务一个对外的虚拟 ip。所有的请求都会通过这个虚拟 ip 到达集群服务内部的机器。

endpoint_mode: dnsrr

DNS 轮询(DNSRR)。所有的请求会自动轮询获取到集群 ip 列表中的一个 ip 地址。


**labels**:在服务上设置标签。可以用容器上的 labels(跟 deploy 同级的配置) 覆盖 deploy 下的 labels。

**mode**:指定服务提供的模式。

- **replicated**:复制服务,复制指定服务到集群的机器上。

- **global**:全局服务,服务将部署至集群的每个节点。

- 图解:下图中黄色的方块是 replicated 模式的运行情况,灰色方块是 global 模式的运行情况。

  ![img](https://itboy-blog-image.oss-cn-hangzhou.aliyuncs.com/20220713152904.png)

**replicas:mode** 为 replicated 时,需要使用此参数配置具体运行的节点数量。

**resources**:配置服务器资源使用的限制,例如上例子,配置 redis 集群运行需要的 cpu 的百分比 和 内存的占用。避免占用资源过高出现异常。

**restart_policy**:配置如何在退出容器时重新启动容器。

- condition:可选 none,on-failure 或者 any(默认值:any)。
- delay:设置多久之后重启(默认值:0)。
- max_attempts:尝试重新启动容器的次数,超出次数,则不再尝试(默认值:一直重试)。
- window:设置容器重启超时时间(默认值:0)。

**rollback_config**:配置在更新失败的情况下应如何回滚服务。

- parallelism:一次要回滚的容器数。如果设置为 0,则所有容器将同时回滚。
- delay:每个容器组回滚之间等待的时间(默认为 0s)。
- failure_action:如果回滚失败,该怎么办。其中一个 continue 或者 pause(默认 pause)。
- monitor:每个容器更新后,持续观察是否失败了的时间 (ns|us|ms|s|m|h)(默认为 0s)。
- max_failure_ratio:在回滚期间可以容忍的故障率(默认为 0)。
- order:回滚期间的操作顺序。其中一个 stop-first(串行回滚),或者 start-first(并行回滚)(默认 stop-first )。

**update_config**:配置应如何更新服务,对于配置滚动更新很有用。

- parallelism:一次更新的容器数。
- delay:在更新一组容器之间等待的时间。
- failure_action:如果更新失败,该怎么办。其中一个 continue,rollback 或者 pause (默认:pause)。
- monitor:每个容器更新后,持续观察是否失败了的时间 (ns|us|ms|s|m|h)(默认为 0s)。
- max_failure_ratio:在更新过程中可以容忍的故障率。
- order:回滚期间的操作顺序。其中一个 stop-first(串行回滚),或者 start-first(并行回滚)(默认 stop-first)。

**注**:仅支持 V3.4 及更高版本。