Skip to content

Commit

Permalink
rolling deployment in partition-style
Browse files Browse the repository at this point in the history
Signed-off-by: mingzhou.swx <[email protected]>
  • Loading branch information
mingzhou.swx committed Jan 18, 2023
1 parent 843e8b8 commit dc47c06
Show file tree
Hide file tree
Showing 24 changed files with 1,617 additions and 53 deletions.
20 changes: 19 additions & 1 deletion .github/workflows/e2e-advanced-deployment-1.19.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ jobs:
echo "Timeout to wait for kruise-rollout ready"
exit 1
fi
- name: Run E2E Tests
- name: Run E2E Tests For Deployment Controller
run: |
export KUBECONFIG=/home/runner/.kube/config
make ginkgo
Expand All @@ -108,3 +108,21 @@ jobs:
exit 1
fi
exit $retVal
- name: Run E2E Tests For Control Plane
run: |
export KUBECONFIG=/home/runner/.kube/config
make ginkgo
set +e
./bin/ginkgo -timeout 60m -v --focus='Advanced Deployment canary rollout with Ingress' test/e2e
retVal=$?
# kubectl get pod -n kruise-rollout --no-headers | grep manager | awk '{print $1}' | xargs kubectl logs -n kruise-rollout
restartCount=$(kubectl get pod -n kruise-rollout --no-headers | awk '{print $4}')
if [ "${restartCount}" -eq "0" ];then
echo "Kruise-rollout has not restarted"
else
kubectl get pod -n kruise-rollout --no-headers
echo "Kruise-rollout has restarted, abort!!!"
kubectl get pod -n kruise-rollout --no-headers| awk '{print $1}' | xargs kubectl logs -p -n kruise-rollout
exit 1
fi
exit $retVal
20 changes: 19 additions & 1 deletion .github/workflows/e2e-advanced-deployment-1.23.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ jobs:
echo "Timeout to wait for kruise-rollout ready"
exit 1
fi
- name: Run E2E Tests
- name: Run E2E Tests For Deployment Controller
run: |
export KUBECONFIG=/home/runner/.kube/config
make ginkgo
Expand All @@ -107,4 +107,22 @@ jobs:
kubectl get pod -n kruise-rollout --no-headers| awk '{print $1}' | xargs kubectl logs -p -n kruise-rollout
exit 1
fi
exit $retVal
- name: Run E2E Tests For Control Plane
run: |
export KUBECONFIG=/home/runner/.kube/config
make ginkgo
set +e
./bin/ginkgo -timeout 60m -v --focus='Advanced Deployment canary rollout with Ingress' test/e2e
retVal=$?
# kubectl get pod -n kruise-rollout --no-headers | grep manager | awk '{print $1}' | xargs kubectl logs -n kruise-rollout
restartCount=$(kubectl get pod -n kruise-rollout --no-headers | awk '{print $4}')
if [ "${restartCount}" -eq "0" ];then
echo "Kruise-rollout has not restarted"
else
kubectl get pod -n kruise-rollout --no-headers
echo "Kruise-rollout has restarted, abort!!!"
kubectl get pod -n kruise-rollout --no-headers| awk '{print $1}' | xargs kubectl logs -p -n kruise-rollout
exit 1
fi
exit $retVal
4 changes: 4 additions & 0 deletions api/v1alpha1/deployment_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ const (
// DeploymentExtraStatusAnnotation is annotation for deployment,
// which is extra status field of Advanced Deployment.
DeploymentExtraStatusAnnotation = "rollouts.kruise.io/deployment-extra-status"

// DeploymentStableRevisionLabel is label for deployment,
// which record the stable revision during the current rolling process.
DeploymentStableRevisionLabel = "rollouts.kruise.io/stable-revision"
)

