Capstone: build a right-sized development server that stops itself when idle

capstone · 75 min · Task statement 1.4

Task

A small company is rehosting one internal development server to AWS and wants it to cost as little as possible. Build it the way lessons 1 to 5 recommend: check that lesson 1's budget is watching the account, choose an Availability Zone that offers the instance type, choose a Linux image so no licence is billed, and describe the whole environment in one AWS CloudFormation template. The template makes a t3.micro instance with no public address and no inbound rule, tags it with a cost centre, and adds an Amazon CloudWatch alarm that stops it automatically when it sits idle. You then read its CPU metrics to judge its size, watch the automation stop it, and map each decision to a Well-Architected pillar and a CAF business outcome. Everything is deleted at the end.

Steps

  1. Check the guard rails before you build. Confirm lesson 1's budget is in place, then find an Availability Zone in your Region that offers t3.micro:
aws budgets describe-budgets --account-id 111122223333 --query "length(Budgets[?BudgetName=='clf-zero-spend-budget'])" --output text
AZ=$(aws ec2 describe-instance-type-offerings --location-type availability-zone --filters Name=instance-type,Values=t3.micro --query 'InstanceTypeOfferings[0].Location' --output text)
echo "$AZ"

The first command must print 1; if it prints 0, go back to lesson 1's lab first. The last prints a zone name such as eu-west-2a. Choosing one zone is fine for a development server; lesson 1 explains why a production workload would use two or more. 2. Make the licence decision. Read how the Amazon Linux 2023 image the template will use is billed:

LNX=$(aws ssm get-parameter --name /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 --query 'Parameter.Value' --output text)
aws ec2 describe-images --image-ids "$LNX" --query 'Images[0].[PlatformDetails,UsageOperation]' --output text

It reads Linux/UNIX and RunInstances: no licence is billed, so neither license-included pricing nor BYOL applies. 3. Write the template. Read it before you save it: the AllowedValues line refuses any larger size, MapPublicIpOnLaunch: false keeps a public address off the server, the security group has no rules, CPUCredits: standard stops the burstable instance from running up extra CPU charges, and the alarm stops the server after two idle five-minute periods.

cat > clf-cap05-template.yaml << 'EOF'
AWSTemplateFormatVersion: '2010-09-09'
Description: clf-cap05 - one small private instance that stops itself when idle
Parameters:
  AzName:
    Type: AWS::EC2::AvailabilityZone::Name
  InstanceType:
    Type: String
    Default: t3.micro
    AllowedValues: [t3.micro, t2.micro]
  LatestAmi:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64
Resources:
  Vpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.50.0.0/16
      Tags: [{Key: Name, Value: clf-cap05-vpc}]
  Subnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone: !Ref AzName
      CidrBlock: 10.50.1.0/24
      MapPublicIpOnLaunch: false
      Tags: [{Key: Name, Value: clf-cap05-subnet}]
  NoInboundGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: clf-cap05-no-inbound
      GroupDescription: clf-cap05 has no inbound rules at all
      VpcId: !Ref Vpc
  Server:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref LatestAmi
      InstanceType: !Ref InstanceType
      SubnetId: !Ref Subnet
      SecurityGroupIds: [!Ref NoInboundGroup]
      CreditSpecification: {CPUCredits: standard}
      PropagateTagsToVolumeOnCreation: true
      Tags:
        - {Key: Name, Value: clf-cap05-dev}
        - {Key: Environment, Value: dev}
        - {Key: CostCentre, Value: clf-cap05}
  IdleStopAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: clf-cap05-idle-stop
      AlarmDescription: Stop the server when its CPU stays under 5 percent
      Namespace: AWS/EC2
      MetricName: CPUUtilization
      Dimensions: [{Name: InstanceId, Value: !Ref Server}]
      Statistic: Average
      Period: 300
      EvaluationPeriods: 2
      Threshold: 5
      ComparisonOperator: LessThanThreshold
      TreatMissingData: ignore
      AlarmActions: [!Sub 'arn:aws:automate:${AWS::Region}:ec2:stop']
Outputs:
  InstanceId:
    Value: !Ref Server
EOF
  1. Validate the template, then build the stack in the zone you found. deploy waits until the stack is complete, which takes a few minutes:
aws cloudformation validate-template --template-body file://clf-cap05-template.yaml --query 'Parameters[].ParameterKey' --output text
aws cloudformation deploy --stack-name clf-cap05-dev --template-file clf-cap05-template.yaml --parameter-overrides AzName=$AZ
IID=$(aws cloudformation describe-stacks --stack-name clf-cap05-dev --query "Stacks[0].Outputs[?OutputKey=='InstanceId'].OutputValue" --output text)
aws ec2 describe-instances --instance-ids "$IID" --query 'Reservations[0].Instances[0].[InstanceType,State.Name,PublicIpAddress]' --output text
aws ec2 describe-security-groups --filters Name=group-name,Values=clf-cap05-no-inbound --query 'length(SecurityGroups[0].IpPermissions)' --output text

The instance reads t3.micro, running and None: it has no public address. The security group has 0 inbound rules. This is a rehost in lesson 3's terms: the server moves unchanged, and the improvements come from how it is run. 5. Gather the rightsizing evidence. Wait about 15 minutes after the stack completes, then read the server's average CPU for each five-minute period and compare the two smallest sizes:

aws cloudwatch get-metric-statistics --namespace AWS/EC2 --metric-name CPUUtilization --dimensions Name=InstanceId,Value=$IID --start-time $(date -u -d '30 minutes ago' +%FT%TZ) --end-time $(date -u +%FT%TZ) --period 300 --statistics Average --query 'Datapoints[].Average' --output text
aws ec2 describe-instance-types --instance-types t3.micro t3.small --query 'InstanceTypes[].[InstanceType,VCpuInfo.DefaultVCpus,MemoryInfo.SizeInMiB]' --output table

An idle server shows averages close to zero. It is already the smallest size the template allows, so the remaining saving is not a smaller size: it is not running at all when nobody uses it. 6. Watch the automation work. Within about 20 minutes of the stack completing, the alarm reaches the alarm state and stops the server:

aws cloudwatch describe-alarms --alarm-names clf-cap05-idle-stop --query 'MetricAlarms[0].StateValue' --output text
aws ec2 describe-instances --instance-ids "$IID" --query 'Reservations[0].Instances[0].State.Name' --output text
aws ec2 describe-volumes --filters Name=tag:Name,Values=clf-cap05-dev --query 'Volumes[0].State' --output text

Expect ALARM, then stopped, then in-use. The last line matters for lesson 4: a stopped instance stops billing for compute, but its EBS volume still exists and is still billed, so stopping is not the same as deleting. If the server is still running after 25 minutes, read the alarm's history with aws cloudwatch describe-alarm-history --alarm-name clf-cap05-idle-stop --output text to see why, then stop it yourself with aws ec2 stop-instances --instance-ids "$IID" and carry on. 7. Write the review. Run nano clf-cap05-review.txt and write one line for each decision below, naming the Well-Architected pillar it serves and the CAF business outcome it supports: the budget alert; the t3.micro limit in AllowedValues; the CostCentre tag; no public address and no inbound rule; the idle-stop alarm; building it all from a template. Save with Ctrl+O, leave with Ctrl+X, and compare with the Notes.

Verify

Run these in the same CloudShell session. Set the instance ID again first if CloudShell has restarted:

IID=$(aws cloudformation describe-stacks --stack-name clf-cap05-dev --query "Stacks[0].Outputs[?OutputKey=='InstanceId'].OutputValue" --output text)
aws ec2 describe-instances --instance-ids "$IID" --query 'Reservations[0].Instances[0].[InstanceType,State.Name,PublicIpAddress]' --output text

Expected output: t3.micro stopped None (or t2.micro if that is the type you chose).

aws cloudwatch describe-alarms --alarm-names clf-cap05-idle-stop --query 'MetricAlarms[0].StateValue' --output text

Expected output: ALARM. If you stopped the server yourself in step 6, this reads INSUFFICIENT_DATA or OK instead, and the history from step 6 says why the action did not run.

aws ec2 describe-instance-credit-specifications --instance-ids "$IID" --query 'InstanceCreditSpecifications[0].CpuCredits' --output text
aws ec2 describe-security-groups --filters Name=group-name,Values=clf-cap05-no-inbound --query 'length(SecurityGroups[0].IpPermissions)' --output text

Expected output: standard, then 0.

Cost and clean-up

Charges are possible for the instance's running hours, its EBS volume (billed even while the instance is stopped), the CloudWatch alarm and the metric requests in step 5, unless the AWS Free Tier covers them in your account. The VPC, subnet and security group carry no charge. The amounts are small for a capstone done in one sitting; the Amazon EC2, Amazon EBS and Amazon CloudWatch pricing pages and the AWS Pricing Calculator give current rates. Delete the stack the same day.

Created by this lab: the CloudFormation stack clf-cap05-dev; the EC2 instance clf-cap05-dev (t3.micro) and its root EBS volume; the VPC clf-cap05-vpc, the subnet clf-cap05-subnet and the security group clf-cap05-no-inbound; the CloudWatch alarm clf-cap05-idle-stop; the files clf-cap05-template.yaml and clf-cap05-review.txt in your CloudShell home directory.

  1. Delete the stack clf-cap05-dev and wait until it is gone. CloudFormation terminates the instance clf-cap05-dev, deletes its root volume with it, and deletes the alarm clf-cap05-idle-stop, the security group clf-cap05-no-inbound, the subnet clf-cap05-subnet and the VPC clf-cap05-vpc:
aws cloudformation delete-stack --stack-name clf-cap05-dev
aws cloudformation wait stack-delete-complete --stack-name clf-cap05-dev
  1. Keep a copy of your review if you want one (Actions, Download file in CloudShell), then remove both files:
rm clf-cap05-template.yaml clf-cap05-review.txt

Check it is gone

aws cloudformation list-stacks --stack-status-filter CREATE_COMPLETE UPDATE_COMPLETE DELETE_FAILED --query "length(StackSummaries[?StackName=='clf-cap05-dev'])" --output text
aws ec2 describe-instances --filters Name=tag:Name,Values=clf-cap05-dev Name=instance-state-name,Values=pending,running,stopping,stopped --query 'length(Reservations)' --output text
aws ec2 describe-volumes --filters Name=tag:Name,Values=clf-cap05-dev --query 'length(Volumes)' --output text
aws ec2 describe-vpcs --filters Name=tag:Name,Values=clf-cap05-vpc --query 'length(Vpcs)' --output text
aws cloudwatch describe-alarms --alarm-names clf-cap05-idle-stop --query 'length(MetricAlarms)' --output text

Expected output: 0 from every command. A terminated instance stays visible as terminated for a while, which is why the second command leaves that state out.

Notes

Answers to step 7. The budget alert: cost optimization (implement Cloud Financial Management), supporting increased operational efficiency. The t3.micro limit: cost optimization (adopt a consumption model), and sustainability (maximize utilization), supporting increased operational efficiency. The CostCentre tag: cost optimization (analyze and attribute expenditure). No public address and no inbound rule: security (apply security at all layers), supporting reduced business risk. The idle-stop alarm: cost optimization, an automated consumption model, supporting increased operational efficiency. Building from a template: operational excellence (safely automate, with small, reversible changes) and reliability (manage change through automation), supporting reduced business risk.

The alarm's stop action uses a service-linked role named AWSServiceRoleForCloudWatchEvents, which CloudWatch creates in the account if it does not already exist. It carries no charge, other alarms can share it, and deleting the stack leaves it in place; it can be deleted from the IAM console once no alarm uses it.

The server has no internet route, so it cannot be patched or reached; that is acceptable for a capstone that only proves cost controls, and a real development server would reach the internet through a design covered later in the course.

This is an independent study companion for the AWS Certified Cloud Practitioner (CLF-C02) exam and is not produced by or endorsed by Amazon Web Services. AWS, Amazon Web Services and AWS Certified Cloud Practitioner are trademarks of Amazon.com, Inc. or its affiliates.

This is an independent study companion for AWS Certified Cloud Practitioner CLF-C02 and is not produced by or endorsed by CompTIA.