# 实现基础
- Keepalived + MySQL双主实现MySQL-HA
- kodbox多节点部署+共享NFS存储
# 推荐环境
> NFS共享存储是多应用节点部署的必需条件。
- **操作系统版本**:统一为 CentOS 7.9
- **应用服务器**:
- 数量:1台或更多
- 软件环境
- **nginx** 1.20 + **php** 7.4
- **数据库服务器**
- 数量:2台
- 软件环境: MySQL 5.7
- **keepalived**: 用于设置VIP(虚拟IP地址),实现MySQL-HA。
- **Redis服务器**
- 数量:1台
- 软件环境: Redis最新版
- **Office解析服务器**
- 数量:1台
- 软件环境:Docker最新版
- **可道云反向代理服务器**
- 数量:1台
- 软件环境: **nginx** 1.20
# 安装步骤
## 安装基础软件环境
### 安装nginx
> 需要在所有应用节点上执行
```bash
yum install yum-utils
vim /etc/yum.repos.d/nginx.repo
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
yum install nginx
systemctl enable nginx
mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.old
vim /etc/nginx/nginx.conf
```
```nginx
user nginx;
worker_processes auto;
pid /var/run/nginx.pid;
events {
use epoll;
worker_connections 51200;
multi_accept on;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
server_names_hash_bucket_size 512;
client_header_buffer_size 32k;
large_client_header_buffers 4 32k;
client_max_body_size 10G;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 60;
fastcgi_connect_timeout 3600;
fastcgi_send_timeout 3600;
fastcgi_read_timeout 3600;
fastcgi_buffer_size 64k;
fastcgi_buffers 4 64k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 256k;
fastcgi_intercept_errors on;
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 2;
gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml;
gzip_vary on;
gzip_proxied expired no-cache no-store private auth;
gzip_disable "MSIE [1-6]\.";
server_tokens off;
server {
listen 80;
root /var/www/html;
index index.php;
server_name _;
error_log /var/log/nginx/kodbox_error.log notice;
# access_log /var/log/nginx/kodbox_access.log main;
location ~ [^/]\.php(/|$) {
try_files $uri =404;
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_index index.php;
set $path_info $fastcgi_path_info;
set $real_script_name $fastcgi_script_name;
if ($fastcgi_script_name ~ "^(.+?\.php)(/.+)$") {
set $real_script_name $1;
set $path_info $2;
}
fastcgi_param SCRIPT_FILENAME $document_root$real_script_name;
fastcgi_param SCRIPT_NAME $real_script_name;
fastcgi_param PATH_INFO $path_info;
include fastcgi_params;
}
location ~* \.(jpg|jpeg|gif|png|css|js|ico|webp|tiff|ttf|svg)$ {
expires 30d;
}
location ~ .*\.(js|css)?$ {
expires 12h;
}
location = /favicon.ico {
log_not_found off;
}
}
}
```
### 安装php和psd支持环境
> 需要在所有应用节点上执行
```bash
yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install https://rpms.remirepo.net/enterprise/remi-release-7.rpm
yum-config-manager --enable remi-php74
yum install php php-cli php-fpm php-mysqlnd php-zip php-gd php-mcrypt php-mbstring php-xml php-pear php-bcmath php-redis php-memcached php-ldap php-intl php-pecl-imagick php-pecl-swoole php-opcache
chown -R nginx:nginx /var/lib/php
echo "cgi.fix_pathinfo=1" >> /etc/php.ini
sed -i \
-e "s/max_execution_time = 30/max_execution_time = 3600/g" \
-e "s/max_input_time = 60/max_input_time = 3600/g" \
-e "s/memory_limit = 128M/memory_limit = 1024M/g" \
-e "s/post_max_size = 8M/post_max_size = 512M/g" \
-e "s/upload_max_filesize = 2M/upload_max_filesize = 512M/g" \
/etc/php.ini
sed -i \
-e "s/pm.max_children = 50/pm.max_children = 1000/g" \
-e "s/pm.start_servers = 5/pm.start_servers = 150/g" \
-e "s/pm.min_spare_servers = 5/pm.min_spare_servers = 150/g" \
-e "s/pm.max_spare_servers = 35/pm.max_spare_servers = 200/g" \
-e "s/;pm.max_requests = 500/pm.max_requests = 500/g" \
-e "s/user = apache/user = nginx/g" \
-e "s/group = apache/group = nginx/g" \
-e "s/;listen.mode = 0660/listen.mode = 0666/g" \
-e "s/;listen.owner = nobody/listen.owner = nginx/g" \
-e "s/;listen.group = nobody/listen.group = nginx/g" \
-e "s/listen = 127.0.0.1:9000/listen = \/var\/run\/php-fpm.sock/g" \
/etc/php-fpm.d/www.conf
systemctl enable php-fpm && systemctl restart php-fpm
yum install ImageMagick dcraw ghostscript ffmpeg libjpeg libjpeg-devel libpng libpng-devel libtiff libtiff-devel libungif libungif-devel freetype zlib
# 安装ffmpeg(视频缩略图)
$ curl http://doc.kodcloud.com/tools/psd/install.sh | sh
# 安装完成后测试(没有报错说明安装成功)
$ convert && dcraw && ffmpeg
```
### 安装MySQL8
> 需要在2台数据库节点上执行
```bash
yum -y localinstall https://dev.mysql.com/get/mysql80-community-release-el7-6.noarch.rpm
yum install -y mysql-community-server
sed -i "s/LimitNOFILE = 10000/LimitNOFILE = infinity\nLimitMEMLOCK = infinity/g" /usr/lib/systemd/system/mysqld.service
cat >> /etc/my.cnf << EOF
thread_cache_size=512
max_allowed_packet=1G
join_buffer_size=8M
sort_buffer_size=8M
read_buffer_size=8M
read_rnd_buffer_size=8M
tmp_table_size=2048M
max_connections=5000
binlog_cache_size=1M
max_heap_table_size=256M
innodb_log_buffer_size=32M
innodb_buffer_pool_size=8G
innodb_flush_log_at_trx_commit=2
EOF
systemctl enable mysqld && systemctl start mysqld
grep 'temporary password' /var/log/mysqld.log ##获取临时密码
mysql_secure_installation ##根据提示步骤进行安全设置
```
### 配置Nginx反向代理服务器
> 作用是转发请求到后端应用节点
```bash
yum install yum-utils
vim /etc/yum.repos.d/nginx.repo
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
yum install nginx
systemctl enable nginx
mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.old
vim /etc/nginx/nginx.conf
```
* Nginx反向代理配置参考
```
#ip_hash;请求发送到的服务器由客户端 IP 地址确定,该方法保证来自同一地址的请求到达同一服务器,除非它不可用
#如果 NGINX 无法向服务器发送请求或在 30 秒内没有收到 3 次响应,它会将服务器标记为 30 秒不可用
#server填写的是后端应用服务器IP地址
{
upstream backend {
ip_hash;
server 192.168.60.45:8080 max_fails=3 fail_timeout=30s;
server 192.168.60.46:8080 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
server_name _;
client_max_body_size 0;
proxy_buffering off;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_connect_timeout 3600;
proxy_read_timeout 3600;
proxy_send_timeout 3600;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://backend;
}
}
}
```
### 安装redis
> 需要在Redis服务器节点上执行
```bash
yum-config-manager --enable remi
yum install redis
systemctl enable redis && sudo systemctl start redis
##若配置了redis密码,部署完成后手动修改站点下./config/setting_user.php,添加一行
$config['cache']['redis']['auth']='123456';//redis连接密码
```
### 安装onlyoffice
> 需要在office解析服务器节点上执行
```bash
curl -sSL https://get.daocloud.io/docker | sh
mkdir /var/lib/onlyoffice
systemctl enable docker && systemctl start docker
docker run -itd -p 8001:80 --name kodoffice -v /var/lib/onlyoffice:/var/lib/onlyoffice --restart always registry.cn-hangzhou.aliyuncs.com/kodcloud/kodoffice
```
## 配置MySQL双主同步
> 假设2台数据库服务器地址为 192.168.200.88、192.168.200.89
```bash
firewall-cmd --zone=public --add-port=3306/tcp --permanent
firewall-cmd --add-rich-rule='rule protocol value="vrrp" accept' --permanent
firewall-cmd --reload
```
### 修改 MySQL 配置文件
两个 MySQL 都必须通过将 log-bin=MySQL-bin 选项添加到 MySQL 配置文件 [mysqld] 部分来打开 bin 日志记录。 两个 MySQL 的 server-ID 不能相同。 默认情况下,两个 MySQL 的 serverID 都是 1,其中一个需要修改为 2。
#### master 1中replication的配置
```bash
vim /etc/my.cnf
log-bin = mysql-bin
binlog_format = mixed
server-id = 1
relay-log = relay-bin
relay-log-index = slave-relay-bin.index
auto-increment-increment = 2
auto-increment-offset = 1
```
#### master 2中replication的配置
```bash
vim /etc/my.cnf
log-bin = mysql-bin
binlog_format = mixed
server-id = 2
relay-log = relay-bin
relay-log-index = slave-relay-bin.index
auto-increment-increment = 2
auto-increment-offset = 2
```
> 注意:master1和master2只有`server-id`和`auto-increment-offset`不同。mysql有自增长字段,数据库主同步时设置自增长的相关配置有两个:auto_increment_offset和auto_increment_increment。 auto-increment-increment 表示从growth 字段开始每次增量的增量,默认值为1。它的值应设置为整个结构中的服务器总数。 本例中使用了两台服务器,因此将值设置为2。Auto-increment-offset用于设置数据库中自动增长的起点(即初始值),因为两台能源服务器都设置了一个auto -growth 值一次为 2,因此它们必须从不同的点开始,以避免在两台服务器之间同步数据时发生主键冲突。
### 设置master 1(主)、master 2(从)
#### 创建授权账户(master 1上)
```bash
mysql> GRANT REPLICATION SLAVE ON *.* TO 'rep'@'192.168.200.89' IDENTIFIED BY 'xxxx';
```
#### 查看master 1当前的binlog状态信息(master 1上)
```bash
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 154 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
```
#### 设置主从并启动slave (master 2上)
```bash
mysql> CHANGE MASTER TO
-> MASTER_HOST='192.168.200.88',
-> MASTER_USER='rep',
-> MASTER_PASSWORD='xxxx',
-> MASTER_PORT=3306,
-> MASTER_LOG_FILE='mysql-bin.000001',
-> MASTER_LOG_POS=154;
Query OK, 0 rows affected, 2 warnings (0.00 sec)
mysql> start slave;
Query OK, 0 rows affected (0.00 sec)
```
#### 查看同步状态
```bash
mysql> show slave status\G
```
> Slave_IO_Running 和 Slave_SQL_The Running 值必须是 Yes 表示可以从服务器正确连接服务器。(类似下图)

### 设置master 1(从)、master 2(主)
#### 创建授权账户(master 2上)
```bash
mysql> GRANT REPLICATION SLAVE ON *.* TO 'rep'@'192.168.200.88' IDENTIFIED BY 'xxxx';
```
#### 查看master 2当前的binlog状态信息(master 2上)
```bash
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 154 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
```
#### 设置主从并启动slave (master 1上)
```bash
mysql> CHANGE MASTER TO
-> MASTER_HOST='192.168.200.89',
-> MASTER_USER='rep',
-> MASTER_PASSWORD='xxxx',
-> MASTER_PORT=3306,
-> MASTER_LOG_FILE='mysql-bin.000001',
-> MASTER_LOG_POS=452;
Query OK, 0 rows affected, 2 warnings (0.00 sec)
mysql> start slave;
Query OK, 0 rows affected (0.00 sec)
```
#### 查看同步状态
```bash
mysql> show slave status\G
```
> Slave_IO_Running 和 Slave_SQL_The Running 值必须是 Yes 表示可以从服务器正确连接服务器。
### 安装配置keepalived
- keepalived 是一种软件解决方案,用于确保集群管理中集群的高可用性。 它的功能类似于心跳,基于VRRP协议,称为虚拟路由器冗余协议(VRRP),防止单点故障keepaliivedis。
- Virtual Routing Redundancy Protocol,可以认为是路由器的高可用协议,由N个提供相同功能的路由器组成一个路由器组,路由器组有一个master和多个备份,一个master有外部vip,一个master有 组播,在backup没有收到VRRP报文时考虑。Master宕机,所以需要根据VRRP的优先级选举一个backup作为master。这样保证了路由的高可用。
- Keepalived有core、check和vrrp三个主要模块,core模块是keepalived的核心,负责主进程的启动、维护以及全局配置文件的加载和解析。Check负责健康 检查,包括普通检查(模式1:tcp_check工作在第四层。模式2:http_get,工作在第五层,对指定的URL执行HTTP请求,将结果用md5加密并与指定的md5值进行比较查看 模式3:ssl_get:和http_get类似。模式4:misc_check:使用脚本检测。模式5:smtp_check:SMTP用于检测邮件服务。)VRRP模块 实现 VRRP 协议。
#### 安装keepalived
> 下面的命令在master1和master2中是一样的
```bash
wget https://keepalived.org/software/keepalived-2.2.7.tar.gz
tar xvf keepalived-2.2.7.tar.gz
cd keepalived-2.2.7/
yum install gcc openssl-devel
./configure --prefix=/usr/local
make -j $(lscpu | awk 'NR==4{print $2}') && make install
```
#### 修改keepalived配置文件
keepalived只有一个profileKeepalived.conf,主要包括以下配置区,global_defs、vrrp_instance和virtual_server。
- global_defs:主要配置故障发生时的通知对象和机器标识。
- vrrp_instance:用于定义外部服务交付的 VIP 区域及其相关属性。
- virtual_server:定义虚拟服务器。
```
# mkdir /etc/keepalived
# vim /etc/keepalived/keepalived.conf
global_defs {
router_id mysql-1 //代表运行keepalived服务器的id
}
vrrp_instance VI_1 {
state BACKUP //指定keepalived的作用,这里两个配置都是BACKUP,设置为BACKUP会根据优先级决定primary或second。
interface eth0 //指定 HA 监控的网络接口
virtual_router_id 51 //VVirtual Routing Identification,是一个数字(0到255之间的值,用来区分多个实例的VRRP Multicast),同一个vrrp实例使用唯一的标识,保证和master2相同,同一网络内不同集群这个必须不同,否则 ,发生冲突。
priority 100 //选举master的优先级,取值范围为1-255(在此范围内)
//默认值是100,然后在Master 2上设置50
advert_int 1 VRRP包的下发间隔,也就是master选举的频率(可以认为是健康检查)
//检查间隔)
authentication { //认证区,认证类型有PASS和HA(IPSEC),推荐PASS(密码只识别前8位)
auth_type PASS
auth_pass 1111
}
virtual_ipaddress { //指定vip地址
192.168.200.200/24
}
}
virtual_server 192.168.200.200 3306 { //要设置虚拟服务器,您需要指定虚拟 IP 地址和服务端口。 IP 用空格分隔端口
delay_loop 2 //以秒为单位设置运行时间
lb_algo rr //设置后端调度算法,其中rr为轮询算法
lb_kind DR //设置LVS机制实现负载均衡,有NAT、TUN、DR三种模式
persistence_timeout 50 //会话保持时间(以秒为单位)。此选项对于动态网页很有用,并为集群系统中的会话共享提供了很好的解决方案。通过此会话保留功能,将用户请求分发到服务节点,直到会话保持更长时间
protocol TCP //指定转发协议类型,TCP和UDP
real_server 192.168.200.88 3306 { //配置服务节点1需要指定真实服务器的真实IP地址和
//端口、IP以空格分隔
weight 1 //配置服务节点权重。重量的大小用数字表示。数字越大权重越高,设置权重
//区分不同性能服务器的值大小
notify_down /etc/keepalived/bin/mysql.sh //检测到realserver的mysql服务down掉后执行的脚本
TCP_CHECK {
connect_timeout 3 //连接超时
nb_get_retry 3 //重连次数
delay_before_retry 3 //重连间隔时间
connect_port 3306 //健康检查端口
}
}
}
```
Master2 的Keepalived.conf文件配置和master 1基本一样,除了`router_id`,`priority`, `Real`_三台不同的服务器,其他配置相同
```bash
## master1
mkdir /etc/keepalived
vim /etc/keepalived/keepalived.conf
global_defs {
router_id mysql-1
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.200.200/24
}
}
virtual_server 192.168.200.200 3306 {
delay_loop 2
lb_algo rr
lb_kind DR
persistence_timeout 50
protocol TCP
real_server 192.168.200.88 3306 {
weight 1
notify_down /etc/keepalived/bin/mysql.sh
TCP_CHECK {
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
connect_port 3306
}
}
}
```
```bash
## master2
mkdir /etc/keepalived
vim /etc/keepalived/keepalived.conf
global_defs {
router_id mysql-2
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 50
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.200.200/24
}
}
virtual_server 192.168.200.89 3306 {
delay_loop 2
lb_algo rr
lb_kind DR
persistence_timeout 50
protocol TCP
real_server 192.168.200.89 3306 {
weight 1
notify_down /etc/keepalived/bin/mysql.sh
TCP_CHECK {
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
connect_port 3306
}
}
}
```
#### 在 master1 和 master2 上添加检测脚本
作用是在mysql停止工作时自动关闭本地keepalived,从而将故障机器排除(因为每台机器上只添加本地realserver到keepalived),并在mysqld正常启动时手动启动keepalived服务。
```bash
mkdir /etc/keepalived/bin
vim /etc/keepalived/bin/mysql.sh
#!/bin/bash
systemctl stop keepalived
(/sbin/ifdown eth0 && /sbin/ifup eth0)
chmod +x /etc/keepalived/bin/mysql.sh
```
#### 在master1和master2上启动keepalived
```bash
## 设置在mysqld之后启动
vim /usr/lib/systemd/system/keepalived.service
## 在[Unit]部分添加
After=mysqld.service
systemctl daemon-reload && systemctl enable keepalived && systemctl start keepalived
```
### 测试
在 master1 和 master2 上分别执行 `ip addr show dev eth0`命令 ,查看 master1 和 master2 如何控制 VIP(集群虚拟 IP)。
出现mysql故障后,keepalived服务会自动停止,恢复命令:
```bash
systemctl start mysqld && systemctl start keepalived
```
> tips: master 1 优先级更高,当出现mysql故障时,VIP会漂移到master2上,故障恢复后 VIP还会 漂移回来。
> 对应的日志也有详细输出,可以查看`tail -f /var/log/messages`
>停止master 1上的 MySQL 服务会触发我们编写的脚本以自动进行故障转移。
# 配置kodbox
### 创建数据库
```sql
## 在master1 上执行,master2上会自动同步数据库和用户
CREATE DATABASE kodbox character set utf8mb4 collate utf8mb4_bin;
grant all privileges on kodbox.* to 'kodbox'@'%' identified by 'xxxx';
flush privileges;
```
### 初始化站点
> tips: 填写数据库配置时,数据库服务器填写VIP地址
```bash
##进入站点目录
cd /var/www/html
curl -L "https://api.kodcloud.com/?app/version&download=server.link" -o kodbox.zip
unzip kodbox.zip && rm -f kodbox.zip
chown -R nginx:nginx /var/www/html
chmod -R 755 /var/www/html
##然后访问服务器IP地址,在kodbox安装页面填写数据库账号,完成kodbox初始化
```
#### 更多说明
> 可道云也支持配置mysql主从读写和redis集群
./config/setting_user.php 配置参考如下
```
$config['cache']['sessionType']='redis';
$config['cache']['cacheType']='redis';
$config['cache']['redis']['auth']='123456';//redis连接密码
//mysql集群
$config['database']=array(
'DB_DEPLOY_TYPE'=>1,//设置分布式数据库支持
'DB_RW_SEPARATE'=>true,//分布式数据库的读写是否分离
'DB_TYPE'=>'mysqli',//数据库类型
'DB_HOST'=>'192.168.1.101,192.168.1.102,192.168.1.103',//数据库服务器地址
'DB_NAME'=>'kodbox2',//数据库名称
'DB_USER'=>'slave,slave,slave',//数据库用户名
'DB_PWD'=>'kod',//数据库密码
'DB_PORT'=>'3306,3306,3306',//数据库端口
'DB_PREFIX'=>'',//数据表前缀
'DB_SLAVE_NO' => '',
//'DB_MASTER_NUM'=>2
);
//redis集群
$config['cache']['redis']['mode'] = 'cluster'; // slave、sentinel、cluster 第二种(哨兵模式)暂不支持
$config['cache']['redis']['server'] = array(
'192.168.1.104:7001',
'192.168.1.104:7002',
'192.168.1.105:7003',
'192.168.1.105:7004',
'192.168.1.106:7005',
'192.168.1.106:7006',
);
```