2015/3/16

HAProxy and sticky session

在處理 HTTP Load Balance 的時候,很多網路服務都必須考量需不需要使用者登入後才能使用,以往不是 connection oriented 的 HTTP 也因此發展出 HTTP Session Tracking 的技術,在導入 HAProxy 時,也必須考量到,要如何持續支援 Session Tracking 的技術。

HTTP session

以往的網站在處理 session 時,會將 user 相關的資料存放在 client 端的 cookie 裡面,但 cookie 會在每一次 http request 都放在 header 裡面,因此產生了資訊安全的疑慮。

後來就發展了 Server Side 的 Session,以 J2EE jsessionid 為例,就是將 user 相關資料存放於 Server 的記憶體中,由 browser client 端 將一個 session id 紀錄起來,藉由這個 session id,來讓每一次同一個 browser 的所有 request 都可認定為同一個 user。

session 在 J2EE Server 可設定其時效,通常是設定為 10-20分鐘,當 server side session 的資料逾時,session 就會被清除,user 也必須重新登入一次,才能再執行接下來的工作。

通常 browser client 都有 enable cookie 的功能,可以將 session id 存放在 cookie 裡面,比較古老的 browser 沒有支援 cookie,或是 user 刻意將 cookie 功能 disabled 了,這時候 J2EE Server 會自動改用 URL Rewrite 的方式,將 jesssionid 存在 url 上面。

sticky session

當 server 要進行 load balance 的時候,通常會導入硬體或軟體的 load balancer,因為 J2EE Server 有存放了 session 資料,我們可選擇將 server 藉由 cluster 的方式,將 session 互相複製,但這種作法會消耗掉過多的 server side 資源,server 之間互相 replicate session 的次數越多,只會讓 dirty data 與 traffic 更頻繁,發生 data 錯誤的機會也會變高。

sticky session 通常是指 load balancer 支援了 session id 的處理能力,可以認出 http request 裡面的 jsessionid 的資料,並將同一個 jsessionid 的多個 request 都導向到同一個後端的 server 上。

load balancer 可選擇使用 J2EE 的 jessionid,或是使用自訂的 SERVERID 作為 sticky session 的判斷基準。

sticky session in HAProxy

HAProxy 要能夠支援 session id ,可以在 backend 的設定中增加 Cookie 的設定值。

設定說明如下:

cookie <name> [ rewrite | insert | prefix ] [ indirect ] [ nocache ] [ postonly ] [ preserve ] [ httponly ] [ secure ] [ domain <domain> ]* [ maxidle <idle> ] [ maxlife <life> ]
  • name
    就是 Cookie 的名稱,會透過 HTTP response 的 "Set-Cookie" header 將 Cookie 設定於 browser client。

  • rewrite | insert | prefix
    rewrite 表示 backend server 會提供此 cookie,但 HAproxy 必須修改此值,並將 server 的 id 填寫進去。因為必須監控所有 response 封包,所以只能在 HTTP close mode 運作。

    insert 表示此 cookie 必須由 HAPRoxy 填寫進去。如果沒有設定 preserve,而 server 也設定了此 cookie,則會先被移除,然後再重新新增進去。通常建議要搭配設定 "nocache" 與 "postonly"

    prefix 表示透過既有的一個 cookie,server id 會填寫到該 cookie 的 prefix 中,當 client 端發送 requst,其夾帶的 cookie value 會有 haproxy 填寫的 prefix,但會被 haproxy 去掉,只保留原本 server 設定的 cookie value,然後再 forward 給 server。

  • indirect
    server 設定的 cookie 都不會送到 client,這可讓 server 完全不知道 haproxy 的 sticky session 機制。

    建議不要同時使用 prefix 與 indirect

  • nocache
    搭配 insert 使用,可讓 cachable respone 貼上 non-cacheable 選項。

  • postonly
    確保 cookie insertion 只會作用在 responses to POST requests,這是 nocache 的替代方案,因為 POST response 都是 not cachable 的,就可以確保 persistence cookie 永遠不會被 cache。

  • preserve
    只能搭配 insert 與 indirect 使用。在這樣的狀況下,haproxy 會保留不更動 response cookie。

    可用在 logout request 之後,要 end persistence的地方,通常 server 會將 cookie 設定為空值。

  • httponly
    當 haproxy 增加新 cookie 時,同時設定了 HttpOnly 的屬性。

  • secure
    當 haproxy 增加新 cookie 時,同時設定了 Secure 的屬性。

  • domain
    設定 cookie 的 domain

  • maxidle
    idle 時間逾時後,cookie 就會沒有作用,只能用在 insert-mode cookies

  • maxlife
    life time 逾時後,cookie 就會沒有作用,只能用在 insert-mode cookies

範例:

cookie JSESSIONID prefix
cookie SRV insert indirect nocache
cookie SRV insert postonly indirect
cookie SRV insert indirect nocache maxidle 30m maxlife 8h

實際上比較完整的範例為

backend web_server_cluster
    mode http
    balance roundrobin
    cookie SERVERID insert indirect nocache
    # Web Server Cluster
    server web1 192.168.1.10:80 check cookie s1
    server web2 192.168.1.20:80 check cookie s2

直接在 JSESSIONID 上加上 server id 的 prefix

frontend ft_web
  bind 0.0.0.0:80
  default_backend bk_web

backend bk_web
  balance roundrobin
  cookie JSESSIONID prefix indirect nocache
  server s1 192.168.1.10:80 check cookie s1
  server s2 192.168.1.20:80 check cookie s2

appsession

設定說明

appsession <cookie> len <length> timeout <holdtime> [request-learn] [prefix] [mode <path-parameters|query-string>]
  • cookie
    application 使用的 cookie name,可讓 haproxy 了解到該 application 處理 session 的 cookie。

  • length
    該 cookie value 儲存的最大字元長度。

  • holdtime
    該 cookie 值不被使用時,保留在 haproxy 記憶體中的時間

  • request-learn
    設定後,haproxy 會由 request 中發現新的 session

  • prefix
    設定 haproxy 將會認定的 cookie prefix

    範例

      appsession ASPSESSIONID len 64 timeout 3h prefix
    

    如果 cookie 是 ASPSESSIONIDXXXX=XXXXX,appsession 的 value 將會是 XXXX=XXXXX

  • mode
    修改 URL parser mode,目前有兩種

    • path-parameters
      這是預設模式,在 path parameter 的裡面尋找 appsession。適用於 JSESSIONID,通常會是在分號的後面。

      當 browser client 不支援 cookie 時,jsessionid 就會放在 url 上面

        a.jsp;jsessionid=5AC6256DD8D4D5D1FDF5D41E9F2FD900?param1=value1&param2=value2
      
    • query-string
      在 query string 裡面尋找 appsession

範例

# Load balancing PHP sessions:

appsession PHPSESSID len 64 timeout 3h request-learn prefix

# Load balancing ASP.Net sessions:

appsession ASP.NET_SessionId len 64 timeout 3h request-learn prefix

# Load balancing ASP sessions:

appsession ASPSESSIONID len 64 timeout 3h request-learn prefix

# Load balancing Java Server sessions:

appsession JSESSIONID len 52 timeout 3h request-learn prefix

比較完整的範例:

global
    maxconn 10000
    daemon

defaults
    log      global
    mode      http
    contimeout      5000
    clitimeout      50000
    srvtimeout      50000

listen webfarm 0.0.0.0:80
    mode http
    balance roundrobin
    option httpchk HEAD /index.html HTTP/1.0
    appsession JSESSIONID len 128 timeout 1m
    server serverA 192.168.1.10:80 check
    server serverB 192.168.1.20:80 check

結語

綜合兩個設定方式的說明,如果認定 browser 一定會支援 cookie,就可以利用 cookie 設定來處理 session id,但如果要同時支援 cookie 以及 url 形式的 session tracking ,就要使用 appsession。

網路上找到的實際範例,多數都是使用 cookie,而不是 appsession。目前除非使用者刻意 disable browser cookie 的機制,否則要遇到不支援 cookie 的 browser 也很困難了。

如果要用 haproxy 專用的 server id 機制,當然就選擇利用 cookie,設定自訂的 cookie name。但如果是要沿用標準的 ASPSESSIONID/PHPSESSID/JSESSIONID,就直接用 appsession 來設定就可以了。

References

富人用 L4 Switch,窮人用 Linux HAProxy!

Configuring haproxy to load balance multiple engine.io servers

Trick My Proxy: Front Apache Tomcat with HAProxy instead of Apache

Sticky Session Load Balancing with HAProxy

load balancing, affinity, persistence, sticky sessions: what you need to know

Haproxy 安裝手記,版本升級並追加 log 機制

HAProxy configuration