// DeploymentStrategy is strategy field for Advanced Deployment
Expand Down
13 changes: 8 additions & 5 deletions api/v1alpha1/rollout_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,15 @@ const (
// RollbackInBatchAnnotation allow use disable quick rollback, and will roll back in batch style.
RollbackInBatchAnnotation = "rollouts.kruise.io/rollback-in-batch"

// DeploymentRolloutStyleAnnotation define the rolling behavior for Deployment.
// RolloutStyleAnnotation define the rolling behavior for Deployment.
// must be "partition" or "canary":
// * "partition" means rolling Deployment in batches just like CloneSet, and will NOT create any extra Deployment;
// * "canary" means rolling in canary way, and will create a canary Deployment.
// Defaults to canary
DeploymentRolloutStyleAnnotation = "rollouts.kruise.io/deployment-rolling-style"
// * "partition" means rolling in batches just like CloneSet, and will NOT create any extra Workload;
// * "canary" means rolling in canary way, and will create a canary Workload.
// Currently, only Deployment support both "partition" and "canary" rolling styles.
// For other workload types, they only support "partition" styles.
// Defaults to "canary" to Deployment.
// Defaults to "partition" to the others.
RolloutStyleAnnotation = "rollouts.kruise.io/rolling-style"
)

// RolloutSpec defines the desired state of Rollout
Expand Down
5 changes: 4 additions & 1 deletion config/default/manager_auth_proxy_patch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@ spec:
- "--metrics-bind-address=127.0.0.1:8080"
- "--leader-elect"
- "--feature-gates=AdvancedDeployment=true"
- "--v=3"
- "--v=5"
env:
- name: KUBE_CACHE_MUTATION_DETECTOR
value: "true"
17 changes: 12 additions & 5 deletions pkg/controller/batchrelease/batchrelease_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,17 @@ package batchrelease
import (
"fmt"
"reflect"
"strings"
"time"

appsv1alpha1 "github.com/openkruise/kruise-api/apps/v1alpha1"
"github.com/openkruise/rollouts/api/v1alpha1"
"github.com/openkruise/rollouts/pkg/controller/batchrelease/control"
"github.com/openkruise/rollouts/pkg/controller/batchrelease/control/canarystyle"
"github.com/openkruise/rollouts/pkg/controller/batchrelease/control/canarystyle/deployment"
canarydeployment "github.com/openkruise/rollouts/pkg/controller/batchrelease/control/canarystyle/deployment"
"github.com/openkruise/rollouts/pkg/controller/batchrelease/control/partitionstyle"
"github.com/openkruise/rollouts/pkg/controller/batchrelease/control/partitionstyle/cloneset"
partitiondeployment "github.com/openkruise/rollouts/pkg/controller/batchrelease/control/partitionstyle/deployment"
"github.com/openkruise/rollouts/pkg/controller/batchrelease/control/partitionstyle/statefulset"
"github.com/openkruise/rollouts/pkg/util"
apps "k8s.io/api/apps/v1"
Expand Down Expand Up @@ -195,19 +197,24 @@ func (r *Executor) getReleaseController(release *v1alpha1.BatchRelease, newStatu
switch targetRef.APIVersion {
case appsv1alpha1.GroupVersion.String():
if targetRef.Kind == reflect.TypeOf(appsv1alpha1.CloneSet{}).Name() {
klog.InfoS("Using CloneSet batch release controller for this batch release", "workload name", targetKey.Name, "namespace", targetKey.Namespace)
klog.InfoS("Using CloneSet partition-style release controller for this batch release", "workload name", targetKey.Name, "namespace", targetKey.Namespace)
return partitionstyle.NewControlPlane(cloneset.NewController, r.client, r.recorder, release, newStatus, targetKey, gvk), nil
}

case apps.SchemeGroupVersion.String():
if targetRef.Kind == reflect.TypeOf(apps.Deployment{}).Name() {
klog.InfoS("Using Deployment batch release controller for this batch release", "workload name", targetKey.Name, "namespace", targetKey.Namespace)
return canarystyle.NewControlPlane(deployment.NewController, r.client, r.recorder, release, newStatus, targetKey), nil
if strings.EqualFold(release.Annotations[v1alpha1.RolloutStyleAnnotation], string(v1alpha1.PartitionRollingStyle)) {
klog.InfoS("Using Deployment partition-style release controller for this batch release", "workload name", targetKey.Name, "namespace", targetKey.Namespace)
return partitionstyle.NewControlPlane(partitiondeployment.NewController, r.client, r.recorder, release, newStatus, targetKey, gvk), nil
} else {
klog.InfoS("Using Deployment canary-style release controller for this batch release", "workload name", targetKey.Name, "namespace", targetKey.Namespace)
return canarystyle.NewControlPlane(canarydeployment.NewController, r.client, r.recorder, release, newStatus, targetKey), nil
}
}
}

// try to use StatefulSet-like rollout controller by default
klog.InfoS("Using StatefulSet-like batch release controller for this batch release", "workload name", targetKey.Name, "namespace", targetKey.Namespace)
klog.InfoS("Using StatefulSet-Like partition-style release controller for this batch release", "workload name", targetKey.Name, "namespace", targetKey.Namespace)
return partitionstyle.NewControlPlane(statefulset.NewController, r.client, r.recorder, release, newStatus, targetKey, gvk), nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func (rc *realController) Finalize(release *v1alpha1.BatchRelease) error {
if err := rc.client.Patch(context.TODO(), clone, client.RawPatch(types.MergePatchType, []byte(body))); err != nil {
return err
}
klog.Infof("Successfully finalize StatefulSet %v", klog.KObj(rc.object))
klog.Infof("Successfully finalize CloneSet %v", klog.KObj(rc.object))
return nil
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/*
Copyright 2022 The Kruise Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package deployment

import (
"context"
"encoding/json"

"github.com/openkruise/rollouts/api/v1alpha1"
batchcontext "github.com/openkruise/rollouts/pkg/controller/batchrelease/context"
"github.com/openkruise/rollouts/pkg/controller/batchrelease/control"
"github.com/openkruise/rollouts/pkg/controller/batchrelease/control/partitionstyle"
deploymentutil "github.com/openkruise/rollouts/pkg/controller/deployment/util"
"github.com/openkruise/rollouts/pkg/util"
apps "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/util/retry"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
)

type realController struct {
*util.WorkloadInfo
client client.Client
pods []*corev1.Pod
key types.NamespacedName
object *apps.Deployment
}

func NewController(cli client.Client, key types.NamespacedName, _ schema.GroupVersionKind) partitionstyle.Interface {
return &realController{
key: key,
client: cli,
}
}

func (rc *realController) GetInfo() *util.WorkloadInfo {
return rc.WorkloadInfo
}

func (rc *realController) BuildController() (partitionstyle.Interface, error) {
if rc.object != nil {
return rc, nil
}
object := &apps.Deployment{}
if err := rc.client.Get(context.TODO(), rc.key, object); err != nil {
return rc, err
}
rc.object = object
workloadInfo, err := rc.getWorkloadInfo(object)
if err != nil {
return nil, err
}
rc.WorkloadInfo = workloadInfo
return rc, nil
}

func (rc *realController) ListOwnedPods() ([]*corev1.Pod, error) {
if rc.pods != nil {
return rc.pods, nil
}
var err error
rc.pods, err = util.ListOwnedPods(rc.client, rc.object)
return rc.pods, err
}

func (rc *realController) Initialize(release *v1alpha1.BatchRelease) error {
if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
d := &apps.Deployment{}
if err := rc.client.Get(context.TODO(), rc.key, d); err != nil {
return err
}
if control.IsControlledByBatchRelease(release, d) {
return nil
}
if d.Annotations == nil {
d.Annotations = map[string]string{}
}
// set strategy to annotations
strategy := util.UnmarshalledDeploymentStrategy(d)
rollingUpdate := strategy.RollingUpdate
if d.Spec.Strategy.RollingUpdate != nil {
rollingUpdate = d.Spec.Strategy.RollingUpdate
}
d.Annotations[v1alpha1.DeploymentStrategyAnnotation] = util.MarshalledDeploymentStrategy(
v1alpha1.PartitionRollingStyle, rollingUpdate, intstr.FromInt(0), false)
// claim the deployment is under our control
owner, _ := json.Marshal(metav1.NewControllerRef(release, release.GetObjectKind().GroupVersionKind()))
d.Annotations[util.BatchReleaseControlAnnotation] = string(owner)
// disable the native deployment controller
d.Spec.Paused = true
d.Spec.Strategy = apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType}
return rc.client.Update(context.TODO(), d)
}); err != nil {
return err
}

klog.Infof("Successfully initialized Deployment %v", rc.key)
return nil
}

func (rc *realController) UpgradeBatch(ctx *batchcontext.BatchContext) error {
if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
d := &apps.Deployment{}
if err := rc.client.Get(context.TODO(), rc.key, d); err != nil {
return err
}
if !deploymentutil.IsUnderRolloutControl(d) {
klog.Warningf("Cannot upgrade batch because Deployment %v is not under our control", rc.key)
return nil
}
strategy := util.UnmarshalledDeploymentStrategy(d)
if control.IsCurrentMoreThanOrEqualToDesired(strategy.Partition, ctx.DesiredPartition) {
return nil
}
strategy.Partition = ctx.DesiredPartition
strategyAnno, _ := json.Marshal(&strategy)
d.Annotations[v1alpha1.DeploymentStrategyAnnotation] = string(strategyAnno)
return rc.client.Update(context.TODO(), d)
}); err != nil {
return err
}

klog.Infof("Successfully submit partition %v for Deployment %v", ctx.DesiredPartition, rc.key)
return nil
}

func (rc *realController) Finalize(release *v1alpha1.BatchRelease) error {
if rc.object == nil {
return nil
}

if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
d := &apps.Deployment{}
if err := rc.client.Get(context.TODO(), rc.key, d); err != nil {
return client.IgnoreNotFound(err)
}
if release.Spec.ReleasePlan.BatchPartition == nil {
d.Spec.Paused = false
strategy := util.UnmarshalledDeploymentStrategy(d)
d.Spec.Strategy.Type = apps.RollingUpdateDeploymentStrategyType
d.Spec.Strategy.RollingUpdate = strategy.RollingUpdate
delete(d.Annotations, v1alpha1.DeploymentStrategyAnnotation)
delete(d.Annotations, v1alpha1.DeploymentExtraStatusAnnotation)
delete(d.Labels, v1alpha1.DeploymentStableRevisionLabel)
}
delete(d.Annotations, util.BatchReleaseControlAnnotation)
return rc.client.Update(context.TODO(), d)
}); err != nil {
return err
}

klog.Infof("Successfully finalize Deployment %v", klog.KObj(rc.object))
return nil
}

func (rc *realController) CalculateBatchContext(release *v1alpha1.BatchRelease) (*batchcontext.BatchContext, error) {
rolloutID := release.Spec.ReleasePlan.RolloutID
if rolloutID != "" {
// if rollout-id is set, the pod will be patched batch label,
// so we have to list pod here.
if _, err := rc.ListOwnedPods(); err != nil {
return nil, err
}
}

currentBatch := release.Status.CanaryStatus.CurrentBatch
desiredPartition := release.Spec.ReleasePlan.Batches[currentBatch].CanaryReplicas
PlannedUpdatedReplicas := deploymentutil.NewRSReplicasLimit(desiredPartition, rc.object)

return &batchcontext.BatchContext{
Pods: rc.pods,
RolloutID: rolloutID,
CurrentBatch: currentBatch,
UpdateRevision: release.Status.UpdateRevision,
DesiredPartition: desiredPartition,
FailureThreshold: release.Spec.ReleasePlan.FailureThreshold,

Replicas: rc.Replicas,
UpdatedReplicas: rc.Status.UpdatedReplicas,
UpdatedReadyReplicas: rc.Status.UpdatedReadyReplicas,
PlannedUpdatedReplicas: PlannedUpdatedReplicas,
DesiredUpdatedReplicas: PlannedUpdatedReplicas,
}, nil
}

func (rc *realController) getWorkloadInfo(d *apps.Deployment) (*util.WorkloadInfo, error) {
workloadInfo := util.ParseWorkload(d)
extraStatus := util.UnmarshalledDeploymentExtraStatus(d)
workloadInfo.Status.UpdatedReadyReplicas = extraStatus.UpdatedReadyReplicas
finder := util.NewControllerFinder(rc.client)
rss, err := finder.GetReplicaSetsForDeployment(d)
if err != nil {
return nil, err
}
_, oldRS := util.FindCanaryAndStableReplicaSet(rss, d)
if oldRS != nil {
workloadInfo.Status.StableRevision = oldRS.Labels[apps.DefaultDeploymentUniqueLabelKey]
}
return workloadInfo, nil
}
Loading

0 comments on commit dc47c06

Please sign in to comment.