AWS::MSK::Cluster - AWS CloudFormation
AWS 文档中描述的 AWS 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅中国的 AWS 服务入门

AWS::MSK::Cluster

AWS::MSK::Cluster 资源创建一个 Amazon MSK 集群。有关更多信息,请参阅 Amazon MSK 开发人员指南 中的什么是 Amazon MSK?

语法

要在 AWS CloudFormation 模板中声明此实体,请使用以下语法:

属性

BrokerNodeGroupInfo

要用于集群中的代理的设置。

必需:是

类型BrokerNodeGroupInfo

Update requires: Replacement

ClientAuthentication

包括与客户端身份验证相关的信息。

必需:否

类型ClientAuthentication

Update requires: Replacement

ClusterName

集群的名称。

必需:是

类型:字符串

Update requires: Replacement

ConfigurationInfo

要用于集群的 Amazon MSK 配置。

必需:否

类型ConfigurationInfo

Update requires: No interruption

EncryptionInfo

包括所有与加密相关的信息。

必需:否

类型EncryptionInfo

Update requires: Replacement

EnhancedMonitoring

指定用于 MSK 集群的监控的级别。可能的值为DEFAULTPER_BROKERPER_TOPIC_PER_BROKER

必需:否

类型:字符串

Update requires: No interruption

KafkaVersion

Apache Kafka 的版本。您可以使用 Amazon MSK 创建使用 Apache Kafka 版本 1.1.1 和 2.2.1 的集群。

必需:是

类型:字符串

Update requires: No interruption

LoggingInfo

您可以将 MSK 集群配置为将代理日志发送到不同的目标类型。这是与代理日志相关的配置详细信息的容器。

必需:否

类型LoggingInfo

Update requires: No interruption

NumberOfBrokerNodes

在 Amazon MSK 集群中需要的代理节点的数量。您可以提交更新以增加集群中的代理节点数量。

必需:是

类型:整数

Update requires: No interruption

OpenMonitoring

开放监控的设置。

必需:否

类型OpenMonitoring

Update requires: No interruption

Tags

要应用于该资源的键值对的映射。键和值均为 String 类型。

必需:否

类型:Json

Update requires: Replacement

返回值

Ref

在将此资源的逻辑 ID 传递给内部 Ref 函数时,Ref 返回 Amazon MSK 集群 ARN。例如:

REF MyTestCluster

对于 Amazon MSK 集群 MyTestCluster,Ref 将返回集群的 ARN。

For more information about using the Ref function, see Ref.

示例

在以下示例中,您可以找到每个模板的 YAML,后跟等效的 JSON。您可以使用任一语言。

创建一个 MSK 集群,其中只指定所需属性的值

YAML

Description: MSK Cluster with required properties. Resources: TestCluster: Type: 'AWS::MSK::Cluster' Properties: ClusterName: ClusterWithRequiredProperties KafkaVersion: 2.2.1 NumberOfBrokerNodes: 3 BrokerNodeGroupInfo: InstanceType: kafka.m5.large ClientSubnets: - ReplaceWithSubnetId1 - ReplaceWithSubnetId2 - ReplaceWithSubnetId3

{ "Description": "MSK Cluster with required properties.", "Resources": { "TestCluster": { "Type": "AWS::MSK::Cluster", "Properties": { "ClusterName": "ClusterWithRequiredProperties", "KafkaVersion": "2.2.1", "NumberOfBrokerNodes": 3, "BrokerNodeGroupInfo": { "InstanceType": "kafka.m5.large", "ClientSubnets": [ "ReplaceWithSubnetId1", "ReplaceWithSubnetId2", "ReplaceWithSubnetId3" ] } } } } }

创建一个 MSK 集群,其中显式设置所有属性

YAML

Description: MSK Cluster with all properties Resources: TestCluster: Type: 'AWS::MSK::Cluster' Properties: ClusterName: ClusterWithAllProperties KafkaVersion: 2.2.1 NumberOfBrokerNodes: 3 EnhancedMonitoring: PER_BROKER EncryptionInfo: EncryptionAtRest: DataVolumeKMSKeyId: ReplaceWithKmsKeyArn EncryptionInTransit: ClientBroker: TLS InCluster: true OpenMonitoring: Prometheus: JmxExporter: EnabledInBroker: "true" NodeExporter: EnabledInBroker: "true" ConfigurationInfo: Arn: ReplaceWithConfigurationArn Revision: 1 ClientAuthentication: Tls: CertificateAuthorityArnList: - ReplaceWithCAArn Tags: Environment: Test Owner: QATeam BrokerNodeGroupInfo: BrokerAZDistribution: DEFAULT InstanceType: kafka.m5.large SecurityGroups: - ReplaceWithSecurityGroupId StorageInfo: EBSStorageInfo: VolumeSize: 100 ClientSubnets: - ReplaceWithSubnetId1 - ReplaceWithSubnetId2 - ReplaceWithSubnetId3

{ "Description": "MSK Cluster with all properties", "Resources": { "TestCluster": { "Type": "AWS::MSK::Cluster", "Properties": { "ClusterName": "ClusterWithAllProperties", "KafkaVersion": "2.2.1", "NumberOfBrokerNodes": 3, "EnhancedMonitoring": "PER_BROKER", "EncryptionInfo": { "EncryptionAtRest": { "DataVolumeKMSKeyId": "ReplaceWithKmsKeyArn" }, "EncryptionInTransit": { "ClientBroker": "TLS", "InCluster": true } }, "OpenMonitoring": { "Prometheus": { "JmxExporter": { "EnabledInBroker": "true" } "NodeExporter": { "EnabledInBroker": "true" } } }, "ConfigurationInfo": { "Arn": "ReplaceWithConfigurationArn", "Revision": 1 }, "ClientAuthentication": { "Tls": { "CertificateAuthorityArnList": [ "ReplaceWithCAArn" ] } }, "Tags": { "Environment": "Test", "Owner" : "QATeam" }, "BrokerNodeGroupInfo": { "BrokerAZDistribution": "DEFAULT", "InstanceType": "kafka.m5.large", "SecurityGroups": [ "ReplaceWithSecurityGroupId" ], "StorageInfo": { "EBSStorageInfo": { "VolumeSize": 100 } }, "ClientSubnets": [ "ReplaceWithSubnetId1", "ReplaceWithSubnetId2", "ReplaceWithSubnetId3" ] } } } } }

开始使用 Amazon MSK

此示例模板以简单的架构创建 MSK 群集,以帮助您入门。

YAML

AWSTemplateFormatVersion: 2010-09-09 Parameters: KeyName: Description: Name of an existing EC2 KeyPair to enable SSH access to the instance Type: 'AWS::EC2::KeyPair::KeyName' ConstraintDescription: Can contain only ASCII characters. SSHLocation: Description: The IP address range that can be used to SSH to the EC2 instances Type: String MinLength: '9' MaxLength: '18' Default: 0.0.0.0/0 AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})' ConstraintDescription: Must be a valid IP CIDR range of the form x.x.x.x/x Mappings: SubnetConfig: VPC: CIDR: 10.0.0.0/16 PublicOne: CIDR: 10.0.0.0/24 PrivateOne: CIDR: 10.0.1.0/24 PrivateTwo: CIDR: 10.0.2.0/24 PrivateThree: CIDR: 10.0.3.0/24 RegionAMI: us-east-1: HVM64: ami-0c6b1d09930fac512 us-west-2: HVM64: ami-0cb72367e98845d43 Resources: VPC: Type: 'AWS::EC2::VPC' Properties: EnableDnsSupport: true EnableDnsHostnames: true CidrBlock: !FindInMap - SubnetConfig - VPC - CIDR Tags: - Key: Name Value: MMVPC PublicSubnetOne: Type: 'AWS::EC2::Subnet' Properties: AvailabilityZone: !Select - 0 - !GetAZs Ref: 'AWS::Region' VpcId: !Ref VPC CidrBlock: !FindInMap - SubnetConfig - PublicOne - CIDR MapPublicIpOnLaunch: true Tags: - Key: Name Value: MMPublicSubnet PrivateSubnetOne: Type: 'AWS::EC2::Subnet' Properties: AvailabilityZone: !Select - 0 - !GetAZs Ref: 'AWS::Region' VpcId: !Ref VPC CidrBlock: !FindInMap - SubnetConfig - PrivateOne - CIDR Tags: - Key: Name Value: MMPrivateSubnetOne PrivateSubnetTwo: Type: 'AWS::EC2::Subnet' Properties: AvailabilityZone: !Select - 1 - !GetAZs Ref: 'AWS::Region' VpcId: !Ref VPC CidrBlock: !FindInMap - SubnetConfig - PrivateTwo - CIDR Tags: - Key: Name Value: MMPrivateSubnetTwo PrivateSubnetThree: Type: 'AWS::EC2::Subnet' Properties: AvailabilityZone: !Select - 2 - !GetAZs Ref: 'AWS::Region' VpcId: !Ref VPC CidrBlock: !FindInMap - SubnetConfig - PrivateThree - CIDR Tags: - Key: Name Value: MMPrivateSubnetThree InternetGateway: Type: 'AWS::EC2::InternetGateway' GatewayAttachement: Type: 'AWS::EC2::VPCGatewayAttachment' Properties: VpcId: !Ref VPC InternetGatewayId: !Ref InternetGateway PublicRouteTable: Type: 'AWS::EC2::RouteTable' Properties: VpcId: !Ref VPC PublicRoute: Type: 'AWS::EC2::Route' DependsOn: GatewayAttachement Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway PublicSubnetOneRouteTableAssociation: Type: 'AWS::EC2::SubnetRouteTableAssociation' Properties: SubnetId: !Ref PublicSubnetOne RouteTableId: !Ref PublicRouteTable PrivateRouteTable: Type: 'AWS::EC2::RouteTable' Properties: VpcId: !Ref VPC PrivateSubnetOneRouteTableAssociation: Type: 'AWS::EC2::SubnetRouteTableAssociation' Properties: RouteTableId: !Ref PrivateRouteTable SubnetId: !Ref PrivateSubnetOne PrivateSubnetTwoRouteTableAssociation: Type: 'AWS::EC2::SubnetRouteTableAssociation' Properties: RouteTableId: !Ref PrivateRouteTable SubnetId: !Ref PrivateSubnetTwo PrivateSubnetThreeRouteTableAssociation: Type: 'AWS::EC2::SubnetRouteTableAssociation' Properties: RouteTableId: !Ref PrivateRouteTable SubnetId: !Ref PrivateSubnetThree KafkaClientInstanceSecurityGroup: Type: 'AWS::EC2::SecurityGroup' Properties: GroupDescription: Enable SSH access via port 22 VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !Ref SSHLocation MSKSecurityGroup: Type: 'AWS::EC2::SecurityGroup' Properties: GroupDescription: Enable SSH access via port 22 VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 2181 ToPort: 2181 SourceSecurityGroupId: !GetAtt - KafkaClientInstanceSecurityGroup - GroupId - IpProtocol: tcp FromPort: 9094 ToPort: 9094 SourceSecurityGroupId: !GetAtt - KafkaClientInstanceSecurityGroup - GroupId - IpProtocol: tcp FromPort: 9092 ToPort: 9092 SourceSecurityGroupId: !GetAtt - KafkaClientInstanceSecurityGroup - GroupId KafkaClientEC2Instance: Type: 'AWS::EC2::Instance' Properties: InstanceType: m5.large KeyName: !Ref KeyName IamInstanceProfile: !Ref EC2InstanceProfile AvailabilityZone: !Select - 0 - !GetAZs Ref: 'AWS::Region' SubnetId: !Ref PublicSubnetOne SecurityGroupIds: - !GetAtt - KafkaClientInstanceSecurityGroup - GroupId ImageId: !FindInMap - RegionAMI - !Ref 'AWS::Region' - HVM64 Tags: - Key: Name Value: KafkaClientInstance UserData: !Base64 > #!/bin/bash yum update -y yum install python3.7 -y yum install java-1.8.0-openjdk-devel -y yum erase awscli -y cd /home/ec2-user echo "export PATH=.local/bin:$PATH" >> .bash_profile mkdir kafka mkdir mm cd kafka wget https://archive.apache.org/dist/kafka/2.2.1/kafka_2.12-2.2.1.tgz tar -xzf kafka_2.12-2.2.1.tgz cd /home/ec2-user wget https://bootstrap.pypa.io/get-pip.py su -c "python3.7 get-pip.py --user" -s /bin/sh ec2-user su -c "/home/ec2-user/.local/bin/pip3 install boto3 --user" -s /bin/sh ec2-user su -c "/home/ec2-user/.local/bin/pip3 install awscli --user" -s /bin/sh ec2-user chown -R ec2-user ./kafka chgrp -R ec2-user ./kafka chown -R ec2-user ./mm chgrp -R ec2-user ./mm EC2Role: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Sid: '' Effect: Allow Principal: Service: ec2.amazonaws.com Action: 'sts:AssumeRole' Path: / ManagedPolicyArns: - 'arn:aws:iam::aws:policy/AmazonMSKFullAccess' - 'arn:aws:iam::aws:policy/AWSCloudFormationReadOnlyAccess' EC2InstanceProfile: Type: 'AWS::IAM::InstanceProfile' Properties: InstanceProfileName: EC2MSKCFProfile Roles: - !Ref EC2Role MSKCluster: Type: 'AWS::MSK::Cluster' Properties: BrokerNodeGroupInfo: ClientSubnets: - !Ref PrivateSubnetOne - !Ref PrivateSubnetTwo - !Ref PrivateSubnetThree InstanceType: kafka.m5.large SecurityGroups: - !GetAtt - MSKSecurityGroup - GroupId StorageInfo: EBSStorageInfo: VolumeSize: 2000 ClusterName: MSKCluster EncryptionInfo: EncryptionInTransit: ClientBroker: TLS InCluster: true EnhancedMonitoring: PER_TOPIC_PER_BROKER KafkaVersion: 2.2.1 NumberOfBrokerNodes: 3 Outputs: VPCId: Description: The ID of the VPC created Value: !Ref VPC PublicSubnetOne: Description: The name of the public subnet created Value: !Ref PublicSubnetOne PrivateSubnetOne: Description: The ID of private subnet one created Value: !Ref PrivateSubnetOne PrivateSubnetTwo: Description: The ID of private subnet two created Value: !Ref PrivateSubnetTwo PrivateSubnetThree: Description: The ID of private subnet three created Value: !Ref PrivateSubnetThree MSKSecurityGroupID: Description: The ID of the security group created for the MSK clusters Value: !GetAtt - MSKSecurityGroup - GroupId KafkaClientEC2InstancePublicDNS: Description: The Public DNS for the MirrorMaker EC2 instance Value: !GetAtt - KafkaClientEC2Instance - PublicDnsName MSKClusterArn: Description: The Arn for the MSKMMCluster1 MSK cluster Value: !Ref MSKCluster

