mirror of
https://github.com/Azure/k8s-deploy.git
synced 2026-07-01 01:39:26 +08:00
Blue/Green Refactor (#229)
* fresh new branch * Added coverage to gitignore Signed-off-by: Jaiveer Katariya <jaiveerkatariya@Jaiveers-MBP.lan> * reverted package-lock.json Signed-off-by: Jaiveer Katariya <jaiveerkatariya@Jaiveers-MBP.lan> Co-authored-by: Jaiveer Katariya <jaiveerkatariya@Jaiveers-MBP.lan>
This commit is contained in:
@@ -1,106 +1,35 @@
|
||||
import * as core from '@actions/core'
|
||||
import {Kubectl} from '../../types/kubectl'
|
||||
import * as kubectlUtils from '../../utilities/trafficSplitUtils'
|
||||
import * as fileHelper from '../../utilities/fileUtils'
|
||||
import {
|
||||
BlueGreenManifests,
|
||||
createWorkloadsWithLabel,
|
||||
deleteObjects,
|
||||
deleteWorkloadsWithLabel,
|
||||
deployObjects,
|
||||
fetchResource,
|
||||
getBlueGreenResourceName,
|
||||
getManifestObjects,
|
||||
getNewBlueGreenObject,
|
||||
GREEN_LABEL_VALUE,
|
||||
GREEN_SUFFIX,
|
||||
NONE_LABEL_VALUE,
|
||||
STABLE_SUFFIX
|
||||
} from './blueGreenHelper'
|
||||
import {BlueGreenDeployment} from '../../types/blueGreenTypes'
|
||||
import {
|
||||
K8sDeleteObject,
|
||||
K8sObject,
|
||||
TrafficSplitObject
|
||||
} from '../../types/k8sObject'
|
||||
import {DeployResult} from '../../types/deployResult'
|
||||
import {inputAnnotations} from '../../inputUtils'
|
||||
|
||||
const TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX = '-trafficsplit'
|
||||
const TRAFFIC_SPLIT_OBJECT = 'TrafficSplit'
|
||||
const MIN_VAL = 0
|
||||
const MAX_VAL = 100
|
||||
|
||||
export async function deployBlueGreenSMI(
|
||||
kubectl: Kubectl,
|
||||
filePaths: string[],
|
||||
annotations: {[key: string]: string} = {}
|
||||
) {
|
||||
// get all kubernetes objects defined in manifest files
|
||||
const manifestObjects: BlueGreenManifests = getManifestObjects(filePaths)
|
||||
|
||||
// create services and other objects
|
||||
const newObjectsList = manifestObjects.otherObjects
|
||||
.concat(manifestObjects.serviceEntityList)
|
||||
.concat(manifestObjects.ingressEntityList)
|
||||
.concat(manifestObjects.unroutedServiceEntityList)
|
||||
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList)
|
||||
await kubectl.apply(manifestFiles)
|
||||
|
||||
// make extraservices and trafficsplit
|
||||
await setupSMI(kubectl, manifestObjects.serviceEntityList, annotations)
|
||||
|
||||
// create new deloyments
|
||||
const workloadDeployment = await createWorkloadsWithLabel(
|
||||
kubectl,
|
||||
manifestObjects.deploymentEntityList,
|
||||
GREEN_LABEL_VALUE
|
||||
)
|
||||
|
||||
return {workloadDeployment, newObjectsList}
|
||||
}
|
||||
|
||||
export async function promoteBlueGreenSMI(kubectl: Kubectl, manifestObjects) {
|
||||
// checking if there is something to promote
|
||||
if (
|
||||
!(await validateTrafficSplitsState(
|
||||
kubectl,
|
||||
manifestObjects.serviceEntityList
|
||||
))
|
||||
) {
|
||||
throw Error('Not in promote state SMI')
|
||||
}
|
||||
|
||||
// create stable deployments with new configuration
|
||||
return await createWorkloadsWithLabel(
|
||||
kubectl,
|
||||
manifestObjects.deploymentEntityList,
|
||||
NONE_LABEL_VALUE
|
||||
)
|
||||
}
|
||||
|
||||
export async function rejectBlueGreenSMI(
|
||||
kubectl: Kubectl,
|
||||
filePaths: string[],
|
||||
annotations: {[key: string]: string} = {}
|
||||
) {
|
||||
// get all kubernetes objects defined in manifest files
|
||||
const manifestObjects: BlueGreenManifests = getManifestObjects(filePaths)
|
||||
|
||||
// route trafficsplit to stable deployments
|
||||
await routeBlueGreenSMI(
|
||||
kubectl,
|
||||
NONE_LABEL_VALUE,
|
||||
manifestObjects.serviceEntityList,
|
||||
annotations
|
||||
)
|
||||
|
||||
// delete rejected new bluegreen deployments
|
||||
await deleteWorkloadsWithLabel(
|
||||
kubectl,
|
||||
GREEN_LABEL_VALUE,
|
||||
manifestObjects.deploymentEntityList
|
||||
)
|
||||
|
||||
// delete trafficsplit and extra services
|
||||
await cleanupSMI(kubectl, manifestObjects.serviceEntityList)
|
||||
}
|
||||
export const TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX = '-trafficsplit'
|
||||
export const TRAFFIC_SPLIT_OBJECT = 'TrafficSplit'
|
||||
export const MIN_VAL = 0
|
||||
export const MAX_VAL = 100
|
||||
|
||||
export async function setupSMI(
|
||||
kubectl: Kubectl,
|
||||
serviceEntityList: any[],
|
||||
annotations: {[key: string]: string} = {}
|
||||
) {
|
||||
serviceEntityList: any[]
|
||||
): Promise<BlueGreenDeployment> {
|
||||
const newObjectsList = []
|
||||
const trafficObjectList = []
|
||||
|
||||
@@ -108,56 +37,66 @@ export async function setupSMI(
|
||||
// create a trafficsplit for service
|
||||
trafficObjectList.push(serviceObject)
|
||||
// set up the services for trafficsplit
|
||||
const newStableService = getSMIServiceResource(
|
||||
serviceObject,
|
||||
STABLE_SUFFIX
|
||||
)
|
||||
const newGreenService = getSMIServiceResource(serviceObject, GREEN_SUFFIX)
|
||||
const newStableService = getStableSMIServiceResource(serviceObject)
|
||||
const newGreenService = getGreenSMIServiceResource(serviceObject)
|
||||
newObjectsList.push(newStableService)
|
||||
newObjectsList.push(newGreenService)
|
||||
})
|
||||
|
||||
// create services
|
||||
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList)
|
||||
await kubectl.apply(manifestFiles)
|
||||
|
||||
const tsObjects: TrafficSplitObject[] = []
|
||||
// route to stable service
|
||||
trafficObjectList.forEach((inputObject) => {
|
||||
createTrafficSplitObject(
|
||||
for (const svc of trafficObjectList) {
|
||||
const tsObject = await createTrafficSplitObject(
|
||||
kubectl,
|
||||
inputObject.metadata.name,
|
||||
NONE_LABEL_VALUE,
|
||||
annotations
|
||||
svc.metadata.name,
|
||||
NONE_LABEL_VALUE
|
||||
)
|
||||
})
|
||||
tsObjects.push(tsObject as TrafficSplitObject)
|
||||
}
|
||||
|
||||
const objectsToDeploy = [].concat(newObjectsList, tsObjects)
|
||||
|
||||
// create services
|
||||
const smiDeploymentResult: DeployResult = await deployObjects(
|
||||
kubectl,
|
||||
objectsToDeploy
|
||||
)
|
||||
|
||||
return {
|
||||
objects: objectsToDeploy,
|
||||
deployResult: smiDeploymentResult
|
||||
}
|
||||
}
|
||||
|
||||
let trafficSplitAPIVersion = ''
|
||||
|
||||
async function createTrafficSplitObject(
|
||||
export async function createTrafficSplitObject(
|
||||
kubectl: Kubectl,
|
||||
name: string,
|
||||
nextLabel: string,
|
||||
annotations: {[key: string]: string} = {}
|
||||
): Promise<any> {
|
||||
nextLabel: string
|
||||
): Promise<TrafficSplitObject> {
|
||||
// cache traffic split api version
|
||||
if (!trafficSplitAPIVersion)
|
||||
trafficSplitAPIVersion = await kubectlUtils.getTrafficSplitAPIVersion(
|
||||
kubectl
|
||||
)
|
||||
|
||||
// retrieve annotations for TS object
|
||||
const annotations = inputAnnotations
|
||||
|
||||
// decide weights based on nextlabel
|
||||
const stableWeight: number =
|
||||
nextLabel === GREEN_LABEL_VALUE ? MIN_VAL : MAX_VAL
|
||||
const greenWeight: number =
|
||||
nextLabel === GREEN_LABEL_VALUE ? MAX_VAL : MIN_VAL
|
||||
|
||||
const trafficSplitObject = JSON.stringify({
|
||||
const trafficSplitObject: TrafficSplitObject = {
|
||||
apiVersion: trafficSplitAPIVersion,
|
||||
kind: 'TrafficSplit',
|
||||
kind: TRAFFIC_SPLIT_OBJECT,
|
||||
metadata: {
|
||||
name: getBlueGreenResourceName(name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX),
|
||||
annotations: annotations
|
||||
annotations: annotations,
|
||||
labels: new Map<string, string>()
|
||||
},
|
||||
spec: {
|
||||
service: name,
|
||||
@@ -172,52 +111,24 @@ async function createTrafficSplitObject(
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// create traffic split object
|
||||
const trafficSplitManifestFile = fileHelper.writeManifestToFile(
|
||||
trafficSplitObject,
|
||||
TRAFFIC_SPLIT_OBJECT,
|
||||
getBlueGreenResourceName(name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX)
|
||||
)
|
||||
|
||||
await kubectl.apply(trafficSplitManifestFile)
|
||||
return trafficSplitObject
|
||||
}
|
||||
|
||||
export function getSMIServiceResource(
|
||||
inputObject: any,
|
||||
suffix: string
|
||||
): object {
|
||||
export function getStableSMIServiceResource(inputObject: K8sObject): K8sObject {
|
||||
const newObject = JSON.parse(JSON.stringify(inputObject))
|
||||
|
||||
if (suffix === STABLE_SUFFIX) {
|
||||
// adding stable suffix to service name
|
||||
newObject.metadata.name = getBlueGreenResourceName(
|
||||
inputObject.metadata.name,
|
||||
STABLE_SUFFIX
|
||||
)
|
||||
return getNewBlueGreenObject(newObject, NONE_LABEL_VALUE)
|
||||
} else {
|
||||
// green label will be added for these
|
||||
return getNewBlueGreenObject(newObject, GREEN_LABEL_VALUE)
|
||||
}
|
||||
// adding stable suffix to service name
|
||||
newObject.metadata.name = getBlueGreenResourceName(
|
||||
inputObject.metadata.name,
|
||||
STABLE_SUFFIX
|
||||
)
|
||||
return getNewBlueGreenObject(newObject, NONE_LABEL_VALUE)
|
||||
}
|
||||
|
||||
export async function routeBlueGreenSMI(
|
||||
kubectl: Kubectl,
|
||||
nextLabel: string,
|
||||
serviceEntityList: any[],
|
||||
annotations: {[key: string]: string} = {}
|
||||
) {
|
||||
for (const serviceObject of serviceEntityList) {
|
||||
// route trafficsplit to given label
|
||||
await createTrafficSplitObject(
|
||||
kubectl,
|
||||
serviceObject.metadata.name,
|
||||
nextLabel,
|
||||
annotations
|
||||
)
|
||||
}
|
||||
export function getGreenSMIServiceResource(inputObject: K8sObject): K8sObject {
|
||||
const newObject = JSON.parse(JSON.stringify(inputObject))
|
||||
return getNewBlueGreenObject(newObject, GREEN_LABEL_VALUE)
|
||||
}
|
||||
|
||||
export async function validateTrafficSplitsState(
|
||||
@@ -233,32 +144,38 @@ export async function validateTrafficSplitsState(
|
||||
TRAFFIC_SPLIT_OBJECT,
|
||||
getBlueGreenResourceName(name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX)
|
||||
)
|
||||
|
||||
core.debug(
|
||||
`ts object extracted was ${JSON.stringify(trafficSplitObject)}`
|
||||
)
|
||||
if (!trafficSplitObject) {
|
||||
// no traffic split exits
|
||||
core.debug(`no traffic split exits for ${name}`)
|
||||
trafficSplitsInRightState = false
|
||||
continue
|
||||
}
|
||||
|
||||
trafficSplitObject = JSON.parse(JSON.stringify(trafficSplitObject))
|
||||
trafficSplitObject.spec.backends.forEach((element) => {
|
||||
// checking if trafficsplit in right state to deploy
|
||||
if (element.service === getBlueGreenResourceName(name, GREEN_SUFFIX)) {
|
||||
if (element.weight != MAX_VAL) trafficSplitsInRightState = false
|
||||
trafficSplitsInRightState =
|
||||
trafficSplitsInRightState && element.weight == MAX_VAL
|
||||
}
|
||||
|
||||
if (
|
||||
element.service === getBlueGreenResourceName(name, STABLE_SUFFIX)
|
||||
) {
|
||||
if (element.weight != MIN_VAL) trafficSplitsInRightState = false
|
||||
trafficSplitsInRightState =
|
||||
trafficSplitsInRightState && element.weight == MIN_VAL
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return trafficSplitsInRightState
|
||||
}
|
||||
|
||||
export async function cleanupSMI(kubectl: Kubectl, serviceEntityList: any[]) {
|
||||
const deleteList = []
|
||||
export async function cleanupSMI(
|
||||
kubectl: Kubectl,
|
||||
serviceEntityList: any[]
|
||||
): Promise<K8sDeleteObject[]> {
|
||||
const deleteList: K8sDeleteObject[] = []
|
||||
|
||||
serviceEntityList.forEach((serviceObject) => {
|
||||
deleteList.push({
|
||||
@@ -288,4 +205,6 @@ export async function cleanupSMI(kubectl: Kubectl, serviceEntityList: any[]) {
|
||||
|
||||
// delete all objects
|
||||
await deleteObjects(kubectl, deleteList)
|
||||
|
||||
return deleteList
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user