Auto Scaling examples using Amazon SDK for .NET - Amazon SDK for .NET
Services or capabilities described in Amazon Web Services documentation might vary by Region. To see the differences applicable to the China Regions, see Getting Started with Amazon Web Services in China (PDF).

Auto Scaling examples using Amazon SDK for .NET

The following code examples show you how to perform actions and implement common scenarios by using the Amazon SDK for .NET with Auto Scaling.

Actions are code excerpts from larger programs and must be run in context. While actions show you how to call individual service functions, you can see actions in context in their related scenarios and cross-service examples.

Scenarios are code examples that show you how to accomplish a specific task by calling multiple functions within the same service.

Each example includes a link to GitHub, where you can find instructions on how to set up and run the code in context.

Get started

The following code examples show how to get started using Auto Scaling.

Amazon SDK for .NET
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the Amazon Code Examples Repository.

namespace AutoScalingActions; using Amazon.AutoScaling; public class HelloAutoScaling { /// <summary> /// Hello Amazon EC2 Auto Scaling. List EC2 Auto Scaling groups. /// </summary> /// <param name="args"></param> /// <returns>Async Task.</returns> static async Task Main(string[] args) { var client = new AmazonAutoScalingClient(); Console.WriteLine("Welcome to Amazon EC2 Auto Scaling."); Console.WriteLine("Let's get a description of your Auto Scaling groups."); var response = await client.DescribeAutoScalingGroupsAsync(); response.AutoScalingGroups.ForEach(autoScalingGroup => { Console.WriteLine($"{autoScalingGroup.AutoScalingGroupName}\t{autoScalingGroup.AvailabilityZones}"); }); if (response.AutoScalingGroups.Count == 0) { Console.WriteLine("Sorry, you don't have any Amazon EC2 Auto Scaling groups."); } } }

Actions

The following code example shows how to use AttachLoadBalancerTargetGroups.

Amazon SDK for .NET
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the Amazon Code Examples Repository.

/// <summary> /// Attaches an Elastic Load Balancing (ELB) target group to this EC2 Auto Scaling group. /// The /// </summary> /// <param name="autoScalingGroupName">The name of the Auto Scaling group.</param> /// <param name="targetGroupArn">The Arn for the target group.</param> /// <returns>Async task.</returns> public async Task AttachLoadBalancerToGroup(string autoScalingGroupName, string targetGroupArn) { await _amazonAutoScaling.AttachLoadBalancerTargetGroupsAsync( new AttachLoadBalancerTargetGroupsRequest() { AutoScalingGroupName = autoScalingGroupName, TargetGroupARNs = new List<string>() { targetGroupArn } }); }

The following code example shows how to use CreateAutoScalingGroup.

Amazon SDK for .NET
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the Amazon Code Examples Repository.

/// <summary> /// Create a new Amazon EC2 Auto Scaling group. /// </summary> /// <param name="groupName">The name to use for the new Auto Scaling /// group.</param> /// <param name="launchTemplateName">The name of the Amazon EC2 Auto Scaling /// launch template to use to create instances in the group.</param> /// <returns>A Boolean value indicating the success of the action.</returns> public async Task<bool> CreateAutoScalingGroupAsync( string groupName, string launchTemplateName, string availabilityZone) { var templateSpecification = new LaunchTemplateSpecification { LaunchTemplateName = launchTemplateName, }; var zoneList = new List<string> { availabilityZone, }; var request = new CreateAutoScalingGroupRequest { AutoScalingGroupName = groupName, AvailabilityZones = zoneList, LaunchTemplate = templateSpecification, MaxSize = 6, MinSize = 1 }; var response = await _amazonAutoScaling.CreateAutoScalingGroupAsync(request); Console.WriteLine($"{groupName} Auto Scaling Group created"); return response.HttpStatusCode == System.Net.HttpStatusCode.OK; }

The following code example shows how to use DeleteAutoScalingGroup.

Amazon SDK for .NET
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the Amazon Code Examples Repository.

Update the minimum size of an Auto Scaling group to zero, terminate all instances in the group, and delete the group.

/// <summary> /// Try to terminate an instance by its Id. /// </summary> /// <param name="instanceId">The Id of the instance to terminate.</param> /// <returns>Async task.</returns> public async Task TryTerminateInstanceById(string instanceId) { var stopping = false; Console.WriteLine($"Stopping {instanceId}..."); while (!stopping) { try { await _amazonAutoScaling.TerminateInstanceInAutoScalingGroupAsync( new TerminateInstanceInAutoScalingGroupRequest() { InstanceId = instanceId, ShouldDecrementDesiredCapacity = false }); stopping = true; } catch (ScalingActivityInProgressException) { Console.WriteLine($"Scaling activity in progress for {instanceId}. Waiting..."); Thread.Sleep(10000); } } } /// <summary> /// Tries to delete the EC2 Auto Scaling group. If the group is in use or in progress, /// waits and retries until the group is successfully deleted. /// </summary> /// <param name="groupName">The name of the group to try to delete.</param> /// <returns>Async task.</returns> public async Task TryDeleteGroupByName(string groupName) { var stopped = false; while (!stopped) { try { await _amazonAutoScaling.DeleteAutoScalingGroupAsync( new DeleteAutoScalingGroupRequest() { AutoScalingGroupName = groupName }); stopped = true; } catch (Exception e) when ((e is ScalingActivityInProgressException) || (e is Amazon.AutoScaling.Model.ResourceInUseException)) { Console.WriteLine($"Some instances are still running. Waiting..."); Thread.Sleep(10000); } } } /// <summary> /// Terminate instances and delete the Auto Scaling group by name. /// </summary> /// <param name="groupName">The name of the group to delete.</param> /// <returns>Async task.</returns> public async Task TerminateAndDeleteAutoScalingGroupWithName(string groupName) { var describeGroupsResponse = await _amazonAutoScaling.DescribeAutoScalingGroupsAsync( new DescribeAutoScalingGroupsRequest() { AutoScalingGroupNames = new List<string>() { groupName } }); if (describeGroupsResponse.AutoScalingGroups.Any()) { // Update the size to 0. await _amazonAutoScaling.UpdateAutoScalingGroupAsync( new UpdateAutoScalingGroupRequest() { AutoScalingGroupName = groupName, MinSize = 0 }); var group = describeGroupsResponse.AutoScalingGroups[0]; foreach (var instance in group.Instances) { await TryTerminateInstanceById(instance.InstanceId); } await TryDeleteGroupByName(groupName); } else { Console.WriteLine($"No groups found with name {groupName}."); } }
/// <summary> /// Delete an Auto Scaling group. /// </summary> /// <param name="groupName">The name of the Amazon EC2 Auto Scaling group.</param> /// <returns>A Boolean value indicating the success of the action.</returns> public async Task<bool> DeleteAutoScalingGroupAsync( string groupName) { var deleteAutoScalingGroupRequest = new DeleteAutoScalingGroupRequest { AutoScalingGroupName = groupName, ForceDelete = true, }; var response = await _amazonAutoScaling.DeleteAutoScalingGroupAsync(deleteAutoScalingGroupRequest); if (response.HttpStatusCode == System.Net.HttpStatusCode.OK) { Console.WriteLine($"You successfully deleted {groupName}"); return true; } Console.WriteLine($"Couldn't delete {groupName}."); return false; }

The following code example shows how to use DescribeAutoScalingGroups.

Amazon SDK for .NET
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the Amazon Code Examples Repository.

/// <summary> /// Get data about the instances in an Amazon EC2 Auto Scaling group. /// </summary> /// <param name="groupName">The name of the Amazon EC2 Auto Scaling group.</param> /// <returns>A list of Amazon EC2 Auto Scaling details.</returns> public async Task<List<AutoScalingInstanceDetails>> DescribeAutoScalingInstancesAsync( string groupName) { var groups = await DescribeAutoScalingGroupsAsync(groupName); var instanceIds = new List<string>(); groups!.ForEach(group => { if (group.AutoScalingGroupName == groupName) { group.Instances.ForEach(instance => { instanceIds.Add(instance.InstanceId); }); } }); var scalingGroupsRequest = new DescribeAutoScalingInstancesRequest { MaxRecords = 10, InstanceIds = instanceIds, }; var response = await _amazonAutoScaling.DescribeAutoScalingInstancesAsync(scalingGroupsRequest); var instanceDetails = response.AutoScalingInstances; return instanceDetails; }

The following code example shows how to use DescribeAutoScalingInstances.

Amazon SDK for .NET
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the Amazon Code Examples Repository.

/// <summary> /// Get data about the instances in an Amazon EC2 Auto Scaling group. /// </summary> /// <param name="groupName">The name of the Amazon EC2 Auto Scaling group.</param> /// <returns>A list of Amazon EC2 Auto Scaling details.</returns> public async Task<List<AutoScalingInstanceDetails>> DescribeAutoScalingInstancesAsync( string groupName) { var groups = await DescribeAutoScalingGroupsAsync(groupName); var instanceIds = new List<string>(); groups!.ForEach(group => { if (group.AutoScalingGroupName == groupName) { group.Instances.ForEach(instance => { instanceIds.Add(instance.InstanceId); }); } }); var scalingGroupsRequest = new DescribeAutoScalingInstancesRequest { MaxRecords = 10, InstanceIds = instanceIds, }; var response = await _amazonAutoScaling.DescribeAutoScalingInstancesAsync(scalingGroupsRequest); var instanceDetails = response.AutoScalingInstances; return instanceDetails; }

The following code example shows how to use DescribeScalingActivities.

Amazon SDK for .NET
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the Amazon Code Examples Repository.

/// <summary> /// Retrieve a list of the Amazon EC2 Auto Scaling activities for an /// Amazon EC2 Auto Scaling group. /// </summary> /// <param name="groupName">The name of the Amazon EC2 Auto Scaling group.</param> /// <returns>A list of Amazon EC2 Auto Scaling activities.</returns> public async Task<List<Amazon.AutoScaling.Model.Activity>> DescribeScalingActivitiesAsync( string groupName) { var scalingActivitiesRequest = new DescribeScalingActivitiesRequest { AutoScalingGroupName = groupName, MaxRecords = 10, }; var response = await _amazonAutoScaling.DescribeScalingActivitiesAsync(scalingActivitiesRequest); return response.Activities; }

The following code example shows how to use DisableMetricsCollection.

Amazon SDK for .NET
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the Amazon Code Examples Repository.

/// <summary> /// Disable the collection of metric data for an Amazon EC2 Auto Scaling /// group. /// </summary> /// <param name="groupName">The name of the Auto Scaling group.</param> /// <returns>A Boolean value that indicates the success or failure of /// the operation.</returns> public async Task<bool> DisableMetricsCollectionAsync(string groupName) { var request = new DisableMetricsCollectionRequest { AutoScalingGroupName = groupName, }; var response = await _amazonAutoScaling.DisableMetricsCollectionAsync(request); return response.HttpStatusCode == System.Net.HttpStatusCode.OK; }

The following code example shows how to use EnableMetricsCollection.

Amazon SDK for .NET
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the Amazon Code Examples Repository.

/// <summary> /// Enable the collection of metric data for an Auto Scaling group. /// </summary> /// <param name="groupName">The name of the Auto Scaling group.</param> /// <returns>A Boolean value indicating the success of the action.</returns> public async Task<bool> EnableMetricsCollectionAsync(string groupName) { var listMetrics = new List<string> { "GroupMaxSize", }; var collectionRequest = new EnableMetricsCollectionRequest { AutoScalingGroupName = groupName, Metrics = listMetrics, Granularity = "1Minute", }; var response = await _amazonAutoScaling.EnableMetricsCollectionAsync(collectionRequest); return response.HttpStatusCode == System.Net.HttpStatusCode.OK; }

The following code example shows how to use SetDesiredCapacity.

Amazon SDK for .NET
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the Amazon Code Examples Repository.

/// <summary> /// Set the desired capacity of an Auto Scaling group. /// </summary> /// <param name="groupName">The name of the Auto Scaling group.</param> /// <param name="desiredCapacity">The desired capacity for the Auto /// Scaling group.</param> /// <returns>A Boolean value indicating the success of the action.</returns> public async Task<bool> SetDesiredCapacityAsync( string groupName, int desiredCapacity) { var capacityRequest = new SetDesiredCapacityRequest { AutoScalingGroupName = groupName, DesiredCapacity = desiredCapacity, }; var response = await _amazonAutoScaling.SetDesiredCapacityAsync(capacityRequest); Console.WriteLine($"You have set the DesiredCapacity to {desiredCapacity}."); return response.HttpStatusCode == System.Net.HttpStatusCode.OK; }

The following code example shows how to use TerminateInstanceInAutoScalingGroup.

Amazon SDK for .NET
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the Amazon Code Examples Repository.

/// <summary> /// Terminate all instances in the Auto Scaling group in preparation for /// deleting the group. /// </summary> /// <param name="instanceId">The instance Id of the instance to terminate.</param> /// <returns>A Boolean value that indicates the success or failure of /// the operation.</returns> public async Task<bool> TerminateInstanceInAutoScalingGroupAsync( string instanceId) { var request = new TerminateInstanceInAutoScalingGroupRequest { InstanceId = instanceId, ShouldDecrementDesiredCapacity = false, }; var response = await _amazonAutoScaling.TerminateInstanceInAutoScalingGroupAsync(request); if (response.HttpStatusCode == System.Net.HttpStatusCode.OK) { Console.WriteLine($"You have terminated the instance: {instanceId}"); return true; } Console.WriteLine($"Could not terminate {instanceId}"); return false; }

The following code example shows how to use UpdateAutoScalingGroup.

Amazon SDK for .NET
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the Amazon Code Examples Repository.

/// <summary> /// Update the capacity of an Auto Scaling group. /// </summary> /// <param name="groupName">The name of the Auto Scaling group.</param> /// <param name="launchTemplateName">The name of the EC2 launch template.</param> /// <param name="maxSize">The maximum number of instances that can be /// created for the Auto Scaling group.</param> /// <returns>A Boolean value indicating the success of the action.</returns> public async Task<bool> UpdateAutoScalingGroupAsync( string groupName, string launchTemplateName, int maxSize) { var templateSpecification = new LaunchTemplateSpecification { LaunchTemplateName = launchTemplateName, }; var groupRequest = new UpdateAutoScalingGroupRequest { MaxSize = maxSize, AutoScalingGroupName = groupName, LaunchTemplate = templateSpecification, }; var response = await _amazonAutoScaling.UpdateAutoScalingGroupAsync(groupRequest); if (response.HttpStatusCode == System.Net.HttpStatusCode.OK) { Console.WriteLine($"You successfully updated the Auto Scaling group {groupName}."); return true; } else { return false; } }

Scenarios

The following code example shows how to create a load-balanced web service that returns book, movie, and song recommendations. The example shows how the service responds to failures, and how to restructure the service for more resilience when failures occur.

  • Use an Amazon EC2 Auto Scaling group to create Amazon Elastic Compute Cloud (Amazon EC2) instances based on a launch template and to keep the number of instances in a specified range.

  • Handle and distribute HTTP requests with Elastic Load Balancing.

  • Monitor the health of instances in an Auto Scaling group and forward requests only to healthy instances.

  • Run a Python web server on each EC2 instance to handle HTTP requests. The web server responds with recommendations and health checks.

  • Simulate a recommendation service with an Amazon DynamoDB table.

  • Control web server response to requests and health checks by updating Amazon Systems Manager parameters.

Amazon SDK for .NET
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the Amazon Code Examples Repository.

Run the interactive scenario at a command prompt.

static async Task Main(string[] args) { _configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("settings.json") // Load settings from .json file. .AddJsonFile("settings.local.json", true) // Optionally, load local settings. .Build(); // Set up dependency injection for the AWS services. using var host = Host.CreateDefaultBuilder(args) .ConfigureLogging(logging => logging.AddFilter("System", LogLevel.Debug) .AddFilter<DebugLoggerProvider>("Microsoft", LogLevel.Information) .AddFilter<ConsoleLoggerProvider>("Microsoft", LogLevel.Trace)) .ConfigureServices((_, services) => services.AddAWSService<IAmazonIdentityManagementService>() .AddAWSService<IAmazonDynamoDB>() .AddAWSService<IAmazonElasticLoadBalancingV2>() .AddAWSService<IAmazonSimpleSystemsManagement>() .AddAWSService<IAmazonAutoScaling>() .AddAWSService<IAmazonEC2>() .AddTransient<AutoScalerWrapper>() .AddTransient<ElasticLoadBalancerWrapper>() .AddTransient<SmParameterWrapper>() .AddTransient<Recommendations>() .AddSingleton<IConfiguration>(_configuration) ) .Build(); ServicesSetup(host); ResourcesSetup(); try { Console.WriteLine(new string('-', 80)); Console.WriteLine("Welcome to the Resilient Architecture Example Scenario."); Console.WriteLine(new string('-', 80)); await Deploy(true); Console.WriteLine("Now let's begin the scenario."); Console.WriteLine(new string('-', 80)); await Demo(true); Console.WriteLine(new string('-', 80)); Console.WriteLine("Finally, let's clean up our resources."); Console.WriteLine(new string('-', 80)); await DestroyResources(true); Console.WriteLine(new string('-', 80)); Console.WriteLine("Resilient Architecture Example Scenario is complete."); Console.WriteLine(new string('-', 80)); } catch (Exception ex) { Console.WriteLine(new string('-', 80)); Console.WriteLine($"There was a problem running the scenario: {ex.Message}"); await DestroyResources(true); Console.WriteLine(new string('-', 80)); } } /// <summary> /// Setup any common resources, also used for integration testing. /// </summary> public static void ResourcesSetup() { _httpClient = new HttpClient(); } /// <summary> /// Populate the services for use within the console application. /// </summary> /// <param name="host">The services host.</param> private static void ServicesSetup(IHost host) { _elasticLoadBalancerWrapper = host.Services.GetRequiredService<ElasticLoadBalancerWrapper>(); _iamClient = host.Services.GetRequiredService<IAmazonIdentityManagementService>(); _recommendations = host.Services.GetRequiredService<Recommendations>(); _autoScalerWrapper = host.Services.GetRequiredService<AutoScalerWrapper>(); _smParameterWrapper = host.Services.GetRequiredService<SmParameterWrapper>(); } /// <summary> /// Deploy necessary resources for the scenario. /// </summary> /// <param name="interactive">True to run as interactive.</param> /// <returns>True if successful.</returns> public static async Task<bool> Deploy(bool interactive) { var protocol = "HTTP"; var port = 80; var sshPort = 22; Console.WriteLine( "\nFor this demo, we'll use the AWS SDK for .NET to create several AWS resources\n" + "to set up a load-balanced web service endpoint and explore some ways to make it resilient\n" + "against various kinds of failures.\n\n" + "Some of the resources create by this demo are:\n"); Console.WriteLine( "\t* A DynamoDB table that the web service depends on to provide book, movie, and song recommendations."); Console.WriteLine( "\t* An EC2 launch template that defines EC2 instances that each contain a Python web server."); Console.WriteLine( "\t* An EC2 Auto Scaling group that manages EC2 instances across several Availability Zones."); Console.WriteLine( "\t* An Elastic Load Balancing (ELB) load balancer that targets the Auto Scaling group to distribute requests."); Console.WriteLine(new string('-', 80)); Console.WriteLine("Press Enter when you're ready to start deploying resources."); if (interactive) Console.ReadLine(); // Create and populate the DynamoDB table. var databaseTableName = _configuration["databaseName"]; var recommendationsPath = Path.Join(_configuration["resourcePath"], "recommendations_objects.json"); Console.WriteLine($"Creating and populating a DynamoDB table named {databaseTableName}."); await _recommendations.CreateDatabaseWithName(databaseTableName); await _recommendations.PopulateDatabase(databaseTableName, recommendationsPath); Console.WriteLine(new string('-', 80)); // Create the EC2 Launch Template. Console.WriteLine( $"Creating an EC2 launch template that runs 'server_startup_script.sh' when an instance starts.\n" + "\nThis script starts a Python web server defined in the `server.py` script. The web server\n" + "listens to HTTP requests on port 80 and responds to requests to '/' and to '/healthcheck'.\n" + "For demo purposes, this server is run as the root user. In production, the best practice is to\n" + "run a web server, such as Apache, with least-privileged credentials."); Console.WriteLine( "\nThe template also defines an IAM policy that each instance uses to assume a role that grants\n" + "permissions to access the DynamoDB recommendation table and Systems Manager parameters\n" + "that control the flow of the demo."); var startupScriptPath = Path.Join(_configuration["resourcePath"], "server_startup_script.sh"); var instancePolicyPath = Path.Join(_configuration["resourcePath"], "instance_policy.json"); await _autoScalerWrapper.CreateTemplate(startupScriptPath, instancePolicyPath); Console.WriteLine(new string('-', 80)); Console.WriteLine( "Creating an EC2 Auto Scaling group that maintains three EC2 instances, each in a different\n" + "Availability Zone.\n"); var zones = await _autoScalerWrapper.DescribeAvailabilityZones(); await _autoScalerWrapper.CreateGroupOfSize(3, _autoScalerWrapper.GroupName, zones); Console.WriteLine(new string('-', 80)); Console.WriteLine( "At this point, you have EC2 instances created. Once each instance starts, it listens for\n" + "HTTP requests. You can see these instances in the console or continue with the demo.\n"); Console.WriteLine(new string('-', 80)); Console.WriteLine("Press Enter when you're ready to continue."); if (interactive) Console.ReadLine(); Console.WriteLine("Creating variables that control the flow of the demo."); await _smParameterWrapper.Reset(); Console.WriteLine( "\nCreating an Elastic Load Balancing target group and load balancer. The target group\n" + "defines how the load balancer connects to instances. The load balancer provides a\n" + "single endpoint where clients connect and dispatches requests to instances in the group."); var defaultVpc = await _autoScalerWrapper.GetDefaultVpc(); var subnets = await _autoScalerWrapper.GetAllVpcSubnetsForZones(defaultVpc.VpcId, zones); var subnetIds = subnets.Select(s => s.SubnetId).ToList(); var targetGroup = await _elasticLoadBalancerWrapper.CreateTargetGroupOnVpc(_elasticLoadBalancerWrapper.TargetGroupName, protocol, port, defaultVpc.VpcId); await _elasticLoadBalancerWrapper.CreateLoadBalancerAndListener(_elasticLoadBalancerWrapper.LoadBalancerName, subnetIds, targetGroup); await _autoScalerWrapper.AttachLoadBalancerToGroup(_autoScalerWrapper.GroupName, targetGroup.TargetGroupArn); Console.WriteLine("\nVerifying access to the load balancer endpoint..."); var endPoint = await _elasticLoadBalancerWrapper.GetEndpointForLoadBalancerByName(_elasticLoadBalancerWrapper.LoadBalancerName); var loadBalancerAccess = await _elasticLoadBalancerWrapper.VerifyLoadBalancerEndpoint(endPoint); if (!loadBalancerAccess) { Console.WriteLine("\nCouldn't connect to the load balancer, verifying that the port is open..."); var ipString = await _httpClient.GetStringAsync("https://checkip.amazonaws.com"); ipString = ipString.Trim(); var defaultSecurityGroup = await _autoScalerWrapper.GetDefaultSecurityGroupForVpc(defaultVpc); var portIsOpen = _autoScalerWrapper.VerifyInboundPortForGroup(defaultSecurityGroup, port, ipString); var sshPortIsOpen = _autoScalerWrapper.VerifyInboundPortForGroup(defaultSecurityGroup, sshPort, ipString); if (!portIsOpen) { Console.WriteLine( "\nFor this example to work, the default security group for your default VPC must\n" + "allows access from this computer. You can either add it automatically from this\n" + "example or add it yourself using the AWS Management Console.\n"); if (!interactive || GetYesNoResponse( "Do you want to add a rule to the security group to allow inbound traffic from your computer's IP address?")) { await _autoScalerWrapper.OpenInboundPort(defaultSecurityGroup.GroupId, port, ipString); } } if (!sshPortIsOpen) { if (!interactive || GetYesNoResponse( "Do you want to add a rule to the security group to allow inbound SSH traffic for debugging from your computer's IP address?")) { await _autoScalerWrapper.OpenInboundPort(defaultSecurityGroup.GroupId, sshPort, ipString); } } loadBalancerAccess = await _elasticLoadBalancerWrapper.VerifyLoadBalancerEndpoint(endPoint); } if (loadBalancerAccess) { Console.WriteLine("Your load balancer is ready. You can access it by browsing to:"); Console.WriteLine($"\thttp://{endPoint}\n"); } else { Console.WriteLine( "\nCouldn't get a successful response from the load balancer endpoint. Troubleshoot by\n" + "manually verifying that your VPC and security group are configured correctly and that\n" + "you can successfully make a GET request to the load balancer endpoint:\n"); Console.WriteLine($"\thttp://{endPoint}\n"); } Console.WriteLine(new string('-', 80)); Console.WriteLine("Press Enter when you're ready to continue with the demo."); if (interactive) Console.ReadLine(); return true; } /// <summary> /// Demonstrate the steps of the scenario. /// </summary> /// <param name="interactive">True to run as an interactive scenario.</param> /// <returns>Async task.</returns> public static async Task<bool> Demo(bool interactive) { var ssmOnlyPolicy = Path.Join(_configuration["resourcePath"], "ssm_only_policy.json"); Console.WriteLine(new string('-', 80)); Console.WriteLine("Resetting parameters to starting values for demo."); await _smParameterWrapper.Reset(); Console.WriteLine("\nThis part of the demonstration shows how to toggle different parts of the system\n" + "to create situations where the web service fails, and shows how using a resilient\n" + "architecture can keep the web service running in spite of these failures."); Console.WriteLine(new string('-', 88)); Console.WriteLine("At the start, the load balancer endpoint returns recommendations and reports that all targets are healthy."); if (interactive) await DemoActionChoices(); Console.WriteLine($"The web service running on the EC2 instances gets recommendations by querying a DynamoDB table.\n" + $"The table name is contained in a Systems Manager parameter named '{_smParameterWrapper.TableParameter}'.\n" + $"To simulate a failure of the recommendation service, let's set this parameter to name a non-existent table.\n"); await _smParameterWrapper.PutParameterByName(_smParameterWrapper.TableParameter, "this-is-not-a-table"); Console.WriteLine("\nNow, sending a GET request to the load balancer endpoint returns a failure code. But, the service reports as\n" + "healthy to the load balancer because shallow health checks don't check for failure of the recommendation service."); if (interactive) await DemoActionChoices(); Console.WriteLine("Instead of failing when the recommendation service fails, the web service can return a static response."); Console.WriteLine("While this is not a perfect solution, it presents the customer with a somewhat better experience than failure."); await _smParameterWrapper.PutParameterByName(_smParameterWrapper.FailureResponseParameter, "static"); Console.WriteLine("\nNow, sending a GET request to the load balancer endpoint returns a static response."); Console.WriteLine("The service still reports as healthy because health checks are still shallow."); if (interactive) await DemoActionChoices(); Console.WriteLine("Let's reinstate the recommendation service.\n"); await _smParameterWrapper.PutParameterByName(_smParameterWrapper.TableParameter, _smParameterWrapper.TableName); Console.WriteLine( "\nLet's also substitute bad credentials for one of the instances in the target group so that it can't\n" + "access the DynamoDB recommendation table.\n" ); await _autoScalerWrapper.CreateInstanceProfileWithName( _autoScalerWrapper.BadCredsPolicyName, _autoScalerWrapper.BadCredsRoleName, _autoScalerWrapper.BadCredsProfileName, ssmOnlyPolicy, new List<string> { "AmazonSSMManagedInstanceCore" } ); var instances = await _autoScalerWrapper.GetInstancesByGroupName(_autoScalerWrapper.GroupName); var badInstanceId = instances.First(); var instanceProfile = await _autoScalerWrapper.GetInstanceProfile(badInstanceId); Console.WriteLine( $"Replacing the profile for instance {badInstanceId} with a profile that contains\n" + "bad credentials...\n" ); await _autoScalerWrapper.ReplaceInstanceProfile( badInstanceId, _autoScalerWrapper.BadCredsProfileName, instanceProfile.AssociationId ); Console.WriteLine( "Now, sending a GET request to the load balancer endpoint returns either a recommendation or a static response,\n" + "depending on which instance is selected by the load balancer.\n" ); if (interactive) await DemoActionChoices(); Console.WriteLine("\nLet's implement a deep health check. For this demo, a deep health check tests whether"); Console.WriteLine("the web service can access the DynamoDB table that it depends on for recommendations. Note that"); Console.WriteLine("the deep health check is only for ELB routing and not for Auto Scaling instance health."); Console.WriteLine("This kind of deep health check is not recommended for Auto Scaling instance health, because it"); Console.WriteLine("risks accidental termination of all instances in the Auto Scaling group when a dependent service fails."); Console.WriteLine("\nBy implementing deep health checks, the load balancer can detect when one of the instances is failing"); Console.WriteLine("and take that instance out of rotation."); await _smParameterWrapper.PutParameterByName(_smParameterWrapper.HealthCheckParameter, "deep"); Console.WriteLine($"\nNow, checking target health indicates that the instance with bad credentials ({badInstanceId})"); Console.WriteLine("is unhealthy. Note that it might take a minute or two for the load balancer to detect the unhealthy"); Console.WriteLine("instance. Sending a GET request to the load balancer endpoint always returns a recommendation, because"); Console.WriteLine("the load balancer takes unhealthy instances out of its rotation."); if (interactive) await DemoActionChoices(); Console.WriteLine("\nBecause the instances in this demo are controlled by an auto scaler, the simplest way to fix an unhealthy"); Console.WriteLine("instance is to terminate it and let the auto scaler start a new instance to replace it."); await _autoScalerWrapper.TryTerminateInstanceById(badInstanceId); Console.WriteLine($"\nEven while the instance is terminating and the new instance is starting, sending a GET"); Console.WriteLine("request to the web service continues to get a successful recommendation response because"); Console.WriteLine("starts and reports as healthy, it is included in the load balancing rotation."); Console.WriteLine("Note that terminating and replacing an instance typically takes several minutes, during which time you"); Console.WriteLine("can see the changing health check status until the new instance is running and healthy."); if (interactive) await DemoActionChoices(); Console.WriteLine("\nIf the recommendation service fails now, deep health checks mean all instances report as unhealthy."); await _smParameterWrapper.PutParameterByName(_smParameterWrapper.TableParameter, "this-is-not-a-table"); Console.WriteLine($"\nWhen all instances are unhealthy, the load balancer continues to route requests even to"); Console.WriteLine("unhealthy instances, allowing them to fail open and return a static response rather than fail"); Console.WriteLine("closed and report failure to the customer."); if (interactive) await DemoActionChoices(); await _smParameterWrapper.Reset(); Console.WriteLine(new string('-', 80)); return true; } /// <summary> /// Clean up the resources from the scenario. /// </summary> /// <param name="interactive">True to ask the user for cleanup.</param> /// <returns>Async task.</returns> public static async Task<bool> DestroyResources(bool interactive) { Console.WriteLine(new string('-', 80)); Console.WriteLine( "To keep things tidy and to avoid unwanted charges on your account, we can clean up all AWS resources\n" + "that were created for this demo." ); if (!interactive || GetYesNoResponse("Do you want to clean up all demo resources? (y/n) ")) { await _elasticLoadBalancerWrapper.DeleteLoadBalancerByName(_elasticLoadBalancerWrapper.LoadBalancerName); await _elasticLoadBalancerWrapper.DeleteTargetGroupByName(_elasticLoadBalancerWrapper.TargetGroupName); await _autoScalerWrapper.TerminateAndDeleteAutoScalingGroupWithName(_autoScalerWrapper.GroupName); await _autoScalerWrapper.DeleteKeyPairByName(_autoScalerWrapper.KeyPairName); await _autoScalerWrapper.DeleteTemplateByName(_autoScalerWrapper.LaunchTemplateName); await _autoScalerWrapper.DeleteInstanceProfile( _autoScalerWrapper.BadCredsProfileName, _autoScalerWrapper.BadCredsRoleName ); await _recommendations.DestroyDatabaseByName(_recommendations.TableName); } else { Console.WriteLine( "Ok, we'll leave the resources intact.\n" + "Don't forget to delete them when you're done with them or you might incur unexpected charges." ); } Console.WriteLine(new string('-', 80)); return true; }

Create a class that wraps Auto Scaling and Amazon EC2 actions.

/// <summary> /// Encapsulates Amazon EC2 Auto Scaling and EC2 management methods. /// </summary> public class AutoScalerWrapper { private readonly IAmazonAutoScaling _amazonAutoScaling; private readonly IAmazonEC2 _amazonEc2; private readonly IAmazonSimpleSystemsManagement _amazonSsm; private readonly IAmazonIdentityManagementService _amazonIam; private readonly string _instanceType = ""; private readonly string _amiParam = ""; private readonly string _launchTemplateName = ""; private readonly string _groupName = ""; private readonly string _instancePolicyName = ""; private readonly string _instanceRoleName = ""; private readonly string _instanceProfileName = ""; private readonly string _badCredsProfileName = ""; private readonly string _badCredsRoleName = ""; private readonly string _badCredsPolicyName = ""; private readonly string _keyPairName = ""; public string GroupName => _groupName; public string KeyPairName => _keyPairName; public string LaunchTemplateName => _launchTemplateName; public string InstancePolicyName => _instancePolicyName; public string BadCredsProfileName => _badCredsProfileName; public string BadCredsRoleName => _badCredsRoleName; public string BadCredsPolicyName => _badCredsPolicyName; /// <summary> /// Constructor for the AutoScalerWrapper. /// </summary> /// <param name="amazonAutoScaling">The injected AutoScaling client.</param> /// <param name="amazonEc2">The injected EC2 client.</param> /// <param name="amazonIam">The injected IAM client.</param> /// <param name="amazonSsm">The injected SSM client.</param> public AutoScalerWrapper( IAmazonAutoScaling amazonAutoScaling, IAmazonEC2 amazonEc2, IAmazonSimpleSystemsManagement amazonSsm, IAmazonIdentityManagementService amazonIam, IConfiguration configuration) { _amazonAutoScaling = amazonAutoScaling; _amazonEc2 = amazonEc2; _amazonSsm = amazonSsm; _amazonIam = amazonIam; var prefix = configuration["resourcePrefix"]; _instanceType = configuration["instanceType"]; _amiParam = configuration["amiParam"]; _launchTemplateName = prefix + "-template"; _groupName = prefix + "-group"; _instancePolicyName = prefix + "-pol"; _instanceRoleName = prefix + "-role"; _instanceProfileName = prefix + "-prof"; _badCredsPolicyName = prefix + "-bc-pol"; _badCredsRoleName = prefix + "-bc-role"; _badCredsProfileName = prefix + "-bc-prof"; _keyPairName = prefix + "-key-pair"; } /// <summary> /// Create a policy, role, and profile that is associated with instances with a specified name. /// An instance's associated profile defines a role that is assumed by the /// instance.The role has attached policies that specify the AWS permissions granted to /// clients that run on the instance. /// </summary> /// <param name="policyName">Name to use for the policy.</param> /// <param name="roleName">Name to use for the role.</param> /// <param name="profileName">Name to use for the profile.</param> /// <param name="ssmOnlyPolicyFile">Path to a policy file for SSM.</param> /// <param name="awsManagedPolicies">AWS Managed policies to be attached to the role.</param> /// <returns>The Arn of the profile.</returns> public async Task<string> CreateInstanceProfileWithName( string policyName, string roleName, string profileName, string ssmOnlyPolicyFile, List<string>? awsManagedPolicies = null) { var assumeRoleDoc = "{" + "\"Version\": \"2012-10-17\"," + "\"Statement\": [{" + "\"Effect\": \"Allow\"," + "\"Principal\": {" + "\"Service\": [" + "\"ec2.amazonaws.com\"" + "]" + "}," + "\"Action\": \"sts:AssumeRole\"" + "}]" + "}"; var policyDocument = await File.ReadAllTextAsync(ssmOnlyPolicyFile); var policyArn = ""; try { var createPolicyResult = await _amazonIam.CreatePolicyAsync( new CreatePolicyRequest { PolicyName = policyName, PolicyDocument = policyDocument }); policyArn = createPolicyResult.Policy.Arn; } catch (EntityAlreadyExistsException) { // The policy already exists, so we look it up to get the Arn. var policiesPaginator = _amazonIam.Paginators.ListPolicies( new ListPoliciesRequest() { Scope = PolicyScopeType.Local }); // Get the entire list using the paginator. await foreach (var policy in policiesPaginator.Policies) { if (policy.PolicyName.Equals(policyName)) { policyArn = policy.Arn; } } if (policyArn == null) { throw new InvalidOperationException("Policy not found"); } } try { await _amazonIam.CreateRoleAsync(new CreateRoleRequest() { RoleName = roleName, AssumeRolePolicyDocument = assumeRoleDoc, }); await _amazonIam.AttachRolePolicyAsync(new AttachRolePolicyRequest() { RoleName = roleName, PolicyArn = policyArn }); if (awsManagedPolicies != null) { foreach (var awsPolicy in awsManagedPolicies) { await _amazonIam.AttachRolePolicyAsync(new AttachRolePolicyRequest() { PolicyArn = $"arn:aws:iam::aws:policy/{awsPolicy}", RoleName = roleName }); } } } catch (EntityAlreadyExistsException) { Console.WriteLine("Role already exists."); } string profileArn = ""; try { var profileCreateResponse = await _amazonIam.CreateInstanceProfileAsync( new CreateInstanceProfileRequest() { InstanceProfileName = profileName }); // Allow time for the profile to be ready. profileArn = profileCreateResponse.InstanceProfile.Arn; Thread.Sleep(10000); await _amazonIam.AddRoleToInstanceProfileAsync( new AddRoleToInstanceProfileRequest() { InstanceProfileName = profileName, RoleName = roleName }); } catch (EntityAlreadyExistsException) { Console.WriteLine("Policy already exists."); var profileGetResponse = await _amazonIam.GetInstanceProfileAsync( new GetInstanceProfileRequest() { InstanceProfileName = profileName }); profileArn = profileGetResponse.InstanceProfile.Arn; } return profileArn; } /// <summary> /// Create a new key pair and save the file. /// </summary> /// <param name="newKeyPairName">The name of the new key pair.</param> /// <returns>Async task.</returns> public async Task CreateKeyPair(string newKeyPairName) { try { var keyResponse = await _amazonEc2.CreateKeyPairAsync( new CreateKeyPairRequest() { KeyName = newKeyPairName }); await File.WriteAllTextAsync($"{newKeyPairName}.pem", keyResponse.KeyPair.KeyMaterial); Console.WriteLine($"Created key pair {newKeyPairName}."); } catch (AlreadyExistsException) { Console.WriteLine("Key pair already exists."); } } /// <summary> /// Delete the key pair and file by name. /// </summary> /// <param name="deleteKeyPairName">The key pair to delete.</param> /// <returns>Async task.</returns> public async Task DeleteKeyPairByName(string deleteKeyPairName) { try { await _amazonEc2.DeleteKeyPairAsync( new DeleteKeyPairRequest() { KeyName = deleteKeyPairName }); File.Delete($"{deleteKeyPairName}.pem"); } catch (FileNotFoundException) { Console.WriteLine($"Key pair {deleteKeyPairName} not found."); } } /// <summary> /// Creates an Amazon EC2 launch template to use with Amazon EC2 Auto Scaling. /// The launch template specifies a Bash script in its user data field that runs after /// the instance is started. This script installs the Python packages and starts a Python /// web server on the instance. /// </summary> /// <param name="startupScriptPath">The path to a Bash script file that is run.</param> /// <param name="instancePolicyPath">The path to a permissions policy to create and attach to the profile.</param> /// <returns>The template object.</returns> public async Task<Amazon.EC2.Model.LaunchTemplate> CreateTemplate(string startupScriptPath, string instancePolicyPath) { await CreateKeyPair(_keyPairName); await CreateInstanceProfileWithName(_instancePolicyName, _instanceRoleName, _instanceProfileName, instancePolicyPath); var startServerText = await File.ReadAllTextAsync(startupScriptPath); var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(startServerText); var amiLatest = await _amazonSsm.GetParameterAsync( new GetParameterRequest() { Name = _amiParam }); var amiId = amiLatest.Parameter.Value; var launchTemplateResponse = await _amazonEc2.CreateLaunchTemplateAsync( new CreateLaunchTemplateRequest() { LaunchTemplateName = _launchTemplateName, LaunchTemplateData = new RequestLaunchTemplateData() { InstanceType = _instanceType, ImageId = amiId, IamInstanceProfile = new LaunchTemplateIamInstanceProfileSpecificationRequest() { Name = _instanceProfileName }, KeyName = _keyPairName, UserData = System.Convert.ToBase64String(plainTextBytes) } }); return launchTemplateResponse.LaunchTemplate; } /// <summary> /// Get a list of Availability Zones in the AWS Region of the Amazon EC2 Client. /// </summary> /// <returns>A list of availability zones.</returns> public async Task<List<string>> DescribeAvailabilityZones() { var zoneResponse = await _amazonEc2.DescribeAvailabilityZonesAsync( new DescribeAvailabilityZonesRequest()); return zoneResponse.AvailabilityZones.Select(z => z.ZoneName).ToList(); } /// <summary> /// Create an EC2 Auto Scaling group of a specified size and name. /// </summary> /// <param name="groupSize">The size for the group.</param> /// <param name="groupName">The name for the group.</param> /// <param name="availabilityZones">The availability zones for the group.</param> /// <returns>Async task.</returns> public async Task CreateGroupOfSize(int groupSize, string groupName, List<string> availabilityZones) { try { await _amazonAutoScaling.CreateAutoScalingGroupAsync( new CreateAutoScalingGroupRequest() { AutoScalingGroupName = groupName, AvailabilityZones = availabilityZones, LaunchTemplate = new Amazon.AutoScaling.Model.LaunchTemplateSpecification() { LaunchTemplateName = _launchTemplateName, Version = "$Default" }, MaxSize = groupSize, MinSize = groupSize }); Console.WriteLine($"Created EC2 Auto Scaling group {groupName} with size {groupSize}."); } catch (EntityAlreadyExistsException) { Console.WriteLine($"EC2 Auto Scaling group {groupName} already exists."); } } /// <summary> /// Get the default VPC for the account. /// </summary> /// <returns>The default VPC object.</returns> public async Task<Vpc> GetDefaultVpc() { var vpcResponse = await _amazonEc2.DescribeVpcsAsync( new DescribeVpcsRequest() { Filters = new List<Amazon.EC2.Model.Filter>() { new ("is-default", new List<string>() { "true" }) } }); return vpcResponse.Vpcs[0]; } /// <summary> /// Get all the subnets for a Vpc in a set of availability zones. /// </summary> /// <param name="vpcId">The Id of the Vpc.</param> /// <param name="availabilityZones">The list of availability zones.</param> /// <returns>The collection of subnet objects.</returns> public async Task<List<Subnet>> GetAllVpcSubnetsForZones(string vpcId, List<string> availabilityZones) { var subnets = new List<Subnet>(); var subnetPaginator = _amazonEc2.Paginators.DescribeSubnets( new DescribeSubnetsRequest() { Filters = new List<Amazon.EC2.Model.Filter>() { new ("vpc-id", new List<string>() { vpcId}), new ("availability-zone", availabilityZones), new ("default-for-az", new List<string>() { "true" }) } }); // Get the entire list using the paginator. await foreach (var subnet in subnetPaginator.Subnets) { subnets.Add(subnet); } return subnets; } /// <summary> /// Delete a launch template by name. /// </summary> /// <param name="templateName">The name of the template to delete.</param> /// <returns>Async task.</returns> public async Task DeleteTemplateByName(string templateName) { try { await _amazonEc2.DeleteLaunchTemplateAsync( new DeleteLaunchTemplateRequest() { LaunchTemplateName = templateName }); } catch (AmazonClientException) { Console.WriteLine($"Unable to delete template {templateName}."); } } /// <summary> /// Detaches a role from an instance profile, detaches policies from the role, /// and deletes all the resources. /// </summary> /// <param name="profileName">The name of the profile to delete.</param> /// <param name="roleName">The name of the role to delete.</param> /// <returns>Async task.</returns> public async Task DeleteInstanceProfile(string profileName, string roleName) { try { await _amazonIam.RemoveRoleFromInstanceProfileAsync( new RemoveRoleFromInstanceProfileRequest() { InstanceProfileName = profileName, RoleName = roleName }); await _amazonIam.DeleteInstanceProfileAsync( new DeleteInstanceProfileRequest() { InstanceProfileName = profileName }); var attachedPolicies = await _amazonIam.ListAttachedRolePoliciesAsync( new ListAttachedRolePoliciesRequest() { RoleName = roleName }); foreach (var policy in attachedPolicies.AttachedPolicies) { await _amazonIam.DetachRolePolicyAsync( new DetachRolePolicyRequest() { RoleName = roleName, PolicyArn = policy.PolicyArn }); // Delete the custom policies only. if (!policy.PolicyArn.StartsWith("arn:aws:iam::aws")) { await _amazonIam.DeletePolicyAsync( new Amazon.IdentityManagement.Model.DeletePolicyRequest() { PolicyArn = policy.PolicyArn }); } } await _amazonIam.DeleteRoleAsync( new DeleteRoleRequest() { RoleName = roleName }); } catch (NoSuchEntityException) { Console.WriteLine($"Instance profile {profileName} does not exist."); } } /// <summary> /// Gets data about the instances in an EC2 Auto Scaling group by its group name. /// </summary> /// <param name="group">The name of the auto scaling group.</param> /// <returns>A collection of instance Ids.</returns> public async Task<IEnumerable<string>> GetInstancesByGroupName(string group) { var instanceResponse = await _amazonAutoScaling.DescribeAutoScalingGroupsAsync( new DescribeAutoScalingGroupsRequest() { AutoScalingGroupNames = new List<string>() { group } }); var instanceIds = instanceResponse.AutoScalingGroups.SelectMany( g => g.Instances.Select(i => i.InstanceId)); return instanceIds; } /// <summary> /// Get the instance profile association data for an instance. /// </summary> /// <param name="instanceId">The Id of the instance.</param> /// <returns>Instance profile associations data.</returns> public async Task<IamInstanceProfileAssociation> GetInstanceProfile(string instanceId) { var response = await _amazonEc2.DescribeIamInstanceProfileAssociationsAsync( new DescribeIamInstanceProfileAssociationsRequest() { Filters = new List<Amazon.EC2.Model.Filter>() { new ("instance-id", new List<string>() { instanceId }) }, }); return response.IamInstanceProfileAssociations[0]; } /// <summary> /// Replace the profile associated with a running instance. After the profile is replaced, the instance /// is rebooted to ensure that it uses the new profile. When the instance is ready, Systems Manager is /// used to restart the Python web server. /// </summary> /// <param name="instanceId">The Id of the instance to update.</param> /// <param name="credsProfileName">The name of the new profile to associate with the specified instance.</param> /// <param name="associationId">The Id of the existing profile association for the instance.</param> /// <returns>Async task.</returns> public async Task ReplaceInstanceProfile(string instanceId, string credsProfileName, string associationId) { await _amazonEc2.ReplaceIamInstanceProfileAssociationAsync( new ReplaceIamInstanceProfileAssociationRequest() { AssociationId = associationId, IamInstanceProfile = new IamInstanceProfileSpecification() { Name = credsProfileName } }); // Allow time before resetting. Thread.Sleep(25000); var instanceReady = false; var retries = 5; while (retries-- > 0 && !instanceReady) { await _amazonEc2.RebootInstancesAsync( new RebootInstancesRequest(new List<string>() { instanceId })); Thread.Sleep(10000); var instancesPaginator = _amazonSsm.Paginators.DescribeInstanceInformation( new DescribeInstanceInformationRequest()); // Get the entire list using the paginator. await foreach (var instance in instancesPaginator.InstanceInformationList) { instanceReady = instance.InstanceId == instanceId; if (instanceReady) { break; } } } Console.WriteLine($"Sending restart command to instance {instanceId}"); await _amazonSsm.SendCommandAsync( new SendCommandRequest() { InstanceIds = new List<string>() { instanceId }, DocumentName = "AWS-RunShellScript", Parameters = new Dictionary<string, List<string>>() { {"commands", new List<string>() { "cd / && sudo python3 server.py 80" }} } }); Console.WriteLine($"Restarted the web server on instance {instanceId}"); } /// <summary> /// Try to terminate an instance by its Id. /// </summary> /// <param name="instanceId">The Id of the instance to terminate.</param> /// <returns>Async task.</returns> public async Task TryTerminateInstanceById(string instanceId) { var stopping = false; Console.WriteLine($"Stopping {instanceId}..."); while (!stopping) { try { await _amazonAutoScaling.TerminateInstanceInAutoScalingGroupAsync( new TerminateInstanceInAutoScalingGroupRequest() { InstanceId = instanceId, ShouldDecrementDesiredCapacity = false }); stopping = true; } catch (ScalingActivityInProgressException) { Console.WriteLine($"Scaling activity in progress for {instanceId}. Waiting..."); Thread.Sleep(10000); } } } /// <summary> /// Tries to delete the EC2 Auto Scaling group. If the group is in use or in progress, /// waits and retries until the group is successfully deleted. /// </summary> /// <param name="groupName">The name of the group to try to delete.</param> /// <returns>Async task.</returns> public async Task TryDeleteGroupByName(string groupName) { var stopped = false; while (!stopped) { try { await _amazonAutoScaling.DeleteAutoScalingGroupAsync( new DeleteAutoScalingGroupRequest() { AutoScalingGroupName = groupName }); stopped = true; } catch (Exception e) when ((e is ScalingActivityInProgressException) || (e is Amazon.AutoScaling.Model.ResourceInUseException)) { Console.WriteLine($"Some instances are still running. Waiting..."); Thread.Sleep(10000); } } } /// <summary> /// Terminate instances and delete the Auto Scaling group by name. /// </summary> /// <param name="groupName">The name of the group to delete.</param> /// <returns>Async task.</returns> public async Task TerminateAndDeleteAutoScalingGroupWithName(string groupName) { var describeGroupsResponse = await _amazonAutoScaling.DescribeAutoScalingGroupsAsync( new DescribeAutoScalingGroupsRequest() { AutoScalingGroupNames = new List<string>() { groupName } }); if (describeGroupsResponse.AutoScalingGroups.Any()) { // Update the size to 0. await _amazonAutoScaling.UpdateAutoScalingGroupAsync( new UpdateAutoScalingGroupRequest() { AutoScalingGroupName = groupName, MinSize = 0 }); var group = describeGroupsResponse.AutoScalingGroups[0]; foreach (var instance in group.Instances) { await TryTerminateInstanceById(instance.InstanceId); } await TryDeleteGroupByName(groupName); } else { Console.WriteLine($"No groups found with name {groupName}."); } } /// <summary> /// Get the default security group for a specified Vpc. /// </summary> /// <param name="vpc">The Vpc to search.</param> /// <returns>The default security group.</returns> public async Task<SecurityGroup> GetDefaultSecurityGroupForVpc(Vpc vpc) { var groupResponse = await _amazonEc2.DescribeSecurityGroupsAsync( new DescribeSecurityGroupsRequest() { Filters = new List<Amazon.EC2.Model.Filter>() { new ("group-name", new List<string>() { "default" }), new ("vpc-id", new List<string>() { vpc.VpcId }) } }); return groupResponse.SecurityGroups[0]; } /// <summary> /// Verify the default security group of a Vpc allows ingress from the calling computer. /// This can be done by allowing ingress from this computer's IP address. /// In some situations, such as connecting from a corporate network, you must instead specify /// a prefix list Id. You can also temporarily open the port to any IP address while running this example. /// If you do, be sure to remove public access when you're done. /// </summary> /// <param name="vpc">The group to check.</param> /// <param name="port">The port to verify.</param> /// <param name="ipAddress">This computer's IP address.</param> /// <returns>True if the ip address is allowed on the group.</returns> public bool VerifyInboundPortForGroup(SecurityGroup group, int port, string ipAddress) { var portIsOpen = false; foreach (var ipPermission in group.IpPermissions) { if (ipPermission.FromPort == port) { foreach (var ipRange in ipPermission.Ipv4Ranges) { var cidr = ipRange.CidrIp; if (cidr.StartsWith(ipAddress) || cidr == "0.0.0.0/0") { portIsOpen = true; } } if (ipPermission.PrefixListIds.Any()) { portIsOpen = true; } if (!portIsOpen) { Console.WriteLine("The inbound rule does not appear to be open to either this computer's IP\n" + "address, to all IP addresses (0.0.0.0/0), or to a prefix list ID."); } else { break; } } } return portIsOpen; } /// <summary> /// Add an ingress rule to the specified security group that allows access on the /// specified port from the specified IP address. /// </summary> /// <param name="groupId">The Id of the security group to modify.</param> /// <param name="port">The port to open.</param> /// <param name="ipAddress">The IP address to allow access.</param> /// <returns>Async task.</returns> public async Task OpenInboundPort(string groupId, int port, string ipAddress) { await _amazonEc2.AuthorizeSecurityGroupIngressAsync( new AuthorizeSecurityGroupIngressRequest() { GroupId = groupId, IpPermissions = new List<IpPermission>() { new IpPermission() { FromPort = port, ToPort = port, IpProtocol = "tcp", Ipv4Ranges = new List<IpRange>() { new IpRange() { CidrIp = $"{ipAddress}/32" } } } } }); } /// <summary> /// Attaches an Elastic Load Balancing (ELB) target group to this EC2 Auto Scaling group. /// The /// </summary> /// <param name="autoScalingGroupName">The name of the Auto Scaling group.</param> /// <param name="targetGroupArn">The Arn for the target group.</param> /// <returns>Async task.</returns> public async Task AttachLoadBalancerToGroup(string autoScalingGroupName, string targetGroupArn) { await _amazonAutoScaling.AttachLoadBalancerTargetGroupsAsync( new AttachLoadBalancerTargetGroupsRequest() { AutoScalingGroupName = autoScalingGroupName, TargetGroupARNs = new List<string>() { targetGroupArn } }); } }

Create a class that wraps Elastic Load Balancing actions.

/// <summary> /// Encapsulates Elastic Load Balancer actions. /// </summary> public class ElasticLoadBalancerWrapper { private readonly IAmazonElasticLoadBalancingV2 _amazonElasticLoadBalancingV2; private string? _endpoint = null; private readonly string _targetGroupName = ""; private readonly string _loadBalancerName = ""; HttpClient _httpClient = new(); public string TargetGroupName => _targetGroupName; public string LoadBalancerName => _loadBalancerName; /// <summary> /// Constructor for the Elastic Load Balancer wrapper. /// </summary> /// <param name="amazonElasticLoadBalancingV2">The injected load balancing v2 client.</param> /// <param name="configuration">The injected configuration.</param> public ElasticLoadBalancerWrapper( IAmazonElasticLoadBalancingV2 amazonElasticLoadBalancingV2, IConfiguration configuration) { _amazonElasticLoadBalancingV2 = amazonElasticLoadBalancingV2; var prefix = configuration["resourcePrefix"]; _targetGroupName = prefix + "-tg"; _loadBalancerName = prefix + "-lb"; } /// <summary> /// Get the HTTP Endpoint of a load balancer by its name. /// </summary> /// <param name="loadBalancerName">The name of the load balancer.</param> /// <returns>The HTTP endpoint.</returns> public async Task<string> GetEndpointForLoadBalancerByName(string loadBalancerName) { if (_endpoint == null) { var endpointResponse = await _amazonElasticLoadBalancingV2.DescribeLoadBalancersAsync( new DescribeLoadBalancersRequest() { Names = new List<string>() { loadBalancerName } }); _endpoint = endpointResponse.LoadBalancers[0].DNSName; } return _endpoint; } /// <summary> /// Return the GET response for an endpoint as text. /// </summary> /// <param name="endpoint">The endpoint for the request.</param> /// <returns>The request response.</returns> public async Task<string> GetEndPointResponse(string endpoint) { var endpointResponse = await _httpClient.GetAsync($"http://{endpoint}"); var textResponse = await endpointResponse.Content.ReadAsStringAsync(); return textResponse!; } /// <summary> /// Get the target health for a group by name. /// </summary> /// <param name="groupName">The name of the group.</param> /// <returns>The collection of health descriptions.</returns> public async Task<List<TargetHealthDescription>> CheckTargetHealthForGroup(string groupName) { List<TargetHealthDescription> result = null!; try { var groupResponse = await _amazonElasticLoadBalancingV2.DescribeTargetGroupsAsync( new DescribeTargetGroupsRequest() { Names = new List<string>() { groupName } }); var healthResponse = await _amazonElasticLoadBalancingV2.DescribeTargetHealthAsync( new DescribeTargetHealthRequest() { TargetGroupArn = groupResponse.TargetGroups[0].TargetGroupArn }); ; result = healthResponse.TargetHealthDescriptions; } catch (TargetGroupNotFoundException) { Console.WriteLine($"Target group {groupName} not found."); } return result; } /// <summary> /// Create an Elastic Load Balancing target group. The target group specifies how the load balancer forwards /// requests to instances in the group and how instance health is checked. /// /// To speed up this demo, the health check is configured with shortened times and lower thresholds. In production, /// you might want to decrease the sensitivity of your health checks to avoid unwanted failures. /// </summary> /// <param name="groupName">The name for the group.</param> /// <param name="protocol">The protocol, such as HTTP.</param> /// <param name="port">The port to use to forward requests, such as 80.</param> /// <param name="vpcId">The Id of the Vpc in which the load balancer exists.</param> /// <returns>The new TargetGroup object.</returns> public async Task<TargetGroup> CreateTargetGroupOnVpc(string groupName, ProtocolEnum protocol, int port, string vpcId) { var createResponse = await _amazonElasticLoadBalancingV2.CreateTargetGroupAsync( new CreateTargetGroupRequest() { Name = groupName, Protocol = protocol, Port = port, HealthCheckPath = "/healthcheck", HealthCheckIntervalSeconds = 10, HealthCheckTimeoutSeconds = 5, HealthyThresholdCount = 2, UnhealthyThresholdCount = 2, VpcId = vpcId }); var targetGroup = createResponse.TargetGroups[0]; return targetGroup; } /// <summary> /// Create an Elastic Load Balancing load balancer that uses the specified subnets /// and forwards requests to the specified target group. /// </summary> /// <param name="name">The name for the new load balancer.</param> /// <param name="subnetIds">Subnets for the load balancer.</param> /// <param name="targetGroup">Target group for forwarded requests.</param> /// <returns>The new LoadBalancer object.</returns> public async Task<LoadBalancer> CreateLoadBalancerAndListener(string name, List<string> subnetIds, TargetGroup targetGroup) { var createLbResponse = await _amazonElasticLoadBalancingV2.CreateLoadBalancerAsync( new CreateLoadBalancerRequest() { Name = name, Subnets = subnetIds }); var loadBalancerArn = createLbResponse.LoadBalancers[0].LoadBalancerArn; // Wait for load balancer to be available. var loadBalancerReady = false; while (!loadBalancerReady) { try { var describeResponse = await _amazonElasticLoadBalancingV2.DescribeLoadBalancersAsync( new DescribeLoadBalancersRequest() { Names = new List<string>() { name } }); var loadBalancerState = describeResponse.LoadBalancers[0].State.Code; loadBalancerReady = loadBalancerState == LoadBalancerStateEnum.Active; } catch (LoadBalancerNotFoundException) { loadBalancerReady = false; } Thread.Sleep(10000); } // Create the listener. await _amazonElasticLoadBalancingV2.CreateListenerAsync( new CreateListenerRequest() { LoadBalancerArn = loadBalancerArn, Protocol = targetGroup.Protocol, Port = targetGroup.Port, DefaultActions = new List<Action>() { new Action() { Type = ActionTypeEnum.Forward, TargetGroupArn = targetGroup.TargetGroupArn } } }); return createLbResponse.LoadBalancers[0]; } /// <summary> /// Verify this computer can successfully send a GET request to the /// load balancer endpoint. /// </summary> /// <param name="endpoint">The endpoint to check.</param> /// <returns>True if successful.</returns> public async Task<bool> VerifyLoadBalancerEndpoint(string endpoint) { var success = false; var retries = 3; while (!success && retries > 0) { try { var endpointResponse = await _httpClient.GetAsync($"http://{endpoint}"); Console.WriteLine($"Response: {endpointResponse.StatusCode}."); if (endpointResponse.IsSuccessStatusCode) { success = true; } else { retries = 0; } } catch (HttpRequestException) { Console.WriteLine("Connection error, retrying..."); retries--; Thread.Sleep(10000); } } return success; } /// <summary> /// Delete a load balancer by its specified name. /// </summary> /// <param name="name">The name of the load balancer to delete.</param> /// <returns>Async task.</returns> public async Task DeleteLoadBalancerByName(string name) { try { var describeLoadBalancerResponse = await _amazonElasticLoadBalancingV2.DescribeLoadBalancersAsync( new DescribeLoadBalancersRequest() { Names = new List<string>() { name } }); var lbArn = describeLoadBalancerResponse.LoadBalancers[0].LoadBalancerArn; await _amazonElasticLoadBalancingV2.DeleteLoadBalancerAsync( new DeleteLoadBalancerRequest() { LoadBalancerArn = lbArn } ); } catch (LoadBalancerNotFoundException) { Console.WriteLine($"Load balancer {name} not found."); } } /// <summary> /// Delete a TargetGroup by its specified name. /// </summary> /// <param name="groupName">Name of the group to delete.</param> /// <returns>Async task.</returns> public async Task DeleteTargetGroupByName(string groupName) { var done = false; while (!done) { try { var groupResponse = await _amazonElasticLoadBalancingV2.DescribeTargetGroupsAsync( new DescribeTargetGroupsRequest() { Names = new List<string>() { groupName } }); var targetArn = groupResponse.TargetGroups[0].TargetGroupArn; await _amazonElasticLoadBalancingV2.DeleteTargetGroupAsync( new DeleteTargetGroupRequest() { TargetGroupArn = targetArn }); Console.WriteLine($"Deleted load balancing target group {groupName}."); done = true; } catch (TargetGroupNotFoundException) { Console.WriteLine( $"Target group {groupName} not found, could not delete."); done = true; } catch (ResourceInUseException) { Console.WriteLine("Target group not yet released, waiting..."); Thread.Sleep(10000); } } } }

Create a class that uses DynamoDB to simulate a recommendation service.

/// <summary> /// Encapsulates a DynamoDB table to use as a service that recommends books, movies, and songs. /// </summary> public class Recommendations { private readonly IAmazonDynamoDB _amazonDynamoDb; private readonly DynamoDBContext _context; private readonly string _tableName; public string TableName => _tableName; /// <summary> /// Constructor for the Recommendations service. /// </summary> /// <param name="amazonDynamoDb">The injected DynamoDb client.</param> /// <param name="configuration">The injected configuration.</param> public Recommendations(IAmazonDynamoDB amazonDynamoDb, IConfiguration configuration) { _amazonDynamoDb = amazonDynamoDb; _context = new DynamoDBContext(_amazonDynamoDb); _tableName = configuration["databaseName"]!; } /// <summary> /// Create the DynamoDb table with a specified name. /// </summary> /// <param name="tableName">The name for the table.</param> /// <returns>True when ready.</returns> public async Task<bool> CreateDatabaseWithName(string tableName) { try { Console.Write($"Creating table {tableName}..."); var createRequest = new CreateTableRequest() { TableName = tableName, AttributeDefinitions = new List<AttributeDefinition>() { new AttributeDefinition() { AttributeName = "MediaType", AttributeType = ScalarAttributeType.S }, new AttributeDefinition() { AttributeName = "ItemId", AttributeType = ScalarAttributeType.N } }, KeySchema = new List<KeySchemaElement>() { new KeySchemaElement() { AttributeName = "MediaType", KeyType = KeyType.HASH }, new KeySchemaElement() { AttributeName = "ItemId", KeyType = KeyType.RANGE } }, ProvisionedThroughput = new ProvisionedThroughput() { ReadCapacityUnits = 5, WriteCapacityUnits = 5 } }; await _amazonDynamoDb.CreateTableAsync(createRequest); // Wait until the table is ACTIVE and then report success. Console.Write("\nWaiting for table to become active..."); var request = new DescribeTableRequest { TableName = tableName }; TableStatus status; do { Thread.Sleep(2000); var describeTableResponse = await _amazonDynamoDb.DescribeTableAsync(request); status = describeTableResponse.Table.TableStatus; Console.Write("."); } while (status != "ACTIVE"); return status == TableStatus.ACTIVE; } catch (ResourceInUseException) { Console.WriteLine($"Table {tableName} already exists."); return false; } } /// <summary> /// Populate the database table with data from a specified path. /// </summary> /// <param name="databaseTableName">The name of the table.</param> /// <param name="recommendationsPath">The path of the recommendations data.</param> /// <returns>Async task.</returns> public async Task PopulateDatabase(string databaseTableName, string recommendationsPath) { var recommendationsText = await File.ReadAllTextAsync(recommendationsPath); var records = JsonSerializer.Deserialize<RecommendationModel[]>(recommendationsText); var batchWrite = _context.CreateBatchWrite<RecommendationModel>(); foreach (var record in records!) { batchWrite.AddPutItem(record); } await batchWrite.ExecuteAsync(); } /// <summary> /// Delete the recommendation table by name. /// </summary> /// <param name="tableName">The name of the recommendation table.</param> /// <returns>Async task.</returns> public async Task DestroyDatabaseByName(string tableName) { try { await _amazonDynamoDb.DeleteTableAsync( new DeleteTableRequest() { TableName = tableName }); Console.WriteLine($"Table {tableName} was deleted."); } catch (ResourceNotFoundException) { Console.WriteLine($"Table {tableName} not found"); } } }

Create a class that wraps Systems Manager actions.

/// <summary> /// Encapsulates Systems Manager parameter operations. This example uses these parameters /// to drive the demonstration of resilient architecture, such as failure of a dependency or /// how the service responds to a health check. /// </summary> public class SmParameterWrapper { private readonly IAmazonSimpleSystemsManagement _amazonSimpleSystemsManagement; private readonly string _tableParameter = "doc-example-resilient-architecture-table"; private readonly string _failureResponseParameter = "doc-example-resilient-architecture-failure-response"; private readonly string _healthCheckParameter = "doc-example-resilient-architecture-health-check"; private readonly string _tableName = ""; public string TableParameter => _tableParameter; public string TableName => _tableName; public string HealthCheckParameter => _healthCheckParameter; public string FailureResponseParameter => _failureResponseParameter; /// <summary> /// Constructor for the SmParameterWrapper. /// </summary> /// <param name="amazonSimpleSystemsManagement">The injected Simple Systems Management client.</param> /// <param name="configuration">The injected configuration.</param> public SmParameterWrapper(IAmazonSimpleSystemsManagement amazonSimpleSystemsManagement, IConfiguration configuration) { _amazonSimpleSystemsManagement = amazonSimpleSystemsManagement; _tableName = configuration["databaseName"]!; } /// <summary> /// Reset the Systems Manager parameters to starting values for the demo. /// </summary> /// <returns>Async task.</returns> public async Task Reset() { await this.PutParameterByName(_tableParameter, _tableName); await this.PutParameterByName(_failureResponseParameter, "none"); await this.PutParameterByName(_healthCheckParameter, "shallow"); } /// <summary> /// Set the value of a named Systems Manager parameter. /// </summary> /// <param name="name">The name of the parameter.</param> /// <param name="value">The value to set.</param> /// <returns>Async task.</returns> public async Task PutParameterByName(string name, string value) { await _amazonSimpleSystemsManagement.PutParameterAsync( new PutParameterRequest() { Name = name, Value = value, Overwrite = true }); } }

The following code example shows how to:

  • Create an Amazon EC2 Auto Scaling group with a launch template and Availability Zones, and get information about running instances.

  • Enable Amazon CloudWatch metrics collection.

  • Update the group's desired capacity and wait for an instance to start.

  • Terminate an instance in the group.

  • List scaling activities that occur in response to user requests and capacity changes.

  • Get statistics for CloudWatch metrics, then clean up resources.

Amazon SDK for .NET
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the Amazon Code Examples Repository.

global using Amazon.AutoScaling; global using Amazon.AutoScaling.Model; global using Amazon.CloudWatch; global using AutoScalingActions; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Logging.Console; global using Microsoft.Extensions.Logging.Debug; using Amazon.EC2; using Microsoft.Extensions.Configuration; using Host = Microsoft.Extensions.Hosting.Host; namespace AutoScalingBasics; public class AutoScalingBasics { static async Task Main(string[] args) { // Set up dependency injection for Amazon EC2 Auto Scaling, Amazon // CloudWatch, and Amazon EC2. using var host = Host.CreateDefaultBuilder(args) .ConfigureLogging(logging => logging.AddFilter("System", LogLevel.Debug) .AddFilter<DebugLoggerProvider>("Microsoft", LogLevel.Information) .AddFilter<ConsoleLoggerProvider>("Microsoft", LogLevel.Trace)) .ConfigureServices((_, services) => services.AddAWSService<IAmazonAutoScaling>() .AddAWSService<IAmazonCloudWatch>() .AddAWSService<IAmazonEC2>() .AddTransient<AutoScalingWrapper>() .AddTransient<CloudWatchWrapper>() .AddTransient<EC2Wrapper>() .AddTransient<UIWrapper>() ) .Build(); var autoScalingWrapper = host.Services.GetRequiredService<AutoScalingWrapper>(); var cloudWatchWrapper = host.Services.GetRequiredService<CloudWatchWrapper>(); var ec2Wrapper = host.Services.GetRequiredService<EC2Wrapper>(); var uiWrapper = host.Services.GetRequiredService<UIWrapper>(); var configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("settings.json") // Load test settings from .json file. .AddJsonFile("settings.local.json", true) // Optionally load local settings. .Build(); var imageId = configuration["ImageId"]; var instanceType = configuration["InstanceType"]; var launchTemplateName = configuration["LaunchTemplateName"]; launchTemplateName += Guid.NewGuid().ToString(); // The name of the Auto Scaling group. var groupName = configuration["GroupName"]; uiWrapper.DisplayTitle("Auto Scaling Basics"); uiWrapper.DisplayAutoScalingBasicsDescription(); // Create the launch template and save the template Id to use when deleting the // launch template at the end of the application. var launchTemplateId = await ec2Wrapper.CreateLaunchTemplateAsync(imageId!, instanceType!, launchTemplateName); // Confirm that the template was created by asking for a description of it. await ec2Wrapper.DescribeLaunchTemplateAsync(launchTemplateName); uiWrapper.PressEnter(); var availabilityZones = await ec2Wrapper.ListAvailabilityZonesAsync(); Console.WriteLine($"Creating an Auto Scaling group named {groupName}."); await autoScalingWrapper.CreateAutoScalingGroupAsync( groupName!, launchTemplateName, availabilityZones.First().ZoneName); // Keep checking the details of the new group until its lifecycle state // is "InService". Console.WriteLine($"Waiting for the Auto Scaling group to be active."); List<AutoScalingInstanceDetails> instanceDetails; do { instanceDetails = await autoScalingWrapper.DescribeAutoScalingInstancesAsync(groupName!); } while (instanceDetails.Count <= 0); Console.WriteLine($"Auto scaling group {groupName} successfully created."); Console.WriteLine($"{instanceDetails.Count} instances were created for the group."); // Display the details of the Auto Scaling group. instanceDetails.ForEach(detail => { Console.WriteLine($"Group name: {detail.AutoScalingGroupName}"); }); uiWrapper.PressEnter(); uiWrapper.DisplayTitle("Metrics collection"); Console.WriteLine($"Enable metrics collection for {groupName}"); await autoScalingWrapper.EnableMetricsCollectionAsync(groupName!); // Show the metrics that are collected for the group. // Update the maximum size of the group to three instances. Console.WriteLine("--- Update the Auto Scaling group to increase max size to 3 ---"); int maxSize = 3; await autoScalingWrapper.UpdateAutoScalingGroupAsync(groupName!, launchTemplateName, maxSize); Console.WriteLine("--- Describe all Auto Scaling groups to show the current state of the group ---"); var groups = await autoScalingWrapper.DescribeAutoScalingGroupsAsync(groupName!); uiWrapper.DisplayGroupDetails(groups!); uiWrapper.PressEnter(); uiWrapper.DisplayTitle("Describe account limits"); await autoScalingWrapper.DescribeAccountLimitsAsync(); uiWrapper.WaitABit(60, "Waiting for the resources to be ready."); uiWrapper.DisplayTitle("Set desired capacity"); int desiredCapacity = 2; await autoScalingWrapper.SetDesiredCapacityAsync(groupName!, desiredCapacity); Console.WriteLine("Get the two instance Id values"); // Empty the group before getting the details again. groups!.Clear(); groups = await autoScalingWrapper.DescribeAutoScalingGroupsAsync(groupName!); if (groups is not null) { foreach (AutoScalingGroup group in groups) { Console.WriteLine($"The group name is {group.AutoScalingGroupName}"); Console.WriteLine($"The group ARN is {group.AutoScalingGroupARN}"); var instances = group.Instances; foreach (Amazon.AutoScaling.Model.Instance instance in instances) { Console.WriteLine($"The instance id is {instance.InstanceId}"); Console.WriteLine($"The lifecycle state is {instance.LifecycleState}"); } } } uiWrapper.DisplayTitle("Scaling Activities"); Console.WriteLine("Let's list the scaling activities that have occurred for the group."); var activities = await autoScalingWrapper.DescribeScalingActivitiesAsync(groupName!); if (activities is not null) { activities.ForEach(activity => { Console.WriteLine($"The activity Id is {activity.ActivityId}"); Console.WriteLine($"The activity details are {activity.Details}"); }); } // Display the Amazon CloudWatch metrics that have been collected. var metrics = await cloudWatchWrapper.GetCloudWatchMetricsAsync(groupName!); Console.WriteLine($"Metrics collected for {groupName}:"); metrics.ForEach(metric => { Console.Write($"Metric name: {metric.MetricName}\t"); Console.WriteLine($"Namespace: {metric.Namespace}"); }); var dataPoints = await cloudWatchWrapper.GetMetricStatisticsAsync(groupName!); Console.WriteLine("Details for the metrics collected:"); dataPoints.ForEach(detail => { Console.WriteLine(detail); }); // Disable metrics collection. Console.WriteLine("Disabling the collection of metrics for {groupName}."); var success = await autoScalingWrapper.DisableMetricsCollectionAsync(groupName!); if (success) { Console.WriteLine($"Successfully stopped metrics collection for {groupName}."); } else { Console.WriteLine($"Could not stop metrics collection for {groupName}."); } // Terminate all instances in the group. uiWrapper.DisplayTitle("Terminating Auto Scaling instances"); Console.WriteLine("Now terminating all instances in the Auto Scaling group."); if (groups is not null) { groups.ForEach(group => { // Only delete instances in the AutoScaling group we created. if (group.AutoScalingGroupName == groupName) { group.Instances.ForEach(async instance => { await autoScalingWrapper.TerminateInstanceInAutoScalingGroupAsync(instance.InstanceId); }); } }); } // After all instances are terminated, delete the group. uiWrapper.DisplayTitle("Clean up resources"); Console.WriteLine("Deleting the Auto Scaling group."); await autoScalingWrapper.DeleteAutoScalingGroupAsync(groupName!); // Delete the launch template. var deletedLaunchTemplateName = await ec2Wrapper.DeleteLaunchTemplateAsync(launchTemplateId); if (deletedLaunchTemplateName == launchTemplateName) { Console.WriteLine("Successfully deleted the launch template."); } Console.WriteLine("The demo is now concluded."); } } namespace AutoScalingBasics; /// <summary> /// A class to provide user interface methods for the EC2 AutoScaling Basics /// scenario. /// </summary> public class UIWrapper { public readonly string SepBar = new('-', Console.WindowWidth); /// <summary> /// Describe the steps in the EC2 AutoScaling Basics scenario. /// </summary> public void DisplayAutoScalingBasicsDescription() { Console.WriteLine("This code example performs the following operations:"); Console.WriteLine(" 1. Creates an Amazon EC2 launch template."); Console.WriteLine(" 2. Creates an Auto Scaling group."); Console.WriteLine(" 3. Shows the details of the new Auto Scaling group"); Console.WriteLine(" to show that only one instance was created."); Console.WriteLine(" 4. Enables metrics collection."); Console.WriteLine(" 5. Updates the Auto Scaling group to increase the"); Console.WriteLine(" capacity to three."); Console.WriteLine(" 6. Describes Auto Scaling groups again to show the"); Console.WriteLine(" current state of the group."); Console.WriteLine(" 7. Changes the desired capacity of the Auto Scaling"); Console.WriteLine(" group to use an additional instance."); Console.WriteLine(" 8. Shows that there are now instances in the group."); Console.WriteLine(" 9. Lists the scaling activities that have occurred for the group."); Console.WriteLine("10. Displays the Amazon CloudWatch metrics that have"); Console.WriteLine(" been collected."); Console.WriteLine("11. Disables metrics collection."); Console.WriteLine("12. Terminates all instances in the Auto Scaling group."); Console.WriteLine("13. Deletes the Auto Scaling group."); Console.WriteLine("14. Deletes the Amazon EC2 launch template."); PressEnter(); } /// <summary> /// Display information about the Amazon Ec2 AutoScaling groups passed /// in the list of AutoScalingGroup objects. /// </summary> /// <param name="groups">A list of AutoScalingGroup objects.</param> public void DisplayGroupDetails(List<AutoScalingGroup> groups) { if (groups is null) return; groups.ForEach(group => { Console.WriteLine($"Group name:\t{group.AutoScalingGroupName}"); Console.WriteLine($"Group created:\t{group.CreatedTime}"); Console.WriteLine($"Maximum number of instances:\t{group.MaxSize}"); Console.WriteLine($"Desired number of instances:\t{group.DesiredCapacity}"); }); } /// <summary> /// Display a message and wait until the user presses enter. /// </summary> public void PressEnter() { Console.Write("\nPress <Enter> to continue. "); _ = Console.ReadLine(); Console.WriteLine(); } /// <summary> /// Pad a string with spaces to center it on the console display. /// </summary> /// <param name="strToCenter">The string to be centered.</param> /// <returns>The padded string.</returns> public string CenterString(string strToCenter) { var padAmount = (Console.WindowWidth - strToCenter.Length) / 2; var leftPad = new string(' ', padAmount); return $"{leftPad}{strToCenter}"; } /// <summary> /// Display a line of hyphens, the centered text of the title and another /// line of hyphens. /// </summary> /// <param name="strTitle">The string to be displayed.</param> public void DisplayTitle(string strTitle) { Console.WriteLine(SepBar); Console.WriteLine(CenterString(strTitle)); Console.WriteLine(SepBar); } /// <summary> /// Display a countdown and wait for a number of seconds. /// </summary> /// <param name="numSeconds">The number of seconds to wait.</param> public void WaitABit(int numSeconds, string msg) { Console.WriteLine(msg); // Wait for the requested number of seconds. for (int i = numSeconds; i > 0; i--) { System.Threading.Thread.Sleep(1000); Console.Write($"{i}..."); } PressEnter(); } }

Define functions that are called by the scenario to manage launch templates and metrics. These functions wrap Auto Scaling, Amazon EC2, and CloudWatch actions.

namespace AutoScalingActions; using Amazon.AutoScaling; using Amazon.AutoScaling.Model; /// <summary> /// A class that includes methods to perform Amazon EC2 Auto Scaling /// actions. /// </summary> public class AutoScalingWrapper { private readonly IAmazonAutoScaling _amazonAutoScaling; /// <summary> /// Constructor for the AutoScalingWrapper class. /// </summary> /// <param name="amazonAutoScaling">The injected Amazon EC2 Auto Scaling client.</param> public AutoScalingWrapper(IAmazonAutoScaling amazonAutoScaling) { _amazonAutoScaling = amazonAutoScaling; } /// <summary> /// Create a new Amazon EC2 Auto Scaling group. /// </summary> /// <param name="groupName">The name to use for the new Auto Scaling /// group.</param> /// <param name="launchTemplateName">The name of the Amazon EC2 Auto Scaling /// launch template to use to create instances in the group.</param> /// <returns>A Boolean value indicating the success of the action.</returns> public async Task<bool> CreateAutoScalingGroupAsync( string groupName, string launchTemplateName, string availabilityZone) { var templateSpecification = new LaunchTemplateSpecification { LaunchTemplateName = launchTemplateName, }; var zoneList = new List<string> { availabilityZone, }; var request = new CreateAutoScalingGroupRequest { AutoScalingGroupName = groupName, AvailabilityZones = zoneList, LaunchTemplate = templateSpecification, MaxSize = 6, MinSize = 1 }; var response = await _amazonAutoScaling.CreateAutoScalingGroupAsync(request); Console.WriteLine($"{groupName} Auto Scaling Group created"); return response.HttpStatusCode == System.Net.HttpStatusCode.OK; } /// <summary> /// Retrieve information about Amazon EC2 Auto Scaling quotas to the /// active AWS account. /// </summary> /// <returns>A Boolean value indicating the success of the action.</returns> public async Task<bool> DescribeAccountLimitsAsync() { var response = await _amazonAutoScaling.DescribeAccountLimitsAsync(); Console.WriteLine("The maximum number of Auto Scaling groups is " + response.MaxNumberOfAutoScalingGroups); Console.WriteLine("The current number of Auto Scaling groups is " + response.NumberOfAutoScalingGroups); return response.HttpStatusCode == System.Net.HttpStatusCode.OK; } /// <summary> /// Retrieve a list of the Amazon EC2 Auto Scaling activities for an /// Amazon EC2 Auto Scaling group. /// </summary> /// <param name="groupName">The name of the Amazon EC2 Auto Scaling group.</param> /// <returns>A list of Amazon EC2 Auto Scaling activities.</returns> public async Task<List<Amazon.AutoScaling.Model.Activity>> DescribeScalingActivitiesAsync( string groupName) { var scalingActivitiesRequest = new DescribeScalingActivitiesRequest { AutoScalingGroupName = groupName, MaxRecords = 10, }; var response = await _amazonAutoScaling.DescribeScalingActivitiesAsync(scalingActivitiesRequest); return response.Activities; } /// <summary> /// Get data about the instances in an Amazon EC2 Auto Scaling group. /// </summary> /// <param name="groupName">The name of the Amazon EC2 Auto Scaling group.</param> /// <returns>A list of Amazon EC2 Auto Scaling details.</returns> public async Task<List<AutoScalingInstanceDetails>> DescribeAutoScalingInstancesAsync( string groupName) { var groups = await DescribeAutoScalingGroupsAsync(groupName); var instanceIds = new List<string>(); groups!.ForEach(group => { if (group.AutoScalingGroupName == groupName) { group.Instances.ForEach(instance => { instanceIds.Add(instance.InstanceId); }); } }); var scalingGroupsRequest = new DescribeAutoScalingInstancesRequest { MaxRecords = 10, InstanceIds = instanceIds, }; var response = await _amazonAutoScaling.DescribeAutoScalingInstancesAsync(scalingGroupsRequest); var instanceDetails = response.AutoScalingInstances; return instanceDetails; } /// <summary> /// Retrieve a list of information about Amazon EC2 Auto Scaling groups. /// </summary> /// <param name="groupName">The name of the Amazon EC2 Auto Scaling group.</param> /// <returns>A list of Amazon EC2 Auto Scaling groups.</returns> public async Task<List<AutoScalingGroup>?> DescribeAutoScalingGroupsAsync( string groupName) { var groupList = new List<string> { groupName, }; var request = new DescribeAutoScalingGroupsRequest { AutoScalingGroupNames = groupList, }; var response = await _amazonAutoScaling.DescribeAutoScalingGroupsAsync(request); var groups = response.AutoScalingGroups; return groups; } /// <summary> /// Delete an Auto Scaling group. /// </summary> /// <param name="groupName">The name of the Amazon EC2 Auto Scaling group.</param> /// <returns>A Boolean value indicating the success of the action.</returns> public async Task<bool> DeleteAutoScalingGroupAsync( string groupName) { var deleteAutoScalingGroupRequest = new DeleteAutoScalingGroupRequest { AutoScalingGroupName = groupName, ForceDelete = true, }; var response = await _amazonAutoScaling.DeleteAutoScalingGroupAsync(deleteAutoScalingGroupRequest); if (response.HttpStatusCode == System.Net.HttpStatusCode.OK) { Console.WriteLine($"You successfully deleted {groupName}"); return true; } Console.WriteLine($"Couldn't delete {groupName}."); return false; } /// <summary> /// Disable the collection of metric data for an Amazon EC2 Auto Scaling /// group. /// </summary> /// <param name="groupName">The name of the Auto Scaling group.</param> /// <returns>A Boolean value that indicates the success or failure of /// the operation.</returns> public async Task<bool> DisableMetricsCollectionAsync(string groupName) { var request = new DisableMetricsCollectionRequest { AutoScalingGroupName = groupName, }; var response = await _amazonAutoScaling.DisableMetricsCollectionAsync(request); return response.HttpStatusCode == System.Net.HttpStatusCode.OK; } /// <summary> /// Enable the collection of metric data for an Auto Scaling group. /// </summary> /// <param name="groupName">The name of the Auto Scaling group.</param> /// <returns>A Boolean value indicating the success of the action.</returns> public async Task<bool> EnableMetricsCollectionAsync(string groupName) { var listMetrics = new List<string> { "GroupMaxSize", }; var collectionRequest = new EnableMetricsCollectionRequest { AutoScalingGroupName = groupName, Metrics = listMetrics, Granularity = "1Minute", }; var response = await _amazonAutoScaling.EnableMetricsCollectionAsync(collectionRequest); return response.HttpStatusCode == System.Net.HttpStatusCode.OK; } /// <summary> /// Set the desired capacity of an Auto Scaling group. /// </summary> /// <param name="groupName">The name of the Auto Scaling group.</param> /// <param name="desiredCapacity">The desired capacity for the Auto /// Scaling group.</param> /// <returns>A Boolean value indicating the success of the action.</returns> public async Task<bool> SetDesiredCapacityAsync( string groupName, int desiredCapacity) { var capacityRequest = new SetDesiredCapacityRequest { AutoScalingGroupName = groupName, DesiredCapacity = desiredCapacity, }; var response = await _amazonAutoScaling.SetDesiredCapacityAsync(capacityRequest); Console.WriteLine($"You have set the DesiredCapacity to {desiredCapacity}."); return response.HttpStatusCode == System.Net.HttpStatusCode.OK; } /// <summary> /// Terminate all instances in the Auto Scaling group in preparation for /// deleting the group. /// </summary> /// <param name="instanceId">The instance Id of the instance to terminate.</param> /// <returns>A Boolean value that indicates the success or failure of /// the operation.</returns> public async Task<bool> TerminateInstanceInAutoScalingGroupAsync( string instanceId) { var request = new TerminateInstanceInAutoScalingGroupRequest { InstanceId = instanceId, ShouldDecrementDesiredCapacity = false, }; var response = await _amazonAutoScaling.TerminateInstanceInAutoScalingGroupAsync(request); if (response.HttpStatusCode == System.Net.HttpStatusCode.OK) { Console.WriteLine($"You have terminated the instance: {instanceId}"); return true; } Console.WriteLine($"Could not terminate {instanceId}"); return false; } /// <summary> /// Update the capacity of an Auto Scaling group. /// </summary> /// <param name="groupName">The name of the Auto Scaling group.</param> /// <param name="launchTemplateName">The name of the EC2 launch template.</param> /// <param name="maxSize">The maximum number of instances that can be /// created for the Auto Scaling group.</param> /// <returns>A Boolean value indicating the success of the action.</returns> public async Task<bool> UpdateAutoScalingGroupAsync( string groupName, string launchTemplateName, int maxSize) { var templateSpecification = new LaunchTemplateSpecification { LaunchTemplateName = launchTemplateName, }; var groupRequest = new UpdateAutoScalingGroupRequest { MaxSize = maxSize, AutoScalingGroupName = groupName, LaunchTemplate = templateSpecification, }; var response = await _amazonAutoScaling.UpdateAutoScalingGroupAsync(groupRequest); if (response.HttpStatusCode == System.Net.HttpStatusCode.OK) { Console.WriteLine($"You successfully updated the Auto Scaling group {groupName}."); return true; } else { return false; } } } namespace AutoScalingActions; using Amazon.EC2; using Amazon.EC2.Model; public class EC2Wrapper { private readonly IAmazonEC2 _amazonEc2; /// <summary> /// Constructor for the EC2Wrapper class. /// </summary> /// <param name="amazonEc2">The injected Amazon EC2 client.</param> public EC2Wrapper(IAmazonEC2 amazonEc2) { _amazonEc2 = amazonEc2; } /// <summary> /// Create a new Amazon EC2 launch template. /// </summary> /// <param name="imageId">The image Id to use for instances launched /// using the Amazon EC2 launch template.</param> /// <param name="instanceType">The type of EC2 instances to create.</param> /// <param name="launchTemplateName">The name of the launch template.</param> /// <returns>Returns the TemplateID of the new launch template.</returns> public async Task<string> CreateLaunchTemplateAsync( string imageId, string instanceType, string launchTemplateName) { var request = new CreateLaunchTemplateRequest { LaunchTemplateData = new RequestLaunchTemplateData { ImageId = imageId, InstanceType = instanceType, }, LaunchTemplateName = launchTemplateName, }; var response = await _amazonEc2.CreateLaunchTemplateAsync(request); return response.LaunchTemplate.LaunchTemplateId; } /// <summary> /// Delete an Amazon EC2 launch template. /// </summary> /// <param name="launchTemplateId">The TemplateId of the launch template to /// delete.</param> /// <returns>The name of the EC2 launch template that was deleted.</returns> public async Task<string> DeleteLaunchTemplateAsync(string launchTemplateId) { var request = new DeleteLaunchTemplateRequest { LaunchTemplateId = launchTemplateId, }; var response = await _amazonEc2.DeleteLaunchTemplateAsync(request); return response.LaunchTemplate.LaunchTemplateName; } /// <summary> /// Retrieve information about an EC2 launch template. /// </summary> /// <param name="launchTemplateName">The name of the EC2 launch template.</param> /// <returns>A Boolean value that indicates the success or failure of /// the operation.</returns> public async Task<bool> DescribeLaunchTemplateAsync(string launchTemplateName) { var request = new DescribeLaunchTemplatesRequest { LaunchTemplateNames = new List<string> { launchTemplateName, }, }; var response = await _amazonEc2.DescribeLaunchTemplatesAsync(request); if (response.LaunchTemplates is not null) { response.LaunchTemplates.ForEach(template => { Console.Write($"{template.LaunchTemplateName}\t"); Console.WriteLine(template.LaunchTemplateId); }); return true; } return false; } /// <summary> /// Retrieve the availability zones for the current region. /// </summary> /// <returns>A collection of availability zones.</returns> public async Task<List<AvailabilityZone>> ListAvailabilityZonesAsync() { var response = await _amazonEc2.DescribeAvailabilityZonesAsync( new DescribeAvailabilityZonesRequest()); return response.AvailabilityZones; } } namespace AutoScalingActions; using Amazon.CloudWatch; using Amazon.CloudWatch.Model; /// <summary> /// Contains methods to access Amazon CloudWatch metrics for the /// Amazon EC2 Auto Scaling basics scenario. /// </summary> public class CloudWatchWrapper { private readonly IAmazonCloudWatch _amazonCloudWatch; /// <summary> /// Constructor for the CloudWatchWrapper. /// </summary> /// <param name="amazonCloudWatch">The injected CloudWatch client.</param> public CloudWatchWrapper(IAmazonCloudWatch amazonCloudWatch) { _amazonCloudWatch = amazonCloudWatch; } /// <summary> /// Retrieve the metrics information collection for the Auto Scaling group. /// </summary> /// <param name="groupName">The name of the Auto Scaling group.</param> /// <returns>A list of Metrics collected for the Auto Scaling group.</returns> public async Task<List<Amazon.CloudWatch.Model.Metric>> GetCloudWatchMetricsAsync(string groupName) { var filter = new DimensionFilter { Name = "AutoScalingGroupName", Value = $"{groupName}", }; var request = new ListMetricsRequest { MetricName = "AutoScalingGroupName", Dimensions = new List<DimensionFilter> { filter }, Namespace = "AWS/AutoScaling", }; var response = await _amazonCloudWatch.ListMetricsAsync(request); return response.Metrics; } /// <summary> /// Retrieve the metric data collected for an Amazon EC2 Auto Scaling group. /// </summary> /// <param name="groupName">The name of the Amazon EC2 Auto Scaling group.</param> /// <returns>A list of data points.</returns> public async Task<List<Datapoint>> GetMetricStatisticsAsync(string groupName) { var metricDimensions = new List<Dimension> { new Dimension { Name = "AutoScalingGroupName", Value = $"{groupName}", }, }; // The start time will be yesterday. var startTime = DateTime.UtcNow.AddDays(-1); var request = new GetMetricStatisticsRequest { MetricName = "AutoScalingGroupName", Dimensions = metricDimensions, Namespace = "AWS/AutoScaling", Period = 60, // 60 seconds. Statistics = new List<string>() { "Minimum" }, StartTimeUtc = startTime, EndTimeUtc = DateTime.UtcNow, }; var response = await _amazonCloudWatch.GetMetricStatisticsAsync(request); return response.Datapoints; } }