Skip to content

Commit

Permalink
Add preview endpoint for ArgoRollouts using BlueGreen (#199)
Browse files Browse the repository at this point in the history
* Changes to support preview endpoint for bluegreen rollout

Signed-off-by: nbn01 <nandan_bn@intuit.com>

* Updated integration test for preview endpoint

Signed-off-by: nbn01 <nandan_bn@intuit.com>

* Minor check fixes

Signed-off-by: nbn01 <nandan_bn@intuit.com>

* Reverted integration test changes

Signed-off-by: nbn01 <nandan_bn@intuit.com>

* Update unit tests

Signed-off-by: nbn01 <nandan_bn@intuit.com>

* Add integration test for preview endpoint of bluegreen rollout

Signed-off-by: nbn01 <nandan_bn@intuit.com>

* Update makefile

Signed-off-by: nbn01 <nandan_bn@intuit.com>

* Modified permissions

Signed-off-by: nbn01 <nandan_bn@intuit.com>

* Reverting argo_rollouts default to false

Signed-off-by: nbn01 <nandan_bn@intuit.com>

* Updated unit tests to check when preview service not defined

Signed-off-by: nbn01 <nandan_bn@intuit.com>

* Improved integration test for bluegreen rollout

Signed-off-by: nbn01 <nandan_bn@intuit.com>

* Continue with normal flow if preview condition fails

Signed-off-by: nbn01 <nandan_bn@intuit.com>

* Uncomment clean up

Signed-off-by: nbn01 <nandan_bn@intuit.com>

* make "preview" standard prefix name for bluegreen

Signed-off-by: nbn01 <nandan_bn@intuit.com>

* abstract update preview endpoint to method

Signed-off-by: nbn01 <nandan_bn@intuit.com>

* Add unit test

Signed-off-by: nbn01 <nandan_bn@intuit.com>

* fix golintci lint to use go1.17.7

Signed-off-by: nbn01 <nandan_bn@intuit.com>

Co-authored-by: nbn01 <nandan_bn@intuit.com>
  • Loading branch information
Nandan B N and nbn01 committed Apr 6, 2022
1 parent 4c9ef81 commit 845e7da
Show file tree
Hide file tree
Showing 11 changed files with 520 additions and 73 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/golang-ci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v3
with:
go-version: '1.17.7'
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.43.0
skip-go-installation: true

# Optional: working directory, useful for monorepos
# working-directory: somedir
Expand Down
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
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 && service.ObjectMeta.Name != blueGreenActiveService && service.ObjectMeta.Name != blueGreenPreviewService {
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
58 changes: 53 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,10 +134,14 @@ 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
}

if len(sourceDeployments) > 0 {
meshPorts = GetMeshPorts(sourceCluster, serviceInstance, sourceDeployments[sourceCluster])
} else {
meshPorts = GetMeshPortsForRollout(sourceCluster, serviceInstance, sourceRollouts[sourceCluster])
}

for key, serviceEntry := range serviceEntries {
Expand All @@ -147,9 +153,20 @@ 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 {
oldPorts := ep.Ports
updateEndpointsForBlueGreen(sourceRollouts[sourceCluster], weightedServices, cnames, ep, sourceCluster, key)

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
meshPorts = GetMeshPortsForRollout(sourceCluster, serviceInstance, sourceRollouts[sourceCluster])
var se = copyServiceEntry(serviceEntry)
updateEndpointsForWeightedServices(se, weightedServices, clusterIngress, meshPorts)
AddServiceEntriesWithDr(remoteRegistry.AdmiralCache, map[string]string{sourceCluster: sourceCluster}, remoteRegistry.RemoteControllers,
Expand All @@ -169,7 +186,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 All @@ -179,6 +196,26 @@ func modifyServiceEntryForNewServiceOrPod(event admiral.EventType, env string, s
return serviceEntries
}

func updateEndpointsForBlueGreen(rollout *argo.Rollout, weightedServices map[string]*WeightedService, cnames map[string]string,
ep *networking.ServiceEntry_Endpoint, sourceCluster string, meshHost string) {
activeServiceName := rollout.Spec.Strategy.BlueGreen.ActiveService
previewServiceName := rollout.Spec.Strategy.BlueGreen.PreviewService

if previewService, ok := weightedServices[previewServiceName]; strings.HasPrefix(meshHost, common.BlueGreenRolloutPreviewPrefix+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, rollout)
} 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, rollout)
}
}

//update endpoints for Argo rollouts specific Service Entries to account for traffic splitting (Canary strategy)
func updateEndpointsForWeightedServices(serviceEntry *networking.ServiceEntry, weightedServices map[string]*WeightedService, clusterIngress string, meshPorts map[string]uint32) {
var endpoints = make([]*networking.ServiceEntry_Endpoint, 0)
Expand Down Expand Up @@ -570,6 +607,17 @@ 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.BlueGreenRolloutPreviewPrefix + common.Sep + common.GetCnameForRollout(destRollout, workloadIdentityKey, common.GetHostnameSuffix())
previewAddress := getUniqueAddress(admiralCache, previewGlobalFqdn)
if len(previewGlobalFqdn) != 0 && len(previewAddress) != 0 {
generateServiceEntry(event, admiralCache, meshPorts, previewGlobalFqdn, rc, serviceEntries, previewAddress, san)
}
}
}

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

0 comments on commit 845e7da

Please sign in to comment.