:::warning 容器集群化是未来的发展方向。需要对容器网络做规划,容器网络有五类:bridge,host,none,container和自定义网络。

容器监控以及可视化容器部署配置。

:::

1 Docker 复杂安装详说

👑1.1 安装 MySQL 主从复制

👑1.1.1 主从复制原理

mysql主从复制原理是什么-mysql教程-PHP中文网

  • 为什么需要主从复制?

1、在业务复杂的系统中,有这么一个情景,有一句SQL语句需要缩表,导致暂时不能使用读的服务,那么就很影响运行中的业务,使用主从复制,让主库负责写,从库负责读,这样,即使主库出现了缩表的情景,通过读从库也可以保证业务的正常运行。

2、做数据的热备,主库宕机后能够及时替换主库,保证业务可用性。

3、架构的扩展。业务量越来越大,I/O 访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的频率,提高单个机器的I/O性能。

4、MySQL 主从经典的部署方式:一主一从;MySQL 主从中从服务器也不需要太多,MySQL 会出现主从的不同步情况,从服务器同步跟不上也会导致数据同步出错。

  • 什么是 MySQL 的主从复制?

主从复制中分为【主服务器(master)】和【从服务器(slave)】,主服务器负责写,而从服务器负责读,MySQL 的主从复制的过程是一个【异步的过程】。
这样读写分离的过程能够是整体的服务性能提高,即时写操作时间比较长,也不影响读操作的进行。

MySQL 主从复制是指数据可以从一个MySQL数据库服务器主节点复制到一个或者多个从节点。
MySQL 默认采用异步复制方式,这样从节点不用一直访问主服务器来更新自己的数据,数据的更新可以在远程连接上进行,从节点可以复制主数据库中所有数据库或者特定的数据库,或者特定的表。

  • MySQL 主从复制的流程

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图1

:::warning

1.主节点必须启用二进制日志,记录任何修改了数据库数据的事件

2.从节点开启了一个线程(I/O Thread)把自己扮演成MySQL的客户端,通过MySQL协议,请求主节点的二进制日志文件中的事件

3.主节点启动一个线程(Dump Thread),检查自己二进制日志中的事件,跟对方请求的位置对比,如果不带请求位置参数,则主节点就会从第一个日志文件中的第一个事件一个一个发送给从节点

4.从节点接收到主节点发送过来的数据把它放置在中继日志(Relay Log)文件中。并记录该次请求到主节点的具体哪一个二进制日志文件内部的哪一个位置(主节点中的二进制文件会有很多个)

5.从节点启动另一个线程(SQL Thread),把Relay Log中的事件读取出来,并在本地再执行一次

:::

:::warning

复制中线程的作用:

从节点

  • I/O Thread:从Master节点请求二进制日志事件,并保存于中继日志中

  • SQL Thread:从Reglay Log中读取日志事件并在本地完成重放

主节点

  • Dump Thread:为每个 Slave 的I/O Thread启动一个Dump,用于向从节点发送二进制事件

:::

  • MySQL 主从复制的原理

MySQL 的主从复制中主要有三个线程:master(binlog dump thread)、slave(I/O thread / SQL thread),Master一条线程和Slave中的两条线程。

master(binlog dump thread)主要负责Master库中有数据更新的时候,会按照binlog格式,将更新的事件类型写入到主库的binlog文件中。

并且,master(binlog dump thread)线程通知Slave主库中存在数据更新,这就是为什么主库的binlog日志一定要开启的原因。

I/O thread线程在Slave中创建,该线程用于请求Master,Master会返回binlog的名称以及当前数据更新的位置,binlog文件位置的副本。

然后,将binlog保存在[Relay Log(中继日志)]中,中继日志也是记录数据更新的信息。

SQL thread也是在Slave创建的,当 Slave 检测到中继日志有更新,就会将更新的内容同步到Slave数据库中,这样就保证了主从的数据的同步。

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图2

:::warning 主库db的更新事件(update,insert,delete)被写到 binlog

主库创建一个 binlog dump thread,把 binlog 的内容发送到从库

从库启动并发起连接,连接到主库

从库启动之后,创建一个I/O线程,读取主库传过来的 binlog 内容并写入到 relay log

从库启动之后,创建一个 SQL 线程,从 relay log 里面读取内容,从 Exec_Master_Log_Pos 位置开始执行读取到的更新事件,将更新内容写入到 Slave 的 db中

:::

以上就是主从复制的过程,当然,主从复制的过程有不同的策略方式进行数据的同步,主要包含以下几种:

  1. 「同步策略」:Master会等待所有的Slave都回应后才会提交,这个主从的同步的性能会严重的影响。
  2. 「半同步策略」:Master至少会等待一个Slave回应后提交。
  3. 「异步策略」:Master不用等待Slave回应就可以提交。
  4. 「延迟策略」:Slave要落后于Master指定的时间。

对于不同的业务需求,有不同的策略方案,但是一般都会采用最终一致性,不会要求强一致性,毕竟强一致性会严重影响性能。

1.1.2 主从搭建步骤

新建主服务器容器实例 Port:3307

  1. $ docker run -it -p 3307:3306 -d \
  2. --name mysql-master --privileged=true \
  3. -v /app/mysql-master/log:/var/log/mysql \
  4. -v /app/mysql-master/data:/var/lib/mysql \
  5. -v /app/mysql-master/conf:/etc/mysql \
  6. -e MYSQL_ROOT_PASSWORD=Admin@h3c \
  7. mysql:5.7.36
  8. $ docker ps -l
  9. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  10. 43c1f8bc896c mysql:5.7.36 "docker-entrypoint.s…" 7 seconds ago Up 6 seconds 33060/tcp, 0.0.0.0:3307->3306/tcp, :::3307->3306/tcp mysql-master
  • 进入 /app/mysql-master/conf 目录下新建 my.cnf
  1. $ cat > /app/mysql-master/conf/my.cnf <<EOF
  2. [client]
  3. ## 设置客户端的默认字符集
  4. default-character-set=utf8mb4
  5. [mysql]
  6. ## 设置MySQL服务器的默认字符集
  7. default-character-set=utf8mb4
  8. [mysqld]
  9. ## 设置MySQL后台服务程序的默认字符集
  10. character-set-server=utf8mb4
  11. collation-server=utf8mb4_unicode_ci
  12. ## 设置server_id,同一局域网中需要唯一
  13. server_id=101
  14. ## 指定不需要同步的数据库名称
  15. binlog-ignore-db=mysql
  16. ## 开启二进制日志功能
  17. log-bin=mall-mysql-bin
  18. ## 开启二进制日志使用内存大小(事务)
  19. binlog_cache_size=1M
  20. ## 设置使用的二进制日志格式(mixed,statement,row)
  21. binlog_format=mixed
  22. ## 二进制日志过期清理时间。默认值为0,表示不自动清理
  23. expire_logs_days=7
  24. ## 跳过主从复制中遇到的所有错误或者指定类型的错误,避免slave端复制终端
  25. ## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致
  26. slave_skip_errors=1062
  27. EOF
  • 修改完配置后重启 master 实例
  1. $ docker restart mysql-master
  • 进入 mysql-master 容器
  1. $ docker exec -it mysql-master /bin/bash
  2. root@43c1f8bc896c:/# mysql -uroot -pAdmin@h3c
  3. mysql> show databases;
  4. +--------------------+
  5. | Database |
  6. +--------------------+
  7. | information_schema |
  8. | mysql |
  9. | performance_schema |
  10. | sys |
  11. +--------------------+
  12. 4 rows in set (0.00 sec)
  • master 容器实例内创建数据同步用户
  1. mysql> CREATE USER 'slave'@'%' IDENTIFIED BY 'Admin@h3c';
  2. mysql> GRANT REPLICATION SLAVE,REPLICATION CLIENT ON *.* TO 'slave'@'%';

新建从服务器容器实例 Port:3308

  1. $ docker run -it -p 3308:3306 -d \
  2. --name mysql-slave --privileged=true\
  3. -v /app/mysql-slave/log:/var/log/mysql \
  4. -v /app/mysql-slave/data:/var/lib/mysql \
  5. -v /app/mysql-slave/conf:/etc/mysql \
  6. -e MYSQL_ROOT_PASSWORD=Admin@h3c \
  7. mysql:5.7.36
  8. $ docker ps -l
  9. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  10. 67ff2cc65475 mysql:5.7.36 "docker-entrypoint.s…" 2 seconds ago Up 1 second 33060/tcp, 0.0.0.0:3308->3306/tcp, :::3308->3306/tcp mysql-slave
  • 进入 /app/mysql-slave/conf 目录下新建 my.cnf
  1. $ cat > /app/mysql-slave/conf/my.cnf <<EOF
  2. [client]
  3. ## 设置客户端的默认字符集
  4. default-character-set=utf8mb4
  5. [mysql]
  6. ## 设置MySQL服务器的默认字符集
  7. default-character-set=utf8mb4
  8. [mysqld]
  9. ## 设置MySQL后台服务程序的默认字符集
  10. character-set-server=utf8mb4
  11. collation-server=utf8mb4_unicode_ci
  12. ## 设置server_id,同一局域网中需要唯一
  13. server_id=102
  14. ## 指定不需要同步的数据库名称
  15. binlog-ignore-db=mysql
  16. ## 开启二进制日志功能,以备Slave作为其他数据库实例的Master时使用
  17. log-bin=mall-slave1-bin
  18. ## 设置二进制日志使用内存大小(事务)
  19. binlog_cache_size=1M
  20. ## 设置使用的二进制日志格式(mixed,statement,row)
  21. binlog_format=mixed
  22. ## 二进制日志过期清理时间。默认值为0,表示不自动清理
  23. expire_logs_days=7
  24. ## 跳过主从复制中遇到的所有错误或者指定类型的错误,避免slave端复制终端
  25. ## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致
  26. slave_skip_errors=1062
  27. ## relay_log配置中继日志
  28. relay_log=mall-mysql-relay-bin
  29. ## log_slave_updates表示slave将复制事件写进自己的二进制日志
  30. log_slave_updates=1
  31. ## slave设置为只读(具有super权限的用户除外)
  32. read_only=1
  33. EOF
  • 修改完配置后重启 slave 实例
  1. $ docker restart mysql-slave
  • 在 mysql-mater 主数据库中查看主从同步状态
  1. $ docker exec -it mysql-master /bin/bash
  2. root@43c1f8bc896c:/# mysql -uroot -pAdmin@h3c
  3. mysql> show master status;
  4. +-----------------------+----------+--------------+------------------+-------------------+
  5. | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
  6. +-----------------------+----------+--------------+------------------+-------------------+
  7. | mall-mysql-bin.000001 | 617 | | mysql | |
  8. +-----------------------+----------+--------------+------------------+-------------------+
  9. 1 row in set (0.00 sec)
  • 进入 mysql-slave 从数据库容器
  1. $ docker exec -it mysql-slave /bin/bash
  2. root@67ff2cc65475:/# mysql -uroot -pAdmin@h3c
  • 在 mysql-slave 从数据库中配置主从复制
  1. ### 主从复制命令参数说明
  2. # master_host:主数据库的IP地址
  3. # master_port:主数据库的运行断开
  4. # master_user:在主数据库创建的用于同步数据的用户账号
  5. # master_password:在主数据库创建的用于同步数据的用户密码
  6. # master_log_file:指定从数据库要复制数据的日志文件,通过查看主数据的状态,获取File参数
  7. # master_log_pos:指定从数据库从哪个位置开始复制数据,通过查看主数据的状态,获取Position参数
  8. # master_connect_retry:连接失败重试的时间间隔,单位为秒
  9. # 公式:change master to master_host='宿主机ip', master_user='slave', master_password='Admin@h3c', master_port=3307, master_log_file='mall-mysql-bin.000001', master_log_pos=617, master_connect_retry=30;
  10. $ docker exec -it mysql-slave /bin/bash
  11. root@67ff2cc65475:/# mysql -uroot -pAdmin@h3c
  12. mysql> change master to master_host='139.198.105.99', master_user='slave', master_password='Admin@h3c', master_port=3307, master_log_file='mall-mysql-bin.000001', master_log_pos=617, master_connect_retry=30;
  • 在 mysql-slave 从数据库中查看主从同步状态
  1. $ docker exec -it mysql-slave /bin/bash
  2. root@67ff2cc65475:/# mysql -uroot -pAdmin@h3c
  3. mysql> show slave status\G;
  4. *************************** 1. row ***************************
  5. Slave_IO_State:
  6. Master_Host: 139.198.105.99
  7. Master_User: slave
  8. Master_Port: 3307
  9. Connect_Retry: 30
  10. Master_Log_File: mall-mysql-bin.000001
  11. Read_Master_Log_Pos: 617
  12. Relay_Log_File: mall-mysql-relay-bin.000001
  13. Relay_Log_Pos: 4
  14. Relay_Master_Log_File: mall-mysql-bin.000001
  15. Slave_IO_Running: No
  16. Slave_SQL_Running: No
  • 在 mysql-slave 从数据库中开启主从同步
  1. $ docker exec -it mysql-slave /bin/bash
  2. root@67ff2cc65475:/# mysql -uroot -pAdmin@h3c
  3. mysql> start slave;
  4. Query OK, 0 rows affected (0.00 sec)
  • 查看从数据库状态发现已经同步
  1. mysql> show slave status\G;
  2. *************************** 1. row ***************************
  3. Slave_IO_State: Waiting for master to send event
  4. Master_Host: 139.198.105.99
  5. Master_User: slave
  6. Master_Port: 3307
  7. Connect_Retry: 30
  8. Master_Log_File: mall-mysql-bin.000001
  9. Read_Master_Log_Pos: 617
  10. Relay_Log_File: mall-mysql-relay-bin.000002
  11. Relay_Log_Pos: 325
  12. Relay_Master_Log_File: mall-mysql-bin.000001
  13. Slave_IO_Running: Yes
  14. Slave_SQL_Running: Yes
  • 主从复制测试
    • 主机新建库 —> 使用库 —> 新建表 —> 插入数据,OK
    • 从机使用库 —> 查看记录,OK
  1. # 在主数据库进行操作
  2. $ docker exec -it mysql-master /bin/bash
  3. root@43c1f8bc896c:/# mysql -uroot -pAdmin@h3c
  4. mysql> create database if not exists db01;
  5. mysql> use db01;
  6. mysql> create table t1 (id INT,name VARCHAR(20));
  7. mysql> insert into t1(id , name) values(1 , 'zhangsan');
  8. mysql> select * from t1;
  9. +------+----------+
  10. | id | name |
  11. +------+----------+
  12. | 1 | zhangsan |
  13. +------+----------+
  14. 1 row in set (0.00 sec)
  15. # 在从数据库进行操作
  16. $ docker exec -it mysql-slave /bin/bash
  17. root@67ff2cc65475:/# mysql -uroot -pAdmin@h3c
  18. mysql> show databases;
  19. +--------------------+
  20. | Database |
  21. +--------------------+
  22. | information_schema |
  23. | db01 |
  24. | mysql |
  25. | performance_schema |
  26. | sys |
  27. +--------------------+
  28. 5 rows in set (0.01 sec)
  29. mysql> use db01;
  30. mysql> show tables;
  31. +----------------+
  32. | Tables_in_db01 |
  33. +----------------+
  34. | t1 |
  35. +----------------+
  36. 1 row in set (0.00 sec)
  37. mysql> select * from t1;
  38. +------+----------+
  39. | id | name |
  40. +------+----------+
  41. | 1 | zhangsan |
  42. +------+----------+
  43. 1 row in set (0.00 sec)

👑1.2 安装 Redis 集群(大厂面试题-分布式存储案例)

Cluster集群模式-Docker版本 ---> 哈希槽分区进行亿级数据存储

Redis 采用的算法就是为了区分不同的请求分配给哪台服务器处理

面试题:1~2亿条数据需要缓存,请问如何设计这个存储案例

回答:单机单台100%不可能实现,肯定是**<font style="color:#E8323C;">分布式存储</font>**,用Redis如何落地。

上述问题阿里 P6~P7 工程案例和场景设计类必考题目,一般业界有三种解决方案。

👑1.2.0 Redis 存储数据的算法

(1)哈希取余分区Redis面试题Hash算法-嗨客网 (haicoder.net)

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图3

  • Hash 基本原理就是把任意长度的输入,通过 Hash 算法变成固定长度的输出。Hash 算法有很多种,常见的算法有 MD5,SHA,CRC等。

2 亿条记录就是2亿条 K/V,我们单机不行必须要分布式多机,假设有3台机器构成一个集群,用户每次读写操作都是根据公式;hash(key) % N 个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。

假设有一个Key,set k1=v1。读写操作需要根据 hash(key) % N = Number(hash) 值,利用该 Number(hash)值 来决定映射到哪个节点。怎么存储的就怎么取出数据。Redis 的 key 一般是不重复的,利用同一套的 hash 算法,例如:得到的[余数]存储的时候是 0号机器,取出数据的时也是0号机器。

哈希取余分区是最通用,最常用的。一般用于小型业务场景,中大型业务场景建议不要使用。

:::warning

优点:

  • 简单粗暴,直接有效,只需要预估好数据规划好节点,例如3台,8台,10台,就能保证一段时间的数据支撑。使用 Hash 算法让固定的一部分请求(并维护这些请求的信息),起到负载均衡+分而治之的作用。

缺点:

  • 原来规划好的节点,进行扩容或者缩容就比较麻烦了,不管扩容缩容,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或者故障停机的情况下,原来的取模公式就会发生变化,Hash(key) % 3会变成Hash(key) % ?,此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变的不可控。

  • 若集群规模台数为3,Hash(key) = 3,则Hash(key) % 3=0,当集群规模扩容之后,台数为7,Hash(key)值不变,Hash(key) % 7 = 1,存储数据的路径即会发生变化。

  • 某个Redis机器宕机了,由于台数数量变化,会导致Hash取余全部数据重新洗牌。

缺点总结:节点映射,数据变动,容易出错。

:::

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图4

(2)一致性哈希算法分区

  • 是什么?一致性 Hash 算法背景:→ 一致性Hash哈希算法在1997年由麻省理工学院中提出的,设计目标是为了解决分布式缓存数据变动和映射问题,某个机器宕机了,分母数量改变了,自然取余数不OK了。
  • 能干嘛?→ 提出一致性Hash解决方案。目的是当服务器个数发生变动时,尽量减少影响客户端到服务器的映射关系。
  • 3大步骤(Redis面试题一致性Hash-嗨客网 (haicoder.net))
(1)i:算法构建一致性哈希环:一致性哈希算法必然有个 hash 函数并按照算法产生 hash 值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个 hash 值空间 [0 , 2^32-1] (根据hash函数算法获得,hash 值空间可变),这个是一个线性空间,但是在算法中,我们通过适当的逻辑控制将它首尾相连(0 = 2^32 可以简单理解为 0 和 2^32 重合 [和时钟相似,0和12的点是重合] ),这样让它逻辑上形成了一个环形空间。(哈希环为了减轻机器台数变动以后数据映射和分配规则) 它也是按照使用取模的方式,前面介绍的节点取模法是对节点(服务器)的数量进行取模。而一致性Hash算法是对 2^32 取模,简单来说,一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为 0 - 2^32 -1 (即哈希值是一个32位无符号整型),整个哈希环如下图:整个空间按顺时针方向组织,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2,3,4……直到2^32-1 ,也就是说0点左侧的第一个点代表2^32-1,0和2^32-1在零点中方向重合,我们把这个由2^32个点组成的圆环称为 Hash 环

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图5

