Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve keepalived handling during updates #185

Merged
merged 29 commits into from
Jun 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1e4643a
Add revision as annotation to LBM
Jun 13, 2023
83af865
add yawol keepalived file to change prio of keepalived
dergeberl Jun 13, 2023
623f3fa
WIP
Jun 13, 2023
98ebd34
Write LBM ContainsKeepAlivedMaster condition
Jun 13, 2023
263ccc5
Add tests for set contains master condition & refactor
Jun 13, 2023
0ba6983
Remove nopreemt from keepalived to ensure keepalived will swap state …
dergeberl Jun 14, 2023
254034f
Modify scaledown of old loadbalancersets to ensure that in the new se…
dergeberl Jun 14, 2023
3aa993c
remove redundant annotation
dergeberl Jun 14, 2023
ca8f9d4
make linter happy
dergeberl Jun 14, 2023
935cc9c
Rename v1 to metav1
Jun 14, 2023
453674a
Simplify patching `lbs` status
Jun 14, 2023
37051da
Rename `ContainsKeepalivedMaster´ to `HasKeepalivedMaster`
Jun 14, 2023
4d51e84
Do patches instead of updates; Improve tests
Jun 14, 2023
ba4da05
use aferoFs for revisionFile and rename function
dergeberl Jun 14, 2023
d4543ed
add comment to yawolfile in keepalived
dergeberl Jun 14, 2023
bfaac5b
rename yawollet keepalived file and create folder of needed
dergeberl Jun 14, 2023
c895dd1
LBSetContainsKeepalivedMaster is also true if there is no condition a…
dergeberl Jun 14, 2023
5f875e5
Use MatchingLabels instead of ListOptions
Jun 14, 2023
46fc909
Rename isMachineMaster to isMachineKeepalivedMaster
Jun 14, 2023
2a9be25
Rename filesystem variables and members
timebertt Jun 14, 2023
b8a4b0c
Move `/var/yawol` to `/var/lib/yawol`
timebertt Jun 14, 2023
34080fe
Make linter happy
timebertt Jun 14, 2023
3bbeefa
Improve `Should eventually have a master` test
Jun 14, 2023
74ae426
create /var/lib/yawol folder in image
dergeberl Jun 14, 2023
5d32018
add printcolumn for lbs
dergeberl Jun 14, 2023
bc9a2ac
Add missing yawollet permissions for `PATCH events`
timebertt Jun 14, 2023
d4637bb
write on 1 in yawol RevisionFile
dergeberl Jun 14, 2023
6e38e48
rename vrrp_track_file to track_file
dergeberl Jun 14, 2023
a46f4ff
write on 1 in yawol RevisionFile
dergeberl Jun 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions api/v1beta1/loadbalancerset_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
// +kubebuilder:printcolumn:name="DESIRED",type=string,JSONPath=`.spec.replicas`
// +kubebuilder:printcolumn:name="CURRENT",type=string,JSONPath=`.status.replicas`
// +kubebuilder:printcolumn:name="READY",type=string,JSONPath=`.status.readyReplicas`
// +kubebuilder:printcolumn:name="HasKeepalivedMaster",type=string,JSONPath=`.status.conditions[?(@.type=="HasKeepalivedMaster")].status`
// +kubebuilder:printcolumn:name="AGE",type=date,JSONPath=`.metadata.creationTimestamp`

// LoadBalancerSet is the Schema for the LoadBalancerSet's API.
Expand Down Expand Up @@ -48,6 +49,9 @@ type LoadBalancerSetStatus struct {
// AvailableReplicas are the current running replicas.
// +optional
AvailableReplicas *int `json:"availableReplicas,omitempty"`
// Conditions contains condition information for a LoadBalancerSet.
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
dergeberl marked this conversation as resolved.
Show resolved Hide resolved
// ReadyReplicas are the current ready replicas.
// +optional
ReadyReplicas *int `json:"readyReplicas,omitempty"`
Expand Down
7 changes: 7 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ spec:
- jsonPath: .status.readyReplicas
name: READY
type: string
- jsonPath: .status.conditions[?(@.type=="HasKeepalivedMaster")].status
name: HasKeepalivedMaster
type: string
- jsonPath: .metadata.creationTimestamp
name: AGE
type: date
Expand Down Expand Up @@ -289,6 +292,75 @@ spec:
availableReplicas:
description: AvailableReplicas are the current running replicas.
type: integer
conditions:
description: Conditions contains condition information for a LoadBalancerSet.
items:
description: "Condition contains details for one aspect of the current
state of this API Resource. --- This struct is intended for direct
use as an array at the field path .status.conditions. For example,
\n type FooStatus struct{ // Represents the observations of a
foo's current state. // Known .status.conditions.type are: \"Available\",
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
// +listType=map // +listMapKey=type Conditions []metav1.Condition
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
properties:
lastTransitionTime:
description: lastTransitionTime is the last time the condition
transitioned from one status to another. This should be when
the underlying condition changed. If that is not known, then
using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: message is a human readable message indicating
details about the transition. This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: observedGeneration represents the .metadata.generation
that the condition was set based upon. For instance, if .metadata.generation
is currently 12, but the .status.conditions[x].observedGeneration
is 9, the condition is out of date with respect to the current
state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: reason contains a programmatic identifier indicating
the reason for the condition's last transition. Producers
of specific condition types may define expected values and
meanings for this field, and whether the values are considered
a guaranteed API. The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
--- Many .condition.type values are consistent across resources
like Available, but because arbitrary conditions can be useful
(see .node.status.conditions), the ability to deconflict is
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
readyReplicas:
description: ReadyReplicas are the current ready replicas.
type: integer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import (

const (
DefaultRequeueTime = 10 * time.Millisecond
RevisionAnnotation = "loadbalancer.yawol.stackit.cloud/revision"
ServiceFinalizer = "yawol.stackit.cloud/controller2"
)

Expand Down Expand Up @@ -983,6 +982,9 @@ func (r *Reconciler) reconcileLoadBalancerSet(
}

if !downscaled {
if !helper.LBSetHasKeepalivedMaster(loadBalancerSet) {
return ctrl.Result{RequeueAfter: 5 * time.Second}, nil
}
r.Log.Info("scale down all lbsets except of", "lbs", loadBalancerSet.Name)
return helper.ScaleDownAllLoadBalancerSetsForLBBut(ctx, r.Client, lb, loadBalancerSet.Name)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package loadbalancer
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
Expand All @@ -17,14 +18,14 @@ import (
"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"

yawolv1beta1 "github.com/stackitcloud/yawol/api/v1beta1"
"github.com/stackitcloud/yawol/internal/helper"
"github.com/stackitcloud/yawol/internal/openstack"
"github.com/stackitcloud/yawol/internal/openstack/testing"
v1 "k8s.io/api/core/v1"

k8sErrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/pointer"
runtimeClient "sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -176,10 +177,7 @@ var _ = Describe("loadbalancer controller", Serial, Ordered, func() {
It("should create a loadbalancerset", func() {
hopefully(lbNN, func(g Gomega, act LB) error {
var lbsetList yawolv1beta1.LoadBalancerSetList
g.Expect(k8sClient.List(ctx, &lbsetList, &runtimeClient.ListOptions{
LabelSelector: labels.SelectorFromSet(lb.Spec.Selector.MatchLabels),
})).Should(Succeed())

g.Expect(k8sClient.List(ctx, &lbsetList, runtimeClient.MatchingLabels(lb.Spec.Selector.MatchLabels))).Should(Succeed())
g.Expect(len(lbsetList.Items)).Should(Equal(1))

// prevent later panic
Expand All @@ -204,10 +202,7 @@ var _ = Describe("loadbalancer controller", Serial, Ordered, func() {
By("waiting for lbset creation")
hopefully(lbNN, func(g Gomega, act LB) error {
var lbsetList yawolv1beta1.LoadBalancerSetList
g.Expect(k8sClient.List(ctx, &lbsetList, &runtimeClient.ListOptions{
LabelSelector: labels.SelectorFromSet(lb.Spec.Selector.MatchLabels),
})).Should(Succeed())

g.Expect(k8sClient.List(ctx, &lbsetList, runtimeClient.MatchingLabels(lb.Spec.Selector.MatchLabels))).Should(Succeed())
g.Expect(len(lbsetList.Items)).Should(Equal(1))

lbset := lbsetList.Items[0]
Expand All @@ -227,10 +222,7 @@ var _ = Describe("loadbalancer controller", Serial, Ordered, func() {
By("checking for a new lbset")
hopefully(lbNN, func(g Gomega, act LB) error {
var lbsetList yawolv1beta1.LoadBalancerSetList
g.Expect(k8sClient.List(ctx, &lbsetList, &runtimeClient.ListOptions{
LabelSelector: labels.SelectorFromSet(lb.Spec.Selector.MatchLabels),
})).Should(Succeed())

g.Expect(k8sClient.List(ctx, &lbsetList, runtimeClient.MatchingLabels(lb.Spec.Selector.MatchLabels))).Should(Succeed())
g.Expect(len(lbsetList.Items)).Should(Equal(2))

By("testing if the new set got a different hash")
Expand All @@ -248,14 +240,117 @@ var _ = Describe("loadbalancer controller", Serial, Ordered, func() {
})
})

It("should scale down old lbset after new one has ready keepalived", func() {
var oldLbs runtimeClient.ObjectKey
By("waiting for lbset creation")
hopefully(lbNN, func(g Gomega, act LB) error {
var lbsetList yawolv1beta1.LoadBalancerSetList
g.Expect(k8sClient.List(ctx, &lbsetList, runtimeClient.MatchingLabels(lb.Spec.Selector.MatchLabels))).Should(Succeed())
g.Expect(len(lbsetList.Items)).Should(Equal(1))
oldLbs = runtimeClient.ObjectKeyFromObject(&lbsetList.Items[0])
return nil
})

By("changing flavorid")
updateLB(lbNN, func(act *LB) {
act.Spec.Infrastructure.Flavor = yawolv1beta1.OpenstackFlavorRef{
FlavorID: pointer.String("somenewid"),
}
})

var newLbs runtimeClient.ObjectKey
By("checking for a new lbset")
hopefully(lbNN, func(g Gomega, act LB) error {
var lbsetList yawolv1beta1.LoadBalancerSetList
g.Expect(k8sClient.List(ctx, &lbsetList, runtimeClient.MatchingLabels(lb.Spec.Selector.MatchLabels))).Should(Succeed())
g.Expect(len(lbsetList.Items)).Should(Equal(2))
for _, lbs := range lbsetList.Items {
if lbs.Annotations[helper.RevisionAnnotation] == "2" {
newLbs = runtimeClient.ObjectKeyFromObject(&lbs)
}
}
return nil
})

By("Make both lbsets available by patching status")
var lbsetList yawolv1beta1.LoadBalancerSetList
Expect(k8sClient.List(ctx, &lbsetList, runtimeClient.MatchingLabels(lb.Spec.Selector.MatchLabels))).Should(Succeed())
for _, lbs := range lbsetList.Items {
patch := runtimeClient.MergeFrom(lbs.DeepCopy())
lbs.Status.ReadyReplicas = &lbs.Spec.Replicas
lbs.Status.Replicas = &lbs.Spec.Replicas
Expect(k8sClient.Status().Patch(ctx, &lbs, patch)).Should(Succeed())
}

By("Old lbset should be scaled up because keepalived is not ready now")
Consistently(func() error {
var lbs yawolv1beta1.LoadBalancerSet
if err := k8sClient.Get(ctx, oldLbs, &lbs); err != nil {
return err
}
if lbs.Spec.Replicas == 0 {
return errors.New("already down scaled")
}

return nil
}, time.Second*8).Should(Succeed())

By("Make latest lbsets keepalived condition true by patching status")
var lbs yawolv1beta1.LoadBalancerSet
Expect(k8sClient.Get(ctx, newLbs, &lbs)).Should(Succeed())
patch := runtimeClient.MergeFrom(lbs.DeepCopy())
lbs.Status.Conditions = []metav1.Condition{
{
Type: helper.HasKeepalivedMaster,
Status: metav1.ConditionTrue,
LastTransitionTime: metav1.Now(),
Reason: "ready",
Message: "ready",
},
}
Expect(k8sClient.Status().Patch(ctx, &lbs, patch)).Should(Succeed())

By("Old lbset should be scaled up because keepalived is not ready long enough")
Consistently(func() error {
var lbs yawolv1beta1.LoadBalancerSet
if err := k8sClient.Get(ctx, oldLbs, &lbs); err != nil {
return err
}
if lbs.Spec.Replicas == 0 {
return errors.New("already down scaled")
}

return nil
}, time.Second*8).Should(Succeed())

By("Make latest lbsets keepalived condition true since longer")
Expect(k8sClient.Get(ctx, newLbs, &lbs)).Should(Succeed())
patch = runtimeClient.MergeFrom(lbs.DeepCopy())
lbs.Status.Conditions = []metav1.Condition{
{
Type: helper.HasKeepalivedMaster,
Status: metav1.ConditionTrue,
LastTransitionTime: metav1.Time{Time: metav1.Now().Add(-120 * time.Second)},
Reason: "ready",
Message: "ready",
},
}
Expect(k8sClient.Status().Patch(ctx, &lbs, patch)).Should(Succeed())

By("Old lbs should be downscaled")
hopefully(lbNN, func(g Gomega, act LB) error {
var lbs yawolv1beta1.LoadBalancerSet
g.Expect(k8sClient.Get(ctx, oldLbs, &lbs)).Should(Succeed())
g.Expect(lbs.Spec.Replicas).To(Equal(0))
return nil
})
})

It("should up- and downscale loadbalancer machines", func() {
By("waiting for lb and lbset creation")
hopefully(lbNN, func(g Gomega, act LB) error {
var lbsetList yawolv1beta1.LoadBalancerSetList
g.Expect(k8sClient.List(ctx, &lbsetList, &runtimeClient.ListOptions{
LabelSelector: labels.SelectorFromSet(lb.Spec.Selector.MatchLabels),
})).Should(Succeed())

g.Expect(k8sClient.List(ctx, &lbsetList, runtimeClient.MatchingLabels(lb.Spec.Selector.MatchLabels))).Should(Succeed())
g.Expect(len(lbsetList.Items)).Should(Equal(1))

lbset := lbsetList.Items[0]
Expand All @@ -270,10 +365,7 @@ var _ = Describe("loadbalancer controller", Serial, Ordered, func() {

Eventually(func(g Gomega) {
var lbsetList yawolv1beta1.LoadBalancerSetList
g.Expect(k8sClient.List(ctx, &lbsetList, &runtimeClient.ListOptions{
LabelSelector: labels.SelectorFromSet(lb.Spec.Selector.MatchLabels),
})).Should(Succeed())

g.Expect(k8sClient.List(ctx, &lbsetList, runtimeClient.MatchingLabels(lb.Spec.Selector.MatchLabels))).Should(Succeed())
g.Expect(len(lbsetList.Items)).Should(Equal(1))

lbset := lbsetList.Items[0]
Expand All @@ -287,10 +379,7 @@ var _ = Describe("loadbalancer controller", Serial, Ordered, func() {

Eventually(func(g Gomega) {
var lbsetList yawolv1beta1.LoadBalancerSetList
g.Expect(k8sClient.List(ctx, &lbsetList, &runtimeClient.ListOptions{
LabelSelector: labels.SelectorFromSet(lb.Spec.Selector.MatchLabels),
})).Should(Succeed())

g.Expect(k8sClient.List(ctx, &lbsetList, runtimeClient.MatchingLabels(lb.Spec.Selector.MatchLabels))).Should(Succeed())
g.Expect(len(lbsetList.Items)).Should(Equal(1))

lbset := lbsetList.Items[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (r *LoadBalancerSetStatusReconciler) Reconcile(ctx context.Context, req ctr
}

// Not the current revision
if lb.Annotations[RevisionAnnotation] != loadBalancerSet.Annotations[RevisionAnnotation] {
if lb.Annotations[helper.RevisionAnnotation] != loadBalancerSet.Annotations[helper.RevisionAnnotation] {
return ctrl.Result{}, nil
}

Expand Down
Loading