<p>It is not possible to join master nodes without having <a href="https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/high-availability/#steps-for-the-first-control-plane-node" rel="nofollow noreferrer">set a <code>controlPlaneEndpoint</code></a>:</p>

<blockquote>
  <p>error execution phase preflight:<br>
  One or more conditions for hosting a new control plane instance is not satisfied.<br>
  unable to add a new control plane instance a cluster that doesn't have a stable controlPlaneEndpoint address<br>
  Please ensure that:<br>
   * The cluster has a stable controlPlaneEndpoint address.</p>
</blockquote>

<p>But if you instead join a worker node (i.e. without <code>--control-plane</code>), then it is not only aware of other nodes in the cluster, but also which ones are masters.</p>

<p>This is because the <code>mark-control-plane</code> phase does:</p>

<blockquote>
  <p>Marking the node  as control-plane by adding the label "node-role.kubernetes.io/master=''"
  Marking the node  as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]</p>
</blockquote>

<p>So couldn't masters (<code>--control-plane</code>) join the cluster and use the role label to <em>discover</em> the other control plane nodes?</p>

<p>Is there any such plugin or other way of configuring this behaviour to avoid separate infrastructure for load balancing the API server?</p>

<p>Looking at the <a href="https://github.com/kubernetes/kubernetes/blob/63e27a02ed41f9554b547b18d4bffd69f6adf08a/cmd/kubeadm/app/apis/kubeadm/v1beta2/types.go#L70-L80" rel="noreferrer">kubeadm types definition</a> I found this nice description that clearly explains it:</p>

<blockquote>
  <p>ControlPlaneEndpoint sets a stable IP address or DNS name for the control plane; it
  can be a valid IP address or a RFC-1123 DNS subdomain, both with optional TCP port.
  In case the ControlPlaneEndpoint is not specified, the AdvertiseAddress + BindPort
  are used; in case the ControlPlaneEndpoint is specified but without a TCP port,
  the BindPort is used.
  Possible usages are:
  e.g. In a cluster with more than one control plane instances, this field should be
  assigned the address of the external load balancer in front of the
  control plane instances.
  e.g.  in environments with enforced node recycling, the ControlPlaneEndpoint
  could be used for assigning a stable DNS to the control plane.</p>
</blockquote>

<p>This also likely affects the PKI generated by kubernetes, as it will need to know a common name/IP that you will access the cluster via to include in the certs it generates for the api nodes, otherwise these won't match up correctly.</p>

<p>If you really didn't want to have a loadbalancer you might be able to set up a round-robin dns entry with the IP's of all the control plane nodes and try specifying this for the <code>controlPlaneEndpoint</code> value. However this won't help with failover and redundancy, since failed nodes won't be removed from the record, and some clients might cache the address and not try to re-resolve it, thus further prolonging any outages.</p>

<p>Hope this helps.</p>