(2)ii:服务器IP节点映射:将集群中各个IP节点映射到环上的某一个位置。将各个服务器使用 Hash 进行一个哈希,具体可以选择服务器的IP或者主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。假如4个节点NodeA,B,C,D,经过IP地址的哈希函数计算(hash(IP)),使用IP地址哈希后再环空间的位置如下: 💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图6 (3)iii:key落到服务器的落键规则:当我们需要存储一个 KV 键值对时,首先计算 Key 的hash 值,hash(key),将这个 key 使用相同的函数 Hash 计算出哈希值并确定此数据在环上的位置,数据Hash值计算定为环上,从此位置沿环顺时针”行走”,第一台遇到的服务器就是其应该定位到的服务器,并将该键值对存储在该节点上。 如果我们有Object A、Object B、Object C、Object D四个数据对象,经过哈希计算后,在环空间上的位置如下:根据一致性Hash算法,数据A会被定为到Node A上,数据B会被定为到Node B上,数据C会被定为到Node C上,数据D会被定为到Node D上。💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图7 :::color1 一致性哈希算法分区:**就是先确定一个环,再在环上确定主机在的位置,然后就在环上插入数据,顺时针转,数据顺时针碰到的第一个节点就是他所在主机。 ::: - 算法构建一致性哈希环 - 节点映射 - Key 落到服务器的落键规则 + 优点 1.一致性哈希算法容错性:假设Node C宕机,可以看到此时对象A,B,D不会受到影响,只有C对象被重定位到Node D。一般的,在一致性Hash算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。简单说,就是C挂了,受到影响的只是B、C之间的数据,并且这些数据会转移到D进行存储。 💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图8 2.一致性哈希算法扩展性:数据量增加了,需要增加一台节点Node X,此时对象 Object A、B、D 不受影响,只有对象 C 需要重定位到新的 Node X 。一般的,在一致性哈希算法中,如果增加一台服务器,则受影响的数据仅仅是新服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它数据也不会受到影响。 若 Node X的位置在Node A 与 Node B 之间,那受到影响的也就是 A 到 X之间的数据,重新把 A 到 X 的数据录入到 X 上即可,不会导致 hash 取余全部数据重新洗牌。 💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图9 所以会影响相邻结点之间的数据缓存,结点越多影响越小。 + 缺点 一致性哈希算法的数据倾斜问题:一致性 Hash 算法在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题,例如系统中只有两台服务器。 此时必然造成大量数据集中到 Node A 上,而只有极少量会定位到 Node B 上。 💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图10 为了解决这种数据倾斜问题,一致性哈希算法引入了虚拟节点机制,即对每一个服务节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点。具体做法可以在服务器 ip 或主机名的后面增加编号来实现。例如上面的情况,可以为每台服务器计算三个虚拟节点,于是可以分别计算 “Node A#1”、“Node A#2”、“Node A#3”、“Node B#1”、“Node B#2”、“Node B#3” 的哈希值,于是形成六个虚拟节点: 💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图11 同时数据定位算法不变,只是多了一步虚拟节点到实际节点的映射,例如定位到 “Node A#1”、“Node A#2”、“Node A#3” 三个虚拟节点的数据均定位到 Node A 上。这样就解决了服务节点少时数据倾斜的问题。在实际应用中,通常将虚拟节点数设置为 32 甚至更大,因此即使很少的服务节点也能做到相对均匀的数据分布。 + 小总结 为了在节点数目发生改变时尽可能少的迁移数据 将所有的存储节点排列在收尾相接的 Hash 环上,每个 key 在计算 Hash 后会顺时针找到临近的存储节点存放。而当有节点加入或 者退出时仅影响该节点在 Hash 环上顺时针相邻的后续节点。 优点:加入和删除节点只影响哈希环中顺时针方向的相邻的节点,对其他节点无影响。 缺点:数据的分布和节点的位置有关,因为这些节点不是均匀的分布在哈希环上的,所以数据在进行存储时达不到均匀分布的效果。 (3)哈希槽分区** 是什么? :::warning # 1.为什么会出现? 一致性哈希算法的数据倾斜问题 > 哈希槽实质上就是一个数组,数组[0,2^14-1]形成一个 hash slot 空间 # 2.能干什么? 解决均匀分配的问题,在数据和节点之间又加入了一层,把这层称为哈希槽(slot),用于管理数据和节点之间的关系,选择就相当于节点上放的是槽,槽里放的是数据(data)。 槽解决的是粒度问题,相当于把粒度变大了,这样就便于数据移动。 哈希解决的是映射问题,使用 key 的哈希值来计算所在的槽,便于数据分配。 ::: 💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图12 :::warning # 3.多少个 hash 槽? Redis一个集群只能有 16384 个槽,编号0-16383( 0-2^14-1 )。这些槽会分配给集群中的所有主节点,分配策略没有要求。可以指定哪些编号的槽分配给哪个主节点。集群会记录节点和槽的对应关系。解决了节点和槽的关系后,接下来就需要对 key 求哈希值,然后对 16384 取余,余数是几key 就落入对应的槽里slot = CRC16(key) % 16384。以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。 ::: 💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图13 :::warning ### 为什么 Redis 集群的最大槽位是16384个? Redis 集群并没有使用一致性 hash 而是引入了哈希槽的概念。Redis 集群有16384个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分槽。但是为什么哈希槽的数量是 16384(2^14)个呢? CRC16算法产生的 hash 值有16bit,该算法可以产生 2^16 =65536个值

换句话说是分布在0-65535之间,那作者在做 mod 运算的时候,为什么不mod65536,而是选择mod16384?

https://github.com/redis/redis/issue/2576

<主要为了心跳方便,以及数据传输的最大化>

说明1:

  • 正确的心跳数据包带有节点的完整配置,可以用幂等方式用新的节点替换旧节点,以便更新旧的配置。

这意味着他们包含原始节点的插槽配置,该节点使用 2K的空间和16K的插槽,但是会使用8K的空间(使用65K的插槽)。

同时,由于其他设计折衷,Redis不太可能扩展到1000个以上的主节点。

因此16k处于正确的范围内,以确保每个主机具有足够的插槽,最多可容纳100个矩阵,但是数据足够少,可以轻松的将插槽配置作为原始位图传播。请注意,在小型群集中,位图将难以压缩,因为当N较小时,位图将设置的 slot/N 占设置位的很大百分比。

说明2:

(1)如果槽位为65536,发送心跳信息的消息头达8K,发送的心跳包过于庞大。

在消息头中最占空间的是myslots[CLUSTER_SLOTS/8]。当槽位为65536时,这块的大小是:65536/8/1024=8KB

因为每秒钟,Redis节点需要发送一定数量的ping消息作为心跳包,如果槽位是65536,则这个ping消息的消息头太大了,浪费带宽。

(2)Redis 的集群主节点数量基本不可能超过1000个。

集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致为了拥堵。因此Redis作者不建议Redis Cluster节点数量超过1000个。那么,对于节点数在1000以内的Redis Cluster集群,16384个槽位足够了。没有必要扩展到 65536 个。

(3)槽位越小,节点少的情况下,压缩比高,容易传输

Redis 主节点的配置信息中它所负责的哈希槽是通过一张 bitmap 的形式来保存的,在传输过程中会对 bitmap 进行压缩,但是如果 bitmap 的填充率 slots / N 很高的话(N代表节点数),bitmap 的压缩率就很低。如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低。

:::

(4)**哈希槽计算**

:::warning

Redis 哈希槽计算

Redis 集群中内置了 16384 个哈希槽,Redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在 Redis 集群中放置一个 key-value 时,Redis 先对 key 使用 CRC16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,也就是映射到某个节点上。如下代码,key之A、B在Node2,key之C落在Node3上。

@Test

public void test3()

{

  1. //import.io.lettuce.core.cluster.SlotHash;
  2. System.out.println(SlotHash.getSlot(key:"A")); //6373
  3. System.out.println(SlotHash.getSlot(key:"B")); //10374
  4. System.out.println(SlotHash.getSlot(key:"C")); //14503
  5. System.out.println(SlotHash.getSlot(key:"hello")); //866

}

:::

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图14

:::warning 哈希槽和一致性 Hash 的最大区别就是引入了 Hash 槽,可以理解为在一致性 Hash 的基础上,变成 16384 个虚拟节点,这也是 Hash 算法算出来的倾斜的概率会降低,但是极端情况还是数据倾斜。

:::

三主三从Redis集群扩缩容配置案例架构说明

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图15

Redis Role Redis Port
Master1 6381
Master2 6382
Master3 6383
Slave1 6384
Slave2 6385
Slave3 6386

👑1.2.1 Redis 三主三从实现步骤

  1. ### 1.关闭防火墙+启动Docker后台服务
  2. $ systemctl stop firewalld && systemctl disable firewalld
  3. $ systemctl start docker && systemctl enable docker
  4. ### 2.新建6个Redis Docker容器实例
  5. # docker run ---> 创建并运行 docker 容器实例
  6. # --name redis-node-1 ---> 容器名字
  7. # -d ---> 容器实例后台运行
  8. # --net host ---> 使用宿主机的IP和端口,默认(host网络模式不需要端口映射,直接使用宿主机端口)
  9. # --privileged=true ---> 获取宿主机 root 用户权限
  10. # -v /data/redis/share/redis-note-1:/data ---> 容器卷,宿主机绝对路径目录:Docker容器内部绝对路径目录
  11. # redis ---> Redis 镜像,默认版本号为 latest
  12. # --cluster-enabled yes ---> 开启 Redis 集群
  13. # --appendonly yes ---> 开启持久化
  14. # --port 6381 ---> 容器实例 Redis 暴露的端口号
  15. docker run -d --name redis-node-1 --net host --privileged=true --restart=always -v /myapp/redis/share/redis-node-1:/data redis --cluster-enabled yes --appendonly yes --port 6381
  16. docker run -d --name redis-node-2 --net host --privileged=true --restart=always -v /myapp/redis/share/redis-node-2:/data redis --cluster-enabled yes --appendonly yes --port 6382
  17. docker run -d --name redis-node-3 --net host --privileged=true --restart=always -v /myapp/redis/share/redis-node-3:/data redis --cluster-enabled yes --appendonly yes --port 6383
  18. docker run -d --name redis-node-4 --net host --privileged=true --restart=always -v /myapp/redis/share/redis-node-4:/data redis --cluster-enabled yes --appendonly yes --port 6384
  19. docker run -d --name redis-node-5 --net host --privileged=true --restart=always -v /myapp/redis/share/redis-node-5:/data redis --cluster-enabled yes --appendonly yes --port 6385
  20. docker run -d --name redis-node-6 --net host --privileged=true --restart=always -v /myapp/redis/share/redis-node-6:/data redis --cluster-enabled yes --appendonly yes --port 6386
  21. ### 3.进入容器 redis-node-1 并为 6台机器构建集群关系
  22. ## 进入容器
  23. $ docker exec -it redis-node-1 /bin/bash
  24. ## 构建主从关系
  25. # 注意,进入Docker容器后才能执行一下命令,并且注意自己的真实IP地址
  26. # --cluster-replicas 1 表示为每个 Master 创建一个 Slave 节点
  27. root@docker:/data# redis-cli --cluster create 139.198.105.99:6381 139.198.105.99:6382 139.198.105.99:6383 139.198.105.99:6384 139.198.105.99:6385 139.198.105.99:6386 --cluster-replicas 1
  28. >>> Performing hash slots allocation on 6 nodes...
  29. Master[0] -> Slots 0 - 5460
  30. Master[1] -> Slots 5461 - 10922
  31. Master[2] -> Slots 10923 - 16383
  32. Adding replica 139.198.105.99:6385 to 139.198.105.99:6381
  33. Adding replica 139.198.105.99:6386 to 139.198.105.99:6382
  34. Adding replica 139.198.105.99:6384 to 139.198.105.99:6383
  35. >>> Trying to optimize slaves allocation for anti-affinity
  36. [WARNING] Some slaves are in the same host as their master
  37. M: daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381
  38. slots:[0-5460] (5461 slots) master
  39. M: a23364d45a6d2f56d94021815af2c8f3ef6cb499 139.198.105.99:6382
  40. slots:[5461-10922] (5462 slots) master
  41. M: 15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383
  42. slots:[10923-16383] (5461 slots) master
  43. S: 150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 139.198.105.99:6384
  44. replicates daa3085081481d5c893f100a4d742a7160a8616c
  45. S: 48656f0b83bd65f31a4478fc17be8322efcd2ede 139.198.105.99:6385
  46. replicates a23364d45a6d2f56d94021815af2c8f3ef6cb499
  47. S: 2075147d5c9e90275c1eb7e1b1ad32912129a13e 139.198.105.99:6386
  48. replicates 15a33cc85697b68cc897137c863238f412ceff46
  49. Can I set the above configuration? (type 'yes' to accept): yes
  50. ## 一切Ok的话,3主3从搞定
  51. ### 4.链接进入 6381 作为切入点,查看集群状态
  52. ## 链接进入 6381 作为切入点,查看节点状态
  53. $ docker exec -it redis-node-1 /bin/bash
  54. $ root@docker:/data# redis-cli -p 6381
  55. ## cluster info
  56. 127.0.0.1:6381> cluster info
  57. cluster_state:ok
  58. cluster_slots_assigned:16384
  59. cluster_slots_ok:16384
  60. cluster_slots_pfail:0
  61. cluster_slots_fail:0
  62. cluster_known_nodes:6
  63. cluster_size:3
  64. cluster_current_epoch:6
  65. cluster_my_epoch:1
  66. cluster_stats_messages_ping_sent:319
  67. cluster_stats_messages_pong_sent:329
  68. cluster_stats_messages_sent:648
  69. cluster_stats_messages_ping_received:324
  70. cluster_stats_messages_pong_received:319
  71. cluster_stats_messages_meet_received:5
  72. cluster_stats_messages_received:648
  73. ## cluster nodes
  74. 127.0.0.1:6381> cluster nodes
  75. 2075147d5c9e90275c1eb7e1b1ad32912129a13e 139.198.105.99:6386@16386 slave 15a33cc85697b68cc897137c863238f412ceff46 0 1642221710000 3 connected
  76. 48656f0b83bd65f31a4478fc17be8322efcd2ede 139.198.105.99:6385@16385 slave a23364d45a6d2f56d94021815af2c8f3ef6cb499 0 1642221711199 2 connected
  77. daa3085081481d5c893f100a4d742a7160a8616c 10.150.22.47:6381@16381 myself,master - 0 1642221711000 1 connected 0-5460
  78. 150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 139.198.105.99:6384@16384 slave daa3085081481d5c893f100a4d742a7160a8616c 0 1642221710196 1 connected
  79. a23364d45a6d2f56d94021815af2c8f3ef6cb499 139.198.105.99:6382@16382 master - 0 1642221712202 2 connected 5461-10922
  80. 15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383@16383 master - 0 1642221709193 3 connected 10923-16383
  81. ### 5.由于是Redis的Master-Slave之间的关系自动分配,所以需要具体问题具体分析
  82. ## 该Redis-Cluster的配置为:
  83. # Master1:Port6381 --> Slave1:Port6384
  84. # Master2:Port6382 --> Slave2:Port6385
  85. # Master3:Port6383 --> Slave3:Port6386
  86. ## Redis-Cluster 内部两两随机匹配了以后,内部打散了以后,由Redis自行决定Master-Slave的顺序。

1.2.2 Redis 主从容错切换迁移案例

  • 数据读写存储
  1. ### 1.启动6台容器构成的集群并通过 exec 进入
  2. $ docker exec -it redis-node-1 /bin/bash
  3. root@docker:/data# redis-cli -p 6381
  4. 127.0.0.1:6381> keys *
  5. (empty array)
  6. ### 2.对 6381 新增两个 key
  7. # 根据key Hash计算后与16384取模算出对应的槽点值,会有数据存储到其他Node节点上。所以会有key存储失败
  8. 127.0.0.1:6381> set k1 v1
  9. (error) MOVED 12706 139.198.105.99:6383
  10. 127.0.0.1:6381> set k2 v2
  11. OK
  12. 127.0.0.1:6381> set k3 v3
  13. OK
  14. 127.0.0.1:6381> set k4 v4
  15. (error) MOVED 8455 139.198.105.99:6382
  16. ### 防止路由失效加传输 -c 并新增两个 key
  17. [
  18. 127.0.0.1:6381> set k1 v1
  19. (error) MOVED 12706 139.198.105.99:6383
  20. # 加入参数 -c,优化路由
  21. root@docker:/data# redis-cli -c -p 6381
  22. 127.0.0.1:6381> set k1 v1
  23. -> Redirected to slot [12706] located at 139.198.105.99:6383
  24. OK
  25. ]
  26. $ docker exec -it redis-node-1 /bin/bash
  27. root@docker:/data# redis-cli -c -p 6381
  28. # 清除Redis存储的KV
  29. 127.0.0.1:6381> FLUSHALL
  30. OK
  31. 127.0.0.1:6381> set k1 v1
  32. -> Redirected to slot [12706] located at 139.198.105.99:6383
  33. OK
  34. # 会重定向到指定的槽位Node
  35. 139.198.105.99:6383> set k2 v2
  36. -> Redirected to slot [449] located at 139.198.105.99:6381
  37. OK
  38. 139.198.105.99:6381> set k3 v3
  39. OK
  40. 139.198.105.99:6381> set k4 v4
  41. -> Redirected to slot [8455] located at 139.198.105.99:6382
  42. OK
  43. ### 查看集群信息
  44. root@docker:/data# redis-cli --cluster check 139.198.105.99:6381
  45. 139.198.105.99:6381 (daa30850...) -> 2 keys | 5461 slots | 1 slaves.
  46. 139.198.105.99:6382 (a23364d4...) -> 1 keys | 5462 slots | 1 slaves.
  47. 139.198.105.99:6383 (15a33cc8...) -> 1 keys | 5461 slots | 1 slaves.
  48. [OK] 4 keys in 3 masters.
  49. 0.00 keys per slot on average.
  50. >>> Performing Cluster Check (using node 139.198.105.99:6381)
  51. M: daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381
  52. slots:[0-5460] (5461 slots) master
  53. 1 additional replica(s)
  54. S: 2075147d5c9e90275c1eb7e1b1ad32912129a13e 139.198.105.99:6386
  55. slots: (0 slots) slave
  56. replicates 15a33cc85697b68cc897137c863238f412ceff46
  57. S: 48656f0b83bd65f31a4478fc17be8322efcd2ede 139.198.105.99:6385
  58. slots: (0 slots) slave
  59. replicates a23364d45a6d2f56d94021815af2c8f3ef6cb499
  60. S: 150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 139.198.105.99:6384
  61. slots: (0 slots) slave
  62. replicates daa3085081481d5c893f100a4d742a7160a8616c
  63. M: a23364d45a6d2f56d94021815af2c8f3ef6cb499 139.198.105.99:6382
  64. slots:[5461-10922] (5462 slots) master
  65. 1 additional replica(s)
  66. M: 15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383
  67. slots:[10923-16383] (5461 slots) master
  68. 1 additional replica(s)
  69. [OK] All nodes agree about slots configuration.
  70. >>> Check for open slots...
  71. >>> Check slots coverage...
  72. [OK] All 16384 slots covered.
  • 容错切换迁移

