AWS unhealthy target group instances

0

I am working on an AWS project at Udacity and deploying an infrastrucute with elastic load balancer, auto scaling group, listeners, listener rules, security groups and target groups. My problem is that the health check status of the instances in the target group always result in unhealthy instances and therefore I am not able to call the load balancer DNS url nor the ip of the instances. I always have 502 bad gateway. What I doing wrong. Here is my code:

Description: >
  xxx / Udacity 2023

Parameters:
  EnvironmentName:
    Description: An environment name that will be prefixed to resource names
    Type: String
  myLaunchTemplateVersionNumber:
    Type: String
    Default: 1

Resources:
  LBSecGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow http to our load balancer
      VpcId: 
        Fn::ImportValue:
          !Sub "${EnvironmentName}-VPCID"
      SecurityGroupIngress: 
      - IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        CidrIp: 0.0.0.0/0 
      SecurityGroupEgress: security group.
      - IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        CidrIp: 0.0.0.0/0
  WebServerSecGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow http to our hosts and SSH from local only
      VpcId:
        Fn::ImportValue:
          !Sub "${EnvironmentName}-VPCID"
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 8080
        ToPort: 8080
        CidrIp: 0.0.0.0/0
      - IpProtocol: tcp 
        FromPort: 22
        ToPort: 22
        CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
      - IpProtocol: tcp
        FromPort: 0
        ToPort: 65535 #end port
        CidrIp: 0.0.0.0/0
  myWebAppLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateData:
        UserData:
          Fn::Base64: !Sub |
            #!/bin/bash
            apt-get update -y
            apt-get install apache2 -y
            systemctl start apache2.service
            cd /var/www/html
            echo "Udacity Demo Web Server Up and Running!" > index.html
        ImageId: ami-0a261c0e5f51090b1 
        KeyName: mykey 
        SecurityGroupIds:    
          - sg-01ad772aba0f98d98  # then note the security group id from AWS console todo: use web server SecurityGroup ID
        InstanceType: t3.medium # Amazon nomenclature for a specific machine which is how much RAM and which CPU
        # Specifying here that we need 10 GB hard drive for this machine and one drive is enough
        BlockDeviceMappings:
          - DeviceName: "/dev/sdk" # where do I put the hard drive, mount point where you can create folders
            Ebs:
              VolumeSize: '10' # how much Hard-drive space this machine going to need
  WebAppGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      VPCZoneIdentifier:
        - Fn::ImportValue: !Sub "${EnvironmentName}-PUB-NETS"
      LaunchTemplate:
        LaunchTemplateId: !Ref myWebAppLaunchTemplate # mandantory
        Version: !Ref myLaunchTemplateVersionNumber # mandantory
      MinSize: "3"
      MaxSize: "5"
      TargetGroupARNs: 
      - Ref: WebAppTargetGroup 
  WebAppLB:  
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer 
    Properties:
      Subnets: 
      - Fn::ImportValue: !Sub "${EnvironmentName}-PUB1-SN"
      - Fn::ImportValue: !Sub "${EnvironmentName}-PUB2-SN"
      SecurityGroups:
      - Ref: LBSecGroup
  Listener: 
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
      - Type: forward
        TargetGroupArn:
          Ref: WebAppTargetGroup
      LoadBalancerArn:
        Ref: WebAppLB
      Port: '80' 
      Protocol: HTTP
  ALBListenerRule:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
      - Type: forward # forward requests to the specified target group
        TargetGroupArn: !Ref 'WebAppTargetGroup'
      Conditions:
      - Field: path-pattern
        Values: [/] 
      ListenerArn: !Ref 'Listener'
      Priority: 1
  WebAppTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties: 
      HealthCheckIntervalSeconds: 30 
      HealthCheckPath: / 
      HealthCheckProtocol: HTTP 
      HealthCheckTimeoutSeconds: 15
      HealthyThresholdCount: 2 
      UnhealthyThresholdCount: 5 
      Port: 8080 
      Protocol: HTTP
      VpcId: # in which VPC the resource is created
        Fn::ImportValue:
          Fn::Sub: "${EnvironmentName}-VPCID"

Before creation of above infrastructure I need to create the stack below:

Parameters:
    EnvironmentName:
        Description: An environment name that will be prefixed to resource names
        Type: String

    VpcCIDR:
        Description: Please enter the IP range (CIDR notatio) for this VPC
        Type: String
        Default: 10.0.0.0/16

    PublicSubnet1CIDR:
        Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
        Type: String
        Default: 10.0.0.0/24

    PublicSubnet2CIDR:
        Description: Please enter the IP range (CIDR notation) for the public subnet in the second Availability Zone
        Type: String
        Default: 10.0.1.0/24

    PrivateSubnet1CIDR:
        Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
        Type: String
        Default: 10.0.2.0/24
    PrivateSubnet2CIDR:
        Description: Please enter the IP range (CIDR notation) for the private subnet in the second Availability Zone
        Type: String
        Default: 10.0.3.0/24
Resources:
    VPC:
        Type: AWS::EC2::VPC
        Properties:
            CidrBlock: !Ref VpcCIDR
            EnableDnsHostnames: true
            Tags:
                - Key: Name
                  Value: !Ref EnvironmentName
    InternetGateway:
        Type: AWS::EC2::InternetGateway
        Properties:
            Tags:
                - Key: Name
                  Value: !Ref EnvironmentName
    InternetGatewayAttachment:
        Type: AWS::EC2::VPCGatewayAttachment
        Properties:
            InternetGatewayId: !Ref InternetGateway
            VpcId: !Ref VPC # referencing to the VPC created earlier

    # Create 2 public subnets each in AZ0 and AZ1, except for the changed value in the field MapPublicIpOnLaunch: true.
    # Marking this field as True will enable the Auto-assign public IP address field of the public subnet

    PublicSubnet1:
        Type: AWS::EC2::Subnet
        Properties:
            VpcId: !Ref VPC # referencing to the VPC created earlier
            AvailabilityZone: !Select [0, !GetAZs ""]
            CidrBlock: !Ref PublicSubnet1CIDR # referencing to PublicSubnet1CIDR parameter
            MapPublicIpOnLaunch: true # do the subnet gets automatically an IP address
            Tags: # Name your subnets using tags, to keep track when you create many subnets.
                - Key: Name
                  Value: !Sub ${EnvironmentName} Public Subnet (AZ1)

    PublicSubnet2:
        Type: AWS::EC2::Subnet
        Properties:
            VpcId: !Ref VPC # referencing to the VPC created earlier
            AvailabilityZone: !Select [1, !GetAZs ""]
            CidrBlock: !Ref PublicSubnet2CIDR # referencing to PublicSubnet2CIDR parameter
            MapPublicIpOnLaunch: true
            Tags:
                - Key: Name
                  Value: !Sub ${EnvironmentName} Public Subnet (AZ2)

    PrivateSubnet1:
        Type: AWS::EC2::Subnet
        Properties:
            VpcId: !Ref VPC # referencing to the VPC created earlier
            AvailabilityZone: !Select [0, !GetAZs ""] # Notice that our private subnets are not sharing availability zones.
            # We are keeping them separated as we displayed in our diagrams from the previous lesson.
            # To do so, the !GetAZs‘’ function fetches the list of AZs in your region which are indexed 0, 1, etc.
            # Then, the !select [0, !GetAZs‘’] returns only the first AZ.
            CidrBlock: !Ref PrivateSubnet1CIDR # referencing to PrivateSubnet1CIDR parameter
            MapPublicIpOnLaunch: false
            Tags:
                - Key: Name
                  Value: !Sub ${EnvironmentName} Private Subnet (AZ1)

    PrivateSubnet2:
        Type: AWS::EC2::Subnet
        Properties:
            VpcId: !Ref VPC # referencing to the VPC created earlier
            AvailabilityZone: !Select [1, !GetAZs ""] # the!Select [ 0, !GetAZs '' ] is returning the first AZ from the list of all AZs in your region.
            # Similarly, for PrivateSubnet2, the !Select [ 1, !GetAZs '' ] will return the second AZ.
            CidrBlock: !Ref PrivateSubnet2CIDR # referencing to PrivateSubnet2CIDR parameter
            MapPublicIpOnLaunch: false
            Tags:
                - Key: NameAW
                  Value: !Sub ${EnvironmentName} Private Subnet (AZ2)

