使用没有Web应用程序的Amazon Elastic Beanstalk部署.NET Windows服务

我想创build一个Elastic Beanstalkconfiguration,允许我部署.NET Windows服务,但不部署Web应用程序。

我刚刚阅读了这篇博客文章 ,其中解释了如何使用.ebextensions在Web应用程序旁部署Windows服务,但是有没有一种scheme可以在不部署Web应用程序的Web Deploy包的情况下运行.ebextensions?

我唯一的select是创build一个包含.ebextensions目录的空Web应用程序,然后部署Web Deploy包?

Elastic Beanstalk FAQ提到能够部署非web应用程序( 在这里 ),我在AWS开发人员论坛上发现了一个类似的(没有回答的)问题。

更新

由于在这个问题上缺乏活动,而且我也无法在互联网上find任何其他信息,所以我只是假设这个问题的答案是“否”(至less现在是这样)。

我最终创build了一个空的Web应用程序,并使用它通过.ebextensions YAMLconfiguration部署我的Windows服务。

作为一个侧面说明,我想从亚马逊的文档中突出显示这个页面 ,我发现这是一个非常有用的指导,创build这些特殊的configuration文件。

另一个更新

在实现上面提到的方法后,我发现Elastic Beanstalk没有为新的Beanstalk实例执行我的.ebextensions脚本。 因此,创build新实例时Windows服务无法安装。 我不得不跳过几圈,最后到达一个可扩展的解决scheme。 请让我知道,如果你想要的最终解决scheme的细节。

最终,Elastic Beanstalk似乎不适合部署可扩展的Windows服务。


基本解决scheme

我不习惯发布源代码,因为它不是个人项目,但是这是我目前部署解决scheme的基本结构:

  1. 自定义EC2 AMI包含启动时运行的“引导程序”程序。 该程序执行以下操作:
    1.1。 从(可configuration的)“部署”S3存储桶下载“zip”存档
    1.2。 将下载的zip文件解压到一个临时目录
    1.3。 定位/执行“install.bat”脚本(脚本的名称也是可configuration的)。 该脚本安装并启动Windows服务。
  2. Elastic Beanstalk“Instance AMI”被设置为使用bootsrap程序的自定义AMI(请参阅: 本文 )

要部署新代码:将安装.zip存档(包含windows service和install.bat文件)上载到S3存储桶,并终止Elastic Beanstalk应用程序的所有EC2实例。 当实例重新创build时,引导程序将下载/安装新更新的代码。

当然,如果我重新开始,我将跳过使用Elastic Beanstalk,并使用标准AWS自动扩展以及类似的部署scheme。 底线是,如果您没有Web应用程序,请不要使用Elastic Beanstalk; 使用标准的AWS自动缩放function更好。

新的AWS部署工具

亚马逊最近宣布了一些似乎解决部署问题的新代码部署/pipe理服务: http : //aws.amazon.com/blogs/aws/code-management-and-deployment/

我还没有使用这些新的服务(我甚至不知道他们是否已经发布),但他们看起来很有前途。

由于这个问题已经存在了一段时间,但仍然没有答案,但仍然引起人们的兴趣,让我分享一个非常类似的问题 – 在EC2实例上安装Windows服务。 虽然我没有使用Beanstalk,因为该服务的devise更多的是快速部署Web应用程序。 相反,我直接使用下面Beanstalk使用的CloudFormation来部署与Web应用程序相关的资源。

堆栈期望现有的VPC(我们跨越多个可用区域),一个存储所有服务构build工件的S3存储桶和一个EC2密钥对。 模板创buildEC2实例使用Windows AMI和其他一些资源,如带访问键的IAM用户和工作的S3存储桶,仅用于说明如何创build服务可能需要的其他资源。 模板还将带有所有服务二进制文件和configuration文件的压缩包的名称作为参数,这些文件已经上传到构build工件S3存储桶中(我们使用TeamCity构build服务器,这对我们来说是这样,但是您可以手动创build和上传包课程)。 当您构build新版本的服务时,只需创build新的包(例如service.v2.zip),使用新名称更新堆栈,服务将自动更新。 模板包含4个不同区域的AMI的ID,但是如果您愿意,您可以随时添加其他区域。 这是堆栈模板:

 { "AWSTemplateFormatVersion": "2010-09-09", "Description": "Service stack template.", "Parameters": { "KeyPair": { "Type": "String", "Default": "MyDefaultKeys", "Description": "Name of EC2 Key Pair." }, "ServicePackageName": { "Type": "String", "Default": "service.zip", "Description": "Name of the zip package of the service files." }, "DeploymentBucketName": { "Type": "String", "Default": "", "Description": "Name of the deployment bucket where all the artifacts are." }, "VPCId": { "Type": "String", "Default": "", "Description": "Identifier of existing VPC." }, "VPCSubnets": { "Default": "", "Description": "Commaseparated list of existing subnets within the existing VPC. Could be just one.", "Type": "CommaDelimitedList" }, "VPCSecurityGroup": { "Default": "", "Description": "Existing VPC security group. That should be the ID of the VPC's default security group.", "Type": "String" } }, "Mappings": { "Region2WinAMI": { "us-east-1": { "64": "ami-40f0d32a" }, "us-west-1": { "64": "ami-20601740" }, "us-west-2": { "64": "ami-ff4baf9f" }, "eu-west-1": { "64": "ami-3367d340" } } }, "Resources": { "ServiceInstance": { "Type": "AWS::EC2::Instance", "Metadata": { "Comment": "Install Service", "AWS::CloudFormation::Init": { "configSets": { "default": [ "ServiceConfig" ] }, "ServiceConfig": { "files": { "c:\\service\\settings.config": { "source": { "Fn::Join": [ "/", [ "https://s3.amazonaws.com", { "Ref": "DeploymentBucketName" }, "deployments/stacks", { "Ref": "AWS::StackName" }, "templates/settings.config.mustache" ] ] }, "context": { "region": { "Ref": "AWS::Region" }, "accesskey": { "Ref": "IAMUserAccessKey" }, "secretkey": { "Fn::GetAtt": [ "IAMUserAccessKey", "SecretAccessKey" ] }, "bucket": { "Ref": "BucketName" } } }, "c:\\cfn\\cfn-hup.conf": { "content": { "Fn::Join": [ "", [ "[main]\n", "stack=", { "Ref": "AWS::StackId" }, "\n", "region=", { "Ref": "AWS::Region" }, "\n", "interval=1" ] ] } }, "c:\\cfn\\hooks.d\\cfn-auto-reloader.conf": { "content": { "Fn::Join": [ "", [ "[cfn-auto-reloader-hook]\n", "triggers=post.update\n", "path=Resources.ServiceInstance.Metadata.AWS::CloudFormation::Init\n", "action=cfn-init.exe -v -s ", { "Ref": "AWS::StackName" }, " -r ServiceInstance --region ", { "Ref": "AWS::Region" }, "\n" ] ] } } }, "sources": { "c:\\tmp\\service": { "Fn::Join": [ "/", [ "https://s3.amazonaws.com", { "Ref": "DeploymentBucketName" }, "deployments/stacks", { "Ref": "AWS::StackName" }, "artifacts/Service", { "Ref": "ServicePackageName" } ] ] } }, "commands": { "Install Service": { "command": "call c:\\tmp\\service\\install.bat", "ignoreErrors": "false" } }, "services": { "windows": { "cfn-hup": { "enabled": "true", "ensureRunning": "true", "files": [ "c:\\cfn\\cfn-hup.conf", "c:\\cfn\\hooks.d\\cfn-auto-reloader.conf" ] } } } } } }, "Properties": { "ImageId": { "Fn::FindInMap": [ "Region2WinAMI", { "Ref": "AWS::Region" }, "64" ] }, "InstanceType": "t2.micro", "KeyName": { "Ref": "KeyPair" }, "SecurityGroupIds" : [{ "Ref": "VPCSecurityGroup" }], "SubnetId" : { "Fn::Select": [ "0", { "Ref": "VPCSubnets" } ] }, "UserData": { "Fn::Base64": { "Fn::Join": [ "", [ "<script>\n", "if not exist \"C:\\logs\" mkdir C:\\logs \n", "cfn-init.exe -v -s ", { "Ref": "AWS::StackName" }, " -r ServiceInstance --region ", { "Ref": "AWS::Region" }, " -c default \n", "</script>\n" ] ] } }, "BlockDeviceMappings": [ { "DeviceName": "/dev/sda1", "Ebs": { "DeleteOnTermination": "true", "VolumeSize": "40", "VolumeType": "gp2" } } ], "Tags": [ { "Key": "Name", "Value": { "Fn::Join": [ ".", [ { "Ref": "AWS::StackName" }, "service" ] ] } } ] } }, "BucketName": { "Type": "AWS::S3::Bucket", "Properties": { "AccessControl": "PublicRead" }, "DeletionPolicy": "Retain" }, "IAMUser": { "Type": "AWS::IAM::User", "Properties": { "Path": "/", "Groups": [ "stack-users" ], "Policies": [ { "PolicyName": "giveaccesstobuckets", "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:*" ], "Resource": [ { "Fn::Join": [ "", [ "arn:aws:s3:::", { "Ref": "BucketName" }, "/*" ] ] } ] } ] } } ] } }, "IAMUserAccessKey": { "Type": "AWS::IAM::AccessKey", "Properties": { "UserName": { "Ref": "IAMUser" } } } } } 

正如你所看到的,在复制工件之后,我们执行install.batbatch file(包含在zip文件中),将文件移动到正确的位置并注册服务。 这里是文件的内容:

 @echo off sc query MyService > NUL IF ERRORLEVEL 1060 GOTO COPYANDCREATE sc stop MyService waitfor /T 20 ServiceStop echo D | xcopy "c:\tmp\service" "c:\service\" /E /Y /i GOTO END :COPYANDCREATE echo D | xcopy "c:\tmp\service" "c:\service\" /E /Y /i sc create MyService binpath= "c:\service\MyService.exe" start= "auto" :END sc start MyService 

模板还会创buildconfiguration文件(来自驻留在工件存储桶中的settings.config.mustache),其中包含有关为服务使用而创build的其他资源的信息。 这里是:

 <appSettings> <add key="AWSAccessKey" value="{{accesskey}}" /> <add key="AWSSecretKey" value="{{secretkey}}" /> <add key="AWSRegion" value="{{region}}" /> <add key="AWSBucket" value="{{bucket}}" /> </appSettings> 

您可以从AWS Web控制台或CLI创build并稍后更新堆栈。

而这是非常多的。 您可以访问AWS CloudFormation网站以获取有关该服务以及如何使用模板的更多信息。

PS:我意识到如果我也分享创buildVPC的模板会更好。 我保持它分开,因为我有一个VPC每个区域。 如果您愿意,可以将其与服务模板集成在一起,但这意味着每次创build新堆栈时,都会创build一个新的VPC。 这是VPC模板:

 { "AWSTemplateFormatVersion": "2010-09-09", "Description": "VPC stack template.", "Mappings": { "Region2AZ": { "us-east-1": { "AZ": [ "us-east-1a", "us-east-1b", "us-east-1d" ] }, "us-west-1": { "AZ": [ "us-west-1b", "us-west-1c" ] }, "us-west-2": { "AZ": [ "us-west-2a", "us-west-2b", "us-west-2c" ] }, "eu-west-1": { "AZ": [ "eu-west-1a", "eu-west-1b", "eu-west-1c" ] } } }, "Conditions": { "RegionHas3Zones": { "Fn::Not" : [ { "Fn::Equals" : [ { "Ref": "AWS::Region" }, "us-west-1" ] } ] } }, "Resources": { "VPC": { "Type": "AWS::EC2::VPC", "Properties": { "CidrBlock": "10.0.0.0/16", "EnableDnsSupport" : "true", "EnableDnsHostnames" : "true" } }, "VPCSecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "Security group for VPC.", "VpcId": { "Ref": "VPC" } } }, "Subnet0": { "Type": "AWS::EC2::Subnet", "Properties": { "VpcId": { "Ref": "VPC" }, "CidrBlock": "10.0.0.0/24", "AvailabilityZone": { "Fn::Select": [ "0", { "Fn::FindInMap": [ "Region2AZ", { "Ref": "AWS::Region" }, "AZ" ] } ] } } }, "Subnet1": { "Type": "AWS::EC2::Subnet", "Properties": { "VpcId": { "Ref": "VPC" }, "CidrBlock": "10.0.1.0/24", "AvailabilityZone": { "Fn::Select": [ "1", { "Fn::FindInMap": [ "Region2AZ", { "Ref": "AWS::Region" }, "AZ" ] } ] } } }, "Subnet2": { "Type": "AWS::EC2::Subnet", "Condition": "RegionHas3Zones", "Properties": { "VpcId": { "Ref": "VPC" }, "CidrBlock": "10.0.2.0/24", "AvailabilityZone": { "Fn::Select": [ "2", { "Fn::FindInMap": [ "Region2AZ", { "Ref": "AWS::Region" }, "AZ" ] } ] } } }, "InternetGateway": { "Type": "AWS::EC2::InternetGateway", "Properties": { } }, "AttachGateway": { "Type": "AWS::EC2::VPCGatewayAttachment", "Properties": { "VpcId": { "Ref": "VPC" }, "InternetGatewayId": { "Ref": "InternetGateway" } } }, "RouteTable": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { "Ref": "VPC" } } }, "Route": { "Type": "AWS::EC2::Route", "DependsOn": "AttachGateway", "Properties": { "RouteTableId": { "Ref": "RouteTable" }, "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { "Ref": "InternetGateway" } } }, "SubnetRouteTableAssociation0": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "SubnetId": { "Ref": "Subnet0" }, "RouteTableId": { "Ref": "RouteTable" } } }, "SubnetRouteTableAssociation1": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "SubnetId": { "Ref": "Subnet1" }, "RouteTableId": { "Ref": "RouteTable" } } }, "SubnetRouteTableAssociation2": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Condition": "RegionHas3Zones", "Properties": { "SubnetId": { "Ref": "Subnet2" }, "RouteTableId": { "Ref": "RouteTable" } } }, "NetworkAcl": { "Type": "AWS::EC2::NetworkAcl", "Properties": { "VpcId": { "Ref": "VPC" } } }, "AllowAllInboundTCPAclEntry": { "Type": "AWS::EC2::NetworkAclEntry", "Properties": { "NetworkAclId": { "Ref": "NetworkAcl" }, "RuleNumber": "100", "Protocol": "6", "RuleAction": "allow", "Egress": "false", "CidrBlock": "0.0.0.0/0", "PortRange": { "From": "0", "To": "65535" } } }, "AllowAllInboundUDPAclEntry": { "Type": "AWS::EC2::NetworkAclEntry", "Properties": { "NetworkAclId": { "Ref": "NetworkAcl" }, "RuleNumber": "101", "Protocol": "17", "RuleAction": "allow", "Egress": "false", "CidrBlock": "0.0.0.0/0", "PortRange": { "From": "0", "To": "65535" } } }, "AllowAllOutboundTCPAclEntry": { "Type": "AWS::EC2::NetworkAclEntry", "Properties": { "NetworkAclId": { "Ref": "NetworkAcl" }, "RuleNumber": "100", "Protocol": "6", "RuleAction": "allow", "Egress": "true", "CidrBlock": "0.0.0.0/0", "PortRange": { "From": "0", "To": "65535" } } }, "AllowAllOutboundUDPAclEntry": { "Type": "AWS::EC2::NetworkAclEntry", "Properties": { "NetworkAclId": { "Ref": "NetworkAcl" }, "RuleNumber": "101", "Protocol": "17", "RuleAction": "allow", "Egress": "true", "CidrBlock": "0.0.0.0/0", "PortRange": { "From": "0", "To": "65535" } } }, "SubnetNetworkAclAssociation0": { "Type": "AWS::EC2::SubnetNetworkAclAssociation", "Properties": { "SubnetId": { "Ref": "Subnet0" }, "NetworkAclId": { "Ref": "NetworkAcl" } } }, "SubnetNetworkAclAssociation1": { "Type": "AWS::EC2::SubnetNetworkAclAssociation", "Properties": { "SubnetId": { "Ref": "Subnet1" }, "NetworkAclId": { "Ref": "NetworkAcl" } } }, "SubnetNetworkAclAssociation2": { "Type": "AWS::EC2::SubnetNetworkAclAssociation", "Condition": "RegionHas3Zones", "Properties": { "SubnetId": { "Ref": "Subnet2" }, "NetworkAclId": { "Ref": "NetworkAcl" } } } }, "Outputs": { "VPC": { "Description": "VPC", "Value": { "Ref": "VPC" } }, "VPCSecurityGroup": { "Description": "VPC Security Group Id", "Value": { "Fn::GetAtt": [ "VPCSecurityGroup", "GroupId" ] } }, "Subnet0": { "Description": "Subnet0 Id", "Value": { "Ref": "Subnet0" } }, "Subnet1": { "Description": "Subnet1 Id", "Value": { "Ref": "Subnet1" } }, "Subnet2": { "Description": "Subnet2 Id", "Condition": "RegionHas3Zones", "Value": { "Ref": "Subnet2" } } } } 

根据我自己的经验,使用ebextensions实现任何东西都会大大增加部署的时间。 如此一来,一个实例在自动缩放时可能需要长达15分钟的时间。 几乎打败了目的。

无论如何,一定要将Auto Scaling Group的“健康检查宽限期”属性configuration为重要的。 例如,我们使用900(即15分钟)。 任何事情less,实例不会通过健康检查,并且扩展事件失败; 这导致了无数的扩大尝试。