A newdeploy backend which uses new deployment to serve requests. This is the second phase of #193 and builds on top of changes in #384 . * Executor layer added on top of pool manager * Removed the external server for executor * Minor changes to keep existing semantics as much possible * Separating the executor vs. poolmgr backend functionality and associated data members * Executor logic separated from Poolmgr backend completely, placeholder for new backend * Changed references to poolmgr in tests * Moved poolmgr to it's package, as a side effect moved Cache to its's package (was causing cyclical dependency) and had to make some data structures exposed outside package * Rebased from master and changed references to tpr -> crd * Executor layer added on top of pool manager * Executor logic separated from Poolmgr backend completely, placeholder for new backend * Changed podName to a generic objectReference in fscache (#391) Changed podName to a generic objectReference in function service cache implementation. * Moved poolmgr to it's package, as a side effect moved Cache to its's package (was causing cyclical dependency) and had to make some data structures exposed outside package * Rebased from master and changed references to tpr -> crd * Merged from master with latest changes * Executor layer added on top of pool manager * Removed the external server for executor * Minor changes to keep existing semantics as much possible * Separating the executor vs. poolmgr backend functionality and associated data members * Executor logic separated from Poolmgr backend completely, placeholder for new backend * Changed references to poolmgr in tests * update compiling.md to use helm * Compile instructions: changed pullPolicy to IfNotPresent (#378) Containers will get stuck in ErrImagePull/ImagePullBackOff state otherwise * Moved poolmgr to it's package, as a side effect moved Cache to its's package (was causing cyclical dependency) and had to make some data structures exposed outside package * Fetcher called when pod is created for newDeploy backend but also supports older way, this is WIP and still needs pod specialization and creating & exposing a service so the URL can be hit by end user * WIP Specializing the POD as part of startup along with fetching * Working specialization of a new deployment. Needs some work on caching, cleanup etc. * Switched to service based address instead of POD address * Minor formating issue fixed * Added logging to pods and a readiness check, the readiness check is flaky though ATM * Fixed some rebase issues that were failing build * Better names for K8S objects and methods * Switched usage of FuncSvc in backends from pod to api.ObjectReference * Adding retry to fetcher request, for now just using default retry client which might need tweaking in future * Switching to plain old retry, some issue in getting retryablehttp with glide import * Removed stale executor service & deployment from previous merge * Addressed review comments, still testing some areas * Added types in FunctionSpec * Resolved conflicts due to merge from executor_abstraction branch * Added backend type on EnvironmentSpec along with operations for create/list/update, the pools are created/destroyed based on change in backend type * Backend from types and a minor err return issue fixed * Draft version of CPU and memory parameters added to environment * Added resourceReq to newDeploy, though it has some issues * Issue with resourceName fixed, now newdeploy pods also pick up resources from the environment config * Adding scale params, removing validation on CPU params for now * Fixed a formatting issue * Checking if slight more delay helps in the test which is currently failing for internal routes * The resourceList newly added in Env can not be compared by compiler, hence must use breakdown comparison instead * Added strategy selection on client side * Added caching, informers, delete operations for newdeploy backend functions * Deleted a stale directory * A simple HPA based on scale parameters, testing still WIP * Fixed a small issue in delete function, added HPA delete too when deleting a function * Previous merge missed the pkg flag for update fn command somehow, fixed that * Fixed comments from review * Changed poolmgr cleanup to be generic cleanup and moved to executor, added instanceID labels to newdeploy so that cleanup works * Moved instanceIdLabel to types to avoid cyclic dependency * More review fixes * Tweaking sleep to see results * If user does not provide poolsize, then it should not default to zero * Switched to naming convention for now, fixed default poolsize if not provided * Changed error return behaviour in delete fn, also changed cleanup to look based on obj type though support for additional type will need more work * Changed check location so avoid false logging * Test for newdeploy backend * Adding tests for poolmgr backend * Fixed an issue with glide dependency version, already fixed in master * Added instanceId for NewDeploy, Initial cleanup now cleans older objects of newdeploy backend, removed eagercreate flag and instead using minScale to drive eager creation * Moved cleanup to executor layer with cleanup for newDeploy backend, changes to use the new Cache impl * Cleaning up pod & rs along with deployment for newdeploy backend * Enhanced fn and env listing to show min/maxscale and resuorces respectively * Added conditional heapster deployment and fixed a small issue with resources for fetcher container in function pod * Addressed review comments from previous change * Addressed some more review comments - majorly create only on NotFoundError * Added TargetCPU as an input for scaling * Bumped target CPU to be greater than 0 and added a default value * Min replicas should be 1 even if the minScale is 0 when creating deployment * Changed name from 'backend' to executorType, added additional test for minscale 0 case, changed TargetCPU to TargetCPUPercent
283 lines
9.0 KiB
Go
283 lines
9.0 KiB
Go
/*
|
|
Copyright 2016 The Fission Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package executor
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
"time"
|
|
|
|
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
"k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/pkg/api"
|
|
|
|
"github.com/fission/fission"
|
|
"github.com/fission/fission/crd"
|
|
"github.com/fission/fission/executor/fscache"
|
|
)
|
|
|
|
// cleanupObjects cleans up resources created by old executortype instances
|
|
func cleanupObjects(kubernetesClient *kubernetes.Clientset,
|
|
namespace string,
|
|
instanceId string) {
|
|
go func() {
|
|
err := cleanup(kubernetesClient, namespace, instanceId)
|
|
if err != nil {
|
|
// TODO retry cleanup; logged and ignored for now
|
|
log.Printf("Failed to cleanup: %v", err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
func cleanup(client *kubernetes.Clientset, namespace string, instanceId string) error {
|
|
|
|
err := cleanupServices(client, namespace, instanceId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = cleanupHpa(client, namespace, instanceId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Deployments are used for idle pools and can be cleaned up
|
|
// immediately. (We should "adopt" these instead of creating
|
|
// a new pool.)
|
|
err = cleanupDeployments(client, namespace, instanceId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// See K8s #33845 and related bugs: deleting a deployment
|
|
// through the API doesn't cause the associated ReplicaSet to
|
|
// be deleted. (Fixed recently, but we may be running a
|
|
// version before the fix.)
|
|
err = cleanupReplicaSets(client, namespace, instanceId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Pods might still be running user functions, so we give them
|
|
// a few minutes before terminating them. This time is the
|
|
// maximum function runtime, plus the time a router might
|
|
// still route to an old instance, i.e. router cache expiry
|
|
// time.
|
|
time.Sleep(6 * time.Minute)
|
|
|
|
err = cleanupPods(client, namespace, instanceId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// idleObjectReaper reaps objects after certain idle time
|
|
func idleObjectReaper(kubeClient *kubernetes.Clientset,
|
|
fissionClient *crd.FissionClient,
|
|
fsCache *fscache.FunctionServiceCache,
|
|
idlePodReapTime time.Duration) {
|
|
|
|
pollSleep := time.Duration(2 * time.Minute)
|
|
for {
|
|
time.Sleep(pollSleep)
|
|
|
|
envs, err := fissionClient.Environments(meta_v1.NamespaceAll).List(meta_v1.ListOptions{})
|
|
if err != nil {
|
|
log.Fatalf("Failed to get environment list: %v", err)
|
|
}
|
|
|
|
for i := range envs.Items {
|
|
env := envs.Items[i]
|
|
if env.Spec.AllowedFunctionsPerContainer == fission.AllowedFunctionsPerContainerInfinite {
|
|
continue
|
|
}
|
|
funcSvcs, err := fsCache.ListOld(&env.Metadata, idlePodReapTime)
|
|
if err != nil {
|
|
log.Printf("Error reaping idle pods: %v", err)
|
|
continue
|
|
}
|
|
|
|
for _, fsvc := range funcSvcs {
|
|
|
|
fn, err := fissionClient.Functions(fsvc.Function.Namespace).Get(fsvc.Function.Name)
|
|
if err != nil {
|
|
log.Printf("Error getting function: %v", fsvc.Function.Name)
|
|
continue
|
|
}
|
|
|
|
// Ignore functions of NewDeploy ExecutorType with MinScale > 0
|
|
if fn.Spec.InvokeStrategy.ExecutionStrategy.MinScale > 0 && fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType == fission.ExecutorTypeNewdeploy {
|
|
continue
|
|
}
|
|
deleted, err := fsCache.DeleteOld(fsvc, idlePodReapTime)
|
|
|
|
if err != nil {
|
|
log.Printf("Error deleting Kubernetes objects for fsvc '%v': %v", fsvc, err)
|
|
log.Printf("Object Name| Object Kind | Object Namespace")
|
|
for _, kubeobj := range fsvc.KubernetesObjects {
|
|
log.Printf("%v | %v | %v", kubeobj.Name, kubeobj.Kind, kubeobj.Namespace)
|
|
}
|
|
}
|
|
|
|
if !deleted {
|
|
continue
|
|
}
|
|
for _, kubeobj := range fsvc.KubernetesObjects {
|
|
deleteKubeobject(kubeClient, &kubeobj)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func deleteKubeobject(kubeClient *kubernetes.Clientset, kubeobj *api.ObjectReference) {
|
|
switch strings.ToLower(kubeobj.Kind) {
|
|
case "pod":
|
|
err := kubeClient.CoreV1().Pods(kubeobj.Namespace).Delete(kubeobj.Name, nil)
|
|
logErr(fmt.Sprintf("cleaning up pod %v ", kubeobj.Name), err)
|
|
|
|
case "service":
|
|
err := kubeClient.CoreV1().Services(kubeobj.Namespace).Delete(kubeobj.Name, nil)
|
|
logErr(fmt.Sprintf("cleaning up service %v ", kubeobj.Name), err)
|
|
|
|
case "deployment":
|
|
depl, err := kubeClient.ExtensionsV1beta1().Deployments(kubeobj.Namespace).Get(kubeobj.Name, meta_v1.GetOptions{})
|
|
err = kubeClient.ExtensionsV1beta1().Deployments(kubeobj.Namespace).Delete(kubeobj.Name, nil)
|
|
logErr(fmt.Sprintf("cleaning up deployment %v ", kubeobj.Name), err)
|
|
cleanupDeploymentObjects(kubeClient, kubeobj.Namespace, depl.Labels)
|
|
|
|
case "horizontalpodautoscaler":
|
|
err := kubeClient.AutoscalingV1().HorizontalPodAutoscalers(kubeobj.Namespace).Delete(kubeobj.Name, nil)
|
|
logErr(fmt.Sprintf("cleaning up horizontalpodautoscaler %v ", kubeobj.Name), err)
|
|
|
|
default:
|
|
log.Printf("There was an error identifying the object type: %v for obj: %v", kubeobj.Kind, kubeobj)
|
|
|
|
}
|
|
}
|
|
|
|
func cleanupDeploymentObjects(kubeClient *kubernetes.Clientset, namespace string, sel map[string]string) {
|
|
rsList, err := kubeClient.ExtensionsV1beta1().ReplicaSets(namespace).List(meta_v1.ListOptions{LabelSelector: labels.Set(sel).AsSelector().String()})
|
|
logErr("Getting replicaset for deployment ", err)
|
|
for _, rs := range rsList.Items {
|
|
err = kubeClient.ExtensionsV1beta1().ReplicaSets(namespace).Delete(rs.Name, nil)
|
|
logErr(fmt.Sprintf("Cleaning replicaset %v for deployment", rs.Name), err)
|
|
}
|
|
|
|
podList, err := kubeClient.CoreV1().Pods(namespace).List(meta_v1.ListOptions{LabelSelector: labels.Set(sel).AsSelector().String()})
|
|
logErr("Getting pods for deployment ", err)
|
|
for _, pod := range podList.Items {
|
|
err = kubeClient.CoreV1().Pods(namespace).Delete(pod.Name, nil)
|
|
logErr(fmt.Sprintf("Cleaning pod %v for deployment", pod.Name), err)
|
|
}
|
|
}
|
|
|
|
func cleanupDeployments(client *kubernetes.Clientset, namespace string, instanceId string) error {
|
|
deploymentList, err := client.ExtensionsV1beta1().Deployments(namespace).List(meta_v1.ListOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, dep := range deploymentList.Items {
|
|
id, ok := dep.ObjectMeta.Labels[fission.EXECUTOR_INSTANCEID_LABEL]
|
|
if ok && id != instanceId {
|
|
log.Printf("Cleaning up deployment %v", dep.ObjectMeta.Name)
|
|
err := client.ExtensionsV1beta1().Deployments(namespace).Delete(dep.ObjectMeta.Name, nil)
|
|
logErr("cleaning up deployment", err)
|
|
// ignore err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func cleanupReplicaSets(client *kubernetes.Clientset, namespace string, instanceId string) error {
|
|
rsList, err := client.ExtensionsV1beta1().ReplicaSets(namespace).List(meta_v1.ListOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, rs := range rsList.Items {
|
|
id, ok := rs.ObjectMeta.Labels[fission.EXECUTOR_INSTANCEID_LABEL]
|
|
if ok && id != instanceId {
|
|
log.Printf("Cleaning up replicaset %v", rs.ObjectMeta.Name)
|
|
err := client.ExtensionsV1beta1().ReplicaSets(namespace).Delete(rs.ObjectMeta.Name, nil)
|
|
logErr("cleaning up replicaset", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func cleanupPods(client *kubernetes.Clientset, namespace string, instanceId string) error {
|
|
podList, err := client.CoreV1().Pods(namespace).List(meta_v1.ListOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, pod := range podList.Items {
|
|
id, ok := pod.ObjectMeta.Labels[fission.EXECUTOR_INSTANCEID_LABEL]
|
|
if ok && id != instanceId {
|
|
log.Printf("Cleaning up pod %v", pod.ObjectMeta.Name)
|
|
err := client.CoreV1().Pods(namespace).Delete(pod.ObjectMeta.Name, nil)
|
|
logErr("cleaning up pod", err)
|
|
// ignore err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func cleanupServices(client *kubernetes.Clientset, namespace string, instanceId string) error {
|
|
svcList, err := client.CoreV1().Services(namespace).List(meta_v1.ListOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, svc := range svcList.Items {
|
|
id, ok := svc.ObjectMeta.Labels[fission.EXECUTOR_INSTANCEID_LABEL]
|
|
if ok && id != instanceId {
|
|
log.Printf("Cleaning up svc %v", svc.ObjectMeta.Name)
|
|
err := client.CoreV1().Services(namespace).Delete(svc.ObjectMeta.Name, nil)
|
|
logErr("cleaning up service", err)
|
|
// ignore err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func cleanupHpa(client *kubernetes.Clientset, namespace string, instanceId string) error {
|
|
hpaList, err := client.AutoscalingV1().HorizontalPodAutoscalers(namespace).List(meta_v1.ListOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, hpa := range hpaList.Items {
|
|
id, ok := hpa.ObjectMeta.Labels[fission.EXECUTOR_INSTANCEID_LABEL]
|
|
if ok && id != instanceId {
|
|
log.Printf("Cleaning up HPA %v", hpa.ObjectMeta.Name)
|
|
err := client.AutoscalingV1().HorizontalPodAutoscalers(namespace).Delete(hpa.ObjectMeta.Name, nil)
|
|
logErr("cleaning up HPA", err)
|
|
}
|
|
|
|
}
|
|
return nil
|
|
|
|
}
|
|
|
|
func logErr(msg string, err error) {
|
|
if err != nil {
|
|
log.Printf("Error %v: %v", msg, err)
|
|
}
|
|
}
|