使用 HAProxy 为多个 web 服务器实现负载均衡

The inside of a computer is as dumb as hell but it goes like mad!

— Richard Feynman

曾几何时,为缓慢的 Web 服务器加速的方式就是增加更多的 CPU 核心。 我记得一个老板买了一台 24核的 Sun 工作站,它是个尺寸接近悍马的怪物, 以至于我们不得不扩修数据中心的大门才能将其放入。

时至今日,扩展 Web 站点规模仍旧是添加 CPU 核心的问题,但与从前不同的是: 要么他们自己拿来若干粉色的 PC 机作为服务器使用; 要么从云服务提供商那里租用服务器作为计算资源使用。 为了对所有这些核心进行分组,从而一起服务于单一的 Web 站点, 我们要使用负载均衡器(load balancer)。

曾经,负载均衡器是坐落在机架上的一个耗资八万美元的大盒子。 尽管你现在仍旧可以购买这种负载均衡器,但对于大多数组织来说, 使用 Linux 商品服务器的软件负载均衡解决方案可以大大削减成本。

对大多数管理员来说,HAProxy 是软件负载均衡器的一种选择:快速、强大、高度可配置。 在下面的处方中,将向你展示如何创建一个 HAProxy 服务器为两个现有的后端服务器实现对其 web 请求的负载均衡。

操作步骤

  1. 创建 loadbalancer 模块:

    # mkdir /etc/puppet/modules/loadbalancer
    # mkdir /etc/puppet/modules/loadbalancer/manifests
    # mkdir /etc/puppet/modules/loadbalancer/files
    
  2. 使用如下内容创建 /etc/puppet/modules/loadbalancer/manifests/init.pp 文件:

    class loadbalancer {
      package { "haproxy": ensure => installed }
    
      file { "/etc/default/haproxy":
        source  => "puppet:///modules/loadbalancer/haproxy.defaults",
        require => Package["haproxy"],
      }
    
      service { "haproxy":
        ensure  => running,
        enable  => true,
        require => Package["haproxy"],
      }
    
      file { "/etc/haproxy/haproxy.cfg":
        source  => "puppet:///modules/loadbalancer/haproxy.cfg",
        require => Package["haproxy"],
        notify  => Service["haproxy"],
      }
    }
    
  3. 使用如下内容创建 /etc/puppet/modules/loadbalancer/files/haproxy.defaults 文件:

    # Don't edit this file - it's managed by Puppet
    # Set ENABLED to 1 if you want the init script to start haproxy.
    ENABLED=1
    # Add extra flags here.
    #EXTRAOPTS="-de -m 16"
    
  4. 使用如下内容创建 /etc/puppet/modules/loadbalancer/files/haproxy.cfg 文件。 在 myapp 部分中,用你的后端服务器 IP 地址替换每个 server 行中的 IP 地址, 并用你的后端服务器监听端口替换端口 :8000。

    global
            daemon
            user haproxy
            group haproxy
            pidfile /var/run/haproxy.pid
    
    defaults
            log        global
            stats      enable
            mode       http
            option     httplog
            option     dontlognull
            option     dontlog-normal
            retries    3
            option     redispatch
            contimeout 4000
            clitimeout 60000
            srvtimeout 30000
    
    listen stats :8080
           mode  http
           stats uri /
           stats auth haproxy:topsecret
    
    listen myapp 0.0.0.0:80
           balance leastconn
           server  myapp1 10.0.2.30:8000    check maxconn 100
           server  myapp2 10.0.2.40:8000    check maxconn 100
    
  5. 在你的 HAProxy 节点中包含如下代码:

    include loadbalancer
    
  6. 运行 Puppet:

    # puppet agent --test
    info: Retrieving plugin
    info: Caching catalog for cookbook.bitfieldconsulting.com
    info: Applying configuration version '1311616315'
    
    notice: /Stage[main]/Loadbalancer/Package[haproxy]/ensure: ensure
    changed 'purged' to 'present'
    
    --- /etc/haproxy/haproxy.cfg 2009-11-06 17:59:44.000000000
    
    +0000
    +++ /tmp/puppet-file20110725-16369-1b85cr8-0 2011-07-25
    18:09:03.749146699 +0000
    
    @@ -1,86 +1,28 @@
    -# this config needs haproxy-1.1.28 or haproxy-1.2.1
    ...
    
    info: /Stage[main]/Loadbalancer/File[/etc/haproxy/haproxy.
    cfg]: Filebucketed /etc/haproxy/haproxy.cfg to puppet with sum
    c3bfb0c86138552475dea458e8ab36f3
    
    notice: /Stage[main]/Loadbalancer/File[/etc/haproxy/haproxy.cfg]/
    content: content changed '{md5}c3bfb0c86138552475dea458e8ab36f3'
    to '{md5}fa5fac3cf31f043f0120d0d45cef3f54'
    
    info: /Stage[main]/Loadbalancer/File[/etc/haproxy/haproxy.cfg]:
    Scheduling refresh of Service[haproxy]
    
    notice: /Stage[main]/Loadbalancer/Service[haproxy]/ensure: ensure
    changed 'stopped' to 'running'
    
    notice: /Stage[main]/Loadbalancer/Service[haproxy]: Triggered
    'refresh' from 1 events
    
    --- /etc/default/haproxy 2009-11-06 17:59:21.000000000 +0000
    
    +++ /tmp/puppet-file20110725-16369-1ndfrti-0 2011-07-25
    18:09:05.749136866 +0000
    @@ -1,4 +1,5 @@
    
     # Set ENABLED to 1 if you want the init script to start haproxy.
    -ENABLED=0
    +ENABLED=1
     # Add extra flags here.
     #EXTRAOPTS="-de -m 16"
    +
    
    notice: /Stage[main]/Loadbalancer/File[/etc/default/haproxy]/
    content: content changed '{md5}a1f2deb7c7a10e55dc7c971a2288f5d4'
    to '{md5}2217d74d66bd72630268598b1f11f173'
    
    notice: Finished catalog run in 22.21 seconds
    
  7. 在你的浏览器中检查 HAProxy 的 stats 界面确保一切工作正常(注意我的 Backend 服务器显示的是 DOWN ,因为这些虚拟机还没有运行。当我启动它们之后,HAProxy 将会自动检测并重新标记它们的状态)。

    img/ch08sec04.png

工作原理

haproxy 守护进程监听进入的请求并将其分发到一组后端服务器(本例中是 myapp1myapp2)。 如果一个后端服务器已超载,HAProxy 将避免向其发送更多的流量直至它恢复。 这有助于防止单台 Web 服务器因为超载(排队等待的不能被处理的请求越来越多)而造成的响应速度大幅放缓。 如果一台后端服务器宕机,HAProxy 将不会为其分发任何请求,直至其重新可用。

stats 界面会显示:你的后端服务器如何执行,有多少会话正在处理, HAProxy 是否将后端服务器标记成了 UPDOWN,等信息。

更多用法

如果你想添加更多的后端服务器以处理不断增长的需求,只需在 haproxy.cfg 中添加更多的 server 行。 如果你发现现有的后端服务器响应速度越来越慢,请适当减少每个服务器的 maxconn 值。 HAProxy 有大量值得探索的配置参数,参考 HAProxy 的文档站点 http://haproxy.1wt.eu/#docs

如果你需要提供 SSL 的能力,可以将 Nginx 放在 HAProxy 的前端进行处理。

尽管 HAProxy 经常用于 Web 服务器,但它可以代理很多服务,不仅仅是 HTTP。 它可以处理任何 TCP 流量,所以你可以使用 HAProxy 为 MySQL 服务器、SMTP 服务器、 视频服务器以及任何你想要的服务器实现负载均衡。