

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

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

为了帮助保护您的数据安全，Amazon ElastiCache 和 Amazon EC2 提供了防止未经授权访问您在服务器上的数据的机制。通过提供传输中加密功能， ElastiCache 为您提供一种工具，用于在数据从一个位置移动到另一个位置时帮助保护数据。

所有 Valkey 或 Redis OSS 无服务器缓存均启用了传输中加密。对于基于节点的集群，在创建复制组时，您可将参数 `TransitEncryptionEnabled` 设置为 `true`（CLI：`--transit-encryption-enabled`），以此在复制组中启用传输中加密。无论您是使用、还是 ElastiCache API 创建复制组AWS 管理控制台AWS CLI，都可以执行此操作。

所有无服务器缓存均启用了传输中加密。对于基于节点的集群，在使用 `CreateCacheCluster`（CLI：`create-cache-cluster`）操作创建集群时，您可以将参数 `TransitEncryptionEnabled` 设置为 `true`（CLI：`--transit-encryption-enabled`），从而在集群上启用传输中加密。

**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 传输中加密的限制：
+ 在运行以下 Redis 版本的复制组上支持传输中加密：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日起，Valkey 7.2及以上版本以及Redis OSS版本6及以上版本AWS将支持的最低TLS版本更新 ElastiCache 为1.2。 ElastiCache 客户必须在该日期之前更新其客户端软件。此更新可帮助您满足安全、合规和监管需求。

## 传输中加密的条件（Memcached）
<a name="in-transit-encryption-constraints"></a>

在规划基于节点的集群实施时，应牢记以下对 Amazon ElastiCache 传输中加密的限制：
+ 在运行 Memcached 1.6.12 及更高版本的集群上支持传输中加密。
+ 传输中加密支持传输层安全性协议（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 VPCs 与 ElastiCache 安全](VPCs.md)
+ [适用于亚马逊的身份和访问管理 ElastiCache](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>

所有无服务器缓存均启用了传输中加密。在基于节点的集群上，您可以使用、CLI 或 AP AWS I 启用传输AWS 管理控制台中加密。 ElastiCache

## 使用启用传输中加密AWS 管理控制台
<a name="in-transit-encryption-enable-console"></a>

### 使用为新的基于节点的集群启用传输中加密AWS 管理控制台
<a name="in-transit-encryption-enable-con"></a>

在设计自己的集群时，采用“轻松创建”方法的“开发/测试”和“生产”配置均启用了传输中加密。在您选择自己的配置时，请进行以下选择：
+ 选择 3.2.6、4.0.10 或更高的引擎版本。
+ 单击**传输中加密**选项的**启用**旁边的复选框。

有关 step-by-step流程，请参阅以下内容：
+ [创建 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 管理控制台并打开 Amazon ElastiCache 控制台，网址为[https://console.aws.amazon.com/elasticache/](https://console.aws.amazon.com/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 管理控制台并打开 Amazon ElastiCache 控制台，网址为[https://console.aws.amazon.com/elasticache/](https://console.aws.amazon.com/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>

要在使用AWS CLI创建 Valkey 或 Redis OSS 复制组时启用传输中加密，请使用参数 `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 缓存中的数据，您可以使用利用安全套接字层（SSL）的客户端。您也可以在 Amazon Linux 和 Amazon Linux 2 上使用具有 TLS/SSL 的 valkey-cli。如果您的客户端不支持 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。在此示例中，主 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 密码，此示例将在 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 密码，此示例将在 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 密码，此示例将在 Amazon Linux 上使用 valkey-cli 通过其完整路径连接到 ElastiCache for Redis OSS 服务器：

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

     如果在创建 Redis OSS 集群时未使用 AUTH 密码，此示例将在 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 隧道，请对 Stunnel 过程执行 `pkill` 操作。

   ```
   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 代码。以下示例中的代码是在将用于在其中创建 ElastiCache Redis OSS 集群的同一 VPC 中启动的 EC2 实例中的进程。

**注意**  
以下示例使用 boto3 SDK 进行 ElastiCache 管理操作（创建集群或用户），使用 redis-p redis-py-cluster y/ 进行数据处理。
您必须至少使用 boto3 版本 (=\$1) 1.26.39，才能通过集群修改 API 使用在线 TLS 迁移。
ElastiCache 仅支持 Valkey 版本 7.2 及以上或 Redis OSS 版本 7.0 或更高版本的集群的在线 TLS 迁移。因此，如果您的集群运行的是 7.0 之前的 Redis OSS 版本，则需要升级集群的 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实体的名称`security-group`，例如`Cache Subnet group`、和 a `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 集群本身。该类将有一个 client 字段，其中包含一个 boto3 客户端，用于 ElastiCache 管理操作，例如创建集群和查询 API。 ElastiCache

```
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 从 TLS `Preferred` 迁移到需要 TLS。

1. 当集群需要迁移到 TLS 时， redis-py-clusterTLS 客户端将向集群发送重复的`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 记录格式不同。当集群配置为 `encryption mode: Preferred` 时，ElastiCache 将保留这两种 DNS 记录，以便应用程序和其他 Valkey 或 Redis OSS 客户端可以在它们之间切换。在 TLS 迁移过程中，DNS 记录发生以下更改：

### 启用传输中加密时发生的 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 名称，因为将集群从无 TLS 迁移到首选 TLS 时，会删除旧的每节点 DNS 名称并生成新的 DNS 名称。不建议直接使用每节点 DNS 名称，因为将来您可能会向集群添加副本。此外，启用自动失效转移后，ElastiCache 服务会自动更改主集群和副本的角色，建议使用主端点和读取器端点来帮助您跟踪这些更改。最后，使用读取器端点将帮助您在集群中的副本之间平均分配从副本进行的读取操作。
+ 在应用程序中对主端点和读取器端点进行硬编码是不好的做法，因为在 TLS 迁移过程中可能会对端点进行更改。完成向首选 TLS 的迁移更改后，使用 describe-replication-group API 查询主端点和读取器端点，然后从此时起使用从响应中获得的 DNS。这样，您将能够以动态方式跟踪端点的变化。

**对于禁用了自动失效转移的 CMD 集群**
+ 在应用程序的代码中使用主端点和读取器端点，而不是每节点 DNS 名称。禁用自动失效转移时，启用自动失效转移时由 ElastiCache 服务自动管理的扩缩、修补、失效转移以及其他过程将改为由您完成。这使您能够更轻松地手动跟踪不同的端点。由于在将集群从无 TLS 迁移到首选 TLS 时，会删除旧的每节点 DNS 名称并生成新的每节点 DNS 名称，因此请勿直接使用每节点 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 部分下的 info 命令，确保与 Valkey 或 Redis OSS 引擎之间没有更清晰的 TCP 连接。

```
# 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 的 Memcached 节点的数据，您需要使用支持安全套接字层 (SSL) 的客户端。您也可以在 Amazon Linux 和 Amazon Linux 2 上使用 Openssl s\$1client。

使用 Openssl s\$1client 连接到 Amazon Linux 2 或 Amazon Linux 上启用了传输中加密的 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 Cluster Client](Appendix.PHPAutoDiscoverySetup.md)。