-
Notifications
You must be signed in to change notification settings - Fork 25
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
fix: Use a FQDN for metadata.google.internal #117
Conversation
Minimize lookups by not expanding search path when resolving metadata.google.internal
It does result in slightly odd looking requests, but I think it's [appending the trailing dot] the correct behavior, otherwise, the resolver will first try metadata.google.internal prepended to every domain in the search path, e.g. (from tcpdump):
The quad-A lookups also multiply the # of total lookups. Ideally, the result could be read once and then cached in-memory, but this should be a big improvement. In normal cases, this might not be so bad, but lots of apps on lots of pods running on GKE with lots of domains in the (default, not trivially changeable) search path results in a pretty heavy load on kube-dns / dnsmasq, which as of now, has no caching layer. |
Thanks for the patience folks. Full disclosure - we are waiting on a dns lookup fix to land in Google Cloud Functions before we can merge this in. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to wait for GCF fix to land
We did this elsewhere: googleapis/google-cloud-node#2214, and we had to revert it because of this: googleapis/google-cloud-node#2249. |
After reviewing the above, I am forced to conclude that software is terrible. 😭 Let's just use an ip address and avoid DNS resolution altogether. I believe the python client libs already use ips. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handing this over to someone else to approve :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is going to be a bit more complicated, unfortunately. I should have thought of this earlier, but given the test:
const request = require('request');
const IP = '169.254.169.254';
const host = process.argv[2] || IP;
const url = `http://${host}/computeMetadata/v1/instance/id`;
console.log('requesting', url);
request(url, (err, res) => {
console.dir(err, res);
});
Consider the output:
❯ time node ip.js metadata.google.internal
requesting http://metadata.google.internal/computeMetadata/v1/instance/id
{ Error: getaddrinfo ENOTFOUND metadata.google.internal metadata.google.internal:80
at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:58:26)
errno: 'ENOTFOUND',
code: 'ENOTFOUND',
syscall: 'getaddrinfo',
hostname: 'metadata.google.internal',
host: 'metadata.google.internal',
port: 80 }
node ip.js metadata.google.internal 0.22s user 0.05s system 97% cpu 0.272 total
~/tmp/foo
❯ time node ip.js
requesting http://169.254.169.254/computeMetadata/v1/instance/id
{ Error: connect ETIMEDOUT 169.254.169.254:80
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1117:14)
errno: 'ETIMEDOUT',
code: 'ETIMEDOUT',
syscall: 'connect',
address: '169.254.169.254',
port: 80 }
node ip.js 0.20s user 0.05s system 0% cpu 1:15.74 total
The good thing about the DNS lookup was that it failed before being subject to the OS controlled TCP timeouts. Not to mention retries.
We need another fast way to detect if an environment is GCP before attempting the TCP connection. |
The solution will need to achieve these attributes:
Concurrently:
Whichever operation finishes first, cache the result as the When running on GCP, step 2 will finish in milliseconds. When running off GCP, step 2 will fail, so the operation latency will be dependent on the first check passing. This adds no additional latency to our current behavior in the off-GCP environment. WDYT? |
The metadata service has absolutely no SLA. |
Resolving it without the trailing dot will result in Nx # of DNS requests for each entry in the OS’s resolver library search path before trying it as a FQDN. My suspicion is that resolving it should work on all platforms; it’s actually using it in the |
Actually as worded, my strategy doesn't depend upon the SLA for metadata service at all (any more than today). The concurrent direct connection to the IP is merely a fast path.
In GCP environments the IP connect will finish first which means that these M DNS queries will be initiated but the result will be inconsequential. |
@ofrobots in GKE (where there are ~ 6 domains in the search path and the resolver makes both A and quad-A lookups), this will hit kube-dns heavily which is the whole thing this is trying to fix, so I wouldn’t say it’s inconsequential. Also, wouldn’t deciding too quickly that we’re not in GCP potentially could cause auth failures, no? If it’s only for |
We don't decide that we are not GCP until both IP-connect and DNS-lookups fail, so no, from auth point of view my proposal is same as current behavior.
To make sure I understand, is the 6 lookups the problem or the fact that there was no cache of the result?
I think we need more definitive information here. Any one with Java chops willing to try OkHttp (possibly on Android)? |
I think I t’ll be 12-14 lookups for each request or so, because there’s both the A and quad-A lookups. @crwilcox has a POC I threw together that generates about 80 or so DNS lookups by connecting to a handful of Google services in GKE using the official libs. Yes, once GKE rolls out better caching in front of dnsmasq / kube-dns, this should be less of a problem. I don’t think that will be rolled out til next year sometime? |
I'm also proposing we cache the resolution result locally in this module – that is this library will try to resolve |
As a further optimization perhaps we can limit to |
Yes, resolving it once should definitely be a massive improvement, and would also be cleaner than hard-coding the IP. I’m pretty confident, though, that if you’re using that approach, it should be safe (and most correct) to use the trailing dot as well. |
Staring at the Java exception a bit more, I think you're right. The error is a SSL handshake error which implies that the DNS lookup was already done. It would be good to get something more definitive though. |
Some really interesting points made in this link pointed to me by Google support: nodejs/node#15780 (comment) These discuss some of the finer points of why this can cause such headaches with GKE, especially with Alpine base images. I'm not sure if there's been any discussion about changing For me, adding this to the Kube deployment spec "fixes" the problem. dnsConfig:
options:
- name: ndots
value: "2" |
It looks like this actually got fixed in GCF! @ofrobots mind taking another look? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change LGTM as it will not cause googleapis/google-cloud-node#2249 to come back as it is related to service endpoints hostnames being used with the trailing dot in the headers.
In other libraries, I expect we will want to add a trailing dot too; but that needs to be done with more care.
FWIW, Here is how go handles it: |
Minimize lookups by not expanding search path when resolving metadata.google.internal.
A customer found that about 85 DNS requests were made at init of packages relying on gcp-metadata. Upon investigation the issue was we were forcing path expansion for DNS resolution. After this change about 21 requests are made.