Skip to content

Commit af7e6a4

Browse files
authored
Merge pull request kubernetes#12983 from zetaab/feature/drainos
Drain OpenStack loadbalancers
2 parents a69f6bb + 117b98d commit af7e6a4

19 files changed

+268
-17
lines changed

cloudmock/gce/mock_gce_cloud.go

+4
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ func (c *MockGCECloud) DeleteInstance(i *cloudinstances.CloudInstance) error {
181181
// return recreateCloudInstance(c, i)
182182
}
183183

184+
func (c *MockGCECloud) DeregisterInstance(i *cloudinstances.CloudInstance) error {
185+
return nil
186+
}
187+
184188
// DetachInstance is not implemented yet. It needs to cause a cloud instance to no longer be counted against the group's size limits.
185189
func (c *MockGCECloud) DetachInstance(i *cloudinstances.CloudInstance) error {
186190
return nil

pkg/instancegroups/instancegroups.go

+4
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,10 @@ func (c *RollingUpdateCluster) drainNode(u *cloudinstances.CloudInstance) error
646646
return fmt.Errorf("error excluding node from load balancer: %v", err)
647647
}
648648

649+
if err := c.Cloud.DeregisterInstance(u); err != nil {
650+
return fmt.Errorf("error deregistering instance %q, node %q: %v", u.ID, u.Node.Name, err)
651+
}
652+
649653
if err := drain.RunNodeDrain(helper, u.Node.Name); err != nil {
650654
if apierrors.IsNotFound(err) {
651655
return nil

pkg/kubeconfig/create_kubecfg_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ func (f fakeStatusCloud) DetachInstance(instance *cloudinstances.CloudInstance)
7474
panic("not implemented")
7575
}
7676

77+
func (f fakeStatusCloud) DeregisterInstance(instance *cloudinstances.CloudInstance) error {
78+
panic("not implemented")
79+
}
80+
7781
func (f fakeStatusCloud) GetCloudGroups(cluster *kops.Cluster, instancegroups []*kops.InstanceGroup, warnUnmatched bool, nodes []v1.Node) (map[string]*cloudinstances.CloudInstanceGroup, error) {
7882
panic("not implemented")
7983
}

pkg/model/openstackmodel/servergroup.go

+1
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ func (b *ServerGroupModelBuilder) Build(c *fi.ModelBuilderContext) error {
329329
InterfaceName: fi.String(ifName),
330330
ProtocolPort: fi.Int(443),
331331
Lifecycle: b.Lifecycle,
332+
Weight: fi.Int(1),
332333
}
333334

334335
c.AddTask(associateTask)

pkg/model/openstackmodel/tests/servergroup/multizone-setup-3-masters-3-nodes-without-bastion-with-API-loadbalancer.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,7 @@ ServerGroup:
769769
Name: cluster-master-a
770770
Policies:
771771
- anti-affinity
772+
Weight: 1
772773
---
773774
ID: null
774775
InterfaceName: cluster
@@ -802,6 +803,7 @@ ServerGroup:
802803
Name: cluster-master-b
803804
Policies:
804805
- anti-affinity
806+
Weight: 1
805807
---
806808
ID: null
807809
InterfaceName: cluster
@@ -835,6 +837,7 @@ ServerGroup:
835837
Name: cluster-master-c
836838
Policies:
837839
- anti-affinity
840+
Weight: 1
838841
---
839842
AdditionalSecurityGroups: null
840843
ID: null

upup/pkg/fi/cloud.go

+3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ type Cloud interface {
3333
// DeleteInstance deletes a cloud instance.
3434
DeleteInstance(instance *cloudinstances.CloudInstance) error
3535

36+
// // DeregisterInstance drains a cloud instance and loadbalancers.
37+
DeregisterInstance(instance *cloudinstances.CloudInstance) error
38+
3639
// DeleteGroup deletes the cloud resources that make up a CloudInstanceGroup, including the instances.
3740
DeleteGroup(group *cloudinstances.CloudInstanceGroup) error
3841

upup/pkg/fi/cloudup/awsup/aws_cloud.go

+10-6
Original file line numberDiff line numberDiff line change
@@ -541,18 +541,22 @@ func (c *awsCloudImplementation) DeleteInstance(i *cloudinstances.CloudInstance)
541541
return deleteInstance(c, i)
542542
}
543543

544-
func deleteInstance(c AWSCloud, i *cloudinstances.CloudInstance) error {
545-
id := i.ID
546-
if id == "" {
547-
return fmt.Errorf("id was not set on CloudInstance: %v", i)
548-
}
549-
544+
// DeregisterInstance drains a cloud instance and loadbalancers.
545+
func (c *awsCloudImplementation) DeregisterInstance(i *cloudinstances.CloudInstance) error {
550546
if i.CloudInstanceGroup.InstanceGroup.Spec.Manager != kops.InstanceManagerKarpenter {
551547
err := deregisterInstance(c, i)
552548
if err != nil {
553549
return fmt.Errorf("failed to deregister instance from loadBalancer before terminating: %v", err)
554550
}
555551
}
552+
return nil
553+
}
554+
555+
func deleteInstance(c AWSCloud, i *cloudinstances.CloudInstance) error {
556+
id := i.ID
557+
if id == "" {
558+
return fmt.Errorf("id was not set on CloudInstance: %v", i)
559+
}
556560

557561
request := &ec2.TerminateInstancesInput{
558562
InstanceIds: []*string{aws.String(id)},

upup/pkg/fi/cloudup/awsup/mock_aws_cloud.go

+4
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ func (c *MockAWSCloud) DeleteInstance(i *cloudinstances.CloudInstance) error {
9696
return deleteInstance(c, i)
9797
}
9898

99+
func (c *MockAWSCloud) DeregisterInstance(i *cloudinstances.CloudInstance) error {
100+
return nil
101+
}
102+
99103
func (c *MockAWSCloud) DetachInstance(i *cloudinstances.CloudInstance) error {
100104
return detachInstance(c, i)
101105
}

upup/pkg/fi/cloudup/azure/azure_cloud.go

+7
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"fmt"
2323

2424
"github.com/Azure/go-autorest/autorest/azure/auth"
25+
"k8s.io/klog/v2"
2526
"k8s.io/kops/dnsprovider/pkg/dnsprovider"
2627
"k8s.io/kops/pkg/apis/kops"
2728
"k8s.io/kops/pkg/cloudinstances"
@@ -144,6 +145,12 @@ func (c *azureCloudImplementation) DeleteInstance(i *cloudinstances.CloudInstanc
144145
return errors.New("DeleteInstance not implemented on azureCloud")
145146
}
146147

148+
// DeregisterInstance drains a cloud instance and loadbalancers.
149+
func (c *azureCloudImplementation) DeregisterInstance(i *cloudinstances.CloudInstance) error {
150+
klog.V(8).Info("Azure DeregisterInstance not implemented")
151+
return nil
152+
}
153+
147154
func (c *azureCloudImplementation) DeleteGroup(g *cloudinstances.CloudInstanceGroup) error {
148155
return errors.New("DeleteGroup not implemented on azureCloud")
149156
}

upup/pkg/fi/cloudup/azuretasks/testing.go

+4
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ func (c *MockAzureCloud) DeleteInstance(i *cloudinstances.CloudInstance) error {
131131
return errors.New("DeleteInstance not implemented on azureCloud")
132132
}
133133

134+
func (c *MockAzureCloud) DeregisterInstance(i *cloudinstances.CloudInstance) error {
135+
return nil
136+
}
137+
134138
// DeleteGroup deletes the group.
135139
func (c *MockAzureCloud) DeleteGroup(g *cloudinstances.CloudInstanceGroup) error {
136140
return errors.New("DeleteGroup not implemented on azureCloud")

upup/pkg/fi/cloudup/do/cloud.go

+6
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ func (c *doCloudImplementation) DeleteGroup(g *cloudinstances.CloudInstanceGroup
136136
return fmt.Errorf("digital ocean cloud provider does not support deleting cloud groups at this time")
137137
}
138138

139+
// DeregisterInstance drains a cloud instance and loadbalancers.
140+
func (c *doCloudImplementation) DeregisterInstance(i *cloudinstances.CloudInstance) error {
141+
klog.V(8).Info("DO DeregisterInstance not implemented")
142+
return nil
143+
}
144+
139145
func (c *doCloudImplementation) DeleteInstance(i *cloudinstances.CloudInstance) error {
140146
dropletID, err := strconv.Atoi(i.ID)
141147
if err != nil {

upup/pkg/fi/cloudup/do/mock_do_cloud.go

+4
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ func (c *doCloudMockImplementation) DeleteInstance(instance *cloudinstances.Clou
6969
return errors.New("not tested")
7070
}
7171

72+
func (c *doCloudMockImplementation) DeregisterInstance(i *cloudinstances.CloudInstance) error {
73+
return nil
74+
}
75+
7276
// DetachInstance is not implemented yet. It needs to cause a cloud instance to no longer be counted against the group's size limits.
7377
func (c *doCloudMockImplementation) DetachInstance(i *cloudinstances.CloudInstance) error {
7478
return fmt.Errorf("digital ocean cloud provider does not support surging")

upup/pkg/fi/cloudup/gce/instancegroups.go

+5
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ func (c *gceCloudImplementation) DeleteInstance(i *cloudinstances.CloudInstance)
5151
return recreateCloudInstance(c, i)
5252
}
5353

54+
func (c *gceCloudImplementation) DeregisterInstance(i *cloudinstances.CloudInstance) error {
55+
klog.V(8).Info("GCE DeregisterInstance not implemented")
56+
return nil
57+
}
58+
5459
// DetachInstance is not implemented yet. It needs to cause a cloud instance to no longer be counted against the group's size limits.
5560
func (c *gceCloudImplementation) DetachInstance(i *cloudinstances.CloudInstance) error {
5661
klog.V(8).Info("gce cloud provider DetachInstance not implemented yet")

upup/pkg/fi/cloudup/openstack/BUILD.bazel

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

upup/pkg/fi/cloudup/openstack/cloud.go

+3
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,11 @@ type OpenstackCloud interface {
252252
// ListDNSRecordsets will list the DNS recordsets for the given zone id
253253
ListDNSRecordsets(zoneID string, opt recordsets.ListOptsBuilder) ([]recordsets.RecordSet, error)
254254
GetLB(loadbalancerID string) (*loadbalancers.LoadBalancer, error)
255+
GetLBStats(loadbalancerID string) (*loadbalancers.Stats, error)
255256
CreateLB(opt loadbalancers.CreateOptsBuilder) (*loadbalancers.LoadBalancer, error)
256257
ListLBs(opt loadbalancers.ListOptsBuilder) ([]loadbalancers.LoadBalancer, error)
258+
UpdateMemberInPool(poolID string, memberID string, opts v2pools.UpdateMemberOptsBuilder) (*v2pools.Member, error)
259+
ListPoolMembers(poolID string, opts v2pools.ListMembersOpts) ([]v2pools.Member, error)
257260

258261
// DeleteLB will delete loadbalancer
259262
DeleteLB(lbID string, opt loadbalancers.DeleteOpts) error

upup/pkg/fi/cloudup/openstack/instance.go

+97
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,19 @@ limitations under the License.
1717
package openstack
1818

1919
import (
20+
"context"
2021
"fmt"
2122
"time"
2223

2324
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
2425

2526
"github.com/gophercloud/gophercloud"
2627
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
28+
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers"
29+
v2pools "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools"
2730
"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
2831
"github.com/mitchellh/mapstructure"
32+
"golang.org/x/sync/errgroup"
2933
"k8s.io/apimachinery/pkg/util/wait"
3034
"k8s.io/klog/v2"
3135
"k8s.io/kops/pkg/cloudinstances"
@@ -170,6 +174,99 @@ func deleteInstanceWithID(c OpenstackCloud, instanceID string) error {
170174
}
171175
}
172176

177+
// DeregisterInstance drains a cloud instance and loadbalancers.
178+
func (c *openstackCloud) DeregisterInstance(i *cloudinstances.CloudInstance) error {
179+
return deregisterInstance(c, i.ID)
180+
}
181+
182+
// deregisterInstance will drain all the loadbalancers attached to instance
183+
func deregisterInstance(c OpenstackCloud, instanceID string) error {
184+
instance, err := c.GetInstance(instanceID)
185+
if err != nil {
186+
return err
187+
}
188+
189+
// Kubernetes creates loadbalancers that member name matches to instance name
190+
// However, kOps uses different name format in API LB which is <cluster>-<ig>
191+
instanceName := instance.Name
192+
kopsName := ""
193+
ig, igok := instance.Metadata[TagKopsInstanceGroup]
194+
clusterName, clusterok := instance.Metadata[TagClusterName]
195+
if igok && clusterok {
196+
kopsName = fmt.Sprintf("%s-%s", clusterName, ig)
197+
}
198+
199+
lbs, err := c.ListLBs(loadbalancers.ListOpts{})
200+
if err != nil {
201+
return err
202+
}
203+
ctx := context.Background()
204+
eg, _ := errgroup.WithContext(ctx)
205+
for i := range lbs {
206+
func(lb loadbalancers.LoadBalancer) {
207+
eg.Go(func() error {
208+
return drainSingleLB(c, lb, instanceName, kopsName)
209+
})
210+
}(lbs[i])
211+
}
212+
213+
if err := eg.Wait(); err != nil {
214+
return fmt.Errorf("failed to deregister instance from load balancers: %v", err)
215+
}
216+
217+
return nil
218+
}
219+
220+
// drainSingleLB will drain single loadbalancer that is attached to instance
221+
func drainSingleLB(c OpenstackCloud, lb loadbalancers.LoadBalancer, instanceName string, kopsName string) error {
222+
oldStats, err := c.GetLBStats(lb.ID)
223+
if err != nil {
224+
return err
225+
}
226+
227+
draining := false
228+
pools, err := c.ListPools(v2pools.ListOpts{
229+
LoadbalancerID: lb.ID,
230+
})
231+
if err != nil {
232+
return err
233+
}
234+
for _, pool := range pools {
235+
members, err := c.ListPoolMembers(pool.ID, v2pools.ListMembersOpts{})
236+
if err != nil {
237+
return err
238+
}
239+
for _, member := range members {
240+
if member.Name == instanceName || (member.Name == kopsName && len(kopsName) > 0) {
241+
// https://docs.openstack.org/api-ref/load-balancer/v2/?expanded=update-a-member-detail
242+
// Setting the member weight to 0 means that the member will not receive new requests but will finish any existing connections.
243+
// This “drains” the backend member of active connections.
244+
_, err := c.UpdateMemberInPool(pool.ID, member.ID, v2pools.UpdateMemberOpts{
245+
Weight: fi.Int(0),
246+
})
247+
if err != nil {
248+
return err
249+
}
250+
draining = true
251+
break
252+
}
253+
}
254+
}
255+
256+
if draining {
257+
// TODO: should we do somekind of loop here and check that connections are really drained?
258+
time.Sleep(20 * time.Second)
259+
260+
newStats, err := c.GetLBStats(lb.ID)
261+
if err != nil {
262+
return err
263+
}
264+
265+
klog.Infof("Loadbalancer %s connections before draining %d and after %d", lb.Name, oldStats.ActiveConnections, newStats.ActiveConnections)
266+
}
267+
return nil
268+
}
269+
173270
// DetachInstance is not implemented yet. It needs to cause a cloud instance to no longer be counted against the group's size limits.
174271
func (c *openstackCloud) DetachInstance(i *cloudinstances.CloudInstance) error {
175272
return detachInstance(c, i)

0 commit comments

Comments
 (0)