# The EIP in AWS::EC2::EIP stands for Elastic IP. 
# This will give us a known/constant IP address to use instead of a disposable or ever-changing IP address. 
# This is important when you have applications that depend on a particular IP address
    NatGateway1EIP:
        Type: AWS::EC2::EIP
        DependsOn: InternetGatewayAttachment
        Properties:
            Domain: vpc

# Use the DependsOn attribute to protect your dependencies from being created without the proper requirements. 
# In the scenario above the EIP allocation will only happen after the InternetGatewayAttachment has completed
    NatGateway2EIP:
        Type: AWS::EC2::EIP
        DependsOn: InternetGatewayAttachment
        Properties:
            Domain: vpc

    NatGateway1:
        Type: AWS::EC2::NatGateway
        Properties:
            AllocationId: !GetAtt NatGateway1EIP.AllocationId
            SubnetId: !Ref PublicSubnet1

    NatGateway2:
        Type: AWS::EC2::NatGateway
        Properties:
            AllocationId: !GetAtt NatGateway2EIP.AllocationId
            SubnetId: !Ref PublicSubnet2

    PublicRouteTable:
        Type: AWS::EC2::RouteTable
        Properties: 
            VpcId: !Ref VPC
            Tags: 
                - Key: Name 
                  Value: !Sub ${EnvironmentName} Public Routes

    DefaultPublicRoute: 
        Type: AWS::EC2::Route
        DependsOn: InternetGatewayAttachment # not neccessary, depends on an InternetGatewayAttachment properly working. 
        # only used when InternetGateway is attached to VPC and not in any other time during the creation of these resources
        Properties: 
            RouteTableId: !Ref PublicRouteTable
            DestinationCidrBlock: 0.0.0.0/0 # rule means: if you're routing traffic to any address (0.0.0.0 is wildcard address or all addresses), 
            #  they will be routed to this particular resource in GatewayId InternetGateway
            # 0.0.0.0 means just send traffic to InternetGateway
            # destination matching and a wildcard address (0.0.0/0) to reference all traffic.
            # when we use the wildcard address 0.0.0.0/0, we are saying for any address that is destined for any IP address in the world, 
            # send it to the referenced GatewayId
            GatewayId: !Ref InternetGateway

#VPC have route table with routes but could have multiple subnets. SubnetRouteTableAssociation associates a rules to subnets 
    PublicSubnet1RouteTableAssociation:
        Type: AWS::EC2::SubnetRouteTableAssociation
        Properties:
            RouteTableId: !Ref PublicRouteTable
            SubnetId: !Ref PublicSubnet1

    PublicSubnet2RouteTableAssociation:
        Type: AWS::EC2::SubnetRouteTableAssociation
        Properties:
            RouteTableId: !Ref PublicRouteTable
            SubnetId: !Ref PublicSubnet2
    

    PrivateRouteTable1:
        Type: AWS::EC2::RouteTable
        Properties: 
            VpcId: !Ref VPC
            Tags: 
                - Key: Name 
                  Value: !Sub ${EnvironmentName} Private Routes (AZ1)

    # route DefaultPrivateRoute1 is attached to the PrivateRouteTable1 and is routed via NatGateway1
    # routing traffic to wildcard address 0.0.0.0/0 then send this traffic to NatGateway1, don't exit to the outside
    # keep traffic for private subnets within VPC
    DefaultPrivateRoute1:
        Type: AWS::EC2::Route
        Properties:
            RouteTableId: !Ref PrivateRouteTable1
            DestinationCidrBlock: 0.0.0.0/0
            NatGatewayId: !Ref NatGateway1
    # servers within private subnet don't have IP Address
    # even placing them on a public subnet, there is no way to access them if they have no IP address

    # associate PrivateSubnet1 with rule DefaultPrivateRoute1 from route table PrivateRouteTable1
    PrivateSubnet1RouteTableAssociation:
        Type: AWS::EC2::SubnetRouteTableAssociation
        Properties:
            RouteTableId: !Ref PrivateRouteTable1
            SubnetId: !Ref PrivateSubnet1

    PrivateRouteTable2:
        Type: AWS::EC2::RouteTable
        Properties: 
            VpcId: !Ref VPC
            Tags: 
                - Key: Name 
                  Value: !Sub ${EnvironmentName} Private Routes (AZ2)

    DefaultPrivateRoute2:
        Type: AWS::EC2::Route
        Properties:
            RouteTableId: !Ref PrivateRouteTable2
            DestinationCidrBlock: 0.0.0.0/0
            NatGatewayId: !Ref NatGateway2

    PrivateSubnet2RouteTableAssociation:
        Type: AWS::EC2::SubnetRouteTableAssociation
        Properties:
            RouteTableId: !Ref PrivateRouteTable2
            SubnetId: !Ref PrivateSubnet2

Outputs:
# use this resources in other scripts or files
#  we are returning the id of our VPC as well as our Environment's Name:
    VPC:
        Description: A reference to the created VPC
        Value: !Ref VPC # referencing to the VPC created earlier
        Export:
            Name: !Sub ${EnvironmentName}-VPCID # Substitution of EnvironmentName from parameter 

    PublicSubnets:
        Description: A list of the public subnets
        Value: !Join [",", [!Ref PublicSubnet1, !Ref PublicSubnet2]]  # join means put several strings together
        Export:
            Name: !Sub ${EnvironmentName}-PUB-NETS
            # Ref get ID of  PublicSubnet1 and PublicSubnet2

    PrivateSubnets:
        Description: A list of the private subnets
        Value: !Join [",", [!Ref PrivateSubnet1, !Ref PrivateSubnet2]]
        Export:
            Name: !Sub ${EnvironmentName}-PRIV-NETS

    PublicSubnet1:
        Description: A reference to the public subnet in the 1st Availability Zone
        Value: !Ref PublicSubnet1
        Export:
            Name: !Sub ${EnvironmentName}-PUB1-SN

    PublicSubnet2:
        Description: A reference to the public subnet in the 2nd Availability Zone
        Value: !Ref PublicSubnet2
        Export:
            Name: !Sub ${EnvironmentName}-PUB2-SN

    PrivateSubnet1:
        Description: A reference to the private subnet in the 1st Availability
  • Break down the troubleshooting into chunks to eliminate possibilities. Start by logging into the instance and seeing if the webserver is running. Can you 'curl localhost' inside the instance and get a reply? If no, its an issue with the userdata script; if yes, its config external to the instance

3 Answers
1
Accepted Answer

It does not look like you are configuring Apache to listen to port 8080.

#!/bin/bash
yum update -y
yum install -y httpd
sed -i 's/Listen 80/Listen 8080/' /etc/httpd/conf/httpd.conf
systemctl start httpd
systemctl enable httpd
profile pictureAWS
EXPERT
kentrad
answered a year ago
0

@Shahad_C do you mean curl host into private instance ? What I tried is to ssh login via a bastion host aka jump box to the private instances but I always get a permission denied. I will try to curl localhost too. The instructor in Udacity also uses the following script which I will use in user data in my next try:

#!/bin/bash

Install docker

apt-get update
apt-get install -y apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
apt-get update
apt-get install -y docker-ce
usermod -aG docker ubuntu
docker run -p 8080:8080 tomcat:8.0
answered a year ago
  • Yes, if its a private instance you'd need to login to it via a Bastion. The bastion would need to have your SSH KeyPair uploaded to it for you to be able to SSH into the private instance. Once inside the private instance you would be able to check if Apache is running and accessible, as well a what port its listening on

0

thank you for your reply @kentrad. I have the same behaviour when I use the following script in UserData:

     #!/bin/bash
      yum update -y
      yum install -y httpd
      systemctl start httpd
      sudo systemctl enable httpd

How can I configure Apache3 to listen on port 8080 in my UserrData script ?

answered a year ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions