

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

# 了解如何使用带有自定义属性的 AWS Cloud Map 服务发现 AWS CLI
<a name="tutorial-microservices-cli"></a>

本教程演示了如何使用带有自定义属性的 AWS Cloud Map 服务发现。您将创建一个微服务应用程序， AWS Cloud Map 用于使用自定义属性动态发现资源。该应用程序由两个 Lambda 函数组成，它们在 DynamoDB 表中写入和读取数据，所有资源都注册在中。 AWS Cloud Map

有关本教程的 AWS 管理控制台 版本，请参阅[了解如何使用带有自定义属性的 AWS Cloud Map 服务发现](tutorial-microservices.md)。

## 先决条件
<a name="prerequisites"></a>

在开始本教程之前，请完成中的步骤[设置为使用 AWS Cloud Map](setting-up-cloud-map.md)。

## 创建 AWS Cloud Map 命名空间
<a name="create-an-aws-cloud-map-namespace"></a>

命名空间是一种用于对应用程序的服务进行分组的结构。在此步骤中，您将创建一个允许通过 AWS Cloud Map API 调用发现资源的命名空间。

1. 运行以下命令创建 HTTP 命名空间：

   ```
   aws servicediscovery create-http-namespace \
     --name cloudmap-tutorial \
     --creator-request-id cloudmap-tutorial-request
   ```

   该命令返回操作 ID。您可以使用以下命令检查操作的状态：

   ```
   aws servicediscovery get-operation \
     --operation-id operation-id
   ```

1. 创建命名空间后，您可以检索其 ID 以用于后续命令：

   ```
   aws servicediscovery list-namespaces \
     --query "Namespaces[?Name=='cloudmap-tutorial'].Id" \
     --output text
   ```

1. 将命名空间 ID 存储在变量中以备后用：

   ```
   NAMESPACE_ID=$(aws servicediscovery list-namespaces \
     --query "Namespaces[?Name=='cloudmap-tutorial'].Id" \
     --output text)
   ```

## 创建 DynamoDB 表
<a name="create-a-dynamodb-table"></a>

接下来，创建一个用于存储应用程序数据的 DynamoDB 表：

1. 运行以下命令来创建表：

   ```
   aws dynamodb create-table \
     --table-name cloudmap \
     --attribute-definitions AttributeName=id,AttributeType=S \
     --key-schema AttributeName=id,KeyType=HASH \
     --billing-mode PAY_PER_REQUEST
   ```

1. 等待表格变为活动状态后再继续：

   ```
   aws dynamodb wait table-exists --table-name cloudmap
   ```

   此命令将等待，直到表完全创建完毕并可供使用。

## 创建 AWS Cloud Map 数据服务并注册 DynamoDB 表
<a name="create-an-aws-cloud-map-data-service-and-register-the-dynamodb-table"></a>

现在，在你的命名空间中创建一个服务来表示数据存储资源：

1. 运行以下命令为数据存储资源创建 AWS Cloud Map 服务：

   ```
   aws servicediscovery create-service \
     --name data-service \
     --namespace-id $NAMESPACE_ID \
     --creator-request-id data-service-request
   ```

1. 获取数据服务的服务 ID：

   ```
   DATA_SERVICE_ID=$(aws servicediscovery list-services \
     --query "Services[?Name=='data-service'].Id" \
     --output text)
   ```

1. 将 DynamoDB 表注册为具有指定表名的自定义属性的服务实例：

   ```
   aws servicediscovery register-instance \
     --service-id $DATA_SERVICE_ID \
     --instance-id data-instance \
     --attributes tablename=cloudmap
   ```

   自定义属性`tablename=cloudmap`允许其他服务动态发现 DynamoDB 表名。

## 为 Lambda 函数创建 IAM 角色
<a name="create-an-iam-role-for-lambda-functions"></a>

创建一个 IAM 角色，Lambda 函数将使用该角色来访问 AWS 资源：

1. 使用以下 JSON 为 IAM 角色创建信任策略文档：

------
#### [ JSON ]

****  

   ```
   {
     "Version":"2012-10-17",		 	 	 		 	 	 
     "Statement": [
       {
         "Effect": "Allow",
         "Principal": {
           "Service": "lambda.amazonaws.com"
         },
         "Action": "sts:AssumeRole"
       }
     ]
   }
   ```

------

1. 运行以下命令以使用信任策略创建 IAM 角色：

   ```
   aws iam create-role \
     --role-name cloudmap-tutorial-role \
     --assume-role-policy-document file://lambda-trust-policy.json
   ```

1. 使用以下 JSON 为具有最低权限的自定义 IAM 策略创建文件：

------
#### [ JSON ]

****  

   ```
   {
     "Version":"2012-10-17",		 	 	 		 	 	 
     "Statement": [
       {
         "Effect": "Allow",
         "Action": [
           "logs:CreateLogGroup",
           "logs:CreateLogStream",
           "logs:PutLogEvents"
         ],
         "Resource": "arn:aws:logs:*:*:*"
       },
       {
         "Effect": "Allow",
         "Action": [
           "servicediscovery:DiscoverInstances"
         ],
         "Resource": "*"
       },
       {
         "Effect": "Allow",
         "Action": [
           "dynamodb:PutItem",
           "dynamodb:Scan"
         ],
         "Resource": "arn:aws:dynamodb:*:*:table/cloudmap"
       }
     ]
   }
   ```

------

1. 创建策略并将其附加到 IAM 角色：

   ```
   aws iam create-policy \
     --policy-name CloudMapTutorialPolicy \
     --policy-document file://cloudmap-policy.json
   
   POLICY_ARN=$(aws iam list-policies \
     --query "Policies[?PolicyName=='CloudMapTutorialPolicy'].Arn" \
     --output text)
   
   aws iam attach-role-policy \
     --role-name cloudmap-tutorial-role \
     --policy-arn $POLICY_ARN
   
   aws iam attach-role-policy \
     --role-name cloudmap-tutorial-role \
     --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
   ```

## 创建 Lambda 函数来写入数据
<a name="create-the-lambda-function-to-write-data"></a>

要创建将数据写入 DynamoDB 表的 Lambda 函数，请执行以下步骤：

1. 为写入函数创建 Python 文件：

   ```
   cat > writefunction.py << EOF
   import json
   import boto3
   import random
   
   def lambda_handler(event, context):
       try:
           serviceclient = boto3.client('servicediscovery')
           
           response = serviceclient.discover_instances(
               NamespaceName='cloudmap-tutorial',
               ServiceName='data-service')
           
           if not response.get("Instances"):
               return {
                   'statusCode': 500,
                   'body': json.dumps({"error": "No instances found"})
               }
               
           tablename = response["Instances"][0]["Attributes"].get("tablename")
           if not tablename:
               return {
                   'statusCode': 500,
                   'body': json.dumps({"error": "Table name attribute not found"})
               }
              
           dynamodbclient = boto3.resource('dynamodb')
              
           table = dynamodbclient.Table(tablename)
           
           # Validate input
           if not isinstance(event, str):
               return {
                   'statusCode': 400,
                   'body': json.dumps({"error": "Input must be a string"})
               }
              
           response = table.put_item(
               Item={ 'id': str(random.randint(1,100)), 'todo': event })
              
           return {
               'statusCode': 200,
               'body': json.dumps(response)
           }
       except Exception as e:
           return {
               'statusCode': 500,
               'body': json.dumps({"error": str(e)})
           }
   EOF
   ```

   此函数 AWS Cloud Map 用于从自定义属性中发现 DynamoDB 表名，然后向表中写入数据。

1. Package 并部署 Lambda 函数：

   ```
   zip writefunction.zip writefunction.py
   
   ROLE_ARN=$(aws iam get-role --role-name cloudmap-tutorial-role \
     --query 'Role.Arn' --output text)
   
   aws lambda create-function \
     --function-name writefunction \
     --runtime python3.12 \
     --role $ROLE_ARN \
     --handler writefunction.lambda_handler \
     --zip-file fileb://writefunction.zip \
     --architectures x86_64
   ```

1. 更新函数超时以避免超时错误：

   ```
   aws lambda update-function-configuration \
     --function-name writefunction \
     --timeout 5
   ```

## 创建 AWS Cloud Map 应用程序服务并注册 Lambda 写入函数
<a name="create-an-aws-cloud-map-app-service-and-register-the-lambda-write-function"></a>

