containers as functions (#1681)

PR adds a new executortype which supports running containers as functions. New CLI under functions is added to create container as functions.

Co-authored-by: Harsh Thakur <harshthakur9030@gmail.com>
Co-authored-by: Sanket Sudake <sanketsudake@gmail.com>
This commit is contained in:
Sahil Lakhwani
2021-07-16 18:08:41 +05:30
committed by GitHub
co-authored by Harsh Thakur Sanket Sudake
parent f3e1f9df90
commit a82281ad1c
33 changed files with 6053 additions and 198 deletions
+21 -5
View File
@@ -1,18 +1,34 @@
linters:
enable:
# Default linter
- deadcode
- gofmt
- goimports
- errcheck
- gosimple
- govet
- ineffassign
- misspell
- nakedret
- staticcheck
- structcheck
- typecheck
- unconvert
- unused
- varcheck
# Additional linters
- gofmt
- goimports
- misspell
- nakedret
- unconvert
# Enable in future
# - bodyclose
# - dogsled
# - dupl
# - gosec
# - ifshort
# - nilerr
# - prealloc
# - revive
# - unparam
# - wrapcheck
# - gocritic
linters-settings:
errcheck:
ignore: go.uber.org/zap:Sync
+2 -1
View File
@@ -99,7 +99,7 @@ func (client *PreUpgradeTaskClient) LatestSchemaApplied() error {
return errors.New("Could not get the Function CRD")
}
// Any new field added in Function spec can be checked here provided the substring matches the description in CRD Validation of the field
if !strings.Contains(funcCRD.Spec.String(), "RequestsPerPod") || !strings.Contains(funcCRD.Spec.String(), "OnceOnly") {
if !strings.Contains(funcCRD.Spec.String(), "RequestsPerPod") || !strings.Contains(funcCRD.Spec.String(), "OnceOnly") || !strings.Contains(funcCRD.Spec.String(), "PodSpec") {
return errors.New("Apply the newer CRDs before upgrading")
}
@@ -112,6 +112,7 @@ func (client *PreUpgradeTaskClient) LatestSchemaApplied() error {
if !strings.Contains(mqtCRD.Spec.String(), "PodSpec") {
return errors.New("Apply the newer CRDs before upgrading")
}
return nil
}
File diff suppressed because it is too large Load Diff
+1
View File
@@ -55,6 +55,7 @@ const (
const (
ExecutorTypePoolmgr ExecutorType = "poolmgr"
ExecutorTypeNewdeploy ExecutorType = "newdeploy"
ExecutorTypeContainer ExecutorType = "container"
)
const (
+6
View File
@@ -409,6 +409,11 @@ type (
// This is optional. If not specified default value will be taken as false
// +optional
OnceOnly bool `json:"onceOnly,omitempty"`
// Podspec specifies podspec to use for executor type container based functions
// Different arguments mentioned for container based function are populated inside a pod.
// +optional
PodSpec *apiv1.PodSpec `json:"podspec,omitempty"`
}
// InvokeStrategy is a set of controls over how the function executes.
@@ -450,6 +455,7 @@ type (
// Available value:
// - poolmgr
// - newdeploy
// - container
// +optional
ExecutorType ExecutorType `json:"ExecutorType"`
@@ -149,7 +149,7 @@ func (EnvironmentSpec) SwaggerDoc() map[string]string {
var map_ExecutionStrategy = map[string]string{
"": "ExecutionStrategy specifies low-level parameters for function execution, such as the number of instances.\n\nMinScale affects the cold start behavior for a function. If MinScale is 0 then the deployment is created on first invocation of function and is good for requests of asynchronous nature. If MinScale is greater than 0 then MinScale number of pods are created at the time of creation of function. This ensures faster response during first invocation at the cost of consuming resources.\n\nMaxScale is the maximum number of pods that function will scale to based on TargetCPUPercent and resources allocated to the function pod.",
"ExecutorType": "ExecutorType is the executor type of a function used. Defaults to \"poolmgr\".\n\nAvailable value:\n - poolmgr\n - newdeploy",
"ExecutorType": "ExecutorType is the executor type of a function used. Defaults to \"poolmgr\".\n\nAvailable value:\n - poolmgr\n - newdeploy\n - container",
"MinScale": "This is only for newdeploy to set up minimum replicas of deployment.",
"MaxScale": "This is only for newdeploy to set up maximum replicas of deployment.",
"TargetCPUPercent": "This is only for newdeploy to set up target CPU utilization of HPA.",
@@ -210,6 +210,7 @@ var map_FunctionSpec = map[string]string{
"concurrency": "Maximum number of pods to be specialized which will serve requests This is optional. If not specified default value will be taken as 500",
"requestsPerPod": "RequestsPerPod indicates the maximum number of concurrent requests that can be served by a specialized pod This is optional. If not specified default value will be taken as 1",
"onceOnly": "OnceOnly specifies if specialized pod will serve exactly one request in its lifetime and would be garbage collected after serving that one request This is optional. If not specified default value will be taken as false",
"podspec": "Podspec specifies podspec to use for executor type container based functions Different arguments mentioned for container based function are populated inside a pod.",
}
func (FunctionSpec) SwaggerDoc() map[string]string {
+5 -1
View File
@@ -274,6 +274,10 @@ func (spec FunctionSpec) Validate() error {
result = multierror.Append(result, spec.InvokeStrategy.Validate())
}
if spec.InvokeStrategy.ExecutionStrategy.ExecutorType == ExecutorTypeContainer && spec.PodSpec == nil {
result = multierror.Append(result, MakeValidationErr(ErrorInvalidObject, "FunctionSpec.PodSpec", "", "executor type container requires a pod spec"))
}
// TODO Add below validation warning
/*if spec.FunctionTimeout <= 0 {
result = multierror.Append(result, MakeValidationErr(ErrorInvalidValue, "FunctionTimeout value", spec.FunctionTimeout, "not a valid value. Should always be more than 0"))
@@ -300,7 +304,7 @@ func (es ExecutionStrategy) Validate() error {
result := &multierror.Error{}
switch es.ExecutorType {
case ExecutorTypeNewdeploy, ExecutorTypePoolmgr: // no op
case ExecutorTypeNewdeploy, ExecutorTypePoolmgr, ExecutorTypeContainer: // no op
default:
result = multierror.Append(result, MakeValidationErr(ErrorUnsupportedType, "ExecutionStrategy.ExecutorType", es.ExecutorType, "not a valid executor type"))
}
@@ -431,6 +431,11 @@ func (in *FunctionSpec) DeepCopyInto(out *FunctionSpec) {
*out = new(int)
**out = **in
}
if in.PodSpec != nil {
in, out := &in.PodSpec, &out.PodSpec
*out = new(corev1.PodSpec)
(*in).DeepCopyInto(*out)
}
return
}
+1 -4
View File
@@ -29,7 +29,6 @@ import (
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
@@ -47,11 +46,10 @@ type canaryConfigMgr struct {
kubeClient *kubernetes.Clientset
canaryConfigInformer *k8sCache.SharedIndexInformer
promClient *PrometheusApiClient
crdClient rest.Interface
canaryCfgCancelFuncMap *canaryConfigCancelFuncMap
}
func MakeCanaryConfigMgr(logger *zap.Logger, fissionClient *crd.FissionClient, kubeClient *kubernetes.Clientset, crdClient rest.Interface, prometheusSvc string) (*canaryConfigMgr, error) {
func MakeCanaryConfigMgr(logger *zap.Logger, fissionClient *crd.FissionClient, kubeClient *kubernetes.Clientset, prometheusSvc string) (*canaryConfigMgr, error) {
if prometheusSvc == "" {
logger.Info("try to retrieve prometheus server information from environment variables")
@@ -91,7 +89,6 @@ func MakeCanaryConfigMgr(logger *zap.Logger, fissionClient *crd.FissionClient, k
logger: logger.Named("canary_config_manager"),
fissionClient: fissionClient,
kubeClient: kubeClient,
crdClient: crdClient,
promClient: promClient,
canaryCfgCancelFuncMap: makecanaryConfigCancelFuncMap(),
}
+1 -2
View File
@@ -31,8 +31,7 @@ import (
func ConfigCanaryFeature(context context.Context, logger *zap.Logger, fissionClient *crd.FissionClient, kubeClient *kubernetes.Clientset, featureConfig *config.FeatureConfig, featureStatus map[string]string) error {
// start the appropriate controller
if featureConfig.CanaryConfig.IsEnabled {
canaryCfgMgr, err := canaryconfigmgr.MakeCanaryConfigMgr(logger, fissionClient, kubeClient, fissionClient.CoreV1().RESTClient(),
featureConfig.CanaryConfig.PrometheusSvc)
canaryCfgMgr, err := canaryconfigmgr.MakeCanaryConfigMgr(logger, fissionClient, kubeClient, featureConfig.CanaryConfig.PrometheusSvc)
if err != nil {
featureStatus[config.CanaryFeature] = err.Error()
return errors.Wrap(err, "failed to start canary config manager")
+4 -3
View File
@@ -19,6 +19,7 @@ package executor
import (
"encoding/json"
"fmt"
"html"
"io/ioutil"
"net/http"
"strings"
@@ -87,10 +88,10 @@ func (executor *Executor) getServiceForFunctionAPI(w http.ResponseWriter, r *htt
if active >= concurrency {
errMsg := fmt.Sprintf("max concurrency reached for %v. All %v instance are active", fn.ObjectMeta.Name, concurrency)
executor.logger.Error("error occurred", zap.String("error", errMsg))
http.Error(w, errMsg, http.StatusTooManyRequests)
http.Error(w, html.EscapeString(errMsg), http.StatusTooManyRequests)
return
}
} else if t == fv1.ExecutorTypeNewdeploy {
} else if t == fv1.ExecutorTypeNewdeploy || t == fv1.ExecutorTypeContainer {
fsvc, err := et.GetFuncSvcFromCache(fn)
if err == nil {
if et.IsValid(fsvc) {
@@ -227,7 +228,7 @@ func (executor *Executor) unTapService(w http.ResponseWriter, r *http.Request) {
t := tapSvcReq.FnExecutorType
if t != fv1.ExecutorTypePoolmgr {
msg := fmt.Sprintf("Unknown executor type '%v'", t)
http.Error(w, msg, http.StatusBadRequest)
http.Error(w, html.EscapeString(msg), http.StatusBadRequest)
return
}
+11 -1
View File
@@ -37,6 +37,7 @@ import (
"github.com/fission/fission/pkg/crd"
"github.com/fission/fission/pkg/executor/cms"
"github.com/fission/fission/pkg/executor/executortype"
"github.com/fission/fission/pkg/executor/executortype/container"
"github.com/fission/fission/pkg/executor/executortype/newdeploy"
"github.com/fission/fission/pkg/executor/executortype/poolmgr"
"github.com/fission/fission/pkg/executor/fscache"
@@ -291,7 +292,7 @@ func StartExecutor(logger *zap.Logger, functionNamespace string, envBuilderNames
ndm, err := newdeploy.MakeNewDeploy(
logger,
fissionClient, kubernetesClient, fissionClient.CoreV1().RESTClient(),
fissionClient, kubernetesClient,
functionNamespace, fetcherConfig, executorInstanceID,
&funcInformer, &envInformer,
)
@@ -299,9 +300,18 @@ func StartExecutor(logger *zap.Logger, functionNamespace string, envBuilderNames
return errors.Wrap(err, "new deploy manager creation faied")
}
cnm, err := container.MakeContainer(
logger,
fissionClient, kubernetesClient,
functionNamespace, executorInstanceID, &funcInformer)
if err != nil {
return errors.Wrap(err, "container manager creation faied")
}
executorTypes := make(map[fv1.ExecutorType]executortype.ExecutorType)
executorTypes[gpm.GetTypeName()] = gpm
executorTypes[ndm.GetTypeName()] = ndm
executorTypes[cnm.GetTypeName()] = cnm
adoptExistingResources, _ := strconv.ParseBool(os.Getenv("ADOPT_EXISTING_RESOURCES"))
@@ -0,0 +1,153 @@
/*
Copyright 2020 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 container
import (
"context"
"strconv"
multierror "github.com/hashicorp/go-multierror"
"go.uber.org/zap"
apiv1 "k8s.io/api/core/v1"
k8s_err "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
)
// getResources gets the resources(CPU, memory) set for the function
func (cn *Container) getResources(fn *fv1.Function) apiv1.ResourceRequirements {
resources := fn.Spec.Resources
if resources.Requests == nil {
resources.Requests = make(map[apiv1.ResourceName]resource.Quantity)
}
if resources.Limits == nil {
resources.Limits = make(map[apiv1.ResourceName]resource.Quantity)
}
val, ok := fn.Spec.Resources.Requests[apiv1.ResourceCPU]
if ok && !val.IsZero() {
resources.Requests[apiv1.ResourceCPU] = fn.Spec.Resources.Requests[apiv1.ResourceCPU]
}
val, ok = fn.Spec.Resources.Requests[apiv1.ResourceMemory]
if ok && !val.IsZero() {
resources.Requests[apiv1.ResourceMemory] = fn.Spec.Resources.Requests[apiv1.ResourceMemory]
}
val, ok = fn.Spec.Resources.Limits[apiv1.ResourceCPU]
if ok && !val.IsZero() {
resources.Limits[apiv1.ResourceCPU] = fn.Spec.Resources.Limits[apiv1.ResourceCPU]
}
val, ok = fn.Spec.Resources.Limits[apiv1.ResourceMemory]
if ok && !val.IsZero() {
resources.Limits[apiv1.ResourceMemory] = fn.Spec.Resources.Limits[apiv1.ResourceMemory]
}
return resources
}
// cleanupContainer cleans all kubernetes objects related to function
func (cn *Container) cleanupContainer(ns string, name string) error {
result := &multierror.Error{}
err := cn.deleteSvc(ns, name)
if err != nil && !k8s_err.IsNotFound(err) {
cn.logger.Error("error deleting service for Container function",
zap.Error(err),
zap.String("function_name", name),
zap.String("function_namespace", ns))
result = multierror.Append(result, err)
}
err = cn.deleteHpa(ns, name)
if err != nil && !k8s_err.IsNotFound(err) {
cn.logger.Error("error deleting HPA for Container function",
zap.Error(err),
zap.String("function_name", name),
zap.String("function_namespace", ns))
result = multierror.Append(result, err)
}
err = cn.deleteDeployment(ns, name)
if err != nil && !k8s_err.IsNotFound(err) {
cn.logger.Error("error deleting deployment for Container function",
zap.Error(err),
zap.String("function_name", name),
zap.String("function_namespace", ns))
result = multierror.Append(result, err)
}
return result.ErrorOrNil()
}
// referencedResourcesRVSum returns the sum of resource version of all resources the function references to.
// We used to update timestamp in the deployment environment field in order to trigger a rolling update when
// the function referenced resources get updated. However, use timestamp means we are not able to avoid tri-
// ggering a rolling update when executor tries to adopt orphaned deployment due to timestamp changed which
// is unwanted. In order to let executor adopt deployment without triggering a rolling update, we need an
// identical way to get a value that can reflect resources changed without affecting by the time.
// To achieve this goal, the sum of the resource version of all referenced resources is a good fit for our
// scenario since the sum of the resource version is always the same as long as no resources changed.
func referencedResourcesRVSum(client *kubernetes.Clientset, namespace string, secrets []fv1.SecretReference, cfgmaps []fv1.ConfigMapReference) (int, error) {
rvCount := 0
if len(secrets) > 0 {
list, err := client.CoreV1().Secrets(namespace).List(context.TODO(), metav1.ListOptions{})
if err != nil {
return 0, err
}
objmap := make(map[string]apiv1.Secret)
for _, secret := range list.Items {
objmap[secret.Namespace+"/"+secret.Name] = secret
}
for _, ref := range secrets {
s, ok := objmap[ref.Namespace+"/"+ref.Name]
if ok {
rv, _ := strconv.ParseInt(s.ResourceVersion, 10, 32)
rvCount += int(rv)
}
}
}
if len(cfgmaps) > 0 {
list, err := client.CoreV1().ConfigMaps(namespace).List(context.TODO(), metav1.ListOptions{})
if err != nil {
return 0, err
}
objmap := make(map[string]apiv1.ConfigMap)
for _, cfg := range list.Items {
objmap[cfg.Namespace+"/"+cfg.Name] = cfg
}
for _, ref := range cfgmaps {
s, ok := objmap[ref.Namespace+"/"+ref.Name]
if ok {
rv, _ := strconv.ParseInt(s.ResourceVersion, 10, 32)
rvCount += int(rv)
}
}
}
return rvCount, nil
}
@@ -0,0 +1,778 @@
/*
Copyright 2020 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 container
import (
"context"
"fmt"
"os"
"reflect"
"strconv"
"strings"
"sync"
"time"
multierror "github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
"go.uber.org/zap"
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
k8sErrs "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
k8sTypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
"github.com/fission/fission/pkg/crd"
"github.com/fission/fission/pkg/executor/executortype"
"github.com/fission/fission/pkg/executor/fscache"
"github.com/fission/fission/pkg/executor/reaper"
"github.com/fission/fission/pkg/throttler"
"github.com/fission/fission/pkg/utils"
"github.com/fission/fission/pkg/utils/maps"
)
var _ executortype.ExecutorType = &Container{}
type (
// Container represents an executor type
Container struct {
logger *zap.Logger
kubernetesClient *kubernetes.Clientset
fissionClient *crd.FissionClient
instanceID string
// fetcherConfig *fetcherConfig.Config
runtimeImagePullPolicy apiv1.PullPolicy
namespace string
useIstio bool
fsCache *fscache.FunctionServiceCache // cache funcSvc's by function, address and pod name
throttler *throttler.Throttler
funcInformer *k8sCache.SharedIndexInformer
serviceInformer k8sCache.SharedIndexInformer
deploymentInformer k8sCache.SharedIndexInformer
defaultIdlePodReapTime time.Duration
}
)
// MakeContainer initializes and returns an instance of CaaF
func MakeContainer(
logger *zap.Logger,
fissionClient *crd.FissionClient,
kubernetesClient *kubernetes.Clientset,
namespace string,
instanceID string,
funcInformer *k8sCache.SharedIndexInformer) (executortype.ExecutorType, error) {
enableIstio := false
if len(os.Getenv("ENABLE_ISTIO")) > 0 {
istio, err := strconv.ParseBool(os.Getenv("ENABLE_ISTIO"))
if err != nil {
logger.Error("failed to parse 'ENABLE_ISTIO', set to false", zap.Error(err))
}
enableIstio = istio
}
caaf := &Container{
logger: logger.Named("CaaF"),
fissionClient: fissionClient,
kubernetesClient: kubernetesClient,
instanceID: instanceID,
namespace: namespace,
fsCache: fscache.MakeFunctionServiceCache(logger),
throttler: throttler.MakeThrottler(1 * time.Minute),
funcInformer: funcInformer,
runtimeImagePullPolicy: utils.GetImagePullPolicy(os.Getenv("RUNTIME_IMAGE_PULL_POLICY")),
useIstio: enableIstio,
// Time is set slightly higher than NewDeploy as cold starts are longer for CaaF
defaultIdlePodReapTime: 1 * time.Minute,
}
(*caaf.funcInformer).AddEventHandler(caaf.FuncInformerHandler())
informerFactory, err := utils.GetInformerFactoryByExecutor(caaf.kubernetesClient, fv1.ExecutorTypeContainer)
if err != nil {
return nil, err
}
caaf.serviceInformer = informerFactory.Core().V1().Services().Informer()
caaf.deploymentInformer = informerFactory.Apps().V1().Deployments().Informer()
return caaf, nil
}
// Run start the function along with an object reaper.
func (caaf *Container) Run(ctx context.Context) {
go caaf.idleObjectReaper()
}
// GetTypeName returns the executor type name.
func (caaf *Container) GetTypeName() fv1.ExecutorType {
return fv1.ExecutorTypeContainer
}
// GetTotalAvailable has not been implemented for CaaF.
func (caaf *Container) GetTotalAvailable(fn *fv1.Function) int {
// Not Implemented for CaaF.
return 0
}
// UnTapService has not been implemented for CaaF.
func (caaf *Container) UnTapService(key string, svcHost string) {
// Not Implemented for CaaF.
}
// GetFuncSvc returns a function service; error otherwise.
func (caaf *Container) GetFuncSvc(ctx context.Context, fn *fv1.Function) (*fscache.FuncSvc, error) {
return caaf.createFunction(fn)
}
// GetFuncSvcFromCache returns a function service from cache; error otherwise.
func (caaf *Container) GetFuncSvcFromCache(fn *fv1.Function) (*fscache.FuncSvc, error) {
return caaf.fsCache.GetByFunction(&fn.ObjectMeta)
}
// DeleteFuncSvcFromCache deletes a function service from cache.
func (caaf *Container) DeleteFuncSvcFromCache(fsvc *fscache.FuncSvc) {
caaf.fsCache.DeleteEntry(fsvc)
}
// GetFuncSvcFromPoolCache has not been implemented for Container Functions
func (caaf *Container) GetFuncSvcFromPoolCache(fn *fv1.Function, requestsPerPod int) (*fscache.FuncSvc, int, error) {
// Not Implemented for NewDeployment. Will be used when support of concurrent specialization of same function is added.
return nil, 0, nil
}
// TapService makes a TouchByAddress request to the cache.
func (caaf *Container) TapService(svcHost string) error {
err := caaf.fsCache.TouchByAddress(svcHost)
if err != nil {
return err
}
return nil
}
func (caaf *Container) getServiceInfo(obj apiv1.ObjectReference) (*apiv1.Service, error) {
item, exists, err := utils.GetCachedItem(obj, caaf.serviceInformer)
if err != nil || !exists {
caaf.logger.Debug(
"Falling back to getting service info from k8s API -- this may cause performance issues for your function.",
zap.Bool("exists", exists),
zap.Error(err),
)
service, err := caaf.kubernetesClient.CoreV1().Services(obj.Namespace).Get(context.TODO(), obj.Name, metav1.GetOptions{})
return service, err
}
service := item.(*apiv1.Service)
return service, nil
}
func (caaf *Container) getDeploymentInfo(obj apiv1.ObjectReference) (*appsv1.Deployment, error) {
item, exists, err := utils.GetCachedItem(obj, caaf.deploymentInformer)
if err != nil || !exists {
caaf.logger.Debug(
"Falling back to getting deployment info from k8s API -- this may cause performance issues for your function.",
zap.Bool("exists", exists),
zap.Error(err),
)
deployment, err := caaf.kubernetesClient.AppsV1().Deployments(obj.Namespace).Get(context.TODO(), obj.Name, metav1.GetOptions{})
return deployment, err
}
deployment := item.(*appsv1.Deployment)
return deployment, nil
}
// IsValid does a get on the service address to ensure it's a valid service, then
// scale deployment to 1 replica if there are no available replicas for function.
// Return true if no error occurs, return false otherwise.
func (caaf *Container) IsValid(fsvc *fscache.FuncSvc) bool {
if len(strings.Split(fsvc.Address, ".")) == 0 {
caaf.logger.Error("address not found in function service")
return false
}
if len(fsvc.KubernetesObjects) == 0 {
caaf.logger.Error("no kubernetes object related to function", zap.String("function", fsvc.Function.Name))
return false
}
for _, obj := range fsvc.KubernetesObjects {
if strings.ToLower(obj.Kind) == "service" {
_, err := caaf.getServiceInfo(obj)
if err != nil {
if !k8sErrs.IsNotFound(err) {
caaf.logger.Error("error validating function service", zap.String("function", fsvc.Function.Name), zap.Error(err))
}
return false
}
} else if strings.ToLower(obj.Kind) == "deployment" {
currentDeploy, err := caaf.getDeploymentInfo(obj)
if err != nil {
if !k8sErrs.IsNotFound(err) {
caaf.logger.Error("error validating function deployment", zap.String("function", fsvc.Function.Name), zap.Error(err))
}
return false
}
if currentDeploy.Status.AvailableReplicas < 1 {
return false
}
}
}
return true
}
// RefreshFuncPods deletes pods related to the function so that new pods are replenished
func (caaf *Container) RefreshFuncPods(logger *zap.Logger, f fv1.Function) error {
funcLabels := caaf.getDeployLabels(f.ObjectMeta)
dep, err := caaf.kubernetesClient.AppsV1().Deployments(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{
LabelSelector: labels.Set(funcLabels).AsSelector().String(),
})
if err != nil {
return err
}
// Ideally there should be only one deployment but for now we rely on label/selector to ensure that condition
for _, deployment := range dep.Items {
rvCount, err := referencedResourcesRVSum(caaf.kubernetesClient, deployment.Namespace, f.Spec.Secrets, f.Spec.ConfigMaps)
if err != nil {
return err
}
patch := fmt.Sprintf(`{"spec" : {"template": {"spec":{"containers":[{"name": "%s", "env":[{"name": "%s", "value": "%v"}]}]}}}}`,
f.ObjectMeta.Name, fv1.ResourceVersionCount, rvCount)
_, err = caaf.kubernetesClient.AppsV1().Deployments(deployment.ObjectMeta.Namespace).Patch(context.TODO(), deployment.ObjectMeta.Name,
k8sTypes.StrategicMergePatchType,
[]byte(patch), metav1.PatchOptions{})
if err != nil {
return err
}
}
return nil
}
// AdoptExistingResources attempts to adopt resources for functions in all namespaces.
func (caaf *Container) AdoptExistingResources() {
fnList, err := caaf.fissionClient.CoreV1().Functions(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{})
if err != nil {
caaf.logger.Error("error getting function list", zap.Error(err))
return
}
wg := &sync.WaitGroup{}
for i := range fnList.Items {
fn := &fnList.Items[i]
if fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType == fv1.ExecutorTypeContainer {
wg.Add(1)
go func() {
defer wg.Done()
_, err = caaf.fnCreate(fn)
if err != nil {
caaf.logger.Warn("failed to adopt resources for function", zap.Error(err))
return
}
caaf.logger.Info("adopt resources for function", zap.String("function", fn.ObjectMeta.Name))
}()
}
}
wg.Wait()
}
// CleanupOldExecutorObjects cleans orphaned resources.
func (caaf *Container) CleanupOldExecutorObjects() {
caaf.logger.Info("CaaF starts to clean orphaned resources", zap.String("instanceID", caaf.instanceID))
errs := &multierror.Error{}
listOpts := metav1.ListOptions{
LabelSelector: labels.Set(map[string]string{fv1.EXECUTOR_TYPE: string(fv1.ExecutorTypeContainer)}).AsSelector().String(),
}
err := reaper.CleanupHpa(caaf.logger, caaf.kubernetesClient, caaf.instanceID, listOpts)
if err != nil {
errs = multierror.Append(errs, err)
}
err = reaper.CleanupDeployments(caaf.logger, caaf.kubernetesClient, caaf.instanceID, listOpts)
if err != nil {
errs = multierror.Append(errs, err)
}
err = reaper.CleanupServices(caaf.logger, caaf.kubernetesClient, caaf.instanceID, listOpts)
if err != nil {
errs = multierror.Append(errs, err)
}
if errs.ErrorOrNil() != nil {
// TODO retry reaper; logged and ignored for now
caaf.logger.Error("Failed to cleanup old executor objects", zap.Error(err))
}
}
func (caaf *Container) createFunction(fn *fv1.Function) (*fscache.FuncSvc, error) {
if fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypeContainer {
return nil, nil
}
fsvcObj, err := caaf.throttler.RunOnce(string(fn.ObjectMeta.UID), func(ableToCreate bool) (interface{}, error) {
if ableToCreate {
return caaf.fnCreate(fn)
}
return caaf.fsCache.GetByFunctionUID(fn.ObjectMeta.UID)
})
if err != nil {
e := "error creating k8s resources for function"
caaf.logger.Error(e,
zap.Error(err),
zap.String("function_name", fn.ObjectMeta.Name),
zap.String("function_namespace", fn.ObjectMeta.Namespace))
return nil, errors.Wrapf(err, "%s %s_%s", e, fn.ObjectMeta.Name, fn.ObjectMeta.Namespace)
}
fsvc, ok := fsvcObj.(*fscache.FuncSvc)
if !ok {
caaf.logger.Panic("receive unknown object while creating function - expected pointer of function service object")
}
return fsvc, err
}
func (caaf *Container) deleteFunction(fn *fv1.Function) error {
if fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypeContainer {
return nil
}
err := caaf.fnDelete(fn)
if err != nil {
err = errors.Wrapf(err, "error deleting kubernetes objects of function %v", fn.ObjectMeta)
}
return err
}
func (caaf *Container) fnCreate(fn *fv1.Function) (*fscache.FuncSvc, error) {
cleanupFunc := func(ns string, name string) {
err := caaf.cleanupContainer(ns, name)
if err != nil {
caaf.logger.Error("received error while cleaning function resources",
zap.String("namespace", ns), zap.String("name", name))
}
}
objName := caaf.getObjName(fn)
deployLabels := caaf.getDeployLabels(fn.ObjectMeta)
deployAnnotations := caaf.getDeployAnnotations(fn.ObjectMeta)
// to support backward compatibility, if the function was created in default ns, we fall back to creating the
// deployment of the function in fission-function ns
ns := caaf.namespace
if fn.ObjectMeta.Namespace != metav1.NamespaceDefault {
ns = fn.ObjectMeta.Namespace
}
// Envoy(istio-proxy) returns 404 directly before istio pilot
// propagates latest Envoy-specific configuration.
// Since Container waits for pods of deployment to be ready,
// change the order of kubeObject creation (create service first,
// then deployment) to take advantage of waiting time.
svc, err := caaf.createOrGetSvc(fn, deployLabels, deployAnnotations, objName, ns)
if err != nil {
caaf.logger.Error("error creating service", zap.Error(err), zap.String("service", objName))
go cleanupFunc(ns, objName)
return nil, errors.Wrapf(err, "error creating service %v", objName)
}
svcAddress := fmt.Sprintf("%v.%v", svc.Name, svc.Namespace)
depl, err := caaf.createOrGetDeployment(fn, objName, deployLabels, deployAnnotations, ns)
if err != nil {
caaf.logger.Error("error creating deployment", zap.Error(err), zap.String("deployment", objName))
go cleanupFunc(ns, objName)
return nil, errors.Wrapf(err, "error creating deployment %v", objName)
}
hpa, err := caaf.createOrGetHpa(objName, &fn.Spec.InvokeStrategy.ExecutionStrategy, depl, deployLabels, deployAnnotations)
if err != nil {
caaf.logger.Error("error creating HPA", zap.Error(err), zap.String("hpa", objName))
go cleanupFunc(ns, objName)
return nil, errors.Wrapf(err, "error creating the HPA %v", objName)
}
kubeObjRefs := []apiv1.ObjectReference{
{
//obj.TypeMeta.Kind does not work hence this, needs investigation and a fix
Kind: "deployment",
Name: depl.ObjectMeta.Name,
APIVersion: depl.TypeMeta.APIVersion,
Namespace: depl.ObjectMeta.Namespace,
ResourceVersion: depl.ObjectMeta.ResourceVersion,
UID: depl.ObjectMeta.UID,
},
{
Kind: "service",
Name: svc.ObjectMeta.Name,
APIVersion: svc.TypeMeta.APIVersion,
Namespace: svc.ObjectMeta.Namespace,
ResourceVersion: svc.ObjectMeta.ResourceVersion,
UID: svc.ObjectMeta.UID,
},
{
Kind: "horizontalpodautoscaler",
Name: hpa.ObjectMeta.Name,
APIVersion: hpa.TypeMeta.APIVersion,
Namespace: hpa.ObjectMeta.Namespace,
ResourceVersion: hpa.ObjectMeta.ResourceVersion,
UID: hpa.ObjectMeta.UID,
},
}
fsvc := &fscache.FuncSvc{
Name: objName,
Function: &fn.ObjectMeta,
Address: svcAddress,
KubernetesObjects: kubeObjRefs,
Executor: fv1.ExecutorTypeContainer,
}
_, err = caaf.fsCache.Add(*fsvc)
if err != nil {
caaf.logger.Error("error adding function to cache", zap.Error(err), zap.Any("function", fsvc.Function))
return fsvc, err
}
caaf.fsCache.IncreaseColdStarts(fn.ObjectMeta.Name, string(fn.ObjectMeta.UID))
return fsvc, nil
}
func (caaf *Container) updateFunction(oldFn *fv1.Function, newFn *fv1.Function) error {
if oldFn.ObjectMeta.ResourceVersion == newFn.ObjectMeta.ResourceVersion {
return nil
}
// Ignoring updates to functions which are not of Container type
if newFn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypeContainer &&
oldFn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypeContainer {
return nil
}
// Executor type is no longer Container
if newFn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypeContainer &&
oldFn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType == fv1.ExecutorTypeContainer {
caaf.logger.Info("function does not use new deployment executor anymore, deleting resources",
zap.Any("function", newFn))
// IMP - pass the oldFn, as the new/modified function is not in cache
return caaf.deleteFunction(oldFn)
}
// Executor type changed to Container from something else
if oldFn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypeContainer &&
newFn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType == fv1.ExecutorTypeContainer {
caaf.logger.Info("function type changed to Container, creating resources",
zap.Any("old_function", oldFn.ObjectMeta),
zap.Any("new_function", newFn.ObjectMeta))
_, err := caaf.createFunction(newFn)
if err != nil {
caaf.updateStatus(oldFn, err, "error changing the function's type to Container")
}
return err
}
if oldFn.Spec.InvokeStrategy != newFn.Spec.InvokeStrategy {
// to support backward compatibility, if the function was created in default ns, we fall back to creating the
// deployment of the function in fission-function ns, so cleaning up resources there
ns := caaf.namespace
if newFn.ObjectMeta.Namespace != metav1.NamespaceDefault {
ns = newFn.ObjectMeta.Namespace
}
fsvc, err := caaf.fsCache.GetByFunctionUID(newFn.ObjectMeta.UID)
if err != nil {
err = errors.Wrapf(err, "error updating function due to unable to find function service cache: %v", oldFn)
return err
}
hpa, err := caaf.getHpa(ns, fsvc.Name)
if err != nil {
caaf.updateStatus(oldFn, err, "error getting HPA while updating function")
return err
}
hpaChanged := false
if newFn.Spec.InvokeStrategy.ExecutionStrategy.MinScale != oldFn.Spec.InvokeStrategy.ExecutionStrategy.MinScale {
replicas := int32(newFn.Spec.InvokeStrategy.ExecutionStrategy.MinScale)
hpa.Spec.MinReplicas = &replicas
hpaChanged = true
}
if newFn.Spec.InvokeStrategy.ExecutionStrategy.MaxScale != oldFn.Spec.InvokeStrategy.ExecutionStrategy.MaxScale {
hpa.Spec.MaxReplicas = int32(newFn.Spec.InvokeStrategy.ExecutionStrategy.MaxScale)
hpaChanged = true
}
if newFn.Spec.InvokeStrategy.ExecutionStrategy.TargetCPUPercent != oldFn.Spec.InvokeStrategy.ExecutionStrategy.TargetCPUPercent {
targetCpupercent := int32(newFn.Spec.InvokeStrategy.ExecutionStrategy.TargetCPUPercent)
hpa.Spec.TargetCPUUtilizationPercentage = &targetCpupercent
hpaChanged = true
}
if hpaChanged {
err := caaf.updateHpa(hpa)
if err != nil {
caaf.updateStatus(oldFn, err, "error updating HPA while updating function")
return err
}
}
}
deployChanged := false
// If length of slice has changed then no need to check individual elements
if len(oldFn.Spec.Secrets) != len(newFn.Spec.Secrets) {
deployChanged = true
} else {
for i, newSecret := range newFn.Spec.Secrets {
if newSecret != oldFn.Spec.Secrets[i] {
deployChanged = true
break
}
}
}
if len(oldFn.Spec.ConfigMaps) != len(newFn.Spec.ConfigMaps) {
deployChanged = true
} else {
for i, newConfig := range newFn.Spec.ConfigMaps {
if newConfig != oldFn.Spec.ConfigMaps[i] {
deployChanged = true
break
}
}
}
if !reflect.DeepEqual(oldFn.Spec.PodSpec, newFn.Spec.PodSpec) {
deployChanged = true
}
if deployChanged {
return caaf.updateFuncDeployment(newFn)
}
return nil
}
func (caaf *Container) updateFuncDeployment(fn *fv1.Function) error {
fsvc, err := caaf.fsCache.GetByFunctionUID(fn.ObjectMeta.UID)
if err != nil {
err = errors.Wrapf(err, "error updating function due to unable to find function service cache: %v", fn)
return err
}
fnObjName := fsvc.Name
deployLabels := caaf.getDeployLabels(fn.ObjectMeta)
caaf.logger.Info("updating deployment due to function update",
zap.String("deployment", fnObjName), zap.Any("function", fn.ObjectMeta.Name))
// to support backward compatibility, if the function was created in default ns, we fall back to creating the
// deployment of the function in fission-function ns
ns := caaf.namespace
if fn.ObjectMeta.Namespace != metav1.NamespaceDefault {
ns = fn.ObjectMeta.Namespace
}
existingDepl, err := caaf.kubernetesClient.AppsV1().Deployments(ns).Get(context.TODO(), fnObjName, metav1.GetOptions{})
if err != nil {
return err
}
// the resource version inside function packageRef is changed,
// so the content of fetchRequest in deployment cmd is different.
// Therefore, the deployment update will trigger a rolling update.
newDeployment, err := caaf.getDeploymentSpec(fn, existingDepl.Spec.Replicas, // use current replicas instead of minscale in the ExecutionStrategy.
fnObjName, ns, deployLabels, caaf.getDeployAnnotations(fn.ObjectMeta))
if err != nil {
caaf.updateStatus(fn, err, "failed to get new deployment spec while updating function")
return err
}
err = caaf.updateDeployment(newDeployment, ns)
if err != nil {
caaf.updateStatus(fn, err, "failed to update deployment while updating function")
return err
}
return nil
}
func (caaf *Container) fnDelete(fn *fv1.Function) error {
multierr := &multierror.Error{}
// GetByFunction uses resource version as part of cache key, however,
// the resource version in function metadata will be changed when a function
// is deleted and cause Container backend fails to delete the entry.
// Use GetByFunctionUID instead of GetByFunction here to find correct
// fsvc entry.
fsvc, err := caaf.fsCache.GetByFunctionUID(fn.ObjectMeta.UID)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("fsvc not found in cache: %v", fn.ObjectMeta))
return err
}
objName := fsvc.Name
_, err = caaf.fsCache.DeleteOld(fsvc, time.Second*0)
if err != nil {
multierr = multierror.Append(multierr,
errors.Wrapf(err, "error deleting the function from cache"))
}
// to support backward compatibility, if the function was created in default ns, we fall back to creating the
// deployment of the function in fission-function ns, so cleaning up resources there
ns := caaf.namespace
if fn.ObjectMeta.Namespace != metav1.NamespaceDefault {
ns = fn.ObjectMeta.Namespace
}
err = caaf.cleanupContainer(ns, objName)
multierr = multierror.Append(multierr, err)
return multierr.ErrorOrNil()
}
// getObjName returns a unique name for kubernetes objects of function
func (caaf *Container) getObjName(fn *fv1.Function) string {
// use meta uuid of function, this ensure we always get the same name for the same function.
uid := fn.ObjectMeta.UID[len(fn.ObjectMeta.UID)-17:]
return strings.ToLower(fmt.Sprintf("Container-%v-%v-%v", fn.ObjectMeta.Name, fn.ObjectMeta.Namespace, uid))
}
func (caaf *Container) getDeployLabels(fnMeta metav1.ObjectMeta) map[string]string {
deployLabels := maps.CopyStringMap(fnMeta.Labels)
deployLabels[fv1.EXECUTOR_TYPE] = string(fv1.ExecutorTypeContainer)
deployLabels[fv1.FUNCTION_NAME] = fnMeta.Name
deployLabels[fv1.FUNCTION_NAMESPACE] = fnMeta.Namespace
deployLabels[fv1.FUNCTION_UID] = string(fnMeta.UID)
return deployLabels
}
func (caaf *Container) getDeployAnnotations(fnMeta metav1.ObjectMeta) map[string]string {
deployAnnotations := maps.CopyStringMap(fnMeta.Annotations)
deployAnnotations[fv1.EXECUTOR_INSTANCEID_LABEL] = caaf.instanceID
deployAnnotations[fv1.FUNCTION_RESOURCE_VERSION] = fnMeta.ResourceVersion
return deployAnnotations
}
// updateStatus is a function which updates status of update.
// Current implementation only logs messages, in future it will update function status
func (caaf *Container) updateStatus(fn *fv1.Function, err error, message string) {
caaf.logger.Error("function status update", zap.Error(err), zap.Any("function", fn), zap.String("message", message))
}
// idleObjectReaper reaps objects after certain idle time
func (caaf *Container) idleObjectReaper() {
pollSleep := 5 * time.Second
for {
time.Sleep(pollSleep)
funcSvcs, err := caaf.fsCache.ListOld(pollSleep)
if err != nil {
caaf.logger.Error("error reaping idle pods", zap.Error(err))
continue
}
for i := range funcSvcs {
fsvc := funcSvcs[i]
if fsvc.Executor != fv1.ExecutorTypeContainer {
continue
}
fn, err := caaf.fissionClient.CoreV1().Functions(fsvc.Function.Namespace).Get(context.TODO(), fsvc.Function.Name, metav1.GetOptions{})
if err != nil {
// CaaF manager handles the function delete event and clean cache/kubeobjs itself,
// so we ignore the not found error for functions with CaaF executor type here.
if k8sErrs.IsNotFound(err) && fsvc.Executor == fv1.ExecutorTypeContainer {
continue
}
caaf.logger.Error("error getting function", zap.Error(err), zap.String("function", fsvc.Function.Name))
continue
}
idlePodReapTime := caaf.defaultIdlePodReapTime
if fn.Spec.IdleTimeout != nil {
idlePodReapTime = time.Duration(*fn.Spec.IdleTimeout) * time.Second
}
if time.Since(fsvc.Atime) < idlePodReapTime {
continue
}
go func() {
deployObj := getDeploymentObj(fsvc.KubernetesObjects)
if deployObj == nil {
caaf.logger.Error("error finding function deployment", zap.Error(err), zap.String("function", fsvc.Function.Name))
return
}
currentDeploy, err := caaf.kubernetesClient.AppsV1().
Deployments(deployObj.Namespace).Get(context.TODO(), deployObj.Name, metav1.GetOptions{})
if err != nil {
caaf.logger.Error("error getting function deployment", zap.Error(err), zap.String("function", fsvc.Function.Name))
return
}
minScale := int32(fn.Spec.InvokeStrategy.ExecutionStrategy.MinScale)
// do nothing if the current replicas is already lower than minScale
if *currentDeploy.Spec.Replicas <= minScale {
return
}
err = caaf.scaleDeployment(deployObj.Namespace, deployObj.Name, minScale)
if err != nil {
caaf.logger.Error("error scaling down function deployment", zap.Error(err), zap.String("function", fsvc.Function.Name))
}
}()
}
}
}
func getDeploymentObj(kubeobjs []apiv1.ObjectReference) *apiv1.ObjectReference {
for _, kubeobj := range kubeobjs {
switch strings.ToLower(kubeobj.Kind) {
case "deployment":
return &kubeobj
}
}
return nil
}
@@ -0,0 +1,289 @@
/*
Copyright 2020 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 container
import (
"context"
"fmt"
"time"
"go.uber.org/zap"
appsv1 "k8s.io/api/apps/v1"
autoscalingv1 "k8s.io/api/autoscaling/v1"
apiv1 "k8s.io/api/core/v1"
k8s_err "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
"github.com/fission/fission/pkg/executor/util"
)
func (cn *Container) createOrGetDeployment(fn *fv1.Function, deployName string, deployLabels map[string]string, deployAnnotations map[string]string, deployNamespace string) (*appsv1.Deployment, error) {
// The specializationTimeout here refers to the creation of the pod and not the loading of function
// as in other executors.
specializationTimeout := fn.Spec.InvokeStrategy.ExecutionStrategy.SpecializationTimeout
minScale := int32(fn.Spec.InvokeStrategy.ExecutionStrategy.MinScale)
// Always scale to at least one pod when createOrGetDeployment
// is called. The idleObjectReaper will scale-in the deployment
// later if no requests to the function.
if minScale <= 0 {
minScale = 1
}
deployment, err := cn.getDeploymentSpec(fn, &minScale, deployName, deployNamespace, deployLabels, deployAnnotations)
if err != nil {
return nil, err
}
existingDepl, err := cn.kubernetesClient.AppsV1().Deployments(deployNamespace).Get(context.TODO(), deployName, metav1.GetOptions{})
if err == nil {
// Try to adopt orphan deployment created by the old executor.
if existingDepl.Annotations[fv1.EXECUTOR_INSTANCEID_LABEL] != cn.instanceID {
existingDepl.Annotations = deployment.Annotations
existingDepl.Labels = deployment.Labels
existingDepl.Spec.Template.Spec.Containers = deployment.Spec.Template.Spec.Containers
existingDepl.Spec.Template.Spec.ServiceAccountName = deployment.Spec.Template.Spec.ServiceAccountName
existingDepl.Spec.Template.Spec.TerminationGracePeriodSeconds = deployment.Spec.Template.Spec.TerminationGracePeriodSeconds
// Update with the latest deployment spec. Kubernetes will trigger
// rolling update if spec is different from the one in the cluster.
existingDepl, err = cn.kubernetesClient.AppsV1().Deployments(deployNamespace).Update(context.TODO(), existingDepl, metav1.UpdateOptions{})
if err != nil {
cn.logger.Warn("error adopting cn", zap.Error(err),
zap.String("cn", deployName), zap.String("ns", deployNamespace))
return nil, err
}
// In this case, we just return without waiting for it for fast bootstraping.
return existingDepl, nil
}
if *existingDepl.Spec.Replicas < minScale {
err = cn.scaleDeployment(existingDepl.Namespace, existingDepl.Name, minScale)
if err != nil {
cn.logger.Error("error scaling up function deployment", zap.Error(err), zap.String("function", fn.ObjectMeta.Name))
return nil, err
}
}
if existingDepl.Status.AvailableReplicas < minScale {
existingDepl, err = cn.waitForDeploy(existingDepl, minScale, specializationTimeout)
}
return existingDepl, err
} else if k8s_err.IsNotFound(err) {
depl, err := cn.kubernetesClient.AppsV1().Deployments(deployNamespace).Create(context.TODO(), deployment, metav1.CreateOptions{})
if err != nil {
if k8s_err.IsAlreadyExists(err) {
depl, err = cn.kubernetesClient.AppsV1().Deployments(deployNamespace).Get(context.TODO(), deployName, metav1.GetOptions{})
}
if err != nil {
cn.logger.Error("error while creating function deployment",
zap.Error(err),
zap.String("function", fn.ObjectMeta.Name),
zap.String("deployment_name", deployName),
zap.String("deployment_namespace", deployNamespace))
return nil, err
}
}
if minScale > 0 {
depl, err = cn.waitForDeploy(depl, minScale, specializationTimeout)
}
return depl, err
}
return nil, err
}
func (cn *Container) updateDeployment(deployment *appsv1.Deployment, ns string) error {
_, err := cn.kubernetesClient.AppsV1().Deployments(ns).Update(context.TODO(), deployment, metav1.UpdateOptions{})
return err
}
func (cn *Container) deleteDeployment(ns string, name string) error {
// DeletePropagationBackground deletes the object immediately and dependent are deleted later
// DeletePropagationForeground not advisable; it marks for deleteion and API can still serve those objects
deletePropagation := metav1.DeletePropagationBackground
return cn.kubernetesClient.AppsV1().Deployments(ns).Delete(context.TODO(), name, metav1.DeleteOptions{
PropagationPolicy: &deletePropagation,
})
}
func (cn *Container) waitForDeploy(depl *appsv1.Deployment, replicas int32, specializationTimeout int) (*appsv1.Deployment, error) {
// if no specializationTimeout is set, use default value
if specializationTimeout < fv1.DefaultSpecializationTimeOut {
specializationTimeout = fv1.DefaultSpecializationTimeOut
}
for i := 0; i < specializationTimeout; i++ {
latestDepl, err := cn.kubernetesClient.AppsV1().Deployments(depl.ObjectMeta.Namespace).Get(context.TODO(), depl.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
// TODO check for imagePullerror
// use AvailableReplicas here is better than ReadyReplicas
// since the pods may not be able to serve network traffic yet.
if latestDepl.Status.AvailableReplicas >= replicas {
return latestDepl, err
}
time.Sleep(time.Second)
}
// this error appears in the executor pod logs
timeoutError := fmt.Errorf("failed to create deployment within the timeout window of %d seconds", specializationTimeout)
return nil, timeoutError
}
func (cn *Container) getDeploymentSpec(fn *fv1.Function, targetReplicas *int32,
deployName string, deployNamespace string, deployLabels map[string]string, deployAnnotations map[string]string) (*appsv1.Deployment, error) {
replicas := int32(fn.Spec.InvokeStrategy.ExecutionStrategy.MinScale)
if targetReplicas != nil {
replicas = *targetReplicas
}
gracePeriodSeconds := int64(6 * 60)
podAnnotations := make(map[string]string)
if cn.useIstio {
podAnnotations["sidecar.istio.io/inject"] = "false"
}
podLabels := make(map[string]string)
for k, v := range deployLabels {
podLabels[k] = v
}
// Set maxUnavailable and maxSurge to 20% is because we want
// fission to rollout newer function version gradually without
// affecting any online service. For example, if you set maxSurge
// to 100%, the new ReplicaSet scales up immediately and may
// consume all remaining compute resources which might be an
// issue if a cluster's resource is on a budget.
// TODO: add to ExecutionStrategy so that the user
// can do more fine control over different functions.
maxUnavailable := intstr.FromString("20%")
maxSurge := intstr.FromString("20%")
// Container updates the environment variable "LastUpdateTimestamp" of deployment
// whenever a configmap/secret gets an update, but it also leaves multiple ReplicaSets for
// rollback purpose. Since fission always update a deployment instead of performing a
// rollback, set RevisionHistoryLimit to 0 to disable this feature.
revisionHistoryLimit := int32(0)
resources := cn.getResources(fn)
// Other executor types rely on Environments to add configmaps and secrets
envFromSources, err := util.ConvertConfigSecrets(fn, cn.kubernetesClient)
if err != nil {
return nil, err
}
rvCount, err := referencedResourcesRVSum(cn.kubernetesClient, fn.ObjectMeta.Namespace, fn.Spec.Secrets, fn.Spec.ConfigMaps)
if err != nil {
return nil, err
}
if fn.Spec.PodSpec == nil {
return nil, fmt.Errorf("podSpec is not set for function %s", fn.ObjectMeta.Name)
}
container := &apiv1.Container{
Name: fn.ObjectMeta.Name,
ImagePullPolicy: cn.runtimeImagePullPolicy,
TerminationMessagePath: "/dev/termination-log",
Lifecycle: &apiv1.Lifecycle{
PreStop: &apiv1.Handler{
Exec: &apiv1.ExecAction{
Command: []string{
"/bin/sleep",
fmt.Sprintf("%v", gracePeriodSeconds),
},
},
},
},
Env: []apiv1.EnvVar{
{
Name: fv1.ResourceVersionCount,
Value: fmt.Sprintf("%v", rvCount),
},
},
EnvFrom: envFromSources,
// https://istio.io/docs/setup/kubernetes/additional-setup/requirements/
Resources: resources,
}
podSpec, err := util.MergePodSpec(&apiv1.PodSpec{
Containers: []apiv1.Container{*container},
TerminationGracePeriodSeconds: &gracePeriodSeconds,
}, fn.Spec.PodSpec)
if err != nil {
return nil, err
}
pod := apiv1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: podLabels,
Annotations: podAnnotations,
},
Spec: *podSpec,
}
pod.Spec = *(util.ApplyImagePullSecret("", pod.Spec))
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: deployName,
Labels: deployLabels,
Annotations: deployAnnotations,
},
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: deployLabels,
},
Template: pod,
Strategy: appsv1.DeploymentStrategy{
Type: appsv1.RollingUpdateDeploymentStrategyType,
RollingUpdate: &appsv1.RollingUpdateDeployment{
MaxUnavailable: &maxUnavailable,
MaxSurge: &maxSurge,
},
},
RevisionHistoryLimit: &revisionHistoryLimit,
},
}
return deployment, nil
}
func (caaf *Container) scaleDeployment(deplNS string, deplName string, replicas int32) error {
caaf.logger.Info("scaling deployment",
zap.String("deployment", deplName),
zap.String("namespace", deplNS),
zap.Int32("replicas", replicas))
_, err := caaf.kubernetesClient.AppsV1().Deployments(deplNS).UpdateScale(context.TODO(), deplName, &autoscalingv1.Scale{
ObjectMeta: metav1.ObjectMeta{
Name: deplName,
Namespace: deplNS,
},
Spec: autoscalingv1.ScaleSpec{
Replicas: replicas,
},
}, metav1.UpdateOptions{})
return err
}
@@ -0,0 +1,85 @@
/*
Copyright 2020 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 container
import (
"go.uber.org/zap"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
)
func (caaf *Container) FuncInformerHandler() k8sCache.ResourceEventHandlerFuncs {
return k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
fn := obj.(*fv1.Function)
fnExecutorType := fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType
if fnExecutorType != "" && fnExecutorType != fv1.ExecutorTypeContainer {
return
}
// TODO: A workaround to process items in parallel. We should use workqueue ("k8s.io/client-go/util/workqueue")
// and worker pattern to process items instead of moving process to another goroutine.
// example: https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/job/job_controller.go
go func() {
log := caaf.logger.With(zap.String("function_name", fn.ObjectMeta.Name), zap.String("function_namespace", fn.ObjectMeta.Namespace))
log.Debug("start function create handler")
_, err := caaf.createFunction(fn)
if err != nil {
log.Error("error eager creating function", zap.Error(err))
}
log.Debug("end function create handler")
}()
},
DeleteFunc: func(obj interface{}) {
fn := obj.(*fv1.Function)
fnExecutorType := fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType
if fnExecutorType != "" && fnExecutorType != fv1.ExecutorTypeContainer {
return
}
go func() {
log := caaf.logger.With(zap.String("function_name", fn.ObjectMeta.Name), zap.String("function_namespace", fn.ObjectMeta.Namespace))
log.Debug("start function delete handler")
err := caaf.deleteFunction(fn)
if err != nil {
log.Error("error deleting function", zap.Error(err))
}
log.Debug("end function delete handler")
}()
},
UpdateFunc: func(oldObj interface{}, newObj interface{}) {
oldFn := oldObj.(*fv1.Function)
newFn := newObj.(*fv1.Function)
fnExecutorType := oldFn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType
if fnExecutorType != "" && fnExecutorType != fv1.ExecutorTypeContainer {
return
}
go func() {
log := caaf.logger.With(zap.String("function_name", newFn.ObjectMeta.Name),
zap.String("function_namespace", newFn.ObjectMeta.Namespace),
zap.String("old_function_name", oldFn.ObjectMeta.Name))
log.Debug("start function update handler")
err := caaf.updateFunction(oldFn, newFn)
if err != nil {
log.Error("error updating function",
zap.Error(err))
}
log.Debug("end function update handler")
}()
},
}
}
+113
View File
@@ -0,0 +1,113 @@
/*
Copyright 2020 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 container
import (
"context"
"github.com/pkg/errors"
"go.uber.org/zap"
appsv1 "k8s.io/api/apps/v1"
asv1 "k8s.io/api/autoscaling/v1"
k8s_err "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
)
const (
DeploymentKind = "Deployment"
DeploymentVersion = "apps/v1"
)
func (cn *Container) createOrGetHpa(hpaName string, execStrategy *fv1.ExecutionStrategy,
depl *appsv1.Deployment, deployLabels map[string]string, deployAnnotations map[string]string) (*asv1.HorizontalPodAutoscaler, error) {
if depl == nil {
return nil, errors.New("failed to create HPA, found empty deployment")
}
minRepl := int32(execStrategy.MinScale)
if minRepl == 0 {
minRepl = 1
}
maxRepl := int32(execStrategy.MaxScale)
if maxRepl == 0 {
maxRepl = minRepl
}
targetCPU := int32(execStrategy.TargetCPUPercent)
hpa := &asv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: hpaName,
Labels: deployLabels,
Annotations: deployAnnotations,
},
Spec: asv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: asv1.CrossVersionObjectReference{
Kind: DeploymentKind,
Name: depl.ObjectMeta.Name,
APIVersion: DeploymentVersion,
},
MinReplicas: &minRepl,
MaxReplicas: maxRepl,
TargetCPUUtilizationPercentage: &targetCPU,
},
}
existingHpa, err := cn.getHpa(depl.ObjectMeta.Namespace, hpaName)
if err == nil {
// to adopt orphan service
if existingHpa.Annotations[fv1.EXECUTOR_INSTANCEID_LABEL] != cn.instanceID {
existingHpa.Annotations = hpa.Annotations
existingHpa.Labels = hpa.Labels
existingHpa.Spec = hpa.Spec
existingHpa, err = cn.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(depl.ObjectMeta.Namespace).Update(context.TODO(), existingHpa, metav1.UpdateOptions{})
if err != nil {
cn.logger.Warn("error adopting HPA", zap.Error(err),
zap.String("HPA", hpaName), zap.String("ns", depl.ObjectMeta.Namespace))
return nil, err
}
}
return existingHpa, err
} else if k8s_err.IsNotFound(err) {
cHpa, err := cn.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(depl.ObjectMeta.Namespace).Create(context.TODO(), hpa, metav1.CreateOptions{})
if err != nil {
if k8s_err.IsAlreadyExists(err) {
cHpa, err = cn.getHpa(depl.ObjectMeta.Namespace, hpaName)
}
if err != nil {
return nil, err
}
}
return cHpa, nil
}
return nil, err
}
func (cn *Container) getHpa(ns, name string) (*asv1.HorizontalPodAutoscaler, error) {
return cn.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(ns).Get(context.TODO(), name, metav1.GetOptions{})
}
func (cn *Container) updateHpa(hpa *asv1.HorizontalPodAutoscaler) error {
_, err := cn.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(hpa.ObjectMeta.Namespace).Update(context.TODO(), hpa, metav1.UpdateOptions{})
return err
}
func (cn *Container) deleteHpa(ns string, name string) error {
return cn.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(ns).Delete(context.TODO(), name, metav1.DeleteOptions{})
}
+103
View File
@@ -0,0 +1,103 @@
/*
Copyright 2020 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 container
import (
"context"
"fmt"
"go.uber.org/zap"
apiv1 "k8s.io/api/core/v1"
k8s_err "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
)
func (cn *Container) getSvPort(fn *fv1.Function) (port int32, err error) {
if fn.Spec.PodSpec == nil {
return port, fmt.Errorf("podspec is empty for function %s", fn.ObjectMeta.Name)
}
if len(fn.Spec.PodSpec.Containers) != 1 {
return port, fmt.Errorf("podspec should have exactly one container %s", fn.ObjectMeta.Name)
}
if len(fn.Spec.PodSpec.Containers[0].Ports) != 1 {
return port, fmt.Errorf("container should have exactly one port %s", fn.ObjectMeta.Name)
}
return fn.Spec.PodSpec.Containers[0].Ports[0].ContainerPort, nil
}
func (cn *Container) createOrGetSvc(fn *fv1.Function, deployLabels map[string]string, deployAnnotations map[string]string, svcName string, svcNamespace string) (*apiv1.Service, error) {
targetPort, err := cn.getSvPort(fn)
if err != nil {
return nil, err
}
service := &apiv1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: svcName,
Labels: deployLabels,
Annotations: deployAnnotations,
},
Spec: apiv1.ServiceSpec{
Ports: []apiv1.ServicePort{
{
Name: "http-env",
Port: int32(80),
TargetPort: intstr.FromInt(int(targetPort)),
},
},
Selector: deployLabels,
Type: apiv1.ServiceTypeClusterIP,
},
}
existingSvc, err := cn.kubernetesClient.CoreV1().Services(svcNamespace).Get(context.TODO(), svcName, metav1.GetOptions{})
if err == nil {
// to adopt orphan service
if existingSvc.Annotations[fv1.EXECUTOR_INSTANCEID_LABEL] != cn.instanceID {
existingSvc.Annotations = service.Annotations
existingSvc.Labels = service.Labels
existingSvc.Spec.Ports = service.Spec.Ports
existingSvc.Spec.Selector = service.Spec.Selector
existingSvc.Spec.Type = service.Spec.Type
existingSvc, err = cn.kubernetesClient.CoreV1().Services(svcNamespace).Update(context.TODO(), existingSvc, metav1.UpdateOptions{})
if err != nil {
cn.logger.Warn("error adopting service", zap.Error(err),
zap.String("service", svcName), zap.String("ns", svcNamespace))
return nil, err
}
}
return existingSvc, err
} else if k8s_err.IsNotFound(err) {
svc, err := cn.kubernetesClient.CoreV1().Services(svcNamespace).Create(context.TODO(), service, metav1.CreateOptions{})
if err != nil {
if k8s_err.IsAlreadyExists(err) {
svc, err = cn.kubernetesClient.CoreV1().Services(svcNamespace).Get(context.TODO(), svcName, metav1.GetOptions{})
}
if err != nil {
return nil, err
}
}
return svc, nil
}
return nil, err
}
func (cn *Container) deleteSvc(ns string, name string) error {
return cn.kubernetesClient.CoreV1().Services(ns).Delete(context.TODO(), name, metav1.DeleteOptions{})
}
@@ -263,6 +263,7 @@ func (deploy *NewDeploy) getDeploymentSpec(fn *fv1.Function, env *fv1.Environmen
Ports: []apiv1.ContainerPort{
{
Name: "http-env",
// Now that we have added Port field in spec, should we make this configurable too?
ContainerPort: int32(8888),
},
},
@@ -455,6 +456,7 @@ func (deploy *NewDeploy) createOrGetSvc(deployLabels map[string]string, deployAn
{
Name: "http-env",
Port: int32(80),
// Since Function spec now supports Port , should we make this configurable too?
TargetPort: intstr.FromInt(8888),
},
},
@@ -36,7 +36,6 @@ import (
"k8s.io/apimachinery/pkg/labels"
k8sTypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
@@ -59,7 +58,6 @@ type (
kubernetesClient *kubernetes.Clientset
fissionClient *crd.FissionClient
crdClient rest.Interface
instanceID string
fetcherConfig *fetcherConfig.Config
@@ -86,7 +84,6 @@ func MakeNewDeploy(
logger *zap.Logger,
fissionClient *crd.FissionClient,
kubernetesClient *kubernetes.Clientset,
crdClient rest.Interface,
namespace string,
fetcherConfig *fetcherConfig.Config,
instanceID string,
@@ -107,7 +104,6 @@ func MakeNewDeploy(
fissionClient: fissionClient,
kubernetesClient: kubernetesClient,
crdClient: crdClient,
instanceID: instanceID,
namespace: namespace,
@@ -123,10 +119,8 @@ func MakeNewDeploy(
envInformer: envInformer,
}
if nd.crdClient != nil {
(*nd.funcInformer).AddEventHandler(nd.FunctionEventHandlers())
(*nd.envInformer).AddEventHandler(nd.EnvEventHandlers())
}
informerFactory, err := utils.GetInformerFactoryByExecutor(nd.kubernetesClient, fv1.ExecutorTypePoolmgr)
if err != nil {
@@ -226,9 +220,13 @@ func (deploy *NewDeploy) getDeploymentInfo(obj apiv1.ObjectReference) (*appsv1.D
// Return true if no error occurs, return false otherwise.
func (deploy *NewDeploy) IsValid(fsvc *fscache.FuncSvc) bool {
if len(strings.Split(fsvc.Address, ".")) == 0 {
deploy.logger.Error("address not found in function service")
return false
}
if len(fsvc.KubernetesObjects) == 0 {
deploy.logger.Error("no kubernetes object related to function", zap.String("function", fsvc.Function.Name))
return false
}
for _, obj := range fsvc.KubernetesObjects {
if strings.ToLower(obj.Kind) == "service" {
_, err := deploy.getServiceInfo(obj)
@@ -239,11 +237,7 @@ func (deploy *NewDeploy) IsValid(fsvc *fscache.FuncSvc) bool {
return false
}
}
}
for _, obj := range fsvc.KubernetesObjects {
if strings.ToLower(obj.Kind) == "deployment" {
} else if strings.ToLower(obj.Kind) == "deployment" {
currentDeploy, err := deploy.getDeploymentInfo(obj)
if err != nil {
if !k8sErrs.IsNotFound(err) {
@@ -251,17 +245,15 @@ func (deploy *NewDeploy) IsValid(fsvc *fscache.FuncSvc) bool {
}
return false
}
// return directly when available replicas > 0
if currentDeploy.Status.AvailableReplicas > 0 {
return true
}
}
}
if currentDeploy.Status.AvailableReplicas < 1 {
return false
}
}
}
return true
}
// RefreshFuncPods deleted pods related to the function so that new pods are replenished
// RefreshFuncPods deletes pods related to the function so that new pods are replenished
func (deploy *NewDeploy) RefreshFuncPods(logger *zap.Logger, f fv1.Function) error {
env, err := deploy.fissionClient.CoreV1().Environments(f.Spec.Environment.Namespace).Get(context.TODO(), f.Spec.Environment.Name, metav1.GetOptions{})
+1 -1
View File
@@ -186,7 +186,7 @@ func (gp *GenericPool) updateCPUUtilizationSvc() {
if !gp.checkMetricsApi() {
checkDuration = 180
gp.logger.Error("Metrics API not available")
gp.logger.Warn("Metrics API not available")
}
serviceFunc := func() {
+64
View File
@@ -17,10 +17,16 @@ limitations under the License.
package util
import (
"context"
"errors"
"sync"
"time"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
)
// ApplyImagePullSecret applies image pull secret to the give pod spec.
@@ -49,3 +55,61 @@ func WaitTimeout(wg *sync.WaitGroup, timeout time.Duration) {
case <-time.After(timeout):
}
}
// ConvertConfigSecrets returns envFromSource which can be passed directly into the pod spec
func ConvertConfigSecrets(fn *fv1.Function, kc *kubernetes.Clientset) ([]apiv1.EnvFromSource, error) {
cmList := fn.Spec.ConfigMaps
secList := fn.Spec.Secrets
cmEnvSources := make([]*apiv1.ConfigMapEnvSource, 0)
secEnvSources := make([]*apiv1.SecretEnvSource, 0)
for _, cm := range cmList {
if cm.Namespace != fn.Namespace {
return nil, errors.New("Function should not reference config map of different namespace")
}
_, err := kc.CoreV1().ConfigMaps(cm.Namespace).Get(context.TODO(), cm.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
cmEnvSource := &apiv1.ConfigMapEnvSource{
LocalObjectReference: apiv1.LocalObjectReference{Name: cm.Name},
}
cmEnvSources = append(cmEnvSources, cmEnvSource)
}
for _, sec := range secList {
if sec.Namespace != fn.Namespace {
return nil, errors.New("Function should not reference secret of different namespace")
}
_, err := kc.CoreV1().Secrets(sec.Namespace).Get(context.TODO(), sec.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
secEnvSource := &apiv1.SecretEnvSource{
LocalObjectReference: apiv1.LocalObjectReference{Name: sec.Name},
}
secEnvSources = append(secEnvSources, secEnvSource)
}
envFromSources := make([]apiv1.EnvFromSource, 0)
for _, cmEnvSource := range cmEnvSources {
envFromSource := apiv1.EnvFromSource{
ConfigMapRef: cmEnvSource,
}
envFromSources = append(envFromSources, envFromSource)
}
for _, secEnvSource := range secEnvSources {
envFromSource := apiv1.EnvFromSource{
SecretRef: secEnvSource,
}
envFromSources = append(envFromSources, envFromSource)
}
return envFromSources, nil
}
+49 -2
View File
@@ -154,13 +154,60 @@ func Commands() *cobra.Command {
},
})
runContainerCmd := &cobra.Command{
Use: "run-container",
Aliases: []string{"runc"},
Short: "Alpha: Run a container image as a function",
RunE: wrapper.Wrapper(RunContainer),
}
wrapper.SetFlags(runContainerCmd, flag.FlagSet{
Required: []flag.Flag{flag.FnName, flag.FnImageName},
Optional: []flag.Flag{
flag.FnPort, flag.FnCommand, flag.FnArgs,
flag.FnCfgMap, flag.FnSecret,
flag.FnExecutionTimeout,
flag.FnIdleTimeout,
flag.Labels, flag.Annotation,
// flag for newdeploy to use.
flag.RunTimeMinCPU, flag.RunTimeMaxCPU, flag.RunTimeMinMemory,
flag.RunTimeMaxMemory, flag.ReplicasMin,
flag.ReplicasMax, flag.RunTimeTargetCPU,
flag.NamespaceFunction, flag.SpecSave, flag.SpecDry,
},
})
updateContainerCmd := &cobra.Command{
Use: "update-container",
Aliases: []string{"updatec"},
Short: "Alpha: Update a function running a container",
RunE: wrapper.Wrapper(UpdateContainer),
}
wrapper.SetFlags(updateContainerCmd, flag.FlagSet{
Required: []flag.Flag{flag.FnName},
Optional: []flag.Flag{
flag.FnImageName, flag.FnPort,
flag.FnCommand, flag.FnArgs,
flag.FnSecret, flag.FnCfgMap,
flag.FnExecutionTimeout, flag.FnIdleTimeout,
flag.Labels, flag.Annotation,
flag.RunTimeMinCPU, flag.RunTimeMaxCPU, flag.RunTimeMinMemory,
flag.RunTimeMaxMemory, flag.ReplicasMin, flag.ReplicasMax,
flag.RunTimeTargetCPU,
flag.NamespaceFunction, flag.SpecSave,
},
})
command := &cobra.Command{
Use: "function",
Aliases: []string{"fn"},
Short: "Create, update and manage functions",
}
command.AddCommand(createCmd, getCmd, getmetaCmd, updateCmd, deleteCmd, listCmd, logsCmd, testCmd)
command.AddCommand(createCmd, getCmd, getmetaCmd, updateCmd, deleteCmd, listCmd, logsCmd, testCmd,
runContainerCmd, updateContainerCmd)
return command
}
+35 -24
View File
@@ -285,18 +285,6 @@ func (opts *CreateSubCommand) complete(input cli.Input) error {
Namespace: fnNamespace,
},
Spec: fv1.FunctionSpec{
Environment: fv1.EnvironmentReference{
Name: envName,
Namespace: envNamespace,
},
Package: fv1.FunctionPackageRef{
FunctionName: entrypoint,
PackageRef: fv1.PackageRef{
Namespace: pkgMetadata.Namespace,
Name: pkgMetadata.Name,
ResourceVersion: pkgMetadata.ResourceVersion,
},
},
Secrets: secrets,
ConfigMaps: cfgmaps,
Resources: *resourceReq,
@@ -313,6 +301,18 @@ func (opts *CreateSubCommand) complete(input cli.Input) error {
if err != nil {
return err
}
opts.function.Spec.Environment = fv1.EnvironmentReference{
Name: envName,
Namespace: envNamespace,
}
opts.function.Spec.Package = fv1.FunctionPackageRef{
FunctionName: entrypoint,
PackageRef: fv1.PackageRef{
Namespace: pkgMetadata.Namespace,
Name: pkgMetadata.Name,
ResourceVersion: pkgMetadata.ResourceVersion,
},
}
return nil
}
@@ -392,14 +392,20 @@ func getInvokeStrategy(input cli.Input, existingInvokeStrategy *fv1.InvokeStrate
var es *fv1.ExecutionStrategy
if existingInvokeStrategy == nil {
es, err = getExecutionStrategy(input)
} else {
es, err = updateExecutionStrategy(input, &existingInvokeStrategy.ExecutionStrategy)
}
executorType, err := getExecutorType(input)
if err != nil {
return nil, err
}
es, err = getExecutionStrategy(executorType, input)
if err != nil {
return nil, err
}
} else {
es, err = updateExecutionStrategy(input, &existingInvokeStrategy.ExecutionStrategy)
if err != nil {
return nil, err
}
}
return &fv1.InvokeStrategy{
ExecutionStrategy: *es,
@@ -407,20 +413,23 @@ func getInvokeStrategy(input cli.Input, existingInvokeStrategy *fv1.InvokeStrate
}, nil
}
func getExecutionStrategy(input cli.Input) (strategy *fv1.ExecutionStrategy, err error) {
var fnExecutor fv1.ExecutorType
func getExecutorType(input cli.Input) (executorType fv1.ExecutorType, err error) {
switch input.String(flagkey.FnExecutorType) {
case "":
fallthrough
case string(fv1.ExecutorTypePoolmgr):
fnExecutor = fv1.ExecutorTypePoolmgr
executorType = fv1.ExecutorTypePoolmgr
case string(fv1.ExecutorTypeNewdeploy):
fnExecutor = fv1.ExecutorTypeNewdeploy
executorType = fv1.ExecutorTypeNewdeploy
case string(fv1.ExecutorTypeContainer):
executorType = fv1.ExecutorTypeContainer
default:
return nil, errors.Errorf("executor type must be one of '%v' or '%v'", fv1.ExecutorTypePoolmgr, fv1.ExecutorTypeNewdeploy)
err = errors.Errorf("executor type must be one of '%v', '%v' or '%v'", fv1.ExecutorTypePoolmgr, fv1.ExecutorTypeNewdeploy, fv1.ExecutorTypeContainer)
}
return executorType, err
}
func getExecutionStrategy(fnExecutor fv1.ExecutorType, input cli.Input) (strategy *fv1.ExecutionStrategy, err error) {
specializationTimeout := fv1.DefaultSpecializationTimeOut
if input.IsSet(flagkey.FnSpecializationTimeout) {
@@ -495,8 +504,10 @@ func updateExecutionStrategy(input cli.Input, existingExecutionStrategy *fv1.Exe
fnExecutor = fv1.ExecutorTypePoolmgr
case string(fv1.ExecutorTypeNewdeploy):
fnExecutor = fv1.ExecutorTypeNewdeploy
case string(fv1.ExecutorTypeContainer):
fnExecutor = fv1.ExecutorTypeContainer
default:
return nil, errors.Errorf("executor type must be one of '%v' or '%v'", fv1.ExecutorTypePoolmgr, fv1.ExecutorTypeNewdeploy)
return nil, errors.Errorf("executor type must be one of '%v', %v or '%v'", fv1.ExecutorTypePoolmgr, fv1.ExecutorTypeNewdeploy, fv1.ExecutorTypeContainer)
}
}
@@ -0,0 +1,238 @@
/*
Copyright 2019 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 function
import (
"fmt"
"strings"
"github.com/pkg/errors"
apiv1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
ferror "github.com/fission/fission/pkg/error"
"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
"github.com/fission/fission/pkg/fission-cli/cmd"
"github.com/fission/fission/pkg/fission-cli/cmd/spec"
"github.com/fission/fission/pkg/fission-cli/console"
flagkey "github.com/fission/fission/pkg/fission-cli/flag/key"
"github.com/fission/fission/pkg/fission-cli/util"
)
type RunContainerSubCommand struct {
cmd.CommandActioner
function *fv1.Function
specFile string
}
func RunContainer(input cli.Input) error {
return (&RunContainerSubCommand{}).do(input)
}
func (opts *RunContainerSubCommand) do(input cli.Input) error {
err := opts.complete(input)
if err != nil {
return err
}
return opts.run(input)
}
func (opts *RunContainerSubCommand) complete(input cli.Input) error {
fnName := input.String(flagkey.FnName)
fnNamespace := input.String(flagkey.NamespaceFunction)
// user wants a spec, create a yaml file with package and function
toSpec := false
if input.Bool(flagkey.SpecSave) {
toSpec = true
opts.specFile = fmt.Sprintf("function-%v.yaml", fnName)
}
if !toSpec {
// check for unique function names within a namespace
fn, err := opts.Client().V1().Function().Get(&metav1.ObjectMeta{
Name: input.String(flagkey.FnName),
Namespace: input.String(flagkey.NamespaceFunction),
})
if err != nil && !ferror.IsNotFound(err) {
return err
} else if fn != nil {
return errors.New("a function with the same name already exists")
}
}
fnTimeout := input.Int(flagkey.FnExecutionTimeout)
if fnTimeout <= 0 {
return errors.Errorf("--%v must be greater than 0", flagkey.FnExecutionTimeout)
}
fnIdleTimeout := input.Int(flagkey.FnIdleTimeout)
secretNames := input.StringSlice(flagkey.FnSecret)
cfgMapNames := input.StringSlice(flagkey.FnCfgMap)
es, err := getExecutionStrategy(fv1.ExecutorTypeContainer, input)
if err != nil {
return err
}
invokeStrategy := &fv1.InvokeStrategy{
ExecutionStrategy: *es,
StrategyType: fv1.StrategyTypeExecution,
}
resourceReq, err := util.GetResourceReqs(input, &apiv1.ResourceRequirements{})
if err != nil {
return err
}
var imageName string
var port int
var command, args string
imageName = input.String(flagkey.FnImageName)
if imageName == "" {
return errors.New("need --image argument")
}
port = input.Int(flagkey.FnPort)
command = input.String(flagkey.FnCommand)
args = input.String(flagkey.FnArgs)
var secrets []fv1.SecretReference
var cfgmaps []fv1.ConfigMapReference
if len(secretNames) > 0 {
// check the referenced secret is in the same ns as the function, if not give a warning.
if !toSpec { // TODO: workaround in order not to block users from creating function spec, remove it.
for _, secretName := range secretNames {
err := opts.Client().V1().Misc().SecretExists(&metav1.ObjectMeta{
Namespace: fnNamespace,
Name: secretName,
})
if err != nil {
if k8serrors.IsNotFound(err) {
console.Warn(fmt.Sprintf("Secret %s not found in Namespace: %s. Secret needs to be present in the same namespace as function", secretName, fnNamespace))
} else {
return errors.Wrapf(err, "error checking secret %s", secretName)
}
}
}
}
for _, secretName := range secretNames {
newSecret := fv1.SecretReference{
Name: secretName,
Namespace: fnNamespace,
}
secrets = append(secrets, newSecret)
}
}
if len(cfgMapNames) > 0 {
// check the referenced cfgmap is in the same ns as the function, if not give a warning.
if !toSpec {
for _, cfgMapName := range cfgMapNames {
err := opts.Client().V1().Misc().ConfigMapExists(&metav1.ObjectMeta{
Namespace: fnNamespace,
Name: cfgMapName,
})
if err != nil {
if k8serrors.IsNotFound(err) {
console.Warn(fmt.Sprintf("ConfigMap %s not found in Namespace: %s. ConfigMap needs to be present in the same namespace as function", cfgMapName, fnNamespace))
} else {
return errors.Wrapf(err, "error checking configmap %s", cfgMapName)
}
}
}
}
for _, cfgMapName := range cfgMapNames {
newCfgMap := fv1.ConfigMapReference{
Name: cfgMapName,
Namespace: fnNamespace,
}
cfgmaps = append(cfgmaps, newCfgMap)
}
}
opts.function = &fv1.Function{
ObjectMeta: metav1.ObjectMeta{
Name: fnName,
Namespace: fnNamespace,
},
Spec: fv1.FunctionSpec{
Secrets: secrets,
ConfigMaps: cfgmaps,
Resources: *resourceReq,
InvokeStrategy: *invokeStrategy,
FunctionTimeout: fnTimeout,
IdleTimeout: &fnIdleTimeout,
},
}
err = util.ApplyLabelsAndAnnotations(input, &opts.function.ObjectMeta)
if err != nil {
return err
}
container := &apiv1.Container{
Name: fnName,
Image: imageName,
Ports: []apiv1.ContainerPort{
{
Name: "http-env",
ContainerPort: int32(port),
},
},
}
if command != "" {
container.Command = strings.Split(command, " ")
}
if args != "" {
container.Args = strings.Split(args, " ")
}
opts.function.Spec.PodSpec = &apiv1.PodSpec{
Containers: []apiv1.Container{*container},
}
return nil
}
// run write the resource to a spec file or create a fission CRD with remote fission server.
// It also prints warning/error if necessary.
func (opts *RunContainerSubCommand) run(input cli.Input) error {
// if we're writing a spec, don't create the function
// save to spec file or display the spec to console
if input.Bool(flagkey.SpecDry) {
return spec.SpecDry(*opts.function)
}
if input.Bool(flagkey.SpecSave) {
err := spec.SpecSave(*opts.function, opts.specFile)
if err != nil {
return errors.Wrap(err, "error saving function spec")
}
return nil
}
_, err := opts.Client().V1().Function().Create(opts.function)
if err != nil {
return errors.Wrap(err, "error creating function")
}
fmt.Printf("function '%v' created\n", opts.function.ObjectMeta.Name)
return nil
}
@@ -0,0 +1,197 @@
/*
Copyright 2019 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 function
import (
"fmt"
"strings"
"github.com/pkg/errors"
apiv1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
"github.com/fission/fission/pkg/fission-cli/cmd"
"github.com/fission/fission/pkg/fission-cli/console"
flagkey "github.com/fission/fission/pkg/fission-cli/flag/key"
"github.com/fission/fission/pkg/fission-cli/util"
)
type UpdateContainerSubCommand struct {
cmd.CommandActioner
function *fv1.Function
}
func UpdateContainer(input cli.Input) error {
return (&UpdateContainerSubCommand{}).do(input)
}
func (opts *UpdateContainerSubCommand) do(input cli.Input) error {
err := opts.complete(input)
if err != nil {
return err
}
return opts.run(input)
}
func (opts *UpdateContainerSubCommand) complete(input cli.Input) error {
fnName := input.String(flagkey.FnName)
fnNamespace := input.String(flagkey.NamespaceFunction)
function, err := opts.Client().V1().Function().Get(&metav1.ObjectMeta{
Name: input.String(flagkey.FnName),
Namespace: input.String(flagkey.NamespaceFunction),
})
if err != nil {
return errors.Wrap(err, fmt.Sprintf("read function '%v'", fnName))
}
if fv1.ExecutorTypeContainer != function.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType {
return fmt.Errorf("executor type for function is not %s", fv1.ExecutorTypeContainer)
}
imageName := input.String(flagkey.FnImageName)
port := input.Int(flagkey.FnPort)
command := input.String(flagkey.FnCommand)
args := input.String(flagkey.FnArgs)
secretNames := input.StringSlice(flagkey.FnSecret)
cfgMapNames := input.StringSlice(flagkey.FnCfgMap)
var secrets []fv1.SecretReference
var configMaps []fv1.ConfigMapReference
if len(secretNames) > 0 {
// check that the referenced secret is in the same ns as the function, if not give a warning.
for _, secretName := range secretNames {
err := opts.Client().V1().Misc().SecretExists(&metav1.ObjectMeta{
Namespace: fnNamespace,
Name: secretName,
})
if k8serrors.IsNotFound(err) {
console.Warn(fmt.Sprintf("secret %s not found in Namespace: %s. Secret needs to be present in the same namespace as function", secretName, fnNamespace))
}
}
for _, secretName := range secretNames {
newSecret := fv1.SecretReference{
Name: secretName,
Namespace: fnNamespace,
}
secrets = append(secrets, newSecret)
}
function.Spec.Secrets = secrets
}
if len(cfgMapNames) > 0 {
// check that the referenced cfgmap is in the same ns as the function, if not give a warning.
for _, cfgMapName := range cfgMapNames {
err := opts.Client().V1().Misc().ConfigMapExists(&metav1.ObjectMeta{
Namespace: fnNamespace,
Name: cfgMapName,
})
if k8serrors.IsNotFound(err) {
console.Warn(fmt.Sprintf("ConfigMap %s not found in Namespace: %s. ConfigMap needs to be present in the same namespace as the function", cfgMapName, fnNamespace))
}
}
for _, cfgMapName := range cfgMapNames {
newCfgMap := fv1.ConfigMapReference{
Name: cfgMapName,
Namespace: fnNamespace,
}
configMaps = append(configMaps, newCfgMap)
}
function.Spec.ConfigMaps = configMaps
}
if input.IsSet(flagkey.FnExecutionTimeout) {
fnTimeout := input.Int(flagkey.FnExecutionTimeout)
if fnTimeout <= 0 {
return errors.Errorf("--%v must be greater than 0", flagkey.FnExecutionTimeout)
}
function.Spec.FunctionTimeout = fnTimeout
}
if input.IsSet(flagkey.FnIdleTimeout) {
fnTimeout := input.Int(flagkey.FnIdleTimeout)
function.Spec.IdleTimeout = &fnTimeout
}
strategy, err := getInvokeStrategy(input, &function.Spec.InvokeStrategy)
if err != nil {
return err
}
function.Spec.InvokeStrategy = *strategy
resReqs, err := util.GetResourceReqs(input, &function.Spec.Resources)
if err != nil {
return err
}
function.Spec.Resources = *resReqs
if len(function.Spec.PodSpec.Containers) > 1 {
return errors.Errorf("function %s has more than one container, only one container is supported", fnName)
}
container := &function.Spec.PodSpec.Containers[0]
if imageName != "" {
container.Image = imageName
}
if port != 0 {
if len(container.Ports) > 1 {
return errors.Errorf("function %s has more than one port, only one port is supported", fnName)
}
container.Ports = []apiv1.ContainerPort{
{
Name: "http-env",
ContainerPort: int32(port),
},
}
}
if command != "" {
container.Command = strings.Split(command, " ")
}
if args != "" {
container.Args = strings.Split(args, " ")
}
function.Spec.Environment = fv1.EnvironmentReference{}
function.Spec.Package = fv1.FunctionPackageRef{}
opts.function = function
err = util.ApplyLabelsAndAnnotations(input, &opts.function.ObjectMeta)
if err != nil {
return err
}
return nil
}
func (opts *UpdateContainerSubCommand) run(input cli.Input) error {
_, err := opts.Client().V1().Function().Update(opts.function)
if err != nil {
return errors.Wrap(err, "error updating function")
}
return nil
}
+3
View File
@@ -383,6 +383,9 @@ func applyResources(fclient client.Interface, specDir string, fr *FissionResourc
// of the package. This ensures that various caches can invalidate themselves
// when the package changes.
for i, f := range fr.Functions {
if f.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType == fv1.ExecutorTypeContainer {
continue
}
k := mapKey(&metav1.ObjectMeta{
Namespace: f.Spec.Package.PackageRef.Namespace,
Name: f.Spec.Package.PackageRef.Name,
+2
View File
@@ -361,6 +361,7 @@ func (fr *FissionResources) Validate(input cli.Input) ([]string, error) {
for _, f := range fr.Functions {
functions[MapKey(&f.ObjectMeta)] = false
if f.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypeContainer {
pkgMeta := &metav1.ObjectMeta{
Name: f.Spec.Package.PackageRef.Name,
Namespace: f.Spec.Package.PackageRef.Namespace,
@@ -394,6 +395,7 @@ func (fr *FissionResources) Validate(input cli.Input) ([]string, error) {
} else {
packages[MapKey(pkgMeta)] = true
}
}
client, err := util.GetServer(input)
if err != nil {
+4
View File
@@ -96,6 +96,10 @@ var (
FnSpecializationTimeout = Flag{Type: Int, Name: flagkey.FnSpecializationTimeout, Aliases: []string{"st"}, Usage: "Timeout for executor to wait for function pod creation", DefaultValue: fv1.DefaultSpecializationTimeOut}
FnEnvName = Flag{Type: String, Name: flagkey.FnEnvironmentName, Usage: "Environment name for function"}
FnPkgName = Flag{Type: String, Name: flagkey.FnPackageName, Aliases: []string{"pkg"}, Usage: "Name of the existing package (--deploy and --src and --env will be ignored), should be in the same namespace as the function"}
FnImageName = Flag{Type: String, Name: flagkey.FnImageName, Usage: "Name of the Docker image to be deployed as a function. Valid only when executorType is set to 'container'"}
FnPort = Flag{Type: Int, Name: flagkey.FnPort, Usage: "Port where the application is running", DefaultValue: 8888}
FnCommand = Flag{Type: String, Name: flagkey.FnCommand, Usage: "Command to be passed to the container. If not specified , the ones defined in the image are used"}
FnArgs = Flag{Type: String, Name: flagkey.FnArgs, Usage: "Args to be passed to the command on the container. If not specified , the ones defined in the image are used"}
FnEntryPoint = Flag{Type: String, Name: flagkey.FnEntrypoint, Aliases: []string{"entry"}, Usage: "Entry point for environment v2 to load with"}
FnBuildCmd = Flag{Type: String, Name: flagkey.FnBuildCmd, Usage: "Package build command for builder to run with"}
FnSecret = Flag{Type: StringSlice, Name: flagkey.FnSecret, Usage: "Function access to secret, should be present in the same namespace as the function. You can provide multiple secrets using multiple --secrets flags. In the case of fn update the the secrets will be replaced by the provided list of secrets."}
+4
View File
@@ -48,6 +48,10 @@ const (
FnSpecializationTimeout = "specializationtimeout"
FnEnvironmentName = "env"
FnPackageName = "pkgname"
FnImageName = "image"
FnPort = "port"
FnCommand = "command"
FnArgs = "args"
FnEntrypoint = "entrypoint"
FnBuildCmd = "buildcmd"
FnSecret = "secret"
+2 -6
View File
@@ -25,7 +25,6 @@ import (
"go.uber.org/zap"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
@@ -46,7 +45,6 @@ type HTTPTriggerSet struct {
kubeClient *kubernetes.Clientset
executor *executorClient.Client
resolver *functionReferenceResolver
crdClient rest.Interface
triggers []fv1.HTTPTrigger
triggerInformer k8sCache.SharedIndexInformer
functions []fv1.Function
@@ -59,7 +57,7 @@ type HTTPTriggerSet struct {
}
func makeHTTPTriggerSet(logger *zap.Logger, fmap *functionServiceMap, fissionClient *crd.FissionClient,
kubeClient *kubernetes.Clientset, executor *executorClient.Client, crdClient rest.Interface, params *tsRoundTripperParams, isDebugEnv bool, unTapServiceTimeout time.Duration, actionThrottler *throttler.Throttler) *HTTPTriggerSet {
kubeClient *kubernetes.Clientset, executor *executorClient.Client, params *tsRoundTripperParams, isDebugEnv bool, unTapServiceTimeout time.Duration, actionThrottler *throttler.Throttler) *HTTPTriggerSet {
httpTriggerSet := &HTTPTriggerSet{
logger: logger.Named("http_trigger_set"),
@@ -68,21 +66,19 @@ func makeHTTPTriggerSet(logger *zap.Logger, fmap *functionServiceMap, fissionCli
fissionClient: fissionClient,
kubeClient: kubeClient,
executor: executor,
crdClient: crdClient,
updateRouterRequestChannel: make(chan struct{}, 10), // use buffer channel
tsRoundTripperParams: params,
isDebugEnv: isDebugEnv,
svcAddrUpdateThrottler: actionThrottler,
unTapServiceTimeout: unTapServiceTimeout,
}
if httpTriggerSet.crdClient != nil {
informerFactory := genInformer.NewSharedInformerFactory(fissionClient, time.Second*30)
httpTriggerSet.triggerInformer = informerFactory.Core().V1().HTTPTriggers().Informer()
httpTriggerSet.funcInformer = informerFactory.Core().V1().Functions().Informer()
httpTriggerSet.addTriggerHandlers()
httpTriggerSet.addFunctionHandlers()
}
return httpTriggerSet
}
+1 -1
View File
@@ -236,7 +236,7 @@ func Start(logger *zap.Logger, port int, executorURL string) {
zap.Bool("default", displayAccessLog))
}
triggers := makeHTTPTriggerSet(logger.Named("triggerset"), fmap, fissionClient, kubeClient, executor, fissionClient.CoreV1().RESTClient(), &tsRoundTripperParams{
triggers := makeHTTPTriggerSet(logger.Named("triggerset"), fmap, fissionClient, kubeClient, executor, &tsRoundTripperParams{
timeout: timeout,
timeoutExponent: timeoutExponent,
disableKeepAlive: disableKeepAlive,
+27 -29
View File
@@ -1,7 +1,7 @@
####################################
# This file can be used with Skaffold (https://github.com/GoogleContainerTools/skaffold) to
# build and deploy Fission to Kubernetes cluster.
# Skaffold version v1.10.1 is used for this configuration.
# Skaffold version v1.26.1 is used for this configuration.
############## Usage ##############
# Skaffold CLI should be installed on your machine.
# For building & deploying to Cloud Provider
@@ -10,7 +10,7 @@
# For building & deploying to Kind cluster use Kind profile
# $ `skaffold run -p kind`
####################################
apiVersion: skaffold/v2beta4
apiVersion: skaffold/v2beta17
kind: Config
build:
artifacts:
@@ -27,43 +27,41 @@ build:
- image: reporter
docker:
dockerfile: cmd/reporter/Dockerfile.reporter
deploy:
helm:
flags:
upgrade:
["--timeout=3m", "--install", "--force", "--debug"]
install:
["--timeout=3m","--debug","--dependency-update"]
releases:
- name: fission
chartPath: ./charts/fission-all
valuesFiles:
- ./charts/fission-all/values.yaml
namespace: "fission"
artifactOverrides:
image: "fission"
preUpgradeChecksImage: "preupgradechecks"
postInstallReportImage: "reporter"
fetcher.image: "fetcher"
setValues:
fetcher.image: fetcher
image: fission
postInstallReportImage: reporter
preUpgradeChecksImage: preupgradechecks
namespace: fission
repository: "index.docker.io"
routerServiceType: LoadBalancer
pruneInterval: 60
setValues:
analytics: "false"
debugEnv: "false"
fetcher.imageTag: ""
imageTag: ""
prometheus.enabled: false
debugEnv: false
analytics: false
namespace: fission
pprof.enabled: false
prometheus.enabled: "false"
pruneInterval: "60"
repository: index.docker.io
routerServiceType: LoadBalancer
wait: true
recreatePods: false
packaged: null
imageStrategy:
fqn: null
helm: null
flags:
install:
- --timeout=3m
- --debug
- --dependency-update
upgrade:
- --timeout=3m
- --install
- --force
- --debug
profiles:
- name: kind
patches:
@@ -72,7 +70,7 @@ profiles:
value: ""
- op: replace
path: /deploy/helm/releases/0/setValues/routerServiceType
value: "NodePort"
value: NodePort
- name: kind-debug
patches:
- op: replace
@@ -80,7 +78,7 @@ profiles:
value: ""
- op: replace
path: /deploy/helm/releases/0/setValues/routerServiceType
value: "NodePort"
value: NodePort
- op: replace
path: /deploy/helm/releases/0/setValues/debugEnv
value: true
@@ -97,7 +95,7 @@ profiles:
value: 1
- op: replace
path: /deploy/helm/releases/0/setValues/routerServiceType
value: "NodePort"
value: NodePort
- op: replace
path: /deploy/helm/releases/0/setValues/prometheus.enabled
value: true