'AWS Network LoadBalancer listener and the following target groups have incompatible protocols

I have run into an issue trying to setup rtmp on AWS EC2 using CloudFormation. I have two stack's aws.yaml and web.yaml. CF responds with the following error on execution: Internal error reported from downstream service during operation 'The listener and the following target groups have incompatible protocols: [arn:...]. I've tried to specify a protocol directly in the listener but this just results in the error, You cannot specify the protocol version for a target group with the 'TCP_UDP' protocol. Thanks in advance for help!

nlb.yaml

AWSTemplateFormatVersion: 2010-09-09
Description: Deploys a single network load balancer

Parameters:
  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: ID of the VPC

  PublicSubnets:
    Type: List<AWS::EC2::Subnet::Id>
    Description: List of Public subnets to use for the Load Balancer

  CertificateArn:
    Type: String
    Description: Arn of the ACM certificate to be used by the load balancer listener.

Resources:
  PublicNlb:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      IpAddressType: ipv4
      Name: publicNlb
      Scheme: internet-facing
      Subnets: !Ref PublicSubnets
      Tags:
        - Key: Name
          Value: wordpress-nlb
      Type: network

  PublicNlbTargetGroupHttp:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Port: 80
      Protocol: TCP
      Targets:
        - Id: !ImportValue WordPressPublicAlbArn
      TargetType: alb
      VpcId: !Ref VpcId

  PublicNlbTargetGroupHttps:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Port: 443
      Protocol: TCP
      Targets:
        - Id: !ImportValue WordPressPublicAlbArn
      TargetType: alb
      VpcId: !Ref VpcId

  PublicNlbHttpListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref PublicNlbTargetGroupHttp
      LoadBalancerArn: !Ref PublicNlb
      Port: 80
      Protocol: TCP

  PublicNlbHttpsListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref PublicNlbTargetGroupHttps
      LoadBalancerArn: !Ref PublicNlb
      Port: 443
      Protocol: TCP

Outputs:
  PublicNlbTargetGroupHttpArn:
    Value: !Ref PublicNlbTargetGroupHttp
    Export:
      Name: WordPressPublicNlbTargetGroupHttpArn
  PublicNlbTargetGroupHttpsArn:
    Value: !Ref PublicNlbTargetGroupHttps
    Export:
      Name: WordPressPublicNlbTargetGroupHttpsArn
  PublicNlbCanonicalHostedZoneId:
    Value: !GetAtt PublicNlb.CanonicalHostedZoneID
  PublicNlbDnsName:
    Value: !GetAtt PublicNlb.DNSName
  PublicNlbFullName:
    Value: !GetAtt PublicNlb.LoadBalancerFullName
  PublicNlbHostname:
    Value:
      !Sub https://${PublicNlb.DNSName}
    # !If [ NoSslCertificate, !Join [ '', [ 'http://', !GetAtt PublicAlb.DNSName ] ], !Join [ '', [ 'https://', !GetAtt PublicAlb.DNSName ] ] ]
  PublicNlbHttpsListenerArn:
    Value: !Ref PublicNlbHttpsListener
    Export:
      Name: WordPressPublicNlbHttpsListenerArn
  PublicNlbHttpListenerArn:
    Value: !Ref PublicNlbHttpListener
    Export:
      Name: WordPressPublicNlbHttpListenerArn

web.yaml

AWSTemplateFormatVersion: "2010-09-09"
Description: Deploys highly available wordpress application

Parameters:
  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: ID of the VPC

  RecipeVersion:
    Type: String
    Description: The semantic version number you want to give to the recipe (in Major.Minor.Patch format).
    AllowedPattern: ^[0-9]+\.[0-9]+\.[0-9]+$

  PrivateSubnets:
    Type: List<AWS::EC2::Subnet::Id>
    Description: List of Private subnets to use for the application

  PrivateSubnetsString:
    Type: String
    Description: List of Private subnets to use for the application as  string

  Account:
    Type: String
    Default: non-prod
    AllowedValues:
      - prod
      - non-prod

  Environment:
    Type: String
    Default: dev
    AllowedValues:
      - dev
      - preprod
      - prod

  InstanceProfileName:
    Type: String
    Default: EC2RoleForSSM
    Description: The name of the InstanceProfile to be used with the instances.

  AmazonLinuxAMI:
    Type: AWS::EC2::Image::Id

  MinSize:
    Type: Number
    Description: The minimum limit of allowed instances to be deployed.
    Default: 1

  MaxSize:
    Type: Number
    Description: The maximum limit of allowed instances to be deployed.
    Default: 8

  DesiredCapacity:
    Type: Number
    Description: The average amount of instances to be deployed.
    Default: 1

  OnDemandBaseCapacity:
    Type: Number
    Description: The minimum amount of the Auto Scaling group's capacity that must be fulfilled by On-Demand Instances. For production you should always have at least 1 On-Demand instance (set to 1).
    Default: 1

  OnDemandPercentageAboveBaseCapacity:
    Type: Number
    Description: Controls the percentages of On-Demand Instances and Spot Instances for your additional capacity beyond OnDemandBaseCapacity. Expressed as a number (for example, 20 specifies 20% On-Demand Instances, 80% Spot Instances). Defaults to 0 if not specified. If set to 100, only On-Demand Instances are provisioned.
    Default: 0