{ "AWSTemplateFormatVersion": "2010-09-09", "Parameters": { "KeyName": { "Description": "Name of an existing EC2 KeyPair to enable SSH access to the instance", "Type": "AWS::EC2::KeyPair::KeyName", "ConstraintDescription": "Can contain only ASCII characters." }, "SSHLocation": { "Description": "The IP address range that can be used to SSH to the EC2 instances", "Type": "String", "MinLength": "9", "MaxLength": "18", "Default": "0.0.0.0/0", "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", "ConstraintDescription": "Must be a valid IP CIDR range of the form x.x.x.x/x" } }, "Mappings": { "SubnetConfig": { "VPC": { "CIDR": "10.0.0.0/16" }, "PublicOne": { "CIDR": "10.0.0.0/24" }, "PrivateOne": { "CIDR": "10.0.1.0/24" }, "PrivateTwo": { "CIDR": "10.0.2.0/24" }, "PrivateThree": { "CIDR": "10.0.3.0/24" } }, "RegionAMI": { "us-east-1": { "HVM64": "ami-0c6b1d09930fac512" }, "us-west-2": { "HVM64": "ami-0cb72367e98845d43" } } }, "Resources": { "VPC": { "Type": "AWS::EC2::VPC", "Properties": { "EnableDnsSupport": true, "EnableDnsHostnames": true, "CidrBlock": { "Fn::FindInMap": [ "SubnetConfig", "VPC", "CIDR" ] }, "Tags": [ { "Key": "Name", "Value": "MMVPC" } ] } }, "PublicSubnetOne": { "Type": "AWS::EC2::Subnet", "Properties": { "AvailabilityZone": { "Fn::Select": [ 0, { "Fn::GetAZs": { "Ref": "AWS::Region" } } ] }, "VpcId": { "Ref": "VPC" }, "CidrBlock": { "Fn::FindInMap": [ "SubnetConfig", "PublicOne", "CIDR" ] }, "MapPublicIpOnLaunch": true, "Tags": [ { "Key": "Name", "Value": "MMPublicSubnet" } ] } }, "PrivateSubnetOne": { "Type": "AWS::EC2::Subnet", "Properties": { "AvailabilityZone": { "Fn::Select": [ 0, { "Fn::GetAZs": { "Ref": "AWS::Region" } } ] }, "VpcId": { "Ref": "VPC" }, "CidrBlock": { "Fn::FindInMap": [ "SubnetConfig", "PrivateOne", "CIDR" ] }, "Tags": [ { "Key": "Name", "Value": "MMPrivateSubnetOne" } ] } }, "PrivateSubnetTwo": { "Type": "AWS::EC2::Subnet", "Properties": { "AvailabilityZone": { "Fn::Select": [ 1, { "Fn::GetAZs": { "Ref": "AWS::Region" } } ] }, "VpcId": { "Ref": "VPC" }, "CidrBlock": { "Fn::FindInMap": [ "SubnetConfig", "PrivateTwo", "CIDR" ] }, "Tags": [ { "Key": "Name", "Value": "MMPrivateSubnetTwo" } ] } }, "PrivateSubnetThree": { "Type": "AWS::EC2::Subnet", "Properties": { "AvailabilityZone": { "Fn::Select": [ 2, { "Fn::GetAZs": { "Ref": "AWS::Region" } } ] }, "VpcId": { "Ref": "VPC" }, "CidrBlock": { "Fn::FindInMap": [ "SubnetConfig", "PrivateThree", "CIDR" ] }, "Tags": [ { "Key": "Name", "Value": "MMPrivateSubnetThree" } ] } }, "InternetGateway": { "Type": "AWS::EC2::InternetGateway" }, "GatewayAttachement": { "Type": "AWS::EC2::VPCGatewayAttachment", "Properties": { "VpcId": { "Ref": "VPC" }, "InternetGatewayId": { "Ref": "InternetGateway" } } }, "PublicRouteTable": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { "Ref": "VPC" } } }, "PublicRoute": { "Type": "AWS::EC2::Route", "DependsOn": "GatewayAttachement", "Properties": { "RouteTableId": { "Ref": "PublicRouteTable" }, "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { "Ref": "InternetGateway" } } }, "PublicSubnetOneRouteTableAssociation": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "SubnetId": { "Ref": "PublicSubnetOne" }, "RouteTableId": { "Ref": "PublicRouteTable" } } }, "PrivateRouteTable": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { "Ref": "VPC" } } }, "PrivateSubnetOneRouteTableAssociation": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { "Ref": "PrivateRouteTable" }, "SubnetId": { "Ref": "PrivateSubnetOne" } } }, "PrivateSubnetTwoRouteTableAssociation": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { "Ref": "PrivateRouteTable" }, "SubnetId": { "Ref": "PrivateSubnetTwo" } } }, "PrivateSubnetThreeRouteTableAssociation": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { "Ref": "PrivateRouteTable" }, "SubnetId": { "Ref": "PrivateSubnetThree" } } }, "KafkaClientInstanceSecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "Enable SSH access via port 22", "VpcId": { "Ref": "VPC" }, "SecurityGroupIngress": [ { "IpProtocol": "tcp", "FromPort": 22, "ToPort": 22, "CidrIp": { "Ref": "SSHLocation" } } ] } }, "MSKSecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "Enable SSH access via port 22", "VpcId": { "Ref": "VPC" }, "SecurityGroupIngress": [ { "IpProtocol": "tcp", "FromPort": 2181, "ToPort": 2181, "SourceSecurityGroupId": { "Fn::GetAtt": [ "KafkaClientInstanceSecurityGroup", "GroupId" ] } }, { "IpProtocol": "tcp", "FromPort": 9094, "ToPort": 9094, "SourceSecurityGroupId": { "Fn::GetAtt": [ "KafkaClientInstanceSecurityGroup", "GroupId" ] } }, { "IpProtocol": "tcp", "FromPort": 9092, "ToPort": 9092, "SourceSecurityGroupId": { "Fn::GetAtt": [ "KafkaClientInstanceSecurityGroup", "GroupId" ] } } ] } }, "KafkaClientEC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "InstanceType": "m5.large", "KeyName": { "Ref": "KeyName" }, "IamInstanceProfile": { "Ref": "EC2InstanceProfile" }, "AvailabilityZone": { "Fn::Select": [ 0, { "Fn::GetAZs": { "Ref": "AWS::Region" } } ] }, "SubnetId": { "Ref": "PublicSubnetOne" }, "SecurityGroupIds": [ { "Fn::GetAtt": [ "KafkaClientInstanceSecurityGroup", "GroupId" ] } ], "ImageId": { "Fn::FindInMap": [ "RegionAMI", { "Ref": "AWS::Region" }, "HVM64" ] }, "Tags": [ { "Key": "Name", "Value": "KafkaClientInstance" } ], "UserData": { "Fn::Base64": "#!/bin/bash\nyum update -y \nyum install python3.7 -y\nyum install java-1.8.0-openjdk-devel -y\nyum erase awscli -y\ncd /home/ec2-user\necho \"export PATH=.local/bin:$PATH\" >> .bash_profile\nmkdir kafka\nmkdir mm\ncd kafka\nwget https://archive.apache.org/dist/kafka/2.2.1/kafka_2.12-2.2.1.tgz\ntar -xzf kafka_2.12-2.2.1.tgz\ncd /home/ec2-user\nwget https://bootstrap.pypa.io/get-pip.py\nsu -c \"python3.7 get-pip.py --user\" -s /bin/sh ec2-user\nsu -c \"/home/ec2-user/.local/bin/pip3 install boto3 --user\" -s /bin/sh ec2-user\nsu -c \"/home/ec2-user/.local/bin/pip3 install awscli --user\" -s /bin/sh ec2-user\nchown -R ec2-user ./kafka\nchgrp -R ec2-user ./kafka\nchown -R ec2-user ./mm\nchgrp -R ec2-user ./mm\n" } } }, "EC2Role": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }, "Path": "/", "ManagedPolicyArns": [ "arn:aws:iam::aws:policy/AmazonMSKFullAccess", "arn:aws:iam::aws:policy/AWSCloudFormationReadOnlyAccess" ] } }, "EC2InstanceProfile": { "Type": "AWS::IAM::InstanceProfile", "Properties": { "InstanceProfileName": "EC2MSKCFProfile", "Roles": [ { "Ref": "EC2Role" } ] } }, "MSKCluster": { "Type": "AWS::MSK::Cluster", "Properties": { "BrokerNodeGroupInfo": { "ClientSubnets": [ { "Ref": "PrivateSubnetOne" }, { "Ref": "PrivateSubnetTwo" }, { "Ref": "PrivateSubnetThree" } ], "InstanceType": "kafka.m5.large", "SecurityGroups": [ { "Fn::GetAtt": [ "MSKSecurityGroup", "GroupId" ] } ], "StorageInfo": { "EBSStorageInfo": { "VolumeSize": 2000 } } }, "ClusterName": "MSKCluster", "EncryptionInfo": { "EncryptionInTransit": { "ClientBroker": "TLS", "InCluster": true } }, "EnhancedMonitoring": "PER_TOPIC_PER_BROKER", "KafkaVersion": "2.2.1", "NumberOfBrokerNodes": 3 } } }, "Outputs": { "VPCId": { "Description": "The ID of the VPC created", "Value": { "Ref": "VPC" } }, "PublicSubnetOne": { "Description": "The name of the public subnet created", "Value": { "Ref": "PublicSubnetOne" } }, "PrivateSubnetOne": { "Description": "The ID of private subnet one created", "Value": { "Ref": "PrivateSubnetOne" } }, "PrivateSubnetTwo": { "Description": "The ID of private subnet two created", "Value": { "Ref": "PrivateSubnetTwo" } }, "PrivateSubnetThree": { "Description": "The ID of private subnet three created", "Value": { "Ref": "PrivateSubnetThree" } }, "MSKSecurityGroupID": { "Description": "The ID of the security group created for the MSK clusters", "Value": { "Fn::GetAtt": [ "MSKSecurityGroup", "GroupId" ] } }, "KafkaClientEC2InstancePublicDNS": { "Description": "The Public DNS for the MirrorMaker EC2 instance", "Value": { "Fn::GetAtt": [ "KafkaClientEC2Instance", "PublicDnsName" ] } }, "MSKClusterArn": { "Description": "The Arn for the MSKMMCluster1 MSK cluster", "Value": { "Ref": "MSKCluster" } } } }

创建两个 MSK 群集以便用于 Apache MirrorMaker

此 YAML 显示了如何为 MirrorMaker 设置两个 MSK 群集。它还设置 Amazon VPC、子网、安全组和此示例所必需的 IAM 角色。此外,它会创建一个 EC2 实例,该实例具有 Apache Kafka、Java 和 AWS CLI。您可以使用此 EC2 实例运行 Apache Kafka 工具,包括 MirrorMaker。您必须手动创建 MirrorMaker 配置文件。

AWSTemplateFormatVersion: 2010-09-09 Parameters: KeyName: Description: The name of an existing EC2 KeyPair to enable SSH access to the instance. Type: 'AWS::EC2::KeyPair::KeyName' ConstraintDescription: Can contain only ASCII characters. SSHLocation: Description: The IP address range that can be used to SSH to the EC2 instances. Type: String MinLength: '9' MaxLength: '18' Default: 0.0.0.0/0 AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})' ConstraintDescription: Must be a valid IP CIDR range of the form x.x.x.x/x Mappings: SubnetConfig: VPC: CIDR: 10.0.0.0/16 PublicOne: CIDR: 10.0.0.0/24 PrivateOne: CIDR: 10.0.1.0/24 PrivateTwo: CIDR: 10.0.2.0/24 PrivateThree: CIDR: 10.0.3.0/24 RegionAMI: us-east-1: HVM64: ami-0c6b1d09930fac512 us-west-2: HVM64: ami-0cb72367e98845d43 Resources: VPC: Type: 'AWS::EC2::VPC' Properties: EnableDnsSupport: true EnableDnsHostnames: true CidrBlock: !FindInMap - SubnetConfig - VPC - CIDR Tags: - Key: Name Value: MMVPC PublicSubnetOne: Type: 'AWS::EC2::Subnet' Properties: AvailabilityZone: !Select - 0 - !GetAZs Ref: 'AWS::Region' VpcId: !Ref VPC CidrBlock: !FindInMap - SubnetConfig - PublicOne - CIDR MapPublicIpOnLaunch: true Tags: - Key: Name Value: MMPublicSubnet PrivateSubnetOne: Type: 'AWS::EC2::Subnet' Properties: AvailabilityZone: !Select - 0 - !GetAZs Ref: 'AWS::Region' VpcId: !Ref VPC CidrBlock: !FindInMap - SubnetConfig - PrivateOne - CIDR Tags: - Key: Name Value: MMPrivateSubnetOne PrivateSubnetTwo: Type: 'AWS::EC2::Subnet' Properties: AvailabilityZone: !Select - 1 - !GetAZs Ref: 'AWS::Region' VpcId: !Ref VPC CidrBlock: !FindInMap - SubnetConfig - PrivateTwo - CIDR Tags: - Key: Name Value: MMPrivateSubnetTwo PrivateSubnetThree: Type: 'AWS::EC2::Subnet' Properties: AvailabilityZone: !Select - 2 - !GetAZs Ref: 'AWS::Region' VpcId: !Ref VPC CidrBlock: !FindInMap - SubnetConfig - PrivateThree - CIDR Tags: - Key: Name Value: MMPrivateSubnetThree InternetGateway: Type: 'AWS::EC2::InternetGateway' GatewayAttachement: Type: 'AWS::EC2::VPCGatewayAttachment' Properties: VpcId: !Ref VPC InternetGatewayId: !Ref InternetGateway PublicRouteTable: Type: 'AWS::EC2::RouteTable' Properties: VpcId: !Ref VPC PublicRoute: Type: 'AWS::EC2::Route' DependsOn: GatewayAttachement Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway PublicSubnetOneRouteTableAssociation: Type: 'AWS::EC2::SubnetRouteTableAssociation' Properties: SubnetId: !Ref PublicSubnetOne RouteTableId: !Ref PublicRouteTable PrivateRouteTable: Type: 'AWS::EC2::RouteTable' Properties: VpcId: !Ref VPC PrivateSubnetOneRouteTableAssociation: Type: 'AWS::EC2::SubnetRouteTableAssociation' Properties: RouteTableId: !Ref PrivateRouteTable SubnetId: !Ref PrivateSubnetOne PrivateSubnetTwoRouteTableAssociation: Type: 'AWS::EC2::SubnetRouteTableAssociation' Properties: RouteTableId: !Ref PrivateRouteTable SubnetId: !Ref PrivateSubnetTwo PrivateSubnetThreeRouteTableAssociation: Type: 'AWS::EC2::SubnetRouteTableAssociation' Properties: RouteTableId: !Ref PrivateRouteTable SubnetId: !Ref PrivateSubnetThree MMInstanceSecurityGroup: Type: 'AWS::EC2::SecurityGroup' Properties: GroupDescription: Enable SSH access via port 22 VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !Ref SSHLocation MSKSecurityGroup: Type: 'AWS::EC2::SecurityGroup' Properties: GroupDescription: Enable SSH access via port 22 VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 2181 ToPort: 2181 SourceSecurityGroupId: !GetAtt - MMInstanceSecurityGroup - GroupId - IpProtocol: tcp FromPort: 9094 ToPort: 9094 SourceSecurityGroupId: !GetAtt - MMInstanceSecurityGroup - GroupId - IpProtocol: tcp FromPort: 9092 ToPort: 9092 SourceSecurityGroupId: !GetAtt - MMInstanceSecurityGroup - GroupId MMEC2Instance: Type: 'AWS::EC2::Instance' Properties: InstanceType: m5.large KeyName: !Ref KeyName IamInstanceProfile: !Ref EC2InstanceProfile AvailabilityZone: !Select - 0 - !GetAZs Ref: 'AWS::Region' SubnetId: !Ref PublicSubnetOne SecurityGroupIds: - !GetAtt - MMInstanceSecurityGroup - GroupId ImageId: !FindInMap - RegionAMI - !Ref 'AWS::Region' - HVM64 Tags: - Key: Name Value: MMInstance UserData: !Base64 > #!/bin/bash yum update -y yum install python3.7 -y yum install java-1.8.0-openjdk-devel -y yum erase awscli -y cd /home/ec2-user echo "export PATH=.local/bin:$PATH" >> .bash_profile mkdir kafka mkdir mm cd kafka wget https://archive.apache.org/dist/kafka/2.2.1/kafka_2.12-2.2.1.tgz tar -xzf kafka_2.12-2.2.1.tgz cd /home/ec2-user wget https://bootstrap.pypa.io/get-pip.py su -c "python3.7 get-pip.py --user" -s /bin/sh ec2-user su -c "/home/ec2-user/.local/bin/pip3 install boto3 --user" -s /bin/sh ec2-user su -c "/home/ec2-user/.local/bin/pip3 install awscli --user" -s /bin/sh ec2-user chown -R ec2-user ./kafka chgrp -R ec2-user ./kafka chown -R ec2-user ./mm chgrp -R ec2-user ./mm EC2Role: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Sid: '' Effect: Allow Principal: Service: ec2.amazonaws.com Action: 'sts:AssumeRole' Path: / ManagedPolicyArns: - 'arn:aws:iam::aws:policy/AmazonMSKFullAccess' - 'arn:aws:iam::aws:policy/AWSCloudFormationReadOnlyAccess' EC2InstanceProfile: Type: 'AWS::IAM::InstanceProfile' Properties: InstanceProfileName: EC2MSKCFProfile Roles: - !Ref EC2Role MSKMMCluster1: Type: 'AWS::MSK::Cluster' Properties: BrokerNodeGroupInfo: ClientSubnets: - !Ref PrivateSubnetOne - !Ref PrivateSubnetTwo - !Ref PrivateSubnetThree InstanceType: kafka.m5.large SecurityGroups: - !GetAtt - MSKSecurityGroup - GroupId StorageInfo: EBSStorageInfo: VolumeSize: 2000 ClusterName: MSKMMCluster1 EncryptionInfo: EncryptionInTransit: ClientBroker: TLS InCluster: true EnhancedMonitoring: PER_TOPIC_PER_BROKER KafkaVersion: 2.2.1 NumberOfBrokerNodes: 3 MSKMMCluster2: Type: 'AWS::MSK::Cluster' Properties: BrokerNodeGroupInfo: ClientSubnets: - !Ref PrivateSubnetOne - !Ref PrivateSubnetTwo - !Ref PrivateSubnetThree InstanceType: kafka.m5.large SecurityGroups: - !GetAtt - MSKSecurityGroup - GroupId StorageInfo: EBSStorageInfo: VolumeSize: 2000 ClusterName: MSKMMCluster2 EncryptionInfo: EncryptionInTransit: ClientBroker: TLS InCluster: true EnhancedMonitoring: PER_TOPIC_PER_BROKER KafkaVersion: 2.2.1 NumberOfBrokerNodes: 3 Outputs: VPCId: Description: The ID of the VPC created Value: !Ref VPC PublicSubnetOne: Description: The name of the public subnet created Value: !Ref PublicSubnetOne PrivateSubnetOne: Description: The ID of private subnet one created Value: !Ref PrivateSubnetOne PrivateSubnetTwo: Description: The ID of private subnet two created Value: !Ref PrivateSubnetTwo PrivateSubnetThree: Description: The ID of private subnet three created Value: !Ref PrivateSubnetThree MSKSecurityGroupID: Description: The ID of the security group created for the MSK clusters Value: !GetAtt - MSKSecurityGroup - GroupId MMEC2InstancePublicDNS: Description: The Public DNS for the MirrorMaker EC2 instance Value: !GetAtt - MMEC2Instance - PublicDnsName MSKMMCluster1Arn: Description: The Arn for the MSKMMCluster1 MSK cluster Value: !Ref MSKMMCluster1 MSKMMCluster2Arn: Description: The Arn for the MSKMMCluster2 MSK cluster Value: !Ref MSKMMCluster2

{ "AWSTemplateFormatVersion": "2010-09-09", "Parameters": { "KeyName": { "Description": "The name of an existing EC2 KeyPair to enable SSH access to the instance.", "Type": "AWS::EC2::KeyPair::KeyName", "ConstraintDescription": "Can contain only ASCII characters." }, "SSHLocation": { "Description": "The IP address range that can be used to SSH to the EC2 instances.", "Type": "String", "MinLength": "9", "MaxLength": "18", "Default": "0.0.0.0/0", "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", "ConstraintDescription": "Must be a valid IP CIDR range of the form x.x.x.x/x" } }, "Mappings": { "SubnetConfig": { "VPC": { "CIDR": "10.0.0.0/16" }, "PublicOne": { "CIDR": "10.0.0.0/24" }, "PrivateOne": { "CIDR": "10.0.1.0/24" }, "PrivateTwo": { "CIDR": "10.0.2.0/24" }, "PrivateThree": { "CIDR": "10.0.3.0/24" } }, "RegionAMI": { "us-east-1": { "HVM64": "ami-0c6b1d09930fac512" }, "us-west-2": { "HVM64": "ami-0cb72367e98845d43" } } }, "Resources": { "VPC": { "Type": "AWS::EC2::VPC", "Properties": { "EnableDnsSupport": true, "EnableDnsHostnames": true, "CidrBlock": { "Fn::FindInMap": [ "SubnetConfig", "VPC", "CIDR" ] }, "Tags": [ { "Key": "Name", "Value": "MMVPC" } ] } }, "PublicSubnetOne": { "Type": "AWS::EC2::Subnet", "Properties": { "AvailabilityZone": { "Fn::Select": [ 0, { "Fn::GetAZs": { "Ref": "AWS::Region" } } ] }, "VpcId": { "Ref": "VPC" }, "CidrBlock": { "Fn::FindInMap": [ "SubnetConfig", "PublicOne", "CIDR" ] }, "MapPublicIpOnLaunch": true, "Tags": [ { "Key": "Name", "Value": "MMPublicSubnet" } ] } }, "PrivateSubnetOne": { "Type": "AWS::EC2::Subnet", "Properties": { "AvailabilityZone": { "Fn::Select": [ 0, { "Fn::GetAZs": { "Ref": "AWS::Region" } } ] }, "VpcId": { "Ref": "VPC" }, "CidrBlock": { "Fn::FindInMap": [ "SubnetConfig", "PrivateOne", "CIDR" ] }, "Tags": [ { "Key": "Name", "Value": "MMPrivateSubnetOne" } ] } }, "PrivateSubnetTwo": { "Type": "AWS::EC2::Subnet", "Properties": { "AvailabilityZone": { "Fn::Select": [ 1, { "Fn::GetAZs": { "Ref": "AWS::Region" } } ] }, "VpcId": { "Ref": "VPC" }, "CidrBlock": { "Fn::FindInMap": [ "SubnetConfig", "PrivateTwo", "CIDR" ] }, "Tags": [ { "Key": "Name", "Value": "MMPrivateSubnetTwo" } ] } }, "PrivateSubnetThree": { "Type": "AWS::EC2::Subnet", "Properties": { "AvailabilityZone": { "Fn::Select": [ 2, { "Fn::GetAZs": { "Ref": "AWS::Region" } } ] }, "VpcId": { "Ref": "VPC" }, "CidrBlock": { "Fn::FindInMap": [ "SubnetConfig", "PrivateThree", "CIDR" ] }, "Tags": [ { "Key": "Name", "Value": "MMPrivateSubnetThree" } ] } }, "InternetGateway": { "Type": "AWS::EC2::InternetGateway" }, "GatewayAttachement": { "Type": "AWS::EC2::VPCGatewayAttachment", "Properties": { "VpcId": { "Ref": "VPC" }, "InternetGatewayId": { "Ref": "InternetGateway" } } }, "PublicRouteTable": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { "Ref": "VPC" } } }, "PublicRoute": { "Type": "AWS::EC2::Route", "DependsOn": "GatewayAttachement", "Properties": { "RouteTableId": { "Ref": "PublicRouteTable" }, "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { "Ref": "InternetGateway" } } }, "PublicSubnetOneRouteTableAssociation": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "SubnetId": { "Ref": "PublicSubnetOne" }, "RouteTableId": { "Ref": "PublicRouteTable" } } }, "PrivateRouteTable": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { "Ref": "VPC" } } }, "PrivateSubnetOneRouteTableAssociation": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { "Ref": "PrivateRouteTable" }, "SubnetId": { "Ref": "PrivateSubnetOne" } } }, "PrivateSubnetTwoRouteTableAssociation": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { "Ref": "PrivateRouteTable" }, "SubnetId": { "Ref": "PrivateSubnetTwo" } } }, "PrivateSubnetThreeRouteTableAssociation": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { "Ref": "PrivateRouteTable" }, "SubnetId": { "Ref": "PrivateSubnetThree" } } }, "MMInstanceSecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "Enable SSH access via port 22", "VpcId": { "Ref": "VPC" }, "SecurityGroupIngress": [ { "IpProtocol": "tcp", "FromPort": 22, "ToPort": 22, "CidrIp": { "Ref": "SSHLocation" } } ] } }, "MSKSecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "Enable SSH access via port 22", "VpcId": { "Ref": "VPC" }, "SecurityGroupIngress": [ { "IpProtocol": "tcp", "FromPort": 2181, "ToPort": 2181, "SourceSecurityGroupId": { "Fn::GetAtt": [ "MMInstanceSecurityGroup", "GroupId" ] } }, { "IpProtocol": "tcp", "FromPort": 9094, "ToPort": 9094, "SourceSecurityGroupId": { "Fn::GetAtt": [ "MMInstanceSecurityGroup", "GroupId" ] } }, { "IpProtocol": "tcp", "FromPort": 9092, "ToPort": 9092, "SourceSecurityGroupId": { "Fn::GetAtt": [ "MMInstanceSecurityGroup", "GroupId" ] } } ] } }, "MMEC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "InstanceType": "m5.large", "KeyName": { "Ref": "KeyName" }, "IamInstanceProfile": { "Ref": "EC2InstanceProfile" }, "AvailabilityZone": { "Fn::Select": [ 0, { "Fn::GetAZs": { "Ref": "AWS::Region" } } ] }, "SubnetId": { "Ref": "PublicSubnetOne" }, "SecurityGroupIds": [ { "Fn::GetAtt": [ "MMInstanceSecurityGroup", "GroupId" ] } ], "ImageId": { "Fn::FindInMap": [ "RegionAMI", { "Ref": "AWS::Region" }, "HVM64" ] }, "Tags": [ { "Key": "Name", "Value": "MMInstance" } ], "UserData": { "Fn::Base64": "#!/bin/bash\nyum update -y \nyum install python3.7 -y\nyum install java-1.8.0-openjdk-devel -y\nyum erase awscli -y\ncd /home/ec2-user\necho \"export PATH=.local/bin:$PATH\" >> .bash_profile\nmkdir kafka\nmkdir mm\ncd kafka\nwget https://archive.apache.org/dist/kafka/2.2.1/kafka_2.12-2.2.1.tgz\ntar -xzf kafka_2.12-2.2.1.tgz\ncd /home/ec2-user\nwget https://bootstrap.pypa.io/get-pip.py\nsu -c \"python3.7 get-pip.py --user\" -s /bin/sh ec2-user\nsu -c \"/home/ec2-user/.local/bin/pip3 install boto3 --user\" -s /bin/sh ec2-user\nsu -c \"/home/ec2-user/.local/bin/pip3 install awscli --user\" -s /bin/sh ec2-user\nchown -R ec2-user ./kafka\nchgrp -R ec2-user ./kafka\nchown -R ec2-user ./mm\nchgrp -R ec2-user ./mm\n" } } }, "EC2Role": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }, "Path": "/", "ManagedPolicyArns": [ "arn:aws:iam::aws:policy/AmazonMSKFullAccess", "arn:aws:iam::aws:policy/AWSCloudFormationReadOnlyAccess" ] } }, "EC2InstanceProfile": { "Type": "AWS::IAM::InstanceProfile", "Properties": { "InstanceProfileName": "EC2MSKCFProfile", "Roles": [ { "Ref": "EC2Role" } ] } }, "MSKMMCluster1": { "Type": "AWS::MSK::Cluster", "Properties": { "BrokerNodeGroupInfo": { "ClientSubnets": [ { "Ref": "PrivateSubnetOne" }, { "Ref": "PrivateSubnetTwo" }, { "Ref": "PrivateSubnetThree" } ], "InstanceType": "kafka.m5.large", "SecurityGroups": [ { "Fn::GetAtt": [ "MSKSecurityGroup", "GroupId" ] } ], "StorageInfo": { "EBSStorageInfo": { "VolumeSize": 2000 } } }, "ClusterName": "MSKMMCluster1", "EncryptionInfo": { "EncryptionInTransit": { "ClientBroker": "TLS", "InCluster": true } }, "EnhancedMonitoring": "PER_TOPIC_PER_BROKER", "KafkaVersion": "2.2.1", "NumberOfBrokerNodes": 3 } }, "MSKMMCluster2": { "Type": "AWS::MSK::Cluster", "Properties": { "BrokerNodeGroupInfo": { "ClientSubnets": [ { "Ref": "PrivateSubnetOne" }, { "Ref": "PrivateSubnetTwo" }, { "Ref": "PrivateSubnetThree" } ], "InstanceType": "kafka.m5.large", "SecurityGroups": [ { "Fn::GetAtt": [ "MSKSecurityGroup", "GroupId" ] } ], "StorageInfo": { "EBSStorageInfo": { "VolumeSize": 2000 } } }, "ClusterName": "MSKMMCluster2", "EncryptionInfo": { "EncryptionInTransit": { "ClientBroker": "TLS", "InCluster": true } }, "EnhancedMonitoring": "PER_TOPIC_PER_BROKER", "KafkaVersion": "2.2.1", "NumberOfBrokerNodes": 3 } } }, "Outputs": { "VPCId": { "Description": "The ID of the VPC created", "Value": { "Ref": "VPC" } }, "PublicSubnetOne": { "Description": "The name of the public subnet created", "Value": { "Ref": "PublicSubnetOne" } }, "PrivateSubnetOne": { "Description": "The ID of private subnet one created", "Value": { "Ref": "PrivateSubnetOne" } }, "PrivateSubnetTwo": { "Description": "The ID of private subnet two created", "Value": { "Ref": "PrivateSubnetTwo" } }, "PrivateSubnetThree": { "Description": "The ID of private subnet three created", "Value": { "Ref": "PrivateSubnetThree" } }, "MSKSecurityGroupID": { "Description": "The ID of the security group created for the MSK clusters", "Value": { "Fn::GetAtt": [ "MSKSecurityGroup", "GroupId" ] } }, "MMEC2InstancePublicDNS": { "Description": "The Public DNS for the MirrorMaker EC2 instance", "Value": { "Fn::GetAtt": [ "MMEC2Instance", "PublicDnsName" ] } }, "MSKMMCluster1Arn": { "Description": "The Arn for the MSKMMCluster1 MSK cluster", "Value": { "Ref": "MSKMMCluster1" } }, "MSKMMCluster2Arn": { "Description": "The Arn for the MSKMMCluster2 MSK cluster", "Value": { "Ref": "MSKMMCluster2" } } } }