- 1 Docker 复杂安装详说
- 1.主节点必须启用二进制日志,记录任何修改了数据库数据的事件
- 2.从节点开启了一个线程(I/O Thread)把自己扮演成MySQL的客户端,通过MySQL协议,请求主节点的二进制日志文件中的事件
- 3.主节点启动一个线程(Dump Thread),检查自己二进制日志中的事件,跟对方请求的位置对比,如果不带请求位置参数,则主节点就会从第一个日志文件中的第一个事件一个一个发送给从节点
- 4.从节点接收到主节点发送过来的数据把它放置在中继日志(Relay Log)文件中。并记录该次请求到主节点的具体哪一个二进制日志文件内部的哪一个位置(主节点中的二进制文件会有很多个)
- 5.从节点启动另一个线程(SQL Thread),把Relay Log中的事件读取出来,并在本地再执行一次
- 从节点
- 主节点
- 👑1.2 安装 Redis 集群(大厂面试题-分布式存储案例)
- 说明1:
- 说明2:
- Redis 集群中内置了 16384 个哈希槽,Redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在 Redis 集群中放置一个 key-value 时,Redis 先对 key 使用 CRC16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,也就是映射到某个节点上。如下代码,key之A、B在Node2,key之C落在Node3上。
- 槽号分派说明
- 为什么6387是3个新的区间,以前的还是连续?
- 重新分配成本太高,所以前3Master各自匀出来一部分,从6381 / 6382 / 6383三个旧的Master节点分别匀出1364个槽位给新的节点6387
- 2 Dockerfile 解析
- 表示注释
- 2.3 Dockerfile 常用保留字指令
- 2.4 案例
- 2.5 小总结
- 3 Docker 微服务实战
- 4 Docker 网络
- 5 Docker-compose 容器编排
- Docker 轻量级可视化工具 Portainer">6 Docker 轻量级可视化工具 Portainer
- 7 Docker 容器监视之 CAdvisor + InfluxDB + Grafana
- 8 终章 & 总结
:::warning 容器集群化是未来的发展方向。需要对容器网络做规划,容器网络有五类:bridge,host,none,container和自定义网络。
容器监控以及可视化容器部署配置。
:::
1 Docker 复杂安装详说
👑1.1 安装 MySQL 主从复制
👑1.1.1 主从复制原理
- 为什么需要主从复制?
1、在业务复杂的系统中,有这么一个情景,有一句SQL语句需要缩表,导致暂时不能使用读的服务,那么就很影响运行中的业务,使用主从复制,让主库负责写,从库负责读,这样,即使主库出现了缩表的情景,通过读从库也可以保证业务的正常运行。
2、做数据的热备,主库宕机后能够及时替换主库,保证业务可用性。
3、架构的扩展。业务量越来越大,I/O 访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的频率,提高单个机器的I/O性能。
4、MySQL 主从经典的部署方式:一主一从;MySQL 主从中从服务器也不需要太多,MySQL 会出现主从的不同步情况,从服务器同步跟不上也会导致数据同步出错。
- 什么是 MySQL 的主从复制?
主从复制中分为【主服务器(master)
】和【从服务器(slave)
】,主服务器负责写,而从服务器负责读,MySQL 的主从复制的过程是一个【异步的过程
】。
这样读写分离的过程能够是整体的服务性能提高,即时写操作时间比较长,也不影响读操作的进行。
MySQL 主从复制是指数据可以从一个MySQL数据库服务器主节点复制到一个或者多个从节点。
MySQL 默认采用异步复制方式,这样从节点不用一直访问主服务器来更新自己的数据,数据的更新可以在远程连接上进行,从节点可以复制主数据库中所有数据库或者特定的数据库,或者特定的表。
- MySQL 主从复制的流程
:::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数据库中,这样就保证了主从的数据的同步。
:::warning 主库db的更新事件(update,insert,delete)被写到 binlog
主库创建一个 binlog dump thread,把 binlog 的内容发送到从库
从库启动并发起连接,连接到主库
从库启动之后,创建一个I/O线程,读取主库传过来的 binlog 内容并写入到 relay log
从库启动之后,创建一个 SQL 线程,从 relay log 里面读取内容,从 Exec_Master_Log_Pos 位置开始执行读取到的更新事件,将更新内容写入到 Slave 的 db中
:::
以上就是主从复制的过程,当然,主从复制的过程有不同的策略方式进行数据的同步,主要包含以下几种:
- 「同步策略」:Master会等待所有的Slave都回应后才会提交,这个主从的同步的性能会严重的影响。
- 「半同步策略」:Master至少会等待一个Slave回应后提交。
- 「异步策略」:Master不用等待Slave回应就可以提交。
- 「延迟策略」:Slave要落后于Master指定的时间。
对于不同的业务需求,有不同的策略方案,但是一般都会采用最终一致性,不会要求强一致性,毕竟强一致性会严重影响性能。
1.1.2 主从搭建步骤
新建主服务器容器实例 Port:3307
$ docker run -it -p 3307:3306 -d \
--name mysql-master --privileged=true \
-v /app/mysql-master/log:/var/log/mysql \
-v /app/mysql-master/data:/var/lib/mysql \
-v /app/mysql-master/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=Admin@h3c \
mysql:5.7.36
$ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
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
$ cat > /app/mysql-master/conf/my.cnf <<EOF
[client]
## 设置客户端的默认字符集
default-character-set=utf8mb4
[mysql]
## 设置MySQL服务器的默认字符集
default-character-set=utf8mb4
[mysqld]
## 设置MySQL后台服务程序的默认字符集
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
## 设置server_id,同一局域网中需要唯一
server_id=101
## 指定不需要同步的数据库名称
binlog-ignore-db=mysql
## 开启二进制日志功能
log-bin=mall-mysql-bin
## 开启二进制日志使用内存大小(事务)
binlog_cache_size=1M
## 设置使用的二进制日志格式(mixed,statement,row)
binlog_format=mixed
## 二进制日志过期清理时间。默认值为0,表示不自动清理
expire_logs_days=7
## 跳过主从复制中遇到的所有错误或者指定类型的错误,避免slave端复制终端
## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致
slave_skip_errors=1062
EOF
- 修改完配置后重启 master 实例
$ docker restart mysql-master
- 进入 mysql-master 容器
$ docker exec -it mysql-master /bin/bash
root@43c1f8bc896c:/# mysql -uroot -pAdmin@h3c
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.00 sec)
- master 容器实例内创建数据同步用户
mysql> CREATE USER 'slave'@'%' IDENTIFIED BY 'Admin@h3c';
mysql> GRANT REPLICATION SLAVE,REPLICATION CLIENT ON *.* TO 'slave'@'%';
新建从服务器容器实例 Port:3308
$ docker run -it -p 3308:3306 -d \
--name mysql-slave --privileged=true\
-v /app/mysql-slave/log:/var/log/mysql \
-v /app/mysql-slave/data:/var/lib/mysql \
-v /app/mysql-slave/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=Admin@h3c \
mysql:5.7.36
$ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
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
$ cat > /app/mysql-slave/conf/my.cnf <<EOF
[client]
## 设置客户端的默认字符集
default-character-set=utf8mb4
[mysql]
## 设置MySQL服务器的默认字符集
default-character-set=utf8mb4
[mysqld]
## 设置MySQL后台服务程序的默认字符集
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
## 设置server_id,同一局域网中需要唯一
server_id=102
## 指定不需要同步的数据库名称
binlog-ignore-db=mysql
## 开启二进制日志功能,以备Slave作为其他数据库实例的Master时使用
log-bin=mall-slave1-bin
## 设置二进制日志使用内存大小(事务)
binlog_cache_size=1M
## 设置使用的二进制日志格式(mixed,statement,row)
binlog_format=mixed
## 二进制日志过期清理时间。默认值为0,表示不自动清理
expire_logs_days=7
## 跳过主从复制中遇到的所有错误或者指定类型的错误,避免slave端复制终端
## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致
slave_skip_errors=1062
## relay_log配置中继日志
relay_log=mall-mysql-relay-bin
## log_slave_updates表示slave将复制事件写进自己的二进制日志
log_slave_updates=1
## slave设置为只读(具有super权限的用户除外)
read_only=1
EOF
- 修改完配置后重启 slave 实例
$ docker restart mysql-slave
- 在 mysql-mater 主数据库中查看主从同步状态
$ docker exec -it mysql-master /bin/bash
root@43c1f8bc896c:/# mysql -uroot -pAdmin@h3c
mysql> show master status;
+-----------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+-----------------------+----------+--------------+------------------+-------------------+
| mall-mysql-bin.000001 | 617 | | mysql | |
+-----------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
- 进入 mysql-slave 从数据库容器
$ docker exec -it mysql-slave /bin/bash
root@67ff2cc65475:/# mysql -uroot -pAdmin@h3c
- 在 mysql-slave 从数据库中配置主从复制
### 主从复制命令参数说明
# master_host:主数据库的IP地址
# master_port:主数据库的运行断开
# master_user:在主数据库创建的用于同步数据的用户账号
# master_password:在主数据库创建的用于同步数据的用户密码
# master_log_file:指定从数据库要复制数据的日志文件,通过查看主数据的状态,获取File参数
# master_log_pos:指定从数据库从哪个位置开始复制数据,通过查看主数据的状态,获取Position参数
# master_connect_retry:连接失败重试的时间间隔,单位为秒
# 公式: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;
$ docker exec -it mysql-slave /bin/bash
root@67ff2cc65475:/# mysql -uroot -pAdmin@h3c
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 从数据库中查看主从同步状态
$ docker exec -it mysql-slave /bin/bash
root@67ff2cc65475:/# mysql -uroot -pAdmin@h3c
mysql> show slave status\G;
*************************** 1. row ***************************
Slave_IO_State:
Master_Host: 139.198.105.99
Master_User: slave
Master_Port: 3307
Connect_Retry: 30
Master_Log_File: mall-mysql-bin.000001
Read_Master_Log_Pos: 617
Relay_Log_File: mall-mysql-relay-bin.000001
Relay_Log_Pos: 4
Relay_Master_Log_File: mall-mysql-bin.000001
Slave_IO_Running: No
Slave_SQL_Running: No
- 在 mysql-slave 从数据库中开启主从同步
$ docker exec -it mysql-slave /bin/bash
root@67ff2cc65475:/# mysql -uroot -pAdmin@h3c
mysql> start slave;
Query OK, 0 rows affected (0.00 sec)
- 查看从数据库状态发现已经同步
mysql> show slave status\G;
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 139.198.105.99
Master_User: slave
Master_Port: 3307
Connect_Retry: 30
Master_Log_File: mall-mysql-bin.000001
Read_Master_Log_Pos: 617
Relay_Log_File: mall-mysql-relay-bin.000002
Relay_Log_Pos: 325
Relay_Master_Log_File: mall-mysql-bin.000001
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
- 主从复制测试
- 主机新建库 —> 使用库 —> 新建表 —> 插入数据,OK
- 从机使用库 —> 查看记录,OK
# 在主数据库进行操作
$ docker exec -it mysql-master /bin/bash
root@43c1f8bc896c:/# mysql -uroot -pAdmin@h3c
mysql> create database if not exists db01;
mysql> use db01;
mysql> create table t1 (id INT,name VARCHAR(20));
mysql> insert into t1(id , name) values(1 , 'zhangsan');
mysql> select * from t1;
+------+----------+
| id | name |
+------+----------+
| 1 | zhangsan |
+------+----------+
1 row in set (0.00 sec)
# 在从数据库进行操作
$ docker exec -it mysql-slave /bin/bash
root@67ff2cc65475:/# mysql -uroot -pAdmin@h3c
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| db01 |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.01 sec)
mysql> use db01;
mysql> show tables;
+----------------+
| Tables_in_db01 |
+----------------+
| t1 |
+----------------+
1 row in set (0.00 sec)
mysql> select * from t1;
+------+----------+
| id | name |
+------+----------+
| 1 | zhangsan |
+------+----------+
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))
- 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取余全部数据重新洗牌。
缺点总结:节点映射,数据变动,容易出错。
:::
(2)一致性哈希算法分区
- 是什么?一致性 Hash 算法背景:→ 一致性Hash哈希算法在1997年由麻省理工学院中提出的,设计目标是为了解决分布式缓存数据变动和映射问题,某个机器宕机了,分母数量改变了,自然取余数不OK了。
- 能干嘛?→ 提出一致性Hash解决方案。目的是当服务器个数发生变动时,尽量减少影响客户端到服务器的映射关系。
- 3大步骤(Redis面试题一致性Hash-嗨客网 (haicoder.net))
![💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图6](/uploads/projects/seekerzw@yaaygq/623805992e27cbb5550824dd050e39d7.png)
![💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图7](/uploads/projects/seekerzw@yaaygq/ecfcfd495e2786831449b2f822dfdbde.png)
一致性哈希算法容错性
:假设Node C宕机,可以看到此时对象A,B,D不会受到影响,只有C对象被重定位到Node D。一般的,在一致性Hash算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。简单说,就是C挂了,受到影响的只是B、C之间的数据,并且这些数据会转移到D进行存储。
![💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图8](/uploads/projects/seekerzw@yaaygq/7b7c253f4acd17108cefe0a3f35cd4c5.png)
一致性哈希算法扩展性
:数据量增加了,需要增加一台节点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](/uploads/projects/seekerzw@yaaygq/28d61024ddd047da208c1cac8331e913.png)
![💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图10](/uploads/projects/seekerzw@yaaygq/484d9fb3fb7b399545e16823dc9aa33e.jpeg)
![💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图11](/uploads/projects/seekerzw@yaaygq/dc63bb4bbaa18ab95eb7330b9c5715e8.png)
![💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图12](/uploads/projects/seekerzw@yaaygq/89d13b7e70ce9886e6b230684381f520.png)
![💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图13](/uploads/projects/seekerzw@yaaygq/7e18d305e09d95a9fac5a329ce4b6112.png)
换句话说是分布在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()
{
//import.io.lettuce.core.cluster.SlotHash;
System.out.println(SlotHash.getSlot(key:"A")); //6373
System.out.println(SlotHash.getSlot(key:"B")); //10374
System.out.println(SlotHash.getSlot(key:"C")); //14503
System.out.println(SlotHash.getSlot(key:"hello")); //866
}
:::
:::warning 哈希槽和一致性 Hash 的最大区别就是引入了 Hash 槽,可以理解为在一致性 Hash 的基础上,变成 16384 个虚拟节点,这也是 Hash 算法算出来的倾斜的概率会降低,但是极端情况还是数据倾斜。
:::
三主三从Redis集群扩缩容配置案例架构说明
Redis Role | Redis Port |
---|---|
Master1 | 6381 |
Master2 | 6382 |
Master3 | 6383 |
Slave1 | 6384 |
Slave2 | 6385 |
Slave3 | 6386 |
👑1.2.1 Redis 三主三从实现步骤
### 1.关闭防火墙+启动Docker后台服务
$ systemctl stop firewalld && systemctl disable firewalld
$ systemctl start docker && systemctl enable docker
### 2.新建6个Redis Docker容器实例
# docker run ---> 创建并运行 docker 容器实例
# --name redis-node-1 ---> 容器名字
# -d ---> 容器实例后台运行
# --net host ---> 使用宿主机的IP和端口,默认(host网络模式不需要端口映射,直接使用宿主机端口)
# --privileged=true ---> 获取宿主机 root 用户权限
# -v /data/redis/share/redis-note-1:/data ---> 容器卷,宿主机绝对路径目录:Docker容器内部绝对路径目录
# redis ---> Redis 镜像,默认版本号为 latest
# --cluster-enabled yes ---> 开启 Redis 集群
# --appendonly yes ---> 开启持久化
# --port 6381 ---> 容器实例 Redis 暴露的端口号
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
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
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
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
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
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
### 3.进入容器 redis-node-1 并为 6台机器构建集群关系
## 进入容器
$ docker exec -it redis-node-1 /bin/bash
## 构建主从关系
# 注意,进入Docker容器后才能执行一下命令,并且注意自己的真实IP地址
# --cluster-replicas 1 表示为每个 Master 创建一个 Slave 节点
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
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 139.198.105.99:6385 to 139.198.105.99:6381
Adding replica 139.198.105.99:6386 to 139.198.105.99:6382
Adding replica 139.198.105.99:6384 to 139.198.105.99:6383
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381
slots:[0-5460] (5461 slots) master
M: a23364d45a6d2f56d94021815af2c8f3ef6cb499 139.198.105.99:6382
slots:[5461-10922] (5462 slots) master
M: 15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383
slots:[10923-16383] (5461 slots) master
S: 150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 139.198.105.99:6384
replicates daa3085081481d5c893f100a4d742a7160a8616c
S: 48656f0b83bd65f31a4478fc17be8322efcd2ede 139.198.105.99:6385
replicates a23364d45a6d2f56d94021815af2c8f3ef6cb499
S: 2075147d5c9e90275c1eb7e1b1ad32912129a13e 139.198.105.99:6386
replicates 15a33cc85697b68cc897137c863238f412ceff46
Can I set the above configuration? (type 'yes' to accept): yes
## 一切Ok的话,3主3从搞定
### 4.链接进入 6381 作为切入点,查看集群状态
## 链接进入 6381 作为切入点,查看节点状态
$ docker exec -it redis-node-1 /bin/bash
$ root@docker:/data# redis-cli -p 6381
## cluster info
127.0.0.1:6381> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:319
cluster_stats_messages_pong_sent:329
cluster_stats_messages_sent:648
cluster_stats_messages_ping_received:324
cluster_stats_messages_pong_received:319
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:648
## cluster nodes
127.0.0.1:6381> cluster nodes
2075147d5c9e90275c1eb7e1b1ad32912129a13e 139.198.105.99:6386@16386 slave 15a33cc85697b68cc897137c863238f412ceff46 0 1642221710000 3 connected
48656f0b83bd65f31a4478fc17be8322efcd2ede 139.198.105.99:6385@16385 slave a23364d45a6d2f56d94021815af2c8f3ef6cb499 0 1642221711199 2 connected
daa3085081481d5c893f100a4d742a7160a8616c 10.150.22.47:6381@16381 myself,master - 0 1642221711000 1 connected 0-5460
150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 139.198.105.99:6384@16384 slave daa3085081481d5c893f100a4d742a7160a8616c 0 1642221710196 1 connected
a23364d45a6d2f56d94021815af2c8f3ef6cb499 139.198.105.99:6382@16382 master - 0 1642221712202 2 connected 5461-10922
15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383@16383 master - 0 1642221709193 3 connected 10923-16383
### 5.由于是Redis的Master-Slave之间的关系自动分配,所以需要具体问题具体分析
## 该Redis-Cluster的配置为:
# Master1:Port6381 --> Slave1:Port6384
# Master2:Port6382 --> Slave2:Port6385
# Master3:Port6383 --> Slave3:Port6386
## Redis-Cluster 内部两两随机匹配了以后,内部打散了以后,由Redis自行决定Master-Slave的顺序。
1.2.2 Redis 主从容错切换迁移案例
- 数据读写存储
### 1.启动6台容器构成的集群并通过 exec 进入
$ docker exec -it redis-node-1 /bin/bash
root@docker:/data# redis-cli -p 6381
127.0.0.1:6381> keys *
(empty array)
### 2.对 6381 新增两个 key
# 根据key Hash计算后与16384取模算出对应的槽点值,会有数据存储到其他Node节点上。所以会有key存储失败
127.0.0.1:6381> set k1 v1
(error) MOVED 12706 139.198.105.99:6383
127.0.0.1:6381> set k2 v2
OK
127.0.0.1:6381> set k3 v3
OK
127.0.0.1:6381> set k4 v4
(error) MOVED 8455 139.198.105.99:6382
### 防止路由失效加传输 -c 并新增两个 key
[
127.0.0.1:6381> set k1 v1
(error) MOVED 12706 139.198.105.99:6383
# 加入参数 -c,优化路由
root@docker:/data# redis-cli -c -p 6381
127.0.0.1:6381> set k1 v1
-> Redirected to slot [12706] located at 139.198.105.99:6383
OK
]
$ docker exec -it redis-node-1 /bin/bash
root@docker:/data# redis-cli -c -p 6381
# 清除Redis存储的KV
127.0.0.1:6381> FLUSHALL
OK
127.0.0.1:6381> set k1 v1
-> Redirected to slot [12706] located at 139.198.105.99:6383
OK
# 会重定向到指定的槽位Node
139.198.105.99:6383> set k2 v2
-> Redirected to slot [449] located at 139.198.105.99:6381
OK
139.198.105.99:6381> set k3 v3
OK
139.198.105.99:6381> set k4 v4
-> Redirected to slot [8455] located at 139.198.105.99:6382
OK
### 查看集群信息
root@docker:/data# redis-cli --cluster check 139.198.105.99:6381
139.198.105.99:6381 (daa30850...) -> 2 keys | 5461 slots | 1 slaves.
139.198.105.99:6382 (a23364d4...) -> 1 keys | 5462 slots | 1 slaves.
139.198.105.99:6383 (15a33cc8...) -> 1 keys | 5461 slots | 1 slaves.
[OK] 4 keys in 3 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 139.198.105.99:6381)
M: daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: 2075147d5c9e90275c1eb7e1b1ad32912129a13e 139.198.105.99:6386
slots: (0 slots) slave
replicates 15a33cc85697b68cc897137c863238f412ceff46
S: 48656f0b83bd65f31a4478fc17be8322efcd2ede 139.198.105.99:6385
slots: (0 slots) slave
replicates a23364d45a6d2f56d94021815af2c8f3ef6cb499
S: 150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 139.198.105.99:6384
slots: (0 slots) slave
replicates daa3085081481d5c893f100a4d742a7160a8616c
M: a23364d45a6d2f56d94021815af2c8f3ef6cb499 139.198.105.99:6382
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
M: 15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
- 容错切换迁移
Redis 集群主从切换(1号宕机4号上位)
### 主6381和从机切换,先停止主机6381
$ docker stop redis-node-1
### 再次查看集群信息
# 使用 redis-node-2 进入 Redis
$ docker exec -it redis-node-2 /bin/bash
root@docker:/data# redis-cli -c -p 6382
127.0.0.1:6382> cluster nodes
# 139.198.105.99:6381@16381 master,fail 主节点连接失败
daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381@16381 master,fail - 1642226063583 1642226056560 1 disconnected
15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383@16383 master - 0 1642226096000 3 connected 10923-16383
48656f0b83bd65f31a4478fc17be8322efcd2ede 139.198.105.99:6385@16385 slave a23364d45a6d2f56d94021815af2c8f3ef6cb499 0 1642226096834 2 connected
2075147d5c9e90275c1eb7e1b1ad32912129a13e 139.198.105.99:6386@16386 slave 15a33cc85697b68cc897137c863238f412ceff46 0 1642226095826 3 connected
a23364d45a6d2f56d94021815af2c8f3ef6cb499 10.150.22.47:6382@16382 myself,master - 0 1642226094000 2 connected 5461-10922
# 139.198.105.99:6384@16384 master 从节点成主节点
150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 139.198.105.99:6384@16384 master - 0 1642226095000 7 connected 0-5460
# 6381 宕机了,6384上位成为了新的Master。6381的数据同样存储在6384
# 备注:每次案例下面挂的从机以实际情况为准,具体是几号机器就是几号
127.0.0.1:6382> get k1
-> Redirected to slot [12706] located at 139.198.105.99:6383
"v1"
139.198.105.99:6383> get k2
-> Redirected to slot [449] located at 139.198.105.99:6384
"v2"
139.198.105.99:6384> get k3
"v3"
139.198.105.99:6384> get k4
-> Redirected to slot [8455] located at 139.198.105.99:6382
"v4"
### 先还原之前的3主3从
# 先启 6381 --> docker start redis-node-1
# 再停 6384 --> docker stop redis-node-4
# 再启 6384 --> docker start redis-node-4
# 主从机器分配情况以实际情况为准
$ docker start redis-node-1
# 6381 主节点恢复后,角色会变成 Slave,不会自动切换到 Master
139.198.105.99:6382> cluster nodes
daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381@16381 slave 150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 0 1642226771967 7 connected
15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383@16383 master - 0 1642226771000 3 connected 10923-16383
48656f0b83bd65f31a4478fc17be8322efcd2ede 139.198.105.99:6385@16385 slave a23364d45a6d2f56d94021815af2c8f3ef6cb499 0 1642226771018 2 connected
2075147d5c9e90275c1eb7e1b1ad32912129a13e 139.198.105.99:6386@16386 slave 15a33cc85697b68cc897137c863238f412ceff46 0 1642226771000 3 connected
a23364d45a6d2f56d94021815af2c8f3ef6cb499 10.150.22.47:6382@16382 myself,master - 0 1642226769000 2 connected 5461-10922
150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 139.198.105.99:6384@16384 master - 0 1642226770000 7 connected 0-5460
-------
$ docker stop redis-node-4
# 6381 主节点会因为 6384 宕机而将角色切换到 Master
139.198.105.99:6382> cluster nodes
daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381@16381 master - 0 1642227020000 8 connected 0-5460
15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383@16383 master - 0 1642227021206 3 connected 10923-16383
48656f0b83bd65f31a4478fc17be8322efcd2ede 139.198.105.99:6385@16385 slave a23364d45a6d2f56d94021815af2c8f3ef6cb499 0 1642227021894 2 connected
2075147d5c9e90275c1eb7e1b1ad32912129a13e 139.198.105.99:6386@16386 slave 15a33cc85697b68cc897137c863238f412ceff46 0 1642227019000 3 connected
a23364d45a6d2f56d94021815af2c8f3ef6cb499 10.150.22.47:6382@16382 myself,master - 0 1642227018000 2 connected 5461-10922
# 6384 节点宕机
150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 139.198.105.99:6384@16384 master,fail - 1642227003710 1642226998715 7 disconnected
-------
$ docker start redis-node-4
# 6384 节点的角色恢复到 Slave
139.198.105.99:6382> cluster nodes
daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381@16381 master - 0 1642227143806 8 connected 0-5460
15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383@16383 master - 0 1642227144821 3 connected 10923-16383
48656f0b83bd65f31a4478fc17be8322efcd2ede 139.198.105.99:6385@16385 slave a23364d45a6d2f56d94021815af2c8f3ef6cb499 0 1642227142814 2 connected
2075147d5c9e90275c1eb7e1b1ad32912129a13e 139.198.105.99:6386@16386 slave 15a33cc85697b68cc897137c863238f412ceff46 0 1642227143000 3 connected
a23364d45a6d2f56d94021815af2c8f3ef6cb499 10.150.22.47:6382@16382 myself,master - 0 1642227142000 2 connected 5461-10922
150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 139.198.105.99:6384@16384 slave daa3085081481d5c893f100a4d742a7160a8616c 0 1642227143000 8 connected
### 查看集群状态
root@docker:/data# redis-cli --cluster check 139.198.105.99:6381
139.198.105.99:6381 (daa30850...) -> 2 keys | 5461 slots | 1 slaves.
139.198.105.99:6382 (a23364d4...) -> 1 keys | 5462 slots | 1 slaves.
139.198.105.99:6383 (15a33cc8...) -> 1 keys | 5461 slots | 1 slaves.
[OK] 4 keys in 3 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 139.198.105.99:6381)
M: daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: 2075147d5c9e90275c1eb7e1b1ad32912129a13e 139.198.105.99:6386
slots: (0 slots) slave
replicates 15a33cc85697b68cc897137c863238f412ceff46
M: a23364d45a6d2f56d94021815af2c8f3ef6cb499 139.198.105.99:6382
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
M: 15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
S: 48656f0b83bd65f31a4478fc17be8322efcd2ede 139.198.105.99:6385
slots: (0 slots) slave
replicates a23364d45a6d2f56d94021815af2c8f3ef6cb499
S: 150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 139.198.105.99:6384
slots: (0 slots) slave
replicates daa3085081481d5c893f100a4d742a7160a8616c
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
1.2.3 Redis 主从扩容案例
先恢复到3主3从,Redis集群扩容(新增主机6387无槽号) Master4 —> Port:6387;Slave4 —> Port:6388先添加 Master4 分配槽位号,再添加 Slave4 于 Master4连接
### 1.新建6387、6388两个节点+新建后启动+查看是否8节点
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
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
docker ps
### 2.进入6387容器实例内部
$ docker exec -it redis-node-7 /bin/bash
### 3.将新增的6387节点(空槽号)作为Master节点加入原集群
# 将新增的6387作为master节点加入集群
# redis-cli --cluster add-node 自己实际IP地址:6387 自己实际IP地址:6381
# 6387 就是将要作为 master 新增节点
# 6388 就是原来集群节点里面的领路人,相当于6387拜访6381的码头从而找到组织加入集群
$ docker exec -it redis-node-7 /bin/bash
root@docker:/data# redis-cli --cluster add-node 139.198.105.99:6387 139.198.105.99:6381
>>> Adding node 139.198.105.99:6387 to cluster 139.198.105.99:6381
>>> Performing Cluster Check (using node 139.198.105.99:6381)
......
>>> Send CLUSTER MEET to node 139.198.105.99:6387 to make it join the cluster.
[OK] New node added correctly.
### 4.检查集群情况
root@docker:/data# redis-cli --cluster check 139.198.105.99:6387
139.198.105.99:6387 (4cc6ed4c...) -> 0 keys | 0 slots | 0 slaves. "(暂时没有槽位号)"
139.198.105.99:6383 (15a33cc8...) -> 1 keys | 5461 slots | 1 slaves.
139.198.105.99:6381 (daa30850...) -> 2 keys | 5461 slots | 1 slaves.
139.198.105.99:6382 (a23364d4...) -> 1 keys | 5462 slots | 1 slaves.
[OK] 4 keys in 4 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 139.198.105.99:6387)
M: 4cc6ed4c3b00d3c9f625f9f83d34420dc34d8033 139.198.105.99:6387
slots: (0 slots) master
M: 15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
S: 2075147d5c9e90275c1eb7e1b1ad32912129a13e 139.198.105.99:6386
slots: (0 slots) slave
replicates 15a33cc85697b68cc897137c863238f412ceff46
S: 48656f0b83bd65f31a4478fc17be8322efcd2ede 139.198.105.99:6385
slots: (0 slots) slave
replicates a23364d45a6d2f56d94021815af2c8f3ef6cb499
S: 150f6fdc57070f702fb8bb9cfe40a1bd94608a7b 139.198.105.99:6384
slots: (0 slots) slave
replicates daa3085081481d5c893f100a4d742a7160a8616c
M: daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381
slots:[0-5460] (5461 slots) master
1 additional replica(s)
M: a23364d45a6d2f56d94021815af2c8f3ef6cb499 139.198.105.99:6382
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
### 5.重新分派槽号
# 重新分派槽号
# 命令:redis-cli --cluster reshard IP地址:端口号
# redis-cli --cluster reshard 139.198.105.99:6381
$ docker exec -it redis-node-1 /bin/bash
root@docker:/data# redis-cli --cluster reshard 139.198.105.99:6381
>>> Performing Cluster Check (using node 139.198.105.99:6387)
M: 4cc6ed4c3b00d3c9f625f9f83d34420dc34d8033 139.198.105.99:6387 # [--> 没有槽号]
slots: (0 slots) master
M: 15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
......
replicates daa3085081481d5c893f100a4d742a7160a8616c
M: daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381
slots:[0-5460] (5461 slots) master
1 additional replica(s)
M: a23364d45a6d2f56d94021815af2c8f3ef6cb499 139.198.105.99:6382
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
How many slots do you want to move (from 1 to 16384)? 4096
# [--> 16384/(Master数)->16384/4]
What is the receiving node ID? 4cc6ed4c3b00d3c9f625f9f83d34420dc34d8033
# [--> 4cc6ed4c3b00d3c9f625f9f83d34420dc34d8033 (redis-node-7 ID号)]
Please enter all the source node IDs.
Type 'all' to use all the nodes as source nodes for the hash slots.
Type 'done' once you entered all the source nodes IDs.
Source node #1: all
...
Ready to move 4096 slots.
Source nodes:
M: 15a33cc85697b68cc897137c863238f412ceff46 139.198.105.99:6383
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
M: daa3085081481d5c893f100a4d742a7160a8616c 139.198.105.99:6381
slots:[0-5460] (5461 slots) master
1 additional replica(s)
M: a23364d45a6d2f56d94021815af2c8f3ef6cb499 139.198.105.99:6382
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
Destination node:
M: 4cc6ed4c3b00
......
Do you want to proceed with the proposed reshard plan (yes/no)? yes
......"(等待数据迁移,迁移速度与机器的性能有关)"
### 6.检查集群情况
# 迁移数据完毕后,之前的节点匀了一部分槽号给新的Master
# redis-cli --cluster check 真实IP地址:6381
root@docker:/data# redis-cli --cluster check 139.198.105.99:6381
139.198.105.99:6381 (58e4217a...) -> 1 keys | 4096 slots | 1 slaves.
139.198.105.99:6383 (b67e4f11...) -> 1 keys | 4096 slots | 1 slaves.
139.198.105.99:6382 (a93838a2...) -> 1 keys | 4096 slots | 1 slaves.
139.198.105.99:6387 (fb19d789...) -> 1 keys | 4096 slots | 0 slaves.
[OK] 4 keys in 4 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 139.198.105.99:6381)
M: 58e4217a0588e05a8806411f61fc54915b781e5a 139.198.105.99:6381
slots:[1365-5460] (4096 slots) master
1 additional replica(s)
M: b67e4f11be048b6a0796de83b2350e44419621ac 139.198.105.99:6383
slots:[12288-16383] (4096 slots) master
1 additional replica(s)
M: a93838a2a0df5f771443f2000a650ff49b1a3e7e 139.198.105.99:6382
slots:[6827-10922] (4096 slots) master
1 additional replica(s)
replicates b67e4f11be048b6a0796de83b2350e44419621ac
M: fb19d789d779dd48ff2d78016a112880f10a3caf 139.198.105.99:6387
slots:[0-1364],[5461-6826],[10923-12287] (4096 slots) master [3 Master匀了一部分槽号给新的Master使用]
......
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
### 7.为主节点6387分配从节点6388
# 数据存储在节点上,节点存储在槽上,所以重新分配槽点,原有数据不影响读取
# 命令:redis-cli --cluster add-node Slave-IP:Slave-Port Master-IP:Master-Port --cluster-slave --cluster-master-id 新主机节点ID
# redis-cli --cluster add-node 139.198.105.99:6388 139.198.105.99:6387 --cluster-slave --cluster-master-id fb19d789d779dd48ff2d78016a112880f10a3caf(是6387的ID编号,按照自己实际情况填写)
$ docker exec -it redis-node-7 /bin/bash
root@docker:/data# redis-cli --cluster add-node 139.198.105.99:6388 139.198.105.99:6387 --cluster-slave --cluster-master-id fb19d789d779dd48ff2d78016a112880f10a3caf
>>> Adding node 139.198.105.99:6388 to cluster 139.198.105.99:6387
>>> Performing Cluster Check (using node 139.198.105.99:6387)
M: fb19d789d779dd48ff2d78016a112880f10a3caf 139.198.105.99:6387
slots:[0-1364],[5461-6826],[10923-12287] (4096 slots) master
M: b67e4f11be048b6a0796de83b2350e44419621ac 139.198.105.99:6383
slots:[12288-16383] (4096 slots) master
1 additional replica(s)
S: bf66a51258fd046b3e3e741d3adc8bdef7b34ffc 139.198.105.99:6384
slots: (0 slots) slave
replicates a93838a2a0df5f771443f2000a650ff49b1a3e7e
S: 1dcd70196a57e2862463938aa776c2ef3555b01c 139.198.105.99:6386
slots: (0 slots) slave
replicates 58e4217a0588e05a8806411f61fc54915b781e5a
S: 264e4329f261c70b3001028334a2fe161f30113d 139.198.105.99:6385
slots: (0 slots) slave
replicates b67e4f11be048b6a0796de83b2350e44419621ac
M: 58e4217a0588e05a8806411f61fc54915b781e5a 139.198.105.99:6381
slots:[1365-5460] (4096 slots) master
1 additional replica(s)
M: a93838a2a0df5f771443f2000a650ff49b1a3e7e 139.198.105.99:6382
slots:[6827-10922] (4096 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 139.198.105.99:6388 to make it join the cluster.
Waiting for the cluster to join
>>> Configure node as replica of 139.198.105.99:6387.
[OK] New node added correctly.
### 8.检查集群情况
root@docker:/data# redis-cli --cluster check 139.198.105.99:6381
139.198.105.99:6381 (58e4217a...) -> 1 keys | 4096 slots | 1 slaves.
139.198.105.99:6383 (b67e4f11...) -> 1 keys | 4096 slots | 1 slaves.
139.198.105.99:6382 (a93838a2...) -> 1 keys | 4096 slots | 1 slaves.
139.198.105.99:6387 (fb19d789...) -> 1 keys | 4096 slots | 1 slaves.
[OK] 4 keys in 4 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 139.198.105.99:6381)
M: 58e4217a0588e05a8806411f61fc54915b781e5a 139.198.105.99:6381
slots:[1365-5460] (4096 slots) master
1 additional replica(s)
M: b67e4f11be048b6a0796de83b2350e44419621ac 139.198.105.99:6383
slots:[12288-16383] (4096 slots) master
1 additional replica(s)
M: a93838a2a0df5f771443f2000a650ff49b1a3e7e 139.198.105.99:6382
slots:[6827-10922] (4096 slots) master
1 additional replica(s)
S: 264e4329f261c70b3001028334a2fe161f30113d 139.198.105.99:6385
slots: (0 slots) slave
replicates b67e4f11be048b6a0796de83b2350e44419621ac
M: fb19d789d779dd48ff2d78016a112880f10a3caf 139.198.105.99:6387
slots:[0-1364],[5461-6826],[10923-12287] (4096 slots) master
1 additional replica(s)
S: 08fc0002346c3dcbc343982388aa78c1d57c7d68 139.198.105.99:6388
slots: (0 slots) slave
replicates fb19d789d779dd48ff2d78016a112880f10a3caf
S: bf66a51258fd046b3e3e741d3adc8bdef7b34ffc 139.198.105.99:6384
slots: (0 slots) slave
replicates a93838a2a0df5f771443f2000a650ff49b1a3e7e
S: 1dcd70196a57e2862463938aa776c2ef3555b01c 139.198.105.99:6386
slots: (0 slots) slave
replicates 58e4217a0588e05a8806411f61fc54915b781e5a
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
# 测试数据是否存在
root@docker:/data# redis-cli -c -p 6381
127.0.0.1:6381> get k1
-> Redirected to slot [12706] located at 139.198.105.99:6383
"v1"
139.198.105.99:6383> get k2
-> Redirected to slot [449] located at 139.198.105.99:6387
"v2"
139.198.105.99:6387> get k3
-> Redirected to slot [4576] located at 139.198.105.99:6381
"v3"
139.198.105.99:6381> get k4
-> Redirected to slot [8455] located at 139.198.105.99:6382
"v4"
:::warning
槽号分派说明
为什么6387是3个新的区间,以前的还是连续?
重新分配成本太高,所以前3Master各自匀出来一部分,从6381 / 6382 / 6383三个旧的Master节点分别匀出1364个槽位给新的节点6387
:::
1.2.4 Redis 主从缩容案例
Redis 集群缩容,删除6387和6388,恢复3主3从:::warning
- 先清除从节点6388
- 清出来的槽号重新分配
- 再删除6387
- 恢复成3主3从
:::
### 1.目的:6387和6388下线
$ docker exec -it redis-node-1 /bin/bash
--------
### 2.检查集群情况1获得6388节点ID
root@docker:/data# redis-cli --cluster check 139.198.105.99:6381
139.198.105.99:6381 (58e4217a...) -> 1 keys | 4096 slots | 1 slaves.
139.198.105.99:6383 (b67e4f11...) -> 1 keys | 4096 slots | 1 slaves.
139.198.105.99:6382 (a93838a2...) -> 1 keys | 4096 slots | 1 slaves.
139.198.105.99:6387 (fb19d789...) -> 1 keys | 4096 slots | 1 slaves.
[OK] 4 keys in 4 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 139.198.105.99:6381)
M: 58e4217a0588e05a8806411f61fc54915b781e5a 139.198.105.99:6381
slots:[1365-5460] (4096 slots) master
1 additional replica(s)
M: b67e4f11be048b6a0796de83b2350e44419621ac 139.198.105.99:6383
slots:[12288-16383] (4096 slots) master
1 additional replica(s)
M: a93838a2a0df5f771443f2000a650ff49b1a3e7e 139.198.105.99:6382
slots:[6827-10922] (4096 slots) master
1 additional replica(s)
M: fb19d789d779dd48ff2d78016a112880f10a3caf 139.198.105.99:6387
slots:[0-1364],[5461-6826],[10923-12287] (4096 slots) master
1 additional replica(s)
S: 08fc0002346c3dcbc343982388aa78c1d57c7d68 139.198.105.99:6388
slots: (0 slots) slave
replicates fb19d789d779dd48ff2d78016a112880f10a3caf
......
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
--------
### 3.将6388删除,从集群中将4号从节点6388删除
# 命令:redis-cli --cluster del-node Slave-IP:Slave-Port 从机6388节点ID
# redis-cli --cluster del-node 139.198.105.99:6388 08fc0002346c3dcbc343982388aa78c1d57c7d68
root@docker:/data# redis-cli --cluster del-node 139.198.105.99:6388 08fc0002346c3dcbc343982388aa78c1d57c7d68
--------
# 检查集群情况1
root@docker:/data# redis-cli --cluster check 139.198.105.99:6381
139.198.105.99:6381 (58e4217a...) -> 1 keys | 4096 slots | 1 slaves.
139.198.105.99:6383 (b67e4f11...) -> 1 keys | 4096 slots | 1 slaves.
139.198.105.99:6382 (a93838a2...) -> 1 keys | 4096 slots | 1 slaves.
139.198.105.99:6387 (fb19d789...) -> 1 keys | 4096 slots | 0 slaves.
# 检查一下发现,6388被删除了,只剩下7台机器了
--------
### 4.将6387的槽号清空,重新分配,本例将清出来的槽号都给6381
root@docker:/data# redis-cli --cluster reshard 139.198.105.99:6381
......
How many slots do you want to move (from 1 to 16384)? 4096 "[--> 6381的节点,由它来接手空出来的槽号(共4096个)]"
What is the receiving node ID? 58e4217a0588e05a8806411f61fc54915b781e5a "[--> 6381的节点ID]"
Please enter all the source node IDs.
Type 'all' to use all the nodes as source nodes for the hash slots.
Type 'done' once you entered all the source nodes IDs.
Source node #1: fb19d789d779dd48ff2d78016a112880f10a3caf "[--> 6387的节点ID,告知删除该节点]"
Source node #2: done
Do you want to proceed with the proposed reshard plan (yes/no)? yes
......(等待数据迁移,迁移速度与机器的性能有关)
--------
### 5.检查集群情况2
root@docker:/data# redis-cli --cluster check 139.198.105.99:6381
139.198.105.99:6381 (58e4217a...) -> 2 keys | 8192 slots | 2 slaves.
139.198.105.99:6383 (b67e4f11...) -> 1 keys | 4096 slots | 1 slaves.
139.198.105.99:6382 (a93838a2...) -> 1 keys | 4096 slots | 1 slaves.
[OK] 4 keys in 3 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 139.198.105.99:6381)
M: 58e4217a0588e05a8806411f61fc54915b781e5a 139.198.105.99:6381
slots:[0-6826],[10923-12287] (8192 slots) master
2 additional replica(s)
M: b67e4f11be048b6a0796de83b2350e44419621ac 139.198.105.99:6383
slots:[12288-16383] (4096 slots) master
1 additional replica(s)
M: a93838a2a0df5f771443f2000a650ff49b1a3e7e 139.198.105.99:6382
slots:[6827-10922] (4096 slots) master
1 additional replica(s)
S: 264e4329f261c70b3001028334a2fe161f30113d 139.198.105.99:6385
slots: (0 slots) slave
replicates b67e4f11be048b6a0796de83b2350e44419621ac
S: fb19d789d779dd48ff2d78016a112880f10a3caf 139.198.105.99:6387
slots: (0 slots) slave
replicates 58e4217a0588e05a8806411f61fc54915b781e5a
S: bf66a51258fd046b3e3e741d3adc8bdef7b34ffc 139.198.105.99:6384
slots: (0 slots) slave
replicates a93838a2a0df5f771443f2000a650ff49b1a3e7e
S: 1dcd70196a57e2862463938aa776c2ef3555b01c 139.198.105.99:6386
slots: (0 slots) slave
replicates 58e4217a0588e05a8806411f61fc54915b781e5a
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
## 4096个槽位都指给了6381,它变成了8192个槽位,相当于全部都给6381了,不让要输入3次,一锅端。
--------
### 6.将6387删除
# 命令:redis-cli --cluster del-node Master-IP:Master-Port 主Master-6387节点ID
# redis-cli --cluster del-node 139.198.105.99:6387 fb19d789d779dd48ff2d78016a112880f10a3caf
root@docker:/data# redis-cli --cluster del-node 139.198.105.99:6387 fb19d789d779dd48ff2d78016a112880f10a3caf
------------------------------------------------------------------------------------------------------------
### 7.检查集群情况3
root@docker:/data# redis-cli --cluster check 139.198.105.99:6381
139.198.105.99:6381 (58e4217a...) -> 2 keys | 8192 slots | 1 slaves.
139.198.105.99:6383 (b67e4f11...) -> 1 keys | 4096 slots | 1 slaves.
139.198.105.99:6382 (a93838a2...) -> 1 keys | 4096 slots | 1 slaves.
[OK] 4 keys in 3 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 139.198.105.99:6381)
M: 58e4217a0588e05a8806411f61fc54915b781e5a 139.198.105.99:6381
slots:[0-6826],[10923-12287] (8192 slots) master
1 additional replica(s)
M: b67e4f11be048b6a0796de83b2350e44419621ac 139.198.105.99:6383
slots:[12288-16383] (4096 slots) master
1 additional replica(s)
M: a93838a2a0df5f771443f2000a650ff49b1a3e7e 139.198.105.99:6382
slots:[6827-10922] (4096 slots) master
1 additional replica(s)
S: 264e4329f261c70b3001028334a2fe161f30113d 139.198.105.99:6385
slots: (0 slots) slave
replicates b67e4f11be048b6a0796de83b2350e44419621ac
S: bf66a51258fd046b3e3e741d3adc8bdef7b34ffc 139.198.105.99:6384
slots: (0 slots) slave
replicates a93838a2a0df5f771443f2000a650ff49b1a3e7e
S: 1dcd70196a57e2862463938aa776c2ef3555b01c 139.198.105.99:6386
slots: (0 slots) slave
replicates 58e4217a0588e05a8806411f61fc54915b781e5a
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
Redis 存储数据的算法有:哈希取余分区,一致性哈希算法分区,哈希槽分区。
Redis 集群动态扩缩容,类似于弹性云的思想。需要使用的时候就进行扩容,不需要使用的时候进行缩容。
讲师邮箱:zzyybs@126.com
2 Dockerfile 解析
2.1 Dockerfile 是什么
:::warning
Dockerfile 是用来构建 Docker 镜像的文本文件(类似于Shell脚本),是由一条条构建镜像所需的指令和参数构成的脚本。:::
概述
官网
https://docs.docker.com/engine/reference/builder/
构建三步骤
- 编写Dockerfile文件
- docker build 命令构建镜像
- docker run 依镜像运行容器实例
2.2 Dockerfile 构建过程解析
2.2.1 Dockerfile 内容基础知识
:::warning
- 每条保留字指令都
**<font style="color:#E8323C;">必须为大写字母</font>**
并且后面要跟随至少一个参数 - 指令按照从上到下,顺序执行
表示注释
- 每条指令都会创建一个新的镜像层并对镜像进行提交
:::
2.2.2 Docker 执行 Dockerfile 大致流程
:::warning
- docker 从基础镜像运行一个容器
- 执行一条指令并对容器作出修改
- 执行类似 docker commit 的操作提交一个新的镜像层
- docker 再基于刚提交的镜像运行一个新容器
- 执行 dockerfile 中的下一条指令直到所有指令都执行完成
- 最后将所有指令汇集的容器执行 docker commit 操作提交镜像
- 并将最后所有指令汇集的容器删除
:::
2.2.3 小总结
:::warning 从应用软件的角度来看,Dockerfile、Docker镜像与Docker容器分别代表软件的三个不同阶段
- Dockerfile 是软件的原材料
- Docker 镜像是软件的交付品
- Docker 容器则可以认为是软件镜像的运行态,也即依照镜像运行的容器实例
Dockerfile 面向开发,Docker 镜像成为交付标准,Docker 容器则涉及部署与运维,三者缺一不可,合力充当Docker 体系的基石。
:::
- Dockerfile,需要定义一个 Dockerfile文件,Dockerfile 定义了进程需要的一切东西。Dockerfile 涉及的内容包括执行代码或者是文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版、服务进程和内核进程(当应用进程需要和系统服务和内核进程打交道,这时需要考虑如何设计 namespace 的权限控制)等等。
- Docker 镜像,在用Dockerfile 定义一个文件之后,docker build 时产生一个Docker 镜像,当运行 Docker 镜像时会真正开始提供服务。
- 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 格式
RUN <命令行命令>
# <命令行命令> 等同于,在终端操作的 shell 指令
# 例如:
RUN yum install -y vim
- exec 格式
RUN ["可执行文件", "参数1", "参数2"]
# 例如:
# RUN ["./test.php", "dev", "offline"] 等价于 RUN ./test.php dev offline
RUN 是在 docker build 时运行
2.3.4 EXPOSE
EXPOSEEXPOSE 定义说明里面的服务端口。 该指令通知 Docker 容器在运行时侦听指定的网络端口。您可以指定端口是在 TCP 还是 UDP 上侦听,如果未指定协议,则默认值为 TCP。EXPOSE。 该指令实际上并不发布端口。它充当构建映像的人员和运行容器的人员之间的一种文档类型,有关要发布哪些端口。若要在运行容器时实际发布端口,请使用 标志 on 发布和映射一个或多个端口,或使用标志发布所有公开的端口并将其映射到高阶端口。EXPOSE-pdocker run-P[ / …]
2.3.5 WORKDIR
WORKDIR /path/to/workdir指定在创建容器后,终端默认登录的进来工作目录,一个落脚点
2.3.6 USER
指定该镜像以什么样的用户去执行,如果都不指定,默认是 root2.3.7 ENV
用来在构建镜像过程中设置环境变量
ENV MY_PATH /usr/mytest
这个环境变量可以在后续的任何 RUN 指令中使用,这就如同在命令前面指定了环境变量前缀一样;
也可以在其他指令中直接使用这些环境变量
比如:WORKDIR $MY_PATH
2.3.8 ADD
ADD [—chown=将宿主机目录下的文件拷贝进镜像并且会自动处理 URL 和解压 tar 压缩包。该功能仅在用于构建 Linux 容器的 Dockerfiles 上受支持,在 Windows 容器上不起作用。由于用户和组所有权概念不会在 Linux 和 Windows 之间转换,因此使用 和 用于将用户和组名称转换为 ID 会将此功能限制为仅适用于基于 Linux 操作系统的容器。 如果 是可识别的压缩格式(标识、gzip、bzip2 或 xz)的本地 tar 存档,则将其解压缩为目录。来自远程 URL 的资源不会解压缩。当复制或解压缩目录时,它的行为与 相同,结果是:: ADD [—chown=] … : ] [“ “,… “ “]
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 演示介绍
演示覆盖操作
# 官网Dockerfile文件内容
......
EXPOSE 8080
CMD ["catalina.sh", "run"]
$ docker run -it -p 8080:8080 -d billygoo/tomcat8-jdk8:latest /bin/bash
# 浏览器将无法访问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>
命令格式和案例说明
命令格式:
ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT 可以和 CMD 一起使用,一般是"变参"才会使用 CMD,这里的 CMD 等于是在给 ENTRYPOINT 传参
当指定了 ENTRYPOINT 后,CMD 的含义就发生了变化,
不再是直接运行其命令而是将 CMD 的内容作为参数传递给 ENTRYPOINT 指令,它两个组合会变成 <ENTRYPOINT> "<CMD>"
案例如下:假设已通过Dockerfile 构建了 "nginx:test 镜像"
FROM nginx
EXPOSE 80
ENTRYPOINT ["nginx", "-c"] # 定参
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 # 指定这个容器启动的时候要运行的命令,可以追加命令
:::
2.3.13 小总结
2.4 案例
2.4.1 自定义镜像 mycentos-java8
范例:centos 原始镜像
$ docker pull centos
$ docker images centos
REPOSITORY TAG IMAGE ID CREATED SIZE
centos latest 5d0da3dc9764 4 months ago 231MB
- 要求(
CentOS8镜像具备 vim+ifconfig+jdk8
) - 编写
准备编写 Dockerfile 文件(大写字母 D)
$ mkdir -pv /data/Dockerfile/mycentos-java8
# 将 jdk-8u301-linux-x64.tar.gz 存放在该目录下
$ tee > /data/Dockerfile/mycentos-java8/Dockerfile <<-'EOF'
FROM centos
MAINTAINER zhongzhiwei <93552399@qq.com>
ENV MYPATH /usr/local
WORKDIR ${MYPATH}
# 安装 VIM 编辑器
RUN yum install -y vim
# 安装 ifconfig 命令查看网络IP
RUN yum install -y net-tools
# 安装 Java 8 以及 lib 库
RUN yum install -y glibc.i686
RUN mkdir -p /usr/local/java
# ADD 是相对路径 jar,把jdk-8u301-linux-x64.tar.gz添加到容器中,安装包必须要和Dockerfile文件在同一位置
ADD jdk-8u301-linux-x64.tar.gz /usr/local/java
# 配置java环境变量
ENV JAVA_HOME /usr/local/java/jdk1.8.0_301
ENV JRE_HOME $JAVA_HOME/jre
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
ENV PATH $JAVA_HOME/bin:$PATH
EXPOSE 80
CMD echo ${MYPATH}"
CMD echo "Success----------OK"
CMD /bin/bash
EOF
##################################################################################################
FROM centos:centos7.9.2009
MAINTAINER zhongzhiwei <zhongzhiwei@kubesphere.io>
LABEL name="zhongzhiwei"
LABEL email="zhongzhiwei@kubesphere.io"
ENV MYPATH /usr/local
WORKDIR ${MYPATH}
# 安装VIM编辑器
RUN yum install -y vim
# 安装ifconfig查看网络IP
RUN yum install -y net-tools
# 安装java8以及lib库
RUN yum install -y glibc.i686
# ADD 是相对路径 jar,把jdk-8u301-linux-x64.tar.gz添加到容器中,安装包必须要和Dockerfile文件在同一位置
ADD jdk-8u301-linux-x64.tar.gz /usr/local/
RUN ln -s /usr/local/jdk1.8.0_301 /usr/local/java
# 配置java环境变量
ENV JAVA_HOME /usr/local/java
ENV JRE_HOME ${JAVA_HOME}/jre
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
ENV PATH $JAVA_HOME/bin:$PATH
EXPOSE 80
RUN echo "WORKDIR=${MYPATH}"
RUN echo "Success-----------------OK"
CMD [ "/bin/bash" ]
- 构建
# docker build -t 新镜像名字:TAG .
# 注意,上面TAG后面的有个空格,有个"."
# .号是指镜像构建时打包上传到Docker引擎中的文件的目录,不是本机目录
$ cd /data/Dockerfile/mycentos-java8/
$ docker build -t mycentos-java8:v1.0 .
- 运行
# docker run -it 新镜像名字:TAG
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mycentos-java8 v1.0 703a23d519ba 7 minutes ago 759MB
$ docker history mycentos-java8:v1.0
$ docker run -it --name mycentos mycentos-java8:v1.0 /bin/bash
[root@e61b71723942 local]# pwd
/usr/local
[root@77bee12e9cd0 local]# vim
vim vimdiff vimtutor
[root@77bee12e9cd0 local]# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.12 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:ac:11:00:0c txqueuelen 0 (Ethernet)
RX packets 8 bytes 656 (656.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[root@77bee12e9cd0 local]# java -version
java version "1.8.0_301"
Java(TM) SE Runtime Environment (build 1.8.0_301-b09)
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 虚悬镜像是什么
在 构建镜像,和删除镜像的时候出现一些错误,导致仓库名、标签都是
Dockerfile 写一个
$ tee > Dockerfile <<-'EOF'
# dangling image
FROM ubuntu
CMD echo 'action is success'
EOF
$ docker build .
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> bfe4d15873ec 5 seconds ago 72.8MB
2.4.2.2 查看虚悬镜像
$ docker image ls -f dangling=true
# 命令结果
$ docker image ls -f dangling=true
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> bfe4d15873ec 50 seconds ago 72.8MB
2.4.2.3 删除虚悬镜像
$ docker image prune
# 虚悬镜像已经失去存在价值,可以删除
$ docker image ls -f dangling=true
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> bfe4d15873ec 50 seconds ago 72.8MB
$ docker image prune
WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N] y
Deleted Images:
deleted: sha256:bfe4d15873eccee446d23e20acb01cf720c1d2b0590ceb12cc65211bcd0e83ec
Total reclaimed space: 0B
$ docker image ls -f dangling=true
2.4.3 自定义镜像 myubuntu-java8
- 编写:准备编写 Dockerfile 文件
$ mkdir -pv /data/Dockerfile/myubuntu-java8
# 将 jdk-8u301-linux-x64.tar.gz 存放在该目录下
$ tee > /data/Dockerfile/myubuntu-java8/Dockerfile <<-'EOF'
FROM ubuntu:20.04
MAINTAINER zhongzhiwei <zhongzhiwei@kubesphere.io>
LABEL name="zhongzhiwei"
LABEL email="zhongzhiwei@kubesphere.io"
ENV MYPATH /usr/local/
WORKDIR ${MYPATH}
# 修改APT为阿里云源
RUN sed -ir 's#http://(archive|security).ubuntu.com#http://mirrors.aliyun.com#ig' /etc/apt/sources.list
# 安装VIM编辑器
RUN apt-get update && \
apt-get install -y vim
# 安装ifconfig查看网络IP
RUN apt-get install -y net-tools iproute2 inetutils-ping
# ADD 是相对路径 jar,把jdk-8u301-linux-x64.tar.gz添加到容器中,安装包必须要和Dockerfile文件在同一位置
ADD jdk-8u301-linux-x64.tar.gz /usr/local/
RUN ln -sv /usr/local/jdk1.8.0_301 /usr/local/java
# 配置java环境变量
ENV JAVA_HOME /usr/local/java
ENV JRE_HOME ${JAVA_HOME}/jre
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
ENV PATH $JAVA_HOME/bin:$PATH
EXPOSE 80
RUN echo "WORKDIR=${MYPATH}"
RUN echo "Success-----------------OK"
CMD [ "/bin/bash" ]
EOF
- 构建:docker build -t 新镜像名字:TAG .
# docker build -t 新镜像名字:TAG .
# 注意,上面TAG后面的有个空格,有个"."
# .号是指镜像构建时打包上传到Docker引擎中的文件的目录,不是本机目录
$ cd /data/Dockerfile/myubuntu-java8/
$ docker build -t myubuntu-java8:v1.0 .
- 运行:docker run 新镜像名字:TAG
$ docker run -it --name myubuntu myubuntu-java8:1.0
root@7d64b9ac0320:/usr/local# pwd
/usr/local
root@7d64b9ac0320:/usr/local# java -version
java version "1.8.0_301"
Java(TM) SE Runtime Environment (build 1.8.0_301-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.301-b09, mixed mode)
root@7d64b9ac0320:/usr/local# vim
vim vim.basic vimdiff vimtutor
root@7d64b9ac0320:/usr/local# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.8 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:ac:11:00:08 txqueuelen 0 (Ethernet)
RX packets 8 bytes 656 (656.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions
2.4.4 自定义镜像 myalpine-java8
- 编写:准备编写 Dockerfile 文件
$ tee > /data/Dockerfile/myalpine-java8/Dockerfile <<-'EOF'
FROM alpine
MAINTAINER zhongzhiwei <zhongzhiwei@kubesphere.io>
LABEL name="zhongzhiwei"
LABEL email="zhongzhiwei@kubesphere.io"
ENV MYPATH /usr/local/
WORKDIR ${MYPATH}
# 修改APT为清华源
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
# 安装VIM编辑器
RUN apk add vim
# 安装ifconfig查看网络IP
RUN apk add net-tools
# 安装OpenJDK 和 bash
RUN apk add bash openjdk8
# 配置java环境变量
ENV JAVA_HOME=/usr/lib/jvm/java-1.8-openjdk
ENV PATH="$JAVA_HOME/bin:${PATH}"
EXPOSE 80
RUN echo "WORKDIR=${MYPATH}"
RUN java -version
RUN echo "Success-----------------OK"
CMD [ "/bin/bash" ]
EOF
- 构建:docker build -t 新镜像名字:TAG .
# docker build -t 新镜像名字:TAG .
# 注意,上面TAG后面的有个空格,有个"."
# .号是指镜像构建时打包上传到Docker引擎中的文件的目录,不是本机目录
$ cd /data/Dockerfile/myalpine-java8/
$ docker build -t myalpine-java8:v1.0 .
- 运行:docker run 新镜像名字:TAG
$ docker run -it --rm myalpine-java8:1.0
bash-5.1# pwd
/usr/local
bash-5.1# vim --version | head -n 3
VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Apr 28 2022 19:03:08)
Included patches: 1-4836
Compiled by Alpine Linux
bash-5.1# java -version
openjdk version "1.8.0_322"
OpenJDK Runtime Environment (IcedTea 3.22.0) (Alpine 8.322.06-r0)
OpenJDK 64-Bit Server VM (build 25.322-b06, mixed mode)
2.5 小总结
3 Docker 微服务实战
3.1 通过 IDEA 新建一个普通微服务模块
- Spring Initializr
- 建 Module
docker_boot
# 添加 Spring_web的模块
- 改 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.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.atguigu</groupId>
<artifactId>docker_boot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>docker_boot</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<testcontainers.version>1.16.2</testcontainers.version>
</properties>
<dependencies>
<!--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>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>${testcontainers.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 写 YML
$ vim application.properties
server.port=8080
- 主启动
$ vim DockerApplication
package com.atguigu.docker_boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DockerBootApplication {
public static void main(String[] args) {
SpringApplication.run(DockerBootApplication.class, args);
}
}
- 业务类
$ vim OrderController
package com.atguigu.docker_boot;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
@RestController
public class OrderController {
@Value("${server.port}")
private String port;
@RequestMapping("/order/docker")
public String helloDocker() {
return "hello docker"+"\t"+port+"\t"+ UUID.randomUUID().toString();
}
@RequestMapping(value = "/order/index",method = RequestMethod.GET)
public String index() {
return "服务端口号:"+"\t"+port+"\t"+ UUID.randomUUID().toString();
}
}
运行结果:
将微服务项目打包成 jar 包,上传到 Docker 服务器中
3.2 通过 Dockerfile 发布微服务部署 Docker 容器
3.2.1 IDEA 工具搞定微服务.jar包
将IDEA的Springboot
项目的利用maven package
生成jar包上传至云服务器上
$ mkdir -pv /data/Dockerfile/springboot ; cd /data/Dockerfile/springboot
$ tree
.
├── Dockerfile
└── target
└── docker_boot-0.0.1-SNAPSHOT.jar
1 directory, 2 files
3.2.2 编写 Dockerfile
- Dockerfile 内容
$ tee > /data/Dockerfile/springboot/Dockerfile <<-'EOF'
# 基础镜像使用 openjdk:8-jdk
FROM openjdk:8-jdk
# 作者
MAINTAINER zhongzhiwei <935523993@qq.com>
# 环境变量
ENV MYPATH /
WORKDIR $MYPATH
# VOLUME 指定临时文件目录为/tmp,在主机/var/lib/docker目录下创建了一个临时文件并链接到容器的/tmp
VOLUME /tmp
# 将jar包添加到容器中并更名为zzw_docker.jar
ADD target/docker_boot-0.0.1-SNAPSHOT.jar zzw_docker.jar
# 运行jar包
# 这个touch命令的作用是修改这个文件的访问时间和修改时间为当前时间,而不会修改文件的内容。
RUN bash -c 'touch /zzw_docker.jar'
ENTRYPOINT ["java", "-jar", "/zzw_docker.jar"]
# 暴露 8080 端口作为微服务
EXPOSE 8080
EOF
- 将微服务jar包和Dockerfile文件上传到同一个目录下 /data/Dockerfile/springboot
3.2.3 构建镜像
打包成镜像文件
$ cd /data/Dockerfile/springboot/
$ docker build -t zzw_docker:v1.0 .
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
zzw_docker v1.0 5c14d7858a82 25 seconds ago 565MB
3.2.4 运行容器
$ docker run --name zzw_springboot -it -d -p 8080:8080 zzw_docker:v1.0
6354b13dd4f5a18b8990f626a9ff510937156910aa6384079f5bbaecab497528
$ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
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 访问测试
# http://<IP Address>:8080/order/index
# http://<IP Address>:8080/order/docker
# http://139.198.105.99:8080/order/index
# http://139.198.105.99:8080/order/docker
范例:
$ curl 127.0.0.1:8080/order/docker
hello docker 8080 422b9d9b-562e-4d48-a198-c5d3659708b2
$ curl 127.0.0.1:8080/order/index
服务端口号: 8080 ece1f242-efbe-4aba-80a7-a3c59856b1f9
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。
- network:网络,可以理解为 Driver,是一个第三方网络栈,包含多种网络模式。包括单主机网络模式(none , host , bridge , container),多主机网络模式(overlay , flannel , calico , macvlan)
- sandbox:沙盒,定义了容器内的虚拟网卡,DNS和路由表,是 Network NameSpace 的一种实现,是容器的内部网络栈
- endpoint:端点,用于连接 sandbox 和 network
类比传统网络模型,将 network 比作交换机,sandbox 比作网卡,endpoint 比作接口和网线。
Docker 在创建容器时,先调用控制器创建 sandbox 对象,再调用容器运行时为容器创建 Network NameSpace。
4.1.1 Docker 不启动,默认网络情况
- eth0:Linux 宿主机网卡地址;
- lo:本地回环网卡地址;
- 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 ]
$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.150.22.47 netmask 255.255.255.0 broadcast 10.150.22.255
inet6 fe80::5054:96ff:fe78:7a16 prefixlen 64 scopeid 0x20<link>
ether 52:54:96:78:7a:16 txqueuelen 1000 (Ethernet)
RX packets 14706908 bytes 7035382687 (6.5 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 8721596 bytes 4537719701 (4.2 GiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 1027 bytes 217941 (212.8 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 1027 bytes 217941 (212.8 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
virbr0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 192.168.122.1 netmask 255.255.255.0 broadcast 192.168.122.255
ether 52:54:00:6d:68:42 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
4.1.2 Docker 启动后,网络情况
Docker Service 启动后,会产生一个名为 docker0 的虚拟网桥
$ ifconfig docker0
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:de:b5:b9:db txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
# 查看 docker 网络模式命令
# 默认创建 3大网络模式
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
bc338e0ef136 bridge bridge local
3355fadc24fb host host local
e6896debfa34 none null local
4.2 Docker Network 常用基本命令
- All 命令
$ docker network --help
Usage: docker network COMMAND
Manage networks
Commands:
connect Connect a container to a network
create Create a network
disconnect Disconnect a container from a network
inspect Display detailed information on one or more networks
ls List networks
prune Remove all unused networks
rm Remove one or more networks
Run 'docker network COMMAND --help' for more information on a command.
- 查看 Docker 网络
$ docker network ls
范例:
# 当我们安装 Docker 后,默认创建 3大网络模式
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
f385248bf26c bridge bridge local
207f34d7d987 host host local
416f2c51849f none null local
- 查看网络源数据
$ docker network inspect xxx网络名字
# docker network inspect bridge
- 删除网络
$ docker network create xxx网络名字
- 案例
# 创建 docker 网络
$ docker network create aa_network
86d3b99dbb14502c244df115d30ccc2fedc9dfe3d03a7a43023911df83541f96
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
86d3b99dbb14 aa_network bridge local
bc338e0ef136 bridge bridge local
3355fadc24fb host host local
e6896debfa34 none null local
# 删除 docker 网络
$ docker network rm aa_network
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
bc338e0ef136 bridge bridge local
3355fadc24fb host host local
e6896debfa34 none null local
# 查看网络源数据
$ 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.先启动两个Ubuntu容器实例
$ docker run -it --name u1 -d ubuntu /bin/bash
$ docker run -it --name u2 -d ubuntu /bin/bash
# 2.docker inspect 容器ID or 容器名字
$ docker inspect u1 | tail -n 20
docker inspect u1 | tail -n20
"Networks": {
"bridge": {
......
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.13",
"IPPrefixLen": 16,
......
$ docker inspect u2 | tail -n 20
"Networks": {
"bridge": {
......
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.7",
"IPPrefixLen": 16,
......
# 3.关闭u1容器实例,新建u3容器实例,查看IP变化
$ docker rm -f u1
$ docker run -it --name u3 -d ubuntu /bin/bash
$ docker inspect u3 | tail -n 20
"Networks": {
"bridge": {
......
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.13",
"IPPrefixLen": 16,
......
# u1容器实例的IP地址为172.17.0.13,当u1容器实例down掉之后,u3容器实例的IP地址继承了172.17.0.13,会导致底层网络访问对象发生改变
# 后期通过 docker compose 管理各个容器通信
# 容器里面用外网 就有点违背设计初衷了,隔离性沙箱就是他的一个优点。是可以实现的 用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地址和子网掩码,让主机和容器之间可以通过网桥相互通信
。
$ docker network inspect bridge
# 创建 bb_network bridge网络
$ docker network create bb_network
$ docker network inspect bb_network
# 查看 bridge 网络的详细信息,并通过grep获取名称项
$ docker network inspect bridge | grep name
"com.docker.network.bridge.name": "docker0",
# 使用ifconfig查看
$ ifconfig | grep docker
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
# 使用的驱动 Driver 均为 bridge
# 查看 docker 底层的防火墙策略(底层实现的NAT技术)
# 其实docker0是通过iptables和主机通信的,所有符合条件的的请求都会通过iptables转发到docker0并由网桥分发给对应的容器。
$ 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 run -it -d -p 8081:8080 --name tomcat81 billygoo/tomcat8-jdk8
docker run -it -d -p 8082:8080 --name tomcat82 billygoo/tomcat8-jdk8
- 两两匹配验证
$ ip addr
"353: vetha0c56a3@if352": <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether a6:d4:24:08:69:f6 brd ff:ff:ff:ff:ff:ff link-netnsid 12
inet6 fe80::a4d4:24ff:fe08:69f6/64 scope link
valid_lft forever preferred_lft forever
......
$ 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
#####
$ ip addr
"355: veth329b516@if354": <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 96:23:ec:3b:fb:31 brd ff:ff:ff:ff:ff:ff link-netnsid 13
inet6 fe80::9423:ecff:fe3b:fb31/64 scope link
valid_lft forever preferred_lft forever
$ docker exec -it tomcat82 /bin/bash
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
4.4.3.2 host
:::color1 host 模式:docker —network host 指定
:::
- 是什么
直接使用宿主机的IP地址与外界进行通信,不再需要额外进行NAT转换。
$ docker network inspect host
案例
- 说明
容器将不会获得一个独立的 Network NameSpace
,而是和宿主机共用一个 Network NameSpace。<font style="color:#F5222D;">容器将不会虚拟出自己的网卡而是使用宿主机的IP和端口。</font>
代码
- 警告
$ docker run -d -p 8083:8080 --network host --name tomcat83 billygoo/tomcat8-jdk8
# WARNING: Published ports are discarded when using host network mode
# 当使用host网络模式的时候,Published发布的端口是不被推荐的
# 问题:docker 启动时总是遇到标题中的警告
# 原因:
docker 启动时指定 --network=host 或者 -net host,如果还指定了 -p 映射端口,那么这个时候会有此警告,并且通过 -p 设置的参数将不会起到任何作用,端口号会以主机端口号为主,重复时则会递增。
# 解决:
解决的方法就是使用 docker 的其他网络模式,例如 --network=bridge,这样就可以解决问题,或者直接无视。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
819798960a6a billygoo/tomcat8-jdk8 "catalina.sh run" 3 minutes ago Up 3 minutes tomcat83
- 正确
$ docker run -d --network host --name tomcat83 billygoo/tomcat8-jdk8
- 无之前的配对显示,看容器实例内部
$ docker inspect tomcat83 | tail -n 20
"Networks": {
"host": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "3355fadc24fbafd84c894915c5ead2defedf496088605ae66cc009abcdd909e2",
"EndpointID": "a916ec2c7d46defeb16d444d553273cae2c8062fcfb25a9c8aebf155fdb1fb9f",
"Gateway": "",
"IPAddress": "",
"IPPrefixLen": 0,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "",
"DriverOpts": null
}
}
}
}
]
$ docker exec -it tomcat83 /bin/bash
root@docker:/usr/local/tomcat# ip addr
# 可以查看到宿主机的网络配置
- 没有设置 -p 的端口映射,如何访问启动的tomcat83
# 若有两个Tomcat容器使用默认8080端口并且使用host模式,那么将只会有一个容器启动成功,另外一个容器是退出状态
$ curl localhost:8080
# http://宿主机IP:8080/
# 在CentOS里面用默认的火狐浏览器访问容器内的tomcat83看到访问成功,因为此时容器的IP借用主机的,
# 所以容器共享宿主机网络IP,这样的好处是外部主机与容器可以直接通信。
4.4.3.3 none
:::color1 none 模式:docker —network none 指定
:::
- 是什么
禁用了网络功能,只有 lo 标识(就是127.0.0.1表示本地回环地址)。
在 none 模式下,并不为 Docker 容器进行任何网络配置;也就是说,这个Docker 容器没有网卡、IP、路由等信息,只有loopback(lo);需要自己为Docker容器添加网卡,配置IP等。
- 案例
$ docker run -it --name tomcat84 -p 8084:8080 --network none -d billygoo/tomcat8-jdk8
$ docker inspect tomcat84 | tail -n 20
"Networks": {
"none": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "e6896debfa348a25d13e1c52c74e2a219a6b5b69fa53962366abf984f8bf11d0",
"EndpointID": "a48129e97081eec074ffc7bfea6ce72343c74ee7a336322aea9202f374c35570",
"Gateway": "",
"IPAddress": "",
"IPPrefixLen": 0,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "",
"DriverOpts": null
}
}
}
}
]
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9f2e27b93f0a billygoo/tomcat8-jdk8 "catalina.sh run" 58 seconds ago Up 55 seconds tomcat84
$ docker exec -it tomcat84 /bin/bash
root@9f2e27b93f0a:/usr/local/tomcat# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 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
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)方面共享;其他的如文件系统,进程列表等还是隔离的。
- 案例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
- 案例
### 案例
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 运行的基本流程为:
用户是使用 Docker Client 与 Docker Daemon 建立通信,并发送请求给后者
Docker Daemon 作为 Docker 架构中的主体部分,首先提供 Docker Server 的功能使其可以接受 Docker Client 的请求
Docker Engine 作为 Docker 内部的一系列工作,每一项工作都是以一个 Job 的形式的存在
Job 的运行过程中,当需要容器镜像时,则从Docker Registry 中下载镜像,并通过镜像管理驱动 Graph Driver 将下载镜像以 Graph 的形式存储。
当需要为 Docker 创建网络环境时,通过网络管理驱动 Network Driver 创建并配置 Docker 容器网络环境
当需要限制 Docker 容器运行资源或者执行用户指令等操作时,则通过 Exec Driver 来完成
Libcontainer 是一项独立的容器管理包,Network Driver 以及 Exec Driver 都是通过 Libcontainer 来实现具体对容器进行的操作
4.5.2 整体架构
5 Docker-compose 容器编排
:::color1
- 涉及容器的启动顺序和加载条件及要求
- 容器越来越多,如何统一管理起来
:::
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
- 编写
<font style="color:#E8323C;">Dockerfile</font>
定义各个微服务应用并构建出对应的镜像文件 - 使用
<font style="color:#E8323C;">docker-compose.yml</font>
定义一个完整业务单元,安排好整体应用中的各个容器服务 - 最后,执行
<font style="color:#E8323C;">docker-compose -f docker-compose.yml up -d</font>
命令来启动并运行整体应用程序,完成一键部署上线 [ 等价于一次性运行了多次 docker run …… ] - 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.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 所在路径的目录)
# 若指定容器名称,则默认的命名规则优先级低,将不会生效
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 则适合集群部署
6 Docker 轻量级可视化工具 Portainer
6.1 Portainer 是什么
Portainer 是一款轻量级的应用,它提供了图形化界面,用于方便的管理Docker环境,包括单机环境和集群环境(毫不犹豫直接使用 K8S)。用于监控和统计。6.2 Portainer 安装
安装文档: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
- 第一次登录需创建admin,访问地址:IP地址:9000 [http://IP:9000/#/init/admin](http://IP:9000/#/init/admin)
- 设置admin用户和密码后首次登录
:::warning
用户名,直接用默认admin
(密码记得8位,随便你写)
:::
- 选择local选项卡后本地docker详细信息显示
- 上一步的图形展示,以及对应的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>**
解决方法:
:::warning
输入指令:systemctl restart docker
。重启Docker服务即可重新生成自定义链DOCKER
:::
6.3 登录并演示介绍常用操作 case
查看容器的资源占用
创建容器实例
根据引导对容器的配置进行修改
7 Docker 容器监视之 CAdvisor + InfluxDB + Grafana
7.1 原生命令
# 查看本机所有容器的资源占用情况
# -a 选项 :查看所有的容器实例的资源情况
# --no-stream 选项:禁用流统计,只提取第一个结果
$ docker stats
# 查看本地指定容器的资源占用情况
$ docker stats portainer
# 显示一个容器正在运行的进程
$ docker top portainer
:::warning 问题:通过 docker stats 命令可以很方便的看到当前宿主机上所有容器的CPU,内存以及网络流量等数据,一般小公司够用了。。。
但是,docker stats 统计结果只能是当前宿主机的全部容器,数据资料是实时的,没有地方存储,没有监控指标过线预警等功能。
:::
7.2 CIG 是什么
容器监控三剑客,主要是为了更复杂,大型Docker容器的监控。 一句话:CAdvisor 监控收集 + InfluxDB 存储数据 + Grafana 展示图表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](/uploads/projects/seekerzw@yaaygq/4a025fa63d7f63e7ae854a416eb80342.png)
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](/uploads/projects/seekerzw@yaaygq/eecc726a06aca8375e132a3b90a141a9.png)
![💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图52](/uploads/projects/seekerzw@yaaygq/5c493bec664f9a75d8d939f09feaa2ef.png)
![💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图53](/uploads/projects/seekerzw@yaaygq/2a7f91f22a121a00178228e7d13bbee9.png)
![💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图54](/uploads/projects/seekerzw@yaaygq/7dcc40e5d0ecc8f7bf11c07412439426.png)
![💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图55](/uploads/projects/seekerzw@yaaygq/658564747035a6851f08373531727d78.png)
![💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图56](/uploads/projects/seekerzw@yaaygq/573bb971e048601bdb0d76867329d0f8.png)
![💎[尚硅谷-Docker] 2022版Docker实战教程(高级篇) - 图57](/uploads/projects/seekerzw@yaaygq/6bffa5640b57c004c6cd64a43087bcc1.png)
- 添加数据源成功
8 终章 & 总结
8.1 知识回顾简单串讲和总结
Docker 的简介、Docker 安装以及配置、Docker 容器命令、Dockerfile 解析、Docker 网络、Docker-compose 编排微服务、Docker 复杂安装详说、Docker 轻量级可视化工具 Portainer、Docker 容器监控之CAdvisor + InfluxDB + Granfana8.2 进阶篇:Kubernetes
:::warning 【尚硅谷】Kubernetes(k8s)入门到实战教程丨全新升级完整版_哔哩哔哩_bilibili
尚硅谷Kubernetes教程(K8s入门到精通)_哔哩哔哩_bilibili
云原生Java架构师的第一课K8s+Docker+KubeSphere+DevOps_哔哩哔哩_bilibili
:::