diff --git a/PetAdoptions/cdk/pet_stack/app/pet_stack.ts b/PetAdoptions/cdk/pet_stack/app/pet_stack.ts index 491d1d54..cbbd3db1 100644 --- a/PetAdoptions/cdk/pet_stack/app/pet_stack.ts +++ b/PetAdoptions/cdk/pet_stack/app/pet_stack.ts @@ -4,7 +4,7 @@ import { Services } from '../lib/services'; import { Applications } from '../lib/applications'; //import { EKSPetsite } from '../lib/ekspetsite' import { App, Tags, Aspects } from 'aws-cdk-lib'; -import { AwsSolutionsChecks } from 'cdk-nag'; +//import { AwsSolutionsChecks } from 'cdk-nag'; const stackName = "Services"; diff --git a/PetAdoptions/cdk/pet_stack/lib/applications.ts b/PetAdoptions/cdk/pet_stack/lib/applications.ts index f84dd406..6736c1f5 100644 --- a/PetAdoptions/cdk/pet_stack/lib/applications.ts +++ b/PetAdoptions/cdk/pet_stack/lib/applications.ts @@ -1,6 +1,7 @@ import * as iam from 'aws-cdk-lib/aws-iam'; import * as ssm from 'aws-cdk-lib/aws-ssm'; import * as eks from 'aws-cdk-lib/aws-eks'; +import * as resourcegroups from 'aws-cdk-lib/aws-resourcegroups'; import { DockerImageAsset } from 'aws-cdk-lib/aws-ecr-assets'; import * as yaml from 'js-yaml'; import { Stack, StackProps, CfnJson, Fn, CfnOutput } from 'aws-cdk-lib'; @@ -119,6 +120,14 @@ export class Applications extends Stack { 'PetSiteECRImageURL': petsiteAsset.imageUri, 'PetStoreServiceAccountArn': petstoreserviceaccount.roleArn, }))); + // Creating AWS Resource Group for all the resources of stack. + const applicationsCfnGroup = new resourcegroups.CfnGroup(this, 'ApplicationsCfnGroup', { + name: stackName, + description: 'Contains all the resources deployed by Cloudformation Stack ' + stackName, + resourceQuery: { + type: 'CLOUDFORMATION_STACK_1_0', + } + }); } private createSsmParameters(params: Map) { diff --git a/PetAdoptions/cdk/pet_stack/lib/services.ts b/PetAdoptions/cdk/pet_stack/lib/services.ts index 903725da..c2a27068 100644 --- a/PetAdoptions/cdk/pet_stack/lib/services.ts +++ b/PetAdoptions/cdk/pet_stack/lib/services.ts @@ -19,6 +19,8 @@ import * as cloud9 from 'aws-cdk-lib/aws-cloud9'; import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch'; import * as ecr from 'aws-cdk-lib/aws-ecr'; import * as ecrassets from 'aws-cdk-lib/aws-ecr-assets'; +import * as applicationinsights from 'aws-cdk-lib/aws-applicationinsights'; +import * as resourcegroups from 'aws-cdk-lib/aws-resourcegroups'; import { Construct } from 'constructs' import { PayForAdoptionService } from './services/pay-for-adoption-service' @@ -547,60 +549,17 @@ export class Services extends Stack { awsLoadBalancerManifest.node.addDependency(loadBalancerServiceAccount); awsLoadBalancerManifest.node.addDependency(waitForLBServiceAccount); - // NOTE: amazon-cloudwatch namespace is created here!! - var fluentbitYaml = yaml.loadAll(readFileSync("./resources/cwagent-fluent-bit-quickstart.yaml","utf8")) as Record[]; - fluentbitYaml[1].metadata.annotations["eks.amazonaws.com/role-arn"] = new CfnJson(this, "fluentbit_Role", { value : `${cwserviceaccount.roleArn}` }); - - fluentbitYaml[4].data["cwagentconfig.json"] = JSON.stringify({ - agent: { - region: region }, - logs: { - metrics_collected: { - kubernetes: { - cluster_name: "PetSite", - metrics_collection_interval: 60 - } - }, - force_flush_interval: 5 - - } - - }); - - fluentbitYaml[6].data["cluster.name"] = "PetSite"; - fluentbitYaml[6].data["logs.region"] = region; - fluentbitYaml[7].metadata.annotations["eks.amazonaws.com/role-arn"] = new CfnJson(this, "cloudwatch_Role", { value : `${cwserviceaccount.roleArn}` }); - - // The `cluster-info` configmap is used by the current Python implementation for the `AwsEksResourceDetector` - fluentbitYaml[12].data["cluster.name"] = "PetSite"; - fluentbitYaml[12].data["logs.region"] = region; - - const fluentbitManifest = new eks.KubernetesManifest(this,"cloudwatcheployment",{ - cluster: cluster, - manifest: fluentbitYaml - }); - - // CloudWatch agent for prometheus metrics - var prometheusYaml = yaml.loadAll(readFileSync("./resources/prometheus-eks.yaml","utf8")) as Record[]; - - prometheusYaml[0].metadata.annotations["eks.amazonaws.com/role-arn"] = new CfnJson(this, "prometheus_Role", { value : `${cwserviceaccount.roleArn}` }); - - const prometheusManifest = new eks.KubernetesManifest(this,"prometheusdeployment",{ - cluster: cluster, - manifest: prometheusYaml - }); - prometheusManifest.node.addDependency(fluentbitManifest); // Namespace creation dependency - - -var dashboardBody = readFileSync("./resources/cw_dashboard_fluent_bit.json","utf-8"); - dashboardBody = dashboardBody.replaceAll("{{YOUR_CLUSTER_NAME}}","PetSite"); - dashboardBody = dashboardBody.replaceAll("{{YOUR_AWS_REGION}}",region); - - const fluentBitDashboard = new cloudwatch.CfnDashboard(this, "FluentBitDashboard", { - dashboardName: "EKS_FluentBit_Dashboard", - dashboardBody: dashboardBody - }); + // NOTE: Amazon CloudWatch Observability Addon for CloudWatch Agent and Fluentbit + const otelAddon = new eks.CfnAddon(this, 'otelObservabilityAddon', { + addonName: 'amazon-cloudwatch-observability', + addonVersion: 'v1.2.0-eksbuild.1', + clusterName: cluster.clusterName, + // the properties below are optional + resolveConflicts: 'OVERWRITE', + preserveOnDelete: false, + serviceAccountRoleArn: cwserviceaccount.roleArn, + }); const customWidgetResourceControllerPolicy = new iam.PolicyStatement({ effect: iam.Effect.ALLOW, @@ -656,13 +615,50 @@ var dashboardBody = readFileSync("./resources/cw_dashboard_fluent_bit.json","utf dashboardBody: costControlDashboardBody }); + // Creating AWS Resource Group for all the resources of stack. + const servicesCfnGroup = new resourcegroups.CfnGroup(this, 'ServicesCfnGroup', { + name: stackName, + description: 'Contains all the resources deployed by Cloudformation Stack ' + stackName, + resourceQuery: { + type: 'CLOUDFORMATION_STACK_1_0', + } + }); + // Enabling CloudWatch Application Insights for Resource Group + const servicesCfnApplication = new applicationinsights.CfnApplication(this, 'ServicesApplicationInsights', { + resourceGroupName: servicesCfnGroup.name, + autoConfigurationEnabled: true, + cweMonitorEnabled: true, + opsCenterEnabled: true, + }); + // Adding dependency to create these resources at last + servicesCfnGroup.node.addDependency(petSiteCostControlDashboard); + servicesCfnApplication.node.addDependency(servicesCfnGroup); + // Adding a Lambda function to produce the errors - manually executed + var dynamodbQueryLambdaRole = new iam.Role(this, 'dynamodbQueryLambdaRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromManagedPolicyArn(this, 'manageddynamodbread', 'arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess'), + iam.ManagedPolicy.fromManagedPolicyArn(this, 'lambdaBasicExecRoletoddb', 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole') + ] + }); + + var dynamodbQueryFunction = new lambda.Function(this, 'dynamodb-query-function', { + code: lambda.Code.fromAsset(path.join(__dirname, '/../resources/application-insights')), + handler: 'dynamodb-query-function.lambda_handler', + memorySize: 128, + runtime: lambda.Runtime.PYTHON_3_9, + role: dynamodbQueryLambdaRole, + timeout: Duration.seconds(900) + }); + dynamodbQueryFunction.addEnvironment("DYNAMODB_TABLE_NAME", dynamodb_petadoption.tableName); this.createOuputs(new Map(Object.entries({ 'CWServiceAccountArn': cwserviceaccount.roleArn, 'XRayServiceAccountArn': xrayserviceaccount.roleArn, 'OIDCProviderUrl': cluster.clusterOpenIdConnectIssuerUrl, 'OIDCProviderArn': cluster.openIdConnectProvider.openIdConnectProviderArn, - 'PetSiteUrl': `http://${alb.loadBalancerDnsName}` + 'PetSiteUrl': `http://${alb.loadBalancerDnsName}`, + 'DynamoDBQueryFunction': dynamodbQueryFunction.functionName }))); diff --git a/PetAdoptions/cdk/pet_stack/resources/application-insights/dynamodb-query-function.py b/PetAdoptions/cdk/pet_stack/resources/application-insights/dynamodb-query-function.py new file mode 100644 index 00000000..ec66e514 --- /dev/null +++ b/PetAdoptions/cdk/pet_stack/resources/application-insights/dynamodb-query-function.py @@ -0,0 +1,30 @@ +import os +import time +import boto3 +from boto3.dynamodb.conditions import Key + +dynamodb = boto3.resource('dynamodb') +DYNAMODB_TABLE_NAME = os.environ['DYNAMODB_TABLE_NAME'] + +def lambda_handler(event, context): + table = dynamodb.Table(DYNAMODB_TABLE_NAME) + error_mode = event.get('error_mode') + if error_mode == 'true': + query_key = 'wrongKey' + else: + query_key = 'pettype' + t_end = time.time() + 60 * 13 + while time.time() < t_end: + try: + response = table.query( + KeyConditionExpression=Key(query_key).eq('puppy') + ) + items = response['Items'] + except Exception as e: + print("An exception occurred, but still continuing. The error is: ",e) + items = "FunctionError" + time.sleep(30) + return { + 'statusCode': 200, + 'body': items + } \ No newline at end of file diff --git a/PetAdoptions/cdk/pet_stack/resources/use_cases/observability-getting-started-ADOT.yml b/PetAdoptions/cdk/pet_stack/resources/use_cases/observability-getting-started-ADOT.yml new file mode 100644 index 00000000..a8ebf1b6 --- /dev/null +++ b/PetAdoptions/cdk/pet_stack/resources/use_cases/observability-getting-started-ADOT.yml @@ -0,0 +1,203 @@ +#* +#* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +#* SPDX-License-Identifier: MIT-0 +#* +#* Permission is hereby granted, free of charge, to any person obtaining a copy of this +#* software and associated documentation files (the "Software"), to deal in the Software +#* without restriction, including without limitation the rights to use, copy, modify, +#* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +#* permit persons to whom the Software is furnished to do so. +#* +#* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +#* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +#* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +#* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +#* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +#* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#* + +#------------------------------------------------------------------------------ +# +# Template: observability-getting-started-ADOT.yml +# Purpose: CloudFormation template to deploy EC2 instance for observability immersion day. +# +#------------------------------------------------------------------------------ +--- +AWSTemplateFormatVersion: '2023-10-10' +Description: AWS CloudFormation template to launch an EC2 instance with required IAM permissions. Written for Observability getting started workshop Februray 2023. **WARNING** This template creates a VPC, public subnet, Internet Gateway, 1 EC2 with Apache installed, and associated route tables and permissions. You will be billed for the AWS resources used if you create a stack from this template. + +#----------------------------------------------------------- +# Parameters +#----------------------------------------------------------- +Parameters : + LatestAmazonLinuxAmiId : + # Use public Systems Manager Parameter + Type : 'AWS::SSM::Parameter::Value' + Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2' + +# Calling AMI public parameters +# https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-public-parameters-ami.html + +Resources: + + #------------------------------------------------- + # VPC and required resources to enable network connectivity to AWS Systems Manager + #------------------------------------------------- + VPC: + Type: 'AWS::EC2::VPC' + Properties: + CidrBlock: 10.0.0.0/16 + EnableDnsSupport: true + EnableDnsHostnames: true + InstanceTenancy: default + Tags: + - Key: Name + Value: ObservabilityGettingStartedADOT + InternetGateway: + Type: 'AWS::EC2::InternetGateway' + Properties: + Tags: + - Key: Name + Value: ObservabilityGettingStartedADOT + VPCGatewayAttachment: + Type: 'AWS::EC2::VPCGatewayAttachment' + Properties: + VpcId: !Ref VPC + InternetGatewayId: !Ref InternetGateway + SubnetPublic: + Type: 'AWS::EC2::Subnet' + Properties: + AvailabilityZone: !Select [0, !GetAZs ''] + CidrBlock: 10.0.0.0/20 + VpcId: !Ref VPC + Tags: + - Key: Name + Value: ObservabilityGettingStartedADOT + RouteTablePublic: + Type: 'AWS::EC2::RouteTable' + Properties: + VpcId: !Ref VPC + Tags: + - Key: Name + Value: ObservabilityGettingStartedADOT + RouteTableAssociationPublic: + Type: 'AWS::EC2::SubnetRouteTableAssociation' + Properties: + SubnetId: !Ref SubnetPublic + RouteTableId: !Ref RouteTablePublic + RouteTablePublicInternetRoute: + Type: 'AWS::EC2::Route' + DependsOn: VPCGatewayAttachment + Properties: + RouteTableId: !Ref RouteTablePublic + DestinationCidrBlock: '0.0.0.0/0' + GatewayId: !Ref InternetGateway + InstanceSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: 'Security Group for CW ImmersionDay test instances' + GroupName: ObservabilityGettingStartedADOT + SecurityGroupIngress: + - Description: Ingress to allow access for Apache from Internet on port 80 + IpProtocol: 6 + FromPort: 80 + ToPort: 80 + CidrIp: 0.0.0.0/0 + - Description: Ingress to allow API invocation on port 4000 + IpProtocol: 6 + FromPort: 4000 + ToPort: 4000 + CidrIp: 0.0.0.0/0 + SecurityGroupEgress: + - Description: Egress to allow ADOT to communicate with CloudWatch and Amazon Managed Prometheus service + IpProtocol: 6 + FromPort: 443 + ToPort: 443 + CidrIp: 0.0.0.0/0 + Tags: + - Key: Name + Value: ObservabilityGettingStartedADOT + VpcId: !Ref VPC + #------------------------------------------------- + # IAM ROLE FOR EC2 Instance + #------------------------------------------------- + InstanceRole: + Type: AWS::IAM::Role + Properties: + RoleName: SSMCloudWatchADOTInstanceRole + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + Action: + - sts:AssumeRole + Path: '/' + ManagedPolicyArns: + - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore + - arn:aws:iam::aws:policy/AmazonPrometheusRemoteWriteAccess + RolePolicies: + Type: AWS::IAM::Policy + Properties: + PolicyName: AWSDistroOpenTelemetryPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - logs:PutLogEvents + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:DescribeLogStreams + - logs:DescribeLogGroups + - ssm:GetParameters + - ssm:PutParameter + Resource: '*' + Roles: + - !Ref InstanceRole + + InstanceProfile: + Type: AWS::IAM::InstanceProfile + Properties: + InstanceProfileName: SSMCloudWatchADOTInstanceRole + Path: '/' + Roles: + - !Ref InstanceRole + #------------------------------------------------- + # EC2 instance using the latest Amazon Linux AMI + #------------------------------------------------- + LinuxEC2Instance: + Type: AWS::EC2::Instance + Properties: + InstanceType: t2.small + ImageId: !Ref LatestAmazonLinuxAmiId + NetworkInterfaces: + - AssociatePublicIpAddress: true + DeviceIndex: 0 + GroupSet: + - !Ref InstanceSecurityGroup + SubnetId: !Ref SubnetPublic + UserData: + Fn::Base64: + !Sub | + #!/bin/bash + #Cloudformation Stack: ${AWS::StackName} + sudo yum install httpd -y + sudo service httpd start + sudo chkconfig httpd on + sudo su + echo "Welcome to ADOT monitoring for EC2 instances and workloads" >> /var/www/html/index.html + exit + IamInstanceProfile: !Ref InstanceProfile + Tags: + - Key: Name + Value: AppServer +Outputs: + IAMRole: + Description: IAM Role + Value: !Ref InstanceRole + WebsiteURL: + Description: Website URL "http:///" + Value: !Sub "http://${LinuxEC2Instance.PublicIp}/" \ No newline at end of file diff --git a/PetAdoptions/cdk/pet_stack/resources/use_cases/observability-getting-started.yml b/PetAdoptions/cdk/pet_stack/resources/use_cases/observability-getting-started.yml new file mode 100644 index 00000000..142b61e2 --- /dev/null +++ b/PetAdoptions/cdk/pet_stack/resources/use_cases/observability-getting-started.yml @@ -0,0 +1,325 @@ +#* +#* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +#* SPDX-License-Identifier: MIT-0 +#* +#* Permission is hereby granted, free of charge, to any person obtaining a copy of this +#* software and associated documentation files (the "Software"), to deal in the Software +#* without restriction, including without limitation the rights to use, copy, modify, +#* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +#* permit persons to whom the Software is furnished to do so. +#* +#* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +#* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +#* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +#* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +#* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +#* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#* + +#------------------------------------------------------------------------------ +# +# Template: cw-immersionday-resources.yml +# Purpose: CloudFormation template to deploy EC2 instance for observability immersion day. +# +#------------------------------------------------------------------------------ +--- +AWSTemplateFormatVersion: '2010-09-09' +Description: AWS CloudFormation template to launch EC2 instances and create apache style log events. Written for Observability getting started workshop June 2022. **WARNING** This template creates a VPC, public subnet, Internet Gateway, 2 EC2s, a Lambda function and CloudWatch Log Group, and associated route tables and permissions. You will be billed for the AWS resources used if you create a stack from this template. + +#----------------------------------------------------------- +# Parameters +#----------------------------------------------------------- +Parameters : + LatestAmazonLinuxAmiId : + # Use public Systems Manager Parameter + Type : 'AWS::SSM::Parameter::Value' + Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2' + +# Calling AMI public parameters +# https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-public-parameters-ami.html + +Resources: + + #------------------------------------------------- + # VPC and required resources to enable network connectivity to AWS Systems Manager + #------------------------------------------------- + VPC: + Type: 'AWS::EC2::VPC' + Properties: + CidrBlock: 10.0.0.0/16 + EnableDnsSupport: true + EnableDnsHostnames: true + InstanceTenancy: default + Tags: + - Key: Name + Value: ObservabilityGettingStartedImmersionDay + InternetGateway: + Type: 'AWS::EC2::InternetGateway' + Properties: + Tags: + - Key: Name + Value: ObservabilityGettingStartedImmersionDay + VPCGatewayAttachment: + Type: 'AWS::EC2::VPCGatewayAttachment' + Properties: + VpcId: !Ref VPC + InternetGatewayId: !Ref InternetGateway + SubnetPublic: + Type: 'AWS::EC2::Subnet' + Properties: + AvailabilityZone: !Select [0, !GetAZs ''] + CidrBlock: 10.0.0.0/20 + VpcId: !Ref VPC + Tags: + - Key: Name + Value: ObservabilityGettingStartedImmersionDay + RouteTablePublic: + Type: 'AWS::EC2::RouteTable' + Properties: + VpcId: !Ref VPC + Tags: + - Key: Name + Value: ObservabilityGettingStartedImmersionDay + RouteTableAssociationPublic: + Type: 'AWS::EC2::SubnetRouteTableAssociation' + Properties: + SubnetId: !Ref SubnetPublic + RouteTableId: !Ref RouteTablePublic + RouteTablePublicInternetRoute: + Type: 'AWS::EC2::Route' + DependsOn: VPCGatewayAttachment + Properties: + RouteTableId: !Ref RouteTablePublic + DestinationCidrBlock: '0.0.0.0/0' + GatewayId: !Ref InternetGateway + InstanceSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: 'Security Group for CW ImmersionDay test instances' + GroupName: ObservabilityGettingStartedImmersionDay + SecurityGroupEgress: + - Description: Egress to allow CloudWatch agent to communicate with CloudWatch service + IpProtocol: 6 + FromPort: 443 + ToPort: 443 + CidrIp: 0.0.0.0/0 + Tags: + - Key: Name + Value: ObservabilityGettingStartedImmersionDay + VpcId: !Ref VPC + #------------------------------------------------- + # IAM ROLE FOR EC2 Instance + #------------------------------------------------- + InstanceRole: + Type: AWS::IAM::Role + Properties: + RoleName: SSMCloudWatchInstanceRole + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + Action: + - sts:AssumeRole + Path: '/' + ManagedPolicyArns: + - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy + - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore + - arn:aws:iam::aws:policy/AmazonSSMPatchAssociation + Tags: + - Key: Name + Value: ObservabilityGettingStartedImmersionDay + + InstanceProfile: + Type: AWS::IAM::InstanceProfile + Properties: + InstanceProfileName: SSMCloudWatchInstanceRole + Path: '/' + Roles: + - !Ref InstanceRole + #------------------------------------------------- + # EC2 instance using the latest Amazon Linux AMI + #------------------------------------------------- + LinuxEc2Instance1: + Type: AWS::EC2::Instance + Properties: + InstanceType: t2.small + ImageId: !Ref LatestAmazonLinuxAmiId + NetworkInterfaces: + - AssociatePublicIpAddress: true + DeviceIndex: 0 + GroupSet: + - !Ref InstanceSecurityGroup + SubnetId: !Ref SubnetPublic + UserData: + Fn::Base64: + !Sub | + #!/bin/bash + #Cloudformation Stack: ${AWS::StackName} + yum update -y + yum install -y tomcat tomcat-webapps + systemctl start tomcat.service + IamInstanceProfile: !Ref InstanceProfile + Tags: + - Key: Name + Value: AppServer1 + + LinuxEc2Instance2: + Type: AWS::EC2::Instance + Properties: + InstanceType: t2.small + ImageId: !Ref LatestAmazonLinuxAmiId + NetworkInterfaces: + - AssociatePublicIpAddress: true + DeviceIndex: 0 + GroupSet: + - !Ref InstanceSecurityGroup + SubnetId: !Ref SubnetPublic + UserData: + Fn::Base64: + !Sub | + #!/bin/bash + #Cloudformation Stack: ${AWS::StackName} + yum update -y + yum install -y tomcat tomcat-webapps + systemctl start tomcat.service + IamInstanceProfile: !Ref InstanceProfile + Tags: + - Key: Name + Value: AppServer2 + + #------------------------------------------------- + # Lambda log group to store the fake logs in (ensures it will be deleted on stack deletion) + # Note: can't change the path for the lambda to log to + #------------------------------------------------- + LambdaLogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Sub /aws/lambda/${LambdaFunctionApacheLogGenerator}-${AWS::StackName} + RetentionInDays: 7 + Tags: + - Key: Name + Value: ObservabilityGettingStartedImmersionDay + #------------------------------------------------- + # Lambda function to generate fake looking apache logs + #------------------------------------------------ + LambdaFunctionApacheLogGenerator: + Type: AWS::Lambda::Function + Properties: + Code: + ZipFile: | + # To generate fake apache style logs + import json + import datetime + import random + import time + + def lambda_handler(event, context): + maxnumevents = 5 + numevents = int(random.randrange(1,maxnumevents,1)) # can set numevents to a fixed value if wish + + + # create list of pages + pages = ['searchProduct.html','addToCart.html','makePayment.html'] + + # create list of possible events + # 127.0.0.1 - frank [10Oct2000135536 -0700] "GET apache_pb.gif HTTP/1.0" 200 2326 123 + eventList = ['127.0.0.1 - - timestamp "GET page HTTP/1.0" status loadtime bytesLoaded'] + + # have a few more 200s to make these the most common + statusValues = ['200','200','200','200','200','200','200','200','403','404','500','303'] + + + for x in range(1,numevents+1): + # create log events + event = random.choice(eventList) + page = random.choice(pages) + status = random.choice(statusValues) + loadtime = random.randrange(300,1000,1) + bytesLoaded = random.randrange(100,1000,1) + + # create timestamp + timestamp = datetime.datetime.now().astimezone() + # 2021-08-25T150000.000-0600 + timestr = timestamp.strftime('%Y-%m-%dT%H%M%S%z') + # 10-Oct-2000T135536 -0700 + + # replace variables + event = event.replace('page', str(page)) + event = event.replace('status', str(status)) + event = event.replace('loadtime', str(loadtime)) + event = event.replace('bytesLoaded', str(bytesLoaded)) + event = event.replace('timestamp', str(timestr)) + + print(event) + + # add a delay - want a timegap between events (between 1 and 5 secs) + sleepfor = random.randrange(1, 5, 1) + time.sleep(sleepfor); + + # end loop through numevents + + return + + Description: Apache log generator + FunctionName: !Sub LambdaFunctionApacheLogGenerator-${AWS::StackName} + Handler: index.lambda_handler + MemorySize: 128 + Role: !GetAtt LambdaIAMRole.Arn + Runtime: python3.9 + Timeout: 60 + Tags: + - Key: Name + Value: ObservabilityGettingStartedImmersionDay + + #------------------------------------------------- + # Lambda Role for ability to log events + #------------------------------------------------ + LambdaIAMRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Policies: + - PolicyDocument: + Version: 2012-10-17 + Statement: + - Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + Effect: Allow + Resource: !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/LambdaFunctionApacheLogGenerator-${AWS::StackName}:* + PolicyName: LambdaCloudWatchLogs + Tags: + - Key: Name + Value: ObservabilityGettingStartedImmersionDay + #------------------------------------------------- + # Scheduled event to run the Lambda every 2 minutes to generate timespaced logs + #------------------------------------------------ + EventBridgeRule: + Type: AWS::Events::Rule + Properties: + Name: !Sub InvokeLambdaApacheLogs-${AWS::StackName} + ScheduleExpression: rate(2 minutes) + Targets: + - Arn: !GetAtt LambdaFunctionApacheLogGenerator.Arn + Id: !Sub LambdaFunctionApacheLogGenerator-${AWS::StackName} + + + PermissionForEventsToInvokeLambda: + Type: AWS::Lambda::Permission + Properties: + FunctionName: !Ref LambdaFunctionApacheLogGenerator + Action: lambda:InvokeFunction + Principal: events.amazonaws.com + SourceArn: !GetAtt EventBridgeRule.Arn diff --git a/PetAdoptions/petsite/petsite/wwwroot/images/main_banner_text.png b/PetAdoptions/petsite/petsite/wwwroot/images/main_banner_text.png index aabc70d6..0f2d1e29 100644 Binary files a/PetAdoptions/petsite/petsite/wwwroot/images/main_banner_text.png and b/PetAdoptions/petsite/petsite/wwwroot/images/main_banner_text.png differ