Improve executor bootstrap speed (#1446)

This commit is contained in:
Ta-Ching Chen
2019-11-29 14:18:23 +08:00
committed by GitHub
parent 3067ecdee3
commit 47aaa85108
9 changed files with 134 additions and 127 deletions
-1
View File
@@ -18,7 +18,6 @@ package v1
const ( const (
EXECUTOR_INSTANCEID_LABEL string = "executorInstanceId" EXECUTOR_INSTANCEID_LABEL string = "executorInstanceId"
POOLMGR_INSTANCEID_LABEL string = "poolmgrInstanceId"
DEFAULT_FUNCTION_TIMEOUT int = 60 DEFAULT_FUNCTION_TIMEOUT int = 60
) )
+10 -4
View File
@@ -37,6 +37,7 @@ import (
"github.com/fission/fission/pkg/executor/executortype/poolmgr" "github.com/fission/fission/pkg/executor/executortype/poolmgr"
"github.com/fission/fission/pkg/executor/fscache" "github.com/fission/fission/pkg/executor/fscache"
"github.com/fission/fission/pkg/executor/reaper" "github.com/fission/fission/pkg/executor/reaper"
"github.com/fission/fission/pkg/executor/util"
fetcherConfig "github.com/fission/fission/pkg/fetcher/config" fetcherConfig "github.com/fission/fission/pkg/fetcher/config"
) )
@@ -74,6 +75,12 @@ func MakeExecutor(logger *zap.Logger, cms *cms.ConfigSecretController,
requestChan: make(chan *createFuncServiceRequest), requestChan: make(chan *createFuncServiceRequest),
fsCreateWg: make(map[string]*sync.WaitGroup), fsCreateWg: make(map[string]*sync.WaitGroup),
} }
for _, et := range types {
go func(et executortype.ExecutorType) {
et.Run(context.Background())
}(et)
}
go cms.Run(context.Background())
go executor.serveCreateFuncServices() go executor.serveCreateFuncServices()
return executor, nil return executor, nil
@@ -241,21 +248,20 @@ func StartExecutor(logger *zap.Logger, functionNamespace string, envBuilderNames
go func(et executortype.ExecutorType) { go func(et executortype.ExecutorType) {
defer wg.Done() defer wg.Done()
et.AdoptOrphanResources() et.AdoptOrphanResources()
et.Run(context.Background())
}(et) }(et)
} }
wg.Wait() // set hard timeout for resource adoption
util.WaitTimeout(wg, 30*time.Second)
cms := cms.MakeConfigSecretController(logger, fissionClient, kubernetesClient, executorTypes) cms := cms.MakeConfigSecretController(logger, fissionClient, kubernetesClient, executorTypes)
cms.Run(context.Background())
api, err := MakeExecutor(logger, cms, fissionClient, executorTypes) api, err := MakeExecutor(logger, cms, fissionClient, executorTypes)
if err != nil { if err != nil {
return err return err
} }
go reaper.CleanupRoleBindings(logger, kubernetesClient, fissionClient, functionNamespace, envBuilderNamespace, time.Minute*30)
go reaper.CleanupOldExecutorObjects(logger, kubernetesClient, executorInstanceID) go reaper.CleanupOldExecutorObjects(logger, kubernetesClient, executorInstanceID)
go reaper.CleanupRoleBindings(logger, kubernetesClient, fissionClient, functionNamespace, envBuilderNamespace, time.Minute*30)
go api.Serve(port) go api.Serve(port)
go serveMetric(logger) go serveMetric(logger)
@@ -44,22 +44,22 @@ const (
) )
func (deploy *NewDeploy) createOrGetDeployment(fn *fv1.Function, env *fv1.Environment, func (deploy *NewDeploy) createOrGetDeployment(fn *fv1.Function, env *fv1.Environment,
deployName string, deployLabels map[string]string, deployAnnotations map[string]string, deployNamespace string, firstcreate bool) (*appsv1.Deployment, error) { deployName string, deployLabels map[string]string, deployAnnotations map[string]string, deployNamespace string) (*appsv1.Deployment, error) {
minScale := int32(fn.Spec.InvokeStrategy.ExecutionStrategy.MinScale)
specializationTimeout := int(fn.Spec.InvokeStrategy.ExecutionStrategy.SpecializationTimeout) specializationTimeout := int(fn.Spec.InvokeStrategy.ExecutionStrategy.SpecializationTimeout)
minScale := int32(fn.Spec.InvokeStrategy.ExecutionStrategy.MinScale)
// If it's not the first time creation and minscale is 0 means that all pods for function were recycled, // Always scale to at least one pod when createOrGetDeployment
// in such cases we need set minscale to 1 for router to serve requests. // is called. The idleObjectReaper will scale-in the deployment
if !firstcreate && minScale <= 0 { // later if no requests to the function.
if minScale <= 0 {
minScale = 1 minScale = 1
} }
waitForDeploy := minScale > 0
existingDepl, err := deploy.kubernetesClient.AppsV1().Deployments(deployNamespace).Get(deployName, metav1.GetOptions{}) existingDepl, err := deploy.kubernetesClient.AppsV1().Deployments(deployNamespace).Get(deployName, metav1.GetOptions{})
if err == nil { if err == nil {
if existingDepl.Labels[fv1.EXECUTOR_INSTANCEID_LABEL] != deploy.instanceID { // Try to adopt orphan deployment created by the old executor.
if existingDepl.Annotations[fv1.EXECUTOR_INSTANCEID_LABEL] != deploy.instanceID {
patch := fmt.Sprintf(`{"metadata":{"annotations":{"%v":"%v"}}}`, types.EXECUTOR_INSTANCEID_LABEL, deploy.instanceID) patch := fmt.Sprintf(`{"metadata":{"annotations":{"%v":"%v"}}}`, types.EXECUTOR_INSTANCEID_LABEL, deploy.instanceID)
existingDepl, err = deploy.kubernetesClient.AppsV1().Deployments(deployNamespace).Patch(deployName, k8sTypes.StrategicMergePatchType, []byte(patch)) existingDepl, err = deploy.kubernetesClient.AppsV1().Deployments(deployNamespace).Patch(deployName, k8sTypes.StrategicMergePatchType, []byte(patch))
if err != nil { if err != nil {
@@ -67,19 +67,21 @@ func (deploy *NewDeploy) createOrGetDeployment(fn *fv1.Function, env *fv1.Enviro
zap.String("deploy", deployName), zap.String("ns", deployNamespace)) zap.String("deploy", deployName), zap.String("ns", deployNamespace))
return nil, err return nil, err
} }
// In this case, we just return without waiting for it for fast bootstraping.
return existingDepl, nil
} }
if waitForDeploy {
if *existingDepl.Spec.Replicas < minScale { if *existingDepl.Spec.Replicas < minScale {
err = deploy.scaleDeployment(existingDepl.Namespace, existingDepl.Name, minScale) err = deploy.scaleDeployment(existingDepl.Namespace, existingDepl.Name, minScale)
if err != nil { if err != nil {
deploy.logger.Error("error scaling up function deployment", zap.Error(err), zap.String("function", fn.Metadata.Name)) deploy.logger.Error("error scaling up function deployment", zap.Error(err), zap.String("function", fn.Metadata.Name))
return nil, err return nil, err
}
}
if existingDepl.Status.AvailableReplicas < minScale {
existingDepl, err = deploy.waitForDeploy(existingDepl, minScale, specializationTimeout)
} }
} }
if existingDepl.Status.AvailableReplicas < minScale {
existingDepl, err = deploy.waitForDeploy(existingDepl, minScale, specializationTimeout)
}
return existingDepl, err return existingDepl, err
} else if k8s_err.IsNotFound(err) { } else if k8s_err.IsNotFound(err) {
err := deploy.setupRBACObjs(deployNamespace, fn) err := deploy.setupRBACObjs(deployNamespace, fn)
@@ -87,7 +89,7 @@ func (deploy *NewDeploy) createOrGetDeployment(fn *fv1.Function, env *fv1.Enviro
return nil, err return nil, err
} }
deployment, err := deploy.getDeploymentSpec(fn, env, deployName, deployNamespace, deployLabels, deployAnnotations) deployment, err := deploy.getDeploymentSpec(fn, env, &minScale, deployName, deployNamespace, deployLabels, deployAnnotations)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -102,7 +104,7 @@ func (deploy *NewDeploy) createOrGetDeployment(fn *fv1.Function, env *fv1.Enviro
return nil, err return nil, err
} }
if waitForDeploy { if minScale > 0 {
depl, err = deploy.waitForDeploy(depl, minScale, specializationTimeout) depl, err = deploy.waitForDeploy(depl, minScale, specializationTimeout)
} }
@@ -168,10 +170,13 @@ func (deploy *NewDeploy) deleteDeployment(ns string, name string) error {
}) })
} }
func (deploy *NewDeploy) getDeploymentSpec(fn *fv1.Function, env *fv1.Environment, func (deploy *NewDeploy) getDeploymentSpec(fn *fv1.Function, env *fv1.Environment, targetReplicas *int32,
deployName string, deployNamespace string, deployLabels map[string]string, deployAnnotations map[string]string) (*appsv1.Deployment, error) { deployName string, deployNamespace string, deployLabels map[string]string, deployAnnotations map[string]string) (*appsv1.Deployment, error) {
replicas := int32(fn.Spec.InvokeStrategy.ExecutionStrategy.MinScale) replicas := int32(fn.Spec.InvokeStrategy.ExecutionStrategy.MinScale)
if targetReplicas != nil {
replicas = *targetReplicas
}
gracePeriodSeconds := int64(6 * 60) gracePeriodSeconds := int64(6 * 60)
if env.Spec.TerminationGracePeriod > 0 { if env.Spec.TerminationGracePeriod > 0 {
@@ -351,7 +356,7 @@ func (deploy *NewDeploy) createOrGetHpa(hpaName string, execStrategy *fv1.Execut
existingHpa, err := deploy.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(depl.ObjectMeta.Namespace).Get(hpaName, metav1.GetOptions{}) existingHpa, err := deploy.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(depl.ObjectMeta.Namespace).Get(hpaName, metav1.GetOptions{})
if err == nil { if err == nil {
if existingHpa.Labels[fv1.EXECUTOR_INSTANCEID_LABEL] != deploy.instanceID { if existingHpa.Annotations[fv1.EXECUTOR_INSTANCEID_LABEL] != deploy.instanceID {
patch := fmt.Sprintf(`{"metadata":{"annotations":{"%v":"%v"}}}`, types.EXECUTOR_INSTANCEID_LABEL, deploy.instanceID) patch := fmt.Sprintf(`{"metadata":{"annotations":{"%v":"%v"}}}`, types.EXECUTOR_INSTANCEID_LABEL, deploy.instanceID)
existingHpa, err = deploy.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(depl.ObjectMeta.Namespace).Patch(hpaName, k8sTypes.StrategicMergePatchType, []byte(patch)) existingHpa, err = deploy.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(depl.ObjectMeta.Namespace).Patch(hpaName, k8sTypes.StrategicMergePatchType, []byte(patch))
if err != nil { if err != nil {
@@ -407,7 +412,7 @@ func (deploy *NewDeploy) deleteHpa(ns string, name string) error {
func (deploy *NewDeploy) createOrGetSvc(deployLabels map[string]string, deployAnnotations map[string]string, svcName string, svcNamespace string) (*apiv1.Service, error) { func (deploy *NewDeploy) createOrGetSvc(deployLabels map[string]string, deployAnnotations map[string]string, svcName string, svcNamespace string) (*apiv1.Service, error) {
existingSvc, err := deploy.kubernetesClient.CoreV1().Services(svcNamespace).Get(svcName, metav1.GetOptions{}) existingSvc, err := deploy.kubernetesClient.CoreV1().Services(svcNamespace).Get(svcName, metav1.GetOptions{})
if err == nil { if err == nil {
if existingSvc.Labels[fv1.EXECUTOR_INSTANCEID_LABEL] != deploy.instanceID { if existingSvc.Annotations[fv1.EXECUTOR_INSTANCEID_LABEL] != deploy.instanceID {
patch := fmt.Sprintf(`{"metadata":{"annotations":{"%v":"%v"}}}`, types.EXECUTOR_INSTANCEID_LABEL, deploy.instanceID) patch := fmt.Sprintf(`{"metadata":{"annotations":{"%v":"%v"}}}`, types.EXECUTOR_INSTANCEID_LABEL, deploy.instanceID)
existingSvc, err = deploy.kubernetesClient.CoreV1().Services(svcNamespace).Patch(svcName, k8sTypes.StrategicMergePatchType, []byte(patch)) existingSvc, err = deploy.kubernetesClient.CoreV1().Services(svcNamespace).Patch(svcName, k8sTypes.StrategicMergePatchType, []byte(patch))
if err != nil { if err != nil {
@@ -143,7 +143,7 @@ func (deploy *NewDeploy) GetFuncSvc(ctx context.Context, fn *fv1.Function) (*fsc
// TODO: client-go doesn't support to pass in context. // TODO: client-go doesn't support to pass in context.
// Once it supports context, we should change the signature of method. // Once it supports context, we should change the signature of method.
// https://github.com/kubernetes/kubernetes/issues/46503 // https://github.com/kubernetes/kubernetes/issues/46503
return deploy.createFunction(fn, false) return deploy.createFunction(fn)
} }
func (deploy *NewDeploy) GetFuncSvcFromCache(fn *fv1.Function) (*fscache.FuncSvc, error) { func (deploy *NewDeploy) GetFuncSvcFromCache(fn *fv1.Function) (*fscache.FuncSvc, error) {
@@ -249,24 +249,21 @@ func (deploy *NewDeploy) AdoptOrphanResources() {
podList, err := deploy.kubernetesClient.CoreV1().Pods(metav1.NamespaceAll).List(metav1.ListOptions{ podList, err := deploy.kubernetesClient.CoreV1().Pods(metav1.NamespaceAll).List(metav1.ListOptions{
LabelSelector: labels.Set(l).AsSelector().String(), LabelSelector: labels.Set(l).AsSelector().String(),
}) })
if err != nil { if err != nil {
deploy.logger.Error("error getting pod list", zap.Error(err)) deploy.logger.Error("error getting pod list", zap.Error(err))
return return
} }
podWG := &sync.WaitGroup{} // Unlike poolmanager manages the lifecycle of function pod directly,
// newdeploy is only responsible to create the deployment and Kubernetes
// will handle the rest. Hence we don't need to wait for the pod patching
// process to finish.
for i := range podList.Items { for i := range podList.Items {
pod := &podList.Items[i] pod := &podList.Items[i]
if !utils.IsReadyPod(pod) { if !utils.IsReadyPod(pod) {
continue continue
} }
podWG.Add(1)
go func() { go func() {
defer podWG.Done()
// avoid too many requests arrive Kubernetes API server at the same time. // avoid too many requests arrive Kubernetes API server at the same time.
time.Sleep(time.Duration(rand.Intn(30)) * time.Millisecond) time.Sleep(time.Duration(rand.Intn(30)) * time.Millisecond)
@@ -278,7 +275,6 @@ func (deploy *NewDeploy) AdoptOrphanResources() {
zap.String("pod", pod.Name), zap.String("ns", pod.Namespace)) zap.String("pod", pod.Name), zap.String("ns", pod.Namespace))
return return
} }
deploy.logger.Info("adopt newdeploy function pod", deploy.logger.Info("adopt newdeploy function pod",
zap.String("pod", pod.Name), zap.Any("labels", pod.Labels), zap.Any("annotations", pod.Annotations)) zap.String("pod", pod.Name), zap.Any("labels", pod.Labels), zap.Any("annotations", pod.Annotations))
}() }()
@@ -290,16 +286,16 @@ func (deploy *NewDeploy) AdoptOrphanResources() {
return return
} }
deployWG := &sync.WaitGroup{} wg := &sync.WaitGroup{}
for i := range fnList.Items { for i := range fnList.Items {
fn := &fnList.Items[i] fn := &fnList.Items[i]
if fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType == fv1.ExecutorTypeNewdeploy { if fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType == fv1.ExecutorTypeNewdeploy {
deployWG.Add(1) wg.Add(1)
go func() { go func() {
defer deployWG.Done() defer wg.Done()
_, err = deploy.fnCreate(fn, true) _, err = deploy.fnCreate(fn)
if err != nil { if err != nil {
deploy.logger.Warn("failed to adopt resources for function", zap.Error(err)) deploy.logger.Warn("failed to adopt resources for function", zap.Error(err))
return return
@@ -309,8 +305,7 @@ func (deploy *NewDeploy) AdoptOrphanResources() {
} }
} }
podWG.Wait() wg.Wait()
deployWG.Wait()
} }
func (deploy *NewDeploy) initFuncController() (k8sCache.Store, k8sCache.Controller) { func (deploy *NewDeploy) initFuncController() (k8sCache.Store, k8sCache.Controller) {
@@ -324,7 +319,7 @@ func (deploy *NewDeploy) initFuncController() (k8sCache.Store, k8sCache.Controll
go func() { go func() {
fn := obj.(*fv1.Function) fn := obj.(*fv1.Function)
deploy.logger.Debug("create deployment for function", zap.Any("fn", fn.Metadata), zap.Any("fnspec", fn.Spec)) deploy.logger.Debug("create deployment for function", zap.Any("fn", fn.Metadata), zap.Any("fnspec", fn.Spec))
_, err := deploy.createFunction(fn, true) _, err := deploy.createFunction(fn)
if err != nil { if err != nil {
deploy.logger.Error("error eager creating function", deploy.logger.Error("error eager creating function",
zap.Error(err), zap.Error(err),
@@ -406,14 +401,14 @@ func (deploy *NewDeploy) getEnvFunctions(m *metav1.ObjectMeta) []fv1.Function {
return relatedFunctions return relatedFunctions
} }
func (deploy *NewDeploy) createFunction(fn *fv1.Function, firstcreate bool) (*fscache.FuncSvc, error) { func (deploy *NewDeploy) createFunction(fn *fv1.Function) (*fscache.FuncSvc, error) {
if fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypeNewdeploy { if fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypeNewdeploy {
return nil, nil return nil, nil
} }
fsvcObj, err := deploy.throttler.RunOnce(string(fn.Metadata.UID), func(ableToCreate bool) (interface{}, error) { fsvcObj, err := deploy.throttler.RunOnce(string(fn.Metadata.UID), func(ableToCreate bool) (interface{}, error) {
if ableToCreate { if ableToCreate {
return deploy.fnCreate(fn, firstcreate) return deploy.fnCreate(fn)
} }
return deploy.fsCache.GetByFunctionUID(fn.Metadata.UID) return deploy.fsCache.GetByFunctionUID(fn.Metadata.UID)
}) })
@@ -446,7 +441,7 @@ func (deploy *NewDeploy) deleteFunction(fn *fv1.Function) error {
return err return err
} }
func (deploy *NewDeploy) fnCreate(fn *fv1.Function, firstcreate bool) (*fscache.FuncSvc, error) { func (deploy *NewDeploy) fnCreate(fn *fv1.Function) (*fscache.FuncSvc, error) {
env, err := deploy.fissionClient. env, err := deploy.fissionClient.
Environments(fn.Spec.Environment.Namespace). Environments(fn.Spec.Environment.Namespace).
Get(fn.Spec.Environment.Name) Get(fn.Spec.Environment.Name)
@@ -478,7 +473,7 @@ func (deploy *NewDeploy) fnCreate(fn *fv1.Function, firstcreate bool) (*fscache.
} }
svcAddress := fmt.Sprintf("%v.%v", svc.Name, svc.Namespace) svcAddress := fmt.Sprintf("%v.%v", svc.Name, svc.Namespace)
depl, err := deploy.createOrGetDeployment(fn, env, objName, deployLabels, deployAnnotations, ns, firstcreate) depl, err := deploy.createOrGetDeployment(fn, env, objName, deployLabels, deployAnnotations, ns)
if err != nil { if err != nil {
deploy.logger.Error("error creating deployment", zap.Error(err), zap.String("deployment", objName)) deploy.logger.Error("error creating deployment", zap.Error(err), zap.String("deployment", objName))
go deploy.cleanupNewdeploy(ns, objName) go deploy.cleanupNewdeploy(ns, objName)
@@ -567,7 +562,7 @@ func (deploy *NewDeploy) updateFunction(oldFn *fv1.Function, newFn *fv1.Function
deploy.logger.Info("function type changed to new deployment, creating resources", deploy.logger.Info("function type changed to new deployment, creating resources",
zap.Any("old_function", oldFn.Metadata), zap.Any("old_function", oldFn.Metadata),
zap.Any("new_function", newFn.Metadata)) zap.Any("new_function", newFn.Metadata))
_, err := deploy.createFunction(newFn, true) _, err := deploy.createFunction(newFn)
if err != nil { if err != nil {
deploy.updateStatus(oldFn, err, "error changing the function's type to newdeploy") deploy.updateStatus(oldFn, err, "error changing the function's type to newdeploy")
} }
@@ -686,7 +681,14 @@ func (deploy *NewDeploy) updateFuncDeployment(fn *fv1.Function, env *fv1.Environ
ns = fn.Metadata.Namespace ns = fn.Metadata.Namespace
} }
newDeployment, err := deploy.getDeploymentSpec(fn, env, fnObjName, ns, deployLabels, deploy.getDeployAnnotations(fn.Metadata)) existingDepl, err := deploy.kubernetesClient.AppsV1().Deployments(ns).Get(fnObjName, metav1.GetOptions{})
if err != nil {
return err
}
// use current replicas instead of minscale in the ExecutionStrategy.
newDeployment, err := deploy.getDeploymentSpec(fn, env,
existingDepl.Spec.Replicas, fnObjName, ns, deployLabels, deploy.getDeployAnnotations(fn.Metadata))
if err != nil { if err != nil {
deploy.updateStatus(fn, err, "failed to get new deployment spec while updating function") deploy.updateStatus(fn, err, "failed to get new deployment spec while updating function")
return err return err
+7 -7
View File
@@ -456,20 +456,20 @@ func (gp *GenericPool) createPool() error {
deployment.Spec.Template.Spec = *newPodSpec deployment.Spec.Template.Spec = *newPodSpec
} }
_, err = gp.kubernetesClient.AppsV1().Deployments(gp.namespace).Get(deployment.Name, metav1.GetOptions{}) depl, err := gp.kubernetesClient.AppsV1().Deployments(gp.namespace).Get(deployment.Name, metav1.GetOptions{})
if err == nil { if err == nil {
patch := fmt.Sprintf(`{"metadata":{"annotations":{"%v":"%v"}}}`, fv1.EXECUTOR_INSTANCEID_LABEL, gp.instanceId) if depl.Annotations[fv1.EXECUTOR_INSTANCEID_LABEL] != gp.instanceId {
depl, err := gp.kubernetesClient.AppsV1().Deployments(gp.namespace).Patch(deployment.Name, k8sTypes.StrategicMergePatchType, []byte(patch)) patch := fmt.Sprintf(`{"metadata":{"annotations":{"%v":"%v"}}}`, fv1.EXECUTOR_INSTANCEID_LABEL, gp.instanceId)
if err == nil { depl, err = gp.kubernetesClient.AppsV1().Deployments(gp.namespace).Patch(deployment.Name, k8sTypes.StrategicMergePatchType, []byte(patch))
gp.deployment = depl
return nil
} }
gp.deployment = depl
return err
} else if !k8sErrs.IsNotFound(err) { } else if !k8sErrs.IsNotFound(err) {
gp.logger.Error("error getting deployment in kubernetes", zap.Error(err), zap.String("deployment", deployment.Name)) gp.logger.Error("error getting deployment in kubernetes", zap.Error(err), zap.String("deployment", deployment.Name))
return err return err
} }
depl, err := gp.kubernetesClient.AppsV1().Deployments(gp.namespace).Create(deployment) depl, err = gp.kubernetesClient.AppsV1().Deployments(gp.namespace).Create(deployment)
if err != nil { if err != nil {
gp.logger.Error("error creating deployment in kubernetes", zap.Error(err), zap.String("deployment", deployment.Name)) gp.logger.Error("error creating deployment in kubernetes", zap.Error(err), zap.String("deployment", deployment.Name))
return err return err
+13 -18
View File
@@ -251,15 +251,15 @@ func (gpm *GenericPoolManager) AdoptOrphanResources() {
} }
envMap := make(map[string]fv1.Environment, len(envs.Items)) envMap := make(map[string]fv1.Environment, len(envs.Items))
envWG := &sync.WaitGroup{} wg := &sync.WaitGroup{}
for i := range envs.Items { for i := range envs.Items {
env := envs.Items[i] env := envs.Items[i]
if gpm.getEnvPoolsize(&env) > 0 { if gpm.getEnvPoolsize(&env) > 0 {
envWG.Add(1) wg.Add(1)
go func() { go func() {
defer envWG.Done() defer wg.Done()
_, err := gpm.getPool(&env) _, err := gpm.getPool(&env)
if err != nil { if err != nil {
gpm.logger.Error("adopt pool failed", zap.Error(err)) gpm.logger.Error("adopt pool failed", zap.Error(err))
@@ -285,17 +285,15 @@ func (gpm *GenericPoolManager) AdoptOrphanResources() {
return return
} }
podWG := &sync.WaitGroup{}
for i := range podList.Items { for i := range podList.Items {
pod := &podList.Items[i] pod := &podList.Items[i]
if !utils.IsReadyPod(pod) { if !utils.IsReadyPod(pod) {
continue continue
} }
podWG.Add(1) wg.Add(1)
go func() { go func() {
defer podWG.Done() defer wg.Done()
// avoid too many requests arrive Kubernetes API server at the same time. // avoid too many requests arrive Kubernetes API server at the same time.
time.Sleep(time.Duration(rand.Intn(30)) * time.Millisecond) time.Sleep(time.Duration(rand.Intn(30)) * time.Millisecond)
@@ -324,7 +322,7 @@ func (gpm *GenericPoolManager) AdoptOrphanResources() {
env, ok8 := envMap[fmt.Sprintf("%v/%v", envNS, envName)] env, ok8 := envMap[fmt.Sprintf("%v/%v", envNS, envName)]
if !(ok1 && ok2 && ok3 && ok4 && ok5 && ok6 && ok7 && ok8) { if !(ok1 && ok2 && ok3 && ok4 && ok5 && ok6 && ok7 && ok8) {
gpm.logger.Warn("failed to adopt pod for function due to lack necessary information", gpm.logger.Warn("failed to adopt pod for function due to lack of necessary information",
zap.String("pod", pod.Name), zap.Any("labels", pod.Labels), zap.Any("annotations", pod.Annotations), zap.String("pod", pod.Name), zap.Any("labels", pod.Labels), zap.Any("annotations", pod.Annotations),
zap.String("env", env.Metadata.Name)) zap.String("env", env.Metadata.Name))
return return
@@ -355,16 +353,14 @@ func (gpm *GenericPoolManager) AdoptOrphanResources() {
Atime: time.Now(), Atime: time.Now(),
} }
// If fsvc already exists we just skip the duplicate one. And let reaper to recycle the duplicate pod.
// This is for the case that there are multiple function pods for the same function due to unknown reason.
_, err := gpm.fsCache.GetByFunction(fsvc.Function)
if err == nil {
return
}
_, err = gpm.fsCache.Add(fsvc) _, err = gpm.fsCache.Add(fsvc)
if err != nil { if err != nil {
gpm.logger.Warn("failed to adopt pod for function", zap.Error(err), zap.String("pod", pod.Name)) // If fsvc already exists we just skip the duplicate one. And let reaper to recycle the duplicate pods.
// This is for the case that there are multiple function pods for the same function due to unknown reason.
if !fscache.IsNameExistError(err) {
gpm.logger.Warn("failed to adopt pod for function", zap.Error(err), zap.String("pod", pod.Name))
}
return return
} }
@@ -373,8 +369,7 @@ func (gpm *GenericPoolManager) AdoptOrphanResources() {
}() }()
} }
envWG.Wait() wg.Wait()
podWG.Wait()
} }
func (gpm *GenericPoolManager) service() { func (gpm *GenericPoolManager) service() {
+37 -51
View File
@@ -20,7 +20,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/fission/fission/pkg/utils" "github.com/hashicorp/go-multierror"
"go.uber.org/zap" "go.uber.org/zap"
apiv1 "k8s.io/api/core/v1" apiv1 "k8s.io/api/core/v1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -29,6 +29,7 @@ import (
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1" fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
"github.com/fission/fission/pkg/crd" "github.com/fission/fission/pkg/crd"
"github.com/fission/fission/pkg/types" "github.com/fission/fission/pkg/types"
"github.com/fission/fission/pkg/utils"
) )
var ( var (
@@ -38,45 +39,39 @@ var (
// CleanupOldExecutorObjects cleans up resources created by old executor instances // CleanupOldExecutorObjects cleans up resources created by old executor instances
func CleanupOldExecutorObjects(logger *zap.Logger, kubernetesClient *kubernetes.Clientset, instanceId string) { func CleanupOldExecutorObjects(logger *zap.Logger, kubernetesClient *kubernetes.Clientset, instanceId string) {
err := cleanup(logger, kubernetesClient, instanceId) errs := &multierror.Error{}
if err != nil {
// TODO retry reaper; logged and ignored for now
logger.Error("Failed to cleanup old executor objects", zap.Error(err))
}
}
func cleanup(logger *zap.Logger, client *kubernetes.Clientset, instanceId string) error { err := cleanupHpa(logger, kubernetesClient, instanceId)
// Pods might still be running user functions, so we give them if err != nil {
errs = multierror.Append(errs, err)
}
err = cleanupDeployments(logger, kubernetesClient, instanceId)
if err != nil {
errs = multierror.Append(errs, err)
}
// Pods might be running user functions, so we give them
// a few minutes before terminating them. This time is the // a few minutes before terminating them. This time is the
// maximum function runtime, plus the time a router might // maximum function runtime, plus the time a router might
// still route to an old instance, i.e. router cache expiry // still route to an old instance, i.e. router cache expiry
// time. // time.
time.Sleep(6 * time.Minute) time.Sleep(6 * time.Minute)
err := cleanupServices(logger, client, instanceId) err = cleanupServices(logger, kubernetesClient, instanceId)
if err != nil { if err != nil {
return err errs = multierror.Append(errs, err)
} }
err = cleanupHpa(logger, client, instanceId) err = cleanupPods(logger, kubernetesClient, instanceId)
if err != nil { if err != nil {
return err errs = multierror.Append(errs, err)
} }
// Deployments are used for idle pools and can be cleaned up if errs.ErrorOrNil() != nil {
// immediately. (We should "adopt" these instead of creating // TODO retry reaper; logged and ignored for now
// a new pool.) logger.Error("Failed to cleanup old executor objects", zap.Error(err))
err = cleanupDeployments(logger, client, instanceId)
if err != nil {
return err
} }
err = cleanupPods(logger, client, instanceId)
if err != nil {
return err
}
return nil
} }
// CleanupKubeObject deletes given kubernetes object // CleanupKubeObject deletes given kubernetes object
@@ -119,20 +114,11 @@ func cleanupDeployments(logger *zap.Logger, client *kubernetes.Clientset, instan
} }
for _, dep := range deploymentList.Items { for _, dep := range deploymentList.Items {
id, ok := dep.ObjectMeta.Annotations[types.EXECUTOR_INSTANCEID_LABEL] id, ok := dep.ObjectMeta.Annotations[types.EXECUTOR_INSTANCEID_LABEL]
if ok && id != instanceId { if !ok {
logger.Debug("cleaning up deployment", zap.String("deployment", dep.ObjectMeta.Name)) // Backward compatibility with older label name
err := client.AppsV1().Deployments(dep.ObjectMeta.Namespace).Delete(dep.ObjectMeta.Name, &delOpt) id, ok = dep.ObjectMeta.Labels[types.EXECUTOR_INSTANCEID_LABEL]
if err != nil {
logger.Error("error cleaning up deployment",
zap.Error(err),
zap.String("deployment_name", dep.ObjectMeta.Name),
zap.String("deployment_namespace", dep.ObjectMeta.Namespace))
}
// ignore err
} }
// Backward compatibility with older label name if ok && id != instanceId {
pid, pok := dep.ObjectMeta.Annotations[types.POOLMGR_INSTANCEID_LABEL]
if pok && pid != instanceId {
logger.Debug("cleaning up deployment", zap.String("deployment", dep.ObjectMeta.Name)) logger.Debug("cleaning up deployment", zap.String("deployment", dep.ObjectMeta.Name))
err := client.AppsV1().Deployments(dep.ObjectMeta.Namespace).Delete(dep.ObjectMeta.Name, &delOpt) err := client.AppsV1().Deployments(dep.ObjectMeta.Namespace).Delete(dep.ObjectMeta.Name, &delOpt)
if err != nil { if err != nil {
@@ -154,6 +140,10 @@ func cleanupPods(logger *zap.Logger, client *kubernetes.Clientset, instanceId st
} }
for _, pod := range podList.Items { for _, pod := range podList.Items {
id, ok := pod.ObjectMeta.Annotations[types.EXECUTOR_INSTANCEID_LABEL] id, ok := pod.ObjectMeta.Annotations[types.EXECUTOR_INSTANCEID_LABEL]
if !ok {
// Backward compatibility with older label name
id, ok = pod.ObjectMeta.Labels[types.EXECUTOR_INSTANCEID_LABEL]
}
if ok && id != instanceId { if ok && id != instanceId {
logger.Debug("cleaning up pod", zap.String("pod", pod.ObjectMeta.Name)) logger.Debug("cleaning up pod", zap.String("pod", pod.ObjectMeta.Name))
err := client.CoreV1().Pods(pod.ObjectMeta.Namespace).Delete(pod.ObjectMeta.Name, nil) err := client.CoreV1().Pods(pod.ObjectMeta.Namespace).Delete(pod.ObjectMeta.Name, nil)
@@ -165,18 +155,6 @@ func cleanupPods(logger *zap.Logger, client *kubernetes.Clientset, instanceId st
} }
// ignore err // ignore err
} }
// Backward compatibility with older label name
pid, pok := pod.ObjectMeta.Annotations[types.POOLMGR_INSTANCEID_LABEL]
if pok && pid != instanceId {
logger.Debug("cleaning up pod", zap.String("pod", pod.ObjectMeta.Name))
err := client.CoreV1().Pods(pod.ObjectMeta.Namespace).Delete(pod.ObjectMeta.Name, nil)
if err != nil {
logger.Error("error cleaning up pod",
zap.Error(err),
zap.String("pod_name", pod.ObjectMeta.Name),
zap.String("pod_namespace", pod.ObjectMeta.Namespace))
}
}
} }
return nil return nil
} }
@@ -188,6 +166,10 @@ func cleanupServices(logger *zap.Logger, client *kubernetes.Clientset, instanceI
} }
for _, svc := range svcList.Items { for _, svc := range svcList.Items {
id, ok := svc.ObjectMeta.Annotations[types.EXECUTOR_INSTANCEID_LABEL] id, ok := svc.ObjectMeta.Annotations[types.EXECUTOR_INSTANCEID_LABEL]
if !ok {
// Backward compatibility with older label name
id, ok = svc.ObjectMeta.Labels[types.EXECUTOR_INSTANCEID_LABEL]
}
if ok && id != instanceId { if ok && id != instanceId {
logger.Debug("cleaning up service", zap.String("service", svc.ObjectMeta.Name)) logger.Debug("cleaning up service", zap.String("service", svc.ObjectMeta.Name))
err := client.CoreV1().Services(svc.ObjectMeta.Namespace).Delete(svc.ObjectMeta.Name, nil) err := client.CoreV1().Services(svc.ObjectMeta.Namespace).Delete(svc.ObjectMeta.Name, nil)
@@ -211,6 +193,10 @@ func cleanupHpa(logger *zap.Logger, client *kubernetes.Clientset, instanceId str
for _, hpa := range hpaList.Items { for _, hpa := range hpaList.Items {
id, ok := hpa.ObjectMeta.Annotations[types.EXECUTOR_INSTANCEID_LABEL] id, ok := hpa.ObjectMeta.Annotations[types.EXECUTOR_INSTANCEID_LABEL]
if !ok {
// Backward compatibility with older label name
id, ok = hpa.ObjectMeta.Labels[types.EXECUTOR_INSTANCEID_LABEL]
}
if ok && id != instanceId { if ok && id != instanceId {
logger.Debug("cleaning up HPA", zap.String("hpa", hpa.ObjectMeta.Name)) logger.Debug("cleaning up HPA", zap.String("hpa", hpa.ObjectMeta.Name))
err := client.AutoscalingV1().HorizontalPodAutoscalers(hpa.ObjectMeta.Namespace).Delete(hpa.ObjectMeta.Name, nil) err := client.AutoscalingV1().HorizontalPodAutoscalers(hpa.ObjectMeta.Namespace).Delete(hpa.ObjectMeta.Name, nil)
+15
View File
@@ -17,6 +17,9 @@ limitations under the License.
package util package util
import ( import (
"sync"
"time"
apiv1 "k8s.io/api/core/v1" apiv1 "k8s.io/api/core/v1"
) )
@@ -31,3 +34,15 @@ func ApplyImagePullSecret(secret string, podspec apiv1.PodSpec) *apiv1.PodSpec {
podspec.ImagePullSecrets = []apiv1.LocalObjectReference{{Name: secret}} podspec.ImagePullSecrets = []apiv1.LocalObjectReference{{Name: secret}}
return &podspec return &podspec
} }
func WaitTimeout(wg *sync.WaitGroup, timeout time.Duration) {
waitCh := make(chan struct{})
go func() {
defer close(waitCh)
wg.Wait()
}()
select {
case <-waitCh:
case <-time.After(timeout):
}
}
-1
View File
@@ -91,7 +91,6 @@ const (
) )
const EXECUTOR_INSTANCEID_LABEL = fv1.EXECUTOR_INSTANCEID_LABEL const EXECUTOR_INSTANCEID_LABEL = fv1.EXECUTOR_INSTANCEID_LABEL
const POOLMGR_INSTANCEID_LABEL = fv1.POOLMGR_INSTANCEID_LABEL
const ( const (
ChecksumTypeSHA256 = fv1.ChecksumTypeSHA256 ChecksumTypeSHA256 = fv1.ChecksumTypeSHA256