Mappings:
  LoadBalancerRulePriority:
    dev:
      "rule": "3"
    preprod:
      "rule": "2"
    prod:
      "rule": "1"
  LoadBalancerHosts:
    dev:
      hostOne: "*.example.*"
    preprod:
      hostOne: "preprod.example.*"
    prod:
      hostOne: "*.example.*"
  SalingServerProperties:
    # @link https://aws.amazon.com/ec2/instance-types/
    # @link https://instaguide.io/#sortField=vcpu&sortDir=-1&platform=rhel&tenancyCode=dedicated&priceCalc=abs&period=mo
    dev:
      instanceTypeOne: "c6gd.xlarge"
    preprod:
      instanceTypeOne: "c6gd.xlarge"
    prod:
      instanceTypeOne: "c6gd.xlarge"

Conditions:
  IsProd: !Equals [!Ref Environment, prod]
  IsPreProd: !Equals [!Ref Environment, preprod]
  IsDev: !Equals [!Ref Environment, dev]

Resources: 
  AlbTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub "${Environment}-wordpress"
      HealthCheckEnabled: true
      HealthCheckIntervalSeconds: 10
      HealthCheckPath: /
      HealthCheckPort: "80"
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 4
      UnhealthyThresholdCount: 2
      Port: 80
      Protocol: HTTP
      ProtocolVersion: HTTP1
      Tags:
        - Key: Name
          Value: !Sub "${Environment}-${RecipeVersion}-wordpress"
      TargetGroupAttributes:
        - Key: stickiness.enabled
          Value: "false"
        - Key: stickiness.type
          Value: lb_cookie # or app_cookie
      TargetType: instance
      VpcId: !Ref VpcId

  AlbListenerRule:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - Type: forward
          TargetGroupArn: !Ref AlbTargetGroup
      Conditions:
        - Field: host-header
          HostHeaderConfig:
            Values:
              - dropingaming.*
              - !FindInMap [ LoadBalancerHosts, !Ref Environment, hostOne ]
      ListenerArn: !ImportValue WordPressPublicAlbHttpsListenerArn
      Priority: !FindInMap [ LoadBalancerRulePriority, !Ref Environment, rule ]

  NlbTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub "${Environment}-wordpress-rtmp"
      HealthCheckEnabled: true
      HealthCheckIntervalSeconds: 30
      HealthCheckPort: "4444"
      HealthCheckProtocol: HTTP
      Port: 1935
      Protocol: TCP_UDP
      Tags:
        - Key: Name
          Value: !Sub "${Environment}-${RecipeVersion}-wordpress"
      TargetGroupAttributes:
        - Key: stickiness.enabled
          Value: "true"
      TargetType: instance
      VpcId: !Ref VpcId

  NlbListenerRule:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - Type: forward
          TargetGroupArn: !Ref NlbTargetGroup
      Conditions:
        # Configures the rule to apply when the host header matches the domain we want to send to the target group
        # https://stackoverflow.com/questions/45664638/how-to-make-a-list-item-conditional-in-cloud-formation-template
        - Field: host-header
          HostHeaderConfig:
            Values:
              - dropingaming.*
              - !FindInMap [ LoadBalancerHosts, !Ref Environment, hostOne ]
      ListenerArn: !ImportValue WordPressPublicAlbHttpsListenerArn
      Priority: !FindInMap [ LoadBalancerRulePriority, !Ref Environment, rule ]

  LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: !Sub "${Environment}-wordpress"
      LaunchTemplateData:
        ImageId: !Ref AmazonLinuxAMI
        EbsOptimized: 'true'
        IamInstanceProfile:
          Name: EC2RoleForSSM
        SecurityGroupIds:
          - !ImportValue WordPressEc2SecurityGroupId
        # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html
        # The user data to make available to the instance. You must provide base64-encoded text.
        # User data is limited to 16 KB. For more information, see Running Commands on Your Linux Instance at Launch (Linux) or Adding User Data (Windows).
        # logging is sent to :: /var/log/cloud-init-output.log
        # this script is stored to :: /var/lib/cloud/instance/scripts/part-001
        UserData:
          Fn::Base64: !Sub
            - |
              #!/bin/bash -ex

              EC2_INSTANCE_ID="`wget -q -O - http://instance-data/latest/meta-data/instance-id || die \"wget instance-id has failed: $?\"`"

              test -n "$EC2_INSTANCE_ID" || die 'cannot obtain instance-id'

              EC2_AVAIL_ZONE="`wget -q -O - http://instance-data/latest/meta-data/placement/availability-zone || die \"wget availability-zone has failed: $?\"`"

              test -n "$EC2_AVAIL_ZONE" || die 'cannot obtain availability-zone'

              EC2_REGION="`echo \"$EC2_AVAIL_ZONE\" | sed -e 's:\([0-9][0-9]*\)[a-z]*\$:\\1:'`"
              
              EC2_PUBLIC_IPV4="`curl http://checkip.amazonaws.com`"
              
              EC2_PRIVATE_IPV4="`curl http://169.254.169.254/latest/meta-data/local-ipv4`"

              # https://tldp.org/LDP/abs/html/here-docs.html
              sudo cat > '/var/ebs-www-copy/aws.json' <<End-of-message
              {
                "AmazonLinuxAMI": "${AmazonLinuxAMI}",
                "RecipeVersion": "${RecipeVersion}",
                "EfsId": "${EfsId}",
                "VpcId": "${VpcId}",
                "PrivateSubnets": "${PrivateSubnetsString}",
                "Account": "${Account}",
                "Environment": "${Environment}",
                "InstanceProfileName": "${InstanceProfileName}",
                "MinSize": "${MinSize}",
                "MaxSize": "${MaxSize}",
                "DesiredCapacity": "${DesiredCapacity}",
                "OnDemandBaseCapacity": "${OnDemandBaseCapacity}",
                "OnDemandPercentageAboveBaseCapacity": "${OnDemandPercentageAboveBaseCapacity}",
                "EC2_PUBLIC_IPV4": "$EC2_PUBLIC_IPV4",
                "EC2_PRIVATE_IPV4": "$EC2_PRIVATE_IPV4",
                "EC2_INSTANCE_ID": "$EC2_INSTANCE_ID",
                "EC2_AVAIL_ZONE": "$EC2_AVAIL_ZONE",
                "EC2_REGION": "$EC2_REGION"
              }
              End-of-message

              cat /var/ebs-www-copy/aws.json

              # redacting the overly specific :)

              systemctl start httpd

            - EfsId: !ImportValue "WordPressEfsId"

  # https://github.com/aws-samples/session-manager-without-igw/blob/main/session-manager-without-igw.yml
  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AutoScalingGroupName: !Sub "${Environment}-wordpress"
      TargetGroupARNs:
        - !Ref AlbTargetGroup
        - !Ref NlbTargetGroup
      MinSize: !Ref MinSize
      MaxSize: !Ref MaxSize
      DesiredCapacity: !Ref DesiredCapacity
      VPCZoneIdentifier: !Ref PrivateSubnets
      MixedInstancesPolicy:
        LaunchTemplate:
          LaunchTemplateSpecification:
            # Version: !Ref LaunchTemplateVersionNumber
            Version: !GetAtt
              - LaunchTemplate
              - LatestVersionNumber
            LaunchTemplateId: !Ref LaunchTemplate
          # https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-mixed-instances-groups-launch-template-overrides.html
          # @link https://aws.amazon.com/ec2/instance-types/
          # @link https://instaguide.io/#sortField=vcpu&sortDir=-1&platform=rhel&tenancyCode=dedicated&priceCalc=abs&period=mo
          Overrides:
            # I want to know if predictive load b is bs or nah
            - InstanceType: !FindInMap [SalingServerProperties, !Ref Environment, instanceTypeOne]

        InstancesDistribution:
          OnDemandBaseCapacity: !Ref OnDemandBaseCapacity
          OnDemandPercentageAboveBaseCapacity: !Ref OnDemandPercentageAboveBaseCapacity
          # https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-mixed-instances-groups.html#allocation-strategies
          SpotAllocationStrategy: lowest-price
          OnDemandAllocationStrategy: lowest-price
      Tags:
        - Key: "Name"
          PropagateAtLaunch: true
          Value: !Sub "${Environment}-wordpress"
        - Key: "Version"
          PropagateAtLaunch: true
          Value: !Sub "${RecipeVersion}"

  AutoScalingPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AutoScalingGroupName: !Ref AutoScalingGroup
      PolicyType: PredictiveScaling
      PredictiveScalingConfiguration:
        MetricSpecifications:
          - TargetValue: 70
            PredefinedMetricPairSpecification:
              PredefinedMetricType: ASGCPUUtilization

So based on the error the NlbListenerRule seems to be what is failing; though, the downstream service bit has me confused. Google return near nothing, so I'm hopeful you may know!?



Solution 1:[1]

So AWS support along with the comment above lead me straight to the problem. The NlbListenerRule was using an incorrect ListenerArn: !ImportValue WordPressPublicAlbHttpsListenerArn. Also, another issues which is catchable in the provided templates above was caught by the support team "NLB work on Layer-4 of OSI Model , and we cannot create NLB Listener rule based on Host-Header Condition." Thus ListenerRule's may only be applied to HTTP based hosts.

changed in web.yaml

NlbListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref NlbTargetGroup
      LoadBalancerArn: !ImportValue PublicNlbLoadBalancerArn
      Port: 1935
      Protocol: TCP_UDP

added in alb.yaml

Outputs:
  PublicNlb:
    Value: !Ref PublicNlb
    Export:
      Name: PublicNlbLoadBalancerArn
  PublicNlbTargetGroupHttpArn:

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Richard Tyler Miles