Skip to content

fix(dataplane): make container workloads and load balancers reach each other - #2666

Merged
vieiralucas merged 7 commits into
mainfrom
fix-container-dataplane-reachability
Oct 4, 2026
Merged

vieiralucas merged 7 commits into
mainfrom
fix-container-dataplane-reachability

Conversation

@vieiralucas

@vieiralucas vieiralucas commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Summary

Container data-plane reachability fixes (bug audit 2026-10-01, Tier 0.10/0.11 + 1g). AWS-visible addresses stay in every API response; fakecloud now resolves them to sockets it can actually open.

  • ALB -> awsvpc ECS (HIGH). The ENI private IP is allocated from the task's subnet CIDR (new Ec2NetworkLookup hook on the delivery bus; AWS-reserved addresses and IPs held by live tasks are skipped; 10.0.0.0/16 fallback), with subnetId, networkInterfaceId, privateDnsName. RunTask precreates the ENI attachment (PRECREATED). awsvpc container ports are published on ephemeral host ports and registered in a new fakecloud_core::dataplane resolver (<ENI IP>:<containerPort> -> sibling_host:<hostPort>). The ELBv2 data plane and health prober resolve through it; access logs keep the ENI IP. k8s backend: ENI attaches at Running and targets route to the Pod IP.
  • ALB -> EC2 i-* (MEDIUM-HIGH). The EC2 runtime installs an instance resolver: the first time a target group routes to i-...:<port>, it starts a socat forwarder (default bridge + subnet network, ephemeral published port). Forwarders are removed on stop/reboot/terminate/reset.
  • ECS ECR pulls (MEDIUM-HIGH). ECS (and Batch through ECS) now uses the same registry host as Lambda via the shared container_net::ecr_registry_host: loopback, host.containers.internal for podman machine (macOS/Windows), and FAKECLOUD_ECR_REGISTRY_HOST overrides. It no longer uses sibling_host.
  • EC2 IMDS (MEDIUM). Each instance container gets a sidecar in its netns (Docker: --network container:; k8s: a second Pod container). The sidecar adds an nft/iptables redirect of 169.254.169.254:80 to a loopback nginx, which forwards to the new server route /_fakecloud/ec2/imds/<instance-id>/latest/*. That route answers with the instance's own id, AMI, type, local-ipv4, AZ, identity document, and its instance-profile role credentials (404 under iam/ when there's no profile). User-data waits (up to 60s) for /run/fakecloud/imds-ready. If the helper image can't be had, the instance gets AWS_EC2_METADATA_SERVICE_ENDPOINT instead. The helper image is built locally (alpine + nftables + nginx + socat); override it with FAKECLOUD_EC2_HELPER_IMAGE.
  • MSK / RDS / ElastiCache endpoints in container env (MEDIUM). The broker gets a third CONTAINER listener advertised as host_alias:P2. Env rewriting is now one shared helper, container_net::rewrite_loopback_value, used by Lambda, ECS and CodeBuild. It covers URLs, bare 127.0.0.1 / localhost, host:port lists and DSNs, and swaps the registered container-listener port into 127.0.0.1:P. Prose like EHLO localhost and look-alikes like 127.0.0.10 are left alone.
  • Unconfirmed items, also fixed:
    • MSK retries with fresh port pairs when the daemon host already has a pre-allocated port taken (the containerized-fakecloud case).
    • KDA publishes the Flink REST port ephemerally and reads it back.
    • RDS sqlserver/db2 pass --platform linux/amd64.

Non-code surfaces:

  • Updated: docs for EC2 (IMDS inside instances, LB targets), ECS (awsvpc ENI, ECR registry host), MSK (container listener, env rewriting), configuration reference (FAKECLOUD_EC2_HELPER_IMAGE, FAKECLOUD_ECR_REGISTRY_HOST), the instance-credentials guide, and AGENTS.md.
  • SDKs are unchanged: no new /_fakecloud/* introspection endpoint for SDK users. /_fakecloud/ec2/imds/... is internal plumbing for the in-instance proxy.

Test plan

  • Unit tests: dataplane resolver (registered endpoint vs. fallback vs. instance resolver, account scoping), rewrite_loopback_value cases, ecr_registry_host resolution, parse_published_port, ENI IP allocation (subnet bounds, reserved addresses, in-use skip, exhaustion), awsvpc ephemeral publish argv (task and holder), ECS env rewrite of bare endpoints, ELBv2 upstream resolution plus a prober test against a published awsvpc target (HTTP and TCP), EC2 sidecar/forwarder argv, the IMDS setup script run under sh with fake tools (proxy starts before the redirect, readiness file is written), k8s instance Pod sidecar plus shared volume, Kafka listener env/args, port-conflict detection, and the container-port registration round trip. KDA publish spec and RDS platform.

  • cargo test --lib --bins passes for core, elbv2, ecs, ec2, kafka, kinesisanalyticsv2, rds, lambda, codebuild and fakecloud.

  • cargo test -p fakecloud-conformance ecs_: 78/78 pass. Workspace cargo clippy --all-targets -D warnings and cargo fmt are clean.

  • New Docker-backed E2E crates/fakecloud-e2e/tests/container_dataplane_reachability.rs:

    • ALB -> awsvpc ECS service returns 200, the ENI IP is inside the subnet, and the target id is the ENI IP;
    • ALB -> EC2 instance port opened by user-data returns 200, and the forwarder is reaped on terminate;
    • an EC2 instance reads its instance id, role credentials and identity document from 169.254.169.254, including from user-data;
    • an ECS task pulls an ECR image with FAKECLOUD_IN_CONTAINER=1.

    The local Docker daemon hangs (docker info times out), so these E2E tests ran only in CI (general partitions). They compile locally (--no-run).


Summary by cubic

Makes container workloads and the load balancers in front of them reachable through the data plane: an ALB can route to awsvpc ECS tasks and EC2 instance ports, and EC2 instances reach the real IMDS inside their container.

Reachability fixes

  • awsvpc ECS tasks get a real requester-managed ENI created by EC2 in the task's subnet (visible in DescribeNetworkInterfaces, using the service's security groups) — PRECREATED at task creation, attached on start, deleted when the task stops; subnets EC2 doesn't know keep a synthetic ENI. Container ports publish to ephemeral host ports registered in a new shared dataplane resolver the ALB data plane and health prober consult, keeping the ENI IP in every API response.
  • EC2 instance ports are forwarded on demand through a socat forwarder per (instance, port); forwarders are serialized, swept on stop/reboot/terminate, never point at a stopped instance, and count as members of the load balancer's security groups so instance SGs admitting only the ALB's group admit its traffic.
  • Each instance container gets a sidecar that redirects 169.254.169.254:80 to a per-instance IMDS route serving the instance's own identity and instance-profile credentials; user-data waits for a tmpfs readiness marker that resets on stop/start/reboot. The helper-image build and sidecar setup run as a detached task outside the lifecycle lock, so a reset right after run/start no longer stalls on them.
  • MSK brokers advertise a second container-facing listener, and container env rewriting (Lambda, ECS, CodeBuild) now swaps loopback host:port/DSN endpoints to it.

Other fixes

  • ECS ECR pulls use the registry host shared with Lambda instead of the sibling alias; the FAKECLOUD_ECR_REGISTRY_HOST override is authorized in the registry auth map.
  • MSK retries with fresh port pairs when a pre-allocated port is taken, KDA publishes the Flink REST port ephemerally, and RDS sqlserver/db2 request linux/amd64.
  • FAKECLOUD_EC2_HELPER_IMAGE and FAKECLOUD_ECR_REGISTRY_HOST overrides documented; ELBv2 targets are now health-checked. No SDK changes.

Written for commit 62cfdcf. Summary will update on new commits.

Review in cubic

@vieiralucas
vieiralucas force-pushed the fix-container-dataplane-reachability branch 2 times, most recently from 1a20063 to 72dba7d Compare October 2, 2026 22:24
@vieiralucas
vieiralucas force-pushed the fix-container-dataplane-reachability branch 2 times, most recently from 33307ae to 5c0ba3e Compare October 4, 2026 10:59
…h other

- ELBv2 -> awsvpc ECS: ENI private IP allocated from the task's subnet CIDR
  (EC2 lookup on the delivery bus), container ports published ephemerally and
  registered with a shared core dataplane resolver the ALB data plane and
  health prober use; DescribeTasks/target health keep the ENI IP.
- ELBv2 -> EC2 i-* targets: the EC2 runtime installs an instance resolver that
  starts a socat forwarder per instance port on demand.
- EC2 instances reach IMDS at 169.254.169.254 (docker sidecar / k8s sidecar:
  nft redirect -> loopback nginx -> per-instance /_fakecloud/ec2/imds route
  with the instance's identity and instance-profile role); user-data waits for
  it; AWS_EC2_METADATA_SERVICE_ENDPOINT fallback without a helper image.
- ECS ECR pulls use the registry host shared with Lambda (loopback, podman
  machine alias, FAKECLOUD_ECR_REGISTRY_HOST), not the sibling alias.
- MSK broker gets a container-facing listener; container env rewriting
  (Lambda, ECS, CodeBuild) now covers bare host / host:port / DSN loopback
  endpoints and swaps in the container listener port.
- MSK retries port pairs taken on the daemon host; KDA publishes ephemerally;
  RDS sqlserver/db2 request linux/amd64.
- EC2 forwarders: serialize creation per (instance, port), sweep in-flight
  ones by label on stop, never forward to a stopped instance (address
  cleared on stop/reboot, re-validated after a forwarder starts).
- Forwarders count as members of the load balancer's security groups in the
  nft firewall model (LB groups passed through the dataplane resolver), so
  an instance SG admitting only the ALB's group admits its traffic.
- IMDS readiness marker on a tmpfs, so user-data waits for IMDS again after
  stop/start and reboot.
- ECS: awsvpc service tasks get their ENI attachment at creation (decided by
  the task definition's network mode); the k8s backend attaches the ENI for
  every awsvpc task.
- FAKECLOUD_ECR_REGISTRY_HOST override is authorized in the registry auth map.
- elbv2 docs: targets are health-checked; how targets are reached.
…ring

- Forwarder security groups accumulate across load balancers sharing a
  target; the firewall re-renders only when the set grows.
- A new forwarder's endpoint is returned only after the firewall re-render
  admitting it has landed.
- remove_sidecars lists in-flight forwarders with an async, bounded CLI call.
- The stale-forwarder check runs under the per-(instance, port) lock before
  the forwarder is recorded, so it can never drop a newer one.
- A running instance whose address is unknown (failed inspect after a
  reboot) is re-inspected on demand instead of staying unreachable.
- Firewall / NetworkPolicy models are built inside the reconcile lock.
…le lock

The helper image build and sidecar setup ran inside run_instance /
start / reboot, whose caller holds the instance lifecycle lock that reset,
stop and terminate wait on, so a reset right after RunInstances stalled for
the whole setup. The setup now runs as a detached task that re-checks the
instance is still the same running container; the env fallback is decided
from the cached helper state without waiting.
After rebasing onto the deferred reset teardown (#2677): the reset test now
also checks the IMDS sidecar is removed, the duplicate reset e2e is dropped
(ec2_instance_runtime covers it), and fake CLI scripts wait out ETXTBSY
before use so parallel tests forking can't fail their first exec.
… path

Reuse vpc_lookup::create_service_eni (#2674) for awsvpc task ENIs instead of
allocating a private IP from the subnet CIDR on the ECS side: the ENI is a
real EC2 interface (DescribeNetworkInterfaces, EC2's own address allocator,
the service's security groups, described by the attachment ARN) and is
deleted when the task stops. Subnets EC2 doesn't know keep a synthetic ENI.
Also track the Flink REST port read back after start on the cluster tracked
at create (#2667).
@vieiralucas
vieiralucas force-pushed the fix-container-dataplane-reachability branch from 5c0ba3e to 62cfdcf Compare October 4, 2026 12:18
@vieiralucas
vieiralucas merged commit 385c1f3 into main Oct 4, 2026
159 checks passed
@vieiralucas
vieiralucas deleted the fix-container-dataplane-reachability branch October 4, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant