<p>I deployed a kubernetes cluster on Google Cloud using VMs and <a href="https://github.com/kubernetes-sigs/kubespray/blob/master/docs/setting-up-your-first-cluster.md" rel="nofollow noreferrer">Kubespray</a>.</p>
<p>Right now, I am  looking to expose a simple node app to external IP using <a href="https://cloud.google.com/kubernetes-engine/docs/tutorials/configuring-domain-name-static-ip#gcloud" rel="nofollow noreferrer">loadbalancer</a>  but showing my external IP from gcloud to service does not work. It stays on pending state when I query <code>kubectl get services</code>.</p>
<p>According to <a href="https://stackoverflow.com/questions/44110876/kubernetes-service-external-ip-pending">this</a>, kubespray does not have any loadbalancer mechanicsm included/integrated by default. How should I progress?</p>

bcan
<p>Let me start of by summarizing the problem we are trying to solve here.</p>
<p>The problem is that you have self-hosted kubernetes cluster and you want to be able to create a service of type=LoadBalancer and you want k8s to create a LB for you with externlIP and in fully automated way, just like it would if you used a GKE (kubernetes as a service solution).</p>
<p>Additionally I have to mention that I don't know much of a kubespray, so I will only describe all the steps that need to bo done to make it work, and leave the rest to you. So if you want to make changes in kubespray code, it's on you.
All the tests I did with kubeadm cluster but it should not be very difficult to apply it to kubespray.</p>
<hr />
<p>I will start of by summarizing all that has to be done into 4 steps:</p>
<ol>
<li>tagging the instances</li>
<li>enabling cloud-provider functionality</li>
<li>IAM and service accounts</li>
<li>additional info</li>
</ol>
<hr />
<p><strong>Tagging the instances</strong>
All worker node instances on GCP have to be labeled with unique tag that is the name of an instance; these tags are later used to create a firewall rules and target lists for LB. So lets say that you have an instance called <strong>worker-0</strong>; you need to tag that instance with a tag <code>worker-0</code></p>
<p>Otherwise it will result in an error (that can be found in controller-manager logs):</p>
<pre><code>Error syncing load balancer: failed to ensure load balancer: no node tags supplied and also failed to parse the given lists of hosts for tags. Abort creating firewall rule
</code></pre>
<hr />
<p><strong>Enabling cloud-provider functionality</strong>
K8s has to be informed that it is running in cloud and what cloud provider that is so that it knows how to talk with the api.</p>
<p>controller manager logs informing you that it wont create an LB.</p>
<pre><code>WARNING: no cloud provider provided, services of type LoadBalancer will fail
</code></pre>
<p>Controller Manager is responsible for creation of a LoadBalancer. It can be passed a flag <code>--cloud-provider</code>. You can manually add this flag to controller manager pod manifest file; or like in your case since you are running kubespray, you can add this flag somewhere in kubespray code (maybe its already automated and just requires you to set some env or sth, but you need to find it out yourself).</p>
<p>Here is how this file looks like with the flag:</p>
<pre><code>apiVersion: v1
kind: Pod
metadata:
  labels:
    component: kube-controller-manager
    tier: control-plane
  name: kube-controller-manager
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-controller-manager
    ...
    - --cloud-provider=gce    # &lt;----- HERE
</code></pre>
<p>As you can see the value in our case is <code>gce</code>, which stangs for Google Compute Engine. It informs k8s that its running on GCE/GCP.</p>
<hr />
<p><strong>IAM and service accounts</strong>
Now that you have your provider enabled, and tags covered, I will talk about IAM and permissions.</p>
<p>For k8s to be able to create a LB in GCE, it needs to be allowed to do so. Every GCE instance has a deafult service account assigned. Controller Manager uses instance service account, stored within <a href="https://cloud.google.com/compute/docs/storing-retrieving-metadata" rel="nofollow noreferrer">instance metadata</a> to access GCP API.</p>
<p>For this to happen you need to set Access Scopes for GCE instance (master node; the one where controller manager is running) so it can use Cloud Engine API.</p>
<blockquote>
<p>Access scopes -&gt; Set access for each API -&gt; compute engine=Read Write</p>
</blockquote>
<p>To do this the instance has to be stopped, so now stop the instance. It's better to set these scopes during instance creation so that you don't need to make any unnecessary steps.</p>
<p>You also need to go to IAM &amp; Admin page in GCP Console and add permissions so that master instance's service account has <code>Kubernetes Engine Service Agent</code> role assigned. This is a predefined role that has much more permissions than you probably need but I have found that everything works with this role so I decided to use is for demonstration purposes, but you probably want to use <em>least privilege rule</em>.</p>
<hr />
<p><strong>additional info</strong>
There is one more thing I need to mention. It does not impact you but while testing I have found out an interesting thing.</p>
<p>Firstly I created only one node cluster (single master node). Even though this is  allowed from k8s point of view, controller manager would not allow me to create a LB and point it to a master node where my application was running. This draws a conclusion that one cannot use LB with only master node and has to create at least one worker node.</p>
<hr />
<p>PS
I had to figure it out the hard way; by looking at logs, changing things and looking at logs again to see if the issue got solved. I didn't find a single article/documentation page where it is documented in one place. If you manage to solve it for yourself, write the answer for others. Thank you.</p>
