【问题标题】:Deploy a .NET Windows Service with Amazon Elastic Beanstalk with no Web Application使用没有 Web 应用程序的 Amazon Elastic Beanstalk 部署 .NET Windows 服务
【发布时间】:2014-09-01 09:42:43
【问题描述】:

我想创建一个 Elastic Beanstalk 配置,允许我部署 .NET Windows 服务,但无需部署 Web 应用程序。

我刚刚阅读了this blog post,它解释了如何使用 .ebextensions 在您的 Web 应用程序旁边部署 Windows 服务,但是是否存在这样的场景,即无需为 Web 应用程序部署 Web Deploy 包即可运行 .ebextensions ?

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

Elastic Beanstalk 常见问题解答提到了部署非 Web 应用程序的能力 (here),我在 AWS 开发人员论坛 (here) 上发现了一个类似的(未回答的)问题。

更新

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

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

作为旁注,我想强调一下this page from Amazon's documentation,我发现它是创建这些特殊配置文件的非常有用的指南。

另一个更新

实施上述方法后,我发现 Elastic Beanstalk 没有为新的 Beanstalk 实例执行我的 .ebextensions 脚本。因此,创建新实例时无法安装 Windows 服务。我不得不再跳几圈才能最终得到一个可扩展的解决方案。如果您想要最终解决方案的详细信息,请告诉我。

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


基本解决方案

我不喜欢发布源代码,因为它不是用于个人项目,但这是我当前部署解决方案的基本结构:

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

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

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

新的 AWS 部署工具

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

我还没有使用这些新服务(我什至不确定它们是否已经发布),但它们看起来很有希望。

【问题讨论】:

  • 注意添加一些代码示例:您是如何解决的?有过同样的愿望。想知道我是否应该探索 docker 和 mono。
  • @sobelito 我刚刚添加了一些关于我目前的方法以及我会做些什么不同的额外信息。我还没有研究过 docker,但这可能是一个更好的方法。似乎大多数 AWS 服务都是为在 linux 机器上使用而设计的。也就是说,有很多方法可以实现可扩展的 Windows 服务(例如上面提到的那些)。他们的新部署服务也值得研究。我知道他们还发布了一个“容器服务”,增加了对 Docker 的支持。让我知道您最终选择了哪个选项。
  • 你好@skeryl。我不知道您的方法是否最好,但我的做法完全相同。从 S3 存储桶下载 ZIP 文件后,释放或多或少为空的 Web 应用程序并安装 Windows 服务。当然,更新 Windows 服务会更让人头疼,但这与 EB 无关。我也在研究其他方法,将调查您与新部署工具的链接。并且我同意;对于这种情况,我对 EB 也不满意。
  • 根据您的个人项目创建概念验证项目并发布代码。
  • 我有类似的设置,但我直接使用 CloudFormation。 Beanstalk 是一项专注于快速发布 Web 应用程序的服务,因此它不是最合适的选择,并且在它的底层仍然使用 CloudFormation 来部署资源。如果你有兴趣,我可以分享我的模板。

标签: c# .net amazon-web-services amazon-elastic-beanstalk


【解决方案1】:

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

堆栈需要现有的 VPC(我们跨越多个可用区)、一个存储所有服务构建工件的 S3 存储桶和一个 EC2 密钥对。模板使用 Windows AMI 和少量其他资源(例如具有访问密钥的 IAM 用户和工作 S3 存储桶)创建 EC2 实例,仅用于说明如何创建您的服务可能需要的其他资源。模板还将压缩包的名称作为参数,其中包含已上传到构建工件 S3 存储桶上的所有服务二进制文件和配置文件(我们使用为我们制作的 TeamCity 构建服务器,但您可以手动创建和上传包课程)。当您构建新版本的服务时,您只需创建新包(例如 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.bat 批处理文件(包含在 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

模板还创建配置文件(来自同样驻留在工件存储桶上的 settings.config.mustache),其中包含有关已创建供服务使用的其他资源的信息。这里是:

<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 创建并稍后更新堆栈。

差不多就是这样。您可以访问the AWS CloudFormation website 以获取有关该服务以及如何使用模板的更多信息。

P.S.:我意识到如果我也共享创建 VPC 的模板会更好。我将其分开,因为每个区域都有一个 VPC。如果您愿意,可以将其与服务模板集成,但这意味着每次创建新堆栈时,也会创建一个新 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" }
        }
    }
}

【讨论】:

    【解决方案2】:

    根据我自己的经验,使用 ebextensions 实现任何东西都会显着增加部署时间。如此之多,以至于在自动缩放时实例最多可能需要 15 分钟才能启动。几乎达不到目的。

    无论如何,请务必将 Auto Scaling 组的“健康检查宽限期”属性配置为重要的内容。例如,我们使用 900(即 15 分钟)。少一点,实例永远不会通过健康检查,并且扩展事件失败;这导致了一系列无休止的扩大规模尝试。

    【讨论】:

      猜你喜欢
      • 2014-01-06
      • 2012-07-04
      • 2013-01-20
      • 2014-03-31
      • 1970-01-01
      • 2013-06-25
      • 2018-06-13
      • 2016-11-12
      • 2016-02-17
      相关资源
      最近更新 更多