本文共 18365 字,大约阅读时间需要 61 分钟。
如果要为基础设施用例构建单独VPC模板,则可以使用Parameters、Conditions、Mapping和Outputs让现有模板更加灵活。
在本系列文章的,我们探讨了如何使用基础设施即代码(特别是CloudFormation)来创建和维护AWS VPC。我们创建的CloudFormation模板提供了简单、可重用的组件,我们可以用它创建简单VPC。
但是,这个模板还不够灵活。我们希望可以有一个这样的模板,可以用它为开发、测试和生产环境构建具有不同数量子网的VPC。我们想要一些在需要进行演示或POC时能够用于创建公共子网的东西,或者,我们可能希望使用NAT实例而不是NAT网关。
我们可以通过使用参数、条件、映射和输出让现有的模板变得更灵活,而不是为各种情况创建单独的模板。这是本系列的第二篇文章,所以你应该已经阅读了第一篇文章,并对模板已经很熟悉了。在这篇文章中,我将从增强开始说起。
本文涉及的CloudFormation模板可以在上找到,读者可以下载、修改和使用它。
可用区域:AWS已经让在给定地区内利用多个可用区域(AZ)变得更容易,而且成本更低。简单地说,你可以将可用区域视为一个巨大的独立数据中心。给定地区内的AZ通过高速、低延迟的私有链接相互连接。它们彼此足够接近,支持同步通信,但相隔得又足够远,可以减轻自然灾害、停电等事件所带来的影响。确切地说,我们并不知道究竟相隔多远,但其实也没必要知道。
以最低的成本实现基本的高可用性是使用两个AZ。有时候单个AZ更适合简单的情况,例如演示或POC。其他时候需要三个AZ来获得略微改善的高可用性。所以,接下来让我们来调整模板,让它支持可变的AZ数量。
使用第一篇文章中的模板,在“Resources”部分上方添加以下内容。
Parameters: NumberOfAZs: Type: Number AllowedValues: - 1 - 2 - 3 Default: 2 Description: How many Availability Zones do you wish to utilize?
YAML基础知识:YAML使用双空格缩进表示层次结构(没有制表符!)。短划线“-”表示“序列”,即属于同一组的多个值。参数通常位于资源上方,但从技术上讲,它们可以被放在模板中的任何位置。
NumberOfAZs:这个条目定义了模板的输入参数。在AWS管理控制台中使用这个模板创建资源栈时,UI将提示输入“NumberOfAZs”,输入框旁边是描述内容。因为我们提供了“AllowedValues”,所以输入字段将是一个下拉框,其中包含1、2和3这三个选项。如果我们不做选择,将默认使用2。定义了有效的参数类型,在这里我们可以使用Number或String。
我们的目标是能够使用这个模板在任意地区创建资源栈。在撰写本文时,大多数地区现在至少有三个可用区域,但有些地域则没有(蒙特利尔、孟买、北京、首尔只有两个)。在这些地区选择使用三个AZ将导致错误。限制模板的灵活性以便避免这种少数情况下才会发生的尴尬错误,这样值不值得取决于你。
CLI用法:通过AWS命令行界面(CLI)创建资源栈时,输入参数仍然有用。如果有必要,我们可以为参数提供值,或者使用默认值。如果提供的值超出了允许的范围将会出现错误。
在指定所需的AZ数量之后,我们需要修改模板的其余部分,以便让CloudFormation构建我们想要的子网。
为了让CloudFormation可以构建一个、两个或三个子网,我们将定义一些可以在Resources部分使用的“条件”。在Parameters部分下方和Resources部分上方添加以下代码:
Conditions: BuildPublicB: !Not [ !Equals [ !Ref NumberOfAZs, 1 ]] BuildPublicC: !Equals [ !Ref NumberOfAZs, 3 ]
条件是布尔值(true/false)表达式,我们将在模板中使用它们。这里我们创建两个条件,一个用于指示我们是否要构建“B”子网,一个用于指示我们是否要构建“C”子网。由于“1”是NumberOfAZs允许的最小数量,因此我们始终都会构建“A”子网。
BuildPublicB:这个表达式检查所选的NumberOfAZs是否为1以外的值。因为CloudFormation中没有大于和小于内联函数,所以我们将使用!Equals函数来引用输入参数并检查值是否与“ 1”相等。!Not用于获得相反的结果(即false变true,true变false)。布尔值结果将被保存在BuildPublicB中,然后在模板中的其他位置引用它。
BuildPublicC:这个表达式更简单,NumberOfAZs要么是“3”(我们允许的最大值)要么不是。如果它是true,我们将只构建PublicSubnetC。
现在,我们已经明确定义了要创建哪些子网的条件,接下来可以用它们来创建实际的资源。
在第一篇文章的原始模板中,我们使用以下代码创建了PublicSubnetB:
PublicSubnetB: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: 10.1.20.0/24 AvailabilityZone: !Select [ 1, !GetAZs ] # Get the second AZ in the list Tags: - Key: Name Value: !Sub ${AWS::StackName}-Public-B
注意下面的替换代码,特别是新的“Condition”属性:
PublicSubnetB: Type: AWS::EC2::Subnet Condition: BuildPublicB Properties: VpcId: !Ref VPC CidrBlock: 10.1.20.0/24 AvailabilityZone: !Select [ 1, !GetAZs ] # Get the second AZ in the list Tags: - Key: Name Value: !Sub ${AWS::StackName}-Public-B
条件属性就是任意CloudFormation资源的可用选项。也就是说,“只有在BuildPublicB条件为true时才创建这个资源”。当它为false时,就会忽略资源的创建——将不会有PublicSubnetB。
现在让我们添加第三个公共子网,但仅当BuildPublicC条件为true时:
PublicSubnetC: Type: AWS::EC2::Subnet Condition: BuildPublicC Properties: VpcId: !Ref VPC CidrBlock: 10.1.30.0/24 AvailabilityZone: !Select [ 2, !GetAZs ] # Get the third AZ in the list Tags: - Key: Name Value: !Sub ${AWS::StackName}-Public-C
你可能想知道是否有一种方法可以直接在资源上内联表达式条件,而不是使用单独的“Coditions”部分。在撰写本文时,没有。但是在编写了很多模板之后,我发现逻辑表达式的计算与用法的简单解耦其实是有好处的。毕竟,如果使用内联表达式,这些模板可能会变得非常复杂,例如你在此处看到的AvailabilityZone或Tag/Value。
最后一步,为不同数量的公共子网调整子网的路由表关联。请注意以下的条件属性:
PublicSubnetBRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Condition: BuildPublicB Properties: SubnetId: !Ref PublicSubnetB RouteTableId: !Ref PublicRouteTable PublicSubnetCRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Condition: BuildPublicC Properties: SubnetId: !Ref PublicSubnetC RouteTableId: !Ref PublicRouteTable
这里没有显示PublicSubnetA的关联。因为它始终存在于资源栈中,因此不需要条件属性。同样,PublicRouteTable也是必需存在的。我们的资源栈将根据输入参数创建一个、两个或三个公共子网。接下来让我们来看看私有子网……
我们假设要将这个模板生成的VPC用于一些面向公众的快速演示。在这样的VPC中拥有私有子网或NAT有点超出了实际,而且需要更长的时间来创建。让我们添加一个参数,可以用它来指定纯公共子网。在“Parameters”部分添加:
PrivateSubnets: Type: String AllowedValues: - True - False Default: True Description: Do you want to create private subnets in addition to public subnets?
我们定义了一个输入参数来控制是否创建了任何私有子网。我希望CloudFormation为这样的情况提供“布尔”输入类型,但现在我们不得不是有只接受“True”或“False”的String类型。
让我们在Coditions部分中添加以下这些条件,用于计算输入值。这个会有点复杂:
BuildPrivateSubnets: !Equals [ !Ref PrivateSubnets, True ] BuildPrivateA: !Equals [ !Ref PrivateSubnets, True ] BuildPrivateB: !And[!Not[!Equals[!Ref NumberOfAZs,1]],!Equals[!Ref PrivateSubnets,True]] BuildPrivateC: !And[!Equals[!Ref NumberOfAZs,3],!Equals[!Ref PrivateSubnets, True]]
BuildPrivateSubnets:这是一个直接用于表达输入参数的简单条件。有时候,我们会根据是否存在私有子网(即NAT)来构建一些东西。
BuildPrivateA:“BuildPrivateSubnets”的同义词,不是绝对必需的,但它的代码看起来非常干净。但有点遗憾的是,我们无法在一个条件中引用另一个条件。
BuildPrivateB:这里的逻辑是“如果我们想要使用多个AZ并且想要构建私有子网,那么就构建PrivateSubnetB”。
BuildPrivateC:这里的逻辑是“如果我们想要使用三个AZ,并且想要构建私有子网,那么就构建PrivateSubnetC”。
现在,我们可以将私有子网定义从原始模板转换为使用条件属性,如下所示:
PrivateSubnetA: Type: AWS::EC2::Subnet Condition: BuildPrivateA Properties: VpcId: !Ref VPC CidrBlock: 10.1.50.0/24 AvailabilityZone: !Select [ 0, !GetAZs ] # Get the first AZ in the list Tags: - Key: Name Value: !Sub ${AWS::StackName}-Private-A PrivateSubnetB: Type: AWS::EC2::Subnet Condition: BuildPrivateB Properties: VpcId: !Ref VPC CidrBlock: 10.1.60.0/24 AvailabilityZone: !Select [ 1, !GetAZs ] # Get the second AZ in the list Tags: - Key: Name Value: !Sub ${AWS::StackName}-Private-B PrivateSubnetC: Type: AWS::EC2::Subnet Condition: BuildPrivateC Properties: VpcId: !Ref VPC CidrBlock: 10.1.70.0/24 AvailabilityZone: !Select [ 2, !GetAZs ] # Get the third AZ in the list Tags: - Key: Name Value: !Sub ${AWS::StackName}-Private-C
我们所做的修改就是在原始模板中添加了条件属性。另外,我们还添加了PrivateSubnetC,它可以很容易地从PrivateSubnetA和PrivateSubnetB的定义中克隆出来。
子网路由表关联也需要修改。如果没有子网,则不需要子网关联:
PrivateSubnetARouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Condition: BuildPrivateA Properties: SubnetId: !Ref PrivateSubnetA RouteTableId: !Ref PrivateRouteTable PrivateSubnetBRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Condition: BuildPrivateB Properties: SubnetId: !Ref PrivateSubnetB RouteTableId: !Ref PrivateRouteTable PrivateSubnetCRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Condition: BuildPrivateC Properties: SubnetId: !Ref PrivateSubnetC RouteTableId: !Ref PrivateRouteTable
我们的模板现在可以有条件地创建私有子网,因此我们需要相应地调整NAT网关和路由表条目。首先是NAT网关,如果我们不构建私有子网,就没有理由创建它或与其关联的弹性IP地址:
# A NAT Gateway will be built and used if the user selected Private subnets and a Gateway instead of an EC2 instance. NATGateway: Type: AWS::EC2::NatGateway Condition: BuildPrivateSubnets Properties: AllocationId: !GetAtt ElasticIPAddress.AllocationId SubnetId: !Ref PublicSubnetA Tags: - Key: Name Value: !Sub NAT-${AWS::StackName} ElasticIPAddress: Type: AWS::EC2::EIP Condition: BuildPrivateSubnets Properties: Domain: VPC
原始模板的唯一变化是条件属性,我们希望只在选择了要构建私有子网时才构建这些东西。
接下来,条件表明我们可能不需要私有路由表或路由表的条目:
# Here is a private route table: PrivateRouteTable: Type: AWS::EC2::RouteTable Condition: BuildPrivateSubnets Properties: VpcId: !Ref VPC Tags: - Key: Name Value: Private PrivateRoute1: # Private route table can access web via NAT (created below) Type: AWS::EC2::Route Condition: BuildPrivateSubnets Properties: RouteTableId: !Ref PrivateRouteTable DestinationCidrBlock: 0.0.0.0/0 # Route traffic through the NAT Gateway: NatGatewayId: !Ref NATGateway
这个时候,当“BuildPrivateSubnets”为false时,我们将忽略任何私有子网、路由表或NAT的创建。我们的模板能够根据参数输入创建一到六个子网。相当灵活,而且要实现这一点不需要太多的工作量。
为了进一步提高灵活性,我们让模板支持NAT网关。内建的托管服务非常适合用于生产环境,但用于POC可能就有点贵了。在出现NAT网关之前,我们通过常规的EC2实例来提供NAT支持,不管哪种方式都有其优缺点。所以,出于实验的目的,我们添加一个参数,以便提供这种选择:
NATType: Type: String AllowedValues: - \u0026quot;EC2 NAT Instance\u0026quot; - \u0026quot;NAT Gateway\u0026quot; Default: \u0026quot;NAT Gateway\u0026quot; Description: What type of NAT to use for private instances to communicate with the internet. A single EC2 instance can be used as a NAT, or you can use the AWS NAT Gateway (managed, scalable, more expensive). This setting will be IGNORED if you do not build private subnets.
并在Conditions部分添加以下内容:
BuildNATGateway: !And[!Equals[!Ref PrivateSubnets,True],!Equals[!Ref NATType, \u0026quot;NAT Gateway\u0026quot;]] BuildNATInstance: !And[!Equals[!Ref PrivateSubnets,True],!Equals[!Ref NATType, \u0026quot;EC2 NAT Instance\u0026quot;]]
第一行的意思是“如果我们要构建私有子网和NAT网关,那么就构建NAT网关”。第二行的意思是“如果我们要构建私有子网,并选择使用EC2实例,那么就构建一个EC2实例作为NAT”。
我们之前描述的NAT网关/弹性IP地址条件也需要做出调整,我们现在想要根据BuildNATGateway条件控制它们的创建:
# A NAT Gateway will be built and used if the user selected Private subnets and a Gateway instead of an EC2 instance. NATGateway: Type: AWS::EC2::NatGateway Condition: BuildNATGateway Properties: AllocationId: !GetAtt ElasticIPAddress.AllocationId SubnetId: !Ref PublicSubnetA Tags: - Key: Name Value: !Sub NAT-${AWS::StackName} ElasticIPAddress: Type: AWS::EC2::EIP Condition: BuildNATGateway Properties: Domain: VPC
基于EC2的NAT实例需要一些新的构造。首先,EC2实例需要AMI,但不同地区的AMI ID值是不一样的。为了可以在任意地区使用这个模板,我们在Condition部分之前将以下的Mappings部分添加到模板中(从技术上说,这些部分的放置顺序是随意的,有些人喜欢把它放在底部附近):
Mappings: # This is the Amazon Linux 2 AMI. Adjust these values as needed, they can change a few times per year: AmazonLinuxAMI: us-east-1: AMI: ami-04681a1dbd79675a5 # N Virginia us-east-2: AMI: ami-0cf31d971a3ca20d6 # Ohio us-west-1: AMI: ami-0782017a917e973e7 # N California us-west-2: AMI: ami-6cd6f714 # Oregon eu-west-1: AMI: ami-0bdb1d6c15a40392c # Ireland eu-central-1: AMI: ami-0f5dbc86dd9cbf7a8 # Frankfurt sa-east-1: AMI: ami-0ad7b0031d41ed4b9 # Sao Paulo ap-southeast-1: AMI: ami-01da99628f381e50a # Singapore ap-southeast-2: AMI: ami-00e17d1165b9dd3ec # Sydney ap-northeast-1: AMI: ami-08847abae18baa040 # Tokyo
这个Mapping部分定义了Amazon Linux 2 OS的AMI ID值。ID值根据资源栈所在地区的不同而不同。稍后我们将看到在定义EC2实例资源时如何使用这个映射表。但在继续往下介绍之前,有一些要点需要提及:1)注释是你的好朋友;2)我没有为每个地区都提供值;3)这些值只是截止撰写本文时的值,EC2团队将会时不时发布改进过的新AMI版本,你或许可以使用它们。
找到这些值并不难,我通过使用AWS管理控制台的EC2实例创建向导就可以找到它们。在AMI选择页面,我利用区域选择来获得所有集合的值。当然,还有更多高级技术可以取代映射表(例如由Lambda函数支持的或CloudFormation自定义资源),但我不想在这篇文章中过多地介绍它们。
接下来,我们的EC2实例需要一个安全组:
# A security group for our NAT. Ingress from the VPC IPs only. Egress is TCP \u0026amp; UDP only: NATSecurityGroup: Type: AWS::EC2::SecurityGroup Condition: BuildNATInstance DependsOn: AttachGateway Properties: GroupName: !Sub NATSecurityGroup-${AWS::StackName} GroupDescription: Enable internal access to the NAT device VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: '0' ToPort: '1024' CidrIp: !GetAtt VPC.CidrBlock SecurityGroupEgress: - IpProtocol: tcp FromPort: '0' ToPort: '65535' CidrIp: 0.0.0.0/0 - IpProtocol: udp FromPort: '0' ToPort: '65535' CidrIp: 0.0.0.0/0
为了保持这篇文章的简短,我不打算解释所有复杂的东西,所以这里给出了摘要:
接下来是EC2实例:
# A NAT Instance will be used if the user selected Private subnets and EC2-based NAT. NATInstance: Type: AWS::EC2::Instance Condition: BuildNATInstance DependsOn: PublicRoute1 # Must have route to IGW established. Properties: ImageId: !FindInMap [ AmazonLinuxAMI, !Ref \u0026quot;AWS::Region\u0026quot;, AMI] # lookup from AMI map InstanceType: t2.small # Any instance type is fine NetworkInterfaces: - DeviceIndex: '0' SubnetId: !Ref PublicSubnetA # Any public subnet is fine AssociatePublicIpAddress: true # We will need a public IP address GroupSet: [!Ref NATSecurityGroup] # Plug in the security group SourceDestCheck: false # NATs don't work if EC2 matches source with destinations. Tags: - Key: Name Value: !Sub NAT-${AWS::StackName} UserData: # This code is NAT code. Last line signals completion: Fn::Base64: !Sub | #!/bin/bash yum -y update yum install -y aws-cfn-bootstrap echo 1 \u0026gt; /proc/sys/net/ipv4/ip_forward echo 0 \u0026gt; /proc/sys/net/ipv4/conf/eth0/send_redirects /sbin/iptables -t nat -A POSTROUTING -o eth0 -s 0.0.0.0/0 -j MASQUERADE /sbin/iptables-save \u0026gt; /etc/sysconfig/iptables mkdir -p /etc/sysctl.d/ cat \u0026lt;\u0026lt; NatConfFileMarker \u0026gt; /etc/sysctl.d/nat.conf net.ipv4.ip_forward = 1 net.ipv4.conf.eth0.send_redirects = 0 NatConfFileMarker /opt/aws/bin/cfn-signal -e 0 --resource NATInstance --stack ${AWS::StackName} --region ${AWS::Region} # This NATInstance is only complete when you get 1 signal back within 5 minutes'. CreationPolicy: ResourceSignal: Count: 1 Timeout: PT5M
简述:
好多东西啊!但CloudFormation或基础设施即代码的美妙之处在于,这些事情我只需要做一次。有关所有这些设置的完整细节,请参阅。
最后,我们需要调整之前构建的PrivateRoute。我们需要将出站流量路由到NAT网关或NAT实例,具体取决于创建的是哪个:
PrivateRoute1: # Private route table can access web via NAT (created below) Type: AWS::EC2::Route Condition: BuildPrivateSubnets Properties: RouteTableId: !Ref PrivateRouteTable DestinationCidrBlock: 0.0.0.0/0 # If we are using a NAT Instance, route traffic through the NAT Instance: InstanceId: !If [ BuildNATInstance, !Ref NATInstance, !Ref \u0026quot;AWS::NoValue\u0026quot; ] # Otherwise if we are using a NAT Gateway, route traffic through the NAT Gateway: NatGatewayId: !If [ BuildNATGateway, !Ref NATGateway, !Ref \u0026quot;AWS::NoValue\u0026quot; ]
请注意InstanceId和NatGatewayId这两个属性,根据文档,它们是互斥的。当我们将流量路由到EC2实例是会用到。如果我们选择了BuildNATInstance,那么内部函数只会将该值设置为NATInstance。所做的事情比它看起来的要多,它不只是表示没有设置值,而且CloudFormation知道这意味着根本不需要设置这个属性。如果我们选择了BuildNATGateway,的镜像逻辑会将值设置为NATGateway。由于条件是互斥的,因此只有一个会设置成功,出站流量将使用NATInstance或NATGateway,最终取决于我们的原始输入决策。
我们可以对修改后的模板进行一些装饰性的调整。我们希望能够控制参数的输入顺序,为此,在“Parameters”部分之前添加“Metadata”部分(尽管有些人喜欢将其放在模板的底部):
Metadata: # Control the UI display when running this template from the AWS Management Console: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: \u0026quot;Network Configuration\u0026quot; Parameters: - NumberOfAZs - PrivateSubnets - NATType
现在,在AWS管理控制台中使用这个模板创建资源栈时,参数页将提示操作员进行“网络配置”,并按所需顺序显示参数。当在使用CLI时,这部分不会有任何影响。
我们创建的模板是一个很好的通用模板,可以作为其他需要使用VPC的CloudFormation资源栈的起点。我们希望能够轻松地将其作为其他资源栈的输入。这对于大多数IT组织来说尤为重要,在这些组织中,团队之间承担着细分的责任,负责管理网络资源的团队和负责构建使用网络的资源的团队是不同的团队。要让资源栈提供输出值,可以通过在模板中创建Outputs部分来实现:
Outputs: VPC: Description: VPC of the base network Value: !Ref VPC Export: Name: !Sub ${AWS::StackName}-VPC PublicSubnetA: Description: First Public Subnet Value: !Ref PublicSubnetA Export: Name: !Sub ${AWS::StackName}-PublicSubnetA PublicSubnetB: Description: Second Public Subnet Condition: BuildPublicB Value: !Ref PublicSubnetB Export: Name: !Sub ${AWS::StackName}-PublicSubnetB PublicSubnetC: Description: Third Public Subnet Condition: BuildPublicC Value: !Ref PublicSubnetC Export: Name: !Sub ${AWS::StackName}-PublicSubnetC PrivateSubnetA: Condition: BuildPrivateSubnets Description: First Private Subnet Value: !Ref PrivateSubnetA Export: Name: !Sub ${AWS::StackName}-PrivateSubnetA PrivateSubnetB: Condition: BuildPrivateB Description: Second Private Subnet Value: !Ref PrivateSubnetB Export: Name: !Sub ${AWS::StackName}-PrivateSubnetB PrivateSubnetC: Condition: BuildPrivateC Description: Third Private Subnet Value: !Ref PrivateSubnetC Export: Name: !Sub ${AWS::StackName}-PrivateSubnetC
基本上,在资源栈创建完成后,这些输出条目会在AWS管理控制台/CLI JSON输出上显示相关的值。请注意,包含条件属性是为了仅触发实际创建的资源的值。
需要注意的部分是Export/Name。它生成了一个区域范围的名称,另一个资源栈可以通过这个名称引用该资源。以PublicSubnetA为例,并假设资源栈名称为“my-network”,导出的值为“my-network-PublicSubnetA”。另一个资源栈可以使用“ my-network-PublicSubnetA”来引用该资源,就像在资源栈中使用“!Ref”一样容易。通常,初始(基础)资源栈会被作为输入参数,因此,资源栈名称部分可以是动态的,例如:
Fn::ImportValue: !Sub ${BaseStack}-PublicSubnetA
其中“BaseStack”是另一个资源栈的输入参数。!ImportValue里的!Sub是无效的YAML,所以我们必须使用“长格式”的函数名称。
这里的的Export/Name/!ImportValue技术在多团队环境中很常见。每个团队通常需要引用其他团队生成的资源栈中的资源,并生成被其他资源栈引用的资源。导出的名称成为团队间的可靠通信点。此外,CloudFormation会跟踪这些资源栈间的引用,以防止因为删除或更新资源栈导致另一个资源栈中的相关资源无效。
除了添加EC2 NAT实例选项和输出部分外,我们只修改了原始模板中的少量行。参数和条件为我们的模板带来了更强悍的功能。我们现在可以创建具有一到六个子网的VPC,包含各种可能的排列组合。我们创建的资源栈可以被其他资源栈引用。更令人惊奇的是,我们可以使用这个模板修改生成的资源栈,为其添加或删除子网,例如在POC时进行试用性部署。你可以扩展这项技术,让这个模板变得更加复杂,例如创建只包含私有子网的VPC。
Ken Krueger的专业使命宣言是“通过应用现代技术来指导组织和个人实现商业成功”。他拥有超过30年软件从业经验,做过软件开发者、项目负责人、项目经理、Scrum Master,以及大型机、客户端-服务器、Web讲师。他在Java、Spring、SQL、Web开发、云计算和相关技术方面有着丰富的经验。他的行业经验包括电信、金融、房地产、零售、电力、航运、酒店和软件开发。他拥有南佛罗里达大学MIS学位、罗林斯学院Crummer商学院MBA学位,以及Scrum Master、PMP、AWS和Java认证。
查看英文原文:
转载地址:http://kbdvl.baihongyu.com/