排查 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