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

Support for BlueGreen rollout preview endpoint #199

Merged
Merged
Show file tree
Hide file tree
Changes from 10 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
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ gen-yaml:
kustomize build ./install/sample/overlays/rollout-bluegreen > ./out/yaml/sample-greeting-rollout-bluegreen.yaml
kustomize build ./install/sample/overlays/remote > ./out/yaml/remotecluster_sample.yaml
cp ./install/sample/sample_dep.yaml ./out/yaml/sample_dep.yaml
cp ./install/sample/greeting_preview.yaml ./out/yaml/greeting_preview.yaml
cp ./install/sample/gtp.yaml ./out/yaml/gtp.yaml
cp ./install/sample/gtp_failover.yaml ./out/yaml/gtp_failover.yaml
cp ./install/sample/gtp_topology.yaml ./out/yaml/gtp_topology.yaml
Expand Down
11 changes: 7 additions & 4 deletions admiral/cmd/admiral/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import (
"context"
"flag"
"fmt"
"os"
"os/signal"
"syscall"
"time"

"github.com/istio-ecosystem/admiral/admiral/pkg/apis/admiral/routes"
"github.com/istio-ecosystem/admiral/admiral/pkg/apis/admiral/server"
"github.com/istio-ecosystem/admiral/admiral/pkg/clusters"
"github.com/istio-ecosystem/admiral/admiral/pkg/controller/common"
log "github.com/sirupsen/logrus"
"os"
"os/signal"
"syscall"
"time"

"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -103,6 +104,8 @@ func GetRootCmd(args []string) *cobra.Command {
"The label value, on a namespace, which tells Istio to perform sidecar injection")
rootCmd.PersistentFlags().StringVar(&params.HostnameSuffix, "hostname_suffix", "global",
"The hostname suffix to customize the cname generated by admiral. Default suffix value will be \"global\"")
rootCmd.PersistentFlags().StringVar(&params.PreviewHostnamePrefix, "preview_hostname_prefix", "preview",
itsLucario marked this conversation as resolved.
Show resolved Hide resolved
"The hostname prefix to customize the cname generated by admiral for bluegreen rollout preview service. Default suffix value will be \"preview\"")
rootCmd.PersistentFlags().StringVar(&params.LabelSet.WorkloadIdentityKey, "workload_identity_key", "identity",
"The workload identity key, on deployment which holds identity value used to generate cname by admiral. Default label key will be \"identity\" Admiral will look for a label with this key. If present, that will be used. If not, it will try an annotation (for use cases where an identity is longer than 63 chars)")
rootCmd.PersistentFlags().StringVar(&params.LabelSet.GlobalTrafficDeploymentLabel, "globaltraffic_deployment_label", "identity",
Expand Down
16 changes: 11 additions & 5 deletions admiral/pkg/clusters/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -747,7 +747,7 @@ func copyEndpoint(e *v1alpha32.ServiceEntry_Endpoint) *v1alpha32.ServiceEntry_En

// A rollout can use one of 2 stratergies :-
// 1. Canary strategy - which can use a virtual service to manage the weights associated with a stable and canary service. Admiral created endpoints in service entries will use the weights assigned in the Virtual Service
// 2. Blue green strategy- this contains 2 service instances in a namespace, an active service and a preview service. Admiral will always use the active service
// 2. Blue green strategy- this contains 2 service instances in a namespace, an active service and a preview service. Admiral will use repective service to create active and preview endpoints
func getServiceForRollout(rc *RemoteController, rollout *argo.Rollout) map[string]*WeightedService {

if rollout == nil {
Expand All @@ -769,9 +769,12 @@ func getServiceForRollout(rc *RemoteController, rollout *argo.Rollout) map[strin
var istioCanaryWeights = make(map[string]int32)

var blueGreenActiveService string
var blueGreenPreviewService string

if rolloutStrategy.BlueGreen != nil {
// If rollout uses blue green strategy, use the active service
// If rollout uses blue green strategy
blueGreenActiveService = rolloutStrategy.BlueGreen.ActiveService
blueGreenPreviewService = rolloutStrategy.BlueGreen.PreviewService
} else if rolloutStrategy.Canary != nil && rolloutStrategy.Canary.TrafficRouting != nil && rolloutStrategy.Canary.TrafficRouting.Istio != nil {
canaryService = rolloutStrategy.Canary.CanaryService
stableService = rolloutStrategy.Canary.StableService
Expand Down Expand Up @@ -844,7 +847,7 @@ func getServiceForRollout(rc *RemoteController, rollout *argo.Rollout) map[strin
var service = servicesInNamespace[s]
var match = true
//skip services that are not referenced in the rollout
if len(blueGreenActiveService) > 0 && service.ObjectMeta.Name != blueGreenActiveService {
if len(blueGreenActiveService) > 0 && len(blueGreenPreviewService) > 0 && service.ObjectMeta.Name != blueGreenActiveService && service.ObjectMeta.Name != blueGreenPreviewService {
itsLucario marked this conversation as resolved.
Show resolved Hide resolved
log.Infof("Skipping service=%s for rollout=%s in namespace=%s and cluster=%s", service.Name, rollout.Name, rollout.Namespace, rc.ClusterID)
continue
}
Expand All @@ -865,8 +868,11 @@ func getServiceForRollout(rc *RemoteController, rollout *argo.Rollout) map[strin
if match {
ports := GetMeshPortsForRollout(rc.ClusterID, service, rollout)
if len(ports) > 0 {
//if the strategy is bluegreen or using canary with NO istio traffic management, pick the first service that matches
if len(istioCanaryWeights) == 0 {
// if the strategy is bluegreen return matched services
// else if using canary with NO istio traffic management, pick the first service that matches
if rolloutStrategy.BlueGreen != nil {
matchedServices[service.Name] = &WeightedService{Weight: 1, Service: service}
} else if len(istioCanaryWeights) == 0 {
matchedServices[service.Name] = &WeightedService{Weight: 1, Service: service}
break
}
Expand Down
36 changes: 19 additions & 17 deletions admiral/pkg/clusters/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ package clusters

import (
"context"
"strings"
"testing"
"time"

"github.com/google/go-cmp/cmp"
depModel "github.com/istio-ecosystem/admiral/admiral/pkg/apis/admiral/model"
"github.com/istio-ecosystem/admiral/admiral/pkg/apis/admiral/v1"
v1 "github.com/istio-ecosystem/admiral/admiral/pkg/apis/admiral/v1"
"github.com/istio-ecosystem/admiral/admiral/pkg/controller/admiral"
"github.com/istio-ecosystem/admiral/admiral/pkg/controller/common"
"github.com/istio-ecosystem/admiral/admiral/pkg/test"
Expand All @@ -17,9 +21,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"strings"
"testing"
"time"
)

func init() {
Expand All @@ -36,6 +37,7 @@ func init() {
SecretResolver: "",
WorkloadSidecarUpdate: "enabled",
WorkloadSidecarName: "default",
PreviewHostnamePrefix: "preview",
}

p.LabelSet.WorkloadIdentityKey = "identity"
Expand Down Expand Up @@ -449,24 +451,24 @@ func TestUpdateCacheController(t *testing.T) {

//Struct of test case info. Name is required.
testCases := []struct {
name string
oldConfig *rest.Config
newConfig *rest.Config
clusterId string
name string
oldConfig *rest.Config
newConfig *rest.Config
clusterId string
shouldRefresh bool
}{
{
name: "Should update controller when kubeconfig changes",
oldConfig: originalConfig,
newConfig: changedConfig,
clusterId: "test.cluster",
name: "Should update controller when kubeconfig changes",
oldConfig: originalConfig,
newConfig: changedConfig,
clusterId: "test.cluster",
shouldRefresh: true,
},
{
name: "Should not update controller when kubeconfig doesn't change",
oldConfig: originalConfig,
newConfig: originalConfig,
clusterId: "test.cluster",
name: "Should not update controller when kubeconfig doesn't change",
oldConfig: originalConfig,
newConfig: originalConfig,
clusterId: "test.cluster",
shouldRefresh: false,
},
}
Expand All @@ -476,7 +478,7 @@ func TestUpdateCacheController(t *testing.T) {
t.Run(c.name, func(t *testing.T) {
hook := logTest.NewGlobal()
rr.RemoteControllers[c.clusterId].ApiServer = c.oldConfig.Host
d, err := admiral.NewDeploymentController(make(chan struct{}), &test.MockDeploymentHandler{}, c.oldConfig, time.Second*time.Duration(300))
d, err := admiral.NewDeploymentController(make(chan struct{}), &test.MockDeploymentHandler{}, c.oldConfig, time.Second*time.Duration(300))
if err != nil {
t.Fatalf("Unexpected error creating controller %v", err)
}
Expand Down
52 changes: 47 additions & 5 deletions admiral/pkg/clusters/serviceentry.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func modifyServiceEntryForNewServiceOrPod(event admiral.EventType, env string, s
var serviceEntries = make(map[string]*networking.ServiceEntry)

var cname string
cnames := make(map[string]string)
var serviceInstance *k8sV1.Service
var weightedServices map[string]*WeightedService
var rollout *admiral.RolloutClusterEntry
Expand Down Expand Up @@ -104,6 +105,7 @@ func modifyServiceEntryForNewServiceOrPod(event admiral.EventType, env string, s
localMeshPorts := GetMeshPortsForRollout(rc.ClusterID, serviceInstance, rolloutInstance)

cname = common.GetCnameForRollout(rolloutInstance, common.GetWorkloadIdentifier(), common.GetHostnameSuffix())
cnames[cname] = "1"
sourceRollouts[rc.ClusterID] = rolloutInstance
createServiceEntryForRollout(event, rc, remoteRegistry.AdmiralCache, localMeshPorts, rolloutInstance, serviceEntries)
} else {
Expand Down Expand Up @@ -132,12 +134,17 @@ func modifyServiceEntryForNewServiceOrPod(event admiral.EventType, env string, s
localFqdn := serviceInstance.Name + common.Sep + serviceInstance.Namespace + common.DotLocalDomainSuffix
rc := remoteRegistry.RemoteControllers[sourceCluster]
var meshPorts map[string]uint32
isBlueGreenStrategy := false

if len(sourceRollouts) > 0 {
isBlueGreenStrategy = sourceRollouts[sourceCluster].Spec.Strategy.BlueGreen != nil && sourceRollouts[sourceCluster].Spec.Strategy.BlueGreen.PreviewService != ""
}

if len(sourceDeployments) > 0 {
meshPorts = GetMeshPorts(sourceCluster, serviceInstance, sourceDeployments[sourceCluster])
} else {
itsLucario marked this conversation as resolved.
Show resolved Hide resolved
} else if !isBlueGreenStrategy {
meshPorts = GetMeshPortsForRollout(sourceCluster, serviceInstance, sourceRollouts[sourceCluster])
}

for key, serviceEntry := range serviceEntries {
if len(serviceEntry.Endpoints) == 0 {
AddServiceEntriesWithDr(remoteRegistry.AdmiralCache, map[string]string{sourceCluster: sourceCluster}, remoteRegistry.RemoteControllers,
Expand All @@ -147,8 +154,31 @@ func modifyServiceEntryForNewServiceOrPod(event admiral.EventType, env string, s
for _, ep := range serviceEntry.Endpoints {
//replace istio ingress-gateway address with local fqdn, note that ingress-gateway can be empty (not provisoned, or is not up)
if ep.Address == clusterIngress || ep.Address == "" {
// see if we have weighted services (rollouts with canary strategy)
if len(weightedServices) > 1 {
// Update endpoints with locafqdn for active and preview se of bluegreen rollout
if isBlueGreenStrategy {
activeServiceName := sourceRollouts[sourceCluster].Spec.Strategy.BlueGreen.ActiveService
itsLucario marked this conversation as resolved.
Show resolved Hide resolved
previewServiceName := sourceRollouts[sourceCluster].Spec.Strategy.BlueGreen.PreviewService
oldPorts := ep.Ports
if previewService, ok := weightedServices[previewServiceName]; strings.HasPrefix(key, common.GetPreviewHostnamePrefix()+common.Sep) && ok {
previewServiceInstance := previewService.Service
localFqdn := previewServiceInstance.Name + common.Sep + previewServiceInstance.Namespace + common.DotLocalDomainSuffix
cnames[localFqdn] = "1"
ep.Address = localFqdn
ep.Ports = GetMeshPortsForRollout(sourceCluster, previewServiceInstance, sourceRollouts[sourceCluster])
} else if activeService, ok := weightedServices[activeServiceName]; ok {
activeServiceInstance := activeService.Service
localFqdn := activeServiceInstance.Name + common.Sep + activeServiceInstance.Namespace + common.DotLocalDomainSuffix
cnames[localFqdn] = "1"
ep.Address = localFqdn
ep.Ports = GetMeshPortsForRollout(sourceCluster, activeServiceInstance, sourceRollouts[sourceCluster])
}
AddServiceEntriesWithDr(remoteRegistry.AdmiralCache, map[string]string{sourceCluster: sourceCluster}, remoteRegistry.RemoteControllers,
map[string]*networking.ServiceEntry{key: serviceEntry})
//swap it back to use for next iteration
ep.Address = clusterIngress
ep.Ports = oldPorts
// see if we have weighted services (rollouts with canary strategy)
} else if len(weightedServices) > 1 {
//add one endpoint per each service, may be modify
var se = copyServiceEntry(serviceEntry)
updateEndpointsForWeightedServices(se, weightedServices, clusterIngress, meshPorts)
Expand All @@ -169,7 +199,7 @@ func modifyServiceEntryForNewServiceOrPod(event admiral.EventType, env string, s
}

for _, val := range dependents {
remoteRegistry.AdmiralCache.DependencyNamespaceCache.Put(val, serviceInstance.Namespace, localFqdn, map[string]string{cname: "1"})
remoteRegistry.AdmiralCache.DependencyNamespaceCache.Put(val, serviceInstance.Namespace, localFqdn, cnames)
}

if common.GetWorkloadSidecarUpdate() == "enabled" {
Expand Down Expand Up @@ -570,6 +600,18 @@ func createServiceEntryForRollout(event admiral.EventType, rc *RemoteController,

san := getSanForRollout(destRollout, workloadIdentityKey)

if destRollout.Spec.Strategy.BlueGreen != nil && destRollout.Spec.Strategy.BlueGreen.PreviewService != "" {
rolloutServices := getServiceForRollout(rc, destRollout)
if _, ok := rolloutServices[destRollout.Spec.Strategy.BlueGreen.PreviewService]; ok {
previewGlobalFqdn := common.GetPreviewHostnamePrefix() + common.Sep + common.GetCnameForRollout(destRollout, workloadIdentityKey, common.GetHostnameSuffix())
previewAddress := getUniqueAddress(admiralCache, previewGlobalFqdn)
if len(previewGlobalFqdn) == 0 || len(previewAddress) == 0 {
return nil
}
generateServiceEntry(event, admiralCache, meshPorts, previewGlobalFqdn, rc, serviceEntries, previewAddress, san)
}
}

tmpSe := generateServiceEntry(event, admiralCache, meshPorts, globalFqdn, rc, serviceEntries, address, san)
return tmpSe
}
Expand Down
Loading