-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Ability to select private/public IP for specific task/port #646
Comments
Hey @skozin, Thanks for the input. I like the idea of a netmask and it is a valid way of constraining your task to the correct network. The one major draw back to this approach is that it forces the end user (not the operator) to know the network configuration. What I would like is to allow operators to set up arbitrary key/value metadata for network interfaces and then end users can use a simpler constraint. For example an operator could tag a network interface as
Obviously syntax was just something I came up with right now but does this seem reasonable to you? With a more generic constraint system (like that for jobs) we could also support net mask and other constraints. |
Hi @dadgar, Thanks for the prompt response =) Yes, this seems perfectly reasonable to me. Your proposal is more elegant and flexible than mine, because it yields more readable/maintainable job definitions and, at the same time, allows for greater precision, e.g. it is possible to bind some task to private interface on a set of machines even if those machines have private interfaces pointing into different private networks. But, in order for this to cover the same cases, the implementation should meet these two requirements:
So, if some port matches interfaces I'm not sure I understand the new port "http" {
constraint {
static = 1234
interface {
availability = "public"
}
}
} Or, maybe, it would be better to not introduce new inner block that would always be required? port "http" {
static = 1234
interface {
availability = "public"
}
} Another question is about how to specify metadata for network interfaces. Do I get it right that it would go into client {
network {
interface "eth0" {
availability = "public"
}
interface "eth1" {
availability = "private"
}
interface "docker0" {
is_docker_bridge = true
}
}
} If that's correct, then client {
network {
interface "eth1" {
# Special meta key that prohibits fingerprinting this interface
disable = true
}
interface "eth0" {
availability = "public"
}
} And the last one: I think it would be useful to support negative meta constraints, e.g.: # Client config
client {
network {
interface "eth0" {
availability = "public"
}
interface "eth1" {
availability = "private"
}
interface "lo" {
availability = "local"
}
}
}
# Task config
# ...
port "http" {
static = 1234
interface {
# would listen on "eth1" and "lo"
not {
availability = "public"
}
}
} Or, maybe, regex constraints? port "http" {
static = 1234
interface {
availability = "/^(?!public)./"
# or:
# availability = "/^(private|local)$/"
}
} What do you think? |
Yeah the constraints should have the follow expressional power of our other constraints: https://www.nomadproject.io/docs/jobspec/index.html#attribute. As for binding to all interfaces/ips, I think that should be a configurable thing. There are cases in which you only need to bind to a single IP/interface. |
Ah, completely missed the constraints syntax, sorry. So, the port configuration would look like this, right? port "http" {
static = 1234
constraint {
# "interface." is a prefix for all interface-related attributes
# "availability" is a user-defined attribute; a set of system-defined attributes
# may be added in the future, e.g. "netmask-cidr", "is-default-route", etc.
attribute = "interface.availability"
value = "public"
}
# there may be multiple constraints applied to the same port
} Regarding binding to all interfaces that match port's constraints: of course there are cases when you need to bind to a single interface; I would even say that these cases are the most common ones. But wouldn't constraints system already enable you to do that? For example, if there are several interfaces marked with As for the case when an interface has several IPs, honestly I have no idea how this can be made configurable without requiring the job author to specify the exact IP or netmask. Of course you can add an option to bind to only one (first/random) IP of an interface, but that would be a fairly useless option, because now you have no idea what address the service will actually listen on, and whether this address would be routable from some network. The only generic and useful options I can think about is |
Yeah more or less! And I think that is a fairly clean syntax. The constraint system would let you do it but then you are breaking Nomads abstraction a bit by targeting a single IP/interface in particular when you just care about certain attributes. So I think there can be a |
@dadgar, I know that IP is being passed to the task and is available via Consul, but, in some cases, you really need to listen on a specific ip. The good example is DigitalOcean's floating IPs. Every instance in DO has two IPs attached to its I'm not particularly against I guess that choosing first/random interface/IP would be useless and confusing in such cases, especially if Another reason for listening on all interfaces by default is that it is the behavior that most developers are used to. For example, in Node.js and many other APIs, including Go, the socket will be bound to all interfaces if you don't explicitly pass the interface. It would be intuitive if Nomad exhibited the same behavior: in the absence of constraints, the port would be bound to all interfaces. And when you add constraints, you just narrow the set of chosen interfaces (and thus IPs). This is a really simple mental model. That said, I understand that listening on multiple, but not all, interfaces in the case when driver doesn't support port mapping makes no sense, because you usually can't tell your program/API to listen on a set of IPs. Usually you have just two choices: either bind to all IPs, or to some particular IP. So, maybe the following would be a good compromise:
|
Yeah I don't think we disagree very much. I think we can randomly select an IP and that will be the desired behavior because the job should have constraints that logically match the desired attributes of the interface or set of IPs. So within that constraint group any IP we choose should be fine. For your DO example, there should be a way for an operator to mark that this CIDR block is of type floating/regular. And if an application cares they can put a constraint. As for the environment variable I think we made a mistake by injecting only one IP variable. I think moving forward it will be something like |
Yup, passing port and IP together in a single variable would be a good choice too. Do I understand correctly that But I think that it would be nice to additionally pass IP and port in different variables, to avoid parsing when you need IP and port separately, like in e.g. Node's http.listen API. That way, the task would get three variables for each port:
Regarding random selection, I would suggest printing a warning anyway, because random IP selection, imho, is not what someone usually wants when binding a socket, and with great probability is a result of a mistake. |
Hi @dadgar, here are some points by @DanielDent against using interface names: #223 (comment)
I can say that I agree with all of them, and that the cases described in that comment are the real ones and occur fairly frequently in production clusters. In our infrastructure, using interface names will not be a problem because we provision all machines with Ansible, so we can just implement additional logic for configuring Nomad, but not every setup allows this, and anyway it brings additional unneeded complexity. I understand that CIDR notation is too low-level and infrastructure-specific, but, as you can see, interface names may be even more so. What if we allowed infra operators to attach metadata to networks instead of interfaces? This way, job authors would still be able to use high-level attributes to specify port constraints. What do you think? |
Yeah I think in practice it will be something like that as interfaces can have many IPs |
Hi, is there any update regarding this issue? Will Nomad support this feature fairly soon? |
@tugbabodrumlu Yeah it should be possible to use multiple network interfaces on Nomad fairly soon. |
Any updates on this? |
Related: moby/moby#17750 It seems you can use |
Sorry no update yet, priorities got shuffled a bit |
We are also moving forward docker driver and need to support multiple interfaces per jobs or tasks. Is there any workaround yet? @dadgar do you have any milestone plan for this feature? |
@kaskavalci It will likely be in a 0.6.X release! Timeline on that is early half on next year. |
Would love to see this as well! |
It would be best to abstract iterfaces into client configuration instead. For example put following in job description:
and in client configuration
|
We have a similar case where every Docker container is assigned an ipv6 address (using Docker's So what we need is either: I thought b) would be easy using Docker's Is our use case sufficiently covered here, or should we open a separate feature request for this? |
Just to chime in, not being able to have nomad select ipv6 instead of ipv4 address for service registration in consul shall be considered a bug. |
The ability to add additional networks and explicitly specify ip addresses when scheduling Docker containers is something we currently would require for our direct ip / routing of private, public unicast and anycast addressing to containers, not quite ready for ipv6 but would be nice to see that added as requested above. |
Hi, Is there any update on this? Thanks |
@darren-west No update yet! |
Hi, also interested in being able to select specific IP addresses for specific services. Thanks! |
@yishan-lin Any results of the discuss? |
@luckyraul On the roadmap! Coming soon. |
Another use case: I am just starting out with the Hashicorp stack, and I'm running Nomad (+Consul) on an untrusted network. I have a WireGuard peer-to-peer VPN setup, and I want jobs to map ports only to the VPN network interface. Then I can expose them properly through something like Traefik to manage access control. |
Thanks all for the input and appreciate deeply the patience from the community. Please feel free to writeup any of your targeted use-cases in thread. We’re currently in design phase and are targeting this for our next major release in Nomad 0.12. While we’re confident in our understanding of the core problem from earlier feedback, all voices are still welcome. Helps us cover and consider all potential angles and edge cases. 🙂 |
suggest having an option to allow binding on 0.0.0.0, and let users to configure firewall. This is an easy and flexible way and should satisfy most of the use cases. |
My current workaround for this is specifying a macvlan Docker network as a |
My use case is that each server has localhost, a public use IP and a internal use IP. The localhost IP is IPv6 whereas the other IPs are IPv4. At a bare minimum I would like to restrict some services to only bind to the local IP, others that are only for server to server communications I would bind to the internal use IPs and services such as proxy servers I would bind to the external IPs. In this case it would be easiest to allow configuration on each server to create a named group of IPs and/or network interfaces to be used. |
Hi Nomad friends! I'm currently working on the design for this feature and wanted to solicit some feedback since there has been alot of participants on this issue. You can either reply to this thread or feel free to reach out to me directly at nethier@hashicorp.com Thanks! The TL;DR is to support fingerprinting multiple interfaces and addresses on Nomad clients, then expose a couple ways to select which of those interfaces a port should be allocated for. From the perspective of the jobspec the idea is to support a new field in the port block
This example says to allocate 2 dynamic ports. The This work without bridge networking and port forwarding as well. Nomad just expects the application to honor the NOMAD_IP_label|NOMAD_ADDR_label environment variables so the application binds to the correct ip and port. To further enhance the UX of this I want to introduce a new client configuration to alias a node’s network to a string name. Example:
With this configuration, the host node networks would get fingerprinted with the corresponding aliases. Then the job author could use the alias–in the above example, "public" or "internal"– instead of a cidr notation range for the If no With this approach there are no changes to how a service advertises the address to consul. It will use the address associated with the given port. |
Looks good, my main focus is docker, would this work as I imagine, binding a docker container to a specific IP? |
@dariusj18 yeah the associated IP will get passed in with the port spec. |
For our on premise network we are using nomad to deploy our applications.
Behind the firewall we are using keepalived to masquerade public to a
floating internal ip.
To use nomad to deploy HAProxy as „ingress controller“ we would need the
option to bind haproxy to the host and the keepalive virtual ip.
For that to work we need the support to bind haproxy to 0.0.0.0:80/443.
Thanks,
Robert
Nick Ethier <notifications@github.com> schrieb am Fr. 15. Mai 2020 um 20:09:
… @dariusj18 <https://github.com/dariusj18> yeah the associated IP will get
passed in with the port spec.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#646 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABCZH2IDMBAWCGFW744ULWLRRWANBANCNFSM4BX4EILA>
.
|
I like how your handling the IP selection using CIDR. How about the reverse where the driver tells nomad the ip's it's using? Currently the driver can only expose a single IP. We only use the host network for prometheus exporters. All our containers have their IP's assigned by the container program. We use podman with CNI where all of our containers have a private IP and for services needing to be accessed from the internet are also assigned an additional public IP. I can imagine docker being similar when using a docker network plugin such as weave. We use raw_exec with a wrapper around podman that registers services/checks with consul because multiple service stanza's per task cannot specify which IP the service is for. |
I like your suggestion. Please remember to support IPv6 😉 |
@rkno82 Yeah this is an edge case I thought about initially but forgot to write down so thankyou! I'll be sure to cover it as well. @daledude Yeah I explored this a bit in my initial discovery. Unfortunately it is not something I'm working to include in this initial design but is something we'll think about in iterative improvements. I tried to narrow the scope to portmapping/multiple host networks and address multiple interfaces/networks inside the netns later. After some discussions with @schmichael I think I'm going to drop the support for using a cidr in the port block and instead require any host network to be defined in the client config (other than the current default one). This lines up a bit more with host volumes. You can't just mount any filesystem path on the host and I think we need the same safe guards around binding to an address. I've changes the proposed client configuration to be:
In the |
@nickethier , in case I wasn't clear. I'm talking about possibly changing drivers.DriverNetwork.IP from string to []string the driver (i.e. docker) fills in within it's StartTask(). No need to deal with netns's. |
Will this issue be solved with "Multi-Interface Networking" mentioned in https://www.hashicorp.com/blog/announcing-hashicorp-nomad-0-12-beta/ ? |
@rkno82 correct! |
@yishan-lin , @nickethier can you help me? Is there any documentation how to configure my use-case? I've watched https://digital.hashiconf.com/post-live/nomad-networking-demystified and tried to reproduce with "nomad -dev" mode, but cannot get the "redis-task" of the example.job to bind to 0.0.0.0. configclient config
example.job with adjustment
logs
|
Is it possible that this is currently not working at all for localhost address? It seems to ignore all my attempts to bind a job to the localhost (127.0.0.1)? |
Hey @rkno82 @iBoMbY you are correct the logic is that the mapped ports now have a destination address rule to match the host_network IP. I added a PR to augment this logic when not using host_networks. See #8321 If this doesn't solve your usecase would you mind explaining a bit what you expect to happen and I'll dive in. Thanks! |
Well, the idea would be to have services running behind a reverse proxy on the machine. So there would be no need for them to be accessible directly from the outside, plus the connection between the proxy and the services should be faster when using the loopback device. |
Folks with 0.12 GA with support for multiple host network interfaces I'm going to close this one out. There was a lot of discussion on this *checks notes* 4 year old issue so please don't hesitate to open a new issue if this feature doesn't cover your use case. Thank you to everyone for contributing to the discussion and design of this feature. If you have any questions head on over to our discuss forum and please open an issue if you run into any bugs. Cheers! |
I'm going to lock this issue because it has been closed for 120 days ⏳. This helps our maintainers find and focus on the active issues. |
Extracted from #209.
We use Nomad with Docker driver to operate cluster of machines. Some of them have both public and private interfaces. These two-NIC machines run internal services that need to listen only on a private interface, as well as public services, which should listen on a public interface.
So we need a way of specifying whether some task should listen on public or private IP.
I think this can be generalized to the ability to specify subnet mask for a specific port:
This would be the most flexible solution that would cover most, if not all, cases. For example, to listen on all interfaces, as requested in #209, you would just pass
0.0.0.0/0
netmask that matches all possible IPs. Maybe it makes sense to make this netmask the default, i.e. bind to all interfaces if no netmask is specified for a port.I think this is really important feature, because its lack prevents people from running Nomad in VPC (virtual private cloud) environments, like Amazon VPC, Google Cloud Platform with subnetworks, OVH Dedicated Cloud and many others, as well as any other environments where some machines are connected to more than one network.
Another solution is to allow specifying interface name(s), like
eth0
, but that wouldn't work in our case because:Example for point 1: assume that I want to start some task on all machines in the cluster, and that I want this task to listen only on private interface to prevent exposing it to the outer world. Consul agent is a nice example of such service.
Now, some machines in the cluster are connected to both public and private networks, and have two NICs:
eth0
corresponds to public network, say,162.243.197.49/24
;eth1
corresponds to my private network10.10.0.1/24
.But majority of machines are only connected to a private net, and have only one NIC:
eth0
corresponds to the private net10.10.0.1/24
.This is fairly typical setup in VPC environments.
You can see that it would be impossible to constrain my service only to private subnet by specifying interface name, because
eth0
corresponds to different networks on different machines, andeth1
is even missing on some machines.The text was updated successfully, but these errors were encountered: