Sitelet https://github.com/kubernetes/kops/pull/18459
Skip to content

Added support for APIServer using LB - #18459

Merged
k8s-ci-robot merged 2 commits into
kubernetes:masterfrom
cheftako:apiOnly
Jun 16, 2026
Merged

k8s-ci-robot merged 2 commits into
kubernetes:masterfrom
cheftako:apiOnly

Conversation

@cheftako

@cheftako cheftako commented Jun 9, 2026 •

Copy link
Copy Markdown
Member

Added support for APIServer without local Etcd
Etcd is hosted on ControlPlane nodes.
For GCP setup a LB and plumb it's address into /opt/kops/conf/kube_env.yaml
Fix up /etc/hosts to point the etcd host to the LB address.

@k8s-ci-robot k8s-ci-robot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Jun 9, 2026
@k8s-ci-robot
k8s-ci-robot requested review from hakman and zetaab June 9, 2026 19:59
@k8s-ci-robot k8s-ci-robot added area/api area/nodeup area/provider/gcp Issues or PRs related to gcp provider cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Jun 9, 2026
Comment thread hack/upload Outdated
Comment thread hack/upload Outdated
Comment thread pkg/apis/kops/validation/instancegroup.go Outdated
Comment thread pkg/apis/kops/validation/legacy.go Outdated
Comment thread upup/pkg/fi/nodeup/command.go Outdated
Comment thread pkg/model/resources/nodeup.go Outdated
Comment thread upup/pkg/fi/http.go Outdated
@k8s-ci-robot k8s-ci-robot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Jun 9, 2026
@k8s-ci-robot k8s-ci-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Jun 10, 2026
@cheftako
cheftako force-pushed the apiOnly branch 2 times, most recently from 1621616 to f7fee3c Compare June 10, 2026 20:10
@cheftako cheftako changed the title [WIP] Added support for APIServer using LB Added support for APIServer using LB Jun 10, 2026
@k8s-ci-robot k8s-ci-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jun 10, 2026
Added support for APIServer without local Etcd
Etcd is hosted on ControlPlane nodes.
For GCP setup a LB and plumb it's address into /opt/kops/conf/kube_env.yaml
Fix up /etc/hosts to point the etcd host to the LB address.

Also added support for gs: storage buckets.
Needed to use the hack/dev-build-gce.sh build.

make gomod
hack/update-expected.sh
Fixed template for google storage copy & check
Wired context up to make call stack more obvious
Also provided better reuse of context object
make gofmt
make goimports
Fixed "APIServer role forbidden on GCE with dns=None" test to reflect
that use case should no longer generate an error
Fixed unit test.
@k8s-ci-robot k8s-ci-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Jun 15, 2026
@cheftako

Copy link
Copy Markdown
Member Author

/test pull-kops-verify-terraform

})
}

if b.HasAPIServer && !b.IsMaster {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we could probably just check whether b.BootConfig.EtcdLBAddress != "" - we're always trying to make nodeup "dumber" :-)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now we're not writing different data to /opt/kops/conf/kube_env.yaml on different kinds of control plane/api server nodes. So I believe this field is currently going to be populated the same way on both Master and APIServer Nodes. I think writing etcd LB to /etc/hosts would work on both. However, I suspect its probably better to talk to the localhost etcd for master nodes rather than routing their traffic through the load balancer. (Though I guess there is a HA argument to be made for the latter)

Comment on lines 261 to 267
if cluster.GetCloudProvider() != kops.CloudProviderAWS && cluster.GetCloudProvider() != kops.CloudProviderGCE {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "role"), "APIServer role only supported on AWS and GCE"))
}
if cluster.UsesNoneDNS() {
// GCE now supports using an internal LB to resolve Etcd with topology.dns.type=None
if cluster.UsesNoneDNS() && cluster.GetCloudProvider() == kops.CloudProviderAWS {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "role"), "APIServer cannot be used with topology.dns.type=None"))
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: might be easier to refactor these into a switch statement (on GetCloudProvider), but not a blocker

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the logic is easier to read with your suggestion, so I 'switch'ed it.

}
if !hasEtcd {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "metadata", "name"), fmt.Sprintf("InstanceGroup \"%s\" with role ControlPlane must have a member in etcd cluster \"%s\"", g.ObjectMeta.Name, etcd.Name)))
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "metadata", "name"), fmt.Sprintf("InstanceGroup \"%s\" with role ControlPlane must have a member in etcd (IG: \"%s\") cluster \"%s\"", g.ObjectMeta.Name, last, etcd.Name)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to log this? Obviously the two instance groups are confusing, but I'm not sure it's going to be easy to know what to do here. Did you hit this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did hit this case. I was manually crafting the template and this helped my see what my misconfiguration was. I realize its a little annoying adding to the expected test output. However I think the extra data is helpful if you hit this case. Even if it just rules out getting the wrong IG.

Comment thread pkg/apis/nodeup/config.go Outdated
// NodeupConfigHash holds a secure hash of the nodeup.Config.
NodeupConfigHash string
// EtcdLBAddress holds the address of the load balancer to use if Etcd is not local.
EtcdLBAddress string `json:",omitempty"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For api server, we use APIServerIPs (I think that's the closest equivalent). Should we name this EtcdIPs or similar? (I think I actually prefer EtcdAddresses or EtcdEndpoints, but I think what matters is that this probably should be a []string not a string)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with being consistent on IP vs Address or Endpoint. I also removed the 'LB' substring. Part of me suspects we my eventually want to track both actual Etcd IPs and the LB IP. However I can't come up with a case right now, so going with it. I do think for now we will to enforce EtcdIPs should be length 0 or 1.

Comment thread pkg/model/gcemodel/api_loadbalancer.go Outdated
if ig.IsControlPlane() {
controlPlaneIGMs = append(controlPlaneIGMs, igm)
} else if ig.IsAPIServerOnly() {
requireEtcdLB = true

@justinsb justinsb Jun 16, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could check for dns=none here, as I think this works with DNS?

But on the other hand, if DNS + APIServerOnly + GCE was previously blocked by validation, then we can just support our preferred approach (the load balancer)

But .... I don't think it was blocked, so we don't want to change the configuration of people that might be using this setup (unlikely as that might be). If this is behind a feature-flag we can change it, but ... there's no feature flag IIRC?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Fixed.

}
c.AddTask(event_hc)
*/
bs := &gcetasks.BackendService{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: backendService :-)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, it took me way too long to understand what you were saying.

Comment thread pkg/model/gcemodel/api_loadbalancer.go Outdated
}

if requireEtcdLB {
b.createEtcdInternalLB(c, controlPlaneIGMs)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should check for error here

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread pkg/model/bootstrapscript.go Outdated
Comment on lines +121 to +123
if len(wellKnownAddresses[wellknownservices.EtcdMain]) > 0 {
bootConfig.EtcdLBAddress = strings.Join(wellKnownAddresses[wellknownservices.EtcdMain], ",")
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we move this into BuildConfig? I think that's where we deal with other wellKnownAddresses (?)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is. Moved.

}

func (_ *LoadImageTask) RenderLocal(t *local.LocalTarget, a, e, changes *LoadImageTask) error {
// Not adding ctx to signature as RenderLocal seems to be part of a common interface

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One day :-)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly. I'll even go with soon but it seemed a bit large of a scope creep for this PR.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to try a parameterized interface when we do... replace some gnarly reflection.

@justinsb

Copy link
Copy Markdown
Member

Looks great, I think the only blocker is the naming of the EtcdLBAddress field in the BootConfig struct because that is actually a (private) API for nodeup, and is painful to change because of version skew

One significant change was renaming EtcdLBAddresses as EtcdIPs.
Cleanup k8s tf test file.
case kops.CloudProviderGCE:
// Fully supported do nothing.
case kops.CloudProviderAWS:
// AWS only supports APIServer if DNS is set to None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: not set to None, but no big deal

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops

if role == kops.InstanceGroupRoleAPIServer {
if len(wellKnownAddresses[wellknownservices.EtcdMain]) > 0 {
if len(wellKnownAddresses[wellknownservices.EtcdMain]) > 1 {
return nil, nil, fmt.Errorf("we currently do not support multiple Etcd IPs")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker, but ... do we not?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a CUJ and not sure if what we would try to do if there were multiple IPs is correct. I'd rather force a discussion so we can capture the CUJ and ensure we are behaving sanely. I'd rather not accidentally enshrine the current behavior for this case as correct.

@justinsb justinsb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/approve
/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Jun 16, 2026
@k8s-ci-robot

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: justinsb

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jun 16, 2026
@hakman

hakman commented Jun 16, 2026

Copy link
Copy Markdown
Member

/lgtm

@k8s-ci-robot
k8s-ci-robot merged commit d44c007 into kubernetes:master Jun 16, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. area/api area/nodeup area/provider/gcp Issues or PRs related to gcp provider cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants