排查 Amazon EKS 環境中 Redis 連線逾時 (Timeout) 及除錯

排查 Amazon EKS 環境中 Redis 連線逾時 (Timeout) 及除錯

Redis 已成為現代應用程式架構的基石,作為高速緩存、會話管理和 pub/sub 訊息傳輸等多功能記憶體資料儲存系統。然而,當與容器化應用程式一起部署在 Amazon Elastic Kubernetes Service (EKS) 環境中時,團隊經常遇到難以診斷和解決的連線問題。這些問題包括高流量期間的間歇性超時、叢集擴充時的連線問題、跨命名空間的效能不一致,以及突發的連線中斷。

簡介

Redis 連線問題和超時特別令人困擾,因為這些問題多半在高負載時才會間歇性出現,且難以在開發環境中重現。當團隊將系統遷移至 EKS 或擴充 Redis 使用規模時,經常會遇到以下問題:

  • 高流量期間發生間歇性連線逾時
  • 叢集擴充後出現連線問題
  • 跨 Kubernetes 命名空間的效能表現不一致
  • 應用程式 Pod 與 Redis 端點之間發生意外連線中斷

這些問題可能導致使用者體驗下降,嚴重時甚至造成服務完全中斷。因此,確保 Redis 連線的穩定性成為一項關鍵的營運任務。本文將提供完整指南,協助您診斷、排除及解決 EKS 環境中的 Redis 連線問題。

Kubernetes 環境中 Redis 超時的常見原因

在深入探討疑難排解技巧之前,先了解容器化環境中造成 Redis 超時的主要原因至關重要:

Connection Pool 用盡

現代應用程式通常採用連線池來有效管理 Redis 連線。在 Kubernetes 環境中,隨著 Pod 動態擴充,連線池會快速倍增:

  • 每個 Pod 維護獨立的連線池(一般介於 5-50 個連線)
  • Pod 擴充時,總連線數可能超出 Redis 的限制
  • Redis 的預設連線上限(10,000 個連線1)雖然看似充裕,但系統效能通常在達到此限制前就會開始下降

以下是連線池用盡時的應用程式日誌範例:

2023-08-12T14:25:18.456Z ERROR [app-service] - Redis connection error: JedisPoolExhaustedException: Could not get a resource from the pool

網路延遲與不穩定性

Kubernetes 增加了額外的網路層,可能會造成延遲:

  • 容器網路介面 (CNI) 路由開銷
  • 如果 Pod 和 Redis 位於不同的 AWS Availability Zone,則會產生跨可用性區流量
  • DNS 解析出現延遲或解析失敗
  • Pod 被驅逐時引發大量連線重新建立

超時配置不一致

技術堆疊中的各層超時設定若不一致,往往會引發問題:

  • Redis 伺服器本身的超時設定
  • 用戶端程式庫對連線和操作各自的超時設定
  • Kubernetes 的存活性與就緒性探測的超時設定
  • 負載平衡器或代理層的超時設定

資源限制

Redis 或客戶端的資源受限可能導致連線異常:

  • Redis 伺服器記憶體壓力過大,造成資料驅逐或效能降低
  • 應用程式 Pod 的 CPU 限制影響 Redis 回應的處理速度
  • 節點或 Pod 層級的網路流量限制

EKS Pod 與 Redis 之間的網路連線疑難排解

面對連線問題時,系統化的網路故障排除至關重要。從基礎的 TCP/IP 連線測試到應用程式層級的 Redis 指令驗證,每一層都需要仔細檢查。同時監控系統指標和日誌,以找出可能的瓶頸或故障點。透過這種結構化的排除流程,能更有效地找出並解決 EKS 環境中的 Redis 連線問題。

以下列舉幾個最常用來檢查和解決 Redis 連線問題的診斷步驟。從基礎連線測試到進階監控工具的使用,這些步驟將協助您有系統地找出問題所在。

基本連接測試

讓我們從基本的驗證連線開始:

# 在相同命名空間建立除錯用的 Pod(有時候可能需要部署到特定節點)
kubectl run redis-debug --rm -it --image=redis:alpine -- sh

# 測試基本連線能力
nc -zv my-redis-master.xxxx.ng.0001.use1.cache.amazonaws.com 6379

# 用 Redis CLI 做簡單測試
redis-cli -h my-redis-master.xxxx.ng.0001.use1.cache.amazonaws.com ping

