Skip to content

feat(ec2-worker): ANYSCAN_EC2_ASSOCIATE_PUBLIC_IP knob for multi-ENI launches#79

Merged
skullcrushercmd merged 1 commit intomainfrom
feat/ec2-associate-public-ip-knob
Apr 28, 2026
Merged

feat(ec2-worker): ANYSCAN_EC2_ASSOCIATE_PUBLIC_IP knob for multi-ENI launches#79
skullcrushercmd merged 1 commit intomainfrom
feat/ec2-associate-public-ip-knob

Conversation

@skullcrushercmd
Copy link
Copy Markdown
Contributor

Summary

When PR #74's multi-ENI launch path is engaged (ANYSCAN_MAX_ENIS set), RunInstances is invoked with an explicit NetworkInterfaces[] payload — and in that mode AWS ignores the subnet's MapPublicIpOnLaunch=true. The launched instance comes up with no public IP and is unreachable from outside the VPC unless AssociatePublicIpAddress=True is set on the primary ENI explicitly.

This bit anygpt-48 in PR #65 issuecomment-4338158487:

A single-NIC launch on this subnet auto-assigns a public IP because MapPublicIpOnLaunch=true on the subnet. The multi-ENI path (launch_args[\"NetworkInterfaces\"] = build_network_interfaces(...)) does not set AssociatePublicIpAddress=True on the primary interface, so the launched metal had no public IP and was unreachable from outside the VPC. I worked around it by aws ec2 allocate-address + associate-address on the primary ENI.

What changed

  • New env knob ANYSCAN_EC2_ASSOCIATE_PUBLIC_IP (default false). Truthy values: 1, true, yes, on (case-insensitive).
  • ManagerConfig.associate_public_ip field plumbed through from_env() to the call site in recreate_instancebuild_network_interfaces(associate_public_ip=...).
  • build_network_interfaces sets AssociatePublicIpAddress=True on exactly one NIC: the entry with DeviceIndex=0 AND NetworkCardIndex=0 (or just DeviceIndex=0 on the legacy single-card path). Secondaries are silent on the field — AWS rejects AssociatePublicIpAddress on non-primary ENIs.

Why default off

Existing fleets that rely on MapPublicIpOnLaunch for the legacy single-NIC launch path are unaffected — the legacy path sets top-level SubnetId/SecurityGroupIds which DO honor the subnet flag. The knob only matters for ANYSCAN_MAX_ENIS-set fleets, and even there an operator may want a private launch (no public IP) so opt-in is correct.

Test plan

  • python3 -m unittest tools.test_ec2_worker_manager -v → 48 tests pass (40 existing + 8 new)
  • New tests:
    • 3 unit tests on build_network_interfaces: default-off, multi-card primary-only, single-card primary-only
    • 2 launch-path integration tests: knob propagates from ManagerConfig to RunInstances payload
    • 3 from_env tests: default off, truthy variants, falsy variants

🤖 Generated with Claude Code

…launches

When the launch payload uses an explicit NetworkInterfaces[] list
(ANYSCAN_MAX_ENIS set), AWS does NOT honor the subnet's
MapPublicIpOnLaunch — the operator has to opt in by setting
AssociatePublicIpAddress=True on the primary ENI explicitly.

In anygpt-48 (PR #65 issuecomment-4338158487) this caused the
c6in.metal launch to come up unreachable from outside the VPC; the
operator had to manually allocate-address + associate-address
post-launch as a workaround.

Add an opt-in env knob ANYSCAN_EC2_ASSOCIATE_PUBLIC_IP (default off so
existing fleets are unchanged). When set, plumb through ManagerConfig
to build_network_interfaces, which sets AssociatePublicIpAddress=True
on the entry with DeviceIndex=0 NetworkCardIndex=0 only — AWS rejects
the field on secondaries.
@skullcrushercmd skullcrushercmd merged commit 4a406a9 into main Apr 28, 2026
@skullcrushercmd skullcrushercmd deleted the feat/ec2-associate-public-ip-knob branch April 28, 2026 19:30
skullcrushercmd added a commit that referenced this pull request Apr 28, 2026
…ches (#82)

PR #79's ANYSCAN_EC2_ASSOCIATE_PUBLIC_IP=true on a multi-ENI launch is
hard-rejected by AWS:

  InvalidParameterCombination — The associatePublicIPAddress parameter
  cannot be specified when launching with multiple network interfaces.

AWS only honors AssociatePublicIpAddress on NetworkInterfaces[] when
exactly one entry is supplied, even if the field appears only on the
primary entry of a multi-NIC payload. The entire RunInstances call
fails. Reported in PR #65 issuecomment-4339242358 (anygpt-52).

Fix: when len(NetworkInterfaces) > 1, suppress the field inline and
allocate-address + associate-address on the primary ENI post-launch.
The recreate path now also releases the previously-recorded EIP
before terminating the old instance so we don't leak Elastic IPs on
every recreate.

- build_network_interfaces only emits AssociatePublicIpAddress when
  the resulting payload is single-NIC (target_count == 1).
- Ec2WorkerManager._associate_public_ip_post_launch allocates an EIP
  (Domain=vpc), associates it with the primary ENI (DeviceIndex=0),
  records AllocationId/AssociationId in self.state. Allocate or
  associate failures are surfaced in eni_attach.public_ip but do not
  abort the recreate — the worker is still usable on private IPs.
- Ec2WorkerManager._release_recorded_eip disassociates and releases
  any previously-recorded EIP at the start of recreate_instance.

Tests:
- New: launch payload free of AssociatePublicIpAddress on multi-ENI;
  allocate_address + associate_address called post-launch with the
  primary ENI's NetworkInterfaceId; allocation_id persisted.
- New: AllocateAddress failure does not abort recreate.
- New: AssociateAddress failure still records AllocationId so the
  next recreate can release it.
- New: previously-recorded EIP is disassociated + released before
  terminating the old instance on the next recreate.
- Updated: prior tests that asserted the broken inline-flag behavior
  on multi-NIC now assert the field is suppressed everywhere.

Co-authored-by: skullcmd <skullcmd@anyvm.tech>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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