在 Kubernetes 內取得使用者 IP - HTTP Loadbalancer
介紹如何在 GKE 上透過 HTTP LB 取得使用者真實 IP

文章目錄
對於提供 HTTP 服務的系統來說,取得來源 IP 方式有兩種:
- 利用封包標頭取得來源 IP
此方案是直接讀取封包的來源 IP,但由於容器和外界溝通不像傳統 Linux 主機有實體網卡對接,而是透過一系列的 NAT 規則置換封包標頭後才傳進容器內 (Understand container communication),導致取得錯誤的使用者 IP。
- 通過夾帶在 HTTP 請求的 X-Forwarded-For 來取得
此方案則是利用 PROXY Protocol,此方案是讓 Proxy Server 將 IP 附加在 HTTP 標頭 X-Forwarded-For 內,因此該標頭內的第一個位址即是使用者的真實 IP。
X-Forwarded-For:[61.219.125.41, 10.140.0.2]
下面會介紹在 GKE (Google Container Engine) 上透過 L7 HTTP Load Balancer 取得使用者真實 IP。
下圖為目前 Google 支援三種 LB

在 GKE 上新增 spec.type=LoadBalancer 的 Service,Kubernetes 會協助建立一組擁有獨立 IP 位址的 L4 TCP Load Balancer,因此無法支援 L7 應用層的 PROXY Protocol。

為此我們必須建立 Layer 7 的 HTTP Load Balancer,將其先連接到 NGINX instance group 再導向後方的 Kubernetes 叢集內。由於 HTTP 請求會先經過 NGINX reverse proxy,此時使用者 IP 會被紀錄在 X-Forwarded-For 內。

在 GCP 上建立 HTTP Load Balancer 的方式請參考 Setting Up HTTP(S) Load Balancing。
下面附上 NGINX 設定檔以及應用端取得 IP 的程式。
## /etc/nginx/nginx.conf
worker_processes  1;
error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;
events {
    worker_connections  1024;
}
http {
    include       /etc/nginx/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"';
    sendfile        on;
    tcp_nopush      on;
    tcp_nodelay     on;
    keepalive_timeout  65;
    gzip  on;
    client_max_body_size 5M;
    include /etc/nginx/conf.d/*.conf;
    #additional config
    include /etc/nginx/extra-conf.d/*;
}
## /etc/nginx/conf.d/realip.conf
upstream realip {
  server server <k8s service LB ip>;
}
server {
  server_name _;
  listen 80;
  location / {
    proxy_set_header           Host $host;
    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_pass                 http://realip;
    proxy_connect_timeout      10;
    proxy_send_timeout         10;
    proxy_read_timeout         10;
    proxy_buffer_size          4k;
    proxy_buffers              4 32k;
    proxy_busy_buffers_size    64k;
    proxy_temp_file_write_size 64k;
    access_log on;
  }
}
package main
import (
    "fmt"
    "net/http"
    "os"
    "github.com/tomasen/realip"
)
func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        host := os.Getenv("HOSTNAME")
        reply := fmt.Sprintf("Hostname:\n%s\n\nUser-Agent:\n%v\n\nHeader:\n%v\n\nIP:\n%v", host, r.UserAgent(), r.Header, realip.RealIP(r))
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(reply))
    })
    http.ListenAndServe(":80", nil)
}
相關文章
- 從 Google Kubernetes Engine 移除節點
- Adopting Container and Kubernetes in Production
- 實作 Google Cloud Pub/Sub Push Subscription
- 實作 Google Cloud Pub/Sub Pull Subscription
- 實作 Google Cloud Pub/Sub Publisher
文章內容的轉載、重製、發佈,請註明出處: https://tachingchen.com/tw/ 
Twitter
Google+
Facebook
Reddit
LinkedIn
StumbleUpon
Pinterest
Email