Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DHPA] Upudate dockerPortMap() in task.go with dynamic host port range support - part 1 #3584

Merged

Conversation

chienhanlin
Copy link
Contributor

@chienhanlin chienhanlin commented Feb 21, 2023

Summary

DHPA - Dynamic Host Port Assignment

The target branch of this PR is feature/dynamicHostPortAssignment.

This is a follow-up PR for [DHPA] Add GetHostPort() and update unit tests to utilize function GetHostPort() in dockerPortMap() to construct a docker port map for an application container with the user-specified container port but without an user-defined host port running in bridge network mode.

Comparing to the current dockerPortMap(), main changes in this PR include:

  1. Use GetHostPort() to get a host port from the given dynamicHostPortRange when (a) it's a bridge network mode task and (b) there is the defined container port but no defined host in the port mapping configured by customers. With this change, ECS Agent will be the single source of truth for host port assignment. We will not fall back to Docker dynamic host port assignment if no host port can be found by ECS Agent within the given dynamicHostPortRange.
{
  "networkMode": "bridge",
  "containerDefinitions": [
    {
      "portMappings": [
        {
          "protocol": "tcp",
          "containerPort": 100,  
        }
      ],
      "image": "...",
      "name": "..."
    }
  ]
}

Note that dynamicHostPortRange can be configured by customers using ECS Agent environment variable ECS_DYNAMIC_HOST_PORT_RANGE; if the customized value is not provided, ECS Agent will use the default value returned from GetDynamicHostPortRange().

  1. Update test cases in TestDockerHostConfigPortBinding to validate changes made in this PR
  2. Make function PortIsInRange() and VerifyPortsWithinRange() public
  3. Add dynamicHostPortRange to String() for Agent config.go

Implementation details

agent/api/task/task.go

  • Update dockerPortMap() to include ECS Agent dynamic host port assignment for a singular port case
  • Update comments for the function

agent/api/task/task_test.go

  • Update test cases in TestDockerHostConfigPortBinding to test changes

agent/utils/ephemeral_ports.go

  • Move VerifyPortsWithinRange() and PortIsInRange() from the test file and make them public for reusing them in task_test.go

agent/utils/ephemeral_ports_test.go

  • Add unit tests for VerifyPortsWithinRange() and PortIsInRange()

agent/config/config.go

  • Add dynamicHostPortRange to function String()
level=debug time=xxx msg="Loaded config: Cluster: default,  ..., DynamicHostPortRange: 50000-50010, PauseContainerImageName: amazon/amazon-ecs-pause, PauseContainerTag: 0.1.0" module=agent.go

Testing

New tests cover the changes: yes

Unit test

TestDockerHostConfigPortBinding

--- PASS: TestDockerHostConfigPortBinding (0.00s)
    --- PASS: TestDockerHostConfigPortBinding/user-specified_container_ports_and_host_ports (0.00s)
    --- PASS: TestDockerHostConfigPortBinding/user-specified_container_ports_with_a_ideal_dynamicHostPortRange (0.00s)
    --- PASS: TestDockerHostConfigPortBinding/user-specified_container_ports_with_a_bad_dynamicHostPortRange (0.00s)
    --- PASS: TestDockerHostConfigPortBinding/user-specified_container_port_and_container_port_range_with_a_ideal_dynamicHostPortRange (0.00s)
    --- PASS: TestDockerHostConfigPortBinding/user-specified_container_port_and_container_port_range_with_a_bad_user-specified_dynamicHostPortRange (0.00s)

TestPortIsInRange

--- PASS: TestPortIsInRange (0.00s)
    --- PASS: TestPortIsInRange/in_the_range (0.00s)
    --- PASS: TestPortIsInRange/not_in_the_range (0.00s)

TestVerifyPortsWithinRange

--- PASS: TestVerifyPortsWithinRange (0.00s)
    --- PASS: TestVerifyPortsWithinRange/in_the_expected_range (0.00s)
    --- PASS: TestVerifyPortsWithinRange/not_in_the_expected_range (0.00s)

Manual test

Setup
  • An ECS cluster with only one registered EC2 instance, which is launched with ECS optimized AMI: amzn2-ami-ecs-hvm-2.0.20230127-x86_64-ebs
  • Install AWS CLI version aws-cli/2.10.0 Python/3.9.11 Linux/4.14.301-224.520.amzn2.x86_64 exe/x86_64.amzn.2 prompt/off on the host
  • Run ecs describe-tasks to get the actual networkBindings of the task container
  • Check the default ephemeral port range for Docker version 1.6.0 and later listed on the instance under /proc/sys/net/ipv4/ip_local_port_range
[ec2-user@ip-xxx]$ cat /proc/sys/net/ipv4/ip_local_port_range
32768   60999
Test cases
Case ECS_DYNAMIC_HOST_PORT_RANGE Port mappings in task def Result Note
1 no user-specified value containerPortRange 8080-8084 hostPortRange 332768-32772 bound to host ports in default range
2 invalid value containerPortRange 8080-8084 hostPortRange 32768-32772 bound to host ports in default range
3 50000-50010 containerPortRange 8080-8084 hostPortRange 50000-50004 bound to host ports in user-specified range
4 50000-50001 containerPortRange 8080-8084 HostConfigError: error retrieving docker port map 5 contiguous host ports are unavailable the user-specified dynamic host port range is not enough to cover required # of host ports
5 no user-specified value containerPort 8080 hostPort 32768 bound to a host port in default range
6 invalid value containerPort 8080 hostPort 32768 bound to a host port in default range
7 50000-50010 containerPort 8080 hostPort 50005 bound to a host port in user-specified range
8 50000-50001 containerPort 8080 HostConfigError: error retrieving docker port map: a host port is unavailable host port 50000 and 50001 were both unavailable
9 no user-specified value containerPort 8080 and hostPort 9000 hostPort 9000 bound to the user-specified host port
10 50000-50010 containerPort 8080 and hostPort 9000 hostPort 9000 bound to the user-specified host port

Description for the changelog

[Enhancement] Support user-specified dynamic host port range for a singular port - part 1

Related PRs

  1. [DHPA] Add GetHostPort() and update unit tests #3570
  2. Remove fallback to Docker for host port ranges assignment #3569
  3. Add new config option for DynamicHostPortRange  #3522

Licensing

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

// If there is no user-specified host port, ECS Agent will find an available host port
// within the given dynamic host port range. And if no host port is available within the range,
// an error will be returned.
logger.Debug("No user-specified host port, ECS Agent will find an available host port within the given dynamic host port range", logger.Fields{
Copy link
Contributor Author

Choose a reason for hiding this comment

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

An example ecs-agent.log for line 2415 - 2427

level=info time=xxx msg="Creating container" task=“xxx” container="containerPort"
level=debug time=xxx msg="No user-specified host port, ECS Agent will find an available host port within the given dynamic host port range" container="containerPort" dynamicHostPortRange="50000-50001"
level=debug time=xxx msg="Port 50000 is unavailable or an error occurred while listening on the local tcp network" module=ephemeral_ports.go
level=debug time=xxx msg="Port 50001 is unavailable or an error occurred while listening on the local tcp network" module=ephemeral_ports.go
level=error time=xxx msg="Unable to find a host port for container within the given dynamic host port range" task=“xxx” container="containerPort" dynamicHostPortRange="50000-50001" error="a host port is unavailable"
level=error time=xxx msg="Error transitioning container" runtimeID="" nextState="CREATED" error="error retrieving docker port map: a host port is unavailable" task=“xxx” container="containerPort"
level=info time=xxx msg="Handling container change event" status="CREATED" task=“xxx” container="containerPort" runtimeID=""
level=warn time=xxx msg="Error creating container; marking its desired status as STOPPED" task=“xxx” container="containerPort" error="error retrieving docker port map: a host port is unavailable"
…
level=debug time=xxx msg="Submitted state change to ECS" eventType="task" eventData="TaskChange: [xxx -> STOPPED, Known Sent: STOPPED, PullStartedAt: xxx, PullStoppedAt: xxx, ExecutionStoppedAt: xxx, container change: xxx containerPort -> STOPPED, Reason HostConfigError: error retrieving docker port map: a host port is unavailable, Known Sent: STOPPED] sent: true"

@chienhanlin chienhanlin marked this pull request as ready for review February 21, 2023 06:51
@chienhanlin chienhanlin requested a review from a team as a code owner February 21, 2023 06:51
@chienhanlin chienhanlin changed the title Upudate dockerPortMap() in task.go with dynamic host port range support - part 1 [DHPA] Upudate dockerPortMap() in task.go with dynamic host port range support - part 1 Feb 21, 2023

// VerifyPortsWithinRange returns true if the actualPortRange is within the expectedPortRange;
// otherwise, returns false.
func VerifyPortsWithinRange(actualPortRange, expectedPortRange string) bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

QQ. Is this method used anywhere other than in task_test.go ? If not, could you move it to the test file?

Copy link
Member

Choose a reason for hiding this comment

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

Also do we verify that a port is within range in our taskdef validation or elsewhere? Where is this same validation done outside of our tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

QQ. Is this method used anywhere other than in task_test.go ? If not, could you move it to the test file?

Also do we verify that a port is within range in our taskdef validation or elsewhere? Where is this same validation done outside of our tests?

This method is used in both agent/utils/ephemeral_ports_test.go and agent/api/task/task_test.go in this PR. As it's using in different packages, I have to make it as a public method.

And as fierlion@ suggested, we should validate a host port or a host port range assigned by ECS Agent after calling getHostPort() or getHostPortRange(). Therefore, I would like to keep VerifyPortsWithinRange() in agent/utils/ephemeral_ports.go, and have a follow up PR to implement host port validations outside of our tests.

Copy link
Contributor Author

@chienhanlin chienhanlin Feb 24, 2023

Choose a reason for hiding this comment

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

agent/api/task/task.go Show resolved Hide resolved

// VerifyPortsWithinRange returns true if the actualPortRange is within the expectedPortRange;
// otherwise, returns false.
func VerifyPortsWithinRange(actualPortRange, expectedPortRange string) bool {
Copy link
Member

Choose a reason for hiding this comment

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

Also do we verify that a port is within range in our taskdef validation or elsewhere? Where is this same validation done outside of our tests?

@chienhanlin chienhanlin merged commit 59fa3b7 into aws:feature/dynamicHostPortAssignment Feb 24, 2023
chienhanlin added a commit that referenced this pull request Mar 2, 2023
1. Add GetHostPort() and update unit tests #3570

2. Upudate dockerPortMap() in task.go with dynamic host port range support part 1 #3584

3. Upudate dockerPortMap() in task.go with dynamic host port range support part 2 #3589

4. Validate the host port/host port range found by ECS Agent before returning it #3589
chienhanlin added a commit to chienhanlin/amazon-ecs-agent that referenced this pull request Mar 4, 2023
1. Add GetHostPort() and update unit tests aws#3570

2. Upudate dockerPortMap() in task.go with dynamic host port range support part 1 aws#3584

3. Upudate dockerPortMap() in task.go with dynamic host port range support part 2 aws#3589

4. Validate the host port/host port range found by ECS Agent before returning it aws#3589

5. Refactor buildPortMapWithSCIngressConfig() in task.go aws#3600
chienhanlin added a commit that referenced this pull request Mar 6, 2023
1. Add GetHostPort() and update unit tests #3570

2. Upudate dockerPortMap() in task.go with dynamic host port range support part 1 #3584

3. Upudate dockerPortMap() in task.go with dynamic host port range support part 2 #3589

4. Validate the host port/host port range found by ECS Agent before returning it #3589

5. Refactor buildPortMapWithSCIngressConfig() in task.go #3600
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.

6 participants