要在命名空间中创建另一个服务来表示应用程序函数，请执行以下步骤：

1. 为应用程序函数创建服务：

   ```
   aws servicediscovery create-service \
     --name app-service \
     --namespace-id $NAMESPACE_ID \
     --creator-request-id app-service-request
   ```

1. 获取应用程序服务的服务 ID：

   ```
   APP_SERVICE_ID=$(aws servicediscovery list-services \
     --query "Services[?Name=='app-service'].Id" \
     --output text)
   ```

1. 将 Lambda 写入函数注册为具有自定义属性的服务实例：

   ```
   aws servicediscovery register-instance \
     --service-id $APP_SERVICE_ID \
     --instance-id write-instance \
     --attributes action=write,functionname=writefunction
   ```

   自定义属性`action=write`并`functionname=writefunction`允许客户根据其用途发现此功能。

## 创建 Lambda 函数来读取数据
<a name="create-the-lambda-function-to-read-data"></a>

要创建从 DynamoDB 表中读取数据的 Lambda 函数，请执行以下步骤：

1. 为读取函数创建 Python 文件：

   ```
   cat > readfunction.py << EOF
   import json
   import boto3
   
   def lambda_handler(event, context):
       try:
           serviceclient = boto3.client('servicediscovery')
   
           response = serviceclient.discover_instances(
               NamespaceName='cloudmap-tutorial', 
               ServiceName='data-service')
           
           if not response.get("Instances"):
               return {
                   'statusCode': 500,
                   'body': json.dumps({"error": "No instances found"})
               }
               
           tablename = response["Instances"][0]["Attributes"].get("tablename")
           if not tablename:
               return {
                   'statusCode': 500,
                   'body': json.dumps({"error": "Table name attribute not found"})
               }
              
           dynamodbclient = boto3.resource('dynamodb')
              
           table = dynamodbclient.Table(tablename)
           
           # Use pagination for larger tables
           response = table.scan(
               Select='ALL_ATTRIBUTES',
               Limit=50  # Limit results for demonstration purposes
           )
           
           # For production, you would implement pagination like this:
           # items = []
           # while 'LastEvaluatedKey' in response:
           #     items.extend(response['Items'])
           #     response = table.scan(
           #         Select='ALL_ATTRIBUTES',
           #         ExclusiveStartKey=response['LastEvaluatedKey']
           #     )
           # items.extend(response['Items'])
   
           return {
               'statusCode': 200,
               'body': json.dumps(response)
           }
       except Exception as e:
           return {
               'statusCode': 500,
               'body': json.dumps({"error": str(e)})
           }
   EOF
   ```

   此函数还用于 AWS Cloud Map 发现 DynamoDB 表名，然后从表中读取数据。它包括错误处理和分页注释。

1. Package 并部署 Lambda 函数：

   ```
   zip readfunction.zip readfunction.py
   
   aws lambda create-function \
     --function-name readfunction \
     --runtime python3.12 \
     --role $ROLE_ARN \
     --handler readfunction.lambda_handler \
     --zip-file fileb://readfunction.zip \
     --architectures x86_64
   ```

1. 更新函数超时：

   ```
   aws lambda update-function-configuration \
     --function-name readfunction \
     --timeout 5
   ```

## 将 Lambda 读取函数注册为服务实例
<a name="register-the-lambda-read-function-as-a-service-instance"></a>

要将 Lambda 读取函数注册为应用服务中的另一个服务实例，请执行以下步骤：

```
aws servicediscovery register-instance \
  --service-id $APP_SERVICE_ID \
  --instance-id read-instance \
  --attributes action=read,functionname=readfunction
```

自定义属性`action=read`并`functionname=readfunction`允许客户根据其用途发现此功能。

## 创建和运行客户端应用程序
<a name="create-and-run-client-applications"></a>

要创建用于发现和调用 AWS Cloud Map 写入函数的 Python 客户端应用程序，请执行以下步骤：

1. 为写入客户端应用程序创建一个 Python 文件：

   ```
   cat > writeclient.py << EOF
   import boto3
   import json
   
   try:
       serviceclient = boto3.client('servicediscovery')
   
       print("Discovering write function...")
       response = serviceclient.discover_instances(
           NamespaceName='cloudmap-tutorial', 
           ServiceName='app-service', 
           QueryParameters={ 'action': 'write' }
       )
   
       if not response.get("Instances"):
           print("Error: No instances found")
           exit(1)
           
       functionname = response["Instances"][0]["Attributes"].get("functionname")
       if not functionname:
           print("Error: Function name attribute not found")
           exit(1)
           
       print(f"Found function: {functionname}")
   
       lambdaclient = boto3.client('lambda')
   
       print("Invoking Lambda function...")
       resp = lambdaclient.invoke(
           FunctionName=functionname, 
           Payload='"This is a test data"'
       )
   
       payload = resp["Payload"].read()
       print(f"Response: {payload.decode('utf-8')}")
       
   except Exception as e:
       print(f"Error: {str(e)}")
   EOF
   ```

   此客户端使用该`QueryParameters`选项来查找具有该`action=write`属性的服务实例。

1. 为读取的客户端应用程序创建一个 Python 文件：

   ```
   cat > readclient.py << EOF
   import boto3
   import json
   
   try:
       serviceclient = boto3.client('servicediscovery')
   
       print("Discovering read function...")
       response = serviceclient.discover_instances(
           NamespaceName='cloudmap-tutorial', 
           ServiceName='app-service', 
           QueryParameters={ 'action': 'read' }
       )
   
       if not response.get("Instances"):
           print("Error: No instances found")
           exit(1)
           
       functionname = response["Instances"][0]["Attributes"].get("functionname")
       if not functionname:
           print("Error: Function name attribute not found")
           exit(1)
           
       print(f"Found function: {functionname}")
   
       lambdaclient = boto3.client('lambda')
   
       print("Invoking Lambda function...")
       resp = lambdaclient.invoke(
           FunctionName=functionname, 
           InvocationType='RequestResponse'
       )
   
       payload = resp["Payload"].read()
       print(f"Response: {payload.decode('utf-8')}")
       
   except Exception as e:
       print(f"Error: {str(e)}")
   EOF
   ```

1. 运行写入客户端向 DynamoDB 表添加数据：

   ```
   python3 writeclient.py
   ```

   输出应显示成功响应，HTTP 状态代码为 200。

1. 运行读取客户端从 DynamoDB 表中检索数据：

   ```
   python3 readclient.py
   ```

   输出应显示写入表的数据，包括随机生成的 ID 和 “这是测试数据” 值。

## 清理 资源
<a name="clean-up-resources"></a>

完成本教程后，请清理资源以免产生额外费用。

1. 首先，运行以下命令取消注册服务实例：

   ```
   aws servicediscovery deregister-instance \
     --service-id $APP_SERVICE_ID \
     --instance-id read-instance
   
   aws servicediscovery deregister-instance \
     --service-id $APP_SERVICE_ID \
     --instance-id write-instance
   
   aws servicediscovery deregister-instance \
     --service-id $DATA_SERVICE_ID \
     --instance-id data-instance
   ```

1. 运行以下命令删除服务：

   ```
   aws servicediscovery delete-service \
     --id $APP_SERVICE_ID
   
   aws servicediscovery delete-service \
     --id $DATA_SERVICE_ID
   ```

1. 运行以下命令删除命名空间：

   ```
   aws servicediscovery delete-namespace \
     --id $NAMESPACE_ID
   ```

1. 运行以下命令删除 Lambda 函数：

   ```
   aws lambda delete-function --function-name writefunction
   aws lambda delete-function --function-name readfunction
   ```

1. 运行以下命令删除 IAM 角色和策略：

   ```
   aws iam detach-role-policy \
     --role-name cloudmap-tutorial-role \
     --policy-arn $POLICY_ARN
   
   aws iam detach-role-policy \
     --role-name cloudmap-tutorial-role \
     --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
   
   aws iam delete-policy \
     --policy-arn $POLICY_ARN
   
   aws iam delete-role --role-name cloudmap-tutorial-role
   ```

1. 运行以下命令删除 DynamoDB 表：

   ```
   aws dynamodb delete-table --table-name cloudmap
   ```

1. 运行以下命令清理临时文件：

   ```
   rm -f lambda-trust-policy.json cloudmap-policy.json writefunction.py readfunction.py writefunction.zip readfunction.zip writeclient.py readclient.py
   ```