{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Create Load Balancer Auto Scaled Apache Web Server\n", "\n", "In this workshop we will explore the basics of AWS with EC2, Amazon S3, and the components required for auto scaling that will provide elasticity and durability to tradtional web applications. Python is used extensively so you will need experience in or be comfortable reading python code. \n", "\n", "![Elasticity](../../docs/assets/images/intro_load_balancing.png)\n", "\n", "### Initialize notebook\n", "\n", "We will be using the [boto3](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html) library for creation of all resources." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import boto3\n", "import sys\n", "import os\n", "import json\n", "import base64\n", "import project_path # path to helper methods\n", "import pprint\n", "\n", "from lib import workshop\n", "from botocore.exceptions import ClientError\n", "\n", "ec2_client = boto3.client('ec2')\n", "ec2 = boto3.resource('ec2')\n", "elb = boto3.client(\"elbv2\")\n", "cloudwatch = boto3.client('cloudwatch')\n", "asg = boto3.client('autoscaling')\n", "\n", "session = boto3.session.Session()\n", "region = session.region_name\n", "\n", "alb_sec_group_name = 'alb-sg'\n", "launch_config_name = 'web-lc'\n", "auto_scaling_group_name = 'web-asg'\n", "scale_up_name = 'scale_up'\n", "scale_down_name = 'scale_down'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### [Create S3 Bucket](https://docs.aws.amazon.com/AmazonS3/latest/gsg/CreatingABucket.html)\n", "\n", "We will create an S3 bucket that will be used throughout the workshop for storing data.\n", "\n", "[s3.create_bucket](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.create_bucket) boto3 documentation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "bucket = workshop.create_bucket_name('intro-')\n", "session.resource('s3').create_bucket(Bucket=bucket, CreateBucketConfiguration={'LocationConstraint': region})\n", "print(bucket)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### [Create VPC](https://aws.amazon.com/vpc/)\n", "\n", "Amazon Virtual Private Cloud (Amazon VPC) lets you provision a logically isolated section of the AWS Cloud where you can launch AWS resources in a virtual network that you define. You have complete control over your virtual networking environment, including selection of your own IP address range, creation of subnets, and configuration of route tables and network gateways. You can use both IPv4 and IPv6 in your VPC for secure and easy access to resources and applications." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "vpc, subnet, subnet2 = workshop.create_and_configure_vpc()\n", "vpc_id = vpc.id\n", "subnet_id = subnet.id\n", "subnet2_id = subnet2.id\n", "print(vpc_id)\n", "print(subnet_id)\n", "print(subnet2_id)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create index.html page for the web application\n", "\n", "We will write out a simple html page to demo setting up the Apache web server using an Application Load Balancer and Auto Scaling to provide elasticity to your web application." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%writefile index.html\n", "\n", "

Hello from the intro to AWS workshop!!!

" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### [Upload to S3](https://docs.aws.amazon.com/AmazonS3/latest/dev/Welcome.html)\n", "\n", "Next, we will upload the index.html file created above to S3 to be used later in the workshop.\n", "\n", "[s3.upload_file](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.upload_file) boto3 documentation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "session.resource('s3').Bucket(bucket).Object(os.path.join('web', 'index.html')).upload_file('index.html')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### [Create Security Groups](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_SecurityGroups.html)\n", "\n", "\n", "A security group acts as a virtual firewall for your instance to control inbound and outbound traffic. When you launch an instance in a VPC, you can assign up to five security groups to the instance. Security groups act at the instance level, not the subnet level. Therefore, each instance in a subnet in your VPC could be assigned to a different set of security groups. If you don't specify a particular group at launch time, the instance is automatically assigned to the default security group for the VPC.\n", "\n", "[ec2_client.create_security_group](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.create_security_group) boto3 documentation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sg = ec2_client.create_security_group(\n", " Description='security group for ALB',\n", " GroupName=alb_sec_group_name,\n", " VpcId=vpc_id\n", ")\n", "alb_sec_group_id=sg[\"GroupId\"]\n", "print('ALB security group id - ' + alb_sec_group_id)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Configure available ports\n", "\n", "In order for the ALB to communicate with the outside world, we will open port 80 and 443. As you can see in the call below we can define the `ToPort` and `FromPort` and a `CidrIp` range we want to allow.\n", "\n", "[ec2_client.authorize_security_group_ingress](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.authorize_security_group_ingress) boto3 documentation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data = ec2_client.authorize_security_group_ingress(\n", " GroupId=alb_sec_group_id,\n", " IpPermissions=[\n", " {'IpProtocol': 'tcp',\n", " 'FromPort': 80,\n", " 'ToPort': 80,\n", " 'IpRanges': [\n", " {\n", " 'CidrIp': '0.0.0.0/0',\n", " 'Description': 'HTTP access'\n", " },\n", " ]\n", " },\n", " {'IpProtocol': 'tcp',\n", " 'FromPort': 443,\n", " 'ToPort': 443,\n", " 'IpRanges': [\n", " {\n", " 'CidrIp': '0.0.0.0/0',\n", " 'Description': 'HTTPS access'\n", " },\n", " ]\n", " }\n", " ]\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### [Create Application Load Balancer (ALB)](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html)\n", "\n", "\n", "Elastic Load Balancing supports three types of load balancers: Application Load Balancers, Network Load Balancers, and Classic Load Balancers. In this example we will be using an [Application Load Balancers](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html). For more information about Network Load Balancers, see the [User Guide for Network Load Balancers](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/). For more information about Classic Load Balancers, see the [User Guide for Classic Load Balancers](https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/).\n", "\n", "[elbv2.create_load_balancer](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/elbv2.html#ElasticLoadBalancingv2.Client.create_load_balancer) boto3 documentation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "alb = elb.create_load_balancer(\n", " Name='web-load-balancer',\n", " Subnets=[\n", " subnet_id,\n", " subnet2_id\n", " ],\n", " SecurityGroups=[\n", " alb_sec_group_id,\n", " ],\n", " Scheme='internet-facing',\n", " Type='application',\n", " IpAddressType='ipv4'\n", ")\n", "\n", "alb_arn = alb[\"LoadBalancers\"][0][\"LoadBalancerArn\"]\n", "alb_name = alb[\"LoadBalancers\"][0][\"LoadBalancerName\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### [Create Target Group](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html)\n", "\n", "Each target group is used to route requests to one or more registered targets. When you create each listener rule, you specify a target group and conditions. When a rule condition is met, traffic is forwarded to the corresponding target group. You can create different target groups for different types of requests. For example, create one target group for general requests and other target groups for requests to the microservices for your application.\n", "\n", "[elbv2.create_target_group](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/elbv2.html#ElasticLoadBalancingv2.Client.create_target_group) boto3 documentation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "target_group = elb.create_target_group(\n", " Name='alb-target-group',\n", " Protocol='HTTP',\n", " Port=80,\n", " VpcId=vpc_id,\n", " HealthCheckProtocol='HTTP',\n", " HealthCheckPort='80',\n", " HealthCheckPath='/'\n", ")\n", "\n", "target_group_arn = target_group[\"TargetGroups\"][0][\"TargetGroupArn\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### [Create Listener](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html)\n", "\n", "Before you start using your Application Load Balancer, you must add one or more listeners. A listener is a process that checks for connection requests, using the protocol and port that you configure. The rules that you define for a listener determine how the load balancer routes requests to the targets in one or more target groups.\n", "\n", "[elbv2.create_listener](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/elbv2.html#ElasticLoadBalancingv2.Client.create_listener) boto3 documentation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "listener = elb.create_listener(\n", " DefaultActions=[\n", " {'TargetGroupArn': target_group_arn,\n", " 'Type': 'forward'\n", " }],\n", " LoadBalancerArn=alb_arn,\n", " Port=80,\n", " Protocol='HTTP'\n", ")\n", "\n", "listener_arn = listener[\"Listeners\"][0][\"ListenerArn\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Get Latest Amazon Linux AMI" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ami = workshop.get_latest_amazon_linux()\n", "print(ami)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create UserData to install Apache web server and download index\n", "\n", "Replace the `{{bucket}}` value with the S3 bucket you created above" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%writefile userdata.sh\n", "\n", "#!/bin/bash\n", "yum update -y\n", "yum -y install httpd\n", "service httpd start\n", "\n", "usermod -a -G apache ec2-user\n", "chown -R ec2-user:apache /var/www\n", "chmod 2775 /var/www\n", "find /var/www -type d -exec chmod 2775 {} \\;\n", "find /var/www -type f -exec chmod 0664 {} \\;\n", "\n", "aws s3 cp s3://{{bucket}}/web/index.html /var/www/html/index.html" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Load userdata.sh\n", "\n", "We will read the UserData into a local variable and base64 encode the contents of the file to be used on the EC2 instance launch configuraton." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fh=open(\"userdata.sh\")\n", "userdata=fh.read()\n", "fh.close()\n", "\n", "userdataencode = base64.b64encode(userdata.encode()).decode(\"ascii\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### [Create Launch Configuration](https://docs.aws.amazon.com/autoscaling/ec2/userguide/LaunchConfiguration.html)\n", "\n", "A launch configuration is an instance configuration template that an Auto Scaling group uses to launch EC2 instances. When you create a launch configuration, you specify information for the instances. Include the ID of the Amazon Machine Image (AMI), the instance type, a key pair, one or more security groups, and a block device mapping. If you've launched an EC2 instance before, you specified the same information in order to launch the instance.\n", "\n", "[asg.create_launch_configuration](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/autoscaling.html#AutoScaling.Client.create_launch_configuration) boto3 documentation\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "launch_config = asg.create_launch_configuration(\n", " LaunchConfigurationName=launch_config_name,\n", " ImageId=ami,\n", " SecurityGroups=[alb_sec_group_id], ## change this\n", " InstanceType='m4.large',\n", " InstanceMonitoring={'Enabled': True},\n", " UserData=userdataencode,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### [Create Auto Scaling Group](https://docs.aws.amazon.com/autoscaling/ec2/userguide/AutoScalingGroup.html)\n", "\n", "An Auto Scaling group contains a collection of Amazon EC2 instances that share similar characteristics and are treated as a logical grouping for the purposes of instance scaling and management. For example, if a single application operates across multiple instances, you might want to increase the number of instances in that group to improve the performance of the application. Or, you can decrease the number of instances to reduce costs when demand is low. Use the Auto Scaling group to scale the number of instances automatically based on criteria that you specify. You could also maintain a fixed number of instances even if an instance becomes unhealthy. This automatic scaling and maintaining the number of instances in an Auto Scaling group is the core functionality of the Amazon EC2 Auto Scaling service.\n", "\n", "[asg.create_auto_scaling_group](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/autoscaling.html#AutoScaling.Client.create_auto_scaling_group) boto3 documentation\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "auto_scaling = asg.create_auto_scaling_group(\n", " AutoScalingGroupName=auto_scaling_group_name,\n", " LaunchConfigurationName=launch_config_name, \n", " MinSize=1, \n", " MaxSize=4, \n", " AvailabilityZones=[\n", " subnet.availability_zone,\n", " subnet2.availability_zone\n", " ], \n", " VPCZoneIdentifier=subnet_id+','+subnet2_id,\n", " TargetGroupARNs=[target_group_arn],\n", " HealthCheckType='EC2',\n", " HealthCheckGracePeriod=120,\n", " DefaultCooldown=120\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### [Create Scaling Policies](https://docs.aws.amazon.com/autoscaling/ec2/userguide/scaling_plan.html)\n", "\n", "Scaling is the ability to increase or decrease the compute capacity of your application. Scaling starts with an event, or scaling action, which instructs an Auto Scaling group to either launch or terminate Amazon EC2 instances.\n", "\n", "[asg.put_scaling_policy](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/autoscaling.html#AutoScaling.Client.put_scaling_policy) boto3 documentation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def create_policy(policy_name, adjustment_value):\n", " asg_policy = asg.put_scaling_policy(\n", " AutoScalingGroupName=auto_scaling_group_name,\n", " PolicyName=policy_name,\n", " AdjustmentType='ChangeInCapacity',\n", " ScalingAdjustment=adjustment_value,\n", " Cooldown=60\n", " )\n", " return asg_policy[\"PolicyARN\"]\n", "\n", "scale_up_arn = create_policy(scale_up_name, 1)\n", "scale_down_arn = create_policy(scale_down_name,-1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### [Create Alarms to trigger auto-scaling groups (ASG)](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/US_AlarmAtThresholdEC2.html)\n", "\n", "You can create a CloudWatch alarm that watches a single CloudWatch metric or the result of a math expression based on CloudWatch metrics. The alarm performs one or more actions based on the value of the metric or expression relative to a threshold over a number of time periods. The action can be an Amazon EC2 action, an Amazon EC2 Auto Scaling action, or a notification sent to an Amazon SNS topic.\n", "\n", "[cloudwatch.put_metric_alarm](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cloudwatch.html#CloudWatch.Client.put_metric_alarm) boto3 documentation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def create_alarm(alarm_name, operator, threshold, policy_arn):\n", " alarm = cloudwatch.put_metric_alarm(\n", " AlarmName=alarm_name, \n", " AlarmActions=[policy_arn],\n", " MetricName='CPUUtilization',\n", " Namespace='AWS/EC2',\n", " Statistic='Average',\n", " Dimensions=[{'Name':'AutoScalingGroupName','Value': auto_scaling_group_name}],\n", " Period=120, \n", " EvaluationPeriods=1,\n", " Threshold=threshold,\n", " ComparisonOperator=operator,\n", " Unit='Percent'\n", " )\n", "\n", "create_alarm('High Capacity Alarm','GreaterThanOrEqualToThreshold',65,scale_up_arn)\n", "create_alarm('Low Capacity Alarm','LessThanOrEqualToThreshold',45,scale_down_arn)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Validate web server\n", "\n", "Wait for the ASG to complete launching an EC2 instance. This will take a few minutes to launch." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "response = asg.describe_auto_scaling_groups(\n", " AutoScalingGroupNames=[\n", " auto_scaling_group_name\n", " ],\n", ")\n", "\n", "pprint.pprint(response['AutoScalingGroups'][0]['Instances'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print (\"ASG: https://{0}.console.aws.amazon.com/ec2/autoscaling/home?region={0}#AutoScalingGroups:id={1};view=details\".format(region, auto_scaling_group_name))\n", "print(\"ALB {0}: https://{1}.console.aws.amazon.com/ec2/v2/home?region={1}#LoadBalancers:sort=loadBalancerName\".format(alb_name, region))\n", "print(\"Web App: http://{0}\".format(alb[\"LoadBalancers\"][0][\"DNSName\"]))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Finished!!!!!!\n", "\n", "From the links above you can now click on the Web App link to launch a new tab in the browser to show the index.html page we uploaded from S3. After that, you can click the ASG linkk above and we will change the `Desired Count` attribute to 2 so we can watch the ASG launch another instance based on the launch configuration we created above and register it with the ALB. This should give you a good idea of how you would launch and Apache Web Server with the boto3 calls. If you were to create this in a production environment you could leverage [CloudFormation](https://aws.amazon.com/cloudformation/) templates that will allow you to leverage YAML or JSON templates to launch the resources. If you would like to experiment more you can launch example [CloudFormation application templates](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/deploying.applications.html) to see how you could build your [Infrastructure as Code](https://en.wikipedia.org/wiki/Infrastructure_as_code). " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Clean Up\n", "\n", "In order to remove everything created in this workshop you can run the cells below and finally remove the VPC created for this workshop.\n", "\n", "### Remove CloudWatch Alarms" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "response = cloudwatch.delete_alarms(\n", " AlarmNames=[\n", " scale_up_name,\n", " scale_down_name\n", " ]\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Remove Auto Scaling Groups" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "response = asg.delete_auto_scaling_group(\n", " AutoScalingGroupName=auto_scaling_group_name,\n", " ForceDelete=True\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Remove Launch Configuration" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "response = asg.delete_launch_configuration(LaunchConfigurationName=launch_config_name)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Remove Listener" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "response = elb.delete_listener(ListenerArn=listener_arn)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Remove Target Groups" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "response = elb.delete_target_group(TargetGroupArn=target_group_arn)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Remove Application Load Balancer" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "response = elb.delete_load_balancer(LoadBalancerArn=alb_arn)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Remove Security Group" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "response = ec2_client.delete_security_group(GroupId=alb_sec_group_id)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Remove Virtual Private Cloud" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "workshop.vpc_cleanup(vpc_id)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Remove S3 Bucket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "workshop.delete_bucket_completely(bucket)" ] } ], "metadata": { "kernelspec": { "display_name": "conda_python3", "language": "python", "name": "conda_python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.10" } }, "nbformat": 4, "nbformat_minor": 4 }