haproxy使用小记

使用haproxy可以做到复用网云穿隧道暴露多个内网服务到公网。具体思路为,

  • 如果隧道协议为http
    • 根据http请求url前缀区分,转发到不同的http server
  • 如果隧道协议为tcp,那么可以复用隧道进行ssh协议,http协议传输,二者本质都是tcp协议,在流量层面有所区分。
    • 通过匹配流量特征,转发到ssh server或http server
# file: /etc/haproxy/haproxy.cfg
global
    maxconn     20000
    log         127.0.0.1 local0
    user        haproxy
    chroot      /usr/share/haproxy
    pidfile     /run/haproxy.pid
    daemon
 
defaults
    log global
    errorfile 400 /etc/haproxy/errorfiles/400.http
    errorfile 403 /etc/haproxy/errorfiles/403.http
    ...
 
frontend  main
    bind :5000
    mode                 http
    log                  global
    option               httplog
    option               dontlognull
    option forwardfor    except 127.0.0.0/8
    maxconn              8000
    timeout              client  30s
 
    # see: https://www.haproxy.com/blog/path-based-routing-with-haproxy
    acl app-miniserve      path           -i /miniserve
    acl app-miniserve      path_beg       -i /miniserve/
    use_backend miniserv        if app-miniserve
 
    default_backend             alist
 
# Proxy for l4-protocol (tcp)
frontend tcp_main
    bind :5001 name *:5001
    mode tcp
    tcp-request inspect-delay 1s
    tcp-request content accept if { req_len gt 0 }
    log /dev/log local0
    option tcplog
    timeout client 1h
 
    # 匹配ssh流量
    acl is_ssh payload(0,3) -m bin 535348
 
    # 规则1:匹配HTTP/1.0或HTTP/1.1的核心标识(兼容所有请求方法)
    # acl is_http_core payload(0,200) -m reg -i ^[a-z]+ .+ HTTP/1\.[01]\r?\n
    acl is_http_core payload(0,200) -m reg -i ^[a-z]+ [^\\x0d\\x0a]+ HTTP/1\.[01][\\x0d]?[\\x0a]
 
    # 规则2:兼容HTTP/2(可选,若需支持)
    acl is_http2 payload(0,3) -m bin 505249  # PRI 字符的16进制(HTTP/2 初始帧)
 
 
    # 根据流量转发到不同的后台服务,默认sshd
    use_backend ssh if is_ssh
    use_backend miniserv-tcp if is_http_core or is_http2
 
    default_backend ssh
 
# Http service (miniserve)
backend miniserv
    #http-request        replace-path   ^/miniserve(/)?(.*)     /\2
    mode        http
    balance     roundrobin
    timeout     connect 5s
    timeout     server  5s
    server      static 127.0.0.1:8080 check maxconn 30
 
# Http service (alist)
backend alist
    mode        http
    balance     roundrobin
    timeout     connect 5s
    timeout     server  30s
    timeout     queue   30s
    server  app1 127.0.0.1:5244 check maxconn 30
 
### TCP BACKENDS ###
 
# Tcp service (sshd)
backend ssh
    mode        tcp
    balance     roundrobin
    option      tcp-check
    timeout     connect 3s
    timeout     server  1h
    server  ssh1 127.0.0.1:22 check maxconn 30
 
# Tcp service (miniserve)
backend miniserv-tcp
    mode        tcp
    balance     roundrobin
    option      tcp-check
    timeout     server  60s
    timeout     connect  5s
    server  miniserv1 127.0.0.1:8080 check maxconn 30
 
# Tcp service (alist)
backend alist-tcp
    mode        tcp
    balance     roundrobin
    option      tcp-check
    timeout     server  60s
    timeout     connect  5s
    server  alist1 127.0.0.1:5244 check maxconn 30
 
# Tcp blackhole (reject anything)
backend _deny
    mode        tcp
    tcp-request content reject

必须知道的技巧

  • haproxy -c -f /etc/haproxy/haproxy/cfg:用于快速检验配置文件是否合法
  • haproxy -f haproxy.cfg -Wd:手动运行

日志

haproxy的日志比较特殊,由于安全原因haproxy会在chroot模式下运行,这就导致进程不能直接将日志输出到外部目录。需要一些额外配置,例如将日志输出到rsyslog/systemd-journald.

这里提供一个简单的方法,调整haproxy.service,将日志输出到stdout,

# /usr/lib/systemd/system/haproxy.service
[Unit]
Description=HAProxy Load Balancer
After=network-online.target
Wants=network-online.target
 
[Service]
EnvironmentFile=-/etc/default/haproxy
EnvironmentFile=-/etc/sysconfig/haproxy
Environment="CONFIG=/etc/haproxy/haproxy.cfg" "PIDFILE=/run/haproxy.pid" "EXTRAOPTS=-S /run/haproxy-master.sock"
ExecStart=/usr/bin/haproxy -Ws -f $CONFIG -p $PIDFILE $EXTRAOPTS
ExecReload=/usr/bin/haproxy -Ws -f $CONFIG -c $EXTRAOPTS
ExecReload=/bin/kill -USR2 $MAINPID
KillMode=mixed
Restart=always
SuccessExitStatus=143
#
Type=notify
#User=haproxy
#Group=haproxy
NoNewPrivileges=true
LimitNOFILE=65535
Restart=on-failure
RestartSec=5s
PrivateTmp=true
#ProtectSystem=strict
#ReadWritePaths=/run/haproxy.pid /run/haproxy-master.sock
#
StandardOutput=journal+console
StandardError=journal+console
SyslogIdentifier=haproxy
 
# The following lines leverage SystemD's sandboxing options to provide
# defense in depth protection at the expense of restricting some flexibility
# in your setup (e.g. placement of your configuration files) or possibly
# reduced performance. See systemd.service(5) and systemd.exec(5) for further
# information.
 
# NoNewPrivileges=true
# ProtectHome=true
# If you want to use 'ProtectSystem=strict' you should whitelist the PIDFILE,
# any state files and any other files written using 'ReadWritePaths' or
# 'RuntimeDirectory'.
# ProtectSystem=true
# ProtectKernelTunables=true
# ProtectKernelModules=true
# ProtectControlGroups=true
# If your SystemD version supports them, you can add: @reboot, @swap, @sync
# SystemCallFilter=~@cpu-emulation @keyring @module @obsolete @raw-io
 
[Install]
WantedBy=multi-user.target

配置haproxy.cfg中的日志项,

global
    maxconn     20000
    log         stdout format raw local0 info
    user        haproxy
    chroot      /usr/share/haproxy
    pidfile     /run/haproxy.pid
 
defaults
    log global
    option log-separate-errors
 
frontend  main
    bind :5000
    mode                 http
    log                  global
    option               httplog
    option               dontlognull
    option forwardfor    except 127.0.0.0/8
 
frontend tcp_main
    bind :5022 name *:5022
    mode tcp
    tcp-request inspect-delay 1s
    tcp-request content accept if { req_len gt 0 }
    option tcplog

如此一来,就可通过journalctl -fu haproxy查看haproxy的日志。