What happened?
Deploying kube-prometheus release-0.6 to a kubeadm boostrapped bare-metal cluster causes KubeControllerManagerDown and kubeSchedulerDown alerts to fire.
Did you expect to see some different?
Alerts should not fire as everything is up.
How to reproduce it (as minimally and precisely as possible):
Deploy release-0.6 with the bellow config to a kubeadm boostrapped cluster running 1.18.x
Prometheus Operator version:
prometheus-operator:v0.42.1
Manifests:
local kp =
(import 'kube-prometheus/kube-prometheus.libsonnet') +
(import 'kube-prometheus/kube-prometheus-kubeadm.libsonnet') +
(import 'kube-prometheus/kube-prometheus-all-namespaces.libsonnet') +
(import 'kube-prometheus/kube-prometheus-anti-affinity.libsonnet')
{
_config+:: {
namespace: 'monitoring',
},
};
{ ['setup/0namespace-' + name]: kp.kubePrometheus[name] for name in std.objectFields(kp.kubePrometheus) } +
{
['setup/prometheus-operator-' + name]: kp.prometheusOperator[name]
for name in std.filter((function(name) name != 'serviceMonitor'), std.objectFields(kp.prometheusOperator))
} +
// serviceMonitor is separated so that it can be created after the CRDs are ready
{ 'prometheus-operator-serviceMonitor': kp.prometheusOperator.serviceMonitor } +
{ ['node-exporter-' + name]: kp.nodeExporter[name] for name in std.objectFields(kp.nodeExporter) } +
{ ['kube-state-metrics-' + name]: kp.kubeStateMetrics[name] for name in std.objectFields(kp.kubeStateMetrics) } +
{ ['alertmanager-' + name]: kp.alertmanager[name] for name in std.objectFields(kp.alertmanager) } +
{ ['prometheus-' + name]: kp.prometheus[name] for name in std.objectFields(kp.prometheus) } +
{ ['prometheus-adapter-' + name]: kp.prometheusAdapter[name] for name in std.objectFields(kp.prometheusAdapter) } +
{ ['grafana-' + name]: kp.grafana[name] for name in std.objectFields(kp.grafana) }
Anything else we need to know?:
This issue is related to Kubernetes 1.18.x, it appears a few changes were made to the kube-controller-manager and kube-scheduler.
Firstly, version 1.18+ now uses the more secure https port of 10257 and disables http by default. Unfortunately, the --secure-port used by both kube-controller-manager and kube-scheduler is bound to 127.0.0.1 and not 0.0.0.0.
As a result, metrics cannot be collected until the bound address for both of these is updated.
The workaround:
Updating the manifests in /etc/kubernetes/manifests/ to use the --bind-address 0.0.0.0 for both the scheduler and the controller manager will relaunch the the pods with the correct bind address, but these settings will not survive a kubeadm upgrade.
In order to persist the settings, the kubeadm-config configmap in the kube-system namespace should also be edited to include the following:
controllerManager:
extraArgs:
bind-address: 0.0.0.0
scheduler:
extraArgs:
bind-address: 0.0.0.0
I understand this isn't a bug directly related to kube-prometheus, but I didn't find this documented anywhere and had been scratching my head for a day looking at this. Hoping this will help someone else in the future.
We do have a patch to disable those alerts and ServiceMonitors in clusters which don't expose access to controller manager and scheduler metrics. But yes, our documentation is lacking in this field.
It would be amazing if you could propose a PR to add what you described in "the workaround" where you expected to find this documentation when you were looking for it! :)
yes
@brancz sorry, I've been meaning to get back to this but I keep getting side tracked with other things. Do you think the Troubleshooting section on the main page would be a suitable place for this? If yes, I can put something together linking to either the patch for disabling those checks if on a managed cluster, or for updating kubeadm to use 0.0.0.0 instead of the loopback address for the secure port (at a users discretion of course).
Yes! I think the troubleshooting guide is a great place because that's most likely what people look at when they encounter this.
I have changed the bind address, but it did not work. It is strange.
@KeithTt I have encountered the same trouble recently, It took me 5 days to figure it out.
My K8S cluster was created via kubeadm and the version is 1.19.2
Check these points:
/etc/kubernetes/manifests/kube-scheduler.yaml, change --bind-address=127.0.0.1 to --bind-address=0.0.0.0k8s-app: kube-scheduler) matching with the ServiceMonitor(spec.selector.matchLabels)component: kube-scheduler)https-metrics) must matching with ServiceMonitor(spec.endpoints.port)FYI. my yaml files:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
labels:
k8s-app: kube-scheduler
name: kube-scheduler
namespace: monitoring
spec:
endpoints:
- bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
interval: 30s
port: https-metrics
scheme: https
tlsConfig:
insecureSkipVerify: true
jobLabel: k8s-app
namespaceSelector:
matchNames:
- kube-system
selector:
matchLabels:
k8s-app: kube-scheduler
apiVersion: v1
kind: Service
metadata:
namespace: kube-system
name: kube-scheduler
labels:
k8s-app: kube-scheduler
spec:
ports:
- name: https-metrics
port: 10259
selector:
component: kube-scheduler
Note, that both kube-scheduler and kube-controller-manager certificates have localhost and 127.0.0.1. This is opposing to etcd ones, which contains actual hostname and node IP. So at this moment, both scheduler and controller-manager require insecureSkipVerify: true
Binding to 0.0.0.0 may have some security implications, like exposing previously hidden endpoints to the cluster and maybe even to the public.
insecureSkipVerify is also more of a temporary workaround.
I see two approaches:
kubeadm.Binding to
0.0.0.0may have some security implications, like exposing previously hidden endpoints to the cluster and maybe even to the public.
I totally agree, which is partially why I haven't made time to submit the PR for updating the troubleshooting docs - that and I'm swamped.
So at this moment, both scheduler and controller-manager require insecureSkipVerify: true
Yea, if what you're saying is true than that would be necessary - I don't recall having to set this value though. Does prometheus even check for valid certificates on the endpoints it scrapes?
Design component endpoints with Prometheus scraper in mind: i.e. they provide a certificate that includes master nodes' IPs (similar to etcd). Bind to the main network interface instead of 127.0.0.1. This likely requires support by kubeadm.
I think this solution is more realistic - I set the bind address to 0.0.0.0 out of laziness, and I suspect most folks are like me (not that this is a good thing!)
So at this moment, both scheduler and controller-manager require insecureSkipVerify: true
Yea, if what you're saying is true than that would be necessary - I don't recall having to set this value though. Does prometheus even check for valid certificates on the endpoints it scrapes?
It is. Scraping fails otherwise.
Design component endpoints with Prometheus scraper in mind: i.e. they provide a certificate that includes master nodes' IPs (similar to etcd). Bind to the main network interface instead of 127.0.0.1. This likely requires support by kubeadm.
I think this solution is more realistic - I set the bind address to 0.0.0.0 out of laziness, and I suspect most folks are like me (not that this is a good thing!)
At the moment I have no idea how to make it. bind-address is passed to scheduler/controller-manager manifests from extraArgs: bind-address: 0.0.0.0. This parameter is static among all nodes. The only "universal" addresses are 127.0.0.1 and 0.0.0.0. Instead, the wanted value is the node IP address. The certificate is generated for localhost/127.0.0.1 regardless of the bind-address. So the right config seems not possible with kubeadm init phase control-plane controller-manager --config kubeadm.yml.
Also, I'm not sure if anything relies on scheduler/control-manager being bounded to 127.0.0.1. Opposing to etcd these can be bound only to a single address AFAIK.
https://github.com/kubernetes/kubeadm/issues/2244 - related issue about kube-scheduler and kube-controller-manager certificates.
it seems that in k8s v1.20.2 (probably even before, i didn't check)
the 0.0.0.0 workaround is no longer working, since the default insecure address is already 0.0.0.0 (both for the scheduler and the controller manager) but it is disabled by default, and it looks like the only solution is to use the deprecated port parameter to enable the insecure listening.
in short. the workaround should be configured like so:
controllerManager:
extraArgs:
port: '10252'
scheduler:
extraArgs:
port: '10251'
even though in the documentations they are saying:
the default port for the scheduler is 10251 - (Ref: https://kubernetes.io/docs/reference/command-line-tools-reference/kube-scheduler/)
the default port for the controller-manager is not specified - (Ref: https://kubernetes.io/docs/reference/command-line-tools-reference/kube-controller-manager/)
both /etc/kubernetes/manifests/kube-scheduler.yaml and /etc/kubernetes/manifests/kube-controller-manager.yaml come with: - --port=0 out of the box
which disable the insecure listening.
if someone can approve/deny it i will appreciate it, because i don't like this solution even for a workaround.
@omerozery I think the default value for --bind-address for both kube-controller-manager and kube-scheduler v1.20 is 0.0.0.0, in which case the issue reported by the OP wouldn't occur. But the point is that kubeadm by default sets these values to 127.0.0.1. So, to revert these settings made by kubeadm, the workaround is
controllerManager:
extraArgs:
bind-address: 0.0.0.0
scheduler:
extraArgs:
bind-address: 0.0.0.0
as posted by the OP.
Regarding your workaround, kube-controller-manager v1.20 doesn't have a --port flag anymore, so your configuration likely wouldn't work (kube-scheduler v1.20 still has the --port flag). Unless you report that you could indeed enable port 10252 for kube-controller-manager.
i wrote the comment above after i tried it myself..
i was using the "bind-address" workaround on kubeadm deployed clusters v1.18.2, because like you said it was needed and everything worked fine, (putting aside the security issue).
last couple of days i was working on deploying clusters v1.20.2 (using kubeadm)
and this configuration didn't make any difference (netstat command below returned nothing and prometheus couldn't scrap the metrics).
the only thing that opened the ports and listened using the address 0.0.0.0 is the "ports" workaround.
this is the output from my k8s-master when using the "ports" workaround:
[root@my-k8s-master ~]# cat /etc/kubernetes/manifests/kube-scheduler.yaml | egrep -i '\--bind|\--port'
- --bind-address=127.0.0.1
- --port=10251
[root@my-k8s-master ~]# netstat -tunap | grep -i 10251
tcp6 0 0 :::10251 :::* LISTEN 12058/kube-schedule
[root@my-k8s-master ~]# cat /etc/kubernetes/manifests/kube-controller-manager.yaml | egrep -i '\--bind|\--port'
- --bind-address=127.0.0.1
- --port=10252
[root@my-k8s-master ~]# netstat -tunap | grep -i 10252
tcp6 0 0 :::10252 :::* LISTEN 12065/kube-controll
clearly the 127.0.0.1 is ignored , am i missing something?
@omerozery
https://kubernetes.io/docs/reference/command-line-tools-reference/kube-controller-manager/
--port is non-existent, there is only --secure-port now. Default is 10257 for controller-manager, 10259 for the scheduler.
@weibeld Note that --bind-address=0.0.0.0 may expose your metrics to the internet in the production environment. I see
two solutions at the moment:
extraArg: --bind-address=<NODE IP ADDRESS> on every control plane node before doing kubeadm init phase control-plane controller-manager --config kubeadm.yml (same for scheduler).I made comments in linked kubeadm issue. Generally, IMO kubeadm authors haven't thought it through. The best would be using node IP addresses instead of 127.0.0.1 by default for --bind-address and probes.
see my comment here:
https://github.com/kubernetes/kubeadm/issues/2388#issuecomment-774794312
I made comments in linked kubeadm issue. Generally, IMO kubeadm authors haven't thought it through. The best would be using node IP addresses instead of 127.0.0.1 by default for --bind-address and probes.
we've seen the requests about it, but the response has been that we don't want to expose the components outside of localhost due to metrics.
the current best workaround:
kube-scheduler.json and kube-controller-manager.json in a folder with the following same contents:[
{ "op": "add", "path": "/spec/containers/0/command/-", "value": "--bind-address=SOME_IP" },
{ "op": "replace", "path": "/spec/containers/0/livenessProbe/httpGet/host", "value": "SOME_IP" }
{ "op": "replace", "path": "/spec/containers/0/startupProbe/httpGet/host", "value": "SOME_IP" }
]
SOME_IP to be the IP you want to bind.kubeadm init/join/upgrade with --experimental-patches=thepatchfolder.of course, if there are sufficient votes about this change request let's comment on https://github.com/kubernetes/kubeadm/issues/2388
@ksa-real @weibeld.
the documents and your comments above (which are basically documents references) do not reflect what is actually happening, for kubeadm v1.20.2 deployed clusters, repeating it won't make it true, please try it before you comment.
@ksa-real @weibeld.
the documents and your comments above (which are basically documents references) do not reflect what is actually happening, for kubeadm v1.20.2 deployed clusters, repeating it won't make it true, please try it before you comment.
I meant don't use the deprecated --port at all. Bind to 0.0.0.0 or node IP and scrape the HTTPS 10257/10259 ignoring the certificate.
netstat -tunap | grep -i 1025[79]
The proxy workaround can be relatively easily implemented by running an HAProxy container with the following configuration as a DaemonSet on each master node:
defaults
mode http
timeout connect 5000ms
timeout client 5000ms
timeout server 5000ms
default-server maxconn 10
frontend kube-controller-manager
bind ${NODE_IP}:10257
http-request deny if !{ path /metrics }
default_backend kube-controller-manager
backend kube-controller-manager
server kube-controller-manager 127.0.0.1:10257 ssl verify none
frontend kube-scheduler
bind ${NODE_IP}:10259
http-request deny if !{ path /metrics }
default_backend kube-scheduler
backend kube-scheduler
server kube-scheduler 127.0.0.1:10259 ssl verify none
Note the following:
$NODE_IP environment variable (which is the desired IP address that the proxy should listen on) can be passed into the HAProxy Pod with a fieldRef:yaml
env:
<ul>
<li>name: NODE_IP<br />
valueFrom:<br />
fieldRef:<br />
apiVersion: v1<br />
fieldPath: status.hostIP<br />
verify none). This is due to the kubeadm defaults setting up kube-controller-manager and kube-scheduler with an unsigned TLS server certificate for serving HTTPS which is saved at an unknown location (see https://github.com/kubernetes/kubeadm/issues/2244 and https://github.com/kubernetes/kubernetes/issues/80063). However, this could be changed by using either --tls-private-key-file and --tls-cert-file or --cert-dir on kube-controller-manager and kube-scheduler, in which case it should be possible to validate the TLS server certificate./metrics endpoint to not expose any other functionality of the backing services. If necessary, this could be further restricted in the HAProxy configuration by, e.g. only allowing requests from a certain IP address range.hostNetwork so that they can access the loopback interfaces of the corresponding kube-controller-manager and kube-scheduler Pods.After deploying the DaemonSet, you can scrape the metrics of kube-controller-manager and kube-scheduler on http://<NODE_IP>:10257/metrics and http://<NODE_IP>:10259/metrics.
The main disadvantage of this workaround is that the names of the kube-controller-manager and kube-scheduler Pods are lost. Prometheus discovers only the names of the proxy Pods, which is not really useful. Prometheus discovers the names of the nodes the Pods run on (in the __meta_kubernetes_pod_node_name label), so it's at least possible to tell that a given metric belongs to the kube-controller-manager of kube-scheduler _of that node_, however, there seems to be no easy way to deduce the exact name of this kube-controller-manager or kube-scheduler Pod.
Thanks! But I think this is‘ot good idea.
Most helpful comment
The proxy workaround can be relatively easily implemented by running an HAProxy container with the following configuration as a DaemonSet on each master node:
Note the following:
$NODE_IPenvironment variable (which is the desired IP address that the proxy should listen on) can be passed into the HAProxy Pod with afieldRef:yaml env: <ul> <li>name: NODE_IP<br /> valueFrom:<br /> fieldRef:<br /> apiVersion: v1<br /> fieldPath: status.hostIP<br />verify none). This is due to the kubeadm defaults setting up kube-controller-manager and kube-scheduler with an unsigned TLS server certificate for serving HTTPS which is saved at an unknown location (see https://github.com/kubernetes/kubeadm/issues/2244 and https://github.com/kubernetes/kubernetes/issues/80063). However, this could be changed by using either--tls-private-key-fileand--tls-cert-fileor--cert-diron kube-controller-manager and kube-scheduler, in which case it should be possible to validate the TLS server certificate./metricsendpoint to not expose any other functionality of the backing services. If necessary, this could be further restricted in the HAProxy configuration by, e.g. only allowing requests from a certain IP address range.hostNetworkso that they can access the loopback interfaces of the corresponding kube-controller-manager and kube-scheduler Pods.After deploying the DaemonSet, you can scrape the metrics of kube-controller-manager and kube-scheduler on
http://<NODE_IP>:10257/metricsandhttp://<NODE_IP>:10259/metrics.The main disadvantage of this workaround is that the names of the kube-controller-manager and kube-scheduler Pods are lost. Prometheus discovers only the names of the proxy Pods, which is not really useful. Prometheus discovers the names of the nodes the Pods run on (in the
__meta_kubernetes_pod_node_namelabel), so it's at least possible to tell that a given metric belongs to the kube-controller-manager of kube-scheduler _of that node_, however, there seems to be no easy way to deduce the exact name of this kube-controller-manager or kube-scheduler Pod.