Skip to content

NE-2421: Support dual-stack IngressController on AWS#1376

Open
alebedev87 wants to merge 1 commit intoopenshift:masterfrom
alebedev87:NE-2421-support-dualstack-ingresscontroller
Open

NE-2421: Support dual-stack IngressController on AWS#1376
alebedev87 wants to merge 1 commit intoopenshift:masterfrom
alebedev87:NE-2421-support-dualstack-ingresscontroller

Conversation

@alebedev87
Copy link
Contributor

@alebedev87 alebedev87 commented Mar 5, 2026

Configure the publishing load balancer service for dual-stack AWS clusters based on the Infrastructure CR's ipFamily field (status.platformStatus.aws.ipFamily).

For NLB-type load balancers, set ipFamilyPolicy to RequireDualStack and ipFamilies to match the cluster's IP family ordering. For CLB, explicitly set SingleStack/IPv4 since CLB only forwards IPv4 traffic. Without this, DualStackIPv6Primary clusters would default the CLB service to SingleStack/IPv6, causing OVN to refuse IPv4 traffic on the service's NodePort.

When the cluster IP family is dual-stack, the AWS DNS provider creates both Alias A and Alias AAAA Route53 records for IngressController wildcard domains. AAAA records are always created regardless of LB type because stale AAAA records cannot be easily cleaned up when the LB type changes from NLB to CLB — the old NLB is deleted before the new CLB is created, so the DNS provider can no longer look up the target hostname to delete the AAAA record. For CLBs the AAAA alias simply won't resolve.

When the LB type is changed to CLB on a dual-stack cluster, a warning note is appended to the effectuation message on the cluster operator status indicating that CLBs do not support dual-stack.

Enhancement proposal: openshift/enhancements#1940.
Related upstream cloud-provider-aws change: kubernetes/cloud-provider-aws#1313.
E2E testing will be implemented as part of the featuregate test coverage: openshift/origin#30904.

Manual test

The test can only be manual for the moment while CCM changes are still in progress. Clusterbot command used:

build 4.22,openshift/cluster-ingress-operator#1376,openshift/cloud-provider-aws#135

DualStackIPv4Primary

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
  - cidr: 10.128.0.0/14
    hostPrefix: 23
  - cidr: fd01::/48
    hostPrefix: 64
machineNetwork:
  - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
  - 172.30.0.0/16
  - fd02::/112
$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
  region: us-east-2
  ipFamily: DualStackIPv4Primary
  lbType: NLB

Note that the load balancer type must be NLB for dual stack IP family, this is a requirement.

Infrastructure CR:

$ oc get infrastructure cluster -o yaml | yq .status.platformStatus
aws:
  cloudLoadBalancerConfig:
    dnsType: PlatformDefault
  ipFamily: DualStackIPv4Primary
  region: us-east-2
type: AWS

IngressController CR:

$ oc -n openshift-ingress-operator get ingresscontroller default -o yaml| yq .spec.endpointPublishingStrategy
loadBalancer:
  dnsManagementPolicy: Managed
  providerParameters:
    aws:
      type: Classic
    type: AWS
  scope: External
type: LoadBalancerService

Publishing load balancer service:

$ oc -n openshift-ingress get service router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.241.69
clusterIPs:
  - 172.30.241.69
  - fd02::133d
externalTrafficPolicy: Local
healthCheckNodePort: 30592
internalTrafficPolicy: Cluster
ipFamilies:
  - IPv4
  - IPv6
ipFamilyPolicy: RequireDualStack

$ oc -n openshift-ingress get svc router-default -o yaml | yq .status
loadBalancer:
  ingress:
    - hostname: a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

DNSRecord CR:

$ oc -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .spec
dnsManagementPolicy: Managed
dnsName: '*.apps.alebedev-0317.devcluster.openshift.com.'
recordTTL: 30
recordType: CNAME
targets:
  - a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

$ oc  -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .status.zones
- conditions:
    - lastTransitionTime: "2026-03-17T10:34:39Z"
      message: The DNS provider succeeded in ensuring the record
      reason: ProviderSuccess
      status: "True"
      type: Published
  dnsZone:
    id: Z3URY6TWQ91KVV

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.apps.alebedev-0317.devcluster.openshift.com.']"
[
    {
        "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
        "Type": "A",
        "AliasTarget": {
            "HostedZoneId": "ZLMOA37VPKANP",
            "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
            "EvaluateTargetHealth": false
        }
    },
    {
        "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
        "Type": "AAAA",
        "AliasTarget": {
            "HostedZoneId": "ZLMOA37VPKANP",
            "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
            "EvaluateTargetHealth": false
        }
    }
]

DNS query:

$ dig +short -t AAAA a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7
2600:1f16:c02:8006:b886:bd46:968d:7e64

$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
2600:1f16:c02:8006:b886:bd46:968d:7e64
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7

e2e connectivity (any IPv6 ingress rule is still missing in CCM implementation though, TBC):

$ oc debug node/i-01ee44c772dd27883.us-east-2.compute.internal
Starting pod/i-01ee44c772dd27883us-east-2computeinternal-debug-vnlzq ...
To use host binaries, run `chroot /host`
Pod IP: 10.0.85.93
If you don't see a command prompt, try pressing enter.
sh-5.1# chroot /host
sh-5.1# curl -6 -kI --connect-timeout 5 https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com/
HTTP/1.1 200 OK

Changing the LB type to Classic:

$ oc get co ingress
NAME      VERSION                                                AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
ingress   4.22.0-0-2026-03-13-170918-test-ci-ln-wgyfqy2-latest   True        True          False      73m     ingresscontroller "default" is progressing: IngressControllerProgressing: One or more status conditions indicate progressing: LoadBalancerProgressing=True (OperandsProgressing: One or more managed resources are progressing: [The IngressController loadBalancer.providerParameters.aws.type was changed from "NLB" to "Classic".  To effectuate this change, you must delete the service: `oc -n openshift-ingress delete svc/router-default`; the service load-balancer will then be deprovisioned and a new one created. This will most likely cause the new load-balancer to have a different host name and IP address and cause disruption. To return to the previous state, you can revert the change to the IngressController: `oc -n openshift-ingress-operator patch ingresscontrollers/default --type=merge --patch='{"spec":{"endpointPublishingStrategy":{"type":"LoadBalancerService","loadBalancer":{"providerParameters":{"type":"AWS","aws":{"type":"NLB"}}}}}}'`. Direct updates to the service annotations are not supported. Classic Load Balancers do not support dual-stack. The IngressController "default" will use IPv4-only despite that the cluster is configured as "DualStackIPv4Primary". Use an NLB type to support dual-stack networking.]).

$ oc -n openshift-ingress delete svc/router-default
service "router-default" deleted

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.132.69
clusterIPs:
  - 172.30.132.69
externalTrafficPolicy: Local
healthCheckNodePort: 30575
internalTrafficPolicy: Cluster
ipFamilies:
  - IPv4
ipFamilyPolicy: SingleStack

DNS query after a change to CLB:

$ dig +short -t A console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
16.58.45.52
3.21.115.53
$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Connectivity after a change to CLB:

# curl -4 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
HTTP/1.1 200 OK

# curl -6 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
curl: (6) Could not resolve host: console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Clean up of DNS (new shard to showcase the cleanup):

$ oc -n openshift-ingress-operator get ingresscontroller my -o yaml | yq .spec.domain
hosts.alebedev-0317.devcluster.openshift.com

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[
    {
        "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
        "Type": "A",
        "AliasTarget": {
            "HostedZoneId": "Z3AADJGX6KTTL2",
            "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
            "EvaluateTargetHealth": false
        }
    },
    {
        "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
        "Type": "AAAA",
        "AliasTarget": {
            "HostedZoneId": "Z3AADJGX6KTTL2",
            "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
            "EvaluateTargetHealth": false
        }
    }
]

$ oc -n openshift-ingress-operator delete ingresscontroller my
ingresscontroller.operator.openshift.io "my" deleted

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[]

DualStackIPv6Primary

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
  - cidr: fd01::/48
    hostPrefix: 64
  - cidr: 10.128.0.0/14
    hostPrefix: 23
machineNetwork:
  - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
  - fd02::/112
  - 172.30.0.0/16

$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
  region: us-east-2
  ipFamily: DualStackIPv6Primary
  lbType: NLB

Publishing service:

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
clusterIP: fd02::3dac
clusterIPs:
  - fd02::3dac
  - 172.30.4.110
externalTrafficPolicy: Local
healthCheckNodePort: 32125
internalTrafficPolicy: Cluster
ipFamilies:
  - IPv6
  - IPv4
ipFamilyPolicy: RequireDualStack

DNS check:

$ dig +short -t A console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
3.132.149.10
18.225.48.44

$ dig +short -t AAAA console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
2600:1f16:1b80:2306:a3e4:ea37:6b15:9355
2600:1f16:1b80:2304:7f94:5708:1f66:af79

Connectivity check:

$ oc debug node/i-040c1020eeb1401ab.us-east-2.compute.internal
Starting pod/i-040c1020eeb1401abus-east-2computeinternal-debug-cv64v ...
To use host binaries, run `chroot /host`
Pod IP: 2600:1f16:1b80:2303:669e:5b4d:7a21:56
If you don't see a command prompt, try pressing enter.
sh-5.1# curl -6 -kI https://console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
HTTP/1.1 200 OK

sh-5.1# curl -4 -kI https://console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
HTTP/1.1 200 OK

CLB shard

$ oc -n openshift-ingress-operator get ingresscontroller my -o yaml | yq .spec.domain
hosts.alebedev-03182.devcluster.openshift.com


$ oc -n openshift-ingress get svc router-my -o yaml | yq .metadata.annotations
service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold: "2"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "5"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "4"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: "2"
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: '*'
traffic-policy.network.alpha.openshift.io/local-with-fallback: ""

$ oc -n openshift-ingress get svc router-my -o yaml | yq .spec
clusterIP: 172.30.28.213
clusterIPs:
  - 172.30.28.213
externalTrafficPolicy: Local
healthCheckNodePort: 30968
internalTrafficPolicy: Cluster
ipFamilies:
  - IPv4
ipFamilyPolicy: SingleStack

CLB connectivity check:

$ oc debug node/i-040c1020eeb1401ab.us-east-2.compute.internal
Starting pod/i-040c1020eeb1401abus-east-2computeinternal-debug-7nhlc ...
To use host binaries, run `chroot /host`
cuPod IP: 2600:1f16:1b80:2303:669e:5b4d:7a21:56
If you don't see a command prompt, try pressing enter.
sh-5.1# curl -4 -kI https://downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com
HTTP/1.0 200 OK

sh-5.1# curl -6 -kI https://downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com
curl: (6) Could not resolve host: downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Mar 5, 2026
@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 5, 2026

@alebedev87: This pull request references NE-2421 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Configure the publishing load balancer service's ipFamilies and ipFamilyPolicy based on the Infrastructure CR's platform ipFamily field. For NLB-type load balancers on dual-stack clusters, the service is configured with RequireDualStack policy and the appropriate IP family ordering. CLB does not support dual-stack and falls back to IPv4-only.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@coderabbitai
Copy link

coderabbitai bot commented Mar 5, 2026

Important

Review skipped

Auto reviews are limited based on label configuration.

🚫 Review skipped — only excluded labels are configured. (1)
  • do-not-merge/work-in-progress

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: dc3f8aa0-be7e-4c25-826c-1b6ea908305b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds dual‑stack handling across the ingress and DNS codepaths for AWS: LoadBalancer service creation and reconciliation now set and propagate ipFamilyPolicy and ipFamilies when an AWS NLB and a dual‑stack IPFamily are indicated, and emit a runtime warning when AWS Classic LB is used with dual‑stack. DNS provider construction now receives an IPFamily config and Route53 updates can emit both A and AAAA alias records when the target supports dual‑stack. A new pkg/util/aws.IsDualStack helper was added and tests were extended to cover these behaviors.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title directly describes the main objective: adding dual-stack support for IngressController on AWS, which matches the core changes across load balancer service, DNS provider, and dual-stack utilities.
Docstring Coverage ✅ Passed Docstring coverage is 82.35% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed The PR description clearly explains the objective to support dual-stack IngressController on AWS by configuring ipFamilies and ipFamilyPolicy based on Infrastructure CR platform settings, with NLB supporting dual-stack and CLB falling back to IPv4-only.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@alebedev87 alebedev87 changed the title NE-2421: Support dual-stack IngressController on AWS [WIP] NE-2421: Support dual-stack IngressController on AWS Mar 5, 2026
@openshift-ci openshift-ci bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Mar 5, 2026
@openshift-ci openshift-ci bot requested review from bentito and davidesalerno March 5, 2026 17:07
@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 5, 2026

@alebedev87: This pull request references NE-2421 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Configure the publishing load balancer service's ipFamilies and ipFamilyPolicy based on the Infrastructure CR's platform ipFamily field. For NLB-type load balancers on dual-stack clusters, the service is configured with RequireDualStack policy and the appropriate IP family ordering. CLB does not support dual-stack and falls back to IPv4-only.

Enhancement proposal: openshift/enhancements#1940.
Related cloud-provider-aws change: kubernetes/cloud-provider-aws#1313.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
pkg/operator/controller/ingress/load_balancer_service_test.go (1)

1367-1381: Add a focused test for CLB dual-stack progressing semantics.

You added good update-detection coverage, but there’s still no assertion around the new CLB dual-stack path in loadBalancerServiceIsProgressing. A dedicated test would lock in whether this is advisory-only or should affect Progressing status.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/operator/controller/ingress/load_balancer_service_test.go` around lines
1367 - 1381, Add a focused unit test in load_balancer_service_test.go that
asserts the CLB dual-stack code path in loadBalancerServiceIsProgressing: create
a Service fixture, mutate it to represent the CLB dual-stack scenario (set
.Spec.IPFamilies to both IPv4 and IPv6 and/or .Spec.IPFamilyPolicy to
RequireDualStack as in the existing table entries), call
loadBalancerServiceIsProgressing(originalSvc, mutatedSvc) and assert the
expected boolean (true if this should mark Progressing or false if it's
advisory-only). Reference the existing test table and the function
loadBalancerServiceIsProgressing to add one explicit case that checks the CLB
dual-stack semantics. Ensure the test name and description clearly state it
targets "CLB dual-stack progressing semantics."
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/operator/controller/ingress/load_balancer_service_test.go`:
- Around line 1367-1381: Add a focused unit test in
load_balancer_service_test.go that asserts the CLB dual-stack code path in
loadBalancerServiceIsProgressing: create a Service fixture, mutate it to
represent the CLB dual-stack scenario (set .Spec.IPFamilies to both IPv4 and
IPv6 and/or .Spec.IPFamilyPolicy to RequireDualStack as in the existing table
entries), call loadBalancerServiceIsProgressing(originalSvc, mutatedSvc) and
assert the expected boolean (true if this should mark Progressing or false if
it's advisory-only). Reference the existing test table and the function
loadBalancerServiceIsProgressing to add one explicit case that checks the CLB
dual-stack semantics. Ensure the test name and description clearly state it
targets "CLB dual-stack progressing semantics."

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7d5b6069-ac69-45ba-bcc7-e17d7c92622b

📥 Commits

Reviewing files that changed from the base of the PR and between a516b7d and ff1332c.

📒 Files selected for processing (2)
  • pkg/operator/controller/ingress/load_balancer_service.go
  • pkg/operator/controller/ingress/load_balancer_service_test.go

@alebedev87 alebedev87 force-pushed the NE-2421-support-dualstack-ingresscontroller branch from ff1332c to 0e7d3a5 Compare March 5, 2026 17:17
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
pkg/operator/controller/ingress/load_balancer_service.go (1)

900-902: ⚠️ Potential issue | 🟠 Major

Don't treat CLB dual-stack fallback as a progressing error.

At Line 900, this warning is appended to errs, so it behaves as a persistent progress blocker instead of advisory guidance. That can leave status permanently progressing for an intentionally supported fallback (IPv4-only CLB).

Suggested fix
-		if wantLBType == operatorv1.AWSClassicLoadBalancer && platform.AWS != nil && isAWSDualStack(platform.AWS.IPFamily) {
-			errs = append(errs, fmt.Errorf("Classic Load Balancers do not support dual-stack. The IngressController %q will use IPv4-only despite that the cluster is configured as %q. Use an NLB type to support dual-stack networking.", ic.Name, platform.AWS.IPFamily))
-		}
+		if wantLBType == operatorv1.AWSClassicLoadBalancer && platform.AWS != nil && isAWSDualStack(platform.AWS.IPFamily) {
+			log.Info("Classic Load Balancer does not support dual-stack; falling back to IPv4-only. Use NLB for dual-stack.",
+				"ingresscontroller", ic.Name,
+				"ipFamily", platform.AWS.IPFamily)
+		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/operator/controller/ingress/load_balancer_service.go` around lines 900 -
902, The check that currently appends a dual-stack fallback message to errs (the
fmt.Errorf appended when wantLBType == operatorv1.AWSClassicLoadBalancer &&
platform.AWS != nil && isAWSDualStack(platform.AWS.IPFamily)) should not produce
a progressing/blocking error; change it to emit a non-blocking advisory instead
(e.g., log via the controller logger, record an Event, or append to a dedicated
warnings/advisories slice or status condition) so the CLB IPv4-only fallback is
informational only and does not leave the IngressController permanently
progressing. Ensure you update the code that previously consumed errs (if any)
to surface these advisories appropriately instead of treating them as errors.
🧹 Nitpick comments (2)
pkg/operator/controller/ingress/load_balancer_service_test.go (1)

1017-1052: Consider deduplicating nlbStrategy with the existing nlb helper in the same file.

The new nlbStrategy helper function (lines 1017-1031) appears to duplicate the existing nlb helper defined within Test_desiredLoadBalancerService (lines 70-79). Consider extracting the nlb helper from the existing test to the package level and reusing it, or refactoring both into a single shared helper to reduce duplication.

Example refactor

The existing nlb function at line 70-79 in Test_desiredLoadBalancerService could be moved to package level and renamed to nlbStrategy, then reused in both test functions:

// At package level (outside any test function)
func nlbStrategy(scope operatorv1.LoadBalancerScope) *operatorv1.EndpointPublishingStrategy {
    return &operatorv1.EndpointPublishingStrategy{
        Type: operatorv1.LoadBalancerServiceStrategyType,
        LoadBalancer: &operatorv1.LoadBalancerStrategy{
            Scope: scope,
            ProviderParameters: &operatorv1.ProviderLoadBalancerParameters{
                Type: operatorv1.AWSLoadBalancerProvider,
                AWS: &operatorv1.AWSLoadBalancerParameters{
                    Type: operatorv1.AWSNetworkLoadBalancer,
                },
            },
        },
    }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/operator/controller/ingress/load_balancer_service_test.go` around lines
1017 - 1052, The nlbStrategy function duplicates the existing nlb helper inside
Test_desiredLoadBalancerService; remove the duplication by moving the nlb helper
to package scope (or rename the package-level nlbStrategy to the same helper)
and have both tests call that single helper (reference helpers: nlb, nlbStrategy
and the test Test_desiredLoadBalancerService) so there is one shared function
returning the AWSNetworkLoadBalancer EndpointPublishingStrategy used by both
tests.
pkg/operator/controller/ingress/load_balancer_service.go (1)

733-752: Consider reconciling both IPFamilies and IPFamilyPolicy together to avoid partial state.

The current logic reconciles IPFamilies and IPFamilyPolicy independently. If only one field is set on the expected service but not the other, this could result in a partial configuration. For consistency, consider ensuring both fields are set together when either is present.

Additionally, the pattern of repeatedly checking !changed and calling current.DeepCopy() is repeated here. While this matches the existing codebase style, extracting this into a helper could reduce duplication in the future.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/operator/controller/ingress/load_balancer_service.go` around lines 733 -
752, The code reconciles expected.Spec.IPFamilies and
expected.Spec.IPFamilyPolicy independently which can leave the Service in a
partial state; change the logic in the reconciliation block that currently
references expected.Spec.IPFamilies, expected.Spec.IPFamilyPolicy,
current.Spec.IPFamilies, current.Spec.IPFamilyPolicy, changed, and updated to
reconcile them together: if either expected.Spec.IPFamilies is non-empty or
expected.Spec.IPFamilyPolicy is non-nil, compare both fields to current and if
any differ, set changed = true once, call updated = current.DeepCopy() once, and
set both updated.Spec.IPFamilies = expected.Spec.IPFamilies and
updated.Spec.IPFamilyPolicy = expected.Spec.IPFamilyPolicy (handling nil/empty
appropriately) so the Service is updated atomically; keep the rest of the
surrounding update flow unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@pkg/operator/controller/ingress/load_balancer_service.go`:
- Around line 900-902: The check that currently appends a dual-stack fallback
message to errs (the fmt.Errorf appended when wantLBType ==
operatorv1.AWSClassicLoadBalancer && platform.AWS != nil &&
isAWSDualStack(platform.AWS.IPFamily)) should not produce a progressing/blocking
error; change it to emit a non-blocking advisory instead (e.g., log via the
controller logger, record an Event, or append to a dedicated warnings/advisories
slice or status condition) so the CLB IPv4-only fallback is informational only
and does not leave the IngressController permanently progressing. Ensure you
update the code that previously consumed errs (if any) to surface these
advisories appropriately instead of treating them as errors.

---

Nitpick comments:
In `@pkg/operator/controller/ingress/load_balancer_service_test.go`:
- Around line 1017-1052: The nlbStrategy function duplicates the existing nlb
helper inside Test_desiredLoadBalancerService; remove the duplication by moving
the nlb helper to package scope (or rename the package-level nlbStrategy to the
same helper) and have both tests call that single helper (reference helpers:
nlb, nlbStrategy and the test Test_desiredLoadBalancerService) so there is one
shared function returning the AWSNetworkLoadBalancer EndpointPublishingStrategy
used by both tests.

In `@pkg/operator/controller/ingress/load_balancer_service.go`:
- Around line 733-752: The code reconciles expected.Spec.IPFamilies and
expected.Spec.IPFamilyPolicy independently which can leave the Service in a
partial state; change the logic in the reconciliation block that currently
references expected.Spec.IPFamilies, expected.Spec.IPFamilyPolicy,
current.Spec.IPFamilies, current.Spec.IPFamilyPolicy, changed, and updated to
reconcile them together: if either expected.Spec.IPFamilies is non-empty or
expected.Spec.IPFamilyPolicy is non-nil, compare both fields to current and if
any differ, set changed = true once, call updated = current.DeepCopy() once, and
set both updated.Spec.IPFamilies = expected.Spec.IPFamilies and
updated.Spec.IPFamilyPolicy = expected.Spec.IPFamilyPolicy (handling nil/empty
appropriately) so the Service is updated atomically; keep the rest of the
surrounding update flow unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d9fc3866-31dd-496e-82b7-b8c34041d7de

📥 Commits

Reviewing files that changed from the base of the PR and between ff1332c and 0e7d3a5.

📒 Files selected for processing (2)
  • pkg/operator/controller/ingress/load_balancer_service.go
  • pkg/operator/controller/ingress/load_balancer_service_test.go

@alebedev87 alebedev87 force-pushed the NE-2421-support-dualstack-ingresscontroller branch from 0e7d3a5 to fc38005 Compare March 10, 2026 11:30
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
pkg/operator/controller/ingress/load_balancer_service.go (1)

499-512: LGTM with optional refactor to reduce duplication.

The dual-stack configuration logic is correct. The ipFamilyPolicy variable is identically set in both switch cases and could be hoisted out.

♻️ Optional refactor to reduce duplication
 			// Set ipFamilies and ipFamilyPolicy for dual-stack clusters.
 			// Only NLB supports dual-stack; CLB does not.
-			if platform.AWS != nil && isAWSNLB(lbStatus) {
-				switch platform.AWS.IPFamily {
-				case configv1.DualStackIPv4Primary:
-					ipFamilyPolicy := corev1.IPFamilyPolicyRequireDualStack
-					service.Spec.IPFamilyPolicy = &ipFamilyPolicy
-					service.Spec.IPFamilies = []corev1.IPFamily{corev1.IPv4Protocol, corev1.IPv6Protocol}
-				case configv1.DualStackIPv6Primary:
-					ipFamilyPolicy := corev1.IPFamilyPolicyRequireDualStack
-					service.Spec.IPFamilyPolicy = &ipFamilyPolicy
-					service.Spec.IPFamilies = []corev1.IPFamily{corev1.IPv6Protocol, corev1.IPv4Protocol}
-				}
+			if platform.AWS != nil && isAWSNLB(lbStatus) && isAWSDualStack(platform.AWS.IPFamily) {
+				ipFamilyPolicy := corev1.IPFamilyPolicyRequireDualStack
+				service.Spec.IPFamilyPolicy = &ipFamilyPolicy
+				if platform.AWS.IPFamily == configv1.DualStackIPv4Primary {
+					service.Spec.IPFamilies = []corev1.IPFamily{corev1.IPv4Protocol, corev1.IPv6Protocol}
+				} else {
+					service.Spec.IPFamilies = []corev1.IPFamily{corev1.IPv6Protocol, corev1.IPv4Protocol}
+				}
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/operator/controller/ingress/load_balancer_service.go` around lines 499 -
512, The ipFamilyPolicy variable is duplicated in both switch cases; hoist its
declaration and assignment before the switch so you set
service.Spec.IPFamilyPolicy once (e.g., create ipFamilyPolicy :=
corev1.IPFamilyPolicyRequireDualStack and assign service.Spec.IPFamilyPolicy =
&ipFamilyPolicy before switch), then inside the switch (where isAWSNLB and
platform.AWS.IPFamily are used) only set service.Spec.IPFamilies to the
appropriate slice (corev1.IPv4Protocol, corev1.IPv6Protocol) or
(corev1.IPv6Protocol, corev1.IPv4Protocol) for DualStackIPv4Primary and
DualStackIPv6Primary respectively to preserve behavior while removing
duplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/operator/controller/ingress/load_balancer_service.go`:
- Around line 733-752: The reconciler must treat a change in the primary IP
family (the first element of spec.IPFamilies) as a recreate condition; update
shouldRecreateLoadBalancer() to return true when both current.Spec.IPFamilies
and expected.Spec.IPFamilies are non-empty and their [0] entries differ. Locate
shouldRecreateLoadBalancer() and add a check like: if
len(current.Spec.IPFamilies) > 0 && len(expected.Spec.IPFamilies) > 0 &&
current.Spec.IPFamilies[0] != expected.Spec.IPFamilies[0] { return true } so the
controller will recreate the Service rather than attempting an update that the
API server will reject. Ensure this logic complements the existing
ipFamilies/ipFamilyPolicy update handling in the load balancer reconciliation.

---

Nitpick comments:
In `@pkg/operator/controller/ingress/load_balancer_service.go`:
- Around line 499-512: The ipFamilyPolicy variable is duplicated in both switch
cases; hoist its declaration and assignment before the switch so you set
service.Spec.IPFamilyPolicy once (e.g., create ipFamilyPolicy :=
corev1.IPFamilyPolicyRequireDualStack and assign service.Spec.IPFamilyPolicy =
&ipFamilyPolicy before switch), then inside the switch (where isAWSNLB and
platform.AWS.IPFamily are used) only set service.Spec.IPFamilies to the
appropriate slice (corev1.IPv4Protocol, corev1.IPv6Protocol) or
(corev1.IPv6Protocol, corev1.IPv4Protocol) for DualStackIPv4Primary and
DualStackIPv6Primary respectively to preserve behavior while removing
duplication.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d5d71000-16e8-46e3-93d9-762b30f7c264

📥 Commits

Reviewing files that changed from the base of the PR and between 0e7d3a5 and fc38005.

📒 Files selected for processing (2)
  • pkg/operator/controller/ingress/load_balancer_service.go
  • pkg/operator/controller/ingress/load_balancer_service_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/operator/controller/ingress/load_balancer_service_test.go

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/dns/aws/dns.go`:
- Around line 114-117: The code currently uses IPFamily to decide creating AAAA
records; change the logic to require both a dual-stack cluster (IPFamily ==
IPv6DualStack) AND that the resolved load balancer target is IPv6-capable before
publishing AAAA aliases. Locate the code using the IPFamily field and the
variable named target (and the Route53 record creation code around the same
block and at the later similar block referencing IPFamily) and add a check
against the resolved target's capability (e.g., a property or method on the
resolved target like IsIPv6Capable / IsDualStack / Type != "classic" or presence
of IPv6 addresses) so Classic ELBs remain IPv4-only while ELBv2/NLB/ALB may get
AAAA records when both checks pass.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c968171d-cb12-44e2-b98e-eafa45780990

📥 Commits

Reviewing files that changed from the base of the PR and between fc38005 and c6cdc47.

📒 Files selected for processing (3)
  • pkg/dns/aws/dns.go
  • pkg/operator/controller/dns/controller.go
  • pkg/util/aws/dualstack.go

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
pkg/dns/aws/dns.go (2)

561-569: ⚠️ Potential issue | 🟠 Major

Persist the LB IP address type in the fallback metadata.

When the lookup on Line 561 fails, the fallback restores only targetHostedZoneID. targetIPAddressType stays empty, so the delete path on Line 600 can only remove the A alias and will leave the AAAA alias behind for a dual-stack NLB.

Suggested fix
 const (
 	targetHostedZoneIdAnnotationKey = "ingress.operator.openshift.io/target-hosted-zone-id"
+	targetIPAddressTypeAnnotationKey = "ingress.operator.openshift.io/target-ip-address-type"
 )
 	if err != nil {
 		err = fmt.Errorf("failed to get hosted zone for load balancer target %q: %v", target, err)
 		if v, ok := record.Annotations[targetHostedZoneIdAnnotationKey]; !ok {
 			return err
 		} else {
 			log.Error(err, "falling back to the "+targetHostedZoneIdAnnotationKey+" annotation", "value", v)
 			targetHostedZoneID = v
+			targetIPAddressType = record.Annotations[targetIPAddressTypeAnnotationKey]
 		}
 	}
-		} else if _, ok := current.Annotations[targetHostedZoneIdAnnotationKey]; !ok {
+		} else if current.Annotations[targetHostedZoneIdAnnotationKey] != targetHostedZoneID ||
+			current.Annotations[targetIPAddressTypeAnnotationKey] != targetIPAddressType {
 			updated := current.DeepCopy()
 			if updated.Annotations == nil {
 				updated.Annotations = map[string]string{}
 			}
 			updated.Annotations[targetHostedZoneIdAnnotationKey] = targetHostedZoneID
+			updated.Annotations[targetIPAddressTypeAnnotationKey] = targetIPAddressType
 			if err := m.config.Client.Update(context.TODO(), updated); err != nil {

Also applies to: 585-595, 600-600

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/dns/aws/dns.go` around lines 561 - 569, When
getLBHostedZoneAndIPAddressType(target) fails and you fall back to using the
annotation value for targetHostedZoneID, also persist the load‑balancer IP
address type into targetIPAddressType so delete logic can remove both A and AAAA
records for dual‑stack NLBs; specifically, in the same fallback block where you
read record.Annotations[targetHostedZoneIdAnnotationKey], also read
record.Annotations[targetIPAddressTypeAnnotationKey] (or set a sensible default
if absent) and assign it to targetIPAddressType, and apply the same change to
the other similar fallback blocks around the delete path (the other places
handling getLBHostedZoneAndIPAddressType failures) so the IP address type is
always available.

641-675: ⚠️ Potential issue | 🟠 Major

Don't batch A and AAAA deletes when one record set may not exist.

This code deletes A and AAAA in a single Route53 change batch. Route53 treats change batches as atomic: if validation fails for any change (for example, a DELETE referring to a non-existent record set), the entire batch is canceled and no changes are applied.

If only the A record exists (not AAAA), the DELETE for the AAAA record fails, which cancels the entire batch. The A record remains. However, the error handler at lines 667–673 catches the resulting "not found" error and returns nil, falsely reporting success. The record is silently left behind.

Fix: Either delete records individually per type, or first verify that both record sets exist before batching deletes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/dns/aws/dns.go` around lines 641 - 675, The delete logic currently
batches A and AAAA changes (the changes slice and input.ChangeBatch) which is
atomic and can fail if one record type doesn't exist; update the code path that
sets action == string(deleteAction) so deletes are performed per-record-type
instead of in one batch: build separate ChangeBatch inputs for route53.RRTypeA
and route53.RRTypeAaaa (using the same aliasTarget and zoneID/domain) and call
m.route53.ChangeResourceRecordSets for each type individually, and keep the
existing per-call error handling that checks awserr.Error and "not found" so a
missing AAAA won't block A deletion; reference symbols: changes,
input.ChangeBatch, m.route53.ChangeResourceRecordSets, deleteAction, action,
route53.RRTypeA, route53.RRTypeAaaa, targetIPAddressType.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@pkg/dns/aws/dns.go`:
- Around line 561-569: When getLBHostedZoneAndIPAddressType(target) fails and
you fall back to using the annotation value for targetHostedZoneID, also persist
the load‑balancer IP address type into targetIPAddressType so delete logic can
remove both A and AAAA records for dual‑stack NLBs; specifically, in the same
fallback block where you read
record.Annotations[targetHostedZoneIdAnnotationKey], also read
record.Annotations[targetIPAddressTypeAnnotationKey] (or set a sensible default
if absent) and assign it to targetIPAddressType, and apply the same change to
the other similar fallback blocks around the delete path (the other places
handling getLBHostedZoneAndIPAddressType failures) so the IP address type is
always available.
- Around line 641-675: The delete logic currently batches A and AAAA changes
(the changes slice and input.ChangeBatch) which is atomic and can fail if one
record type doesn't exist; update the code path that sets action ==
string(deleteAction) so deletes are performed per-record-type instead of in one
batch: build separate ChangeBatch inputs for route53.RRTypeA and
route53.RRTypeAaaa (using the same aliasTarget and zoneID/domain) and call
m.route53.ChangeResourceRecordSets for each type individually, and keep the
existing per-call error handling that checks awserr.Error and "not found" so a
missing AAAA won't block A deletion; reference symbols: changes,
input.ChangeBatch, m.route53.ChangeResourceRecordSets, deleteAction, action,
route53.RRTypeA, route53.RRTypeAaaa, targetIPAddressType.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8c6a6ca5-0156-4bd3-a221-6b12bb81f180

📥 Commits

Reviewing files that changed from the base of the PR and between c6cdc47 and 732df99.

📒 Files selected for processing (1)
  • pkg/dns/aws/dns.go

@alebedev87
Copy link
Contributor Author

CodeRabbit's outside of the diff comment:

Persist the LB IP address type in the fallback metadata.

When the lookup on Line 561 fails, the fallback restores only targetHostedZoneID. targetIPAddressType stays empty, so the delete path on Line 600 can only remove the A alias and will leave the AAAA alias behind for a dual-stack NLB.

Fair point, I'm going to address it.

@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 17, 2026

@alebedev87: This pull request references NE-2421 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Configure the publishing load balancer service's ipFamilies and ipFamilyPolicy based on the Infrastructure CR's platform ipFamily field. For NLB-type load balancers on dual-stack clusters, the service is configured with RequireDualStack policy and the appropriate IP family ordering. CLB does not support dual-stack and falls back to IPv4-only.

Enhancement proposal: openshift/enhancements#1940.
Related cloud-provider-aws change: kubernetes/cloud-provider-aws#1313.

Manual test

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: 10.128.0.0/14
   hostPrefix: 23
 - cidr: fd01::/48
   hostPrefix: 64
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - 172.30.0.0/16
 - fd02::/112
$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv4Primary
 lbType: NLB

Note that the load balancer type must be NLB for dual stack IP family, this is a requirement.

Infrastructure CR:

$ oc get infrastructure cluster -o yaml | yq .status.platformStatus
aws:
 cloudLoadBalancerConfig:
   dnsType: PlatformDefault
 ipFamily: DualStackIPv4Primary
 region: us-east-2
type: AWS

IngressController CR:

$ oc -n openshift-ingress-operator get ingresscontroller default -o yaml| yq .spec.endpointPublishingStrategy
loadBalancer:
 dnsManagementPolicy: Managed
 providerParameters:
   aws:
     type: Classic
   type: AWS
 scope: External
type: LoadBalancerService

Publishing load balancer service:

$ oc -n openshift-ingress get service router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.241.69
clusterIPs:
 - 172.30.241.69
 - fd02::133d
externalTrafficPolicy: Local
healthCheckNodePort: 30592
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
 - IPv6
ipFamilyPolicy: RequireDualStack

DNSRecord CR:

TBC

DNS query:

TBC

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 17, 2026

@alebedev87: This pull request references NE-2421 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Configure the publishing load balancer service's ipFamilies and ipFamilyPolicy based on the Infrastructure CR's platform ipFamily field. For NLB-type load balancers on dual-stack clusters, the service is configured with RequireDualStack policy and the appropriate IP family ordering. CLB does not support dual-stack and falls back to IPv4-only.

Enhancement proposal: openshift/enhancements#1940.
Related cloud-provider-aws change: kubernetes/cloud-provider-aws#1313.

Manual test

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: 10.128.0.0/14
   hostPrefix: 23
 - cidr: fd01::/48
   hostPrefix: 64
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - 172.30.0.0/16
 - fd02::/112
$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv4Primary
 lbType: NLB

Note that the load balancer type must be NLB for dual stack IP family, this is a requirement.

Infrastructure CR:

$ oc get infrastructure cluster -o yaml | yq .status.platformStatus
aws:
 cloudLoadBalancerConfig:
   dnsType: PlatformDefault
 ipFamily: DualStackIPv4Primary
 region: us-east-2
type: AWS

IngressController CR:

$ oc -n openshift-ingress-operator get ingresscontroller default -o yaml| yq .spec.endpointPublishingStrategy
loadBalancer:
 dnsManagementPolicy: Managed
 providerParameters:
   aws:
     type: Classic
   type: AWS
 scope: External
type: LoadBalancerService

Publishing load balancer service:

$ oc -n openshift-ingress get service router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.241.69
clusterIPs:
 - 172.30.241.69
 - fd02::133d
externalTrafficPolicy: Local
healthCheckNodePort: 30592
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
 - IPv6
ipFamilyPolicy: RequireDualStack

DNSRecord CR:

TBC

DNS query:

TBC

Changing the LB type to Classic:

$ oc get co ingress
NAME      VERSION                                                AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
ingress   4.22.0-0-2026-03-13-170918-test-ci-ln-wgyfqy2-latest   True        True          False      73m     ingresscontroller "default" is progressing: IngressControllerProgressing: One or more status conditions indicate progressing: LoadBalancerProgressing=True (OperandsProgressing: One or more managed resources are progressing: [The IngressController loadBalancer.providerParameters.aws.type was changed from "NLB" to "Classic".  To effectuate this change, you must delete the service: `oc -n openshift-ingress delete svc/router-default`; the service load-balancer will then be deprovisioned and a new one created. This will most likely cause the new load-balancer to have a different host name and IP address and cause disruption. To return to the previous state, you can revert the change to the IngressController: `oc -n openshift-ingress-operator patch ingresscontrollers/default --type=merge --patch='{"spec":{"endpointPublishingStrategy":{"type":"LoadBalancerService","loadBalancer":{"providerParameters":{"type":"AWS","aws":{"type":"NLB"}}}}}}'`. Direct updates to the service annotations are not supported., Classic Load Balancers do not support dual-stack. The IngressController "default" will use IPv4-only despite that the cluster is configured as "DualStackIPv4Primary". Use an NLB type to support dual-stack networking.]).

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 17, 2026

@alebedev87: This pull request references NE-2421 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Configure the publishing load balancer service's ipFamilies and ipFamilyPolicy based on the Infrastructure CR's platform ipFamily field. For NLB-type load balancers on dual-stack clusters, the service is configured with RequireDualStack policy and the appropriate IP family ordering. CLB does not support dual-stack and falls back to IPv4-only.

Enhancement proposal: openshift/enhancements#1940.
Related cloud-provider-aws change: kubernetes/cloud-provider-aws#1313.

Manual test

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: 10.128.0.0/14
   hostPrefix: 23
 - cidr: fd01::/48
   hostPrefix: 64
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - 172.30.0.0/16
 - fd02::/112
$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv4Primary
 lbType: NLB

Note that the load balancer type must be NLB for dual stack IP family, this is a requirement.

Infrastructure CR:

$ oc get infrastructure cluster -o yaml | yq .status.platformStatus
aws:
 cloudLoadBalancerConfig:
   dnsType: PlatformDefault
 ipFamily: DualStackIPv4Primary
 region: us-east-2
type: AWS

IngressController CR:

$ oc -n openshift-ingress-operator get ingresscontroller default -o yaml| yq .spec.endpointPublishingStrategy
loadBalancer:
 dnsManagementPolicy: Managed
 providerParameters:
   aws:
     type: Classic
   type: AWS
 scope: External
type: LoadBalancerService

Publishing load balancer service:

$ oc -n openshift-ingress get service router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.241.69
clusterIPs:
 - 172.30.241.69
 - fd02::133d
externalTrafficPolicy: Local
healthCheckNodePort: 30592
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
 - IPv6
ipFamilyPolicy: RequireDualStack

$ oc -n openshift-ingress get svc router-default -o yaml | yq .status
loadBalancer:
 ingress:
   - hostname: a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

DNSRecord CR:

$ oc -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .spec
dnsManagementPolicy: Managed
dnsName: '*.apps.alebedev-0317.devcluster.openshift.com.'
recordTTL: 30
recordType: CNAME
targets:
 - a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

$ oc  -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .status.zones
- conditions:
   - lastTransitionTime: "2026-03-17T10:34:39Z"
     message: The DNS provider succeeded in ensuring the record
     reason: ProviderSuccess
     status: "True"
     type: Published
 dnsZone:
   id: Z3URY6TWQ91KVV

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.apps.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

DNS query:

$ dig +short -t AAAA a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7
2600:1f16:c02:8006:b886:bd46:968d:7e64

$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
2600:1f16:c02:8006:b886:bd46:968d:7e64
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7

Changing the LB type to Classic:

$ oc get co ingress
NAME      VERSION                                                AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
ingress   4.22.0-0-2026-03-13-170918-test-ci-ln-wgyfqy2-latest   True        True          False      73m     ingresscontroller "default" is progressing: IngressControllerProgressing: One or more status conditions indicate progressing: LoadBalancerProgressing=True (OperandsProgressing: One or more managed resources are progressing: [The IngressController loadBalancer.providerParameters.aws.type was changed from "NLB" to "Classic".  To effectuate this change, you must delete the service: `oc -n openshift-ingress delete svc/router-default`; the service load-balancer will then be deprovisioned and a new one created. This will most likely cause the new load-balancer to have a different host name and IP address and cause disruption. To return to the previous state, you can revert the change to the IngressController: `oc -n openshift-ingress-operator patch ingresscontrollers/default --type=merge --patch='{"spec":{"endpointPublishingStrategy":{"type":"LoadBalancerService","loadBalancer":{"providerParameters":{"type":"AWS","aws":{"type":"NLB"}}}}}}'`. Direct updates to the service annotations are not supported., Classic Load Balancers do not support dual-stack. The IngressController "default" will use IPv4-only despite that the cluster is configured as "DualStackIPv4Primary". Use an NLB type to support dual-stack networking.]).

$ oc -n openshift-ingress delete svc/router-default
service "router-default" deleted

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.132.69
clusterIPs:
 - 172.30.132.69
externalTrafficPolicy: Local
healthCheckNodePort: 30575
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
ipFamilyPolicy: SingleStack

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@alebedev87 alebedev87 force-pushed the NE-2421-support-dualstack-ingresscontroller branch 4 times, most recently from 0d6ccb5 to 8bcbd2b Compare March 17, 2026 14:04
@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 17, 2026

@alebedev87: This pull request references NE-2421 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Configure the publishing load balancer service's ipFamilies and ipFamilyPolicy based on the Infrastructure CR's platform ipFamily field. For NLB-type load balancers on dual-stack clusters, the service is configured with RequireDualStack policy and the appropriate IP family ordering. CLB does not support dual-stack and falls back to IPv4-only.

Enhancement proposal: openshift/enhancements#1940.
Related cloud-provider-aws change: kubernetes/cloud-provider-aws#1313.

Manual test

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: 10.128.0.0/14
   hostPrefix: 23
 - cidr: fd01::/48
   hostPrefix: 64
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - 172.30.0.0/16
 - fd02::/112
$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv4Primary
 lbType: NLB

Note that the load balancer type must be NLB for dual stack IP family, this is a requirement.

Infrastructure CR:

$ oc get infrastructure cluster -o yaml | yq .status.platformStatus
aws:
 cloudLoadBalancerConfig:
   dnsType: PlatformDefault
 ipFamily: DualStackIPv4Primary
 region: us-east-2
type: AWS

IngressController CR:

$ oc -n openshift-ingress-operator get ingresscontroller default -o yaml| yq .spec.endpointPublishingStrategy
loadBalancer:
 dnsManagementPolicy: Managed
 providerParameters:
   aws:
     type: Classic
   type: AWS
 scope: External
type: LoadBalancerService

Publishing load balancer service:

$ oc -n openshift-ingress get service router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.241.69
clusterIPs:
 - 172.30.241.69
 - fd02::133d
externalTrafficPolicy: Local
healthCheckNodePort: 30592
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
 - IPv6
ipFamilyPolicy: RequireDualStack

$ oc -n openshift-ingress get svc router-default -o yaml | yq .status
loadBalancer:
 ingress:
   - hostname: a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

DNSRecord CR:

$ oc -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .spec
dnsManagementPolicy: Managed
dnsName: '*.apps.alebedev-0317.devcluster.openshift.com.'
recordTTL: 30
recordType: CNAME
targets:
 - a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

$ oc  -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .status.zones
- conditions:
   - lastTransitionTime: "2026-03-17T10:34:39Z"
     message: The DNS provider succeeded in ensuring the record
     reason: ProviderSuccess
     status: "True"
     type: Published
 dnsZone:
   id: Z3URY6TWQ91KVV

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.apps.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

DNS query:

$ dig +short -t AAAA a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7
2600:1f16:c02:8006:b886:bd46:968d:7e64

$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
2600:1f16:c02:8006:b886:bd46:968d:7e64
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7

e2e connectivity (any IPv6 ingress rule is still missing in CCM implementation though, TBC):

$ oc debug node/i-01ee44c772dd27883.us-east-2.compute.internal
Starting pod/i-01ee44c772dd27883us-east-2computeinternal-debug-vnlzq ...
To use host binaries, run `chroot /host`
Pod IP: 10.0.85.93
If you don't see a command prompt, try pressing enter.
sh-5.1# chroot /host
sh-5.1# curl -6 -kI --connect-timeout 5 https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com/
HTTP/1.1 200 OK

Changing the LB type to Classic:

$ oc get co ingress
NAME      VERSION                                                AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
ingress   4.22.0-0-2026-03-13-170918-test-ci-ln-wgyfqy2-latest   True        True          False      73m     ingresscontroller "default" is progressing: IngressControllerProgressing: One or more status conditions indicate progressing: LoadBalancerProgressing=True (OperandsProgressing: One or more managed resources are progressing: [The IngressController loadBalancer.providerParameters.aws.type was changed from "NLB" to "Classic".  To effectuate this change, you must delete the service: `oc -n openshift-ingress delete svc/router-default`; the service load-balancer will then be deprovisioned and a new one created. This will most likely cause the new load-balancer to have a different host name and IP address and cause disruption. To return to the previous state, you can revert the change to the IngressController: `oc -n openshift-ingress-operator patch ingresscontrollers/default --type=merge --patch='{"spec":{"endpointPublishingStrategy":{"type":"LoadBalancerService","loadBalancer":{"providerParameters":{"type":"AWS","aws":{"type":"NLB"}}}}}}'`. Direct updates to the service annotations are not supported., Classic Load Balancers do not support dual-stack. The IngressController "default" will use IPv4-only despite that the cluster is configured as "DualStackIPv4Primary". Use an NLB type to support dual-stack networking.]).

$ oc -n openshift-ingress delete svc/router-default
service "router-default" deleted

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.132.69
clusterIPs:
 - 172.30.132.69
externalTrafficPolicy: Local
healthCheckNodePort: 30575
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
ipFamilyPolicy: SingleStack

DNS query after change to CLB:

TBC

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 17, 2026

@alebedev87: This pull request references NE-2421 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Configure the publishing load balancer service's ipFamilies and ipFamilyPolicy based on the Infrastructure CR's platform ipFamily field. For NLB-type load balancers on dual-stack clusters, the service is configured with RequireDualStack policy and the appropriate IP family ordering. CLB does not support dual-stack and falls back to IPv4-only.

Enhancement proposal: openshift/enhancements#1940.
Related cloud-provider-aws change: kubernetes/cloud-provider-aws#1313.

Manual test

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: 10.128.0.0/14
   hostPrefix: 23
 - cidr: fd01::/48
   hostPrefix: 64
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - 172.30.0.0/16
 - fd02::/112
$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv4Primary
 lbType: NLB

Note that the load balancer type must be NLB for dual stack IP family, this is a requirement.

Infrastructure CR:

$ oc get infrastructure cluster -o yaml | yq .status.platformStatus
aws:
 cloudLoadBalancerConfig:
   dnsType: PlatformDefault
 ipFamily: DualStackIPv4Primary
 region: us-east-2
type: AWS

IngressController CR:

$ oc -n openshift-ingress-operator get ingresscontroller default -o yaml| yq .spec.endpointPublishingStrategy
loadBalancer:
 dnsManagementPolicy: Managed
 providerParameters:
   aws:
     type: Classic
   type: AWS
 scope: External
type: LoadBalancerService

Publishing load balancer service:

$ oc -n openshift-ingress get service router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.241.69
clusterIPs:
 - 172.30.241.69
 - fd02::133d
externalTrafficPolicy: Local
healthCheckNodePort: 30592
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
 - IPv6
ipFamilyPolicy: RequireDualStack

$ oc -n openshift-ingress get svc router-default -o yaml | yq .status
loadBalancer:
 ingress:
   - hostname: a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

DNSRecord CR:

$ oc -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .spec
dnsManagementPolicy: Managed
dnsName: '*.apps.alebedev-0317.devcluster.openshift.com.'
recordTTL: 30
recordType: CNAME
targets:
 - a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

$ oc  -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .status.zones
- conditions:
   - lastTransitionTime: "2026-03-17T10:34:39Z"
     message: The DNS provider succeeded in ensuring the record
     reason: ProviderSuccess
     status: "True"
     type: Published
 dnsZone:
   id: Z3URY6TWQ91KVV

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.apps.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

DNS query:

$ dig +short -t AAAA a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7
2600:1f16:c02:8006:b886:bd46:968d:7e64

$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
2600:1f16:c02:8006:b886:bd46:968d:7e64
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7

e2e connectivity (any IPv6 ingress rule is still missing in CCM implementation though, TBC):

$ oc debug node/i-01ee44c772dd27883.us-east-2.compute.internal
Starting pod/i-01ee44c772dd27883us-east-2computeinternal-debug-vnlzq ...
To use host binaries, run `chroot /host`
Pod IP: 10.0.85.93
If you don't see a command prompt, try pressing enter.
sh-5.1# chroot /host
sh-5.1# curl -6 -kI --connect-timeout 5 https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com/
HTTP/1.1 200 OK

Changing the LB type to Classic:

$ oc get co ingress
NAME      VERSION                                                AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
ingress   4.22.0-0-2026-03-13-170918-test-ci-ln-wgyfqy2-latest   True        True          False      73m     ingresscontroller "default" is progressing: IngressControllerProgressing: One or more status conditions indicate progressing: LoadBalancerProgressing=True (OperandsProgressing: One or more managed resources are progressing: [The IngressController loadBalancer.providerParameters.aws.type was changed from "NLB" to "Classic".  To effectuate this change, you must delete the service: `oc -n openshift-ingress delete svc/router-default`; the service load-balancer will then be deprovisioned and a new one created. This will most likely cause the new load-balancer to have a different host name and IP address and cause disruption. To return to the previous state, you can revert the change to the IngressController: `oc -n openshift-ingress-operator patch ingresscontrollers/default --type=merge --patch='{"spec":{"endpointPublishingStrategy":{"type":"LoadBalancerService","loadBalancer":{"providerParameters":{"type":"AWS","aws":{"type":"NLB"}}}}}}'`. Direct updates to the service annotations are not supported. Classic Load Balancers do not support dual-stack. The IngressController "default" will use IPv4-only despite that the cluster is configured as "DualStackIPv4Primary". Use an NLB type to support dual-stack networking.]).

$ oc -n openshift-ingress delete svc/router-default
service "router-default" deleted

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.132.69
clusterIPs:
 - 172.30.132.69
externalTrafficPolicy: Local
healthCheckNodePort: 30575
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
ipFamilyPolicy: SingleStack

DNS query after a change to CLB:

$ dig +short -t A console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
16.58.45.52
3.21.115.53
$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Connectivity after a change to CLB:

# curl -4 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
HTTP/1.1 200 OK

# curl -6 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
curl: (6) Could not resolve host: console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Clean up of DNS (new shard to showcase the cleanup):

$ oc -n openshift-ingress-operator get ingresscontroller my -o yaml | yq .spec.domain
hosts.alebedev-0317.devcluster.openshift.com

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

$ oc -n openshift-ingress-operator delete ingresscontroller my
ingresscontroller.operator.openshift.io "my" deleted

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[]

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 18, 2026

@alebedev87: This pull request references NE-2421 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Configure the publishing load balancer service's ipFamilies and ipFamilyPolicy based on the Infrastructure CR's platform ipFamily field. For NLB-type load balancers on dual-stack clusters, the service is configured with RequireDualStack policy and the appropriate IP family ordering. CLB does not support dual-stack and falls back to IPv4-only.

Enhancement proposal: openshift/enhancements#1940.
Related cloud-provider-aws change: kubernetes/cloud-provider-aws#1313.

Manual test

DualStackIPv4Primary

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: 10.128.0.0/14
   hostPrefix: 23
 - cidr: fd01::/48
   hostPrefix: 64
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - 172.30.0.0/16
 - fd02::/112
$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv4Primary
 lbType: NLB

Note that the load balancer type must be NLB for dual stack IP family, this is a requirement.

Infrastructure CR:

$ oc get infrastructure cluster -o yaml | yq .status.platformStatus
aws:
 cloudLoadBalancerConfig:
   dnsType: PlatformDefault
 ipFamily: DualStackIPv4Primary
 region: us-east-2
type: AWS

IngressController CR:

$ oc -n openshift-ingress-operator get ingresscontroller default -o yaml| yq .spec.endpointPublishingStrategy
loadBalancer:
 dnsManagementPolicy: Managed
 providerParameters:
   aws:
     type: Classic
   type: AWS
 scope: External
type: LoadBalancerService

Publishing load balancer service:

$ oc -n openshift-ingress get service router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.241.69
clusterIPs:
 - 172.30.241.69
 - fd02::133d
externalTrafficPolicy: Local
healthCheckNodePort: 30592
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
 - IPv6
ipFamilyPolicy: RequireDualStack

$ oc -n openshift-ingress get svc router-default -o yaml | yq .status
loadBalancer:
 ingress:
   - hostname: a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

DNSRecord CR:

$ oc -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .spec
dnsManagementPolicy: Managed
dnsName: '*.apps.alebedev-0317.devcluster.openshift.com.'
recordTTL: 30
recordType: CNAME
targets:
 - a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

$ oc  -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .status.zones
- conditions:
   - lastTransitionTime: "2026-03-17T10:34:39Z"
     message: The DNS provider succeeded in ensuring the record
     reason: ProviderSuccess
     status: "True"
     type: Published
 dnsZone:
   id: Z3URY6TWQ91KVV

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.apps.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

DNS query:

$ dig +short -t AAAA a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7
2600:1f16:c02:8006:b886:bd46:968d:7e64

$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
2600:1f16:c02:8006:b886:bd46:968d:7e64
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7

e2e connectivity (any IPv6 ingress rule is still missing in CCM implementation though, TBC):

$ oc debug node/i-01ee44c772dd27883.us-east-2.compute.internal
Starting pod/i-01ee44c772dd27883us-east-2computeinternal-debug-vnlzq ...
To use host binaries, run `chroot /host`
Pod IP: 10.0.85.93
If you don't see a command prompt, try pressing enter.
sh-5.1# chroot /host
sh-5.1# curl -6 -kI --connect-timeout 5 https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com/
HTTP/1.1 200 OK

Changing the LB type to Classic:

$ oc get co ingress
NAME      VERSION                                                AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
ingress   4.22.0-0-2026-03-13-170918-test-ci-ln-wgyfqy2-latest   True        True          False      73m     ingresscontroller "default" is progressing: IngressControllerProgressing: One or more status conditions indicate progressing: LoadBalancerProgressing=True (OperandsProgressing: One or more managed resources are progressing: [The IngressController loadBalancer.providerParameters.aws.type was changed from "NLB" to "Classic".  To effectuate this change, you must delete the service: `oc -n openshift-ingress delete svc/router-default`; the service load-balancer will then be deprovisioned and a new one created. This will most likely cause the new load-balancer to have a different host name and IP address and cause disruption. To return to the previous state, you can revert the change to the IngressController: `oc -n openshift-ingress-operator patch ingresscontrollers/default --type=merge --patch='{"spec":{"endpointPublishingStrategy":{"type":"LoadBalancerService","loadBalancer":{"providerParameters":{"type":"AWS","aws":{"type":"NLB"}}}}}}'`. Direct updates to the service annotations are not supported. Classic Load Balancers do not support dual-stack. The IngressController "default" will use IPv4-only despite that the cluster is configured as "DualStackIPv4Primary". Use an NLB type to support dual-stack networking.]).

$ oc -n openshift-ingress delete svc/router-default
service "router-default" deleted

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.132.69
clusterIPs:
 - 172.30.132.69
externalTrafficPolicy: Local
healthCheckNodePort: 30575
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
ipFamilyPolicy: SingleStack

DNS query after a change to CLB:

$ dig +short -t A console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
16.58.45.52
3.21.115.53
$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Connectivity after a change to CLB:

# curl -4 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
HTTP/1.1 200 OK

# curl -6 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
curl: (6) Could not resolve host: console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Clean up of DNS (new shard to showcase the cleanup):

$ oc -n openshift-ingress-operator get ingresscontroller my -o yaml | yq .spec.domain
hosts.alebedev-0317.devcluster.openshift.com

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

$ oc -n openshift-ingress-operator delete ingresscontroller my
ingresscontroller.operator.openshift.io "my" deleted

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[]

DualStackIPv6Primary

TBC

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 18, 2026

@alebedev87: This pull request references NE-2421 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Configure the publishing load balancer service's ipFamilies and ipFamilyPolicy based on the Infrastructure CR's platform ipFamily field. For NLB-type load balancers on dual-stack clusters, the service is configured with RequireDualStack policy and the appropriate IP family ordering. CLB does not support dual-stack and falls back to IPv4-only.

Enhancement proposal: openshift/enhancements#1940.
Related cloud-provider-aws change: kubernetes/cloud-provider-aws#1313.

Manual test

The test can only be manual for the moment while CCM changes are still in progress.

DualStackIPv4Primary

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: 10.128.0.0/14
   hostPrefix: 23
 - cidr: fd01::/48
   hostPrefix: 64
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - 172.30.0.0/16
 - fd02::/112
$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv4Primary
 lbType: NLB

Note that the load balancer type must be NLB for dual stack IP family, this is a requirement.

Infrastructure CR:

$ oc get infrastructure cluster -o yaml | yq .status.platformStatus
aws:
 cloudLoadBalancerConfig:
   dnsType: PlatformDefault
 ipFamily: DualStackIPv4Primary
 region: us-east-2
type: AWS

IngressController CR:

$ oc -n openshift-ingress-operator get ingresscontroller default -o yaml| yq .spec.endpointPublishingStrategy
loadBalancer:
 dnsManagementPolicy: Managed
 providerParameters:
   aws:
     type: Classic
   type: AWS
 scope: External
type: LoadBalancerService

Publishing load balancer service:

$ oc -n openshift-ingress get service router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.241.69
clusterIPs:
 - 172.30.241.69
 - fd02::133d
externalTrafficPolicy: Local
healthCheckNodePort: 30592
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
 - IPv6
ipFamilyPolicy: RequireDualStack

$ oc -n openshift-ingress get svc router-default -o yaml | yq .status
loadBalancer:
 ingress:
   - hostname: a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

DNSRecord CR:

$ oc -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .spec
dnsManagementPolicy: Managed
dnsName: '*.apps.alebedev-0317.devcluster.openshift.com.'
recordTTL: 30
recordType: CNAME
targets:
 - a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

$ oc  -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .status.zones
- conditions:
   - lastTransitionTime: "2026-03-17T10:34:39Z"
     message: The DNS provider succeeded in ensuring the record
     reason: ProviderSuccess
     status: "True"
     type: Published
 dnsZone:
   id: Z3URY6TWQ91KVV

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.apps.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

DNS query:

$ dig +short -t AAAA a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7
2600:1f16:c02:8006:b886:bd46:968d:7e64

$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
2600:1f16:c02:8006:b886:bd46:968d:7e64
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7

e2e connectivity (any IPv6 ingress rule is still missing in CCM implementation though, TBC):

$ oc debug node/i-01ee44c772dd27883.us-east-2.compute.internal
Starting pod/i-01ee44c772dd27883us-east-2computeinternal-debug-vnlzq ...
To use host binaries, run `chroot /host`
Pod IP: 10.0.85.93
If you don't see a command prompt, try pressing enter.
sh-5.1# chroot /host
sh-5.1# curl -6 -kI --connect-timeout 5 https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com/
HTTP/1.1 200 OK

Changing the LB type to Classic:

$ oc get co ingress
NAME      VERSION                                                AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
ingress   4.22.0-0-2026-03-13-170918-test-ci-ln-wgyfqy2-latest   True        True          False      73m     ingresscontroller "default" is progressing: IngressControllerProgressing: One or more status conditions indicate progressing: LoadBalancerProgressing=True (OperandsProgressing: One or more managed resources are progressing: [The IngressController loadBalancer.providerParameters.aws.type was changed from "NLB" to "Classic".  To effectuate this change, you must delete the service: `oc -n openshift-ingress delete svc/router-default`; the service load-balancer will then be deprovisioned and a new one created. This will most likely cause the new load-balancer to have a different host name and IP address and cause disruption. To return to the previous state, you can revert the change to the IngressController: `oc -n openshift-ingress-operator patch ingresscontrollers/default --type=merge --patch='{"spec":{"endpointPublishingStrategy":{"type":"LoadBalancerService","loadBalancer":{"providerParameters":{"type":"AWS","aws":{"type":"NLB"}}}}}}'`. Direct updates to the service annotations are not supported. Classic Load Balancers do not support dual-stack. The IngressController "default" will use IPv4-only despite that the cluster is configured as "DualStackIPv4Primary". Use an NLB type to support dual-stack networking.]).

$ oc -n openshift-ingress delete svc/router-default
service "router-default" deleted

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.132.69
clusterIPs:
 - 172.30.132.69
externalTrafficPolicy: Local
healthCheckNodePort: 30575
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
ipFamilyPolicy: SingleStack

DNS query after a change to CLB:

$ dig +short -t A console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
16.58.45.52
3.21.115.53
$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Connectivity after a change to CLB:

# curl -4 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
HTTP/1.1 200 OK

# curl -6 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
curl: (6) Could not resolve host: console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Clean up of DNS (new shard to showcase the cleanup):

$ oc -n openshift-ingress-operator get ingresscontroller my -o yaml | yq .spec.domain
hosts.alebedev-0317.devcluster.openshift.com

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

$ oc -n openshift-ingress-operator delete ingresscontroller my
ingresscontroller.operator.openshift.io "my" deleted

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[]

DualStackIPv6Primary

TBC

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 18, 2026

@alebedev87: This pull request references NE-2421 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Configure the publishing load balancer service's ipFamilies and ipFamilyPolicy based on the Infrastructure CR's platform ipFamily field. For NLB-type load balancers on dual-stack clusters, the service is configured with RequireDualStack policy and the appropriate IP family ordering. CLB does not support dual-stack and falls back to IPv4-only.

Enhancement proposal: openshift/enhancements#1940.
Related cloud-provider-aws change: kubernetes/cloud-provider-aws#1313.

Manual test

The test can only be manual for the moment while CCM changes are still in progress.

DualStackIPv4Primary

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: 10.128.0.0/14
   hostPrefix: 23
 - cidr: fd01::/48
   hostPrefix: 64
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - 172.30.0.0/16
 - fd02::/112
$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv4Primary
 lbType: NLB

Note that the load balancer type must be NLB for dual stack IP family, this is a requirement.

Infrastructure CR:

$ oc get infrastructure cluster -o yaml | yq .status.platformStatus
aws:
 cloudLoadBalancerConfig:
   dnsType: PlatformDefault
 ipFamily: DualStackIPv4Primary
 region: us-east-2
type: AWS

IngressController CR:

$ oc -n openshift-ingress-operator get ingresscontroller default -o yaml| yq .spec.endpointPublishingStrategy
loadBalancer:
 dnsManagementPolicy: Managed
 providerParameters:
   aws:
     type: Classic
   type: AWS
 scope: External
type: LoadBalancerService

Publishing load balancer service:

$ oc -n openshift-ingress get service router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.241.69
clusterIPs:
 - 172.30.241.69
 - fd02::133d
externalTrafficPolicy: Local
healthCheckNodePort: 30592
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
 - IPv6
ipFamilyPolicy: RequireDualStack

$ oc -n openshift-ingress get svc router-default -o yaml | yq .status
loadBalancer:
 ingress:
   - hostname: a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

DNSRecord CR:

$ oc -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .spec
dnsManagementPolicy: Managed
dnsName: '*.apps.alebedev-0317.devcluster.openshift.com.'
recordTTL: 30
recordType: CNAME
targets:
 - a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

$ oc  -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .status.zones
- conditions:
   - lastTransitionTime: "2026-03-17T10:34:39Z"
     message: The DNS provider succeeded in ensuring the record
     reason: ProviderSuccess
     status: "True"
     type: Published
 dnsZone:
   id: Z3URY6TWQ91KVV

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.apps.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

DNS query:

$ dig +short -t AAAA a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7
2600:1f16:c02:8006:b886:bd46:968d:7e64

$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
2600:1f16:c02:8006:b886:bd46:968d:7e64
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7

e2e connectivity (any IPv6 ingress rule is still missing in CCM implementation though, TBC):

$ oc debug node/i-01ee44c772dd27883.us-east-2.compute.internal
Starting pod/i-01ee44c772dd27883us-east-2computeinternal-debug-vnlzq ...
To use host binaries, run `chroot /host`
Pod IP: 10.0.85.93
If you don't see a command prompt, try pressing enter.
sh-5.1# chroot /host
sh-5.1# curl -6 -kI --connect-timeout 5 https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com/
HTTP/1.1 200 OK

Changing the LB type to Classic:

$ oc get co ingress
NAME      VERSION                                                AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
ingress   4.22.0-0-2026-03-13-170918-test-ci-ln-wgyfqy2-latest   True        True          False      73m     ingresscontroller "default" is progressing: IngressControllerProgressing: One or more status conditions indicate progressing: LoadBalancerProgressing=True (OperandsProgressing: One or more managed resources are progressing: [The IngressController loadBalancer.providerParameters.aws.type was changed from "NLB" to "Classic".  To effectuate this change, you must delete the service: `oc -n openshift-ingress delete svc/router-default`; the service load-balancer will then be deprovisioned and a new one created. This will most likely cause the new load-balancer to have a different host name and IP address and cause disruption. To return to the previous state, you can revert the change to the IngressController: `oc -n openshift-ingress-operator patch ingresscontrollers/default --type=merge --patch='{"spec":{"endpointPublishingStrategy":{"type":"LoadBalancerService","loadBalancer":{"providerParameters":{"type":"AWS","aws":{"type":"NLB"}}}}}}'`. Direct updates to the service annotations are not supported. Classic Load Balancers do not support dual-stack. The IngressController "default" will use IPv4-only despite that the cluster is configured as "DualStackIPv4Primary". Use an NLB type to support dual-stack networking.]).

$ oc -n openshift-ingress delete svc/router-default
service "router-default" deleted

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.132.69
clusterIPs:
 - 172.30.132.69
externalTrafficPolicy: Local
healthCheckNodePort: 30575
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
ipFamilyPolicy: SingleStack

DNS query after a change to CLB:

$ dig +short -t A console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
16.58.45.52
3.21.115.53
$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Connectivity after a change to CLB:

# curl -4 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
HTTP/1.1 200 OK

# curl -6 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
curl: (6) Could not resolve host: console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Clean up of DNS (new shard to showcase the cleanup):

$ oc -n openshift-ingress-operator get ingresscontroller my -o yaml | yq .spec.domain
hosts.alebedev-0317.devcluster.openshift.com

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

$ oc -n openshift-ingress-operator delete ingresscontroller my
ingresscontroller.operator.openshift.io "my" deleted

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[]

DualStackIPv6Primary

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: fd01::/48
   hostPrefix: 64
 - cidr: 10.128.0.0/14
   hostPrefix: 23
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - fd02::/112
 - 172.30.0.0/16

$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv6Primary
 lbType: NLB

Publishing service:

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
clusterIP: fd02::3dac
clusterIPs:
 - fd02::3dac
 - 172.30.4.110
externalTrafficPolicy: Local
healthCheckNodePort: 32125
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv6
 - IPv4
ipFamilyPolicy: RequireDualStack

DNS check:

$ dig +short -t A console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
3.132.149.10
18.225.48.44

$ dig +short -t AAAA console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
2600:1f16:1b80:2306:a3e4:ea37:6b15:9355
2600:1f16:1b80:2304:7f94:5708:1f66:af79

Connectivity check:

$ oc debug node/i-040c1020eeb1401ab.us-east-2.compute.internal
Starting pod/i-040c1020eeb1401abus-east-2computeinternal-debug-cv64v ...
To use host binaries, run `chroot /host`
Pod IP: 2600:1f16:1b80:2303:669e:5b4d:7a21:56
If you don't see a command prompt, try pressing enter.
sh-5.1# curl -6 -kI https://console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
HTTP/1.1 200 OK

sh-5.1# curl -4 -kI https://console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
HTTP/1.1 200 OK

CLB shard

$ oc -n openshift-ingress-operator get ingresscontroller my -o yaml | yq .spec.domain
hosts.alebedev-03182.devcluster.openshift.com


$ oc -n openshift-ingress get svc router-my -o yaml | yq .metadata.annotations
service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold: "2"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "5"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "4"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: "2"
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: '*'
traffic-policy.network.alpha.openshift.io/local-with-fallback: ""

$ oc -n openshift-ingress get svc router-my -o yaml | yq .spec
clusterIP: 172.30.28.213
clusterIPs:
 - 172.30.28.213
externalTrafficPolicy: Local
healthCheckNodePort: 30968
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
ipFamilyPolicy: SingleStack

CLB connectivity check:

$ oc debug node/i-040c1020eeb1401ab.us-east-2.compute.internal
Starting pod/i-040c1020eeb1401abus-east-2computeinternal-debug-7nhlc ...
To use host binaries, run `chroot /host`
cuPod IP: 2600:1f16:1b80:2303:669e:5b4d:7a21:56
If you don't see a command prompt, try pressing enter.
sh-5.1# curl -4 -kI https://downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com
HTTP/1.0 200 OK

sh-5.1# curl -6 -kI https://downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com
curl: (6) Could not resolve host: downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@alebedev87 alebedev87 force-pushed the NE-2421-support-dualstack-ingresscontroller branch from 9af1a8c to 53c3acd Compare March 18, 2026 14:30
@alebedev87 alebedev87 changed the title [WIP] NE-2421: Support dual-stack IngressController on AWS NE-2421: Support dual-stack IngressController on AWS Mar 18, 2026
@openshift-ci openshift-ci bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Mar 18, 2026
@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 18, 2026

@alebedev87: This pull request references NE-2421 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Configure the publishing load balancer service's ipFamilies and ipFamilyPolicy based on the Infrastructure CR's platform ipFamily field. For NLB-type load balancers on dual-stack clusters, the service is configured with RequireDualStack policy and the appropriate IP family ordering. CLB does not support dual-stack and falls back to IPv4-only.

Enhancement proposal: openshift/enhancements#1940.
Related upstream cloud-provider-aws change: kubernetes/cloud-provider-aws#1313.

Manual test

The test can only be manual for the moment while CCM changes are still in progress.

DualStackIPv4Primary

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: 10.128.0.0/14
   hostPrefix: 23
 - cidr: fd01::/48
   hostPrefix: 64
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - 172.30.0.0/16
 - fd02::/112
$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv4Primary
 lbType: NLB

Note that the load balancer type must be NLB for dual stack IP family, this is a requirement.

Infrastructure CR:

$ oc get infrastructure cluster -o yaml | yq .status.platformStatus
aws:
 cloudLoadBalancerConfig:
   dnsType: PlatformDefault
 ipFamily: DualStackIPv4Primary
 region: us-east-2
type: AWS

IngressController CR:

$ oc -n openshift-ingress-operator get ingresscontroller default -o yaml| yq .spec.endpointPublishingStrategy
loadBalancer:
 dnsManagementPolicy: Managed
 providerParameters:
   aws:
     type: Classic
   type: AWS
 scope: External
type: LoadBalancerService

Publishing load balancer service:

$ oc -n openshift-ingress get service router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.241.69
clusterIPs:
 - 172.30.241.69
 - fd02::133d
externalTrafficPolicy: Local
healthCheckNodePort: 30592
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
 - IPv6
ipFamilyPolicy: RequireDualStack

$ oc -n openshift-ingress get svc router-default -o yaml | yq .status
loadBalancer:
 ingress:
   - hostname: a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

DNSRecord CR:

$ oc -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .spec
dnsManagementPolicy: Managed
dnsName: '*.apps.alebedev-0317.devcluster.openshift.com.'
recordTTL: 30
recordType: CNAME
targets:
 - a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

$ oc  -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .status.zones
- conditions:
   - lastTransitionTime: "2026-03-17T10:34:39Z"
     message: The DNS provider succeeded in ensuring the record
     reason: ProviderSuccess
     status: "True"
     type: Published
 dnsZone:
   id: Z3URY6TWQ91KVV

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.apps.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

DNS query:

$ dig +short -t AAAA a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7
2600:1f16:c02:8006:b886:bd46:968d:7e64

$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
2600:1f16:c02:8006:b886:bd46:968d:7e64
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7

e2e connectivity (any IPv6 ingress rule is still missing in CCM implementation though, TBC):

$ oc debug node/i-01ee44c772dd27883.us-east-2.compute.internal
Starting pod/i-01ee44c772dd27883us-east-2computeinternal-debug-vnlzq ...
To use host binaries, run `chroot /host`
Pod IP: 10.0.85.93
If you don't see a command prompt, try pressing enter.
sh-5.1# chroot /host
sh-5.1# curl -6 -kI --connect-timeout 5 https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com/
HTTP/1.1 200 OK

Changing the LB type to Classic:

$ oc get co ingress
NAME      VERSION                                                AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
ingress   4.22.0-0-2026-03-13-170918-test-ci-ln-wgyfqy2-latest   True        True          False      73m     ingresscontroller "default" is progressing: IngressControllerProgressing: One or more status conditions indicate progressing: LoadBalancerProgressing=True (OperandsProgressing: One or more managed resources are progressing: [The IngressController loadBalancer.providerParameters.aws.type was changed from "NLB" to "Classic".  To effectuate this change, you must delete the service: `oc -n openshift-ingress delete svc/router-default`; the service load-balancer will then be deprovisioned and a new one created. This will most likely cause the new load-balancer to have a different host name and IP address and cause disruption. To return to the previous state, you can revert the change to the IngressController: `oc -n openshift-ingress-operator patch ingresscontrollers/default --type=merge --patch='{"spec":{"endpointPublishingStrategy":{"type":"LoadBalancerService","loadBalancer":{"providerParameters":{"type":"AWS","aws":{"type":"NLB"}}}}}}'`. Direct updates to the service annotations are not supported. Classic Load Balancers do not support dual-stack. The IngressController "default" will use IPv4-only despite that the cluster is configured as "DualStackIPv4Primary". Use an NLB type to support dual-stack networking.]).

$ oc -n openshift-ingress delete svc/router-default
service "router-default" deleted

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.132.69
clusterIPs:
 - 172.30.132.69
externalTrafficPolicy: Local
healthCheckNodePort: 30575
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
ipFamilyPolicy: SingleStack

DNS query after a change to CLB:

$ dig +short -t A console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
16.58.45.52
3.21.115.53
$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Connectivity after a change to CLB:

# curl -4 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
HTTP/1.1 200 OK

# curl -6 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
curl: (6) Could not resolve host: console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Clean up of DNS (new shard to showcase the cleanup):

$ oc -n openshift-ingress-operator get ingresscontroller my -o yaml | yq .spec.domain
hosts.alebedev-0317.devcluster.openshift.com

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

$ oc -n openshift-ingress-operator delete ingresscontroller my
ingresscontroller.operator.openshift.io "my" deleted

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[]

DualStackIPv6Primary

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: fd01::/48
   hostPrefix: 64
 - cidr: 10.128.0.0/14
   hostPrefix: 23
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - fd02::/112
 - 172.30.0.0/16

$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv6Primary
 lbType: NLB

Publishing service:

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
clusterIP: fd02::3dac
clusterIPs:
 - fd02::3dac
 - 172.30.4.110
externalTrafficPolicy: Local
healthCheckNodePort: 32125
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv6
 - IPv4
ipFamilyPolicy: RequireDualStack

DNS check:

$ dig +short -t A console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
3.132.149.10
18.225.48.44

$ dig +short -t AAAA console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
2600:1f16:1b80:2306:a3e4:ea37:6b15:9355
2600:1f16:1b80:2304:7f94:5708:1f66:af79

Connectivity check:

$ oc debug node/i-040c1020eeb1401ab.us-east-2.compute.internal
Starting pod/i-040c1020eeb1401abus-east-2computeinternal-debug-cv64v ...
To use host binaries, run `chroot /host`
Pod IP: 2600:1f16:1b80:2303:669e:5b4d:7a21:56
If you don't see a command prompt, try pressing enter.
sh-5.1# curl -6 -kI https://console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
HTTP/1.1 200 OK

sh-5.1# curl -4 -kI https://console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
HTTP/1.1 200 OK

CLB shard

$ oc -n openshift-ingress-operator get ingresscontroller my -o yaml | yq .spec.domain
hosts.alebedev-03182.devcluster.openshift.com


$ oc -n openshift-ingress get svc router-my -o yaml | yq .metadata.annotations
service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold: "2"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "5"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "4"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: "2"
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: '*'
traffic-policy.network.alpha.openshift.io/local-with-fallback: ""

$ oc -n openshift-ingress get svc router-my -o yaml | yq .spec
clusterIP: 172.30.28.213
clusterIPs:
 - 172.30.28.213
externalTrafficPolicy: Local
healthCheckNodePort: 30968
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
ipFamilyPolicy: SingleStack

CLB connectivity check:

$ oc debug node/i-040c1020eeb1401ab.us-east-2.compute.internal
Starting pod/i-040c1020eeb1401abus-east-2computeinternal-debug-7nhlc ...
To use host binaries, run `chroot /host`
cuPod IP: 2600:1f16:1b80:2303:669e:5b4d:7a21:56
If you don't see a command prompt, try pressing enter.
sh-5.1# curl -4 -kI https://downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com
HTTP/1.0 200 OK

sh-5.1# curl -6 -kI https://downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com
curl: (6) Could not resolve host: downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@gcs278
Copy link
Contributor

gcs278 commented Mar 18, 2026

/assign @davidesalerno
/assign @jcmoraisjr

@alebedev87 alebedev87 force-pushed the NE-2421-support-dualstack-ingresscontroller branch from 53c3acd to ae1dd7b Compare March 18, 2026 15:24
@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 18, 2026

@alebedev87: This pull request references NE-2421 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Configure the publishing load balancer service's ipFamilies and ipFamilyPolicy based on the Infrastructure CR's platform ipFamily field. For NLB-type load balancers on dual-stack clusters, the service is configured with RequireDualStack policy and the appropriate IP family ordering. CLB does not support dual-stack and falls back to IPv4-only.

Configure the publishing load balancer service for dual-stack AWS clusters based on the Infrastructure CR's ipFamily field (status.platformStatus.aws.ipFamily).

For NLB-type load balancers, set ipFamilyPolicy to RequireDualStack and ipFamilies to match the cluster's IP family ordering. For CLB, explicitly set SingleStack/IPv4 since CLB only forwards IPv4 traffic. Without this, DualStackIPv6Primary clusters would default the CLB service to SingleStack/IPv6, causing OVN to refuse IPv4 traffic on the service's NodePort.

When the cluster IP family is dual-stack, the AWS DNS provider creates both Alias A and Alias AAAA Route53 records for IngressController wildcard domains. AAAA records are always created regardless of LB type because stale AAAA records cannot be easily cleaned up when the LB type changes from NLB to CLB — the old NLB is deleted before the new CLB is created, so the DNS provider can no longer look up the target hostname to delete the AAAA record. For CLBs the AAAA alias simply won't resolve.

When the LB type is changed to CLB on a dual-stack cluster, a warning note is appended to the effectuation message on the cluster operator status indicating that CLBs do not support dual-stack.

Enhancement proposal: openshift/enhancements#1940.
Related upstream cloud-provider-aws change: kubernetes/cloud-provider-aws#1313.

Manual test

The test can only be manual for the moment while CCM changes are still in progress.

DualStackIPv4Primary

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: 10.128.0.0/14
   hostPrefix: 23
 - cidr: fd01::/48
   hostPrefix: 64
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - 172.30.0.0/16
 - fd02::/112
$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv4Primary
 lbType: NLB

Note that the load balancer type must be NLB for dual stack IP family, this is a requirement.

Infrastructure CR:

$ oc get infrastructure cluster -o yaml | yq .status.platformStatus
aws:
 cloudLoadBalancerConfig:
   dnsType: PlatformDefault
 ipFamily: DualStackIPv4Primary
 region: us-east-2
type: AWS

IngressController CR:

$ oc -n openshift-ingress-operator get ingresscontroller default -o yaml| yq .spec.endpointPublishingStrategy
loadBalancer:
 dnsManagementPolicy: Managed
 providerParameters:
   aws:
     type: Classic
   type: AWS
 scope: External
type: LoadBalancerService

Publishing load balancer service:

$ oc -n openshift-ingress get service router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.241.69
clusterIPs:
 - 172.30.241.69
 - fd02::133d
externalTrafficPolicy: Local
healthCheckNodePort: 30592
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
 - IPv6
ipFamilyPolicy: RequireDualStack

$ oc -n openshift-ingress get svc router-default -o yaml | yq .status
loadBalancer:
 ingress:
   - hostname: a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

DNSRecord CR:

$ oc -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .spec
dnsManagementPolicy: Managed
dnsName: '*.apps.alebedev-0317.devcluster.openshift.com.'
recordTTL: 30
recordType: CNAME
targets:
 - a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

$ oc  -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .status.zones
- conditions:
   - lastTransitionTime: "2026-03-17T10:34:39Z"
     message: The DNS provider succeeded in ensuring the record
     reason: ProviderSuccess
     status: "True"
     type: Published
 dnsZone:
   id: Z3URY6TWQ91KVV

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.apps.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

DNS query:

$ dig +short -t AAAA a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7
2600:1f16:c02:8006:b886:bd46:968d:7e64

$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
2600:1f16:c02:8006:b886:bd46:968d:7e64
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7

e2e connectivity (any IPv6 ingress rule is still missing in CCM implementation though, TBC):

$ oc debug node/i-01ee44c772dd27883.us-east-2.compute.internal
Starting pod/i-01ee44c772dd27883us-east-2computeinternal-debug-vnlzq ...
To use host binaries, run `chroot /host`
Pod IP: 10.0.85.93
If you don't see a command prompt, try pressing enter.
sh-5.1# chroot /host
sh-5.1# curl -6 -kI --connect-timeout 5 https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com/
HTTP/1.1 200 OK

Changing the LB type to Classic:

$ oc get co ingress
NAME      VERSION                                                AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
ingress   4.22.0-0-2026-03-13-170918-test-ci-ln-wgyfqy2-latest   True        True          False      73m     ingresscontroller "default" is progressing: IngressControllerProgressing: One or more status conditions indicate progressing: LoadBalancerProgressing=True (OperandsProgressing: One or more managed resources are progressing: [The IngressController loadBalancer.providerParameters.aws.type was changed from "NLB" to "Classic".  To effectuate this change, you must delete the service: `oc -n openshift-ingress delete svc/router-default`; the service load-balancer will then be deprovisioned and a new one created. This will most likely cause the new load-balancer to have a different host name and IP address and cause disruption. To return to the previous state, you can revert the change to the IngressController: `oc -n openshift-ingress-operator patch ingresscontrollers/default --type=merge --patch='{"spec":{"endpointPublishingStrategy":{"type":"LoadBalancerService","loadBalancer":{"providerParameters":{"type":"AWS","aws":{"type":"NLB"}}}}}}'`. Direct updates to the service annotations are not supported. Classic Load Balancers do not support dual-stack. The IngressController "default" will use IPv4-only despite that the cluster is configured as "DualStackIPv4Primary". Use an NLB type to support dual-stack networking.]).

$ oc -n openshift-ingress delete svc/router-default
service "router-default" deleted

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.132.69
clusterIPs:
 - 172.30.132.69
externalTrafficPolicy: Local
healthCheckNodePort: 30575
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
ipFamilyPolicy: SingleStack

DNS query after a change to CLB:

$ dig +short -t A console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
16.58.45.52
3.21.115.53
$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Connectivity after a change to CLB:

# curl -4 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
HTTP/1.1 200 OK

# curl -6 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
curl: (6) Could not resolve host: console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Clean up of DNS (new shard to showcase the cleanup):

$ oc -n openshift-ingress-operator get ingresscontroller my -o yaml | yq .spec.domain
hosts.alebedev-0317.devcluster.openshift.com

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

$ oc -n openshift-ingress-operator delete ingresscontroller my
ingresscontroller.operator.openshift.io "my" deleted

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[]

DualStackIPv6Primary

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: fd01::/48
   hostPrefix: 64
 - cidr: 10.128.0.0/14
   hostPrefix: 23
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - fd02::/112
 - 172.30.0.0/16

$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv6Primary
 lbType: NLB

Publishing service:

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
clusterIP: fd02::3dac
clusterIPs:
 - fd02::3dac
 - 172.30.4.110
externalTrafficPolicy: Local
healthCheckNodePort: 32125
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv6
 - IPv4
ipFamilyPolicy: RequireDualStack

DNS check:

$ dig +short -t A console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
3.132.149.10
18.225.48.44

$ dig +short -t AAAA console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
2600:1f16:1b80:2306:a3e4:ea37:6b15:9355
2600:1f16:1b80:2304:7f94:5708:1f66:af79

Connectivity check:

$ oc debug node/i-040c1020eeb1401ab.us-east-2.compute.internal
Starting pod/i-040c1020eeb1401abus-east-2computeinternal-debug-cv64v ...
To use host binaries, run `chroot /host`
Pod IP: 2600:1f16:1b80:2303:669e:5b4d:7a21:56
If you don't see a command prompt, try pressing enter.
sh-5.1# curl -6 -kI https://console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
HTTP/1.1 200 OK

sh-5.1# curl -4 -kI https://console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
HTTP/1.1 200 OK

CLB shard

$ oc -n openshift-ingress-operator get ingresscontroller my -o yaml | yq .spec.domain
hosts.alebedev-03182.devcluster.openshift.com


$ oc -n openshift-ingress get svc router-my -o yaml | yq .metadata.annotations
service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold: "2"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "5"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "4"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: "2"
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: '*'
traffic-policy.network.alpha.openshift.io/local-with-fallback: ""

$ oc -n openshift-ingress get svc router-my -o yaml | yq .spec
clusterIP: 172.30.28.213
clusterIPs:
 - 172.30.28.213
externalTrafficPolicy: Local
healthCheckNodePort: 30968
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
ipFamilyPolicy: SingleStack

CLB connectivity check:

$ oc debug node/i-040c1020eeb1401ab.us-east-2.compute.internal
Starting pod/i-040c1020eeb1401abus-east-2computeinternal-debug-7nhlc ...
To use host binaries, run `chroot /host`
cuPod IP: 2600:1f16:1b80:2303:669e:5b4d:7a21:56
If you don't see a command prompt, try pressing enter.
sh-5.1# curl -4 -kI https://downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com
HTTP/1.0 200 OK

sh-5.1# curl -6 -kI https://downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com
curl: (6) Could not resolve host: downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 18, 2026

@alebedev87: This pull request references NE-2421 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Configure the publishing load balancer service for dual-stack AWS clusters based on the Infrastructure CR's ipFamily field (status.platformStatus.aws.ipFamily).

For NLB-type load balancers, set ipFamilyPolicy to RequireDualStack and ipFamilies to match the cluster's IP family ordering. For CLB, explicitly set SingleStack/IPv4 since CLB only forwards IPv4 traffic. Without this, DualStackIPv6Primary clusters would default the CLB service to SingleStack/IPv6, causing OVN to refuse IPv4 traffic on the service's NodePort.

When the cluster IP family is dual-stack, the AWS DNS provider creates both Alias A and Alias AAAA Route53 records for IngressController wildcard domains. AAAA records are always created regardless of LB type because stale AAAA records cannot be easily cleaned up when the LB type changes from NLB to CLB — the old NLB is deleted before the new CLB is created, so the DNS provider can no longer look up the target hostname to delete the AAAA record. For CLBs the AAAA alias simply won't resolve.

When the LB type is changed to CLB on a dual-stack cluster, a warning note is appended to the effectuation message on the cluster operator status indicating that CLBs do not support dual-stack.

Enhancement proposal: openshift/enhancements#1940.
Related upstream cloud-provider-aws change: kubernetes/cloud-provider-aws#1313.

Manual test

The test can only be manual for the moment while CCM changes are still in progress.

DualStackIPv4Primary

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: 10.128.0.0/14
   hostPrefix: 23
 - cidr: fd01::/48
   hostPrefix: 64
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - 172.30.0.0/16
 - fd02::/112
$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv4Primary
 lbType: NLB

Note that the load balancer type must be NLB for dual stack IP family, this is a requirement.

Infrastructure CR:

$ oc get infrastructure cluster -o yaml | yq .status.platformStatus
aws:
 cloudLoadBalancerConfig:
   dnsType: PlatformDefault
 ipFamily: DualStackIPv4Primary
 region: us-east-2
type: AWS

IngressController CR:

$ oc -n openshift-ingress-operator get ingresscontroller default -o yaml| yq .spec.endpointPublishingStrategy
loadBalancer:
 dnsManagementPolicy: Managed
 providerParameters:
   aws:
     type: Classic
   type: AWS
 scope: External
type: LoadBalancerService

Publishing load balancer service:

$ oc -n openshift-ingress get service router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.241.69
clusterIPs:
 - 172.30.241.69
 - fd02::133d
externalTrafficPolicy: Local
healthCheckNodePort: 30592
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
 - IPv6
ipFamilyPolicy: RequireDualStack

$ oc -n openshift-ingress get svc router-default -o yaml | yq .status
loadBalancer:
 ingress:
   - hostname: a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

DNSRecord CR:

$ oc -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .spec
dnsManagementPolicy: Managed
dnsName: '*.apps.alebedev-0317.devcluster.openshift.com.'
recordTTL: 30
recordType: CNAME
targets:
 - a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

$ oc  -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .status.zones
- conditions:
   - lastTransitionTime: "2026-03-17T10:34:39Z"
     message: The DNS provider succeeded in ensuring the record
     reason: ProviderSuccess
     status: "True"
     type: Published
 dnsZone:
   id: Z3URY6TWQ91KVV

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.apps.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

DNS query:

$ dig +short -t AAAA a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7
2600:1f16:c02:8006:b886:bd46:968d:7e64

$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
2600:1f16:c02:8006:b886:bd46:968d:7e64
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7

e2e connectivity (any IPv6 ingress rule is still missing in CCM implementation though, TBC):

$ oc debug node/i-01ee44c772dd27883.us-east-2.compute.internal
Starting pod/i-01ee44c772dd27883us-east-2computeinternal-debug-vnlzq ...
To use host binaries, run `chroot /host`
Pod IP: 10.0.85.93
If you don't see a command prompt, try pressing enter.
sh-5.1# chroot /host
sh-5.1# curl -6 -kI --connect-timeout 5 https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com/
HTTP/1.1 200 OK

Changing the LB type to Classic:

$ oc get co ingress
NAME      VERSION                                                AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
ingress   4.22.0-0-2026-03-13-170918-test-ci-ln-wgyfqy2-latest   True        True          False      73m     ingresscontroller "default" is progressing: IngressControllerProgressing: One or more status conditions indicate progressing: LoadBalancerProgressing=True (OperandsProgressing: One or more managed resources are progressing: [The IngressController loadBalancer.providerParameters.aws.type was changed from "NLB" to "Classic".  To effectuate this change, you must delete the service: `oc -n openshift-ingress delete svc/router-default`; the service load-balancer will then be deprovisioned and a new one created. This will most likely cause the new load-balancer to have a different host name and IP address and cause disruption. To return to the previous state, you can revert the change to the IngressController: `oc -n openshift-ingress-operator patch ingresscontrollers/default --type=merge --patch='{"spec":{"endpointPublishingStrategy":{"type":"LoadBalancerService","loadBalancer":{"providerParameters":{"type":"AWS","aws":{"type":"NLB"}}}}}}'`. Direct updates to the service annotations are not supported. Classic Load Balancers do not support dual-stack. The IngressController "default" will use IPv4-only despite that the cluster is configured as "DualStackIPv4Primary". Use an NLB type to support dual-stack networking.]).

$ oc -n openshift-ingress delete svc/router-default
service "router-default" deleted

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.132.69
clusterIPs:
 - 172.30.132.69
externalTrafficPolicy: Local
healthCheckNodePort: 30575
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
ipFamilyPolicy: SingleStack

DNS query after a change to CLB:

$ dig +short -t A console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
16.58.45.52
3.21.115.53
$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Connectivity after a change to CLB:

# curl -4 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
HTTP/1.1 200 OK

# curl -6 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
curl: (6) Could not resolve host: console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Clean up of DNS (new shard to showcase the cleanup):

$ oc -n openshift-ingress-operator get ingresscontroller my -o yaml | yq .spec.domain
hosts.alebedev-0317.devcluster.openshift.com

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

$ oc -n openshift-ingress-operator delete ingresscontroller my
ingresscontroller.operator.openshift.io "my" deleted

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[]

DualStackIPv6Primary

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: fd01::/48
   hostPrefix: 64
 - cidr: 10.128.0.0/14
   hostPrefix: 23
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - fd02::/112
 - 172.30.0.0/16

$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv6Primary
 lbType: NLB

Publishing service:

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
clusterIP: fd02::3dac
clusterIPs:
 - fd02::3dac
 - 172.30.4.110
externalTrafficPolicy: Local
healthCheckNodePort: 32125
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv6
 - IPv4
ipFamilyPolicy: RequireDualStack

DNS check:

$ dig +short -t A console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
3.132.149.10
18.225.48.44

$ dig +short -t AAAA console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
2600:1f16:1b80:2306:a3e4:ea37:6b15:9355
2600:1f16:1b80:2304:7f94:5708:1f66:af79

Connectivity check:

$ oc debug node/i-040c1020eeb1401ab.us-east-2.compute.internal
Starting pod/i-040c1020eeb1401abus-east-2computeinternal-debug-cv64v ...
To use host binaries, run `chroot /host`
Pod IP: 2600:1f16:1b80:2303:669e:5b4d:7a21:56
If you don't see a command prompt, try pressing enter.
sh-5.1# curl -6 -kI https://console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
HTTP/1.1 200 OK

sh-5.1# curl -4 -kI https://console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
HTTP/1.1 200 OK

CLB shard

$ oc -n openshift-ingress-operator get ingresscontroller my -o yaml | yq .spec.domain
hosts.alebedev-03182.devcluster.openshift.com


$ oc -n openshift-ingress get svc router-my -o yaml | yq .metadata.annotations
service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold: "2"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "5"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "4"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: "2"
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: '*'
traffic-policy.network.alpha.openshift.io/local-with-fallback: ""

$ oc -n openshift-ingress get svc router-my -o yaml | yq .spec
clusterIP: 172.30.28.213
clusterIPs:
 - 172.30.28.213
externalTrafficPolicy: Local
healthCheckNodePort: 30968
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
ipFamilyPolicy: SingleStack

CLB connectivity check:

$ oc debug node/i-040c1020eeb1401ab.us-east-2.compute.internal
Starting pod/i-040c1020eeb1401abus-east-2computeinternal-debug-7nhlc ...
To use host binaries, run `chroot /host`
cuPod IP: 2600:1f16:1b80:2303:669e:5b4d:7a21:56
If you don't see a command prompt, try pressing enter.
sh-5.1# curl -4 -kI https://downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com
HTTP/1.0 200 OK

sh-5.1# curl -6 -kI https://downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com
curl: (6) Could not resolve host: downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 18, 2026

@alebedev87: This pull request references NE-2421 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Configure the publishing load balancer service for dual-stack AWS clusters based on the Infrastructure CR's ipFamily field (status.platformStatus.aws.ipFamily).

For NLB-type load balancers, set ipFamilyPolicy to RequireDualStack and ipFamilies to match the cluster's IP family ordering. For CLB, explicitly set SingleStack/IPv4 since CLB only forwards IPv4 traffic. Without this, DualStackIPv6Primary clusters would default the CLB service to SingleStack/IPv6, causing OVN to refuse IPv4 traffic on the service's NodePort.

When the cluster IP family is dual-stack, the AWS DNS provider creates both Alias A and Alias AAAA Route53 records for IngressController wildcard domains. AAAA records are always created regardless of LB type because stale AAAA records cannot be easily cleaned up when the LB type changes from NLB to CLB — the old NLB is deleted before the new CLB is created, so the DNS provider can no longer look up the target hostname to delete the AAAA record. For CLBs the AAAA alias simply won't resolve.

When the LB type is changed to CLB on a dual-stack cluster, a warning note is appended to the effectuation message on the cluster operator status indicating that CLBs do not support dual-stack.

Enhancement proposal: openshift/enhancements#1940.
Related upstream cloud-provider-aws change: kubernetes/cloud-provider-aws#1313.
E2E testing will be implemented as part of the featuergate test coverage: openshift/origin#30904.

Manual test

The test can only be manual for the moment while CCM changes are still in progress.

DualStackIPv4Primary

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: 10.128.0.0/14
   hostPrefix: 23
 - cidr: fd01::/48
   hostPrefix: 64
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - 172.30.0.0/16
 - fd02::/112
$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv4Primary
 lbType: NLB

Note that the load balancer type must be NLB for dual stack IP family, this is a requirement.

Infrastructure CR:

$ oc get infrastructure cluster -o yaml | yq .status.platformStatus
aws:
 cloudLoadBalancerConfig:
   dnsType: PlatformDefault
 ipFamily: DualStackIPv4Primary
 region: us-east-2
type: AWS

IngressController CR:

$ oc -n openshift-ingress-operator get ingresscontroller default -o yaml| yq .spec.endpointPublishingStrategy
loadBalancer:
 dnsManagementPolicy: Managed
 providerParameters:
   aws:
     type: Classic
   type: AWS
 scope: External
type: LoadBalancerService

Publishing load balancer service:

$ oc -n openshift-ingress get service router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.241.69
clusterIPs:
 - 172.30.241.69
 - fd02::133d
externalTrafficPolicy: Local
healthCheckNodePort: 30592
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
 - IPv6
ipFamilyPolicy: RequireDualStack

$ oc -n openshift-ingress get svc router-default -o yaml | yq .status
loadBalancer:
 ingress:
   - hostname: a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

DNSRecord CR:

$ oc -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .spec
dnsManagementPolicy: Managed
dnsName: '*.apps.alebedev-0317.devcluster.openshift.com.'
recordTTL: 30
recordType: CNAME
targets:
 - a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

$ oc  -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .status.zones
- conditions:
   - lastTransitionTime: "2026-03-17T10:34:39Z"
     message: The DNS provider succeeded in ensuring the record
     reason: ProviderSuccess
     status: "True"
     type: Published
 dnsZone:
   id: Z3URY6TWQ91KVV

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.apps.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

DNS query:

$ dig +short -t AAAA a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7
2600:1f16:c02:8006:b886:bd46:968d:7e64

$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
2600:1f16:c02:8006:b886:bd46:968d:7e64
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7

e2e connectivity (any IPv6 ingress rule is still missing in CCM implementation though, TBC):

$ oc debug node/i-01ee44c772dd27883.us-east-2.compute.internal
Starting pod/i-01ee44c772dd27883us-east-2computeinternal-debug-vnlzq ...
To use host binaries, run `chroot /host`
Pod IP: 10.0.85.93
If you don't see a command prompt, try pressing enter.
sh-5.1# chroot /host
sh-5.1# curl -6 -kI --connect-timeout 5 https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com/
HTTP/1.1 200 OK

Changing the LB type to Classic:

$ oc get co ingress
NAME      VERSION                                                AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
ingress   4.22.0-0-2026-03-13-170918-test-ci-ln-wgyfqy2-latest   True        True          False      73m     ingresscontroller "default" is progressing: IngressControllerProgressing: One or more status conditions indicate progressing: LoadBalancerProgressing=True (OperandsProgressing: One or more managed resources are progressing: [The IngressController loadBalancer.providerParameters.aws.type was changed from "NLB" to "Classic".  To effectuate this change, you must delete the service: `oc -n openshift-ingress delete svc/router-default`; the service load-balancer will then be deprovisioned and a new one created. This will most likely cause the new load-balancer to have a different host name and IP address and cause disruption. To return to the previous state, you can revert the change to the IngressController: `oc -n openshift-ingress-operator patch ingresscontrollers/default --type=merge --patch='{"spec":{"endpointPublishingStrategy":{"type":"LoadBalancerService","loadBalancer":{"providerParameters":{"type":"AWS","aws":{"type":"NLB"}}}}}}'`. Direct updates to the service annotations are not supported. Classic Load Balancers do not support dual-stack. The IngressController "default" will use IPv4-only despite that the cluster is configured as "DualStackIPv4Primary". Use an NLB type to support dual-stack networking.]).

$ oc -n openshift-ingress delete svc/router-default
service "router-default" deleted

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.132.69
clusterIPs:
 - 172.30.132.69
externalTrafficPolicy: Local
healthCheckNodePort: 30575
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
ipFamilyPolicy: SingleStack

DNS query after a change to CLB:

$ dig +short -t A console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
16.58.45.52
3.21.115.53
$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Connectivity after a change to CLB:

# curl -4 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
HTTP/1.1 200 OK

# curl -6 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
curl: (6) Could not resolve host: console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Clean up of DNS (new shard to showcase the cleanup):

$ oc -n openshift-ingress-operator get ingresscontroller my -o yaml | yq .spec.domain
hosts.alebedev-0317.devcluster.openshift.com

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

$ oc -n openshift-ingress-operator delete ingresscontroller my
ingresscontroller.operator.openshift.io "my" deleted

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[]

DualStackIPv6Primary

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: fd01::/48
   hostPrefix: 64
 - cidr: 10.128.0.0/14
   hostPrefix: 23
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - fd02::/112
 - 172.30.0.0/16

$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv6Primary
 lbType: NLB

Publishing service:

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
clusterIP: fd02::3dac
clusterIPs:
 - fd02::3dac
 - 172.30.4.110
externalTrafficPolicy: Local
healthCheckNodePort: 32125
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv6
 - IPv4
ipFamilyPolicy: RequireDualStack

DNS check:

$ dig +short -t A console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
3.132.149.10
18.225.48.44

$ dig +short -t AAAA console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
2600:1f16:1b80:2306:a3e4:ea37:6b15:9355
2600:1f16:1b80:2304:7f94:5708:1f66:af79

Connectivity check:

$ oc debug node/i-040c1020eeb1401ab.us-east-2.compute.internal
Starting pod/i-040c1020eeb1401abus-east-2computeinternal-debug-cv64v ...
To use host binaries, run `chroot /host`
Pod IP: 2600:1f16:1b80:2303:669e:5b4d:7a21:56
If you don't see a command prompt, try pressing enter.
sh-5.1# curl -6 -kI https://console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
HTTP/1.1 200 OK

sh-5.1# curl -4 -kI https://console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
HTTP/1.1 200 OK

CLB shard

$ oc -n openshift-ingress-operator get ingresscontroller my -o yaml | yq .spec.domain
hosts.alebedev-03182.devcluster.openshift.com


$ oc -n openshift-ingress get svc router-my -o yaml | yq .metadata.annotations
service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold: "2"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "5"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "4"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: "2"
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: '*'
traffic-policy.network.alpha.openshift.io/local-with-fallback: ""

$ oc -n openshift-ingress get svc router-my -o yaml | yq .spec
clusterIP: 172.30.28.213
clusterIPs:
 - 172.30.28.213
externalTrafficPolicy: Local
healthCheckNodePort: 30968
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
ipFamilyPolicy: SingleStack

CLB connectivity check:

$ oc debug node/i-040c1020eeb1401ab.us-east-2.compute.internal
Starting pod/i-040c1020eeb1401abus-east-2computeinternal-debug-7nhlc ...
To use host binaries, run `chroot /host`
cuPod IP: 2600:1f16:1b80:2303:669e:5b4d:7a21:56
If you don't see a command prompt, try pressing enter.
sh-5.1# curl -4 -kI https://downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com
HTTP/1.0 200 OK

sh-5.1# curl -6 -kI https://downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com
curl: (6) Could not resolve host: downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 18, 2026

@alebedev87: This pull request references NE-2421 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Configure the publishing load balancer service for dual-stack AWS clusters based on the Infrastructure CR's ipFamily field (status.platformStatus.aws.ipFamily).

For NLB-type load balancers, set ipFamilyPolicy to RequireDualStack and ipFamilies to match the cluster's IP family ordering. For CLB, explicitly set SingleStack/IPv4 since CLB only forwards IPv4 traffic. Without this, DualStackIPv6Primary clusters would default the CLB service to SingleStack/IPv6, causing OVN to refuse IPv4 traffic on the service's NodePort.

When the cluster IP family is dual-stack, the AWS DNS provider creates both Alias A and Alias AAAA Route53 records for IngressController wildcard domains. AAAA records are always created regardless of LB type because stale AAAA records cannot be easily cleaned up when the LB type changes from NLB to CLB — the old NLB is deleted before the new CLB is created, so the DNS provider can no longer look up the target hostname to delete the AAAA record. For CLBs the AAAA alias simply won't resolve.

When the LB type is changed to CLB on a dual-stack cluster, a warning note is appended to the effectuation message on the cluster operator status indicating that CLBs do not support dual-stack.

Enhancement proposal: openshift/enhancements#1940.
Related upstream cloud-provider-aws change: kubernetes/cloud-provider-aws#1313.
E2E testing will be implemented as part of the featuregate test coverage: openshift/origin#30904.

Manual test

The test can only be manual for the moment while CCM changes are still in progress.

DualStackIPv4Primary

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: 10.128.0.0/14
   hostPrefix: 23
 - cidr: fd01::/48
   hostPrefix: 64
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - 172.30.0.0/16
 - fd02::/112
$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv4Primary
 lbType: NLB

Note that the load balancer type must be NLB for dual stack IP family, this is a requirement.

Infrastructure CR:

$ oc get infrastructure cluster -o yaml | yq .status.platformStatus
aws:
 cloudLoadBalancerConfig:
   dnsType: PlatformDefault
 ipFamily: DualStackIPv4Primary
 region: us-east-2
type: AWS

IngressController CR:

$ oc -n openshift-ingress-operator get ingresscontroller default -o yaml| yq .spec.endpointPublishingStrategy
loadBalancer:
 dnsManagementPolicy: Managed
 providerParameters:
   aws:
     type: Classic
   type: AWS
 scope: External
type: LoadBalancerService

Publishing load balancer service:

$ oc -n openshift-ingress get service router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.241.69
clusterIPs:
 - 172.30.241.69
 - fd02::133d
externalTrafficPolicy: Local
healthCheckNodePort: 30592
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
 - IPv6
ipFamilyPolicy: RequireDualStack

$ oc -n openshift-ingress get svc router-default -o yaml | yq .status
loadBalancer:
 ingress:
   - hostname: a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

DNSRecord CR:

$ oc -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .spec
dnsManagementPolicy: Managed
dnsName: '*.apps.alebedev-0317.devcluster.openshift.com.'
recordTTL: 30
recordType: CNAME
targets:
 - a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

$ oc  -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .status.zones
- conditions:
   - lastTransitionTime: "2026-03-17T10:34:39Z"
     message: The DNS provider succeeded in ensuring the record
     reason: ProviderSuccess
     status: "True"
     type: Published
 dnsZone:
   id: Z3URY6TWQ91KVV

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.apps.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

DNS query:

$ dig +short -t AAAA a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7
2600:1f16:c02:8006:b886:bd46:968d:7e64

$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
2600:1f16:c02:8006:b886:bd46:968d:7e64
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7

e2e connectivity (any IPv6 ingress rule is still missing in CCM implementation though, TBC):

$ oc debug node/i-01ee44c772dd27883.us-east-2.compute.internal
Starting pod/i-01ee44c772dd27883us-east-2computeinternal-debug-vnlzq ...
To use host binaries, run `chroot /host`
Pod IP: 10.0.85.93
If you don't see a command prompt, try pressing enter.
sh-5.1# chroot /host
sh-5.1# curl -6 -kI --connect-timeout 5 https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com/
HTTP/1.1 200 OK

Changing the LB type to Classic:

$ oc get co ingress
NAME      VERSION                                                AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
ingress   4.22.0-0-2026-03-13-170918-test-ci-ln-wgyfqy2-latest   True        True          False      73m     ingresscontroller "default" is progressing: IngressControllerProgressing: One or more status conditions indicate progressing: LoadBalancerProgressing=True (OperandsProgressing: One or more managed resources are progressing: [The IngressController loadBalancer.providerParameters.aws.type was changed from "NLB" to "Classic".  To effectuate this change, you must delete the service: `oc -n openshift-ingress delete svc/router-default`; the service load-balancer will then be deprovisioned and a new one created. This will most likely cause the new load-balancer to have a different host name and IP address and cause disruption. To return to the previous state, you can revert the change to the IngressController: `oc -n openshift-ingress-operator patch ingresscontrollers/default --type=merge --patch='{"spec":{"endpointPublishingStrategy":{"type":"LoadBalancerService","loadBalancer":{"providerParameters":{"type":"AWS","aws":{"type":"NLB"}}}}}}'`. Direct updates to the service annotations are not supported. Classic Load Balancers do not support dual-stack. The IngressController "default" will use IPv4-only despite that the cluster is configured as "DualStackIPv4Primary". Use an NLB type to support dual-stack networking.]).

$ oc -n openshift-ingress delete svc/router-default
service "router-default" deleted

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.132.69
clusterIPs:
 - 172.30.132.69
externalTrafficPolicy: Local
healthCheckNodePort: 30575
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
ipFamilyPolicy: SingleStack

DNS query after a change to CLB:

$ dig +short -t A console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
16.58.45.52
3.21.115.53
$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Connectivity after a change to CLB:

# curl -4 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
HTTP/1.1 200 OK

# curl -6 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
curl: (6) Could not resolve host: console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Clean up of DNS (new shard to showcase the cleanup):

$ oc -n openshift-ingress-operator get ingresscontroller my -o yaml | yq .spec.domain
hosts.alebedev-0317.devcluster.openshift.com

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

$ oc -n openshift-ingress-operator delete ingresscontroller my
ingresscontroller.operator.openshift.io "my" deleted

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[]

DualStackIPv6Primary

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: fd01::/48
   hostPrefix: 64
 - cidr: 10.128.0.0/14
   hostPrefix: 23
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - fd02::/112
 - 172.30.0.0/16

$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv6Primary
 lbType: NLB

Publishing service:

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
clusterIP: fd02::3dac
clusterIPs:
 - fd02::3dac
 - 172.30.4.110
externalTrafficPolicy: Local
healthCheckNodePort: 32125
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv6
 - IPv4
ipFamilyPolicy: RequireDualStack

DNS check:

$ dig +short -t A console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
3.132.149.10
18.225.48.44

$ dig +short -t AAAA console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
2600:1f16:1b80:2306:a3e4:ea37:6b15:9355
2600:1f16:1b80:2304:7f94:5708:1f66:af79

Connectivity check:

$ oc debug node/i-040c1020eeb1401ab.us-east-2.compute.internal
Starting pod/i-040c1020eeb1401abus-east-2computeinternal-debug-cv64v ...
To use host binaries, run `chroot /host`
Pod IP: 2600:1f16:1b80:2303:669e:5b4d:7a21:56
If you don't see a command prompt, try pressing enter.
sh-5.1# curl -6 -kI https://console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
HTTP/1.1 200 OK

sh-5.1# curl -4 -kI https://console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
HTTP/1.1 200 OK

CLB shard

$ oc -n openshift-ingress-operator get ingresscontroller my -o yaml | yq .spec.domain
hosts.alebedev-03182.devcluster.openshift.com


$ oc -n openshift-ingress get svc router-my -o yaml | yq .metadata.annotations
service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold: "2"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "5"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "4"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: "2"
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: '*'
traffic-policy.network.alpha.openshift.io/local-with-fallback: ""

$ oc -n openshift-ingress get svc router-my -o yaml | yq .spec
clusterIP: 172.30.28.213
clusterIPs:
 - 172.30.28.213
externalTrafficPolicy: Local
healthCheckNodePort: 30968
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
ipFamilyPolicy: SingleStack

CLB connectivity check:

$ oc debug node/i-040c1020eeb1401ab.us-east-2.compute.internal
Starting pod/i-040c1020eeb1401abus-east-2computeinternal-debug-7nhlc ...
To use host binaries, run `chroot /host`
cuPod IP: 2600:1f16:1b80:2303:669e:5b4d:7a21:56
If you don't see a command prompt, try pressing enter.
sh-5.1# curl -4 -kI https://downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com
HTTP/1.0 200 OK

sh-5.1# curl -6 -kI https://downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com
curl: (6) Could not resolve host: downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

lbStatus.ProviderParameters.Type == operatorv1.AWSLoadBalancerProvider &&
lbStatus.ProviderParameters.AWS != nil &&
lbStatus.ProviderParameters.AWS.Type == operatorv1.AWSNetworkLoadBalancer
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grant recently added LB check from the service, when one exists. I'm not sure about the moment checking the LB type is needed, so wondering if the same approach should be applied here as well.

case configv1.AWSPlatformType:
// TRICKY: If the service exists, use the LB Type annotation from the service,
// as status will be inaccurate if a LB Type update is pending.
// If the service does not exist, the status WILL be what determines the next LB Type.
var lbType operatorv1.AWSLoadBalancerType
if service != nil {
lbType = getAWSLoadBalancerTypeFromServiceAnnotation(service)
} else {
lbType = getAWSLoadBalancerTypeInStatus(ic)
}
return lbType == operatorv1.AWSClassicLoadBalancer, nil

This is the relevant thread about the subject
#1349 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right about raising this point because it's a tricky one. Here we are constructing the desired state of the load balancer service. And I stick to the existing approach (approach number 1 as described in the defaulting function which is called before ensuring of ingresscontroller). To make things more consistent with the existing checks of the lb type (like this one), let me do the same check and add a switch. This will remove the need for a dedicated isAWSNLB function too.

expectedIPFamilies: []corev1.IPFamily{corev1.IPv4Protocol},
expectedIPFamilyPolicy: ipFamilyPolicyPtr(corev1.IPFamilyPolicySingleStack),
},
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another useful tests are configuring aws dual stack without a platform type, another one using a platform type other than aws. Both shouldn't configure IP families.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a new test case for the platform different than AWS.

Another useful tests are configuring aws dual stack without a platform type

This should not happen, the platform type is required in Infrastructure API and should be set by the installer.

@alebedev87 alebedev87 force-pushed the NE-2421-support-dualstack-ingresscontroller branch from ae1dd7b to 0404bad Compare March 20, 2026 11:16
Copy link
Member

@jcmoraisjr jcmoraisjr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a minor, wondering if my understanding makes sense, other than that lgtm.

Configure the publishing load balancer service for dual-stack AWS
clusters based on the Infrastructure CR's ipFamily field
(`status.platformStatus.aws.ipFamily`).

For NLB-type load balancers, set `ipFamilyPolicy` to `RequireDualStack`
and `ipFamilies` to match the cluster's IP family ordering. For CLB,
explicitly set SingleStack/IPv4 since CLB only forwards IPv4 traffic.
Without this, `DualStackIPv6Primary` clusters would default the CLB
service to SingleStack/IPv6, causing OVN to refuse IPv4 traffic on
the service's NodePort.

When the cluster IP family is dual-stack, the AWS DNS provider creates
both Alias A and Alias AAAA Route53 records for IngressController
wildcard domains. AAAA records are always created regardless of LB
type because stale AAAA records cannot be easily cleaned up when the
LB type changes from NLB to CLB — the old NLB is deleted before the
new CLB is created, so the DNS provider can no longer look up the
target hostname to delete the AAAA record. For CLBs the AAAA alias
simply won't resolve.

When the LB type is changed to CLB on a dual-stack cluster, a warning
note is appended to the effectuation message on the cluster operator
status indicating that CLBs do not support dual-stack.

Co-authored-by: Claude claude-opus-4-6 <noreply@anthropic.com>
@alebedev87 alebedev87 force-pushed the NE-2421-support-dualstack-ingresscontroller branch from 0404bad to 82dc195 Compare March 20, 2026 13:29
@jcmoraisjr
Copy link
Member

/lgtm

@openshift-ci openshift-ci bot added the lgtm Indicates that a PR is ready to be merged. label Mar 20, 2026
@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 23, 2026

@alebedev87: This pull request references NE-2421 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Configure the publishing load balancer service for dual-stack AWS clusters based on the Infrastructure CR's ipFamily field (status.platformStatus.aws.ipFamily).

For NLB-type load balancers, set ipFamilyPolicy to RequireDualStack and ipFamilies to match the cluster's IP family ordering. For CLB, explicitly set SingleStack/IPv4 since CLB only forwards IPv4 traffic. Without this, DualStackIPv6Primary clusters would default the CLB service to SingleStack/IPv6, causing OVN to refuse IPv4 traffic on the service's NodePort.

When the cluster IP family is dual-stack, the AWS DNS provider creates both Alias A and Alias AAAA Route53 records for IngressController wildcard domains. AAAA records are always created regardless of LB type because stale AAAA records cannot be easily cleaned up when the LB type changes from NLB to CLB — the old NLB is deleted before the new CLB is created, so the DNS provider can no longer look up the target hostname to delete the AAAA record. For CLBs the AAAA alias simply won't resolve.

When the LB type is changed to CLB on a dual-stack cluster, a warning note is appended to the effectuation message on the cluster operator status indicating that CLBs do not support dual-stack.

Enhancement proposal: openshift/enhancements#1940.
Related upstream cloud-provider-aws change: kubernetes/cloud-provider-aws#1313.
E2E testing will be implemented as part of the featuregate test coverage: openshift/origin#30904.

Manual test

The test can only be manual for the moment while CCM changes are still in progress. Clusterbot command used:

build 4.22,openshift/cluster-ingress-operator#1376,openshift/cloud-provider-aws#135

DualStackIPv4Primary

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: 10.128.0.0/14
   hostPrefix: 23
 - cidr: fd01::/48
   hostPrefix: 64
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - 172.30.0.0/16
 - fd02::/112
$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv4Primary
 lbType: NLB

Note that the load balancer type must be NLB for dual stack IP family, this is a requirement.

Infrastructure CR:

$ oc get infrastructure cluster -o yaml | yq .status.platformStatus
aws:
 cloudLoadBalancerConfig:
   dnsType: PlatformDefault
 ipFamily: DualStackIPv4Primary
 region: us-east-2
type: AWS

IngressController CR:

$ oc -n openshift-ingress-operator get ingresscontroller default -o yaml| yq .spec.endpointPublishingStrategy
loadBalancer:
 dnsManagementPolicy: Managed
 providerParameters:
   aws:
     type: Classic
   type: AWS
 scope: External
type: LoadBalancerService

Publishing load balancer service:

$ oc -n openshift-ingress get service router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.241.69
clusterIPs:
 - 172.30.241.69
 - fd02::133d
externalTrafficPolicy: Local
healthCheckNodePort: 30592
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
 - IPv6
ipFamilyPolicy: RequireDualStack

$ oc -n openshift-ingress get svc router-default -o yaml | yq .status
loadBalancer:
 ingress:
   - hostname: a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

DNSRecord CR:

$ oc -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .spec
dnsManagementPolicy: Managed
dnsName: '*.apps.alebedev-0317.devcluster.openshift.com.'
recordTTL: 30
recordType: CNAME
targets:
 - a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com

$ oc  -n openshift-ingress-operator get dnsrecord default-wildcard -o yaml | yq .status.zones
- conditions:
   - lastTransitionTime: "2026-03-17T10:34:39Z"
     message: The DNS provider succeeded in ensuring the record
     reason: ProviderSuccess
     status: "True"
     type: Published
 dnsZone:
   id: Z3URY6TWQ91KVV

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.apps.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.apps.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "ZLMOA37VPKANP",
           "DNSName": "a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

DNS query:

$ dig +short -t AAAA a230525026ec5402da89c05a24e41ac7-b37799d409ca8e7e.elb.us-east-2.amazonaws.com
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7
2600:1f16:c02:8006:b886:bd46:968d:7e64

$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
2600:1f16:c02:8006:b886:bd46:968d:7e64
2600:1f16:c02:8002:ed14:4ef3:266d:e1a7

e2e connectivity (any IPv6 ingress rule is still missing in CCM implementation though, TBC):

$ oc debug node/i-01ee44c772dd27883.us-east-2.compute.internal
Starting pod/i-01ee44c772dd27883us-east-2computeinternal-debug-vnlzq ...
To use host binaries, run `chroot /host`
Pod IP: 10.0.85.93
If you don't see a command prompt, try pressing enter.
sh-5.1# chroot /host
sh-5.1# curl -6 -kI --connect-timeout 5 https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com/
HTTP/1.1 200 OK

Changing the LB type to Classic:

$ oc get co ingress
NAME      VERSION                                                AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
ingress   4.22.0-0-2026-03-13-170918-test-ci-ln-wgyfqy2-latest   True        True          False      73m     ingresscontroller "default" is progressing: IngressControllerProgressing: One or more status conditions indicate progressing: LoadBalancerProgressing=True (OperandsProgressing: One or more managed resources are progressing: [The IngressController loadBalancer.providerParameters.aws.type was changed from "NLB" to "Classic".  To effectuate this change, you must delete the service: `oc -n openshift-ingress delete svc/router-default`; the service load-balancer will then be deprovisioned and a new one created. This will most likely cause the new load-balancer to have a different host name and IP address and cause disruption. To return to the previous state, you can revert the change to the IngressController: `oc -n openshift-ingress-operator patch ingresscontrollers/default --type=merge --patch='{"spec":{"endpointPublishingStrategy":{"type":"LoadBalancerService","loadBalancer":{"providerParameters":{"type":"AWS","aws":{"type":"NLB"}}}}}}'`. Direct updates to the service annotations are not supported. Classic Load Balancers do not support dual-stack. The IngressController "default" will use IPv4-only despite that the cluster is configured as "DualStackIPv4Primary". Use an NLB type to support dual-stack networking.]).

$ oc -n openshift-ingress delete svc/router-default
service "router-default" deleted

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
allocateLoadBalancerNodePorts: true
clusterIP: 172.30.132.69
clusterIPs:
 - 172.30.132.69
externalTrafficPolicy: Local
healthCheckNodePort: 30575
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
ipFamilyPolicy: SingleStack

DNS query after a change to CLB:

$ dig +short -t A console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
16.58.45.52
3.21.115.53
$ dig +short -t AAAA console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Connectivity after a change to CLB:

# curl -4 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
HTTP/1.1 200 OK

# curl -6 -kI https://console-openshift-console.apps.alebedev-0317.devcluster.openshift.com
curl: (6) Could not resolve host: console-openshift-console.apps.alebedev-0317.devcluster.openshift.com

Clean up of DNS (new shard to showcase the cleanup):

$ oc -n openshift-ingress-operator get ingresscontroller my -o yaml | yq .spec.domain
hosts.alebedev-0317.devcluster.openshift.com

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "A",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   },
   {
       "Name": "\\052.hosts.alebedev-0317.devcluster.openshift.com.",
       "Type": "AAAA",
       "AliasTarget": {
           "HostedZoneId": "Z3AADJGX6KTTL2",
           "DNSName": "a5b6d334a7fb043379beca7e55c5e1dc-630903343.us-east-2.elb.amazonaws.com.",
           "EvaluateTargetHealth": false
       }
   }
]

$ oc -n openshift-ingress-operator delete ingresscontroller my
ingresscontroller.operator.openshift.io "my" deleted

$ aws route53 list-resource-record-sets --hosted-zone-id Z3URY6TWQ91KVV --query "ResourceRecordSets[?Name=='\\052.hosts.alebedev-0317.devcluster.openshift.com.']"
[]

DualStackIPv6Primary

Install config:

$ cat ~/install-config-dual-stack.yaml | yq .networking
clusterNetwork:
 - cidr: fd01::/48
   hostPrefix: 64
 - cidr: 10.128.0.0/14
   hostPrefix: 23
machineNetwork:
 - cidr: 10.0.0.0/16
networkType: OVNKubernetes
serviceNetwork:
 - fd02::/112
 - 172.30.0.0/16

$ cat ~/install-config-dual-stack.yaml | yq .platform
aws:
 region: us-east-2
 ipFamily: DualStackIPv6Primary
 lbType: NLB

Publishing service:

$ oc -n openshift-ingress get svc router-default -o yaml | yq .spec
clusterIP: fd02::3dac
clusterIPs:
 - fd02::3dac
 - 172.30.4.110
externalTrafficPolicy: Local
healthCheckNodePort: 32125
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv6
 - IPv4
ipFamilyPolicy: RequireDualStack

DNS check:

$ dig +short -t A console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
3.132.149.10
18.225.48.44

$ dig +short -t AAAA console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
2600:1f16:1b80:2306:a3e4:ea37:6b15:9355
2600:1f16:1b80:2304:7f94:5708:1f66:af79

Connectivity check:

$ oc debug node/i-040c1020eeb1401ab.us-east-2.compute.internal
Starting pod/i-040c1020eeb1401abus-east-2computeinternal-debug-cv64v ...
To use host binaries, run `chroot /host`
Pod IP: 2600:1f16:1b80:2303:669e:5b4d:7a21:56
If you don't see a command prompt, try pressing enter.
sh-5.1# curl -6 -kI https://console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
HTTP/1.1 200 OK

sh-5.1# curl -4 -kI https://console-openshift-console.apps.alebedev-03182.devcluster.openshift.com
HTTP/1.1 200 OK

CLB shard

$ oc -n openshift-ingress-operator get ingresscontroller my -o yaml | yq .spec.domain
hosts.alebedev-03182.devcluster.openshift.com


$ oc -n openshift-ingress get svc router-my -o yaml | yq .metadata.annotations
service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold: "2"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "5"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "4"
service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: "2"
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: '*'
traffic-policy.network.alpha.openshift.io/local-with-fallback: ""

$ oc -n openshift-ingress get svc router-my -o yaml | yq .spec
clusterIP: 172.30.28.213
clusterIPs:
 - 172.30.28.213
externalTrafficPolicy: Local
healthCheckNodePort: 30968
internalTrafficPolicy: Cluster
ipFamilies:
 - IPv4
ipFamilyPolicy: SingleStack

CLB connectivity check:

$ oc debug node/i-040c1020eeb1401ab.us-east-2.compute.internal
Starting pod/i-040c1020eeb1401abus-east-2computeinternal-debug-7nhlc ...
To use host binaries, run `chroot /host`
cuPod IP: 2600:1f16:1b80:2303:669e:5b4d:7a21:56
If you don't see a command prompt, try pressing enter.
sh-5.1# curl -4 -kI https://downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com
HTTP/1.0 200 OK

sh-5.1# curl -6 -kI https://downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com
curl: (6) Could not resolve host: downloads-openshift-console.hosts.alebedev-03182.devcluster.openshift.com

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@alebedev87
Copy link
Contributor Author

/retest

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Mar 23, 2026

@alebedev87: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/e2e-aws-ovn-hypershift-conformance 82dc195 link true /test e2e-aws-ovn-hypershift-conformance
ci/prow/e2e-gcp-operator 82dc195 link true /test e2e-gcp-operator

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Copy link
Contributor

@davidesalerno davidesalerno left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a couple of minor questions
The code in general looks good.

ResourceRecordSet: &route53.ResourceRecordSet{
Name: aws.String(domain),
Type: aws.String(route53.RRTypeAaaa),
AliasTarget: aliasTarget,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both ResourceRecordSet entries point to the same aliasTarget struct. While the AWS SDK v1 is unlikely to mutate it, this is a subtle aliasing risk. If the SDK ever serialises changes concurrently or modifies the struct, both records could be corrupted. Consider copying the AliasTarget for the AAAA entry or creating each inline.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, something like a C++ pointer to a constant would avoid this confusion, but Golang is way simpler. I think it's fine to use a pointer to the same object. AWS SDK should not mess with the input parameters especially from the same change batch. If it does (unlikely), we may have problems with other variables too, like action and domain whose pointers are taken too.

}
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no else statement so if lbStatus is nil or its AWS sub-field is nil (e.g., initial reconcile before status is populated), the IP family configuration is silently skipped.
Are we fine or would it be better to at least log this event?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are fine with this, it's consistent with the rest of desiredLoadBalancerService function and as a matter of fact with most of other desired* functions. The conditions in these functions should be clear and tested, the logging is reserved mostly for errors and warning to avoid any flooding.

@davidesalerno
Copy link
Contributor

/approve
/lgtm

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Mar 24, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: davidesalerno

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Mar 24, 2026
@patrickdillon
Copy link
Contributor

/testwith openshift/installer/main/e2e-aws-ovn-dualstack-ipv6-primary-techpreview openshift/cloud-provider-aws#135

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Mar 24, 2026

@patrickdillon, testwith: could not generate prow job. ERROR:

no ref for requested test included in command. The org, repo, and branch containing the requested test need to be targeted by at least one of the included PRs

@patrickdillon
Copy link
Contributor

/testwith openshift/installer/main/e2e-aws-ovn-dualstack-ipv6-primary-techpreview openshift/cloud-provider-aws#135 openshift/installer#10380

@alebedev87
Copy link
Contributor Author

@patrickdillon : btw, note that there is a dedicated origin test I created for AWSDualStackInstall featuregate. It makes sense to run the dualstack-tp job in there too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. lgtm Indicates that a PR is ready to be merged.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants