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

How can I access IP from aws_ecs_service using assign_public_ip #3444

Open
eschwartz opened this issue Feb 19, 2018 · 35 comments
Open

How can I access IP from aws_ecs_service using assign_public_ip #3444

eschwartz opened this issue Feb 19, 2018 · 35 comments
Labels
enhancement Requests to existing resources that expand the functionality or scope. service/ecs Issues and PRs that pertain to the ecs service.

Comments

@eschwartz
Copy link

I'm creating a ECS service with launch_type = "FARGATE" and assign_public_ip = true.

I would like to be able to access the public IP of the service after it's created,
so that I could create a Route53 record. It doesn't look like there is any way currently to accomplish this.

Could we add a public_ip attribute to the aws_ecs_service resource?

Thanks!

Terraform Version

Terraform v0.11.3
+ provider.aws v1.9.0

Affected Resource(s)

  • aws_ecs_service

Terraform Configuration Files

resource "aws_ecs_service" "mysvc" {
  name = "my_svc"
  cluster = "${aws_ecs_cluster.ecs_cluster.arn}"
  task_definition = "${aws_ecs_task_definition.my_task.arn}"
  desired_count = 1
  launch_type = "FARGATE"

  network_configuration {
    subnets = ["${local.subnet_id}"]
    assign_public_ip = true
    security_groups = ["${aws_security_group.my_sg.id}"]
  }
}

Debug Output

N/A

Panic Output

N/A

Expected Behavior

N/A

Actual Behavior

N/A

Steps to Reproduce

N/A

Important Factoids

N/A

References

@loivis
Copy link
Contributor

loivis commented Feb 19, 2018

  1. Information of public ip is not included in DescribeServices api response. We might need some custom logic to export the attribute.

  2. Curious about your user case. The public ip seems like dynamic. Do you plan another trigger to update dns record on each ecs task change?

@eschwartz
Copy link
Author

eschwartz commented Feb 19, 2018

Curious about your user case. The public ip seems like dynamic. Do you plan another trigger to update dns record on each ecs task change?

I'm trying to create a route53 record, to point at the public IP. So yes, whenever the ECS task changes, it would update the Route53 record

@bflad bflad added enhancement Requests to existing resources that expand the functionality or scope. service/ecs Issues and PRs that pertain to the ecs service. labels Feb 21, 2018
@redbelow
Copy link

redbelow commented Feb 26, 2018

would be nice to have private_ip of host as well, i need that for setting an environment variable (local host ip) to a container.

@dot1q
Copy link

dot1q commented Jun 12, 2018

+1

@unacceptable
Copy link
Contributor

unacceptable commented Aug 14, 2018

I am running into a very similar issue when attempting to create an aws_lb_target_group_attachment resource. Perhaps I am missing something there though.

EDIT:

I was missing the fact that with Fargate you don't have to specify aws_lb_target_group_attachment because it is specified in aws_ecs_service (facepalm).

As for this original issue:

You can have a Fargate aws_ecs_service with multiple containers so I am not sure that public_ip would be a valid attribute. Even if there was a public_ips attribute added, then there would still be the issue that everytime a container was deprovisioned you would have to do another terraform apply so that your new container was recognized by route53.