Redis 集群主从切换(1号宕机4号上位)

  1. ### 主6381和从机切换,先停止主机6381
  2. $ docker stop redis-node-1
  3. ### 再次查看集群信息
  4. # 使用 redis-node-2 进入 Redis
  5. $ docker exec -it redis-node-2 /bin/bash
  6. root@docker:/data# redis-cli -c -p 6382
  7. 127.0.0.1:6382> cluster nodes
  8. # 139.198.105.99:6381@16381 master,fail 主节点连接失败
  9. daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381@16381 master,fail - 1642226063583 1642226056560 1 disconnected
  10. 15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383@16383 master - 0 1642226096000 3 connected 10923-16383
  11. 48656f0b83bd65f31a4478fc17be8322efcd2ede 139.198.105.99:6385@16385 slave a23364d45a6d2f56d94021815af2c8f3ef6cb499 0 1642226096834 2 connected
  12. 2075147d5c9e90275c1eb7e1b1ad32912129a13e 139.198.105.99:6386@16386 slave 15a33cc85697b68cc897137c863238f412ceff46 0 1642226095826 3 connected
  13. a23364d45a6d2f56d94021815af2c8f3ef6cb499 10.150.22.47:6382@16382 myself,master - 0 1642226094000 2 connected 5461-10922
  14. # 139.198.105.99:6384@16384 master 从节点成主节点
  15. 150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 139.198.105.99:6384@16384 master - 0 1642226095000 7 connected 0-5460
  16. # 6381 宕机了,6384上位成为了新的Master。6381的数据同样存储在6384
  17. # 备注:每次案例下面挂的从机以实际情况为准,具体是几号机器就是几号
  18. 127.0.0.1:6382> get k1
  19. -> Redirected to slot [12706] located at 139.198.105.99:6383
  20. "v1"
  21. 139.198.105.99:6383> get k2
  22. -> Redirected to slot [449] located at 139.198.105.99:6384
  23. "v2"
  24. 139.198.105.99:6384> get k3
  25. "v3"
  26. 139.198.105.99:6384> get k4
  27. -> Redirected to slot [8455] located at 139.198.105.99:6382
  28. "v4"
  29. ### 先还原之前的3主3从
  30. # 先启 6381 --> docker start redis-node-1
  31. # 再停 6384 --> docker stop redis-node-4
  32. # 再启 6384 --> docker start redis-node-4
  33. # 主从机器分配情况以实际情况为准
  34. $ docker start redis-node-1
  35. # 6381 主节点恢复后,角色会变成 Slave,不会自动切换到 Master
  36. 139.198.105.99:6382> cluster nodes
  37. daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381@16381 slave 150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 0 1642226771967 7 connected
  38. 15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383@16383 master - 0 1642226771000 3 connected 10923-16383
  39. 48656f0b83bd65f31a4478fc17be8322efcd2ede 139.198.105.99:6385@16385 slave a23364d45a6d2f56d94021815af2c8f3ef6cb499 0 1642226771018 2 connected
  40. 2075147d5c9e90275c1eb7e1b1ad32912129a13e 139.198.105.99:6386@16386 slave 15a33cc85697b68cc897137c863238f412ceff46 0 1642226771000 3 connected
  41. a23364d45a6d2f56d94021815af2c8f3ef6cb499 10.150.22.47:6382@16382 myself,master - 0 1642226769000 2 connected 5461-10922
  42. 150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 139.198.105.99:6384@16384 master - 0 1642226770000 7 connected 0-5460
  43. -------
  44. $ docker stop redis-node-4
  45. # 6381 主节点会因为 6384 宕机而将角色切换到 Master
  46. 139.198.105.99:6382> cluster nodes
  47. daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381@16381 master - 0 1642227020000 8 connected 0-5460
  48. 15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383@16383 master - 0 1642227021206 3 connected 10923-16383
  49. 48656f0b83bd65f31a4478fc17be8322efcd2ede 139.198.105.99:6385@16385 slave a23364d45a6d2f56d94021815af2c8f3ef6cb499 0 1642227021894 2 connected
  50. 2075147d5c9e90275c1eb7e1b1ad32912129a13e 139.198.105.99:6386@16386 slave 15a33cc85697b68cc897137c863238f412ceff46 0 1642227019000 3 connected
  51. a23364d45a6d2f56d94021815af2c8f3ef6cb499 10.150.22.47:6382@16382 myself,master - 0 1642227018000 2 connected 5461-10922
  52. # 6384 节点宕机
  53. 150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 139.198.105.99:6384@16384 master,fail - 1642227003710 1642226998715 7 disconnected
  54. -------
  55. $ docker start redis-node-4
  56. # 6384 节点的角色恢复到 Slave
  57. 139.198.105.99:6382> cluster nodes
  58. daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381@16381 master - 0 1642227143806 8 connected 0-5460
  59. 15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383@16383 master - 0 1642227144821 3 connected 10923-16383
  60. 48656f0b83bd65f31a4478fc17be8322efcd2ede 139.198.105.99:6385@16385 slave a23364d45a6d2f56d94021815af2c8f3ef6cb499 0 1642227142814 2 connected
  61. 2075147d5c9e90275c1eb7e1b1ad32912129a13e 139.198.105.99:6386@16386 slave 15a33cc85697b68cc897137c863238f412ceff46 0 1642227143000 3 connected
  62. a23364d45a6d2f56d94021815af2c8f3ef6cb499 10.150.22.47:6382@16382 myself,master - 0 1642227142000 2 connected 5461-10922
  63. 150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 139.198.105.99:6384@16384 slave daa3085081481d5c893f100a4d742a7160a8616c 0 1642227143000 8 connected
  64. ### 查看集群状态
  65. root@docker:/data# redis-cli --cluster check 139.198.105.99:6381
  66. 139.198.105.99:6381 (daa30850...) -> 2 keys | 5461 slots | 1 slaves.
  67. 139.198.105.99:6382 (a23364d4...) -> 1 keys | 5462 slots | 1 slaves.
  68. 139.198.105.99:6383 (15a33cc8...) -> 1 keys | 5461 slots | 1 slaves.
  69. [OK] 4 keys in 3 masters.
  70. 0.00 keys per slot on average.
  71. >>> Performing Cluster Check (using node 139.198.105.99:6381)
  72. M: daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381
  73. slots:[0-5460] (5461 slots) master
  74. 1 additional replica(s)
  75. S: 2075147d5c9e90275c1eb7e1b1ad32912129a13e 139.198.105.99:6386
  76. slots: (0 slots) slave
  77. replicates 15a33cc85697b68cc897137c863238f412ceff46
  78. M: a23364d45a6d2f56d94021815af2c8f3ef6cb499 139.198.105.99:6382
  79. slots:[5461-10922] (5462 slots) master
  80. 1 additional replica(s)
  81. M: 15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383
  82. slots:[10923-16383] (5461 slots) master
  83. 1 additional replica(s)
  84. S: 48656f0b83bd65f31a4478fc17be8322efcd2ede 139.198.105.99:6385
  85. slots: (0 slots) slave
  86. replicates a23364d45a6d2f56d94021815af2c8f3ef6cb499
  87. S: 150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 139.198.105.99:6384
  88. slots: (0 slots) slave
  89. replicates daa3085081481d5c893f100a4d742a7160a8616c
  90. [OK] All nodes agree about slots configuration.
  91. >>> Check for open slots...
  92. >>> Check slots coverage...
  93. [OK] All 16384 slots covered.

1.2.3 Redis 主从扩容案例

先恢复到3主3从,Redis集群扩容(新增主机6387无槽号) Master4 —> Port:6387;Slave4 —> Port:6388

先添加 Master4 分配槽位号,再添加 Slave4 于 Master4连接

  1. ### 1.新建6387、6388两个节点+新建后启动+查看是否8节点
  2. docker run -d -it --name redis-node-7 --net host --privileged=true --restart=always -v /myapp/redis/share/redis-node-7:/data redis --cluster-enabled yes --appendonly yes --port 6387
  3. docker run -d -it --name redis-node-8 --net host --privileged=true --restart=always -v /myapp/redis/share/redis-node-8:/data redis --cluster-enabled yes --appendonly yes --port 6388
  4. docker ps
  5. ### 2.进入6387容器实例内部
  6. $ docker exec -it redis-node-7 /bin/bash
  7. ### 3.将新增的6387节点(空槽号)作为Master节点加入原集群
  8. # 将新增的6387作为master节点加入集群
  9. # redis-cli --cluster add-node 自己实际IP地址:6387 自己实际IP地址:6381
  10. # 6387 就是将要作为 master 新增节点
  11. # 6388 就是原来集群节点里面的领路人,相当于6387拜访6381的码头从而找到组织加入集群
  12. $ docker exec -it redis-node-7 /bin/bash
  13. root@docker:/data# redis-cli --cluster add-node 139.198.105.99:6387 139.198.105.99:6381
  14. >>> Adding node 139.198.105.99:6387 to cluster 139.198.105.99:6381
  15. >>> Performing Cluster Check (using node 139.198.105.99:6381)
  16. ......
  17. >>> Send CLUSTER MEET to node 139.198.105.99:6387 to make it join the cluster.
  18. [OK] New node added correctly.
  19. ### 4.检查集群情况
  20. root@docker:/data# redis-cli --cluster check 139.198.105.99:6387
  21. 139.198.105.99:6387 (4cc6ed4c...) -> 0 keys | 0 slots | 0 slaves. "(暂时没有槽位号)"
  22. 139.198.105.99:6383 (15a33cc8...) -> 1 keys | 5461 slots | 1 slaves.
  23. 139.198.105.99:6381 (daa30850...) -> 2 keys | 5461 slots | 1 slaves.
  24. 139.198.105.99:6382 (a23364d4...) -> 1 keys | 5462 slots | 1 slaves.
  25. [OK] 4 keys in 4 masters.
  26. 0.00 keys per slot on average.
  27. >>> Performing Cluster Check (using node 139.198.105.99:6387)
  28. M: 4cc6ed4c3b00d3c9f625f9f83d34420dc34d8033 139.198.105.99:6387
  29. slots: (0 slots) master
  30. M: 15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383
  31. slots:[10923-16383] (5461 slots) master
  32. 1 additional replica(s)
  33. S: 2075147d5c9e90275c1eb7e1b1ad32912129a13e 139.198.105.99:6386
  34. slots: (0 slots) slave
  35. replicates 15a33cc85697b68cc897137c863238f412ceff46
  36. S: 48656f0b83bd65f31a4478fc17be8322efcd2ede 139.198.105.99:6385
  37. slots: (0 slots) slave
  38. replicates a23364d45a6d2f56d94021815af2c8f3ef6cb499
  39. S: 150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 139.198.105.99:6384
  40. slots: (0 slots) slave
  41. replicates daa3085081481d5c893f100a4d742a7160a8616c
  42. M: daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381
  43. slots:[0-5460] (5461 slots) master
  44. 1 additional replica(s)
  45. M: a23364d45a6d2f56d94021815af2c8f3ef6cb499 139.198.105.99:6382
  46. slots:[5461-10922] (5462 slots) master
  47. 1 additional replica(s)
  48. [OK] All nodes agree about slots configuration.
  49. >>> Check for open slots...
  50. >>> Check slots coverage...
  51. [OK] All 16384 slots covered.
  52. ### 5.重新分派槽号
  53. # 重新分派槽号
  54. # 命令:redis-cli --cluster reshard IP地址:端口号
  55. # redis-cli --cluster reshard 139.198.105.99:6381
  56. $ docker exec -it redis-node-1 /bin/bash
  57. root@docker:/data# redis-cli --cluster reshard 139.198.105.99:6381
  58. >>> Performing Cluster Check (using node 139.198.105.99:6387)
  59. M: 4cc6ed4c3b00d3c9f625f9f83d34420dc34d8033 139.198.105.99:6387 # [--> 没有槽号]
  60. slots: (0 slots) master
  61. M: 15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383
  62. slots:[10923-16383] (5461 slots) master
  63. 1 additional replica(s)
  64. ......
  65. replicates daa3085081481d5c893f100a4d742a7160a8616c
  66. M: daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381
  67. slots:[0-5460] (5461 slots) master
  68. 1 additional replica(s)
  69. M: a23364d45a6d2f56d94021815af2c8f3ef6cb499 139.198.105.99:6382
  70. slots:[5461-10922] (5462 slots) master
  71. 1 additional replica(s)
  72. [OK] All nodes agree about slots configuration.
  73. >>> Check for open slots...
  74. >>> Check slots coverage...
  75. [OK] All 16384 slots covered.
  76. How many slots do you want to move (from 1 to 16384)? 4096
  77. # [--> 16384/(Master数)->16384/4]
  78. What is the receiving node ID? 4cc6ed4c3b00d3c9f625f9f83d34420dc34d8033
  79. # [--> 4cc6ed4c3b00d3c9f625f9f83d34420dc34d8033 (redis-node-7 ID号)]
  80. Please enter all the source node IDs.
  81. Type 'all' to use all the nodes as source nodes for the hash slots.
  82. Type 'done' once you entered all the source nodes IDs.
  83. Source node #1: all
  84. ...
  85. Ready to move 4096 slots.
  86. Source nodes:
  87. M: 15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383
  88. slots:[10923-16383] (5461 slots) master
  89. 1 additional replica(s)
  90. M: daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381
  91. slots:[0-5460] (5461 slots) master
  92. 1 additional replica(s)
  93. M: a23364d45a6d2f56d94021815af2c8f3ef6cb499 139.198.105.99:6382
  94. slots:[5461-10922] (5462 slots) master
  95. 1 additional replica(s)
  96. Destination node:
  97. M: 4cc6ed4c3b00
  98. ......
  99. Do you want to proceed with the proposed reshard plan (yes/no)? yes
  100. ......"(等待数据迁移,迁移速度与机器的性能有关)"
  101. ### 6.检查集群情况
  102. # 迁移数据完毕后,之前的节点匀了一部分槽号给新的Master
  103. # redis-cli --cluster check 真实IP地址:6381
  104. root@docker:/data# redis-cli --cluster check 139.198.105.99:6381
  105. 139.198.105.99:6381 (58e4217a...) -> 1 keys | 4096 slots | 1 slaves.
  106. 139.198.105.99:6383 (b67e4f11...) -> 1 keys | 4096 slots | 1 slaves.
  107. 139.198.105.99:6382 (a93838a2...) -> 1 keys | 4096 slots | 1 slaves.
  108. 139.198.105.99:6387 (fb19d789...) -> 1 keys | 4096 slots | 0 slaves.
  109. [OK] 4 keys in 4 masters.
  110. 0.00 keys per slot on average.
  111. >>> Performing Cluster Check (using node 139.198.105.99:6381)
  112. M: 58e4217a0588e05a8806411f61fc54915b781e5a 139.198.105.99:6381
  113. slots:[1365-5460] (4096 slots) master
  114. 1 additional replica(s)
  115. M: b67e4f11be048b6a0796de83b2350e44419621ac 139.198.105.99:6383
  116. slots:[12288-16383] (4096 slots) master
  117. 1 additional replica(s)
  118. M: a93838a2a0df5f771443f2000a650ff49b1a3e7e 139.198.105.99:6382
  119. slots:[6827-10922] (4096 slots) master
  120. 1 additional replica(s)
  121. replicates b67e4f11be048b6a0796de83b2350e44419621ac
  122. M: fb19d789d779dd48ff2d78016a112880f10a3caf 139.198.105.99:6387
  123. slots:[0-1364],[5461-6826],[10923-12287] (4096 slots) master [3 Master匀了一部分槽号给新的Master使用]
  124. ......
  125. [OK] All nodes agree about slots configuration.
  126. >>> Check for open slots...
  127. >>> Check slots coverage...
  128. [OK] All 16384 slots covered.
  129. ### 7.为主节点6387分配从节点6388
  130. # 数据存储在节点上,节点存储在槽上,所以重新分配槽点,原有数据不影响读取
  131. # 命令:redis-cli --cluster add-node Slave-IP:Slave-Port Master-IP:Master-Port --cluster-slave --cluster-master-id 新主机节点ID
  132. # redis-cli --cluster add-node 139.198.105.99:6388 139.198.105.99:6387 --cluster-slave --cluster-master-id fb19d789d779dd48ff2d78016a112880f10a3caf(是6387的ID编号,按照自己实际情况填写)
  133. $ docker exec -it redis-node-7 /bin/bash
  134. root@docker:/data# redis-cli --cluster add-node 139.198.105.99:6388 139.198.105.99:6387 --cluster-slave --cluster-master-id fb19d789d779dd48ff2d78016a112880f10a3caf
  135. >>> Adding node 139.198.105.99:6388 to cluster 139.198.105.99:6387
  136. >>> Performing Cluster Check (using node 139.198.105.99:6387)
  137. M: fb19d789d779dd48ff2d78016a112880f10a3caf 139.198.105.99:6387
  138. slots:[0-1364],[5461-6826],[10923-12287] (4096 slots) master
  139. M: b67e4f11be048b6a0796de83b2350e44419621ac 139.198.105.99:6383
  140. slots:[12288-16383] (4096 slots) master
  141. 1 additional replica(s)
  142. S: bf66a51258fd046b3e3e741d3adc8bdef7b34ffc 139.198.105.99:6384
  143. slots: (0 slots) slave
  144. replicates a93838a2a0df5f771443f2000a650ff49b1a3e7e
  145. S: 1dcd70196a57e2862463938aa776c2ef3555b01c 139.198.105.99:6386
  146. slots: (0 slots) slave
  147. replicates 58e4217a0588e05a8806411f61fc54915b781e5a
  148. S: 264e4329f261c70b3001028334a2fe161f30113d 139.198.105.99:6385
  149. slots: (0 slots) slave
  150. replicates b67e4f11be048b6a0796de83b2350e44419621ac
  151. M: 58e4217a0588e05a8806411f61fc54915b781e5a 139.198.105.99:6381
  152. slots:[1365-5460] (4096 slots) master
  153. 1 additional replica(s)
  154. M: a93838a2a0df5f771443f2000a650ff49b1a3e7e 139.198.105.99:6382
  155. slots:[6827-10922] (4096 slots) master
  156. 1 additional replica(s)
  157. [OK] All nodes agree about slots configuration.
  158. >>> Check for open slots...
  159. >>> Check slots coverage...
  160. [OK] All 16384 slots covered.
  161. >>> Send CLUSTER MEET to node 139.198.105.99:6388 to make it join the cluster.
  162. Waiting for the cluster to join
  163. >>> Configure node as replica of 139.198.105.99:6387.
  164. [OK] New node added correctly.
  165. ### 8.检查集群情况
  166. root@docker:/data# redis-cli --cluster check 139.198.105.99:6381
  167. 139.198.105.99:6381 (58e4217a...) -> 1 keys | 4096 slots | 1 slaves.
  168. 139.198.105.99:6383 (b67e4f11...) -> 1 keys | 4096 slots | 1 slaves.
  169. 139.198.105.99:6382 (a93838a2...) -> 1 keys | 4096 slots | 1 slaves.
  170. 139.198.105.99:6387 (fb19d789...) -> 1 keys | 4096 slots | 1 slaves.
  171. [OK] 4 keys in 4 masters.
  172. 0.00 keys per slot on average.
  173. >>> Performing Cluster Check (using node 139.198.105.99:6381)
  174. M: 58e4217a0588e05a8806411f61fc54915b781e5a 139.198.105.99:6381
  175. slots:[1365-5460] (4096 slots) master
  176. 1 additional replica(s)
  177. M: b67e4f11be048b6a0796de83b2350e44419621ac 139.198.105.99:6383
  178. slots:[12288-16383] (4096 slots) master
  179. 1 additional replica(s)
  180. M: a93838a2a0df5f771443f2000a650ff49b1a3e7e 139.198.105.99:6382
  181. slots:[6827-10922] (4096 slots) master
  182. 1 additional replica(s)
  183. S: 264e4329f261c70b3001028334a2fe161f30113d 139.198.105.99:6385
  184. slots: (0 slots) slave
  185. replicates b67e4f11be048b6a0796de83b2350e44419621ac
  186. M: fb19d789d779dd48ff2d78016a112880f10a3caf 139.198.105.99:6387
  187. slots:[0-1364],[5461-6826],[10923-12287] (4096 slots) master
  188. 1 additional replica(s)
  189. S: 08fc0002346c3dcbc343982388aa78c1d57c7d68 139.198.105.99:6388
  190. slots: (0 slots) slave
  191. replicates fb19d789d779dd48ff2d78016a112880f10a3caf
  192. S: bf66a51258fd046b3e3e741d3adc8bdef7b34ffc 139.198.105.99:6384
  193. slots: (0 slots) slave
  194. replicates a93838a2a0df5f771443f2000a650ff49b1a3e7e
  195. S: 1dcd70196a57e2862463938aa776c2ef3555b01c 139.198.105.99:6386
  196. slots: (0 slots) slave
  197. replicates 58e4217a0588e05a8806411f61fc54915b781e5a
  198. [OK] All nodes agree about slots configuration.
  199. >>> Check for open slots...
  200. >>> Check slots coverage...
  201. [OK] All 16384 slots covered.
  202. # 测试数据是否存在
  203. root@docker:/data# redis-cli -c -p 6381
  204. 127.0.0.1:6381> get k1
  205. -> Redirected to slot [12706] located at 139.198.105.99:6383
  206. "v1"
  207. 139.198.105.99:6383> get k2
  208. -> Redirected to slot [449] located at 139.198.105.99:6387
  209. "v2"
  210. 139.198.105.99:6387> get k3
  211. -> Redirected to slot [4576] located at 139.198.105.99:6381
  212. "v3"
  213. 139.198.105.99:6381> get k4
  214. -> Redirected to slot [8455] located at 139.198.105.99:6382
  215. "v4"

:::warning

槽号分派说明

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

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

:::

1.2.4 Redis 主从缩容案例

Redis 集群缩容,删除6387和6388,恢复3主3从

:::warning

  1. 先清除从节点6388
  2. 清出来的槽号重新分配
  3. 再删除6387
  4. 恢复成3主3从

:::

  1. ### 1.目的:6387和6388下线
  2. $ docker exec -it redis-node-1 /bin/bash
  3. --------
  4. ### 2.检查集群情况1获得6388节点ID
  5. root@docker:/data# redis-cli --cluster check 139.198.105.99:6381
  6. 139.198.105.99:6381 (58e4217a...) -> 1 keys | 4096 slots | 1 slaves.
  7. 139.198.105.99:6383 (b67e4f11...) -> 1 keys | 4096 slots | 1 slaves.
  8. 139.198.105.99:6382 (a93838a2...) -> 1 keys | 4096 slots | 1 slaves.
  9. 139.198.105.99:6387 (fb19d789...) -> 1 keys | 4096 slots | 1 slaves.
  10. [OK] 4 keys in 4 masters.
  11. 0.00 keys per slot on average.
  12. >>> Performing Cluster Check (using node 139.198.105.99:6381)
  13. M: 58e4217a0588e05a8806411f61fc54915b781e5a 139.198.105.99:6381
  14. slots:[1365-5460] (4096 slots) master
  15. 1 additional replica(s)
  16. M: b67e4f11be048b6a0796de83b2350e44419621ac 139.198.105.99:6383
  17. slots:[12288-16383] (4096 slots) master
  18. 1 additional replica(s)
  19. M: a93838a2a0df5f771443f2000a650ff49b1a3e7e 139.198.105.99:6382
  20. slots:[6827-10922] (4096 slots) master
  21. 1 additional replica(s)
  22. M: fb19d789d779dd48ff2d78016a112880f10a3caf 139.198.105.99:6387
  23. slots:[0-1364],[5461-6826],[10923-12287] (4096 slots) master
  24. 1 additional replica(s)
  25. S: 08fc0002346c3dcbc343982388aa78c1d57c7d68 139.198.105.99:6388
  26. slots: (0 slots) slave
  27. replicates fb19d789d779dd48ff2d78016a112880f10a3caf
  28. ......
  29. [OK] All nodes agree about slots configuration.
  30. >>> Check for open slots...
  31. >>> Check slots coverage...
  32. [OK] All 16384 slots covered.
  33. --------
  34. ### 3.将6388删除,从集群中将4号从节点6388删除
  35. # 命令:redis-cli --cluster del-node Slave-IP:Slave-Port 从机6388节点ID
  36. # redis-cli --cluster del-node 139.198.105.99:6388 08fc0002346c3dcbc343982388aa78c1d57c7d68
  37. root@docker:/data# redis-cli --cluster del-node 139.198.105.99:6388 08fc0002346c3dcbc343982388aa78c1d57c7d68
  38. --------
  39. # 检查集群情况1
  40. root@docker:/data# redis-cli --cluster check 139.198.105.99:6381
  41. 139.198.105.99:6381 (58e4217a...) -> 1 keys | 4096 slots | 1 slaves.
  42. 139.198.105.99:6383 (b67e4f11...) -> 1 keys | 4096 slots | 1 slaves.
  43. 139.198.105.99:6382 (a93838a2...) -> 1 keys | 4096 slots | 1 slaves.
  44. 139.198.105.99:6387 (fb19d789...) -> 1 keys | 4096 slots | 0 slaves.
  45. # 检查一下发现,6388被删除了,只剩下7台机器了
  46. --------
  47. ### 4.将6387的槽号清空,重新分配,本例将清出来的槽号都给6381
  48. root@docker:/data# redis-cli --cluster reshard 139.198.105.99:6381
  49. ......
  50. How many slots do you want to move (from 1 to 16384)? 4096 "[--> 6381的节点,由它来接手空出来的槽号(共4096个)]"
  51. What is the receiving node ID? 58e4217a0588e05a8806411f61fc54915b781e5a "[--> 6381的节点ID]"
  52. Please enter all the source node IDs.
  53. Type 'all' to use all the nodes as source nodes for the hash slots.
  54. Type 'done' once you entered all the source nodes IDs.
  55. Source node #1: fb19d789d779dd48ff2d78016a112880f10a3caf "[--> 6387的节点ID,告知删除该节点]"
  56. Source node #2: done
  57. Do you want to proceed with the proposed reshard plan (yes/no)? yes
  58. ......(等待数据迁移,迁移速度与机器的性能有关)
  59. --------
  60. ### 5.检查集群情况2
  61. root@docker:/data# redis-cli --cluster check 139.198.105.99:6381
  62. 139.198.105.99:6381 (58e4217a...) -> 2 keys | 8192 slots | 2 slaves.
  63. 139.198.105.99:6383 (b67e4f11...) -> 1 keys | 4096 slots | 1 slaves.
  64. 139.198.105.99:6382 (a93838a2...) -> 1 keys | 4096 slots | 1 slaves.
  65. [OK] 4 keys in 3 masters.
  66. 0.00 keys per slot on average.
  67. >>> Performing Cluster Check (using node 139.198.105.99:6381)
  68. M: 58e4217a0588e05a8806411f61fc54915b781e5a 139.198.105.99:6381
  69. slots:[0-6826],[10923-12287] (8192 slots) master
  70. 2 additional replica(s)
  71. M: b67e4f11be048b6a0796de83b2350e44419621ac 139.198.105.99:6383
  72. slots:[12288-16383] (4096 slots) master
  73. 1 additional replica(s)
  74. M: a93838a2a0df5f771443f2000a650ff49b1a3e7e 139.198.105.99:6382
  75. slots:[6827-10922] (4096 slots) master
  76. 1 additional replica(s)
  77. S: 264e4329f261c70b3001028334a2fe161f30113d 139.198.105.99:6385
  78. slots: (0 slots) slave
  79. replicates b67e4f11be048b6a0796de83b2350e44419621ac
  80. S: fb19d789d779dd48ff2d78016a112880f10a3caf 139.198.105.99:6387
  81. slots: (0 slots) slave
  82. replicates 58e4217a0588e05a8806411f61fc54915b781e5a
  83. S: bf66a51258fd046b3e3e741d3adc8bdef7b34ffc 139.198.105.99:6384
  84. slots: (0 slots) slave
  85. replicates a93838a2a0df5f771443f2000a650ff49b1a3e7e
  86. S: 1dcd70196a57e2862463938aa776c2ef3555b01c 139.198.105.99:6386
  87. slots: (0 slots) slave
  88. replicates 58e4217a0588e05a8806411f61fc54915b781e5a
  89. [OK] All nodes agree about slots configuration.
  90. >>> Check for open slots...
  91. >>> Check slots coverage...
  92. [OK] All 16384 slots covered.
  93. ## 4096个槽位都指给了6381,它变成了8192个槽位,相当于全部都给6381了,不让要输入3次,一锅端。
  94. --------
  95. ### 6.将6387删除
  96. # 命令:redis-cli --cluster del-node Master-IP:Master-Port 主Master-6387节点ID
  97. # redis-cli --cluster del-node 139.198.105.99:6387 fb19d789d779dd48ff2d78016a112880f10a3caf
  98. root@docker:/data# redis-cli --cluster del-node 139.198.105.99:6387 fb19d789d779dd48ff2d78016a112880f10a3caf
  99. ------------------------------------------------------------------------------------------------------------
  100. ### 7.检查集群情况3
  101. root@docker:/data# redis-cli --cluster check 139.198.105.99:6381
  102. 139.198.105.99:6381 (58e4217a...) -> 2 keys | 8192 slots | 1 slaves.
  103. 139.198.105.99:6383 (b67e4f11...) -> 1 keys | 4096 slots | 1 slaves.
  104. 139.198.105.99:6382 (a93838a2...) -> 1 keys | 4096 slots | 1 slaves.
  105. [OK] 4 keys in 3 masters.
  106. 0.00 keys per slot on average.
  107. >>> Performing Cluster Check (using node 139.198.105.99:6381)
  108. M: 58e4217a0588e05a8806411f61fc54915b781e5a 139.198.105.99:6381
  109. slots:[0-6826],[10923-12287] (8192 slots) master
  110. 1 additional replica(s)
  111. M: b67e4f11be048b6a0796de83b2350e44419621ac 139.198.105.99:6383
  112. slots:[12288-16383] (4096 slots) master
  113. 1 additional replica(s)
  114. M: a93838a2a0df5f771443f2000a650ff49b1a3e7e 139.198.105.99:6382
  115. slots:[6827-10922] (4096 slots) master
  116. 1 additional replica(s)
  117. S: 264e4329f261c70b3001028334a2fe161f30113d 139.198.105.99:6385
  118. slots: (0 slots) slave
  119. replicates b67e4f11be048b6a0796de83b2350e44419621ac
  120. S: bf66a51258fd046b3e3e741d3adc8bdef7b34ffc 139.198.105.99:6384
  121. slots: (0 slots) slave
  122. replicates a93838a2a0df5f771443f2000a650ff49b1a3e7e
  123. S: 1dcd70196a57e2862463938aa776c2ef3555b01c 139.198.105.99:6386
  124. slots: (0 slots) slave
  125. replicates 58e4217a0588e05a8806411f61fc54915b781e5a
  126. [OK] All nodes agree about slots configuration.
  127. >>> Check for open slots...
  128. >>> Check slots coverage...
  129. [OK] All 16384 slots covered.

Redis 存储数据的算法有:哈希取余分区,一致性哈希算法分区,哈希槽分区。

Redis 集群动态扩缩容,类似于弹性云的思想。需要使用的时候就进行扩容,不需要使用的时候进行缩容。

讲师邮箱:zzyybs@126.com

2 Dockerfile 解析

2.1 Dockerfile 是什么

:::warning

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

:::

概述

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图16

官网

  1. https://docs.docker.com/engine/reference/builder/

构建三步骤

  • 编写Dockerfile文件
  • docker build 命令构建镜像
  • docker run 依镜像运行容器实例

2.2 Dockerfile 构建过程解析

2.2.1 Dockerfile 内容基础知识

:::warning

  1. 每条保留字指令都**<font style="color:#E8323C;">必须为大写字母</font>**并且后面要跟随至少一个参数
  2. 指令按照从上到下,顺序执行
  3. 表示注释

  4. 每条指令都会创建一个新的镜像层并对镜像进行提交

:::

2.2.2 Docker 执行 Dockerfile 大致流程

:::warning

  1. docker 从基础镜像运行一个容器
  2. 执行一条指令并对容器作出修改
  3. 执行类似 docker commit 的操作提交一个新的镜像层
  4. docker 再基于刚提交的镜像运行一个新容器
  5. 执行 dockerfile 中的下一条指令直到所有指令都执行完成
  6. 最后将所有指令汇集的容器执行 docker commit 操作提交镜像
  7. 并将最后所有指令汇集的容器删除

:::

2.2.3 小总结

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

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

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

:::

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图17

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

2.3 Dockerfile 常用保留字指令

:::warning

参考 tomcat 8 的 dockerfile 入门 —> https://github.com/docker-library/tomcat DockerHub 使用 docker run 运行的镜像,进行反解析之后就是 Dockerfile ::: dockerfile # https://github.com/docker-library/tomcat/blob/master/10.0/jdk8/corretto-al2/Dockerfile # # NOTE: THIS DOCKERFILE IS GENERATED VIA "apply-templates.sh" # # PLEASE DO NOT EDIT IT DIRECTLY. # FROM amazoncorretto:8-al2-jdk ENV CATALINA_HOME /usr/local/tomcat ENV PATH $CATALINA_HOME/bin:$PATH RUN mkdir -p "$CATALINA_HOME" WORKDIR $CATALINA_HOME # let "Tomcat Native" live somewhere isolated ENV TOMCAT_NATIVE_LIBDIR $CATALINA_HOME/native-jni-lib ENV LD_LIBRARY_PATH ${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$TOMCAT_NATIVE_LIBDIR # see https://www.apache.org/dist/tomcat/tomcat-10/KEYS # see also "versions.sh" (https://github.com/docker-library/tomcat/blob/master/versions.sh) ENV GPG_KEYS A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 ENV TOMCAT_MAJOR 10 ENV TOMCAT_VERSION 10.0.23 ENV TOMCAT_SHA512 0e0263e8280f2ccfb4bef916444a6105fef689a3d95c334c8a7bfe59f1e3966d48ea624727f1818a4df331a603f1ac5e21b908dda3cae676ddc1aef90c2d12ab RUN set -eux; \ \ # http://yum.baseurl.org/wiki/YumDB.html if ! command -v yumdb > /dev/null; then \ yum install -y --setopt=skip_missing_names_on_install=False yum-utils; \ yumdb set reason dep yum-utils; \ fi; \ # a helper function to "yum install" things, but only if they aren't installed (and to set their "reason" to "dep" so "yum autoremove" can purge them for us) _yum_install_temporary() { ( set -eu +x; \ local pkg todo=''; \ for pkg; do \ if ! rpm --query "$pkg" > /dev/null 2>&1; then \ todo="$todo $pkg"; \ fi; \ done; \ if [ -n "$todo" ]; then \ set -x; \ yum install -y --setopt=skip_missing_names_on_install=False $todo; \ yumdb set reason dep $todo; \ fi; \ ) }; \ _yum_install_temporary gzip tar; \ \ ddist() { \ local f="$1"; shift; \ local distFile="$1"; shift; \ local mvnFile="${1:-}"; \ local success=; \ local distUrl=; \ for distUrl in \ # https://issues.apache.org/jira/browse/INFRA-8753?focusedCommentId=14735394#comment-14735394 "https://www.apache.org/dyn/closer.cgi?action=download&filename=$distFile" \ # if the version is outdated (or we're grabbing the .asc file), we might have to pull from the dist/archive :/ "https://downloads.apache.org/$distFile" \ "https://www-us.apache.org/dist/$distFile" \ "https://www.apache.org/dist/$distFile" \ "https://archive.apache.org/dist/$distFile" \ # if all else fails, let's try Maven (https://www.mail-archive.com/users@tomcat.apache.org/msg134940.html; https://mvnrepository.com/artifact/org.apache.tomcat/tomcat; https://repo1.maven.org/maven2/org/apache/tomcat/tomcat/) ${mvnFile:+"https://repo1.maven.org/maven2/org/apache/tomcat/tomcat/$mvnFile"} \ ; do \ if curl -fL -o "$f" "$distUrl" && [ -s "$f" ]; then \ success=1; \ break; \ fi; \ done; \ [ -n "$success" ]; \ }; \ \ ddist 'tomcat.tar.gz' "tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz" "$TOMCAT_VERSION/tomcat-$TOMCAT_VERSION.tar.gz"; \ echo "$TOMCAT_SHA512 *tomcat.tar.gz" | sha512sum --strict --check -; \ ddist 'tomcat.tar.gz.asc' "tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz.asc" "$TOMCAT_VERSION/tomcat-$TOMCAT_VERSION.tar.gz.asc"; \ export GNUPGHOME="$(mktemp -d)"; \ for key in $GPG_KEYS; do \ gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key"; \ done; \ gpg --batch --verify tomcat.tar.gz.asc tomcat.tar.gz; \ tar -xf tomcat.tar.gz --strip-components=1; \ rm bin/*.bat; \ rm tomcat.tar.gz*; \ command -v gpgconf && gpgconf --kill all || :; \ rm -rf "$GNUPGHOME"; \ \ # https://tomcat.apache.org/tomcat-9.0-doc/security-howto.html#Default_web_applications mv webapps webapps.dist; \ mkdir webapps; \ # we don't delete them completely because they're frankly a pain to get back for users who do want them, and they're generally tiny (~7MB) \ nativeBuildDir="$(mktemp -d)"; \ tar -xf bin/tomcat-native.tar.gz -C "$nativeBuildDir" --strip-components=1; \ _yum_install_temporary \ apr-devel \ gcc \ make \ openssl11-devel \ ; \ ( \ export CATALINA_HOME="$PWD"; \ cd "$nativeBuildDir/native"; \ aprConfig="$(command -v apr-1-config)"; \ ./configure \ --libdir="$TOMCAT_NATIVE_LIBDIR" \ --prefix="$CATALINA_HOME" \ --with-apr="$aprConfig" \ --with-java-home="$JAVA_HOME" \ --with-ssl \ ; \ nproc="$(nproc)"; \ make -j "$nproc"; \ make install; \ ); \ rm -rf "$nativeBuildDir"; \ rm bin/tomcat-native.tar.gz; \ \ # mark any explicit dependencies as manually installed find "$TOMCAT_NATIVE_LIBDIR" -type f -executable -exec ldd '{}' ';' \ | awk '/=>/ && $(NF-1) != "=>" { print $(NF-1) }' \ | xargs -rt readlink -e \ | sort -u \ | xargs -rt rpm --query --whatprovides \ | sort -u \ | tee "$TOMCAT_NATIVE_LIBDIR/.dependencies.txt" \ | xargs -r yumdb set reason user \ ; \ \ # clean up anything added temporarily and not later marked as necessary yum autoremove -y; \ yum clean all; \ rm -rf /var/cache/yum; \ \ # sh removes env vars it doesn't support (ones with periods) # https://github.com/docker-library/tomcat/issues/77 find ./bin/ -name '*.sh' -exec sed -ri 's|^#!/bin/sh$|#!/usr/bin/env bash|' '{}' +; \ \ # fix permissions (especially for running as non-root) # https://github.com/docker-library/tomcat/issues/35 chmod -R +rX .; \ chmod 777 logs temp work; \ \ # smoke test catalina.sh version # verify Tomcat Native is working properly RUN set -eux; \ nativeLines="$(catalina.sh configtest 2>&1)"; \ nativeLines="$(echo "$nativeLines" | grep 'Apache Tomcat Native')"; \ nativeLines="$(echo "$nativeLines" | sort -u)"; \ if ! echo "$nativeLines" | grep -E 'INFO: Loaded( APR based)? Apache Tomcat Native library' >&2; then \ echo >&2 "$nativeLines"; \ exit 1; \ fi EXPOSE 8080 CMD ["catalina.sh", "run"] ### 2.3.1 FROM 基础镜像,当前新镜像是基于哪个镜像的,指定一个已经存在的镜像作为模板,第一条必须是 from

2.3.2 MAINTAINER

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

2.3.3 RUN

容器构建时需要运行的命令(容器启动前做的一下预操作),在 docker build 命令构建镜像的时候,就会执行RUN的部分

两种格式

  • Shell 格式
  1. RUN <命令行命令>
  2. # <命令行命令> 等同于,在终端操作的 shell 指令
  3. # 例如:
  4. RUN yum install -y vim
  • exec 格式
  1. RUN ["可执行文件", "参数1", "参数2"]
  2. # 例如:
  3. # RUN ["./test.php", "dev", "offline"] 等价于 RUN ./test.php dev offline

RUN 是在 docker build 时运行

2.3.4 EXPOSE

EXPOSE [/…]
EXPOSE 定义说明里面的服务端口。 该指令通知 Docker 容器在运行时侦听指定的网络端口。您可以指定端口是在 TCP 还是 UDP 上侦听,如果未指定协议,则默认值为 TCP。EXPOSE。 该指令实际上并不发布端口。它充当构建映像的人员和运行容器的人员之间的一种文档类型,有关要发布哪些端口。若要在运行容器时实际发布端口,请使用 标志 on 发布和映射一个或多个端口,或使用标志发布所有公开的端口并将其映射到高阶端口。EXPOSE-pdocker run-P

2.3.5 WORKDIR

WORKDIR /path/to/workdir
指定在创建容器后,终端默认登录的进来工作目录,一个落脚点

2.3.6 USER

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

2.3.7 ENV

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

ENV MY_PATH /usr/mytest

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

也可以在其他指令中直接使用这些环境变量

比如:WORKDIR $MY_PATH

2.3.8 ADD

ADD [—chown=:] ADD [—chown=:] [““,… ““]
将宿主机目录下的文件拷贝进镜像并且会自动处理 URL 和解压 tar 压缩包。该功能仅在用于构建 Linux 容器的 Dockerfiles 上受支持,在 Windows 容器上不起作用。由于用户和组所有权概念不会在 Linux 和 Windows 之间转换,因此使用 和 用于将用户和组名称转换为 ID 会将此功能限制为仅适用于基于 Linux 操作系统的容器。 如果 是可识别的压缩格式(标识、gzip、bzip2 或 xz)的本地 tar 存档,则将其解压缩为目录。来自远程 URL 的资源不会解压缩。当复制或解压缩目录时,它的行为与 相同,结果是:tar -x

ADD 命令支持将远程URL的资源,但是 Docker 官方不建议直接用远程url,所以还是先下载到主机

是 COPY 的升级版。

2.3.9 COPY

类似 ADD,拷贝文件和目录到镜像中 将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的<目标路径>位置

:::warning COPY src dest

COPY [“src”, “dest”]

:源文件或者源目录

:容器内的指定路径,该路径不用事先建好,路径不存在的话,会自动创建

:::

2.3.10 VOLUME

VOLUME [“/data”]
容器数据卷,用于数据保存和持久化工作 该指令创建具有指定名称的装入点,并将其标记为保存来自本机主机或其他容器的外部装入卷。该值可以是 JSON 数组,也可以是具有多个参数(如 或 )的纯字符串。有关通过 Docker 客户端的更多信息/示例和挂载说明,请参阅通过卷共享目录文档。
VOLUME VOLUME [“/var/log/“] VOLUME /var/log VOLUME /var/log /var/db

2.3.11 CMD

**<font style="color:#E8323C;">指定容器启动(docker run)后的要干的事情</font>**

CMD 容器启动命令

CMD 指令的格式和 RUN 相似,也是两种格式:

  • shell 格式:CMD <命令>
  • exec 格式:CMD [“可执行文件”, “参数1”, “参数2”, ……]
  • 参数列表格式:CMD[“参数1”, “参数2” ……]。在指定了 ENTRYPOINT 指定后,用 CMD 指定具体的参数

注意

Dockerfile 中可以有很多个 CMD 指令,<font style="color:#E8323C;">但是只有最后一个生效,CMD会被docker run 之后的参数替换</font>

参考官网Tomcat 的 dockerfile 演示介绍

  1. # 官网Dockerfile文件内容
  2. ......
  3. EXPOSE 8080
  4. CMD ["catalina.sh", "run"]
演示覆盖操作
  1. $ docker run -it -p 8080:8080 -d billygoo/tomcat8-jdk8:latest /bin/bash
  2. # 浏览器将无法访问8080 Tomcat 默认网页

它和前面 RUN 命令的区别

**<font style="color:#E8323C;">CMD 是在 docker run 时运行</font>**

**<font style="color:#E8323C;">RUN 是在 docker build 时运行</font>**

2.3.12 ENTRYPOINT

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

类似于 CMD 指令,但是 <font style="color:#E8323C;">ENTRYPOINT 不会被 docker run 后面的命令覆盖,而且这些命令行参数会被当做参数送给 ENTRYPOINT 指令指定的程序</font>

命令格式和案例说明

  1. 命令格式:
  2. ENTRYPOINT ["executable", "param1", "param2"]
  3. ENTRYPOINT 可以和 CMD 一起使用,一般是"变参"才会使用 CMD,这里的 CMD 等于是在给 ENTRYPOINT 传参
  4. 当指定了 ENTRYPOINT 后,CMD 的含义就发生了变化,
  5. 不再是直接运行其命令而是将 CMD 的内容作为参数传递给 ENTRYPOINT 指令,它两个组合会变成 <ENTRYPOINT> "<CMD>"
  6. 案例如下:假设已通过Dockerfile 构建了 "nginx:test 镜像"
  7. FROM nginx
  8. EXPOSE 80
  9. ENTRYPOINT ["nginx", "-c"] # 定参
  10. CMD ["/etc/nginx/nginx.conf"] # 变参
是否传参 按照dockerfile编写执行 传参运行
Docker命令 docker run nginx:test docker run nginx:test /etc/nginx/new.conf
衍生出的实际命令 nginx -c /etc/nginx/nginx.conf nginx -c /etc/nginx/new.conf

优点:在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数。

注意:如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。

CMD 和 ENTRYPOINT 区别

:::warning CMD # 指定这个容器启动的时候要运行的命令,不可以追加命令

ENTRYPOINT # 指定这个容器启动的时候要运行的命令,可以追加命令

:::

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图18

2.3.13 小总结

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图19

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图20

2.4 案例

2.4.1 自定义镜像 mycentos-java8

范例:centos 原始镜像

  1. $ docker pull centos
  2. $ docker images centos
  3. REPOSITORY TAG IMAGE ID CREATED SIZE
  4. centos latest 5d0da3dc9764 4 months ago 231MB

准备编写 Dockerfile 文件(大写字母 D)

  1. $ mkdir -pv /data/Dockerfile/mycentos-java8
  2. # 将 jdk-8u301-linux-x64.tar.gz 存放在该目录下
  3. $ tee > /data/Dockerfile/mycentos-java8/Dockerfile <<-'EOF'
  4. FROM centos
  5. MAINTAINER zhongzhiwei <93552399@qq.com>
  6. ENV MYPATH /usr/local
  7. WORKDIR ${MYPATH}
  8. # 安装 VIM 编辑器
  9. RUN yum install -y vim
  10. # 安装 ifconfig 命令查看网络IP
  11. RUN yum install -y net-tools
  12. # 安装 Java 8 以及 lib 库
  13. RUN yum install -y glibc.i686
  14. RUN mkdir -p /usr/local/java
  15. # ADD 是相对路径 jar,把jdk-8u301-linux-x64.tar.gz添加到容器中,安装包必须要和Dockerfile文件在同一位置
  16. ADD jdk-8u301-linux-x64.tar.gz /usr/local/java
  17. # 配置java环境变量
  18. ENV JAVA_HOME /usr/local/java/jdk1.8.0_301
  19. ENV JRE_HOME $JAVA_HOME/jre
  20. ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
  21. ENV PATH $JAVA_HOME/bin:$PATH
  22. EXPOSE 80
  23. CMD echo ${MYPATH}"
  24. CMD echo "Success----------OK"
  25. CMD /bin/bash
  26. EOF
  27. ##################################################################################################
  28. FROM centos:centos7.9.2009
  29. MAINTAINER zhongzhiwei <zhongzhiwei@kubesphere.io>
  30. LABEL name="zhongzhiwei"
  31. LABEL email="zhongzhiwei@kubesphere.io"
  32. ENV MYPATH /usr/local
  33. WORKDIR ${MYPATH}
  34. # 安装VIM编辑器
  35. RUN yum install -y vim
  36. # 安装ifconfig查看网络IP
  37. RUN yum install -y net-tools
  38. # 安装java8以及lib库
  39. RUN yum install -y glibc.i686
  40. # ADD 是相对路径 jar,把jdk-8u301-linux-x64.tar.gz添加到容器中,安装包必须要和Dockerfile文件在同一位置
  41. ADD jdk-8u301-linux-x64.tar.gz /usr/local/
  42. RUN ln -s /usr/local/jdk1.8.0_301 /usr/local/java
  43. # 配置java环境变量
  44. ENV JAVA_HOME /usr/local/java
  45. ENV JRE_HOME ${JAVA_HOME}/jre
  46. ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
  47. ENV PATH $JAVA_HOME/bin:$PATH
  48. EXPOSE 80
  49. RUN echo "WORKDIR=${MYPATH}"
  50. RUN echo "Success-----------------OK"
  51. CMD [ "/bin/bash" ]
  • 构建
  1. # docker build -t 新镜像名字:TAG .
  2. # 注意,上面TAG后面的有个空格,有个"."
  3. # .号是指镜像构建时打包上传到Docker引擎中的文件的目录,不是本机目录
  4. $ cd /data/Dockerfile/mycentos-java8/
  5. $ docker build -t mycentos-java8:v1.0 .
  • 运行
  1. # docker run -it 新镜像名字:TAG
  2. $ docker images
  3. REPOSITORY TAG IMAGE ID CREATED SIZE
  4. mycentos-java8 v1.0 703a23d519ba 7 minutes ago 759MB
  5. $ docker history mycentos-java8:v1.0
  6. $ docker run -it --name mycentos mycentos-java8:v1.0 /bin/bash
  7. [root@e61b71723942 local]# pwd
  8. /usr/local
  9. [root@77bee12e9cd0 local]# vim
  10. vim vimdiff vimtutor
  11. [root@77bee12e9cd0 local]# ifconfig
  12. eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
  13. inet 172.17.0.12 netmask 255.255.0.0 broadcast 172.17.255.255
  14. ether 02:42:ac:11:00:0c txqueuelen 0 (Ethernet)
  15. RX packets 8 bytes 656 (656.0 B)
  16. RX errors 0 dropped 0 overruns 0 frame 0
  17. TX packets 0 bytes 0 (0.0 B)
  18. TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
  19. lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
  20. inet 127.0.0.1 netmask 255.0.0.0
  21. loop txqueuelen 1000 (Local Loopback)
  22. RX packets 0 bytes 0 (0.0 B)
  23. RX errors 0 dropped 0 overruns 0 frame 0
  24. TX packets 0 bytes 0 (0.0 B)
  25. TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
  26. [root@77bee12e9cd0 local]# java -version
  27. java version "1.8.0_301"
  28. Java(TM) SE Runtime Environment (build 1.8.0_301-b09)
  29. Java HotSpot(TM) 64-Bit Server VM (build 25.301-b09, mixed mode)
  • 再体会下 UnionFS(联合文件系统)

UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层,轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层一层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite serveral directories into a single virtual filesystem)。UnionFS 文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。

特性:一次同时加载多个文件系统,但是从外面看起来,只能看到一个文件系统,联合加载会把各种文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。

2.4.2 虚悬镜像

2.4.2.1 虚悬镜像是什么

在 构建镜像,和删除镜像的时候出现一些错误,导致仓库名、标签都是 的镜像,俗称 dangling image。

Dockerfile 写一个

  1. $ tee > Dockerfile <<-'EOF'
  2. # dangling image
  3. FROM ubuntu
  4. CMD echo 'action is success'
  5. EOF
  6. $ docker build .
  7. $ docker images
  8. REPOSITORY TAG IMAGE ID CREATED SIZE
  9. <none> <none> bfe4d15873ec 5 seconds ago 72.8MB

2.4.2.2 查看虚悬镜像

  1. $ docker image ls -f dangling=true
  2. # 命令结果
  3. $ docker image ls -f dangling=true
  4. REPOSITORY TAG IMAGE ID CREATED SIZE
  5. <none> <none> bfe4d15873ec 50 seconds ago 72.8MB

2.4.2.3 删除虚悬镜像

  1. $ docker image prune
  2. # 虚悬镜像已经失去存在价值,可以删除
  3. $ docker image ls -f dangling=true
  4. REPOSITORY TAG IMAGE ID CREATED SIZE
  5. <none> <none> bfe4d15873ec 50 seconds ago 72.8MB
  6. $ docker image prune
  7. WARNING! This will remove all dangling images.
  8. Are you sure you want to continue? [y/N] y
  9. Deleted Images:
  10. deleted: sha256:bfe4d15873eccee446d23e20acb01cf720c1d2b0590ceb12cc65211bcd0e83ec
  11. Total reclaimed space: 0B
  12. $ docker image ls -f dangling=true

2.4.3 自定义镜像 myubuntu-java8

  • 编写:准备编写 Dockerfile 文件
  1. $ mkdir -pv /data/Dockerfile/myubuntu-java8
  2. # 将 jdk-8u301-linux-x64.tar.gz 存放在该目录下
  3. $ tee > /data/Dockerfile/myubuntu-java8/Dockerfile <<-'EOF'
  4. FROM ubuntu:20.04
  5. MAINTAINER zhongzhiwei <zhongzhiwei@kubesphere.io>
  6. LABEL name="zhongzhiwei"
  7. LABEL email="zhongzhiwei@kubesphere.io"
  8. ENV MYPATH /usr/local/
  9. WORKDIR ${MYPATH}
  10. # 修改APT为阿里云源
  11. RUN sed -ir 's#http://(archive|security).ubuntu.com#http://mirrors.aliyun.com#ig' /etc/apt/sources.list
  12. # 安装VIM编辑器
  13. RUN apt-get update && \
  14. apt-get install -y vim
  15. # 安装ifconfig查看网络IP
  16. RUN apt-get install -y net-tools iproute2 inetutils-ping
  17. # ADD 是相对路径 jar,把jdk-8u301-linux-x64.tar.gz添加到容器中,安装包必须要和Dockerfile文件在同一位置
  18. ADD jdk-8u301-linux-x64.tar.gz /usr/local/
  19. RUN ln -sv /usr/local/jdk1.8.0_301 /usr/local/java
  20. # 配置java环境变量
  21. ENV JAVA_HOME /usr/local/java
  22. ENV JRE_HOME ${JAVA_HOME}/jre
  23. ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
  24. ENV PATH $JAVA_HOME/bin:$PATH
  25. EXPOSE 80
  26. RUN echo "WORKDIR=${MYPATH}"
  27. RUN echo "Success-----------------OK"
  28. CMD [ "/bin/bash" ]
  29. EOF
  • 构建:docker build -t 新镜像名字:TAG .
  1. # docker build -t 新镜像名字:TAG .
  2. # 注意,上面TAG后面的有个空格,有个"."
  3. # .号是指镜像构建时打包上传到Docker引擎中的文件的目录,不是本机目录
  4. $ cd /data/Dockerfile/myubuntu-java8/
  5. $ docker build -t myubuntu-java8:v1.0 .
  • 运行:docker run 新镜像名字:TAG
  1. $ docker run -it --name myubuntu myubuntu-java8:1.0
  2. root@7d64b9ac0320:/usr/local# pwd
  3. /usr/local
  4. root@7d64b9ac0320:/usr/local# java -version
  5. java version "1.8.0_301"
  6. Java(TM) SE Runtime Environment (build 1.8.0_301-b09)
  7. Java HotSpot(TM) 64-Bit Server VM (build 25.301-b09, mixed mode)
  8. root@7d64b9ac0320:/usr/local# vim
  9. vim vim.basic vimdiff vimtutor
  10. root@7d64b9ac0320:/usr/local# ifconfig
  11. eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
  12. inet 172.17.0.8 netmask 255.255.0.0 broadcast 172.17.255.255
  13. ether 02:42:ac:11:00:08 txqueuelen 0 (Ethernet)
  14. RX packets 8 bytes 656 (656.0 B)
  15. RX errors 0 dropped 0 overruns 0 frame 0
  16. TX packets 0 bytes 0 (0.0 B)
  17. TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
  18. lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
  19. inet 127.0.0.1 netmask 255.0.0.0
  20. loop txqueuelen 1000 (Local Loopback)
  21. RX packets 0 bytes 0 (0.0 B)
  22. RX errors 0 dropped 0 overruns 0 frame 0
  23. TX packets 0 bytes 0 (0.0 B)
  24. TX errors 0 dropped 0 overruns 0 carrier 0 collisions

2.4.4 自定义镜像 myalpine-java8

  • 编写:准备编写 Dockerfile 文件
  1. $ tee > /data/Dockerfile/myalpine-java8/Dockerfile <<-'EOF'
  2. FROM alpine
  3. MAINTAINER zhongzhiwei <zhongzhiwei@kubesphere.io>
  4. LABEL name="zhongzhiwei"
  5. LABEL email="zhongzhiwei@kubesphere.io"
  6. ENV MYPATH /usr/local/
  7. WORKDIR ${MYPATH}
  8. # 修改APT为清华源
  9. RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
  10. # 安装VIM编辑器
  11. RUN apk add vim
  12. # 安装ifconfig查看网络IP
  13. RUN apk add net-tools
  14. # 安装OpenJDK 和 bash
  15. RUN apk add bash openjdk8
  16. # 配置java环境变量
  17. ENV JAVA_HOME=/usr/lib/jvm/java-1.8-openjdk
  18. ENV PATH="$JAVA_HOME/bin:${PATH}"
  19. EXPOSE 80
  20. RUN echo "WORKDIR=${MYPATH}"
  21. RUN java -version
  22. RUN echo "Success-----------------OK"
  23. CMD [ "/bin/bash" ]
  24. EOF
  • 构建:docker build -t 新镜像名字:TAG .
  1. # docker build -t 新镜像名字:TAG .
  2. # 注意,上面TAG后面的有个空格,有个"."
  3. # .号是指镜像构建时打包上传到Docker引擎中的文件的目录,不是本机目录
  4. $ cd /data/Dockerfile/myalpine-java8/
  5. $ docker build -t myalpine-java8:v1.0 .
  • 运行:docker run 新镜像名字:TAG
  1. $ docker run -it --rm myalpine-java8:1.0
  2. bash-5.1# pwd
  3. /usr/local
  4. bash-5.1# vim --version | head -n 3
  5. VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Apr 28 2022 19:03:08)
  6. Included patches: 1-4836
  7. Compiled by Alpine Linux
  8. bash-5.1# java -version
  9. openjdk version "1.8.0_322"
  10. OpenJDK Runtime Environment (IcedTea 3.22.0) (Alpine 8.322.06-r0)
  11. OpenJDK 64-Bit Server VM (build 25.322-b06, mixed mode)

2.5 小总结

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图21

3 Docker 微服务实战

3.1 通过 IDEA 新建一个普通微服务模块

  1. docker_boot
  2. # 添加 Spring_web的模块

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图22💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图23

  • 改 POM
  1. $ vim pom.xml
  2. <?xml version="1.0" encoding="UTF-8"?>
  3. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <parent>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-parent</artifactId>
  9. <version>2.6.2</version>
  10. <relativePath/> <!-- lookup parent from repository -->
  11. </parent>
  12. <groupId>com.atguigu</groupId>
  13. <artifactId>docker_boot</artifactId>
  14. <version>0.0.1-SNAPSHOT</version>
  15. <name>docker_boot</name>
  16. <description>Demo project for Spring Boot</description>
  17. <properties>
  18. <java.version>1.8</java.version>
  19. <testcontainers.version>1.16.2</testcontainers.version>
  20. </properties>
  21. <dependencies>
  22. <!--Springboot通用依赖模块-->
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-starter-web</artifactId>
  26. </dependency>
  27. <dependency>
  28. <groupId>org.springframework.boot</groupId>
  29. <artifactId>spring-boot-starter-actuator</artifactId>
  30. </dependency>
  31. <!--test-->
  32. <dependency>
  33. <groupId>org.springframework.boot</groupId>
  34. <artifactId>spring-boot-starter-test</artifactId>
  35. <scope>test</scope>
  36. </dependency>
  37. <dependency>
  38. <groupId>org.testcontainers</groupId>
  39. <artifactId>junit-jupiter</artifactId>
  40. <scope>test</scope>
  41. </dependency>
  42. </dependencies>
  43. <dependencyManagement>
  44. <dependencies>
  45. <dependency>
  46. <groupId>org.testcontainers</groupId>
  47. <artifactId>testcontainers-bom</artifactId>
  48. <version>${testcontainers.version}</version>
  49. <type>pom</type>
  50. <scope>import</scope>
  51. </dependency>
  52. </dependencies>
  53. </dependencyManagement>
  54. <build>
  55. <plugins>
  56. <plugin>
  57. <groupId>org.springframework.boot</groupId>
  58. <artifactId>spring-boot-maven-plugin</artifactId>
  59. </plugin>
  60. </plugins>
  61. </build>
  62. </project>
  • 写 YML
  1. $ vim application.properties
  2. server.port=8080
  • 主启动
  1. $ vim DockerApplication
  2. package com.atguigu.docker_boot;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. @SpringBootApplication
  6. public class DockerBootApplication {
  7. public static void main(String[] args) {
  8. SpringApplication.run(DockerBootApplication.class, args);
  9. }
  10. }
  • 业务类
  1. $ vim OrderController
  2. package com.atguigu.docker_boot;
  3. import org.springframework.beans.factory.annotation.Value;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RequestMethod;
  6. import org.springframework.web.bind.annotation.RestController;
  7. import java.util.UUID;
  8. @RestController
  9. public class OrderController {
  10. @Value("${server.port}")
  11. private String port;
  12. @RequestMapping("/order/docker")
  13. public String helloDocker() {
  14. return "hello docker"+"\t"+port+"\t"+ UUID.randomUUID().toString();
  15. }
  16. @RequestMapping(value = "/order/index",method = RequestMethod.GET)
  17. public String index() {
  18. return "服务端口号:"+"\t"+port+"\t"+ UUID.randomUUID().toString();
  19. }
  20. }

运行结果:

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图24

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图25

将微服务项目打包成 jar 包,上传到 Docker 服务器中

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图26

3.2 通过 Dockerfile 发布微服务部署 Docker 容器

3.2.1 IDEA 工具搞定微服务.jar包

将IDEA的Springboot项目的利用maven package生成jar包上传至云服务器上

  1. $ mkdir -pv /data/Dockerfile/springboot ; cd /data/Dockerfile/springboot
  2. $ tree
  3. .
  4. ├── Dockerfile
  5. └── target
  6. └── docker_boot-0.0.1-SNAPSHOT.jar
  7. 1 directory, 2 files

3.2.2 编写 Dockerfile

  • Dockerfile 内容
  1. $ tee > /data/Dockerfile/springboot/Dockerfile <<-'EOF'
  2. # 基础镜像使用 openjdk:8-jdk
  3. FROM openjdk:8-jdk
  4. # 作者
  5. MAINTAINER zhongzhiwei <935523993@qq.com>
  6. # 环境变量
  7. ENV MYPATH /
  8. WORKDIR $MYPATH
  9. # VOLUME 指定临时文件目录为/tmp,在主机/var/lib/docker目录下创建了一个临时文件并链接到容器的/tmp
  10. VOLUME /tmp
  11. # 将jar包添加到容器中并更名为zzw_docker.jar
  12. ADD target/docker_boot-0.0.1-SNAPSHOT.jar zzw_docker.jar
  13. # 运行jar包
  14. # 这个touch命令的作用是修改这个文件的访问时间和修改时间为当前时间,而不会修改文件的内容。
  15. RUN bash -c 'touch /zzw_docker.jar'
  16. ENTRYPOINT ["java", "-jar", "/zzw_docker.jar"]
  17. # 暴露 8080 端口作为微服务
  18. EXPOSE 8080
  19. EOF
  • 将微服务jar包和Dockerfile文件上传到同一个目录下 /data/Dockerfile/springboot

3.2.3 构建镜像

打包成镜像文件
  1. $ cd /data/Dockerfile/springboot/
  2. $ docker build -t zzw_docker:v1.0 .
  3. $ docker images
  4. REPOSITORY TAG IMAGE ID CREATED SIZE
  5. zzw_docker v1.0 5c14d7858a82 25 seconds ago 565MB

3.2.4 运行容器

  1. $ docker run --name zzw_springboot -it -d -p 8080:8080 zzw_docker:v1.0
  2. 6354b13dd4f5a18b8990f626a9ff510937156910aa6384079f5bbaecab497528
  3. $ docker ps -l
  4. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  5. 6354b13dd4f5 zzw_docker:v1.0 "java -jar /zzw_dock…" 6 seconds ago Up 5 seconds 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp zzw_springboot

3.2.5 访问测试

  1. # http://<IP Address>:8080/order/index
  2. # http://<IP Address>:8080/order/docker
  3. # http://139.198.105.99:8080/order/index
  4. # http://139.198.105.99:8080/order/docker

范例:

  1. $ curl 127.0.0.1:8080/order/docker
  2. hello docker 8080 422b9d9b-562e-4d48-a198-c5d3659708b2
  3. $ curl 127.0.0.1:8080/order/index
  4. 服务端口号: 8080 ece1f242-efbe-4aba-80a7-a3c59856b1f9

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图27

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图28

4 Docker 网络

解释一下桥接模式和NAT模式。

桥接:虚拟机直接连接外部的物理网络,主机起到了网桥的作用,虚拟机可以直接访问外网,并且是透明的

NAT:NAT服务器和DHCP服务器会对IP进行地址转换,虚拟机通过共享主机IP可以访问外部网络,而外网是不能访问到虚拟机的。

4.1 Docker Network 是什么

Docker 的网络隔离是通过 Network Namespace 实现的,不同的网络模式都与 NameSpace 关联。Docker 的网络转发通过 iptables 实现。引入了容器网络模型(Container Network Model , CNM),容器网络模型有3个概念network,sandbox,endpoint。

  1. network:网络,可以理解为 Driver,是一个第三方网络栈,包含多种网络模式。包括单主机网络模式(none , host , bridge , container),多主机网络模式(overlay , flannel , calico , macvlan)
  2. sandbox:沙盒,定义了容器内的虚拟网卡,DNS和路由表,是 Network NameSpace 的一种实现,是容器的内部网络栈
  3. endpoint:端点,用于连接 sandbox 和 network

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图29

类比传统网络模型,将 network 比作交换机,sandbox 比作网卡,endpoint 比作接口和网线。

Docker 在创建容器时,先调用控制器创建 sandbox 对象,再调用容器运行时为容器创建 Network NameSpace。

4.1.1 Docker 不启动,默认网络情况

  1. eth0:Linux 宿主机网卡地址;
  2. lo:本地回环网卡地址;
  3. virbr0:虚拟网桥地址,在CentOS7的安装过程中如果有<font style="color:#E8323C;">选择相关虚拟化的服务安装系统(Linux GUI桌面等)后</font>,启动网卡时会发现有一个以网桥链接的私网地址 virbr0 网卡(virbr0网卡:它还有一个固定的默认IP地址:192.168.122.1),是做虚拟机网桥的使用的,其作为是为连接其上的虚拟网卡提供NAT访问外网的功能

Linux 安装,勾选安装系统的时候附带了 libvirt 服务才会生成的一个东西,如果不需要可以直接将 libvirt 服务卸载[ $ yum remove -y libvirt-libs.x86_64 ]

  1. $ ifconfig
  2. eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
  3. inet 10.150.22.47 netmask 255.255.255.0 broadcast 10.150.22.255
  4. inet6 fe80::5054:96ff:fe78:7a16 prefixlen 64 scopeid 0x20<link>
  5. ether 52:54:96:78:7a:16 txqueuelen 1000 (Ethernet)
  6. RX packets 14706908 bytes 7035382687 (6.5 GiB)
  7. RX errors 0 dropped 0 overruns 0 frame 0
  8. TX packets 8721596 bytes 4537719701 (4.2 GiB)
  9. TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
  10. lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
  11. inet 127.0.0.1 netmask 255.0.0.0
  12. inet6 ::1 prefixlen 128 scopeid 0x10<host>
  13. loop txqueuelen 1000 (Local Loopback)
  14. RX packets 1027 bytes 217941 (212.8 KiB)
  15. RX errors 0 dropped 0 overruns 0 frame 0
  16. TX packets 1027 bytes 217941 (212.8 KiB)
  17. TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
  18. virbr0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
  19. inet 192.168.122.1 netmask 255.255.255.0 broadcast 192.168.122.255
  20. ether 52:54:00:6d:68:42 txqueuelen 1000 (Ethernet)
  21. RX packets 0 bytes 0 (0.0 B)
  22. RX errors 0 dropped 0 overruns 0 frame 0
  23. TX packets 0 bytes 0 (0.0 B)
  24. TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

4.1.2 Docker 启动后,网络情况

Docker Service 启动后,会产生一个名为 docker0 的虚拟网桥
  1. $ ifconfig docker0
  2. docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
  3. inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
  4. ether 02:42:de:b5:b9:db txqueuelen 0 (Ethernet)
  5. RX packets 0 bytes 0 (0.0 B)
  6. RX errors 0 dropped 0 overruns 0 frame 0
  7. TX packets 0 bytes 0 (0.0 B)
  8. TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
  1. # 查看 docker 网络模式命令
  2. # 默认创建 3大网络模式
  3. $ docker network ls
  4. NETWORK ID NAME DRIVER SCOPE
  5. bc338e0ef136 bridge bridge local
  6. 3355fadc24fb host host local
  7. e6896debfa34 none null local

4.2 Docker Network 常用基本命令

  • All 命令
  1. $ docker network --help
  2. Usage: docker network COMMAND
  3. Manage networks
  4. Commands:
  5. connect Connect a container to a network
  6. create Create a network
  7. disconnect Disconnect a container from a network
  8. inspect Display detailed information on one or more networks
  9. ls List networks
  10. prune Remove all unused networks
  11. rm Remove one or more networks
  12. Run 'docker network COMMAND --help' for more information on a command.
  • 查看 Docker 网络
  1. $ docker network ls

范例:

  1. # 当我们安装 Docker 后,默认创建 3大网络模式
  2. $ docker network ls
  3. NETWORK ID NAME DRIVER SCOPE
  4. f385248bf26c bridge bridge local
  5. 207f34d7d987 host host local
  6. 416f2c51849f none null local
  • 查看网络源数据
  1. $ docker network inspect xxx网络名字
  2. # docker network inspect bridge
  • 删除网络
  1. $ docker network create xxx网络名字
  • 案例
  1. # 创建 docker 网络
  2. $ docker network create aa_network
  3. 86d3b99dbb14502c244df115d30ccc2fedc9dfe3d03a7a43023911df83541f96
  4. $ docker network ls
  5. NETWORK ID NAME DRIVER SCOPE
  6. 86d3b99dbb14 aa_network bridge local
  7. bc338e0ef136 bridge bridge local
  8. 3355fadc24fb host host local
  9. e6896debfa34 none null local
  10. # 删除 docker 网络
  11. $ docker network rm aa_network
  12. $ docker network ls
  13. NETWORK ID NAME DRIVER SCOPE
  14. bc338e0ef136 bridge bridge local
  15. 3355fadc24fb host host local
  16. e6896debfa34 none null local
  17. # 查看网络源数据
  18. $ docker network inspect bridge

4.3 Docker Network 能干嘛

docker 网络管理和容器调用之间的规划

  • 容器间的互联和通信以及端口映射
  • 容器IP变动的时候可以通过服务名直接网络通信而不受到影响

4.4 Docker Network 网络模式

4.4.1 总体介绍

网络模型 简介
bridge 为每一个容器分配、设置IP等,并将容器连接到一个<font style="color:#E8323C;">docker0</font>虚拟网桥,默认为该模式
host 容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口
none 容器有独立的 Network Namespace(网络名称空间),但是并没有对其进行任何网络设置,如分配 veth pair 和网桥连接,IP等。(基本上不会使用)
container 新创建的容器不会创建自己的网卡和配置自己的IP,而是和一个指定的容器共享IP,端口范围等

docker network 主流的是 bridge、host、none,常用的是 bridge、host。

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

  • 说明
  1. # 1.先启动两个Ubuntu容器实例
  2. $ docker run -it --name u1 -d ubuntu /bin/bash
  3. $ docker run -it --name u2 -d ubuntu /bin/bash
  4. # 2.docker inspect 容器ID or 容器名字
  5. $ docker inspect u1 | tail -n 20
  6. docker inspect u1 | tail -n20
  7. "Networks": {
  8. "bridge": {
  9. ......
  10. "Gateway": "172.17.0.1",
  11. "IPAddress": "172.17.0.13",
  12. "IPPrefixLen": 16,
  13. ......
  14. $ docker inspect u2 | tail -n 20
  15. "Networks": {
  16. "bridge": {
  17. ......
  18. "Gateway": "172.17.0.1",
  19. "IPAddress": "172.17.0.7",
  20. "IPPrefixLen": 16,
  21. ......
  22. # 3.关闭u1容器实例,新建u3容器实例,查看IP变化
  23. $ docker rm -f u1
  24. $ docker run -it --name u3 -d ubuntu /bin/bash
  25. $ docker inspect u3 | tail -n 20
  26. "Networks": {
  27. "bridge": {
  28. ......
  29. "Gateway": "172.17.0.1",
  30. "IPAddress": "172.17.0.13",
  31. "IPPrefixLen": 16,
  32. ......
  33. # u1容器实例的IP地址为172.17.0.13,当u1容器实例down掉之后,u3容器实例的IP地址继承了172.17.0.13,会导致底层网络访问对象发生改变
  34. # 后期通过 docker compose 管理各个容器通信
  35. # 容器里面用外网 就有点违背设计初衷了,隔离性沙箱就是他的一个优点。是可以实现的 用host模式就可以实现。
  • 结论
    • Docker network 底层IP和容器映射关系是会发生变化的
    • 需要 Docker network 网络规划好,通过服务来进行调用

Docker 容器内部的IP是有可能会发生改变的

4.4.3 案例说明

4.4.3.1 bridge

:::color1 bridge 模式:使用 docker —network bridge 指定,默认使用 docker0

有些时候对于安全加固,网络通信服务特别设置,有些容器需要跑在指定的网络范围中。

bridge 是动态分配ip日常可以用bridge来部署我们的各种服务。而像注册中心,redis,mysql等等这些需要集群的东西,可以用host共享宿主机ip分配端口来做集群。

:::

  • 是什么

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

  1. $ docker network inspect bridge
  2. # 创建 bb_network bridge网络
  3. $ docker network create bb_network
  4. $ docker network inspect bb_network
  5. # 查看 bridge 网络的详细信息,并通过grep获取名称项
  6. $ docker network inspect bridge | grep name
  7. "com.docker.network.bridge.name": "docker0",
  8. # 使用ifconfig查看
  9. $ ifconfig | grep docker
  10. docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
  11. # 使用的驱动 Driver 均为 bridge
  12. # 查看 docker 底层的防火墙策略(底层实现的NAT技术)
  13. # 其实docker0是通过iptables和主机通信的,所有符合条件的的请求都会通过iptables转发到docker0并由网桥分发给对应的容器。
  14. $ iptables -vnL -t nat

案例

  • 说明

1.Docker使用Linux桥接,在宿主机虚拟一个 Docker 容器网桥(docker0),Docker启动一个容器时会根据Docker 网桥的网段分配给容器一个IP地址,称为 Container-IP容器IP,同时Docker网桥是每个容器的默认网关。因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能通过容器的Container-IP直接通信

2.docker run的时候,没有指定 <font style="color:#F5222D;">network 的话默认使用的网桥模式就是 bridge</font>,使用的就是 docker0。在宿主机 ifconfig ,就可以看到 docker0 和自己 create 的network(后面说)eth0、eth1、eth2 …… 代表网卡一、网卡二、网卡三……,lo 代表本地回环网卡,127.0.0.1.即 localhost,inet addr 用来表示网卡的IP地址

:::color1

  • docker0就是默认网络bridge使用的虚拟交换机,docker0就是一个桥设备,虽然bridge网络名为bridge,使用的驱动类型也是bridge,使用的虚拟交换机也是一个桥设备,但是,它并不是传统意义上的“桥接”网络,它本质上是一个nat网络,因为它会借助iptables进行SNAT或者DNAT,所以,它是一个nat网络,而非桥接网络,不要被它的名字迷惑了。
  • docker0 设备理解为交换机(网桥),宿主机拥有容器网络和宿主机网络之间的路由,这样就可以实现不同网段之间的容器网络和宿主机网络的通信。所有容器进行数据通信交换必须经过这个 docker0 才可以。
  • 会发现桥接连接的是容器和主机的网卡,而网卡内部是有ip地址,主机利用桥接提供的容器ip访问容器,容器利用桥接的主机ip访问主机,达到主机和容器的通信。
  • Docker 采用 NAT 方式(NAT 模式由于是在三层网络实现的手段,故肯定影响网络的传输效率),将容器内部的服务监听的端口与宿主机的某一个端口 Port 进行”绑定”,使得宿主机以外的世界可以主动将网络报文发送至容器内部。
  • 外界访问容器内的服务时,需要访问宿主机的IP以及宿主机的端口 Port

:::

3.网桥 docker0 创建一对对等虚拟设备接口一个叫 veth ,另一个叫 eth0,成对匹配

(1)整个宿主机的网桥模式都是 docker0,类似一个交换机有一堆接口,每个接口叫 veth,在本地主机和容器内分别创建一个虚拟接口,并让他们彼此连通(这样一对接口叫 veth pair);

(2)每个容器实例内部也有一块网卡,每个接口叫 eth0

(3)docker0 上面的每个 veth0 匹配某个容器实例内部的 eth0,两两配对,一一匹配。

通过上述,将宿主机上的所有容器都连接到这个内部网络上,两个容器在同一个网络下,会从这个网关下各自拿到分配的IP,此时两个容器的网络是互通的。

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图30

画板

  • 代码
  1. docker run -it -d -p 8081:8080 --name tomcat81 billygoo/tomcat8-jdk8
  2. docker run -it -d -p 8082:8080 --name tomcat82 billygoo/tomcat8-jdk8
  • 两两匹配验证
  1. $ ip addr
  2. "353: vetha0c56a3@if352": <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
  3. link/ether a6:d4:24:08:69:f6 brd ff:ff:ff:ff:ff:ff link-netnsid 12
  4. inet6 fe80::a4d4:24ff:fe08:69f6/64 scope link
  5. valid_lft forever preferred_lft forever
  6. ......
  7. $ docker exec -it tomcat81 /bin/bash
  8. root@7d4f392f4e96:/usr/local/tomcat# ip addr
  9. ......
  10. "352: eth0@if353": <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
  11. link/ether 02:42:ac:11:00:0e brd ff:ff:ff:ff:ff:ff link-netnsid 0
  12. inet 172.17.0.14/16 brd 172.17.255.255 scope global eth0
  13. valid_lft forever preferred_lft forever
  14. #####
  15. $ ip addr
  16. "355: veth329b516@if354": <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
  17. link/ether 96:23:ec:3b:fb:31 brd ff:ff:ff:ff:ff:ff link-netnsid 13
  18. inet6 fe80::9423:ecff:fe3b:fb31/64 scope link
  19. valid_lft forever preferred_lft forever
  20. $ docker exec -it tomcat82 /bin/bash
  21. root@8d5877be3ac7:/usr/local/tomcat# ip addr
  22. ......
  23. "354: eth0@if355": <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
  24. link/ether 02:42:ac:11:00:0f brd ff:ff:ff:ff:ff:ff link-netnsid 0
  25. inet 172.17.0.15/16 brd 172.17.255.255 scope global eth0
  26. valid_lft forever preferred_lft forever

4.4.3.2 host

:::color1 host 模式:docker —network host 指定

:::

画板

  • 是什么

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

  1. $ docker network inspect host

案例

  • 说明

容器将不会获得一个独立的 Network NameSpace,而是和宿主机共用一个 Network NameSpace。<font style="color:#F5222D;">容器将不会虚拟出自己的网卡而是使用宿主机的IP和端口。</font>

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图33

代码

  • 警告
  1. $ docker run -d -p 8083:8080 --network host --name tomcat83 billygoo/tomcat8-jdk8
  2. # WARNING: Published ports are discarded when using host network mode
  3. # 当使用host网络模式的时候,Published发布的端口是不被推荐的
  4. # 问题:docker 启动时总是遇到标题中的警告
  5. # 原因:
  6. docker 启动时指定 --network=host 或者 -net host,如果还指定了 -p 映射端口,那么这个时候会有此警告,并且通过 -p 设置的参数将不会起到任何作用,端口号会以主机端口号为主,重复时则会递增。
  7. # 解决:
  8. 解决的方法就是使用 docker 的其他网络模式,例如 --network=bridge,这样就可以解决问题,或者直接无视。
  9. $ docker ps
  10. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  11. 819798960a6a billygoo/tomcat8-jdk8 "catalina.sh run" 3 minutes ago Up 3 minutes tomcat83
  • 正确
  1. $ docker run -d --network host --name tomcat83 billygoo/tomcat8-jdk8
  • 无之前的配对显示,看容器实例内部
  1. $ docker inspect tomcat83 | tail -n 20
  2. "Networks": {
  3. "host": {
  4. "IPAMConfig": null,
  5. "Links": null,
  6. "Aliases": null,
  7. "NetworkID": "3355fadc24fbafd84c894915c5ead2defedf496088605ae66cc009abcdd909e2",
  8. "EndpointID": "a916ec2c7d46defeb16d444d553273cae2c8062fcfb25a9c8aebf155fdb1fb9f",
  9. "Gateway": "",
  10. "IPAddress": "",
  11. "IPPrefixLen": 0,
  12. "IPv6Gateway": "",
  13. "GlobalIPv6Address": "",
  14. "GlobalIPv6PrefixLen": 0,
  15. "MacAddress": "",
  16. "DriverOpts": null
  17. }
  18. }
  19. }
  20. }
  21. ]
  22. $ docker exec -it tomcat83 /bin/bash
  23. root@docker:/usr/local/tomcat# ip addr
  24. # 可以查看到宿主机的网络配置
  • 没有设置 -p 的端口映射,如何访问启动的tomcat83
  1. # 若有两个Tomcat容器使用默认8080端口并且使用host模式,那么将只会有一个容器启动成功,另外一个容器是退出状态
  2. $ curl localhost:8080
  3. # http://宿主机IP:8080/
  4. # 在CentOS里面用默认的火狐浏览器访问容器内的tomcat83看到访问成功,因为此时容器的IP借用主机的,
  5. # 所以容器共享宿主机网络IP,这样的好处是外部主机与容器可以直接通信。

4.4.3.3 none

:::color1 none 模式:docker —network none 指定

:::

  • 是什么

禁用了网络功能,只有 lo 标识(就是127.0.0.1表示本地回环地址)。

在 none 模式下,并不为 Docker 容器进行任何网络配置;也就是说,这个Docker 容器没有网卡、IP、路由等信息,只有loopback(lo);需要自己为Docker容器添加网卡,配置IP等。

  • 案例
  1. $ docker run -it --name tomcat84 -p 8084:8080 --network none -d billygoo/tomcat8-jdk8
  2. $ docker inspect tomcat84 | tail -n 20
  3. "Networks": {
  4. "none": {
  5. "IPAMConfig": null,
  6. "Links": null,
  7. "Aliases": null,
  8. "NetworkID": "e6896debfa348a25d13e1c52c74e2a219a6b5b69fa53962366abf984f8bf11d0",
  9. "EndpointID": "a48129e97081eec074ffc7bfea6ce72343c74ee7a336322aea9202f374c35570",
  10. "Gateway": "",
  11. "IPAddress": "",
  12. "IPPrefixLen": 0,
  13. "IPv6Gateway": "",
  14. "GlobalIPv6Address": "",
  15. "GlobalIPv6PrefixLen": 0,
  16. "MacAddress": "",
  17. "DriverOpts": null
  18. }
  19. }
  20. }
  21. }
  22. ]
  23. $ docker ps
  24. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  25. 9f2e27b93f0a billygoo/tomcat8-jdk8 "catalina.sh run" 58 seconds ago Up 55 seconds tomcat84
  26. $ docker exec -it tomcat84 /bin/bash
  27. root@9f2e27b93f0a:/usr/local/tomcat# ip addr
  28. 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
  29. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  30. inet 127.0.0.1/8 scope host lo
  31. valid_lft forever preferred_lft forever
  32. root@9f2e27b93f0a:/usr/local/tomcat# curl localhost
# 查看 none 网络模式信息
$ docker network inspect none

4.4.3.4 container

:::color1 container 模式:docker —network container:NAME 或者容器ID 指定

:::

  • 是什么

Container 网络模式

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

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图34

  • 案例1
docker run -it -d --name tomcat85 -p 8085:8080 billygoo/tomcat8-jdk8

docker run -it -d -p 8086:8080 --name tomcat86 --network container:tomcat85 billygoo/tomcat8-jdk8
# docker: Error response from daemon: conflicting options: port publishing and the container type network mode.
See 'docker run --help'.

# 相当于 tomcat86 和 tomcat85共用同一个IP同一个端口,导致端口冲突;
# 本案例用tomcat演示不合适。演示坑......

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

docker run -it -d --name alpine1 alpine /bin/sh
docker run -it -d --network container:alpine1 --name alpine2 alpine /bin/sh
# docker run -it -d --name b1 busybox /bin/sh
# docker run -it -d --name b2 --network container:b1 busybox /bin/sh

## 运行结果,验证共用搭桥
$ docker ps
CONTAINER ID   IMAGE                   COMMAND                  CREATED             STATUS             PORTS                                                  NAMES
62d690e5bd23   alpine                  "/bin/sh"                14 seconds ago      Up 14 seconds                                                             alpine2
9fa00670d1e3   alpine                  "/bin/sh"                2 minutes ago       Up 2 minutes                                                              alpine1
[root@docker springboot]# docker exec -it alpine1 /bin/sh
/ # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    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
360: eth0@if361: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:11:00:10 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.16/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
/ # exit
[root@docker springboot]# docker exec -it alpine2 /bin/sh
/ # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    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
360: eth0@if361: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:11:00:10 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.16/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
/ # exit

## 假如此时关闭 alpine1 ,再看看alpine2
$ docker stop alpine1
$ docker exec -it alpine2 /bin/sh
/ # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    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
# 360: eth0@if361 的网卡就会随着 alpine1停止而消失了

# 当共享网络空间的容器因各种问题宕掉后,那么与该容器共用的网络空间的容器就会失去相关的网络配置,将只会拥有回环网卡

4.4.3.5 自定义网络

  • 过时的 Docker link

官方站点:Legacy container links | Docker Documentation

  • 案例
Before
### 案例
docker run -it -d --name tomcat81 -p 8081:8080 --hostname tomcat81 billygoo/tomcat8-jdk8
docker run -it -d --name tomcat82 -p 8082:8080 --hostname tomcat82 billygoo/tomcat8-jdk8

# 上述成功启动并用docker exec进入各自的容器实例内部
docker exec -it tomcat81 /bin/bash
docker exec -it tomcat82 /bin/bash

### 问题
# 按照IP地址ping是否OK的?
$ docker exec -it tomcat81 /bin/bash
root@7d4f392f4e96:/usr/local/tomcat# ip addr
352: eth0@if353: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:0e brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.14/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
root@7d4f392f4e96:/usr/local/tomcat# ping -c1 172.17.0.15
PING 172.17.0.15 (172.17.0.15) 56(84) bytes of data.
64 bytes from 172.17.0.15: icmp_seq=1 ttl=64 time=0.074 ms
--- 172.17.0.15 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.074/0.074/0.074/0.000 ms
#####
$ docker exec -it tomcat82 /bin/bash
root@8d5877be3ac7:/usr/local/tomcat# ping -c1 172.17.0.14
PING 172.17.0.14 (172.17.0.14) 56(84) bytes of data.
64 bytes from 172.17.0.14: icmp_seq=1 ttl=64 time=0.193 ms
--- 172.17.0.14 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.193/0.193/0.193/0.000 ms
root@8d5877be3ac7:/usr/local/tomcat# ip addr
354: eth0@if355: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:0f brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.15/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

# 按照服务名ping结果为?
$ docker exec -it tomcat81 /bin/bash
root@7d4f392f4e96:/usr/local/tomcat# ping tomcat82
ping: tomcat82: Name or service not known
#####
$ docker exec -it tomcat82 /bin/bash
root@7d4f392f4e96:/usr/local/tomcat# ping tomcat81
ping: tomcat81: Name or service not known
After
### 案例
## 自定义桥接网络,自定义网络默认使用的是桥接网络 bridge
## 新建自定义网络
$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
bc338e0ef136   bridge    bridge    local
3355fadc24fb   host      host      local
e6896debfa34   none      null      local
$ docker network create zzw_network
$ docker network ls
NETWORK ID     NAME          DRIVER    SCOPE
bc338e0ef136   bridge        bridge    local
3355fadc24fb   host          host      local
e6896debfa34   none          null      local
932d527c8823   zzw_network   bridge    local

## 新建容器加入上一步新建的自定义网络
docker run -it -d --name tomcat81 -p 8081:8080 --network zzw_network billygoo/tomcat8-jdk8
docker run -it -d --name tomcat82 -p 8082:8080 --network zzw_network billygoo/tomcat8-jdk8

$ docker ps
CONTAINER ID   IMAGE                   COMMAND                  CREATED             STATUS             PORTS
                                      NAMES
5616fa34f17b   billygoo/tomcat8-jdk8   "catalina.sh run"        5 seconds ago       Up 4 seconds       0.0.0.0:8082->8080/tcp, :::8082->8080/tcp              tomcat82
2098cb35f733   billygoo/tomcat8-jdk8   "catalina.sh run"        6 seconds ago       Up 5 seconds       0.0.0.0:8081->8080/tcp, :::8081->8080/tcp              tomcat81

## 互相ping测试
$ docker exec -it tomcat82 /bin/bash
root@5616fa34f17b:/usr/local/tomcat# ip addr
......
365: eth0@if366: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:14:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.20.0.3/16 brd 172.20.255.255 scope global eth0
       valid_lft forever preferred_lft forever
root@5616fa34f17b:/usr/local/tomcat# ping -c1 -W1 172.20.0.2
PING 172.20.0.2 (172.20.0.2) 56(84) bytes of data.
64 bytes from 172.20.0.2: icmp_seq=1 ttl=64 time=0.045 ms

--- 172.20.0.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.045/0.045/0.045/0.000 ms
root@2098cb35f733:/usr/local/tomcat# ping tomcat82
PING tomcat82 (172.20.0.3) 56(84) bytes of data.
64 bytes from tomcat82.zzw_network (172.20.0.3): icmp_seq=1 ttl=64 time=0.088 ms
^C
--- tomcat82 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.088/0.088/0.088/0.000 ms

#####
$ docker exec -it tomcat81 /bin/bash
root@2098cb35f733:/usr/local/tomcat# ip addr
......
363: eth0@if364: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:14:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.20.0.2/16 brd 172.20.255.255 scope global eth0
       valid_lft forever preferred_lft forever
root@2098cb35f733:/usr/local/tomcat# ping -c1 -W1 172.20.0.3
PING 172.20.0.3 (172.20.0.3) 56(84) bytes of data.
64 bytes from 172.20.0.3: icmp_seq=1 ttl=64 time=0.048 ms

--- 172.20.0.3 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.048/0.048/0.048/0.000 ms
root@5616fa34f17b:/usr/local/tomcat# ping -c1 -W1 tomcat81
PING tomcat81 (172.20.0.2) 56(84) bytes of data.
64 bytes from tomcat81.zzw_network (172.20.0.2): icmp_seq=1 ttl=64 time=0.060 ms

--- tomcat81 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.060/0.060/0.060/0.000 ms

### 问题结论
自定义网络本身就维护好了主机名和IP的对应关系(IP和域名都能通)
自定义网络本身就维护好了主机名和IP的对应关系(IP和域名都能通)
自定义网络本身就维护好了主机名和IP的对应关系(IP和域名都能通)
# 自定义的网络可以在容器之间提供自动的 DNS 解析
# 自定义网桥的容器会开启自己的dns,把名称和IP的对应关系发送到docker维护的dns里,
# docker里有了dns信息,自然可以做地址转换了,就这么easy

4.5 Docker 平台架构图解

4.5.1 整体说明

从其架构和运行流程来看,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 容器运行资源或者执行用户指令等操作时,则通过 Exec Driver 来完成
  7. Libcontainer 是一项独立的容器管理包,Network Driver 以及 Exec Driver 都是通过 Libcontainer 来实现具体对容器进行的操作

4.5.2 整体架构

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图35

5 Docker-compose 容器编排

:::color1

  1. 涉及容器的启动顺序和加载条件及要求
  2. 容器越来越多,如何统一管理起来

:::

5.1 Docker-compose 是什么

Docker-compose 是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排

Docker-compose 是 Docker 公司推出的一个工具软件,可以管理多个 Docker 容器组成一个应用。需要定义一个YAML格式的配置文件 docker-compose.yml写好多个容器之间的调用关系。然后,只要一个命令,就能同时启动/关闭这些容器。

:::color1 docker-compose 还是非常爽的,用了以后,完全不像用 docker 启动容器

最简单的理解就是把多个docker容器的启动命令写在一个脚本里,批量启动容器(写好多个容器之间的命令关系)

:::

画板

5.2 Docker-compose 能干嘛

Docker 建议我们每一个容器中只运行一个服务,因为docker容器本身占用资源极少,所以最好是将服务单独的分割开来,但是这样我们又面临了一个问题?

如果我们需要同时部署好多个服务,难道要每个服务单独写Dockerfile,然后再构建镜像,镜像容器,这样会很累,所以docker官方给我们提供了 docker-compose 多服务部署工具。

例如要实现一个Web微服务项目,除了Web服务容器本身,往往还需要再加上后端的数据库mysql服务容器,redis服务器,注册中心eureka,甚至还包括负载均衡容器等等。。。

Compose 允许用户通过一个单独的 docker-compose.yml 模板文件(YAML格式)来定义一组相关联的应用容器为一个项目(project)

可以很容易的用一个配置文件定义一个/一组多容器的应用,然后使用一条指令安装这个应用的所有依赖,完成构建。Docker-Compose解决了容器与容器之间如何管理编排的问题。

:::color1 Docker compose 是单机(本机)的多容器管理技术

K8s 是跨主机的集群部署工具

:::

5.3 Docker-compose 下载

5.3.1 官网

https://docs.docker.com/compose/compose-file/compose-file-v3/

5.3.2 官网下载

https://docs.docker.com/compose/install/

GitHub 项目地址:https://github.com/docker/compose

5.3.3 安装步骤

$ sudo curl -L "https://github.com/docker/compose/releases/download/v1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# curl -SL https://github.com/docker/compose/releases/download/v2.7.0/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
# curl -SL https://github.com/docker/compose/releases/download/v2.7.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose

# 若无法下载,则科学上网
# sudo curl -L https://get.daocloud.io/docker/compose/releases/download/1.25.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
# 添加执行权限
$ sudo chmod +x /usr/local/bin/docker-compose
$ sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

$ docker-compose version
Docker Compose version v2.7.0

5.3.4 卸载步骤

$ sudo rm -rf /usr/local/bin/docker-compose

5.4 Docker-compose 核心的概念

5.4.1 一个文件

:::warning

docker-compose.yml

:::

5.4.2 两个要素

:::warning

  • 服务(Server)

一个个应用容器实例,比如订单微服务,库存微服务,MySQL容器,Nginx容器或者Redis容器

  • 工程(Project)

由一组关联的应用容器组成的一个<font style="color:#E8323C;">完整业务单元</font>,在 docker-compose.yml 文件中定义。工程=多个服务(容器应用实例)

:::

5.5 Docker-compose 使用的三个步骤

:::warning

  1. 编写 <font style="color:#E8323C;">Dockerfile</font> 定义各个微服务应用并构建出对应的镜像文件
  2. 使用 <font style="color:#E8323C;">docker-compose.yml</font>定义一个完整业务单元,安排好整体应用中的各个容器服务
  3. 最后,执行 <font style="color:#E8323C;">docker-compose -f docker-compose.yml up -d</font> 命令来启动并运行整体应用程序,完成一键部署上线 [ 等价于一次性运行了多次 docker run …… ]
  4. docker-compose 脚本 类似于 Shell 脚本 (Docker-compose 可以使用 Shell 实现)

:::

5.6 Docker-compose 常用的命令

:::warning Compose 常用命令

  • docker-compose -h —-> 查看帮助
  • docker-compose up —-> 启动所有docker-compose服务
  • docker-compose up -d ---> 启动服务 docker-compose 服务,并后台运行
  • docker-compose down ---> 停止并删除容器、网络、卷、镜像
  • docker-compose exec yml里面的服务id —-> 进入容器实例内部 docker-compose exec docker-compose.yml文件中写的服务id /bin/bash
  • docker-compose ps —-> 展示当前 docker-compose 编排过的运行的所有容器
  • docker-compose top —-> 展示当前 docker-compose 编排过的容器进程
  • docker-compose logs yml里面的服务id —-> 检查容器输出日志
  • docker-compose config ---> 检查配置
  • docker-compose config -q ---> 检查配置,有问题才有输出
  • docker-compose restart —-> 重启服务
  • docker-compose start —-> 启动服务
  • docker-compose stop —-> 停止服务

:::

5.7 Docker-compose 编排微服务

:::color1

假设需要编排三个容器:微服务docker_boot、mysql、redis

:::

5.7.1 改进升级微服务工程 docker_boot(本小节为开发同学应用)

:::color1 本章还是以 Docker 为主,Java 的训练和使用主要是开发者为主。

:::

  • 以前的基础版(见3.1 通过 IDEA 新建一个普通微服务模块)
  • SQL建表建库
CREATE TABLE IF NOT EXISTS t_user (
    `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `username` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '用户名',
    `password` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '密码',
    `sex` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '性别 0=女 1=男',
    `deleted` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除标志 默认0不删除 1删除',
    `update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    PRIMARY KEY(`id`)
) ENGINE=INNODB AUTO_INCREMENT=1114 DEFAULT CHARSET=utf8 COMMENT='用户表';
  • 一键生成说明
# IDEA新建一个空的Maven项目(用于连接数据库)
$ vim application.properties
package.name=com.atguigu.docker_boot

jdbc.driverClass = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/db2021
jdbc.user = root
jdbc.password = 123456
  • 改POM
$ vim pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.6</version>
        <!--<version>2.3.10.RELEASE</version>-->
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.atguigu.docker</groupId>
    <artifactId>docker_boot</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.16.18</lombok.version>
        <mysql.version>5.1.47</mysql.version>
        <druid.version>1.1.16</druid.version>
        <mapper.version>4.1.5</mapper.version>
        <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
    </properties>

    <dependencies>
        <!--guava Google 开源的 Guava 中自带的布隆过滤器-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>23.0</version>
        </dependency>
        <!-- redisson -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.4</version>
        </dependency>
        <!--SpringBoot通用依赖模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--swagger2-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--SpringBoot与Redis整合依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--springCache-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <!--springCache连接池依赖包-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <!-- jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.1.0</version>
        </dependency>
        <!--Mysql数据库驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <!--SpringBoot集成druid连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <!--mybatis和springboot整合-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.spring.boot.version}</version>
        </dependency>
        <!-- 添加springboot对amqp的支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.10</version>
        </dependency>
        <!--通用基础配置junit/devtools/test/log4j/lombok/hutool-->
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.2.3</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <optional>true</optional>
        </dependency>
        <!--persistence-->
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>persistence-api</artifactId>
            <version>1.0.2</version>
        </dependency>
        <!--通用Mapper-->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper</artifactId>
            <version>${mapper.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>3.1.0</version>
            </plugin>
        </plugins>
    </build>

</project>
  • 写XML
$ application.properties

server.port=6001
# ========================alibaba.druid相关配置=====================
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://10.0.0.101:3306/db2021?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.druid.test-while-idle=false
# ========================redis相关配置=====================
spring.redis.database=0
spring.redis.host=10.0.0.101
spring.redis.port=6379
spring.redis.password=
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
# ========================mybatis相关配置===================
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.atguigu.docker.entities
# ========================swagger=====================
spring.swagger2.enabled=true
  • 主启动
$ vim DockerBootApplication
package com.atguigu.docker;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;

@SpringBootApplication
@MapperScan("com.atguigu.docker.mapper") //import tk.mybatis.spring.annotation.MapperScan;
public class DockerBootApplication
{
    public static void main(String[] args)
    {
        SpringApplication.run(DockerBootApplication.class, args);
    }
}
  • 业务类
    • config 配置类
$ vim RedisConfig

package com.atguigu.docker.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.io.Serializable;

/**
* @auther zzyy
* @create 2021-10-27 17:19
*/

@Configuration
@Slf4j
public class RedisConfig
{
    /**
    * @param lettuceConnectionFactory
    * @return
    *
    * redis序列化的工具配置类,下面这个请一定开启配置
    * 127.0.0.1:6379> keys *
    * 1) "ord:102"  序列化过
    * 2) "\xac\xed\x00\x05t\x00\aord:102"   野生,没有序列化过
    */
    @Bean
    public RedisTemplate<String,Serializable> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory)
    {
        RedisTemplate<String,Serializable> redisTemplate = new RedisTemplate<>();

        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        //设置key序列化方式string
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置value的序列化方式json
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }

}
$ vim SwaggerConfig

package com.atguigu.docker.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

import java.text.SimpleDateFormat;
import java.util.Date;

public class SwaggerConfig {
    @Value("true")
    private Boolean enabled;

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .enable(enabled)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.atguigu.docker")) //自己的packager
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("大厂技术"+"\t"+new SimpleDateFormat("yyyy-MM-dd").format(new Date()))
                .description("docker-compose")
                .version("1.0")
                .termsOfServiceUrl("http://www.kubesphere.com/")
                .build();
    }

}
- 新建 entity
$ vim User
package com.atguigu.docker.entities;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;

@Table(name = "t_user")
public class User
{
    @Id
    @GeneratedValue(generator = "JDBC")
    private Integer id;

    /**
    * 用户名
    */
    private String username;

    /**
    * 密码
    */
    private String password;

    /**
    * 性别 0=女 1=男 
    */
    private Byte sex;

    /**
    * 删除标志,默认0不删除,1删除
    */
    private Byte deleted;

    /**
    * 更新时间
    */
    @Column(name = "update_time")
    private Date updateTime;

    /**
    * 创建时间
    */
    @Column(name = "create_time")
    private Date createTime;

    /**
    * @return id
    */
    public Integer getId() {
        return id;
    }

    /**
    * @param id
    */
    public void setId(Integer id) {
        this.id = id;
    }

    /**
    * 获取用户名
    *
    * @return username - 用户名
    */
    public String getUsername() {
        return username;
    }

    /**
    * 设置用户名
    *
    * @param username 用户名
    */
    public void setUsername(String username) {
        this.username = username;
    }

    /**
    * 获取密码
    *
    * @return password - 密码
    */
    public String getPassword() {
        return password;
    }

    /**
    * 设置密码
    *
    * @param password 密码
    */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
    * 获取性别 0=女 1=男 
    *
    * @return sex - 性别 0=女 1=男 
    */
    public Byte getSex() {
        return sex;
    }

    /**
    * 设置性别 0=女 1=男 
    *
    * @param sex 性别 0=女 1=男 
    */
    public void setSex(Byte sex) {
        this.sex = sex;
    }

    /**
    * 获取删除标志,默认0不删除,1删除
    *
    * @return deleted - 删除标志,默认0不删除,1删除
    */
    public Byte getDeleted() {
        return deleted;
    }

    /**
    * 设置删除标志,默认0不删除,1删除
    *
    * @param deleted 删除标志,默认0不删除,1删除
    */
    public void setDeleted(Byte deleted) {
        this.deleted = deleted;
    }

    /**
    * 获取更新时间
    *
    * @return update_time - 更新时间
    */
    public Date getUpdateTime() {
        return updateTime;
    }

    /**
    * 设置更新时间
    *
    * @param updateTime 更新时间
    */
    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    /**
    * 获取创建时间
    *
    * @return create_time - 创建时间
    */
    public Date getCreateTime() {
        return createTime;
    }

    /**
    * 设置创建时间
    *
    * @param createTime 创建时间
    */
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}
$ vim UserDTO
package com.atguigu.docker.entities;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;

@NoArgsConstructor
@AllArgsConstructor
@Data
@ApiModel(value = "用户信息")
public class UserDTO implements Serializable
{
    @ApiModelProperty(value = "用户ID")
    private Integer id;

    @ApiModelProperty(value = "用户名")
    private String username;

    @ApiModelProperty(value = "密码")
    private String password;

    @ApiModelProperty(value = "性别 0=女 1=男 ")
    private Byte sex;

    @ApiModelProperty(value = "删除标志,默认0不删除,1删除")
    private Byte deleted;

    @ApiModelProperty(value = "更新时间")
    private Date updateTime;

    @ApiModelProperty(value = "创建时间")
    private Date createTime;

    /**
     * @return id
     */
    public Integer getId() {
        return id;
    }

    /**
     * @param id
     */
    public void setId(Integer id) {
        this.id = id;
    }

    /**
     * 获取用户名
     *
     * @return username - 用户名
     */
    public String getUsername() {
        return username;
    }

    /**
     * 设置用户名
     *
     * @param username 用户名
     */
    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * 获取密码
     *
     * @return password - 密码
     */
    public String getPassword() {
        return password;
    }

    /**
     * 设置密码
     *
     * @param password 密码
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * 获取性别 0=女 1=男 
     *
     * @return sex - 性别 0=女 1=男 
     */
    public Byte getSex() {
        return sex;
    }

    /**
     * 设置性别 0=女 1=男 
     *
     * @param sex 性别 0=女 1=男 
     */
    public void setSex(Byte sex) {
        this.sex = sex;
    }

    /**
     * 获取删除标志,默认0不删除,1删除
     *
     * @return deleted - 删除标志,默认0不删除,1删除
     */
    public Byte getDeleted() {
        return deleted;
    }

    /**
     * 设置删除标志,默认0不删除,1删除
     *
     * @param deleted 删除标志,默认0不删除,1删除
     */
    public void setDeleted(Byte deleted) {
        this.deleted = deleted;
    }

    /**
     * 获取更新时间
     *
     * @return update_time - 更新时间
     */
    public Date getUpdateTime() {
        return updateTime;
    }

    /**
     * 设置更新时间
     *
     * @param updateTime 更新时间
     */
    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    /**
     * 获取创建时间
     *
     * @return create_time - 创建时间
     */
    public Date getCreateTime() {
        return createTime;
    }

    /**
     * 设置创建时间
     *
     * @param createTime 创建时间
     */
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", sex=" + sex +
                '}';
    }
}
  • 新建 mapper
