Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
44 changes: 44 additions & 0 deletions examples/kubernetes_multicluster/canary/app.pipecd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
apiVersion: pipecd.dev/v1beta1
kind: KubernetesApp
spec:
name: canary-multicluster
labels:
env: example
team: product
description: |
This app demonstrates how to deploy a Kubernetes application across multiple clusters
using a Canary strategy with the kubernetes_multicluster plugin.
The canary variant is first rolled out to cluster-us only, then after approval
the primary rollout is applied to all clusters, and finally the canary resources
are cleaned up with K8S_CANARY_CLEAN.
plugins:
kubernetes_multicluster:
input:
multiTargets:
- target:
name: cluster-us
manifests:
- cluster-us/deployment.yaml
- cluster-us/service.yaml
- target:
name: cluster-eu
manifests:
- cluster-eu/deployment.yaml
- cluster-eu/service.yaml
pipeline:
stages:
# Deploy the canary variant to cluster-us only (10% of replicas).
- name: K8S_CANARY_ROLLOUT
with:
replicas: 10%
multiTarget:
- target:
name: cluster-us
# Wait for approval before rolling out to all clusters.
- name: WAIT_APPROVAL
# Roll out the new version as primary to all clusters.
- name: K8S_PRIMARY_ROLLOUT
with:
prune: true
# Remove the canary variant resources from all clusters.
- name: K8S_CANARY_CLEAN
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: canary-multicluster
labels:
app: canary-multicluster
spec:
replicas: 2
revisionHistoryLimit: 2
selector:
matchLabels:
app: canary-multicluster
pipecd.dev/variant: primary
template:
metadata:
labels:
app: canary-multicluster
pipecd.dev/variant: primary
spec:
containers:
- name: helloworld
image: ghcr.io/pipe-cd/helloworld:v0.32.0
args:
- server
ports:
- containerPort: 9085
11 changes: 11 additions & 0 deletions examples/kubernetes_multicluster/canary/cluster-eu/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: canary-multicluster
spec:
selector:
app: canary-multicluster
ports:
- protocol: TCP
port: 9085
targetPort: 9085
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: canary-multicluster
labels:
app: canary-multicluster
spec:
replicas: 2
revisionHistoryLimit: 2
selector:
matchLabels:
app: canary-multicluster
pipecd.dev/variant: primary
template:
metadata:
labels:
app: canary-multicluster
pipecd.dev/variant: primary
spec:
containers:
- name: helloworld
image: ghcr.io/pipe-cd/helloworld:v0.32.0
args:
- server
ports:
- containerPort: 9085
11 changes: 11 additions & 0 deletions examples/kubernetes_multicluster/canary/cluster-us/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: canary-multicluster
spec:
selector:
app: canary-multicluster
ports:
- protocol: TCP
port: 9085
targetPort: 9085
Original file line number Diff line number Diff line change
Expand Up @@ -356,3 +356,87 @@ func patchManifest(m provider.Manifest, patch kubeconfig.K8sResourcePatch) (*pro

return buildManifest(proc.Bytes())
}

func (p *Plugin) executeK8sMultiCanaryCleanStage(ctx context.Context, input *sdk.ExecuteStageInput[kubeconfig.KubernetesApplicationSpec], dts []*sdk.DeployTarget[kubeconfig.KubernetesDeployTargetConfig]) sdk.StageStatus {
lp := input.Client.LogPersister()

cfg, err := input.Request.TargetDeploymentSource.AppConfig()
if err != nil {
lp.Errorf("Failed while decoding application config (%v)", err)
return sdk.StageStatusFailure
}

deployTargetMap := make(map[string]*sdk.DeployTarget[kubeconfig.KubernetesDeployTargetConfig], len(dts))
for _, dt := range dts {
deployTargetMap[dt.Name] = dt
}

type targetConfig struct {
deployTarget *sdk.DeployTarget[kubeconfig.KubernetesDeployTargetConfig]
}

targetConfigs := make([]targetConfig, 0, len(dts))
if len(cfg.Spec.Input.MultiTargets) == 0 {
for _, dt := range dts {
targetConfigs = append(targetConfigs, targetConfig{deployTarget: dt})
}
} else {
for _, mt := range cfg.Spec.Input.MultiTargets {
dt, ok := deployTargetMap[mt.Target.Name]
if !ok {
lp.Infof("Ignore multi target '%s': not matched any deployTarget", mt.Target.Name)
continue
}
targetConfigs = append(targetConfigs, targetConfig{deployTarget: dt})
}
}

eg, ctx := errgroup.WithContext(ctx)
for _, tc := range targetConfigs {
eg.Go(func() error {
lp.Infof("Start cleaning CANARY variant on target %s", tc.deployTarget.Name)
if err := p.canaryClean(ctx, input, tc.deployTarget, cfg); err != nil {
return fmt.Errorf("failed to clean CANARY variant on target %s: %w", tc.deployTarget.Name, err)
}
return nil
})
}

if err := eg.Wait(); err != nil {
lp.Errorf("Failed while cleaning CANARY variant (%v)", err)
return sdk.StageStatusFailure
}

return sdk.StageStatusSuccess
}

func (p *Plugin) canaryClean(
ctx context.Context,
input *sdk.ExecuteStageInput[kubeconfig.KubernetesApplicationSpec],
dt *sdk.DeployTarget[kubeconfig.KubernetesDeployTargetConfig],
cfg *sdk.ApplicationConfig[kubeconfig.KubernetesApplicationSpec],
) error {
lp := input.Client.LogPersister()

var (
appCfg = cfg.Spec
variantLabel = appCfg.VariantLabel.Key
canaryVariant = appCfg.VariantLabel.CanaryValue
)

toolRegistry := toolregistry.NewRegistry(input.Client.ToolRegistry())

kubectlPath, err := toolRegistry.Kubectl(ctx, cmp.Or(appCfg.Input.KubectlVersion, dt.Config.KubectlVersion))
if err != nil {
return fmt.Errorf("failed while getting kubectl tool: %w", err)
}

kubectl := provider.NewKubectl(kubectlPath)
applier := provider.NewApplier(kubectl, appCfg.Input, dt.Config, input.Logger)

if err := deleteVariantResources(ctx, lp, kubectl, dt.Config.KubeConfigPath, applier, input.Request.Deployment.ApplicationID, variantLabel, canaryVariant); err != nil {
return fmt.Errorf("unable to remove canary resources: %w", err)
}

return nil
}
Loading
Loading