# 檢查 DNS 解析是否正常
nslookup my-redis-master.xxxx.ng.0001.use1.cache.amazonaws.com

# 使用 tcpdump 觀察 TCP 連線的細節
tcpdump -i any port 6379 -vv

安全組 (Security Group) 組態

對於 ElastiCache Redis 或 EC2 託管的 Redis,Security Group 通常是造成連線問題的主要原因:

# 找出指派給 EKS 節點的安全群組
NODE_SG=$(aws eks describe-nodegroup --cluster-name my-cluster \\
  --nodegroup-name my-nodegroup --query 'nodegroup.resources.remoteAccessSecurityGroup' \\
  --output text)

# 確保 Redis 的安全群組允許來自節點安全群組的流量
aws ec2 authorize-security-group-ingress \\
  --group-id sg-0123456789abcdef0 \\  # Redis 安全群組
  --source-group $NODE_SG \\
  --protocol tcp \\
  --port 6379

網路政策 (Network Policy) 分析

如果您使用 Kubernetes 網路政策 (Network Policy),驗證它們是否阻擋 Redis 流量:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-redis-egress
  namespace: application
spec:
  podSelector:
    matchLabels:
      app: my-application
  policyTypes:
  - Egress
  egress:
  - to:
    - ipBlock:
        cidr: 10.0.0.0/16  # Subnet containing Redis
    ports:
    - protocol: TCP
      port: 6379

VPC 與子網路路由

對於複雜的 VPC 配置,驗證 EKS 子網路與 Redis 之間的路由和跨可用性區域的連線,確保應用程式可以可靠地訪問 Redis 實例。這需要檢查 VPC 內的路由表配置,以及任何 NAT Gateway 或 Transit Gateway 的設定狀態。此外,還要注意子網路的 CIDR 範圍是否有重疊的問題:

# 檢查 Pod 是否在預期的子網路中
kubectl get pods -o wide

# 檢查這些子網路關聯的路由表設定
aws ec2 describe-route-tables --filters "Name=association.subnet-id,Values=subnet-12345"

# 如果 Redis 在不同的 VPC,檢查 VPC Peering 連線狀態
aws ec2 describe-vpc-peering-connections

容器化環境中 Redis 用戶端的最佳配置實務

正確配置 Redis 用戶端可大幅提升 Kubernetes 環境中的可靠性。以下是針對不同語言和框架的 Redis 用戶端配置最佳實務,這些配置可以協助您建立更穩定和可靠的 Redis 連線。這些設定包括連線池管理、超時處理、錯誤重試策略等關鍵元素。透過遵循這些最佳實務,您可以大幅減少連線問題並提高應用程式的可用性。

Connection Pool

除了使用預設設定,您也可以根據工作負載明確配置 Connection Pool 2

// Java example with Lettuce client
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
    LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
        .poolConfig(poolConfig())
        .commandTimeout(Duration.ofMillis(500))
        .shutdownTimeout(Duration.ZERO)
        .build();

    return new LettuceConnectionFactory(redisStandaloneConfig(), clientConfig);
}

@Bean
public GenericObjectPoolConfig poolConfig() {
    GenericObjectPoolConfig config = new GenericObjectPoolConfig();
    config.setMaxTotal(20);           // Max connections per pod
    config.setMaxIdle(10);            // Connections to maintain when idle
    config.setMinIdle(5);             // Minimum idle connections to maintain
    config.setTestOnBorrow(true);     // Validate connections when borrowed
    config.setTestWhileIdle(true);    // Periodically test idle connections
    config.setMaxWait(Duration.ofMillis(1000)); // Max wait for connection
    return config;
}

適用於 Node.js 應用程式:

// Node.js with ioredis
const Redis = require('ioredis');

const redis = new Redis({
  host: process.env.REDIS_HOST,
  port: 6379,
  password: process.env.REDIS_PASSWORD,
  db: 0,
  maxRetriesPerRequest: 3,
  connectTimeout: 1000,
  commandTimeout: 500,
  retryStrategy(times) {
    const delay = Math.min(times * 50, 2000);
    return delay;
  }
});

逾時 (Timeout) 和重試

根據您的應用程式需求,適當地設定逾時時間:

# Python with redis-py
import redis
from redis.retry import Retry

retry = Retry(ExponentialBackoff(), 3)

redis_client = redis.Redis(
    host=os.environ.get('REDIS_HOST'),
    port=6379,
    socket_timeout=1.0,           # Operation timeout
    socket_connect_timeout=1.0,   # Connection timeout
    retry_on_timeout=True,
    retry=retry,
    health_check_interval=30      # Verify connections every 30 seconds
)

Pod 生命週期中的優雅連線處理

在 Pod 啟動和關閉期間適當管理連線非常重要,但卻經常被忽略:

// Java Spring Boot example
@PreDestroy
public void cleanupRedisConnections() {
    if (redisConnectionFactory instanceof LettuceConnectionFactory) {
        ((LettuceConnectionFactory) redisConnectionFactory).destroy();
        log.info("Cleaned up Redis connections before pod termination");
    }
}

對於 Kubernetes 部署,配置適當的終止寬限期:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-client-app
spec:
  template:
    spec:
      terminationGracePeriodSeconds: 30
      containers:
      - name: app
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 5"]  # Allow time for connection cleanup

影響 Redis 連線的 EKS 網路概念

要解決 Redis 連線問題,我們可以先搞清楚 EKS 網路是怎麼運作的。這其實沒那麼複雜,主要包含幾個部分:AWS VPC CNI 外掛負責處理 Pod 的網路連線、Security Group 幫你管理 Pod 和 Redis 之間的連線權限、服務發現機制讓你可以找到 Redis 在哪裡,還有子網路設定確保你的 Pod 可以連到不同區域的 Redis 叢集。

AWS VPC CNI 概要

Amazon VPC CNI 外掛程式是 EKS 的預設網路解決方案,具有幾個重要的特性:

  • 每個 Pod 都會收到一個真正的 VPC IP 位址
  • Pod IP 位址來自於節點的子網路
  • 根據實體類型,每個節點都有 IP 位址限制
  • 節點的安全群組適用於 Pod 流量

此架構表示您的 Pod 可直接與 Redis 通訊,無需 NAT,簡化了安全設定,但需要適當的子網路和安全群組設定。

Pod 密度與 IP 位址耗盡

EKS 節點可使用的 IP 位址數量有限制3

實體類型 最大 Pod IP
t3.small 11
m5.large 29
c5.xlarge 58

如果 pod 無法取得 IP 位址,就會一直處於 Pending 狀態,影響應用程式的可用性。監控 IP 位址使用情況:

# Check available IP addresses per node
kubectl describe node | grep "Allocated"

服務發現機制

要存取 Redis 服務,以下是常見的方法:

1. 使用外部名稱服務:

apiVersion: v1
kind: Service
metadata:
  name: redis-master
spec:
  type: ExternalName
  externalName: my-redis.xxxx.ng.0001.use1.cache.amazonaws.com

2. 使用環境變數和 ConfigMaps:

apiVersion: v1
kind: ConfigMap
metadata:
  name: redis-config
data:
  redis-host: "my-redis.xxxx.ng.0001.use1.cache.amazonaws.com"
  redis-port: "6379"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  template:
    spec:
      containers:
      - name: app
        envFrom:
        - configMapRef:
            name: redis-config

安全群組考量

EKS pod 流量會繼承節點的安全群組。對於 ElastiCache 連線:

  1. 識別節點安全群組:
aws eks describe-nodegroup --cluster-name your-cluster --nodegroup-name your-nodegroup --query 'nodegroup.remoteAccessSecurityGroup'

  1. 設定 ElastiCache 安全群組:
aws elasticache modify-replication-group --security-group-ids sg-012345abcdef --replication-group-id your-redis-cluster

監控與可觀察性策略

建立完整的監控機制,能讓您在 Redis 連線問題影響應用程式之前及早發現並處理。

監控的關鍵指標

針對 Redis:

  • CurrConnections:目前的用戶端連線 (警示突然的變化)
  • NetworkBytesIn/Out:網路流量模式
  • CPUUtilization:CPU 使用率 (指令處理期間的峰值)
  • SwapUsage:顯示記憶體壓力
  • CommandLatency: 指令延遲:作業回應時間

用於用戶端應用程式:

  • 連線超時與錯誤
  • 連線池使用率
  • 到 Redis 的請求延遲
  • 重試次數和斷路器啟動

用於 Redis 監控的 CloudWatch 面板

為 Redis 監控建立全面的儀表板:

aws cloudwatch put-dashboard --dashboard-name "Redis-Monitoring" --dashboard-body '{
  "widgets": [
    {
      "type": "metric",
      "x": 0,
      "y": 0,
      "width": 12,
      "height": 6,
      "properties": {
        "metrics": [
          [ "AWS/ElastiCache", "CurrConnections", "CacheClusterId", "my-redis" ]
        ],
        "period": 60,
        "stat": "Average",
        "region": "us-east-1",
        "title": "Current Connections"
      }
    },
    {
      "type": "metric",
      "x": 12,
      "y": 0,
      "width": 12,
      "height": 6,
      "properties": {
        "metrics": [
          [ "AWS/ElastiCache", "CPUUtilization", "CacheClusterId", "my-redis" ]
        ],
        "period": 60,
        "stat": "Average",
        "region": "us-east-1",
        "title": "CPU Utilization"
      }
    }
  ]
}'

Prometheus 和 Grafana 整合

如需更詳細的監控,請匯出 Redis 指標至 Prometheus:

apiVersion: v1
kind: ConfigMap
metadata:
  name: redis-exporter-config
data:
  redis-exporter.conf: |
    redis.addr=my-redis:6379
    namespace=redis
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-exporter
spec:
  template:
    spec:
      containers:
      - name: exporter
        image: oliver006/redis_exporter:latest
        ports:
        - containerPort: 9121
        envFrom:
        - configMapRef:
            name: redis-exporter-config

分散式追蹤

實施分散式追蹤以找出 Redis 相關瓶頸:

// Using Spring Cloud Sleuth with Redis
@Bean
public RedisTemplate<String, Object> redisTemplate(
        RedisConnectionFactory redisConnectionFactory,
        SpanCustomizer spanCustomizer) {

    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(
        new TracingRedisConnectionFactory(
            redisConnectionFactory,
            "redis",
            spanCustomizer
        )
    );
    return template;
}

結論與檢查清單

雖然 EKS 環境中的 Redis 連線問題看似複雜,但透過系統化的方法便能有效解決。在容器化環境中維持 Redis 的穩定運作,關鍵在於三個要素:妥善配置用戶端、有效管理容器生命週期,以及規劃合適的基礎架構規模。

要維持一個健康的 Redis 環境,除了剛才提到的這些技術面的設定,你還得常常做一些壓力測試,看看系統到底能撐多少負載。也透過增強監控和自動警報系統,這樣系統有問題時你馬上就知道。別忘了讓團隊的每個人都熟悉一下怎麼解決常見問題,這樣遇到狀況時才不會手忙腳亂。

以下是一份實用的檢查清單,裡面列出了處理 Redis 連線問題時要注意的項目:

Redis 連線疑難排解清單

網路連線

  • 驗證 Redis 端點的 DNS 解析
  • 確認安全群組允許來自 EKS 節點的流量
  • 使用 nc、telnet 和 redis-cli 測試基本連線性
  • 驗證 pod 子網路與 Redis 之間的路由是否正確

用戶端設定

  • 使用明確的連線池設定,而非預設值
  • 為連線和作業設定適當的逾時時間
  • 實施連線健康檢查
  • 在 pod 終止時設定適當的連線處理

Redis 實體大小

  • 確保實體類型可以處理預期的連線數
  • 監控記憶體使用量,並考慮啟用最大記憶體政策
  • 針對讀取繁重的工作負載,使用讀取複本評估讀取/寫入分割
  • 針對大型資料集或高吞吐量,考慮使用 Redis Cluster

擴充行為

  • 在 HPA 配置中實施逐步擴充政策
  • 在擴充事件中監控連線模式
  • 考慮在應用程式層級限制連線
  • 在縮放和縮入事件中測試應用程式行為

監控和警報

  • 為 Redis 指標 (連線、CPU、記憶體) 設定 CloudWatch 警報
  • 實施 Redis 作業的應用程式層級指標
  • 配置分佈式追蹤,以獲得端對端的可視性
  • 建立顯示相關應用程式和 Redis 指標的儀表板

透過留意這些不同面向,你的系統不只能輕鬆應對高峰期的流量,還能保持穩定。不過,在 EKS 環境中設定 Redis 連線只是第一步,更重要的是要打造具有彈性的系統,以優雅地因應容器化環境的動態特性。

參考資料

comments powered by Disqus