# 使用HAProxy实现MySQL负载均衡

[在CentOS7中部署HAProxy](https://github.com/huataihuang/cloud-atlas-draft/tree/6f3204fffc11cf006abd394631e2598d98b415c3/service/haproxy/deploy_haproxy_in_centos7/README.md)实践中介绍了如何安装HAProxy，本文则结合HAProxy和MySQL来实现一个简单的负载均衡机群。注意：

> MySQL必须是[Master-Master replication](https://www.digitalocean.com/community/articles/how-to-set-up-mysql-master-master-replication)，这样才能确保数据库都能够写入。或者数据库都是slave模式并且限制外表访问只能读不能写，以实现只读模式负载均衡。

## 准备MySQL数据库

> 首先创建两个用户用于HAProxy

* 第一个用户账号将用于HAProxy检查服务器状态

```sql
mysql -u root -p -e "grant usage on mysql.user to haproxy_check@'172.17.0.1' identified by 'password'; FLUSH PRIVILEGES;"
```

* 创建一个MySQL用户从HAproxy访问MySQL机群，具有root权限。默认的root权限是只本地登陆，这里通过授权远程root访问，并且使用独立的root授权

```sql
mysql -u root -p -e "GRANT ALL PRIVILEGES ON *.* TO 'haproxy_root'@'172.17.0.1' IDENTIFIED BY 'password' WITH GRANT OPTION; FLUSH PRIVILEGES"
```

> 这里请使用实际的账号密码
>
> 这里`172.17.0.1`是HAProxy服务所在的服务器IP地址

## 在HAproxy服务器上安装MySQL客户端

* HAProxy使用mysql客户端来侦测连接，所以需要安装客户端

```
sudo dnf install mariadb
```

> CentOS 7安装mariadb来替代mysql - 如果是CentOS 6及以下版本使用yum安装`mysql-client`软件包

* 在HAproxy服务器上测试连接MySQL数据库，正常情况应该输出mysql数据库信息

```
mysql -h 172.17.0.5 -u haproxy_root -p -e "SHOW DATABASES"
```

## 安装和配置HAProxy

* 安装HAProxy软件包

```
sudo dnf install haproxy
```

* 备份初始HAProxy配置文件

```
sudo mv /etc/haproxy/haproxy.cfg{,.original}
```

* 编辑新配置文件

```
sudo vi /etc/haproxy/haproxy.cfg
```

首先是全局和默认配置部分：

```
global
    log 127.0.0.1 local0 notice
    user haproxy
    group haproxy

defaults
    log global
    retries 2
    timeout connect 3000
    timeout server 5000
    timeout client 5000
```

> 这里设置`log 127.0.0.1 local0 notice`表示HAProxy将日志发送127.0.0.1本地的rsyslog

然后是主要配置部分：

```
listen mysql-cluster
    bind 0.0.0.0:3306
    mode tcp
    option mysql-check user haproxy_check
    balance roundrobin
    server mysql-1 172.17.0.5:3306 check
    server mysql-2 172.17.0.6:3306 check
```

这个配置使得HAproxy监听在所有接口的3306端口上提供对外服务。此外，配置了服务器列表，通过`balance roundrobin`负载均衡方式转发到后端的2台MySQL服务器

> 注意：和HTTP负载均衡不同，HAProxy没有特定的MySQL模式，所以使用`tcp`
>
> HAProxy支持多种检测，默认是TCP检测，但是，这个检测方式对于数据库是不充分的。HAProxy使用MySQL Handshake Initialisation数据包和错误包来判断MySQL会话是否正常，所以对于数据库只需要设置`Host`表授权就可以了，不需要密码。

```
USE mysql;
INSERT INTO user (Host,User) values ('<ip_of_haproxy>','<username>');
FLUSH PRIVILEGES;
```

最后配置负载均衡的状态信息输出，也就是在HAProxy服务器上，提供一个对外`8080`端口访问提供统计页面 http\://:8080/ ：

```
listen status
    bind 0.0.0.0:8080
    mode http
    stats enable
    stats uri /
    stats realm Strictly\ Private
    stats auth user1:User1_Password
    stats auth user2:User2_Password
```

* 启动haproxy

```
sudo systemctl start haproxy
```

* 设置HAProxy负载均衡host主机的防火墙

> 注意CentOS 7默认开启了firewall防火墙，需要允许访问端口设置：

先检查有那些dmz区

```
$ firewall-cmd --get-active-zones
FedoraServer
  interfaces: wlp3s0
```

然后针对dmz区对外开放访问

```
sudo firewall-cmd --zone=FedoraServer --add-port=3306/tcp --permanent
sudo firewall-cmd --zone=FedoraServer --add-port=8080/tcp --permanent
sudo firewall-cmd --reload
```

### 问题排查

在HAProxy服务器上无法查询数据库表（虽然前面直接连接服务器可以访问到3306端口远程查询）

```
$ mysql -h 127.0.0.1 -u haproxy_root -p -e "SHOW DATABASES"
Enter password: 
ERROR 2013 (HY000): Lost connection to MySQL server at 'reading initial communication packet', system error: 0 "Internal error/check (Not system error)"
```

此外，`telnet <HAProxy>:3306` 显示网络连接立即被断开：`Connection closed by foreign host.`

> 补充说明：这个测试环境是在Docker中部署，mysql/mariadb服务器是在Docker容器中运行，而HAProxy是在host主机上运行。

检查Docker容器中运行的mysql/mariadb服务日志，发现是因为HAProxy是通过`172.17.0.1`的客户端来访问但是不使用密码（晕倒），所以导致了服务器拒绝了连接：

```
2017-11-07T09:44:39.636704Z 3004 [Note] Access denied for user 'haproxy_check'@'172.17.0.1' (using password: NO)
2017-11-07T09:44:41.638068Z 3005 [Note] Access denied for user 'haproxy_check'@'172.17.0.1' (using password: NO)
```

当前MySQL创建账号时候设置了密码，所以导致无法访问。临时的解决方法是，去除掉`haproxy_check`从特定IP访问的时候的密码限制，但是这不是一个解决方法。（真正的解决是要改正HAProxy的mysql检测脚本，添加上数据库访问账号。待以后探索）

* 临时取消密码访问

```
uninstall plugin validate_password;
SET PASSWORD FOR 'haproxy_check'@'172.17.0.1' = PASSWORD('');
```

> **`警告`**：这是一个非常严重的安全隐患，仅在可靠的安全措施下才可以在测试环境使用，务必尽快恢复原有安全设置。
>
> 以上配置修订之后，MySQL服务器上不再出现'haproxy\_check'@'172.17.0.1'连接报错，此时从 http\://:8080 访问，可以看到后端`mysql-1`服务器状态恢复正常，并且也可以在自己的开发桌面电脑上远程mysql访问Docker容器中运行的MySQL数据库。

## 参考

* [How To Use HAProxy to Set Up MySQL Load Balancing](https://www.digitalocean.com/community/tutorials/how-to-use-haproxy-to-set-up-mysql-load-balancing--3) 较为简化的文档
* [MySQL Load Balancing with HAProxy - Tutorial](https://severalnines.com/resources/tutorials/mysql-load-balancing-haproxy-tutorial) - 这篇文档非常详细


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://huataihuang.gitbook.io/cloud-atlas-draft/service/haproxy/mysql_load_balancing_haproxy.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
