

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# ElastiCache 傳輸中加密 (TLS)
<a name="in-transit-encryption"></a>

為了協助保護您的資料安全，Amazon ElastiCache 和 Amazon EC2 提供保護您伺服器上的資料不受未授權存取的機制。ElastiCache 透過提供傳輸中加密功能，在您的資料於不同地點間移動時，讓您有工具可以協助保護資料。

所有 Valkey 或 Redis OSS 無伺服器快取都已啟用傳輸中加密。對於節點型叢集，您可以在建立複寫群組時將 參數設定為 `TransitEncryptionEnabled``true`(CLI：`--transit-encryption-enabled`)，以在複寫群組上啟用傳輸中加密。無論您是使用AWS 管理主控台AWS CLI、 或 ElastiCache API 建立複寫群組，都可以執行此操作。

所有無伺服器快取都會啟用傳輸中加密。對於節點型叢集，您可以在使用 `TransitEncryptionEnabled``true`(CLI：`--transit-encryption-enabled`) 操作建立叢集時，將 參數設定為 `CreateCacheCluster`(CLI：`create-cache-cluster`)，在叢集上啟用傳輸中加密。

**Topics**
+ [傳輸中加密概觀](#in-transit-encryption-overview)
+ [傳輸中加密條件 (Valkey 和 Redis OSS)](#in-transit-encryption-constraints)
+ [傳輸中加密條件 (Memcached)](#in-transit-encryption-constraints)
+ [傳輸中加密最佳實務](#in-transit-encryption-best-practices)
+ [進一步的 Valkey 和 Redis OSS 選項](#in-transit-encryption-see-also)
+ [啟用 Memcached 的傳輸中加密](#in-transit-encryption-enable-existing-mc)
+ [啟用傳輸中加密](in-transit-encryption-enable.md)
+ [使用 valkey-cli 透過傳輸中加密連線至 ElastiCache (Valkey) 或 Amazon ElastiCache for Redis OSS](connect-tls.md)
+ [使用 Python 在以節點為基礎的 Redis OSS 叢集上啟用傳輸中加密](in-transit-encryption-enable-python.md)
+ [啟用傳輸中加密時的最佳實務](enable-python-best-practices.md)
+ [使用 Openssl (Memcached) 連線至已啟用傳輸中加密的節點](#in-transit-encryption-connect-mc)
+ [使用 Java 建立 TLS Memcached 用戶端](#in-transit-encryption-connect-java)
+ [使用 PHP 建立 TLS Memcached 用戶端](#in-transit-encryption-connect-php-mc)

## 傳輸中加密概觀
<a name="in-transit-encryption-overview"></a>

Amazon ElastiCache 傳輸中加密功能可讓您在資料於兩地之間傳輸時，在資料最易遭受攻擊的點提高資料的安全性。因為要加密和解密端點的資料需要一些處理，啟用傳輸中加密可能會有一些效能影響。您應該對您具有和不具有傳輸中加密的資料進行基準分析，以判斷對您的使用案例的影響。

ElastiCache 傳輸中加密會實作下列功能：
+ **加密的用戶端連線**：用戶端與快取節點的連線會經過 TLS 加密。
+ **加密的伺服器連線**：在叢集中的節點之間移動的資料會經過加密。
+ **伺服器身分驗證** - 用戶端可以驗證是否已連線至正確的伺服器。
+ **用戶端身分驗證** - 使用 Valkey 和 Redis OSS AUTH 功能，伺服器可以驗證用戶端。

## 傳輸中加密條件 (Valkey 和 Redis OSS)
<a name="in-transit-encryption-constraints"></a>

當您規劃節點型叢集實作時，應謹記 Amazon ElastiCache 傳輸中加密的下列限制：
+ 執行 Valkey 7.2 和更新版本以及 Redis OSS 3.2.6、4.0.10 和更新版本的複寫群組支援傳輸中加密。
+ 執行 Valkey 7.2 和更新版本以及 Redis OSS 第 7 版和更新版本的複寫群組支援修改現有叢集的傳輸中加密設定。
+ 只有在 Amazon VPC 中執行的複寫群組支援傳輸中加密。
+ 執行下列節點類型的複寫群組不支援傳輸中加密：M1, M2。

  如需詳細資訊，請參閱[支援的節點類型](CacheNodes.SupportedTypes.md)。
+ 透過將參數 `TransitEncryptionEnabled` 明確設定為 `true`，可啟用傳輸中加密。
+ 請確定您的快取用戶端支援 TLS 連線，而且您已在用戶端組態中啟用它。
+ 自 2026 年 1 月 26 日起，AWS將在 ElastiCache for Valkey 7.2 版及更新版本，以及 ElastiCache for Redis OSS 第 6 版及更新版本上，將支援的 TLS 最低版本更新為 1.2。客戶必須在該日期之前更新其用戶端軟體。此更新可協助您滿足安全、合規和法規需求。

## 傳輸中加密條件 (Memcached)
<a name="in-transit-encryption-constraints"></a>

當您規劃節點型叢集實作時，應謹記 Amazon ElastiCache 傳輸中加密的下列限制：
+ 執行 Memcached 1.6.12 和更新版本的叢集上支援傳輸中加密。
+ 傳輸中加密支援 Transport Layer Security (TLS) 1.2 和 1.3 版。
+ 只有在 Amazon VPC 中執行的叢集支援傳輸中加密。
+ 執行下列節點類型的複寫群組不支援傳輸中加密：M1, M2, M3, R3, T2。

  如需詳細資訊，請參閱[支援的節點類型](CacheNodes.SupportedTypes.md)。
+ 透過將參數 `TransitEncryptionEnabled` 明確設定為 `true`，可啟用傳輸中加密。
+ 您只能在建立叢集時，在叢集上啟用傳輸中加密。您無法透過修改叢集來開啟和關閉傳輸中加密。
+ 請確定您的快取用戶端支援 TLS 連線，而且您已在用戶端組態中啟用它。

## 傳輸中加密最佳實務
<a name="in-transit-encryption-best-practices"></a>
+ 因為要加密和解密端點的資料需要一些處理，實作傳輸中加密可能會降低效能。對您自己的資料進行傳輸中加密與無加密基準分析的比較，來判斷對您實作的影響。
+ 因為建立新連線可能所費不貲，您可以透過持續保存您的 TLS 連線來減少傳輸中加密的效能影響。

## 進一步的 Valkey 和 Redis OSS 選項
<a name="in-transit-encryption-see-also"></a>

如需 Valkey 和 Redis OSS 可用選項的詳細資訊，請參閱下列連結。
+ [ElastiCache 中的靜態加密](at-rest-encryption.md)
+ [使用 Valkey 和 Redis OSS AUTH 命令進行驗證](auth.md)
+ [角色型存取控制 (RBAC) 規則數量](Clusters.RBAC.md)
+ [Amazon VPC 和 ElastiCache 安全性](VPCs.md)
+ [Amazon ElastiCache 中的 Identity and Access Management](IAM.md)

## 啟用 Memcached 的傳輸中加密
<a name="in-transit-encryption-enable-existing-mc"></a>

若要在使用AWS管理主控台建立 Memcached 叢集時，啟用傳輸中加密，請選取以下項目：
+ 選擇 Memcached 當作您的引擎。
+ 選擇引擎 1.6.12 或更新版本。
+ 在 **Encryption in transit** (傳輸中加密) 中，選擇 **Enable** (啟用)。

 如需step-by-step程序，請參閱 [為 Valkey 或 Redis OSS 建立叢集](Clusters.Create.md)。

# 啟用傳輸中加密
<a name="in-transit-encryption-enable"></a>

所有無伺服器快取都會啟用傳輸中加密。在節點型叢集上，您可以使用AWS 管理主控台、 CLI 或 ElastiCache API AWS啟用傳輸中加密。

## 使用 啟用傳輸中加密AWS 管理主控台
<a name="in-transit-encryption-enable-console"></a>

### 使用 為新的節點型叢集啟用傳輸中加密AWS 管理主控台
<a name="in-transit-encryption-enable-con"></a>

自行設計叢集時，採用「輕鬆建立」方法的「開發/測試」和「生產」組態會使用傳輸中加密。當您自行選擇組態時，請進行下列選擇：
+ 選擇引擎版本 3.2.6、4.0.10 或更新版本。
+ 按一下**傳輸中加密**選項的**啟用**旁的核取方塊。

如需逐步操作程序，請參閱下列：
+ [建立 Valkey （停用叢集模式） 叢集 （主控台）](SubnetGroups.designing-cluster-pre.valkey.md#Clusters.Create.CON.valkey-gs)
+ [建立 Valkey 或 Redis OSS （啟用叢集模式） 叢集 （主控台）](Clusters.Create.md#Clusters.Create.CON.RedisCluster)

### 使用 啟用現有節點型叢集的傳輸中加密AWS 管理主控台
<a name="in-transit-encryption-enable-existing"></a>

啟用傳輸中加密有兩個步驟，您必須先將傳輸加密模式設為 `preferred`。此模式可讓您的 Valkey 或 Redis OSS 用戶端使用加密和未加密的連線進行連線。遷移所有 Valkey 或 Redis OSS 用戶端以使用加密連線後，您可以修改叢集組態，將傳輸加密模式設定為 `required`。將傳輸加密模式設為 `required` 會捨棄所有未加密連線，且僅允許加密連線。

**將您的**傳輸加密模式**設定為**偏好****

1. 登入AWS 管理主控台，並在 [https://console.aws.amazon.com/elasticache/](https://console.aws.amazon.com/elasticache/)：// 開啟 Amazon ElastiCache 主控台。

1. 從左側導覽窗格中列出的 ElastiCache **資源**中選擇 **Valkey 快取**或 **Redis OSS 快取**。

1. 選擇您要更新的快取。

1. 選擇 **Actions** (動作) 下拉式清單，然後選擇 **Modify** (修改)。

1. 在 **Security** (安全性) 區段的 **Encryption in transit** (傳輸中加密) 下選擇 **Enable** (啟用)。

1. 選擇 **Preferred** (偏好) 做為 **Transit encryption mode** (傳輸加密模式)。

1. 選擇 **Preview changes** (預覽變更)，然後儲存變更。

在您遷移所有 Valkey 或 Redis OSS 用戶端以使用加密連線之後：

**將您的**傳輸加密模式**設定為**必要****

1. 登入AWS 管理主控台，並在 [https://console.aws.amazon.com/elasticache/](https://console.aws.amazon.com/elasticache/)：// 開啟 Amazon ElastiCache 主控台。

1. 從左側導覽窗格中列出的 ElastiCache **資源**中選擇 **Valkey 快取**或 **Redis OSS 快取**。

1. 選擇您要更新的快取。

1. 選擇 **Actions** (動作) 下拉式清單，然後選擇 **Modify** (修改)。

1. 在 **Security** (安全性) 區段中，選擇 **Required** (必要) 做為 **Transit encryption mode** (傳輸加密模式)。

1. 選擇 **Preview changes** (預覽變更)，然後儲存變更。

## 使用 啟用傳輸中加密AWS CLI
<a name="in-transit-encryption-enable-cli"></a>

若要在使用 建立 Valkey 或 Redis OSS 複寫群組時啟用傳輸中加密AWS CLI，請使用 參數 `transit-encryption-enabled`。

### 針對 Valkey 或 Redis OSS （停用叢集模式），在新的節點型叢集上啟用傳輸中加密 (CLI)
<a name="in-transit-encryption-enable-cli-redis-classic-rg"></a>

使用AWS CLI操作`create-replication-group`和下列參數來建立具有已啟用傳輸中加密複本的 Valkey 或 Redis OSS 複寫群組：

**重要參數：**
+ **--engine**- 必須為 `valkey`或 `redis`。
+ **--engine-version**- 如果引擎是 Redis OSS，這必須是 3.2.6、4.0.10 或更新版本。
+ **--transit-encryption-enabled** - 必要項目。如果您啟用傳輸中加密，您也必須為 `--cache-subnet-group` 參數提供一個值。
+ **--num-cache-clusters** - 必須至少為 1。此參數的最大值為 6。

如需詳細資訊，請參閱下列內容：
+ [從頭開始建立 Valkey 或 Redis OSS （停用叢集模式） 複寫群組 (AWS CLI)](Replication.CreatingReplGroup.NoExistingCluster.Classic.md#Replication.CreatingReplGroup.NoExistingCluster.Classic.CLI)
+ [create-replication-group](https://docs.aws.amazon.com/cli/latest/reference/elasticache/create-replication-group.html)

### 針對 Valkey 或 Redis OSS （啟用叢集模式），在新的節點型叢集上啟用傳輸中加密 (CLI)
<a name="in-transit-encryption-enable-cli-redis-cluster"></a>

使用AWS CLI操作`create-replication-group`和下列參數來建立啟用傳輸中加密的 Valkey 或 Redis OSS （啟用叢集模式） 複寫群組：

**重要參數：**
+ **--engine**- 必須為 `valkey`或 `redis`。
+ **--engine-version**- 如果引擎是 Redis OSS，這必須是 3.2.6、4.0.10 或更新版本。
+ **--transit-encryption-enabled** - 必要項目。如果您啟用傳輸中加密，您也必須為 `--cache-subnet-group` 參數提供一個值。
+ 使用下列其中一個參數集來指定複寫群組之節點群組的組態：
  + **--num-node-groups** - 指定此複寫群組中碎片 (節點群組) 的數量。此參數的最大值為 500。

    **--replicas-per-node-group** - 指定每個節點群組中複本節點的數量。此處指定的值會套用到此複寫群組中的所有碎片。此參數的最大值為 5。
  + **--node-group-configuration** - 獨立指定每個碎片的組態。

如需詳細資訊，請參閱下列內容：
+ [從頭開始建立 Valkey 或 Redis OSS （啟用叢集模式） 複寫群組 (AWS CLI)](Replication.CreatingReplGroup.NoExistingCluster.Cluster.md#Replication.CreatingReplGroup.NoExistingCluster.Cluster.CLI)
+ [create-replication-group](https://docs.aws.amazon.com/cli/latest/reference/elasticache/create-replication-group.html)

### 在使用AWS CLI的現有叢集上啟用傳輸中加密
<a name="in-transit-encryption-enable-cli-redis-cluster-existing-cli"></a>

啟用傳輸中加密有兩個步驟，您必須先將傳輸加密模式設為 `preferred`。此模式可讓您的 Valkey 或 Redis OSS 用戶端使用加密和未加密的連線進行連線。遷移所有 Valkey 或 Redis OSS 用戶端以使用加密連線後，您可以修改叢集組態，將傳輸加密模式設定為 `required`。將傳輸加密模式設為 `required` 會捨棄所有未加密連線，且僅允許加密連線。

使用AWS CLI操作`modify-replication-group`和下列參數來更新停用傳輸中加密的 Valkey 或 Redis OSS （啟用叢集模式） 複寫群組。

**啟用傳輸中加密**

1. 使用下列參數，將 transit-encryption-mode 設為 `preferred`
   + **--transit-encryption-enabled** - 必要項目。
   + **--transit-encryption-mode**—必須設定為 `preferred`。

1. 使用下列參數，將 transit-encryption-mode 設為 `required`：
   + **--transit-encryption-enabled** - 必要項目。
   + **--transit-encryption-mode**—必須設定為 `required`。

# 使用 valkey-cli 透過傳輸中加密連線至 ElastiCache (Valkey) 或 Amazon ElastiCache for Redis OSS
<a name="connect-tls"></a>

若要從啟用傳輸中加密的 ElastiCache for Redis OSS 快取存取資料，您可以使用使用 Secure Socket Layer (SSL) 的用戶端。您也可以在 Amazon Linux 和 Amazon Linux 2 上使用 valkey-cli 搭配 TLS/SSL。如果您的用戶端不支援 TLS，您可以在用戶端主機上使用 `stunnel`命令來建立 Redis OSS 節點的 SSL 通道。

## 與 Linux 的加密連線
<a name="connect-tls.linux"></a>

若要使用 valkey-cli 連線到在 Amazon Linux 2 或 Amazon Linux 上啟用傳輸中加密的 Valkey 或 Redis OSS 叢集，請遵循下列步驟。

1. 下載並編譯 valkey-cli 公用程式。此公用程式包含在 Valkey 軟體分發中。

1. 在 EC2 執行個體的命令提示字元中，針對您正在使用的 Linux 版本輸入適當的命令。

   **Amazon Linux 2**

   如果使用 Amazon Linux 2，請輸入以下內容：

   ```
   sudo yum -y install openssl-devel gcc
   wget https://github.com/valkey-io/valkey/archive/refs/tags/7.2.6.tar.gz
   tar xvzf valkey-7.2.6.tar.gz
   cd valkey-7.2.6
   make distclean
   make valkey-cli BUILD_TLS=yes
   sudo install -m 755 src/valkey-cli /usr/local/bin/
   ```

   **Amazon Linux**

   如果使用 Amazon Linux，請輸入以下內容：

   ```
   sudo yum install gcc jemalloc-devel openssl-devel tcl tcl-devel clang wget
   wget https://github.com/valkey-io/valkey/archive/refs/tags/8.0.0.tar.gz
   tar xvzf valkey-8.0.0.tar.gz
   cd valkey-8.0.0
   make valkey-cli CC=clang BUILD_TLS=yes
   sudo install -m 755 src/valkey-cli /usr/local/bin/
   ```

   在 Amazon Linux 上，您可能還需要執行下列額外步驟：

   ```
   sudo yum install clang
   CC=clang make
   sudo make install
   ```

1. 在您下載並安裝 valkey-cli 公用程式之後，建議您執行選用的 `make-test` 命令。

1. 若要連線至已啟用加密和身分驗證的叢集，請輸入此命令：

   ```
   valkey-cli -h Primary or Configuration Endpoint --tls -a 'your-password' -p 6379
   ```
**注意**  
如果您在 Amazon Linux 2023 上安裝 redis6，您現在可以使用 命令`redis6-cli`，而不是 `valkey-cli`：  

   ```
   redis6-cli -h Primary or Configuration Endpoint --tls -p 6379
   ```

## 使用 stunnel 的加密連線
<a name="connect-tls.stunnel"></a>

若要使用 valkey-cli 連線至使用 stunnel 啟用傳輸中加密的 Redis OSS 叢集，請遵循下列步驟。

1. 使用 SSH 連線到用戶端並安裝 `stunnel`。

   ```
   sudo yum install stunnel
   ```

1. 執行下列命令，使用下方提供的輸出做為範本，`'/etc/stunnel/valkey-cli.conf'`同時建立和編輯檔案，將 ElastiCache for Redis OSS 叢集端點新增至一或多個連線參數。

   ```
   vi /etc/stunnel/valkey-cli.conf
   
   				
   fips = no
   setuid = root
   setgid = root
   pid = /var/run/stunnel.pid
   debug = 7 
   delay = yes
   options = NO_SSLv2
   options = NO_SSLv3
   [valkey-cli]
      client = yes
      accept = 127.0.0.1:6379
      connect = primary.ssltest.wif01h.use1.cache.amazonaws.com:6379
   [valkey-cli-replica]
      client = yes
      accept = 127.0.0.1:6380
      connect = ssltest-02.ssltest.wif01h.use1.cache.amazonaws.com:6379
   ```

   在下一個範例中，組態檔案有兩個連線：`valkey-cli` 和 `valkey-cli-replica`。參數的設定方式如下：
   + **client** 設為 yes，以指定此 stunnel 執行個體為用戶端。
   + **accept** 設為用戶端 IP。在此範例中，主要 設定為連接埠 6379 上的 Redis OSS 預設 127.0.0.1。複本主機必須呼叫不同的連接埠，並設為 6380。您可以使用的暫時性連接埠為 1024–65535。如需詳細資訊，請參閱 *Amazon VPC 使用者指南*中的[暫時性連接埠](https://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_ACLs.html#VPC_ACLs_Ephemeral_Ports)。
   + **connect** 設定為 Redis OSS 伺服器端點。如需詳細資訊，請參閱[在 ElastiCache 中尋找連線端點](Endpoints.md)。

1. 啟動 `stunnel`。

   ```
   sudo stunnel /etc/stunnel/valkey-cli.conf
   ```

   使用 `netstat` 命令來確認通道已啟動。

   ```
   sudo netstat -tulnp | grep -i stunnel
   				
   tcp        0      0 127.0.0.1:6379              0.0.0.0:*                   LISTEN      3189/stunnel        
   tcp        0      0 127.0.0.1:6380              0.0.0.0:*                   LISTEN      3189/stunnel
   ```

1. 使用通道的本機端點連線至加密的 Redis OSS 節點。
   + 如果在建立 ElastiCache for Redis OSS 叢集期間未使用 AUTH 密碼，則此範例會使用 valkey-cli，在 Amazon Linux 上使用 valkey-cli 的完整路徑連線至 ElastiCache for Redis OSS 伺服器：

     ```
     /home/ec2-user/redis-7.2.5/src/valkey-cli -h localhost -p 6379
     ```

     如果在建立 Redis OSS 叢集期間使用 AUTH 密碼，此範例會使用 valkey-cli，在 Amazon Linux 上使用 valkey-cli 的完整路徑連線至 Redis OSS 伺服器：

     ```
      /home/ec2-user/redis-7.2.5/src/valkey-cli -h localhost -p 6379 -a my-secret-password
     ```

   或
   + 將目錄變更為 redis-7.2.5 並執行下列動作：

     如果在建立 ElastiCache for Redis OSS 叢集期間未使用 AUTH 密碼，則此範例會使用 valkey-cli，在 Amazon Linux 上使用 valkey-cli 的完整路徑連線至 ElastiCache for Redis OSS 伺服器：

     ```
     src/valkey-cli -h localhost -p 6379
     ```

     如果在 Redis OSS 叢集建立期間使用 AUTH 密碼，則此範例會使用 valkey-cli，在 Amazon Linux 上使用 valkey-cli 的完整路徑連線至 Valkey 或 Redis OSS 伺服器：

     ```
     src/valkey-cli -h localhost -p 6379 -a my-secret-password	
     ```

   此範例使用 Telnet 連線至 Valkey Redis OSS 伺服器。

   ```
   telnet localhost 6379
   			
   Trying 127.0.0.1...
   Connected to localhost.
   Escape character is '^]'.
   auth MySecretPassword
   +OK
   get foo
   $3
   bar
   ```

1. 若要停止和關閉 SSL 通道，請執行 `pkill` stunnel 程序。

   ```
   sudo pkill stunnel
   ```

# 使用 Python 在以節點為基礎的 Redis OSS 叢集上啟用傳輸中加密
<a name="in-transit-encryption-enable-python"></a>

下列指南將示範如何在最初在停用傳輸中加密的情況下建立的 Redis OSS 7.0 叢集上啟用傳輸中加密。在此程序期間，TCP 和 TLS 用戶端將繼續與叢集通訊，而不會停機。

Boto3 將從環境變量中取得所需的憑證 (`aws_access_key_id`、`aws_secret_access_key` 和 `aws_session_token`)。這些憑證將事先貼到同一個 bash 終端中，我們將在其中執行 `python3` 以處理本指南中顯示的 Python 程式碼。以下範例中的程式碼是從在相同 VPC 中啟動的 EC2 執行個體處理，該 VPC 將用於在其中建立 ElastiCache Redis OSS 叢集。

**注意**  
以下範例使用 boto3 SDK 進行 ElastiCache 管理作業 (叢集或使用者建立)，並使用 redis-py/redis-py-cluster 來處理資料。
您必須至少使用 boto3 版本 (=\$1) 1.26.39，才能搭配叢集修改 API 使用線上 TLS 遷移。
ElastiCache 僅支援具有 Valkey 7.2 版和更新版本或 Redis OSS 7.0 版或更新版本的叢集進行線上 TLS 遷移。因此，如果您的叢集執行的 Redis OSS 版本早於 7.0，則需要升級叢集的 Redis OSS 版本。如需這些版本差異的詳細資訊，請參閱 [與 Redis OSS 的主要引擎版本行為和相容性差異](VersionManagementConsiderations.md)。

**Topics**
+ [定義將啟動 ElastiCache Valkey 或 Redis OSS 叢集的字串常數](#enable-python-define-constants)
+ [定義叢集組態類別](#enable-python-define-classes)
+ [定義一個將代表叢集本身的類別](#enable-python-define-classes-cluster)
+ [（選用） 建立包裝函式類別，以示範用戶端與 Valkey 或 Redis OSS 叢集的連線](#enable-python-create-wrapper)
+ [建立主函數，以示範變更傳輸中加密組態的程序](#enable-python-main-function)

## 定義將啟動 ElastiCache Valkey 或 Redis OSS 叢集的字串常數
<a name="enable-python-define-constants"></a>

首先，讓我們定義一些簡單的 Python 字串常數，其中包含建立 ElastiCache 叢集所需的AWS實體名稱`Cache Subnet group`，例如 `security-group`、 和 `default parameter group`。所有這些AWS實體都必須在您願意使用的區域中AWS的帳戶中事先建立。

```
#Constants definitions 
SECURITY_GROUP = "sg-0492aa0a29c558427"
CLUSTER_DESCRIPTION = "This cluster has been launched as part of the online TLS migration user guide"
EC_SUBNET_GROUP = "client-testing"
DEFAULT_PARAMETER_GROUP_REDIS_7_CLUSTER_MODE_ENABLED = "default.redis7.cluster.on"
```

## 定義叢集組態類別
<a name="enable-python-define-classes"></a>

現在，讓我們定義一些簡單的 Python 類別，其將代表叢集的組態，該類別將保留有關叢集的中繼資料，例如 Valkey 或 Redis OSS 版本、執行個體類型，以及是否啟用或停用傳輸中加密 (TLS)。

```
#Class definitions

class Config:
    def __init__(
        self,
        instance_type: str = "cache.t4g.small",
        version: str = "7.0",
        multi_az: bool = True,
        TLS: bool = True,
        name: str = None,
    ):
        self.instance_type = instance_type
        self.version = version
        self.multi_az = multi_az
        self.TLS = TLS
        self.name = name or f"tls-test"

    def create_base_launch_request(self):
        return {
            "ReplicationGroupId": self.name,
            "TransitEncryptionEnabled": self.TLS,
            "MultiAZEnabled": self.multi_az,
            "CacheNodeType": self.instance_type,
            "Engine": "redis",
            "EngineVersion": self.version,
            "CacheSubnetGroupName": EC_SUBNET_GROUP ,
            "CacheParameterGroupName": DEFAULT_PARAMETER_GROUP_REDIS_7_CLUSTER_MODE_ENABLED ,
            "ReplicationGroupDescription": CLUSTER_DESCRIPTION,
            "SecurityGroupIds": [SECURITY_GROUP],
        }
        
class ConfigCME(Config):
    def __init__(
        self,
        instance_type: str = "cache.t4g.small",
        version: str = "7.0",
        multi_az: bool = True,
        TLS: bool = True,
        name: str = None,
        num_shards: int = 2,
        num_replicas_per_shard: int = 1,
    ):
        super().__init__(instance_type, version, multi_az, TLS, name)
        self.num_shards = num_shards
        self.num_replicas_per_shard = num_replicas_per_shard

    def create_launch_request(self) -> dict:
        launch_request = self.create_base_launch_request()
        launch_request["NumNodeGroups"] = self.num_shards
        launch_request["ReplicasPerNodeGroup"] = self.num_replicas_per_shard
        return launch_request
```

## 定義一個將代表叢集本身的類別
<a name="enable-python-define-classes-cluster"></a>

現在，讓我們定義一些簡單的 Python 類別，這些類別將代表 ElastiCache Valkey 或 Redis OSS 叢集本身。這個類別將有一個用戶端欄位，以保留一個 boto3 用戶端的 ElastiCache 管理操作，如建立叢集和查詢 ElastiCache API。

```
import botocore.config
import boto3

# Create boto3 client
def init_client(region: str = "us-east-1"):
    config = botocore.config.Config(retries={"max_attempts": 10, "mode": "standard"})
    init_request = dict()
    init_request["config"] = config
    init_request["service_name"] = "elasticache"
    init_request["region_name"] = region
    return boto3.client(**init_request) 
 
 
class ElastiCacheClusterBase:
    def __init__(self, name: str):
        self.name = name
        self.elasticache_client = init_client()

    def get_first_replication_group(self):
        return self.elasticache_client.describe_replication_groups(
        ReplicationGroupId=self.name
        )["ReplicationGroups"][0]
 
    def get_status(self) -> str:
        return self.get_first_replication_group()["Status"]
 
    def get_transit_encryption_enabled(self) -> bool:
        return self.get_first_replication_group()["TransitEncryptionEnabled"]
 
    def is_available(self) -> bool:
        return self.get_status() == "available"
        
    def is_modifying(self) -> bool:
        return self.get_status() == "modifying"
        
    def wait_for_available(self):
        while True:
            if self.is_available():
                break
            else:
                time.sleep(5)

    def wait_for_modifying(self):
        while True:
            if self.is_modifying():
                break
            else:
                time.sleep(5)
                
    def delete_cluster(self) -> bool:
        self.elasticache_client.delete_replication_group(
            ReplicationGroupId=self.name, RetainPrimaryCluster=False
        )
        
    def modify_transit_encryption_mode(self, new_transit_encryption_mode: str):
        # generate api call to migrate the cluster to TLS preffered or to TLS required
            self.elasticache_client.modify_replication_group(
                ReplicationGroupId=self.name,
                TransitEncryptionMode=new_transit_encryption_mode,
                TransitEncryptionEnabled=True,
                ApplyImmediately=True,
            )  
        self.wait_for_modifying()
              
 class ElastiCacheClusterCME(ElastiCacheClusterBase):
    def __init__(self, name: str):
        super().__init__(name)

    @classmethod
    def launch(cls, config: ConfigCME = None) -> ElastiCacheClusterCME:
        config = config or ConfigCME()
        print(config)
        new_cluster = ElastiCacheClusterCME(config.name)
        launch_request = config.create_launch_request()
        new_cluster.elasticache_client.create_replication_group(**launch_request)
        new_cluster.wait_for_available()
        return new_cluster

    def get_configuration_endpoint(self) -> str:
        return self.get_first_replication_group()["ConfigurationEndpoint"]["Address"]
     
#Since the code can throw exceptions, we define this class to make the code more readable and 
#so we won't forget to delete the cluster    
class ElastiCacheCMEManager:
    def __init__(self, config: ConfigCME = None):
        self.config = config or ConfigCME()

    def __enter__(self) -> ElastiCacheClusterCME:
        self.cluster = ElastiCacheClusterCME.launch(self.config)
        return self.cluster 
          
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.cluster.delete_cluster()
```

## （選用） 建立包裝函式類別，以示範用戶端與 Valkey 或 Redis OSS 叢集的連線
<a name="enable-python-create-wrapper"></a>

現在，讓我們為 `redis-py-cluster` 用戶端建立一個包裝類別。這個包裝類別將支援使用一些鍵預先填充叢集，然後執行隨機重複的 `get` 命令。

**注意**  
這是一個選擇性步驟，但它簡化了在後面步驟中出現的主函數。

```
import redis
improt random
from time import perf_counter_ns, time


class DowntimeTestClient:
    def __init__(self, client):
        self.client = client

        # num of keys prefilled
        self.prefilled = 0
        # percent of get above prefilled
        self.percent_get_above_prefilled = 10 # nil result expected when get hit above prefilled
        # total downtime in nano seconds 
        self.downtime_ns = 0
        # num of success and fail operations
        self.success_ops = 0
        self.fail_ops = 0
        self.connection_errors = 0
        self.timeout_errors = 0
        

    def replace_client(self, client):
        self.client = client

    def prefill_data(self, timelimit_sec=60):
        end_time = time() + timelimit_sec
        while time() < end_time:
            self.client.set(self.prefilled, self.prefilled)
            self.prefilled += 1

    # unsuccesful operations throw exceptions
    def _exec(self, func):
        try:
            start_ns = perf_counter_ns()
            func()
            self.success_ops += 1
            elapsed_ms = (perf_counter_ns() - start_ns) // 10 ** 6
            # upon succesful execution of func
            # reset random_key to None so that the next command
            # will use a new random key
            self.random_key = None

        except Exception as e:
            elapsed_ns = perf_counter_ns() - start_ns
            self.downtime_ns += elapsed_ns
            # in case of failure- increment the relevant counters so that we will keep track 
            # of how many connection issues we had while trying to communicate with
            # the cluster.
            self.fail_ops += 1
            if e.__class__ is redis.exceptions.ConnectionError:
                self.connection_errors += 1
            if e.__class__ is redis.exceptions.TimeoutError:
                self.timeout_errors += 1

    def _repeat_exec(self, func, seconds):
        end_time = time() + seconds
        while time() < end_time:
            self._exec(func)

    def _new_random_key_if_needed(self, percent_above_prefilled):
        if self.random_key is None:
            max = int((self.prefilled * (100 + percent_above_prefilled)) / 100)
            return random.randint(0, max)
        return self.random_key

    def _random_get(self):
        key = self._new_random_key_if_needed(self.percent_get_above_prefilled)
        result = self.client.get(key)
        # we know the key was set for sure only in the case key < self.prefilled
        if key < self.prefilled:
            assert result.decode("UTF-8") == str(key)


    def repeat_get(self, seconds=60):
        self._repeat_exec(self._random_get, seconds)

    def get_downtime_ms(self) -> int:
        return self.downtime_ns // 10 ** 6


    def do_get_until(self, cond_check):
        while not cond_check():
            self.repeat_get()
        # do one more get cycle once condition is met
        self.repeat_get()
```

## 建立主函數，以示範變更傳輸中加密組態的程序
<a name="enable-python-main-function"></a>

現在，讓我們定義主函數，它將執行以下操作：

1. 使用 boto3 ElastiCache 用戶端建立叢集。

1. 初始化將使用不具備 TLS 的清楚 TCP 連線連接到叢集的 `redis-py-cluster` 用戶端。

1. `redis-py-cluster` 用戶端會用一些資料預先填充叢集。

1. boto3 用戶端將會觸發 TLS 從無 TLS 遷移至 TLS 偏好。

1. 當叢集正在遷移到 TLS `Preferred` 時，`redis-py-cluster` TCP 用戶端將向叢集發送重複的 `get` 操作，直到遷移完成。

1. 完成遷移至 TLS `Preferred` 之後，我們會宣告該叢集支援傳輸中加密。之後，我們將建立一個將使用 TLS 連接到叢集的 `redis-py-cluster` 用戶端。

1. 我們將使用新的 TLS 用戶端和舊的 TCP 用戶端發送一些 `get` 命令。

1. boto3 用戶端將觸發從 TLS `Preferred` 到 TLS 所需的 TLS 遷移。

1. 當叢集正在遷移到所需的 TLS 時，redis-py-cluster TLS 用戶端將向叢集發送重複的 `get` 操作，直到遷移完成。

```
import redis

def init_cluster_client(
    cluster: ElastiCacheClusterCME, prefill_data: bool, TLS: bool = True) -> DowntimeTestClient:
    # we must use for the host name the cluster configuration endpoint. 
    redis_client = redis.RedisCluster(
        host=cluster.get_configuration_endpoint(), ssl=TLS, socket_timeout=0.25, socket_connect_timeout=0.1
    )
    test_client = DowntimeTestClient(redis_client)
    if prefill_data:
        test_client.prefill_data()
    return test_client

if __name__ == '__main__':
    config = ConfigCME(TLS=False, instance_type="cache.m5.large")

    with ElastiCacheCMEManager(config) as cluster:
        # create a client that will connect to the cluster with clear tcp connection
        test_client_tcp = init_cluster_client(cluster, prefill_data=True, TLS=False)
        
       # migrate the cluster to TLS Preferred
        cluster.modify_transit_encryption_mode(new_transit_encryption_mode="preferred")
        
        # do repeated get commands until the cluster finishes the migration to TLS Preferred
        test_client_tcp.do_get_until(cluster.is_available)
        
       # verify that in transit encryption is enabled so that clients will be able to connect to the cluster with TLS
        assert cluster.get_transit_encryption_enabled() == True
        
       # create a client that will connect to the cluster with TLS connection. 
        # we must first make sure that the cluster indeed supports TLS
        test_client_tls = init_cluster_client(cluster, prefill_data=True, TLS=True)
        
        # by doing get commands with the tcp client for 60 more seconds
       # we can verify that the existing tcp connection to the cluster still works 
        test_client_tcp.repeat_get(seconds=60)
        
        # do get commands with the new TLS client for 60 more seconds
        test_client_tcp.repeat_get(seconds=60)
        
       # migrate the cluster to TLS required
        cluster.modify_transit_encryption_mode(new_transit_encryption_mode="required")
        
       # from this point the tcp clients will be disconnected and we must not use them anymore.
       # do get commands with the TLS client until the cluster finishes migartion to TLS required mode.
        test_client_tls.do_get_until(cluster.is_available)
```

# 啟用傳輸中加密時的最佳實務
<a name="enable-python-best-practices"></a>

## 啟用傳輸中加密之前：請確定您有適當的 DNS 記錄處理
<a name="enable-python-best-practices-before"></a>

**注意**  
在此過程中，我們正在變更和刪除舊端點。不正確使用端點可能會導致 Valkey 或 Redis OSS 用戶端使用舊的和已刪除的端點，以防止其連線至叢集。

當叢集從非 TLS 遷移至 TLS 偏好時，會保留舊的叢集組態端點 DNS 記錄，並以不同的格式產生新的叢集組態端點 DNS 記錄。啟用 TLS 的叢集使用與停用 TLS 的叢集不同的 DNS 記錄格式。ElastiCache 會保留兩個 DNS 記錄，`encryption mode: Preferred`以便應用程式和其他 Valkey 或 Redis OSS 用戶端可以在它們之間切換。DNS 記錄中的下列變更會在 TLS 遷移程序期間進行：

### 啟用傳輸中加密時所發生的 DNS 記錄變更說明
<a name="enable-python-best-practices-before-desc"></a>

**對於 CME 叢集**

當叢集設定為「傳輸加密模式：偏好」時：
+ 非 TLS 叢集的原始叢集組態端點將保持作用中狀態。將叢集從 TLS 加密模式「無」重新設定為「偏好」時，不會有停機時間。
+ 當叢集設定為 TLS 偏好模式時，將產生新的 TLS Valkey 或 Redis OSS 端點。這些新端點將解析為與舊端點 (非 TLS) 相同的 IP。
+ 新的 TLS Valkey 或 Redis OSS 組態端點將在 ElastiCache 主控台和 `describe-replication-group` API 回應中公開。

當叢集設定為「傳輸加密模式：需要」時：
+ 舊版未啟用 TLS 的端點將被刪除。TLS 叢集端點不會有停機時間。
+ 您可以從 ElastiCache 控制台或 `describe-replication-group` API 檢索新的 `cluster-configuration-endpoint`。

**對於啟用自動容錯移轉或停用自動容錯移轉的 CMD 叢集**

當複寫群組設為「傳輸加密模式：偏好」時：
+ 啟用非 TLS 叢集的原始主端點和讀取器端點將保持作用中狀態。
+ 當叢集設為 TLS `Preferred` 模式時，將產生新的 TLS 主要端點和讀取端點。此新端點將解析為與舊端點 (非 TLS) 相同的 IP。
+ 新的主要端點和讀取器端點將在 ElastiCache 控制台和對 `describe-replication-group` API 的反應中公開。

當複寫群組設為「傳輸加密模式：需要」時：
+ 舊的非 TLS 主端點和讀取器端點將被刪除。TLS 叢集端點不會有停機時間。
+ 您可以從 ElastiCache 控制台或 `describe-replication-group` API 檢索新的主要與讀取器端點。

### DNS 記錄的建議用法
<a name="enable-python-best-practices-before-usage"></a>

**對於 CME 叢集**
+ 使用叢集組態端點，而不是應用程式程式碼中的每個節點 DNS 記錄。不建議直接使用每個節點 DNS 名稱，因為在遷移期間，它們會變更，而應用程式程式碼會中斷與叢集的連線。
+ 請勿硬式編碼應用程式中的叢集組態端點，因為它會在此程序期間變更。
+ 在應用程式中硬式編碼叢集組態端點是錯誤的實務，因為在此過程中可以變更。傳輸中加密完成後，請使用 `describe-replication-group` API 查詢叢集組態端點 （如上所述 （粗體）)，並使用您從該時間點取得的 DNS 來回應。

**對於啟用自動容錯移轉的 CMD 叢集**
+ 使用主要端點和讀取器端點，而不是應用程式程式碼中的每節點 DNS 名稱，因為舊的每節點 DNS 名稱會被刪除，並在將叢集從非 TLS 移轉至 TLS 偏好時產生新的 DNS 名稱。不建議直接使用每個節點的 DNS 名稱，因為您未來可能會將複本新增至叢集。此外，啟用「自動容錯移轉」時，ElastiCache 服務會自動變更主要叢集和複本的角色，建議使用主要端點和讀取器端點來協助您追蹤這些變更。最後，使用讀取器端點可協助您在叢集中的複本之間平均分配複本的讀取。
+ 在應用程式中硬式編碼主要端點和讀取器端點是不好的做法，因為它可以在 TLS 遷移程序期間進行變更。完成 TLS 偏好的遷移變更後，請使用 describe-replication-group API 查詢主要端點和讀取器端點，並使用您從此時取得的 DNS 回應。如此，您將能夠以動態方式追踪端點中的變更。

**對於停用自動容錯移轉的 CMD 叢集**
+ 使用主要端點和讀取器端點，而不是應用程式程式碼中的每個節點 DNS 名稱。當停用自動容錯移轉時，在啟用自動容錯移轉時由 ElastiCache 服務自動管理的擴展、修補、容錯移轉和其他過程將由您完成。這可讓您更輕易地手動追蹤不同端點。由於舊的每個節點 DNS 名稱會被刪除，並在將叢集從非 TLS 移轉至 TLS 偏好時產生新名稱，因此請勿直接使用每個節點的 DNS 名稱。這是強制性的，因此用戶端才能夠在 TLS 遷移期間連線到叢集。此外，使用讀取器端點時，您將受益於在複本之間均勻分散讀取，並在從叢集中新增或刪除複本時追蹤 DNS 記錄。
+ 在應用程式中對叢集組態端點進行硬式編碼是一種不好的做法，因為它可以在 TLS 遷移過程中變更。

## 在傳輸過程中加密：注意遷移過程何時完成
<a name="enable-python-best-practices-during"></a>

傳輸加密模式的變更不是立即的，可能需要一些時間。大型叢集尤其如此。只有當叢集完成 TLS 的遷移作業時，才能接受並提供 TCP 和 TLS 連線。因此，在傳輸中加密完成之前，您不應該建立嘗試與叢集建立 TLS 連線的用戶端。

在傳輸中加密成功完成或失敗時，有幾種方法可以得到通知：(未顯示於上面的程式碼範例中)：
+ 使用 SNS 服務在加密完成時收到通知
+ 使用將在加密完成時發出事件的 `describe-events` API
+ 在 ElastiCache 主控台中看到加密已完成的訊息

您也可以在應用程式中實作邏輯，以瞭解加密是否完成。在上面的範例中，我們看到了幾種確保叢集完成遷移的方法：
+ 等待遷移程序開始 (叢集狀態變更為「修改」)，並等待修改完成 (叢集狀態會變回「可用」)
+ 透過查詢 `describe-replication-group` API 聲明叢集的 `transit_encryption_enabled` 已設為 True。

### 啟用傳輸中加密之後：確定您使用的用戶端設定正確
<a name="enable-python-best-practices-after"></a>

當叢集處於 TLS 偏好的模式時，您的應用程式應該開啟與叢集的 TLS 連線，並且只使用這些連線。如此一來，您的應用程式在啟用傳輸中加密時就不會遇到停機時間。您可以使用 SSL 區段下的資訊命令，確保沒有更明確的 TCP 連線至 Valkey 或 Redis OSS 引擎。

```
# SSL
ssl_enabled:yes
ssl_current_certificate_not_before_date:Mar 20 23:27:07 2017 GMT
ssl_current_certificate_not_after_date:Feb 24 23:27:07 2117 GMT
ssl_current_certificate_serial:D8C7DEA91E684163
tls_mode_connected_tcp_clients:0   (should be zero)
tls_mode_connected_tls_clients:100
```

## 使用 Openssl (Memcached) 連線至已啟用傳輸中加密的節點
<a name="in-transit-encryption-connect-mc"></a>

若要從啟用傳輸中加密的 ElastiCache for Memcached 節點存取資料，需使用可與 Secure Sockets Layer (SSL) 搭配的用戶端。您也可以在 Amazo Linux 和 Amazo Linux 2 上使用 Openssl s\$1client。

在 Amazo Linux 或 Amazo Linux 2 上，使用 Openssl s\$1client 來連線到啟用傳輸中加密的 Memcached 叢集：

```
/usr/bin/openssl s_client -connect memcached-node-endpoint:memcached-port
```

## 使用 Java 建立 TLS Memcached 用戶端
<a name="in-transit-encryption-connect-java"></a>

若要在 TLS 模式下建立用戶端，請執行下列操作，使用適當的 SSLContext 來初始化用戶端：

```
import java.security.KeyStore;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import net.spy.memcached.AddrUtil;
import net.spy.memcached.ConnectionFactoryBuilder;
import net.spy.memcached.MemcachedClient;
public class TLSDemo {
    public static void main(String[] args) throws Exception {
        ConnectionFactoryBuilder connectionFactoryBuilder = new ConnectionFactoryBuilder();
        // Build SSLContext
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init((KeyStore) null);
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, tmf.getTrustManagers(), null);
        // Create the client in TLS mode
        connectionFactoryBuilder.setSSLContext(sslContext);
        MemcachedClient client = new MemcachedClient(connectionFactoryBuilder.build(), AddrUtil.getAddresses("mycluster.fnjyzo.cfg.use1.cache.amazonaws.com:11211"));

        // Store a data item for an hour.
        client.set("theKey", 3600, "This is the data value");
    }
}
```

## 使用 PHP 建立 TLS Memcached 用戶端
<a name="in-transit-encryption-connect-php-mc"></a>

若要在 TLS 模式下建立用戶端，請執行下列操作，使用適當的 SSLContext 來初始化用戶端：

```
<?php

/**
 * Sample PHP code to show how to create a TLS Memcached client. In this example we
 * will use the Amazon ElastiCache Auto Descovery feature, but TLS can also be
 * used with a Static mode client. 
 * See Using the ElastiCache Cluster Client for PHP (https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/AutoDiscovery.Using.ModifyApp.PHP.html) for more information 
 * about Auto Discovery and persistent-id.
 */

/* Configuration endpoint to use to initialize memcached client.
 * this is only an example */
$server_endpoint = "mycluster.fnjyzo.cfg.use1.cache.amazonaws.com";

/* Port for connecting to the cluster. 
 * This is only an example     */
$server_port = 11211;

/* Initialize a persistent Memcached client and configure it with the Dynamic client mode  */
$tls_client =  new Memcached('persistent-id');
$tls_client->setOption(Memcached::OPT_CLIENT_MODE, Memcached::DYNAMIC_CLIENT_MODE);

/* Add the memcached's cluster server/s */
$tls_client->addServer($server_endpoint, $server_port);

/* Configure the client to use TLS */
if(!$tls_client->setOption(Memcached::OPT_USE_TLS, 1)) {
    echo $tls_client->getLastErrorMessage(), "\n";
    exit(1);
}

/* Set your TLS context configurations values.
 * See MemcachedTLSContextConfig in memcached-api.php for all configurations */
$tls_config = new MemcachedTLSContextConfig();
$tls_config->hostname = '*.mycluster.fnjyzo.use1.cache.amazonaws.com';
$tls_config->skip_cert_verify = false;
$tls_config->skip_hostname_verify = false;

/* Use the created TLS context configuration object to create OpenSSL's SSL_CTX and set it to your client.
 * Note:  These TLS context configurations will be applied to all the servers connected to this client. */
$tls_client->createAndSetTLSContext((array)$tls_config);

/* test the TLS connection with set-get scenario: */

 /* store the data for 60 seconds in the cluster.
 * The client will decide which cache host will store this item.
 */
if($tls_client->set('key', 'value', 60)) {
    print "Successfully stored key\n";
} else {
    echo "Failed to set key: ", $tls_client->getLastErrorMessage(), "\n";
    exit(1);
}

/* retrieve the key */
if ($tls_client->get('key') === 'value') {
    print "Successfully retrieved key\n";
} else {
    echo "Failed to get key: ", $tls_client->getLastErrorMessage(), "\n";
    exit(1);
}
```

如需使用 PHP 用戶端的詳細資訊，請參閱[安裝適用於 PHP 的 ElastiCache 叢集用戶端](Appendix.PHPAutoDiscoverySetup.md)。