Je souhaite créer une configuration Elastic Beanstalk qui me permet de déployer un service Windows .NET mais sans déployer une application Web.
Je viens de lire cet article de blog qui explique comment utiliser .ebextensions pour déployer un service Windows aux côtés de votre application Web, mais existe-t-il un scénario pour lequel les .ebextensions peuvent être exécutées sans déployer un package Web Deploy pour une application web?
Est-ce ma seule option pour créer une application Web vide qui contient le répertoire .ebextensions, puis déployer le package Web Deploy?
Elastic Beanstalk FAQ mentionne la possibilité de déployer des applications non Web ( ici ) et j'ai trouvé une question similaire (sans réponse) sur les forums des développeurs AWS (- ici ).
En raison du manque d'activité sur cette question et de mon incapacité à trouver d'autres informations sur Internet, j'ai simplement supposé que la réponse à cette question était "Non" (du moins pour l'instant).
J'ai fini par créer une application Web vide et je l'ai utilisée pour déployer mon service Windows via la configuration YAML .ebextensions.
En guise de remarque, je voudrais souligner cette page de la documentation d'Amazon que j'ai trouvé être un guide très utile pour créer ces fichiers de configuration spéciaux.
Après avoir implémenté l'approche mentionnée ci-dessus, j'ai découvert qu'Elastic Beanstalk n'exécutait pas mon .ebextensions
scripts pour les nouvelles instances de Beanstalk. Par conséquent, le service Windows n'a pas pu être installé lors de la création de nouvelles instances. J'ai dû sauter à travers plusieurs cerceaux pour arriver enfin à une solution évolutive. Veuillez me faire savoir si vous souhaitez obtenir les détails de la solution finale.
En fin de compte, il semble que Elastic Beanstalk n'était pas conçu pour déployer des services Windows évolutifs.
Je ne suis pas à l'aise de publier le code source car ce n'était pas pour un projet personnel, mais voici la structure de base de ma solution de déploiement actuelle:
Pour déployer un nouveau code: téléchargez l'archive .Zip d'installation (qui contient le service Windows et le fichier install.bat) dans le compartiment S3 et mettez fin à toutes les instances EC2 pour l'application Elastic Beanstalk. Au fur et à mesure que les instances sont recréées, le programme d'amorçage téléchargera/installera le code nouvellement mis à jour.
Bien sûr, si je recommençais, je ne ferais qu'utiliser Elastic Beanstalk et utiliser la mise à l'échelle automatique AWS standard avec un schéma de déploiement similaire. L'essentiel est que si vous n'avez pas d'application Web, n'utilisez pas Elastic Beanstalk; vous êtes mieux avec la mise à l'échelle automatique AWS standard.
Amazon a récemment annoncé plusieurs nouveaux services de déploiement/gestion de code qui semblent résoudre des problèmes de déploiement: http://aws.Amazon.com/blogs/aws/code-management-and-deployment/
Je n'ai pas encore utilisé ces nouveaux services (je ne sais même pas s'ils ont déjà été publiés), mais ils semblent prometteurs.
Étant donné que cette question existe depuis un certain temps et n'a toujours pas de réponse, mais continue de susciter l'intérêt, permettez-moi de partager ma solution à un problème très similaire - l'installation d'un service Windows sur une instance EC2. Cependant, je n'utilise pas Beanstalk, car ce service est davantage conçu pour un déploiement rapide d'applications Web. Au lieu de cela, j'utilise directement CloudFormation que Beanstalk utilise en dessous pour déployer des ressources liées à l'application Web.
La pile attend un VPC existant (nos étendues à travers plusieurs zones de disponibilité), un compartiment S3 qui stocke tous les artefacts de génération de service et une paire de clés EC2. Le modèle crée une instance EC2 à l'aide de Windows AMI et de quelques autres ressources comme l'utilisateur IAM avec des clés d'accès et un compartiment S3 fonctionnel juste pour illustrer comment créer des ressources supplémentaires dont votre service pourrait avoir besoin. Le modèle prend également en paramètre le nom d'un package zippé avec tous les binaires de service et les fichiers de configuration qui ont été téléchargés sur le compartiment de génération d'artefacts S3 (nous utilisons le serveur de construction TeamCity qui le fait pour nous, mais vous pouvez créer et télécharger manuellement le package de cours). Lorsque vous créez une nouvelle version du service, vous créez simplement un nouveau package (par exemple service.v2.Zip), mettez à jour la pile avec le nouveau nom et le service sera mis à jour automatiquement. Le modèle contient des identifiants d'AMI dans 4 régions différentes, mais vous pouvez toujours ajouter d'autres régions si vous le souhaitez. Voici le modèle de pile:
{
"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" }
}
}
}
}
Comme vous pouvez le voir, après avoir copié les artefacts, nous exécutons le fichier batch install.bat (inclus dans le fichier Zip) qui déplacera les fichiers à l'emplacement correct et enregistrera le service. Voici le contenu du fichier:
@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
Le modèle crée également un fichier de configuration (à partir du fichier settings.config.mustache qui réside également dans le compartiment d'artefacts) contenant des informations sur les autres ressources qui ont été créées pour le service à utiliser. C'est ici:
<appSettings>
<add key="AWSAccessKey" value="{{accesskey}}" />
<add key="AWSSecretKey" value="{{secretkey}}" />
<add key="AWSRegion" value="{{region}}" />
<add key="AWSBucket" value="{{bucket}}" />
</appSettings>
Vous créez et mettez à jour la pile à partir de la console Web AWS ou CLI .
Et c'est à peu près tout. Vous pouvez visiter le site Web AWS CloudFormation pour obtenir plus d'informations sur le service et comment travailler avec des modèles.
P.S.: J'ai réalisé qu'il serait préférable que je partage également le modèle qui crée le VPC. Je le garde séparé car j'ai un VPC par région. Vous pouvez l'intégrer au modèle de service si vous le souhaitez, mais cela signifierait que chaque fois que vous créez une nouvelle pile, un nouveau VPC sera également créé. Voici le modèle 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" }
}
}
}
D'après ma propre expérience, l'implémentation de tout ce qui utilise des extensions électroniques augmente considérablement le temps de déploiement. À tel point que cela peut prendre jusqu'à 15 minutes pour qu'une instance tourne lors de la mise à l'échelle automatique. Échoue presque le but.
Dans tous les cas, assurez-vous de configurer la propriété "Health Grace Grace Period" du groupe Auto Scaling sur quelque chose d'important. Par exemple, nous utilisons 900 (soit 15 minutes). Rien de moins et l'instance ne passe jamais le bilan de santé et l'événement de mise à l'échelle échoue; ce qui en fait une série interminable de tentatives d'évolution.