在 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