新建接口 UserMapper
$ UserMapper.java
package com.atguigu.docker.mapper;

import com.atguigu.docker.entities.User;
import tk.mybatis.mapper.common.Mapper;

public interface UserMapper extends Mapper<User> {
}
src\main\resources路径下新建mapper文件夹并新增UserMapper.xml
$ vim UserMapper.xml 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.docker.mapper.UserMapper">
  <resultMap id="BaseResultMap" type="com.atguigu.docker.entities.User">
    <!--
      WARNING - @mbg.generated
    -->
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="username" jdbcType="VARCHAR" property="username" />
    <result column="password" jdbcType="VARCHAR" property="password" />
    <result column="sex" jdbcType="TINYINT" property="sex" />
    <result column="deleted" jdbcType="TINYINT" property="deleted" />
    <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
  </resultMap>
</mapper>
  • 新建 service
$ vim UserService.java
package com.atguigu.docker.service;

import com.atguigu.docker.entities.User;
import com.atguigu.docker.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
* @auther zzyy
* @create 2021-05-01 14:58
*/
@Service
@Slf4j
public class UserService {

    public static final String CACHE_KEY_USER = "user:";

    @Resource
    private UserMapper userMapper;
    @Resource
    private RedisTemplate redisTemplate;

    /**
    * addUser
    * @param user
    */
    public void addUser(User user)
    {
        //1 先插入mysql并成功
        int i = userMapper.insertSelective(user);

        if(i > 0)
        {
            //2 需要再次查询一下mysql将数据捞回来并ok
            user = userMapper.selectByPrimaryKey(user.getId());
            //3 将捞出来的user存进redis,完成新增功能的数据一致性。
            String key = CACHE_KEY_USER+user.getId();
            redisTemplate.opsForValue().set(key,user);
        }
    }

    /**
    * findUserById
    * @param id
    * @return
    */
    public User findUserById(Integer id)
    {
        User user = null;
        String key = CACHE_KEY_USER+id;

        //1 先从redis里面查询,如果有直接返回结果,如果没有再去查询mysql
        user = (User) redisTemplate.opsForValue().get(key);

        if(user == null)
        {
            //2 redis里面无,继续查询mysql
            user = userMapper.selectByPrimaryKey(id);
            if(user == null)
            {
                //3.1 redis+mysql 都无数据
                //你具体细化,防止多次穿透,我们规定,记录下导致穿透的这个key回写redis
                return user;
            }else{
                //3.2 mysql有,需要将数据写回redis,保证下一次的缓存命中率
                redisTemplate.opsForValue().set(key,user);
            }
        }
        return user;
    }
}
  • 新建controller

:::color1 生成get、set插件:GenerateAllSetter

:::

$ vim UserController.java
package com.atguigu.docker.controller;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ReferenceUtil;
import com.atguigu.docker.entities.User;
import com.atguigu.docker.entities.UserDTO;
import com.atguigu.docker.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.Random;

/**
* @auther zzyy
* @create 2021-05-01 15:02
*/
@Api(description = "用户User接口")
@RestController
@Slf4j
public class UserController
{
    @Resource
    private UserService userService;

    @ApiOperation("数据库新增3条记录")
    @RequestMapping(value = "/user/add",method = RequestMethod.POST)
    public void addUser()
    {
        for (int i = 1; i <=3; i++) {
            User user = new User();

            user.setUsername("zzyy"+i);
            user.setPassword(IdUtil.simpleUUID().substring(0,6));
            user.setSex((byte) new Random().nextInt(2));

            userService.addUser(user);
        }
    }

    @ApiOperation("删除1条记录")
    @RequestMapping(value = "/user/delete/{id}",method = RequestMethod.POST)
    public void deleteUser(@PathVariable Integer id)
    {
        userService.deleteUser(id);
    }

    @ApiOperation("修改1条记录")
    @RequestMapping(value = "/user/update",method = RequestMethod.POST)
    public void updateUser(@RequestBody UserDTO userDTO)
    {
        User user = new User();
        BeanUtils.copyProperties(userDTO,user);
        userService.updateUser(user);
    }

    @ApiOperation("查询1条记录")
    @RequestMapping(value = "/user/find/{id}",method = RequestMethod.GET)
    public User findUserById(@PathVariable Integer id)
    {
        return userService.findUserById(id);
    }
}
  • mvn package命令将微服务形成新的 jar 包并上传到Linux服务器/mydocker目录下
  • 编写Dockerfile
# 基础镜像使用java
FROM java:8
# 作者
MAINTAINER zhongzhiwei <935523993@qq.com>
# VOLUME 指定临时文件目录为/tmp,在主机/var/lib/docker目录下创建了一个临时文件并链接到容器的/tmp
VOLUME /tmp
# 将jar包添加到容器中并更名为zzyy_docker.jar
ADD docker_boot-0.0.1-SNAPSHOT.jar zzw_docker.jar
# 运行jar包
RUN bash -c 'touch /zzw_docker.jar'
ENTRYPOINT ["java","-jar","/zzw_docker.jar"]
#暴露6001端口作为微服务
EXPOSE 6001
  • 构建镜像
$ docker build -t zzw_docker:v2.0 .

5.7.2 不用 docker-compose

5.7.2.1 单独的 Mysql 容器实例

# 新建MySQL容器实例
$ docker run -p 3306:3306 --name mysql57 --privileged=true \
       -v /zzyyuse/mysql/conf:/etc/mysql/conf.d \
       -v /zzyyuse/mysql/logs:/logs \
       -v /zzyyuse/mysql/data:/var/lib/mysql \
       -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7.36

$ cat > /zzyyuse/mysql/conf/mysql.cnf <<EOF
[client]
## 设置客户端的默认字符集
default-character-set=utf8mb4

[mysql]
## 设置MySQL服务器的默认字符集
default-character-set=utf8mb4

[mysqld]
## 设置MySQL后台服务程序的默认字符集
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
EOF

# 进入MySQL容器实例并创建库db2021+新建表t_user
$ docker exec -it mysql57 /bin/bash
> mysql -uroot -p123456
>> create database db2021;
>> use db2021;
>> CREATE TABLE `t_user` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '用户名',
  `password` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '密码',
  `sex` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '性别 0=女 1=男 ',
  `deleted` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除标志,默认0不删除,1删除',
  `update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

5.7.2.2 单独的 Redis 容器实例

$ docker run  -p 6379:6379 --name redis608 --privileged=true \
         -v /app/redis/redis.conf:/etc/redis/redis.conf \
         -v /app/redis/data:/data \
         -d redis:6.0.8 redis-server /etc/redis/redis.conf

$ cat > /app/redis/redis.conf <<EOF
appendonly yes
port 6379
bind 0.0.0.0
EOF

5.7.2.3 微服务工程

$ docker run -d --name docker_boot -p 6001:6001 zzw_docker:v2.0

5.7.2.4 上面三个容器实例依次顺序启动成功

$ docker ps

5.7.3 swagger

http://localhost:你的微服务端口/swagger-ui.html#/

5.7.4 上面成功了,有哪些问题?

  • 先后顺序要求固定,先mysql+redis才能微服务访问成功(docker run 命令需要执行并且有加载顺序要求)
  • 多个run命令……
  • 容器间的启停或宕机,有可能导致IP地址对应的容器实例变化,映射出错,要么生产IP写死(可以但是不推荐),要么通过服务调用

:::color1 想通过服务名称调用就需要自定义一个网络名称,讲容器指定运行在这个网络中,并且网络模式默认bridge,保证容器IP自动规划

:::

5.7.5 使用 docker-compose

:::color1

服务编排,一套带走,安排。

:::

5.7.5.1 编写 docker-compose.yml 文件

$ vim docker-compose.yml
version: "3"

services: # 固定写死
   microService: # 服务名
      image: zzw_docker:2.0
      container_name: ms01
      ports:
         - "6001:6001"
      volumes:
         - /app/microService:/data
      networks: 
         - atguigu_net
      depends_on: # redis和mysql容器先启动
         - redis
         - mysql

   redis:
      image: redis:6.0.8
      # container_name: redis-node01
      ports:
         - "6379:6379"
      volumes:
         - /app/redis/redis.conf:/etc/redis/redis.conf
         - /app/redis/data:/data
      networks: 
         - atguigu_net
      command: redis-server /etc/redis/redis.conf

   mysql:
      image: mysql:5.7
      # container_name: mysql-node01
      environment:
         MYSQL_ROOT_PASSWORD: '123456'
         MYSQL_ALLOW_EMPTY_PASSWORD: 'no'
         MYSQL_DATABASE: 'db2021'
         MYSQL_USER: 'zzyy'
         MYSQL_PASSWORD: 'zzyy123'
      ports:
         - "3306:3306"
      volumes:
         - /app/mysql/db:/var/lib/mysql
         - /app/mysql/conf/my.cnf:/etc/my.cnf
         - /app/mysql/init:/docker-entrypoint-initdb.d
      networks:
         - atguigu_net
      command: --default-authentication-plugin=mysql_native_password # 解决外部无法访问

networks: 
   atguigu_net: # 构建bridge网络
   # docker network create atguigu_net

5.7.5.2 第二次修改微服务工程 docker_boot

  • 写 YML —-> 通过服务名访问,IP无关

:::color1 开发根据运维给的模版,填写服务名,端口号等信息就行了,Kubernetes 环境也类似

network 只是为了让每个容器实例在同一个网段里面能,容器实例能相互访问,而 docker-compose 则是包装镜像启动顺序外加network功能以及每个容器属性统一个变量暴露让程序能够通过变量访问

:::

$ vim application.properties
server.port=6001

# ========================alibaba.druid相关配置=====================
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#spring.datasource.url=jdbc:mysql://192.168.111.169:3306/db2021?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.url=jdbc:mysql://mysql:3306/db2021?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.druid.test-while-idle=false

# ========================redis相关配置=====================
spring.redis.database=0
#spring.redis.host=192.168.111.169
spring.redis.host=redis
spring.redis.port=6379
spring.redis.password=
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0

# ========================mybatis相关配置===================
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.atguigu.docker.entities

# ========================swagger=====================
spring.swagger2.enabled=true
  • mvn package命令将微服务形成新的jar包并上传到Linux服务器/mydocker目录下
  • 编写Dockerfile
# 基础镜像使用java
FROM java:8
# 作者
MAINTAINER zzyy
# VOLUME 指定临时文件目录为/tmp,在主机/var/lib/docker目录下创建了一个临时文件并链接到容器的/tmp
VOLUME /tmp
# 将jar包添加到容器中并更名为zzyy_docker.jar
ADD docker_boot-0.0.1-SNAPSHOT.jar zzyy_docker.jar
# 运行jar包
RUN bash -c 'touch /zzyy_docker.jar'
ENTRYPOINT ["java","-jar","/zzyy_docker.jar"]
#暴露6001端口作为微服务
EXPOSE 6001
  • 构建镜像
$ docker build -t zzw_docker:v3.0 .

5.7.5.3 执行 docker-compose up 或者执行 docker-compose up -d

$ docker-compose up
$ docker-compose up -d
# 由于是在 mydocker 的路径下运行 docker-compose 文件
# 按照 docker-compose的编排规矩:会在运行容器和网络等添加前缀(docker-compose 所在路径的目录)
# 若指定容器名称,则默认的命名规则优先级低,将不会生效

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图37

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图38

5.7.5.4 进入 mysql 容器实例并创建db2021+新建表t_user

$ docker exec -it 容器实例id /bin/bash
mysql -uroot -p
create database db2021;
use db2021;
CREATE TABLE `t_user` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '用户名',
  `password` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '密码',
  `sex` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '性别 0=女 1=男 ',
  `deleted` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除标志,默认0不删除,1删除',
  `update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

5.7.5.5 测试通过

# 检查配置文件docker-compose
$ docker-compose config -q

# 启动服务docker-compose,后台运行
$ docker-compose up -d

# 查看docker容器实例
$ docker ps

5.7.5.6 Compose 常用命令

Compose 常用命令

  • docker-compose -h —-> 查看帮助
  • docker-compose up —-> 启动所有docker-compose服务
  • <font style="color:#E8323C;">docker-compose up -d ---> 启动服务 docker-compose 服务,并后台运行</font>
  • <font style="color:#E8323C;">docker-compose down ---> 停止并删除容器、网络、卷、镜像</font>
  • docker-compose exec yml里面的服务id —-> 进入容器实例内部 docker-compose exec docker-compose.yml文件中写的服务id /bin/bash
  • docker-compose ps —-> 展示当前 docker-compose 编排过的运行的所有容器
  • docker-compose top —-> 展示当前 docker-compose 编排过的容器进程
  • docker-compose logs yml里面的服务id —-> 检查容器输出日志
  • <font style="color:#E8323C;">docker-compose config ---> 检查配置</font>
  • <font style="color:#E8323C;">docker-compose config -q ---> 检查配置,有问题才有输出</font>
  • docker-compose restart —-> 重启服务
  • docker-compose start —-> 启动服务
  • docker-compose stop —-> 停止服务

5.7.5.7 关停

# 停止服务
$ docker-compose stop
# docker-compose 适合单机部署多个容器
# docker swarm 或者 kubernetes 则适合集群部署

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图39

6 Docker 轻量级可视化工具 Portainer

6.1 Portainer 是什么

Portainer 是一款轻量级的应用,它提供了图形化界面,用于方便的管理Docker环境,包括单机环境和集群环境(毫不犹豫直接使用 K8S)。用于监控和统计

6.2 Portainer 安装

官网:https://www.portainer.io/

安装文档:Install Portainer with Docker on Linux - Portainer Documentation

6.2.1 Portainer 安装步骤

  • docker 命令安装
# 9443 端口是 HTTPS
# --restart=always 在Docker重启服务之后会自动启动
$ docker run -d -p 8000:8000 -p 9000:9000 -p 9443:9443 --name portainer \
         --restart=always \
         -v /var/run/docker.sock:/var/run/docker.sock \
         -v portainer_data:/data \
         portainer/portainer

:::warning

用户名,直接用默认admin

(密码记得8位,随便你写)

:::

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图40

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图41

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

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图42

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图43

  • 上一步的图形展示,以及对应的docker命令
$ docker system df
# Stack 是docker-compose

6.2.2 相关报错

在启动Docker 容器时,会出现报错:**<font style="color:rgb(77, 77, 77);">Error response from daemon: driver failed programming external connectivity on endpoint XXX(端口映射或启动容器时报错)</font>**

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图44

解决方法:

:::warning 输入指令:systemctl restart docker。重启Docker服务即可重新生成自定义链DOCKER

:::

6.3 登录并演示介绍常用操作 case

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图45

查看容器的资源占用

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图46

创建容器实例

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图47

根据引导对容器的配置进行修改

7 Docker 容器监视之 CAdvisor + InfluxDB + Grafana

7.1 原生命令

# 查看本机所有容器的资源占用情况
# -a 选项                    :查看所有的容器实例的资源情况
# --no-stream 选项:禁用流统计,只提取第一个结果
$ docker stats 

# 查看本地指定容器的资源占用情况
$ docker stats portainer

# 显示一个容器正在运行的进程
$ docker top portainer

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图48

:::warning 问题:通过 docker stats 命令可以很方便的看到当前宿主机上所有容器的CPU,内存以及网络流量等数据,一般小公司够用了。。。

但是,docker stats 统计结果只能是当前宿主机的全部容器,数据资料是实时的,没有地方存储,没有监控指标过线预警等功能。

:::

7.2 CIG 是什么

容器监控三剑客,主要是为了更复杂,大型Docker容器的监控。 一句话:CAdvisor 监控收集 + InfluxDB 存储数据 + Grafana 展示图表

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图49

7.2.1 CAdvisor

CAdvisor 是一个容器资源监控工具,包括**<font style="color:#E8323C;">容器的内存,CPU,网络IO,磁盘IO等监控</font>**,同时提供了一个Web页面用于查看容器的实时运行状态。CAdvisor 默认存储2分钟的数据,而且**<font style="color:#E8323C;">只是针对单物理机</font>**。不过,CAdvisor 提供了很多数据集成接口,支持对 InfluxDB,Redis,KafKa,Elasticsearch 等集成,可以加上对应配置将监控数据发往这些数据库存储起来。

Cdvisor 功能主要有两点:

  • 展示Host和容器两个层次的监控数据
  • 展示历史变化的数据

7.2.2 InfluxDB

InfluxDB 是用Go语言编写的一个开源分布式时序,事件和指标数据库,无需外部依赖。 CAdvisor 默认只在本机保存最近2分钟的数据,为了**<font style="color:#E8323C;">持久化数据和统一收集展示监控数据</font>**,需要将数据存储到 InfluxDB 中。InfluxDB 是一个时序数据库,专门用于存储时序相关数据,很适合CAdvisor的数据,而且,CAdvisor 本身已经提供了InfluxDB 的集成方法,启动容器时指定配置即可。 InfluxDB 主要功能: + 基于时间序列:支持与时间有关的相关函数(如最大,最小,求和等); + 可度量性:可以实时对大量数据进行计算; + 基于事件:它支持任意的事件数据。 ### 7.2.3 Grafana Grafana 是**<font style="color:#E8323C;">一个开源的数据监控分析可视化平台</font>**,支持多种数据源配置(支持的数据源包括InfluxDB,MySQL,Elasticsearch,OpenTSDB,Graphite等)和丰富的插件以及模板功能,支持图标权限控制和报警。 Grafana 主要特性: + 灵活丰富的图形化选项 + 可以混合多种风格 + 支持白天和夜间的模式 + 多种数据源。 ### 7.2.4 CIG总结 + cAdvisor:Collects,aggregates,processes,and exports information about running containers(收集监控数据) + InfluxDB:Time Series Database stores all the metrics(存储cAdvisor的监控数据持久化) + Grafana:Metrics Dashboard(展示监控图表) 💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图50 ## 7.3 Docker-compose 容器编排,一套带走 ### 7.3.1 新建目录 bash $ mkdir -pv /app/cig ### 7.3.2 新建三件套组合的 docker-compose.yml bash links:将指定容器连接到当前连接,可以设置别名,避免ip方式导致的容器重启动态改变的无法连接情况 # 指定服务名称:别名 bash tee > /app/cig/docker-compose.yml <<-'EOF' version: "3" # grafana_data 容器卷 volumes: grafana_data: {} services: influxdb: image: tutum/influxdb:0.9 restart: always environment: - PRE_CREATE_DB=cadvisor ports: - "8083:8083" - "8086:8086" volumes: - ./data/influxdb:/data cadvisor: image: google/cadvisor links: - influxdb:influxsrv restart: always command: -storage_driver=influxdb -storage_driver_db=cadvisor -storage_driver_host=influxsrv:8086 ports: - "9090:8080" volumes: - /:/rootfs:ro - /var/run:/var/run:rw - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:ro grafana: image: grafana/grafana user: "104" restart: always links: - influxdb:influxsrv ports: - "3000:3000" volumes: - grafana_data:/var/lib/grafana environment: - HTTP_USER=admin - HTTP_PASS=admin - INFLUXDB_HOST=influxsrv - INFLUXDB_PORT=8086 EOF ### 7.3.3 启动 docker-compose.yml 文件 bash # 前台启动 $ docker-compose up # 后台启动 $ docker-compose up -d ### 7.3.4 查看三个服务容器是否启动 bash $ docker ps 💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图51 ### 7.3.5 测试 浏览 cAdvisor收集服务 [http://ip:9090](http://ip:9090) + 第一次访问慢,请稍等 + cAdvisor 也有基础的图形展示功能,但是只能实时展示数据只有2min 💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图52 + 简单的监控面板展示 💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图53 浏览 InfluxDB 存储服务 < http://ip:8083 > 💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图54 + InfluxDB 中的 8083 是对外暴露的 Web 界面,8086 是后台管理进程。 💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图55 浏览 Grafana 展现服务 < http://ip:3000 > + IP+3000端口的方式访问,默认账号密码admin/admin。登录成功后需要修改密码。 💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图56 + 配置步骤 配置数据源 💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图57 选择influxDB数据源

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图58

配置细节

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图59

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图60

  • 添加数据源成功

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图61

配置面板panel

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图62

cAdvisor + influxDB + Grafana 容器监控体系搭建完成

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图63

💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图64

8 终章 & 总结

8.1 知识回顾简单串讲和总结

Docker 的简介、Docker 安装以及配置、Docker 容器命令、Dockerfile 解析、Docker 网络、Docker-compose 编排微服务、Docker 复杂安装详说、Docker 轻量级可视化工具 Portainer、Docker 容器监控之CAdvisor + InfluxDB + Granfana

8.2 进阶篇:Kubernetes

:::warning 【尚硅谷】Kubernetes(k8s)入门到实战教程丨全新升级完整版_哔哩哔哩_bilibili

尚硅谷Kubernetes教程(K8s入门到精通)_哔哩哔哩_bilibili

云原生Java架构师的第一课K8s+Docker+KubeSphere+DevOps_哔哩哔哩_bilibili

:::