A meshed client isn't able to reach a meshed statefulset pod via the pod's stable network ID. This works if the client isn't meshed.
Deploy a meshed statefulset nginx
cat <<EOF | linkerd inject - | kubectl apply -f -
kind: Service
apiVersion: v1
metadata:
name: nginx
labels:
app: nginx
spec:
clusterIP: None
selector:
app: nginx
ports:
- name: http
port: 80
targetPort: http
---
kind: StatefulSet
apiVersion: apps/v1
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 3
serviceName: nginx
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
name: http
EOF
Use a curl pod to reach nginx:
$ kubectl run curl --image=appropriate/curl --restart=Never --command -- sleep 3600
# this works
$ kubectl exec curl -- curl --silent nginx-0.nginx.default.svc.cluster.local
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
Now meshed the curl pod and notice that it can't reach the nginx pod:
$ kubectl run curl --image=appropriate/curl --restart=Never --dry-run -o yaml --command -- sleep 3600 | linkerd inject - | kubectl apply -f -
# stuck
$ kubectl exec curl -c curl -- curl --silent nginx-0.nginx.default.svc.cluster.local
# this works
$ kubectl exec curl -c curl -- curl --silent nginx.default
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
...
Proxy logs from the curl client:
WARN admin={bg=resolver} linkerd2_proxy::control::destination::background::destination_set Destination.Get stream errored for NameAddr { name: "nginx-0.nginx.default.svc.cluster.local", port: 80 }: Grpc(Status { code: Unknown, message: "resolver [&{k8sDNSZoneLabels:[] controllerNamespace:linkerd endpointsWatcher:0xc4205e9f20 profileWatcher:0xc4203f2ae0}] found error resolving host [nginx-0.nginx.default.svc.cluster.local] port [80]: not a service: nginx-0.nginx.default.svc.cluster.local" })
WARN admin={bg=resolver} linkerd2_proxy::control::destination::background::destination_set Destination.Get stream errored for NameAddr { name: "nginx-0.nginx.default.svc.cluster.local", port: 80 }: Grpc(Status { code: Unknown, message: "resolver [&{k8sDNSZoneLabels:[] controllerNamespace:linkerd endpointsWatcher:0xc4205e9f20 profileWatcher:0xc4203f2ae0}] found error resolving host [nginx-0.nginx.default.svc.cluster.local] port [80]: not a service: nginx-0.nginx.default.svc.cluster.local" })
linkerd check outputkubernetes-api
--------------
โ can initialize the client
โ can query the Kubernetes API
kubernetes-version
------------------
โ is running the minimum Kubernetes API version
linkerd-existence
-----------------
โ control plane namespace exists
โ controller pod is running
โ can initialize the client
โ can query the control plane API
linkerd-api
-----------
โ control plane pods are ready
โ can query the control plane API
โ [kubernetes] control plane can talk to Kubernetes
โ [prometheus] control plane can talk to Prometheus
linkerd-service-profile
-----------------------
โ no invalid service profiles
linkerd-version
---------------
โ can determine the latest version
โ cli is up-to-date
control-plane-version
---------------------
โ control plane is up-to-date
โ control plane and cli versions match
Same here!
@hawkw @adleong any ideas on the scope for this and what pieces of code it'll touch?
@grampelberg I'd have to double-check to be sure, but I think this will only require a change to the control plane's Destination service. I don't think a proxy change is necessary.
If the work isn't massive, this is a great getting started issue as it introduces folks to how discovery is working. I'd love to see if anyone's interested in tackling it (assuming the changes aren't massive).
this also doesn't seem to work for any headless services that expose pods via a fully numeric prefix. not the same error, but the failure is also in the discovery service and affects the same kind of workload/use case.
is an invalid assumption that generates thousands of retries per second to the controller api from the proxies attempting to resolve valid dns names.
@lancehitchcock is it possible to share a k8s manifest that exhibits this behavior? i'd love to capture this in an integration test.
any service with .spec.clusterIP of None exposes pods in the DNS SRV API this way.
Prometheus specifically uses this for scraping if you use dns sd.
using the original example from above and adding a second service with a different name (like nginx-headless) and the clusterIP set to None would be sufficient to give you the SRV DNS entries under _http._tcp.nginx-headless.default.svc.cluster.local
you can verify this with dig SRV _http._tcp.nginx-headless.default.svc.cluster.local
the manifest would look like this:
kind: Service
apiVersion: v1
metadata:
name: nginx-headless
labels:
app: nginx
spec:
clusterIP: None
selector:
app: nginx
ports:
- name: http
port: 80
targetPort: http
edit: it is possible that for statefulset specifically, you may have to specify this service as the .spec.serviceName but this is not necessary for deployment types
;; QUESTION SECTION:
;_http._tcp.<service name>.<namespace>.svc.cluster.local. IN SRV
;; ANSWER SECTION:
_http._tcp.<service name>.<namespace>.svc.cluster.local. 30 IN SRV 10 50 80 3763656465393934.<service name>.<namespace>.svc.cluster.local.
_http._tcp.<service name>.<namespace>.svc.cluster.local. 30 IN SRV 10 50 80 3236616662636137.<service name>.<namespace>.svc.cluster.local.
;; ADDITIONAL SECTION:
3763656465393934.<service name>.<namespace>.svc.cluster.local. 30 IN A 10.4.1.23
3236616662636137.<service name>.<namespace>.svc.cluster.local. 30 IN A 10.4.0.7
the result would look something like this, and calls to those endpoints through a sidecar proxy will fail
Verified / confirmed the same behavior that @lancehitchcock saw on 6 April:
Environment:
Kubernetes Version: v1.13.6-gke.13
Cluster Environment: Google Kubernetes Engine
Host OS: Google Container Optimized OS 1.12.7-gke.7
Linkerd version: stable-2.3.2
Behold, an all-in-one demo of the issue:
https://gist.github.com/memory/21cf2a81637253889f8fe3b11e4e3bc8
With linkerd injection disabled, the gethello pod should be able to successfully query the three pods of the hello statefulset:
checking http://hello-0.hello.default.svc.cluster.local/
200
checking http://hello-1.hello.default.svc.cluster.local/
200
checking http://hello-2.hello.default.svc.cluster.local/
200
With injection _enabled_ (change lines 35 and 88), all requests will return 503 errors:
checking http://hello-0.hello.default.svc.cluster.local/
503
checking http://hello-1.hello.default.svc.cluster.local/
503
checking http://hello-2.hello.default.svc.cluster.local/
503
I think that now we can solve this completely in the destination service by (1) modifying the Get endpoint to serve responses for statefulset names -- this will enable identity, etc, for statefulset traffic -- and (2) modifying GetProfiles to drop the instance ID from the service name before resolving the profile.
Hi @memory, I ran into a strange issue when testing your demo. Your hello pods declare their container port as 8080 but actually just listen on port 80. This causes Linkerd to attempt to connect to port 8080 and get a connection refused error. For some reason, if Linkerd is not used, Kubernetes will forward the connection to port 80 even though the target port is defined as 8080, which is very surprising to me.
Anyway, I've put together https://github.com/linkerd/linkerd2/pull/3113 to add stateful set support. If I modify your demo to declare 80 as the hello container port, this PR fixes the issue.
Please take a look and let me know what you think!
Most helpful comment
I think that now we can solve this completely in the destination service by (1) modifying the
Getendpoint to serve responses for statefulset names -- this will enable identity, etc, for statefulset traffic -- and (2) modifyingGetProfilesto drop the instance ID from the service name before resolving the profile.