Here are a couple of solutions though:

  1. Create an alb and make an alias record in route53. This is what I am doing. (Not ideal if you are worried about the cost for one container, but then again why use Fargate if that's the case).
  2. Create a lambda function to get the container's IP address and update route53 via boto3. (Ideal for cost-effective one-off solutions.)

Let me know if you need any help with the above solutions, and I would be happy to assist.

@r1w1s1
Copy link

r1w1s1 commented Jan 20, 2019

Curious about your user case. The public ip seems like dynamic. Do you plan another trigger to update dns record on each ecs task change?

I'm trying to create a route53 record, to point at the public IP. So yes, whenever the ECS task changes, it would update the Route53 record

nice! +1

@dbogen
Copy link

dbogen commented Apr 18, 2019

This would be really useful when creating a running task in Fargate via a service. Was hoping that maybe there would be a data source associated with the ECS cluster and the associated tasks in it that would provide this information. But there is not.

@lostbearlabs
Copy link

I have a related use case. I am trying to deploy a grafana instance in Fargate and then use the terraform grafana provider to configure it. I need a way to pass the IP address of the instance to the provider so that the provider will know what it's configuring.

@fardin01
Copy link

No news about this?

@tjtaill
Copy link

tjtaill commented Mar 27, 2020

Not sure it helps but I am looking to do this as well and my workaround plan
is to use this
https://github.com/matti/terraform-shell-resource

with ecs-cli ps command to get the ip into terraform https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cmd-ecs-cli-ps.html

@pixelicous
Copy link

Having the same issue with fargate.

Whenever I update ECS or Task Definition (SSM parameters and such) I need to point my routing to this new public ip

however as @loivis its on Amazon's side

@scott-doyland-burrows
Copy link
Contributor

scott-doyland-burrows commented Jul 27, 2020

Just wanted to add that I would also like to get the public IP from the task. In my case it doesn't matter that the IP changes.

Via terraform, I spin up the task, spin up a VM, and then there is an app on the VM that has to connect to the tasks public IP.

Both the task and VM are brought up/down at the same time.

I want to just pass the public IP directly to my terraform VM code.

@phil-smarsh
Copy link

would be nice to have private_ip of host as well, i need that for setting an environment variable (local host ip) to a container.

Is there any update on how i can access the private ip?
I'm trying to send custom metrics to a datadog sidecar and can't seem to figure out how to get the private ip once the task definition is running.

@scott-doyland-burrows
Copy link
Contributor

Hi, I worked around the issue of obtaining the public and private IPs by:

  1. Define a new security group for the ECS service
  2. Define the ECS service and opt to put it in the new security group
  3. The security group will automatically have a network interface attached to it which is owned by the service
  4. Pull out the private and public IPs from the network interface

It is a bit of a faff, you have to make sure you only have the single ECS service using the security group, otherwise you have multiple network interfaces and you cannot determine which one is for the ECS service via terraform.

It takes a few seconds for the network interface to be created. Terraform reports the ECS service resource creation is complete even though the network interface will not yet be available. So you need to set a "sleep" step in terraform (I waited 30 secs) before querying the network interface.

Also (and I cannot remember the exact issue as it was a while ago I tested this), if your ECS service has issues and keeps going up and down, it will keep getting a new public IP each time (and IIRC it deletes and redefines the network interface each time). If you decide to run a terraform apply/destroy at this point, then depending on timing, terraform can get confused as the interface it thinks is there, will not be.

I have the code I used, you are welcome to it, but the code/workaround isn't really suitable for production use due to the issue of changing network interfaces and IPs when the ECS service restarts. But then that's an AWS "feature".

Scott

@phil-smarsh
Copy link

Thank you @scott-doyland-burrows !
It turns out that my issue wasn't really an issue and a lapse in my understanding of networking in Fargate.

With multiple containers in the same Fargate task, you can use localhost/127.0.0.1 to communicate between containers.

@ahmadRMusa
Copy link

Hi, I worked around the issue of obtaining the public and private IPs by:

  1. Define a new security group for the ECS service
  2. Define the ECS service and opt to put it in the new security group
  3. The security group will automatically have a network interface attached to it which is owned by the service
  4. Pull out the private and public IPs from the network interface

It is a bit of a faff, you have to make sure you only have the single ECS service using the security group, otherwise you have multiple network interfaces and you cannot determine which one is for the ECS service via terraform.

It takes a few seconds for the network interface to be created. Terraform reports the ECS service resource creation is complete even though the network interface will not yet be available. So you need to set a "sleep" step in terraform (I waited 30 secs) before querying the network interface.

Also (and I cannot remember the exact issue as it was a while ago I tested this), if your ECS service has issues and keeps going up and down, it will keep getting a new public IP each time (and IIRC it deletes and redefines the network interface each time). If you decide to run a terraform apply/destroy at this point, then depending on timing, terraform can get confused as the interface it thinks is there, will not be.

I have the code I used, you are welcome to it, but the code/workaround isn't really suitable for production use due to the issue of changing network interfaces and IPs when the ECS service restarts. But then that's an AWS "feature".

Scott

@scott-doyland-burrows I am new to Terraform, can you put a sample code for pulling he network interface IPs?

@pandayankush
Copy link

Hi, I have the same requirement. I need to extract the Private Ip of the Fargate task and need to pass the same in the container template as a parameter to the COMMAND so that the application can be run on that IP. I am trying the below command to run consul but it asks for IP.

"command": [
"consul agent -server -data-dir=/consul/data -bootstrap -ui -client=0.0.0.0 -bind=0.0.0.0"
],

Instead of 0.0.0.0 need to pass the private IP address, and couldn't find any solution yet.

@scott-doyland-burrows
Copy link
Contributor

scott-doyland-burrows commented Nov 10, 2020

It was a few months ago so I cannot remember exactly how this works - as I have not had to touch it since then...

Create a security group (not shown below - but I did this via a module).

Create your ecs service (not shown below - but I did this via a module) and put in in the security group.

Then wait 30 seconds (required as it takes a few secs for the NIC to be defined). The NIC will be tied to a security group.

Then call the first data block below, this will get a list of all NICs in the security group (you must have just ONE NIC per security group for this to work).
So you end up with one NIC ID.

The second data block gets the attributes for the ID of the NIC from the first data block.

The first output block displays the private IP of the NIC.

The second output block displays the public IP of the NIC.

resource "time_sleep" "sigserv_30_seconds" {
  depends_on = [module.ecsservicesigserv]

  create_duration = "30s"
}

data "aws_network_interfaces" "networkinterfacesigserv" {
  depends_on = [time_sleep.sigserv_30_seconds]

  filter {
    name   = "group-id"
    values = [module.securitygroupsigserv.security_group_id]
  }
}

data "aws_network_interface" "networkinterfacesigserv" {
  id = join(",", data.aws_network_interfaces.networkinterfacesigserv.ids)
}

output "ecs_privateipv4_sigserv" {
  value = data.aws_network_interface.networkinterfacesigserv.private_ip
}

output "ecs_publicipv4_sigserv" {
  value = join(",", data.aws_network_interface.networkinterfacesigserv[*].association[0].public_ip)
}

The problem you will have is that if the ecs service dies and restarts itself, it will get a different public IP (IIRC) so you would need to manage this by rerunning terraform, so I think you'd need a terraform apply to force terraform to pick up the new IP in terraform output.

I sort of gave up with this in the end as it was all a bit messy, it was fine for just some dev work, but I wouldn't suggest you use it for more than that.

@timhaley94
Copy link

I have this problem and am using @scott-doyland-burrows solution.

My only addition is that instead of using a 30 second timeout, I am listing that the aws_network_interfaces data source depends_on my aws_ecs_service resource, and I have wait_for_steady_state set to true on my aws_ecs_service resource.

I believe this requires that the tasks reach a steady state, and thus be assigned eni's, before this data source is executed.

@akramfstg
Copy link

akramfstg commented Aug 3, 2021

It was a few months ago so I cannot remember exactly how this works - as I have not had to touch it since then...

Create a security group (not shown below - but I did this via a module).

Create your ecs service (not shown below - but I did this via a module) and put in in the security group.

Then wait 30 seconds (required as it takes a few secs for the NIC to be defined). The NIC will be tied to a security group.

Then call the first data block below, this will get a list of all NICs in the security group (you must have just ONE NIC per security group for this to work).
So you end up with one NIC ID.

The second data block gets the attributes for the ID of the NIC from the first data block.

The first output block displays the private IP of the NIC.

The second output block displays the public IP of the NIC.

resource "time_sleep" "sigserv_30_seconds" {
  depends_on = [module.ecsservicesigserv]

  create_duration = "30s"
}

data "aws_network_interfaces" "networkinterfacesigserv" {
  depends_on = [time_sleep.sigserv_30_seconds]

  filter {
    name   = "group-id"
    values = [module.securitygroupsigserv.security_group_id]
  }
}

data "aws_network_interface" "networkinterfacesigserv" {
  id = join(",", data.aws_network_interfaces.networkinterfacesigserv.ids)
}

output "ecs_privateipv4_sigserv" {
  value = data.aws_network_interface.networkinterfacesigserv.private_ip
}

output "ecs_publicipv4_sigserv" {
  value = join(",", data.aws_network_interface.networkinterfacesigserv[*].association[0].public_ip)
}

The problem you will have is that if the ecs service dies and restarts itself, it will get a different public IP (IIRC) so you would need to manage this by rerunning terraform, so I think you'd need a terraform apply to force terraform to pick up the new IP in terraform output.

I sort of gave up with this in the end as it was all a bit messy, it was fine for just some dev work, but I wouldn't suggest you use it for more than that.

Hi Scott,
I like the way you managed to get the public ip but it did not work for me unfortunately. Getting the following error despite both network interfaces are well created there in both availability zones:

 Error: InvalidNetworkInterfaceID.NotFound: The networkInterface ID 'eni-0000e25458ba6b1a6,eni-0b4bb8cac218221ab' does not exist
│       status code: 400, request id: 172695fa-5f7c-4eea-acb4-977893ed695d
│ 
│   with data.aws_network_interface.nic_sg_ecs_service_test[0],
│   on transcoder.tf line 110, in data "aws_network_interface" "nic_sg_ecs_service_test":
│  110: data "aws_network_interface" "nic_sg_ecs_service_test" {

Woderning what is the issue here!

@scott-doyland-burrows
Copy link
Contributor

Hi,
The error suggests you are looking for an interface called eni-0000e25458ba6b1a6,eni-0b4bb8cac218221ab

So it looks like it finds two interfaces - and my original code will only work with one.

Scott

@maximatt
Copy link

maximatt commented Jan 11, 2022

Hi... I'm a newbie in terraform, but quickly I run into this issue and this is my recently workaround, that for now is "successfully enough" to me and perhaps could be useful to someone.

I have two services (service_1 and service_2) and service_2 depends from service_1 to retrieve some resources, where service_2 is defined like

resource "aws_ecs_task_definition" "aws-ecs-service_2-task" {
  :
  container_definitions = templatefile("${path.module}/${var.task_definitions}/service_2.json", {
    :
    service_1_ip        = file("${aws_ecs_service.aws-ecs-service_1.name}.ip")
  })
}

resource "aws_ecs_service" "aws-ecs-service_2" {
  :
  wait_for_steady_state= true
  depends_on = [aws_ecs_service.aws-ecs-service_1]
}

resource "null_resource" "get_ip" {
  provisioner "local-exec" {
    command     = "chmod +x ./get_ip.sh; ./get_ip.sh ${aws_ecs_cluster.aws-ecs-cluster.name} ${aws_ecs_service.aws-ecs-service_1.name}"
    interpreter = ["bash", "-c"]
  }
}

So, the script get_ip.sh has the following content (I didnt try if its possible return the value to variable service_1_ip in a direct manner):

task_arn=$(aws ecs list-tasks --cluster $1 --service-name $2 --query 'taskArns[0]' --output text)
task_details=$(aws ecs describe-tasks --cluster $1 --task ${task_arn} --query 'tasks[0].attachments[0].details')
service_ip=$(echo $task_details | jq -r -c '.[] | select(.name=="privateIPv4Address").value')
echo -n $service_ip > $2.ip

To update service_2

terraform plan -input=false -replace=aws_ecs_task_definition.aws-ecs-service_2-task
terraform apply -input=false -auto-approve

In resume thats all... and I'm sure that this has a lot of issues and limitations (that I will be discovering time by time, but for now..."works")

Regards

@jimsmith
Copy link

I stumbled across this today and found it disappointing that more than 5 years this is still a thing :(

The use case is a single fargate container running so for now #3444 (comment) has given me the IP addresses required.

I hope to see this no longer a thing soon...

@tatliHU
Copy link

tatliHU commented Sep 17, 2023

I found a better solution using tags:

  1. Set enable_ecs_managed_tags = true in your aws_ecs_service. This will tag the network interface with
    aws:ecs:serviceName = <aws_ecs_service.yourservice.name>
    aws:ecs:clusterName = <aws_ecs_service.yourservice.cluster>
  2. Use tag filter to identify the network interface
data "aws_network_interface" "interface_tags" {
  filter {
    name   = "tag:aws:ecs:serviceName"
    values = ["my_service_name"]
  }
}

output "public_ip" {
    value = data.aws_network_interface.interface_tags.association[0].public_ip
}

This is a robust approach as there can't be multiple network interfaces listed for your use-case.

@dverzolla
Copy link

I found a better solution using tags:

1. Set `enable_ecs_managed_tags = true` in your aws_ecs_service. This will tag the network interface with
   aws:ecs:serviceName = <aws_ecs_service.yourservice.name>
   aws:ecs:clusterName  = <aws_ecs_service.yourservice.cluster>

2. Use tag filter to identify the network interface
data "aws_network_interface" "interface_tags" {
  filter {
    name   = "tag:aws:ecs:serviceName"
    values = ["my_service_name"]
  }
}

output "public_ip" {
    value = data.aws_network_interface.interface_tags.association[0].public_ip
}

This is a robust approach as there can't be multiple network interfaces listed for your use-case.

This is the best approach.
I've set wait_for_steady_state = true and output the value properly.

To check if the tag was right propagated.
aws ec2 describe-network-interfaces --filters Name=tag:aws:ecs:serviceName,Values=service_name

@egoriwe999
Copy link

@tatliHU
Copy link

tatliHU commented Mar 21, 2024

My solution with ECS Service + Terraform: https://www.linkedin.com/pulse/how-receive-public-ipv4-from-aws-ecs-via-terraform-egor-salo-l9a9e

This is almost exactly my solution which is right above your comment. I'm happy that people use it but if you create a blog post about it on LinkedIn, you should tag me there.

@egoriwe999
Copy link

My solution with ECS Service + Terraform: https://www.linkedin.com/pulse/how-receive-public-ipv4-from-aws-ecs-via-terraform-egor-salo-l9a9e

This is almost exactly my solution which is right above your comment. I'm happy that people use it but if you create a blog post about it on LinkedIn, you should tag me there.

oh, really, our solutions are the same, but I scrolled through your comment and found it manually(

@narimantos
Copy link

I found a better solution using tags:

1. Set `enable_ecs_managed_tags = true` in your aws_ecs_service. This will tag the network interface with
   aws:ecs:serviceName = <aws_ecs_service.yourservice.name>
   aws:ecs:clusterName  = <aws_ecs_service.yourservice.cluster>

2. Use tag filter to identify the network interface
data "aws_network_interface" "interface_tags" {
  filter {
    name   = "tag:aws:ecs:serviceName"
    values = ["my_service_name"]
  }
}

output "public_ip" {
    value = data.aws_network_interface.interface_tags.association[0].public_ip
}

This is a robust approach as there can't be multiple network interfaces listed for your use-case.

This is the best approach. I've set wait_for_steady_state = true and output the value properly.

To check if the tag was right propagated. aws ec2 describe-network-interfaces --filters > Name=tag:aws:ecs:serviceName,Values=service_name

This won't help if the fargate container crashes and will assign a new ip?

@celesteking
Copy link

robust approach as there can't be multiple network interfaces

@tatliHU, Doesn't work when there are multiple tasks and thus multiple interfaces. Is there other solution based on this one?

@tatliHU
Copy link

tatliHU commented Apr 20, 2024

Hi @celesteking,
it sure works for multiple different tasks as they will have different aws:ecs:serviceName tags.
Now if you want to scale up the same task (i.e using the desired_count attribute) you can do two things:

  1. use the same approach as the filter will list all the network interfaces. since they are the same app you can use any of the interfaces
  2. use a loadbalancer (check out my solution https://github.com/tatliHU/webservers/blob/main/modules/ecs/main.tf)

@celesteking
Copy link

celesteking commented Apr 21, 2024

use the same approach as the filter will list all the network interfaces. since they are the same app you can use any of the interfaces

Doesn't work: │ Error: reading EC2 Network Interface: too many results: wanted 1, got 2

data "aws_network_interface" "netiface" {
  filter {
    name   = "tag:aws:ecs:serviceName"
    values = ["my-runner"]
  }
output "iface_public_ip" {
  description = "Public IP address of service"
  value = [(length(data.aws_network_interface.netiface) > 0) ? data.aws_network_interface.netiface.*.association[0].public_ip : "NONE"]
  }

@tatliHU
Copy link

tatliHU commented Apr 21, 2024

@celesteking use aws_network_interfaces instead of aws_network_interface

@celesteking
Copy link

Max useful output it can return is the list of interface names, but I need per-interface data like private and public IPs. I'm new to Terraform.
Something like:
ifaces = { "my-runner-service": {"eni-123": {public: "1.2.3.4", private: "10.1.1.1"}, "eni-234": {public: "2.3.4.5", private: "10.2.2.2"}}}

@celesteking
Copy link

data "aws_network_interfaces" "interface_tags" {
  filter {
    name   = "tag:aws:ecs:serviceName"
    values = ["my-runner"]
  }
}

data "aws_network_interface" "netiface" {
  for_each = toset(data.aws_network_interfaces.interface_tags.ids)
  id = each.key
}

output "iface_ip_map" {
  description = "IP addresses of XX service"
  value       = { for k,v in data.aws_network_interface.netiface: k => {pub: v.association[0].public_ip, priv: v.private_ip }}
}

But that doesn't show which service has what task with what IP[s].

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Requests to existing resources that expand the functionality or scope. service/ecs Issues and PRs that pertain to the ecs service.
Projects
None yet
Development

No branches or pull requests