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:
co-authored by
Harsh Thakur
Sanket Sudake
parent
f3e1f9df90
commit
a82281ad1c
+22
-6
@@ -1,22 +1,38 @@
|
|||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
|
# Default linter
|
||||||
- deadcode
|
- deadcode
|
||||||
- gofmt
|
- errcheck
|
||||||
- goimports
|
|
||||||
- gosimple
|
- gosimple
|
||||||
- govet
|
- govet
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- misspell
|
|
||||||
- nakedret
|
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- structcheck
|
- structcheck
|
||||||
- typecheck
|
- typecheck
|
||||||
- unconvert
|
- unused
|
||||||
- varcheck
|
- varcheck
|
||||||
|
# Additional linters
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
- misspell
|
||||||
|
- nakedret
|
||||||
|
- unconvert
|
||||||
|
# Enable in future
|
||||||
|
# - bodyclose
|
||||||
|
# - dogsled
|
||||||
|
# - dupl
|
||||||
|
# - gosec
|
||||||
|
# - ifshort
|
||||||
|
# - nilerr
|
||||||
|
# - prealloc
|
||||||
|
# - revive
|
||||||
|
# - unparam
|
||||||
|
# - wrapcheck
|
||||||
|
# - gocritic
|
||||||
linters-settings:
|
linters-settings:
|
||||||
errcheck:
|
errcheck:
|
||||||
ignore: go.uber.org/zap:Sync
|
ignore: go.uber.org/zap:Sync
|
||||||
goimports:
|
goimports:
|
||||||
# put imports beginning with prefix after 3rd-party packages;
|
# put imports beginning with prefix after 3rd-party packages;
|
||||||
# it's a comma-separated list of prefixes
|
# it's a comma-separated list of prefixes
|
||||||
local-prefixes: github.com/fission/fission
|
local-prefixes: github.com/fission/fission
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ func (client *PreUpgradeTaskClient) LatestSchemaApplied() error {
|
|||||||
return errors.New("Could not get the Function CRD")
|
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
|
// 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")
|
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") {
|
if !strings.Contains(mqtCRD.Spec.String(), "PodSpec") {
|
||||||
return errors.New("Apply the newer CRDs before upgrading")
|
return errors.New("Apply the newer CRDs before upgrading")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -55,6 +55,7 @@ const (
|
|||||||
const (
|
const (
|
||||||
ExecutorTypePoolmgr ExecutorType = "poolmgr"
|
ExecutorTypePoolmgr ExecutorType = "poolmgr"
|
||||||
ExecutorTypeNewdeploy ExecutorType = "newdeploy"
|
ExecutorTypeNewdeploy ExecutorType = "newdeploy"
|
||||||
|
ExecutorTypeContainer ExecutorType = "container"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -409,6 +409,11 @@ type (
|
|||||||
// This is optional. If not specified default value will be taken as false
|
// This is optional. If not specified default value will be taken as false
|
||||||
// +optional
|
// +optional
|
||||||
OnceOnly bool `json:"onceOnly,omitempty"`
|
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.
|
// InvokeStrategy is a set of controls over how the function executes.
|
||||||
@@ -450,6 +455,7 @@ type (
|
|||||||
// Available value:
|
// Available value:
|
||||||
// - poolmgr
|
// - poolmgr
|
||||||
// - newdeploy
|
// - newdeploy
|
||||||
|
// - container
|
||||||
// +optional
|
// +optional
|
||||||
ExecutorType ExecutorType `json:"ExecutorType"`
|
ExecutorType ExecutorType `json:"ExecutorType"`
|
||||||
|
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ func (EnvironmentSpec) SwaggerDoc() map[string]string {
|
|||||||
|
|
||||||
var map_ExecutionStrategy = 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.",
|
"": "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.",
|
"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.",
|
"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.",
|
"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",
|
"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",
|
"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",
|
"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 {
|
func (FunctionSpec) SwaggerDoc() map[string]string {
|
||||||
|
|||||||
@@ -274,6 +274,10 @@ func (spec FunctionSpec) Validate() error {
|
|||||||
result = multierror.Append(result, spec.InvokeStrategy.Validate())
|
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
|
// TODO Add below validation warning
|
||||||
/*if spec.FunctionTimeout <= 0 {
|
/*if spec.FunctionTimeout <= 0 {
|
||||||
result = multierror.Append(result, MakeValidationErr(ErrorInvalidValue, "FunctionTimeout value", spec.FunctionTimeout, "not a valid value. Should always be more than 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{}
|
result := &multierror.Error{}
|
||||||
|
|
||||||
switch es.ExecutorType {
|
switch es.ExecutorType {
|
||||||
case ExecutorTypeNewdeploy, ExecutorTypePoolmgr: // no op
|
case ExecutorTypeNewdeploy, ExecutorTypePoolmgr, ExecutorTypeContainer: // no op
|
||||||
default:
|
default:
|
||||||
result = multierror.Append(result, MakeValidationErr(ErrorUnsupportedType, "ExecutionStrategy.ExecutorType", es.ExecutorType, "not a valid executor type"))
|
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 = new(int)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.PodSpec != nil {
|
||||||
|
in, out := &in.PodSpec, &out.PodSpec
|
||||||
|
*out = new(corev1.PodSpec)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import (
|
|||||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/rest"
|
|
||||||
k8sCache "k8s.io/client-go/tools/cache"
|
k8sCache "k8s.io/client-go/tools/cache"
|
||||||
|
|
||||||
fv1 "github.com/fission/fission/pkg/apis/core/v1"
|
fv1 "github.com/fission/fission/pkg/apis/core/v1"
|
||||||
@@ -47,11 +46,10 @@ type canaryConfigMgr struct {
|
|||||||
kubeClient *kubernetes.Clientset
|
kubeClient *kubernetes.Clientset
|
||||||
canaryConfigInformer *k8sCache.SharedIndexInformer
|
canaryConfigInformer *k8sCache.SharedIndexInformer
|
||||||
promClient *PrometheusApiClient
|
promClient *PrometheusApiClient
|
||||||
crdClient rest.Interface
|
|
||||||
canaryCfgCancelFuncMap *canaryConfigCancelFuncMap
|
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 == "" {
|
if prometheusSvc == "" {
|
||||||
logger.Info("try to retrieve prometheus server information from environment variables")
|
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"),
|
logger: logger.Named("canary_config_manager"),
|
||||||
fissionClient: fissionClient,
|
fissionClient: fissionClient,
|
||||||
kubeClient: kubeClient,
|
kubeClient: kubeClient,
|
||||||
crdClient: crdClient,
|
|
||||||
promClient: promClient,
|
promClient: promClient,
|
||||||
canaryCfgCancelFuncMap: makecanaryConfigCancelFuncMap(),
|
canaryCfgCancelFuncMap: makecanaryConfigCancelFuncMap(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
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
|
// start the appropriate controller
|
||||||
if featureConfig.CanaryConfig.IsEnabled {
|
if featureConfig.CanaryConfig.IsEnabled {
|
||||||
canaryCfgMgr, err := canaryconfigmgr.MakeCanaryConfigMgr(logger, fissionClient, kubeClient, fissionClient.CoreV1().RESTClient(),
|
canaryCfgMgr, err := canaryconfigmgr.MakeCanaryConfigMgr(logger, fissionClient, kubeClient, featureConfig.CanaryConfig.PrometheusSvc)
|
||||||
featureConfig.CanaryConfig.PrometheusSvc)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
featureStatus[config.CanaryFeature] = err.Error()
|
featureStatus[config.CanaryFeature] = err.Error()
|
||||||
return errors.Wrap(err, "failed to start canary config manager")
|
return errors.Wrap(err, "failed to start canary config manager")
|
||||||
|
|||||||
+4
-3
@@ -19,6 +19,7 @@ package executor
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -87,10 +88,10 @@ func (executor *Executor) getServiceForFunctionAPI(w http.ResponseWriter, r *htt
|
|||||||
if active >= concurrency {
|
if active >= concurrency {
|
||||||
errMsg := fmt.Sprintf("max concurrency reached for %v. All %v instance are active", fn.ObjectMeta.Name, 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))
|
executor.logger.Error("error occurred", zap.String("error", errMsg))
|
||||||
http.Error(w, errMsg, http.StatusTooManyRequests)
|
http.Error(w, html.EscapeString(errMsg), http.StatusTooManyRequests)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if t == fv1.ExecutorTypeNewdeploy {
|
} else if t == fv1.ExecutorTypeNewdeploy || t == fv1.ExecutorTypeContainer {
|
||||||
fsvc, err := et.GetFuncSvcFromCache(fn)
|
fsvc, err := et.GetFuncSvcFromCache(fn)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if et.IsValid(fsvc) {
|
if et.IsValid(fsvc) {
|
||||||
@@ -227,7 +228,7 @@ func (executor *Executor) unTapService(w http.ResponseWriter, r *http.Request) {
|
|||||||
t := tapSvcReq.FnExecutorType
|
t := tapSvcReq.FnExecutorType
|
||||||
if t != fv1.ExecutorTypePoolmgr {
|
if t != fv1.ExecutorTypePoolmgr {
|
||||||
msg := fmt.Sprintf("Unknown executor type '%v'", t)
|
msg := fmt.Sprintf("Unknown executor type '%v'", t)
|
||||||
http.Error(w, msg, http.StatusBadRequest)
|
http.Error(w, html.EscapeString(msg), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import (
|
|||||||
"github.com/fission/fission/pkg/crd"
|
"github.com/fission/fission/pkg/crd"
|
||||||
"github.com/fission/fission/pkg/executor/cms"
|
"github.com/fission/fission/pkg/executor/cms"
|
||||||
"github.com/fission/fission/pkg/executor/executortype"
|
"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/newdeploy"
|
||||||
"github.com/fission/fission/pkg/executor/executortype/poolmgr"
|
"github.com/fission/fission/pkg/executor/executortype/poolmgr"
|
||||||
"github.com/fission/fission/pkg/executor/fscache"
|
"github.com/fission/fission/pkg/executor/fscache"
|
||||||
@@ -291,7 +292,7 @@ func StartExecutor(logger *zap.Logger, functionNamespace string, envBuilderNames
|
|||||||
|
|
||||||
ndm, err := newdeploy.MakeNewDeploy(
|
ndm, err := newdeploy.MakeNewDeploy(
|
||||||
logger,
|
logger,
|
||||||
fissionClient, kubernetesClient, fissionClient.CoreV1().RESTClient(),
|
fissionClient, kubernetesClient,
|
||||||
functionNamespace, fetcherConfig, executorInstanceID,
|
functionNamespace, fetcherConfig, executorInstanceID,
|
||||||
&funcInformer, &envInformer,
|
&funcInformer, &envInformer,
|
||||||
)
|
)
|
||||||
@@ -299,9 +300,18 @@ func StartExecutor(logger *zap.Logger, functionNamespace string, envBuilderNames
|
|||||||
return errors.Wrap(err, "new deploy manager creation faied")
|
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 := make(map[fv1.ExecutorType]executortype.ExecutorType)
|
||||||
executorTypes[gpm.GetTypeName()] = gpm
|
executorTypes[gpm.GetTypeName()] = gpm
|
||||||
executorTypes[ndm.GetTypeName()] = ndm
|
executorTypes[ndm.GetTypeName()] = ndm
|
||||||
|
executorTypes[cnm.GetTypeName()] = cnm
|
||||||
|
|
||||||
adoptExistingResources, _ := strconv.ParseBool(os.Getenv("ADOPT_EXISTING_RESOURCES"))
|
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")
|
||||||
|
|
||||||
|
}()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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{})
|
||||||
|
}
|
||||||
@@ -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{})
|
||||||
|
}
|
||||||
@@ -262,7 +262,8 @@ func (deploy *NewDeploy) getDeploymentSpec(fn *fv1.Function, env *fv1.Environmen
|
|||||||
// https://istio.io/docs/setup/kubernetes/additional-setup/requirements/
|
// https://istio.io/docs/setup/kubernetes/additional-setup/requirements/
|
||||||
Ports: []apiv1.ContainerPort{
|
Ports: []apiv1.ContainerPort{
|
||||||
{
|
{
|
||||||
Name: "http-env",
|
Name: "http-env",
|
||||||
|
// Now that we have added Port field in spec, should we make this configurable too?
|
||||||
ContainerPort: int32(8888),
|
ContainerPort: int32(8888),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -453,8 +454,9 @@ func (deploy *NewDeploy) createOrGetSvc(deployLabels map[string]string, deployAn
|
|||||||
Spec: apiv1.ServiceSpec{
|
Spec: apiv1.ServiceSpec{
|
||||||
Ports: []apiv1.ServicePort{
|
Ports: []apiv1.ServicePort{
|
||||||
{
|
{
|
||||||
Name: "http-env",
|
Name: "http-env",
|
||||||
Port: int32(80),
|
Port: int32(80),
|
||||||
|
// Since Function spec now supports Port , should we make this configurable too?
|
||||||
TargetPort: intstr.FromInt(8888),
|
TargetPort: intstr.FromInt(8888),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
k8sTypes "k8s.io/apimachinery/pkg/types"
|
k8sTypes "k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/rest"
|
|
||||||
k8sCache "k8s.io/client-go/tools/cache"
|
k8sCache "k8s.io/client-go/tools/cache"
|
||||||
|
|
||||||
fv1 "github.com/fission/fission/pkg/apis/core/v1"
|
fv1 "github.com/fission/fission/pkg/apis/core/v1"
|
||||||
@@ -59,7 +58,6 @@ type (
|
|||||||
|
|
||||||
kubernetesClient *kubernetes.Clientset
|
kubernetesClient *kubernetes.Clientset
|
||||||
fissionClient *crd.FissionClient
|
fissionClient *crd.FissionClient
|
||||||
crdClient rest.Interface
|
|
||||||
instanceID string
|
instanceID string
|
||||||
fetcherConfig *fetcherConfig.Config
|
fetcherConfig *fetcherConfig.Config
|
||||||
|
|
||||||
@@ -86,7 +84,6 @@ func MakeNewDeploy(
|
|||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
fissionClient *crd.FissionClient,
|
fissionClient *crd.FissionClient,
|
||||||
kubernetesClient *kubernetes.Clientset,
|
kubernetesClient *kubernetes.Clientset,
|
||||||
crdClient rest.Interface,
|
|
||||||
namespace string,
|
namespace string,
|
||||||
fetcherConfig *fetcherConfig.Config,
|
fetcherConfig *fetcherConfig.Config,
|
||||||
instanceID string,
|
instanceID string,
|
||||||
@@ -107,7 +104,6 @@ func MakeNewDeploy(
|
|||||||
|
|
||||||
fissionClient: fissionClient,
|
fissionClient: fissionClient,
|
||||||
kubernetesClient: kubernetesClient,
|
kubernetesClient: kubernetesClient,
|
||||||
crdClient: crdClient,
|
|
||||||
instanceID: instanceID,
|
instanceID: instanceID,
|
||||||
|
|
||||||
namespace: namespace,
|
namespace: namespace,
|
||||||
@@ -123,10 +119,8 @@ func MakeNewDeploy(
|
|||||||
envInformer: envInformer,
|
envInformer: envInformer,
|
||||||
}
|
}
|
||||||
|
|
||||||
if nd.crdClient != nil {
|
(*nd.funcInformer).AddEventHandler(nd.FunctionEventHandlers())
|
||||||
(*nd.funcInformer).AddEventHandler(nd.FunctionEventHandlers())
|
(*nd.envInformer).AddEventHandler(nd.EnvEventHandlers())
|
||||||
(*nd.envInformer).AddEventHandler(nd.EnvEventHandlers())
|
|
||||||
}
|
|
||||||
|
|
||||||
informerFactory, err := utils.GetInformerFactoryByExecutor(nd.kubernetesClient, fv1.ExecutorTypePoolmgr)
|
informerFactory, err := utils.GetInformerFactoryByExecutor(nd.kubernetesClient, fv1.ExecutorTypePoolmgr)
|
||||||
if err != nil {
|
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.
|
// Return true if no error occurs, return false otherwise.
|
||||||
func (deploy *NewDeploy) IsValid(fsvc *fscache.FuncSvc) bool {
|
func (deploy *NewDeploy) IsValid(fsvc *fscache.FuncSvc) bool {
|
||||||
if len(strings.Split(fsvc.Address, ".")) == 0 {
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, obj := range fsvc.KubernetesObjects {
|
for _, obj := range fsvc.KubernetesObjects {
|
||||||
if strings.ToLower(obj.Kind) == "service" {
|
if strings.ToLower(obj.Kind) == "service" {
|
||||||
_, err := deploy.getServiceInfo(obj)
|
_, err := deploy.getServiceInfo(obj)
|
||||||
@@ -239,11 +237,7 @@ func (deploy *NewDeploy) IsValid(fsvc *fscache.FuncSvc) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} else if strings.ToLower(obj.Kind) == "deployment" {
|
||||||
}
|
|
||||||
|
|
||||||
for _, obj := range fsvc.KubernetesObjects {
|
|
||||||
if strings.ToLower(obj.Kind) == "deployment" {
|
|
||||||
currentDeploy, err := deploy.getDeploymentInfo(obj)
|
currentDeploy, err := deploy.getDeploymentInfo(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !k8sErrs.IsNotFound(err) {
|
if !k8sErrs.IsNotFound(err) {
|
||||||
@@ -251,17 +245,15 @@ func (deploy *NewDeploy) IsValid(fsvc *fscache.FuncSvc) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// return directly when available replicas > 0
|
if currentDeploy.Status.AvailableReplicas < 1 {
|
||||||
if currentDeploy.Status.AvailableReplicas > 0 {
|
return false
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
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{})
|
env, err := deploy.fissionClient.CoreV1().Environments(f.Spec.Environment.Namespace).Get(context.TODO(), f.Spec.Environment.Name, metav1.GetOptions{})
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ func (gp *GenericPool) updateCPUUtilizationSvc() {
|
|||||||
|
|
||||||
if !gp.checkMetricsApi() {
|
if !gp.checkMetricsApi() {
|
||||||
checkDuration = 180
|
checkDuration = 180
|
||||||
gp.logger.Error("Metrics API not available")
|
gp.logger.Warn("Metrics API not available")
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceFunc := func() {
|
serviceFunc := func() {
|
||||||
|
|||||||
@@ -17,10 +17,16 @@ limitations under the License.
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
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.
|
// 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):
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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{
|
command := &cobra.Command{
|
||||||
Use: "function",
|
Use: "function",
|
||||||
Aliases: []string{"fn"},
|
Aliases: []string{"fn"},
|
||||||
Short: "Create, update and manage functions",
|
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
|
return command
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -285,18 +285,6 @@ func (opts *CreateSubCommand) complete(input cli.Input) error {
|
|||||||
Namespace: fnNamespace,
|
Namespace: fnNamespace,
|
||||||
},
|
},
|
||||||
Spec: fv1.FunctionSpec{
|
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,
|
Secrets: secrets,
|
||||||
ConfigMaps: cfgmaps,
|
ConfigMaps: cfgmaps,
|
||||||
Resources: *resourceReq,
|
Resources: *resourceReq,
|
||||||
@@ -313,6 +301,18 @@ func (opts *CreateSubCommand) complete(input cli.Input) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
@@ -392,13 +392,19 @@ func getInvokeStrategy(input cli.Input, existingInvokeStrategy *fv1.InvokeStrate
|
|||||||
var es *fv1.ExecutionStrategy
|
var es *fv1.ExecutionStrategy
|
||||||
|
|
||||||
if existingInvokeStrategy == nil {
|
if existingInvokeStrategy == nil {
|
||||||
es, err = getExecutionStrategy(input)
|
executorType, err := getExecutorType(input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
es, err = getExecutionStrategy(executorType, input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
es, err = updateExecutionStrategy(input, &existingInvokeStrategy.ExecutionStrategy)
|
es, err = updateExecutionStrategy(input, &existingInvokeStrategy.ExecutionStrategy)
|
||||||
}
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &fv1.InvokeStrategy{
|
return &fv1.InvokeStrategy{
|
||||||
@@ -407,20 +413,23 @@ func getInvokeStrategy(input cli.Input, existingInvokeStrategy *fv1.InvokeStrate
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getExecutionStrategy(input cli.Input) (strategy *fv1.ExecutionStrategy, err error) {
|
func getExecutorType(input cli.Input) (executorType fv1.ExecutorType, err error) {
|
||||||
var fnExecutor fv1.ExecutorType
|
|
||||||
|
|
||||||
switch input.String(flagkey.FnExecutorType) {
|
switch input.String(flagkey.FnExecutorType) {
|
||||||
case "":
|
case "":
|
||||||
fallthrough
|
fallthrough
|
||||||
case string(fv1.ExecutorTypePoolmgr):
|
case string(fv1.ExecutorTypePoolmgr):
|
||||||
fnExecutor = fv1.ExecutorTypePoolmgr
|
executorType = fv1.ExecutorTypePoolmgr
|
||||||
case string(fv1.ExecutorTypeNewdeploy):
|
case string(fv1.ExecutorTypeNewdeploy):
|
||||||
fnExecutor = fv1.ExecutorTypeNewdeploy
|
executorType = fv1.ExecutorTypeNewdeploy
|
||||||
|
case string(fv1.ExecutorTypeContainer):
|
||||||
|
executorType = fv1.ExecutorTypeContainer
|
||||||
default:
|
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
|
specializationTimeout := fv1.DefaultSpecializationTimeOut
|
||||||
|
|
||||||
if input.IsSet(flagkey.FnSpecializationTimeout) {
|
if input.IsSet(flagkey.FnSpecializationTimeout) {
|
||||||
@@ -495,8 +504,10 @@ func updateExecutionStrategy(input cli.Input, existingExecutionStrategy *fv1.Exe
|
|||||||
fnExecutor = fv1.ExecutorTypePoolmgr
|
fnExecutor = fv1.ExecutorTypePoolmgr
|
||||||
case string(fv1.ExecutorTypeNewdeploy):
|
case string(fv1.ExecutorTypeNewdeploy):
|
||||||
fnExecutor = fv1.ExecutorTypeNewdeploy
|
fnExecutor = fv1.ExecutorTypeNewdeploy
|
||||||
|
case string(fv1.ExecutorTypeContainer):
|
||||||
|
fnExecutor = fv1.ExecutorTypeContainer
|
||||||
default:
|
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
|
||||||
|
}
|
||||||
@@ -383,6 +383,9 @@ func applyResources(fclient client.Interface, specDir string, fr *FissionResourc
|
|||||||
// of the package. This ensures that various caches can invalidate themselves
|
// of the package. This ensures that various caches can invalidate themselves
|
||||||
// when the package changes.
|
// when the package changes.
|
||||||
for i, f := range fr.Functions {
|
for i, f := range fr.Functions {
|
||||||
|
if f.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType == fv1.ExecutorTypeContainer {
|
||||||
|
continue
|
||||||
|
}
|
||||||
k := mapKey(&metav1.ObjectMeta{
|
k := mapKey(&metav1.ObjectMeta{
|
||||||
Namespace: f.Spec.Package.PackageRef.Namespace,
|
Namespace: f.Spec.Package.PackageRef.Namespace,
|
||||||
Name: f.Spec.Package.PackageRef.Name,
|
Name: f.Spec.Package.PackageRef.Name,
|
||||||
|
|||||||
@@ -361,38 +361,40 @@ func (fr *FissionResources) Validate(input cli.Input) ([]string, error) {
|
|||||||
for _, f := range fr.Functions {
|
for _, f := range fr.Functions {
|
||||||
functions[MapKey(&f.ObjectMeta)] = false
|
functions[MapKey(&f.ObjectMeta)] = false
|
||||||
|
|
||||||
pkgMeta := &metav1.ObjectMeta{
|
if f.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypeContainer {
|
||||||
Name: f.Spec.Package.PackageRef.Name,
|
pkgMeta := &metav1.ObjectMeta{
|
||||||
Namespace: f.Spec.Package.PackageRef.Namespace,
|
Name: f.Spec.Package.PackageRef.Name,
|
||||||
}
|
Namespace: f.Spec.Package.PackageRef.Namespace,
|
||||||
|
}
|
||||||
|
|
||||||
// check package ref from function
|
// check package ref from function
|
||||||
packageRefExists := func() bool {
|
packageRefExists := func() bool {
|
||||||
_, ok := packages[MapKey(pkgMeta)]
|
_, ok := packages[MapKey(pkgMeta)]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that the package referenced by each function is in the same ns as the function
|
// check that the package referenced by each function is in the same ns as the function
|
||||||
packageRefInFuncNs := func(f *fv1.Function) bool {
|
packageRefInFuncNs := func(f *fv1.Function) bool {
|
||||||
return f.Spec.Package.PackageRef.Namespace == f.ObjectMeta.Namespace
|
return f.Spec.Package.PackageRef.Namespace == f.ObjectMeta.Namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
if !packageRefInFuncNs(&f) {
|
if !packageRefInFuncNs(&f) {
|
||||||
result = multierror.Append(result, fmt.Errorf(
|
result = multierror.Append(result, fmt.Errorf(
|
||||||
"%v: function '%v' references a package outside of its namespace %v/%v",
|
"%v: function '%v' references a package outside of its namespace %v/%v",
|
||||||
fr.SourceMap.Locations["Function"][f.ObjectMeta.Namespace][f.ObjectMeta.Name],
|
fr.SourceMap.Locations["Function"][f.ObjectMeta.Namespace][f.ObjectMeta.Name],
|
||||||
f.ObjectMeta.Name,
|
f.ObjectMeta.Name,
|
||||||
f.Spec.Package.PackageRef.Namespace,
|
f.Spec.Package.PackageRef.Namespace,
|
||||||
f.Spec.Package.PackageRef.Name))
|
f.Spec.Package.PackageRef.Name))
|
||||||
} else if !packageRefExists() {
|
} else if !packageRefExists() {
|
||||||
result = multierror.Append(result, fmt.Errorf(
|
result = multierror.Append(result, fmt.Errorf(
|
||||||
"%v: function '%v' references unknown package %v/%v",
|
"%v: function '%v' references unknown package %v/%v",
|
||||||
fr.SourceMap.Locations["Function"][f.ObjectMeta.Namespace][f.ObjectMeta.Name],
|
fr.SourceMap.Locations["Function"][f.ObjectMeta.Namespace][f.ObjectMeta.Name],
|
||||||
f.ObjectMeta.Name,
|
f.ObjectMeta.Name,
|
||||||
pkgMeta.Namespace,
|
pkgMeta.Namespace,
|
||||||
pkgMeta.Name))
|
pkgMeta.Name))
|
||||||
} else {
|
} else {
|
||||||
packages[MapKey(pkgMeta)] = true
|
packages[MapKey(pkgMeta)] = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := util.GetServer(input)
|
client, err := util.GetServer(input)
|
||||||
|
|||||||
@@ -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}
|
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"}
|
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"}
|
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"}
|
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"}
|
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."}
|
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."}
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ const (
|
|||||||
FnSpecializationTimeout = "specializationtimeout"
|
FnSpecializationTimeout = "specializationtimeout"
|
||||||
FnEnvironmentName = "env"
|
FnEnvironmentName = "env"
|
||||||
FnPackageName = "pkgname"
|
FnPackageName = "pkgname"
|
||||||
|
FnImageName = "image"
|
||||||
|
FnPort = "port"
|
||||||
|
FnCommand = "command"
|
||||||
|
FnArgs = "args"
|
||||||
FnEntrypoint = "entrypoint"
|
FnEntrypoint = "entrypoint"
|
||||||
FnBuildCmd = "buildcmd"
|
FnBuildCmd = "buildcmd"
|
||||||
FnSecret = "secret"
|
FnSecret = "secret"
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/rest"
|
|
||||||
k8sCache "k8s.io/client-go/tools/cache"
|
k8sCache "k8s.io/client-go/tools/cache"
|
||||||
|
|
||||||
fv1 "github.com/fission/fission/pkg/apis/core/v1"
|
fv1 "github.com/fission/fission/pkg/apis/core/v1"
|
||||||
@@ -46,7 +45,6 @@ type HTTPTriggerSet struct {
|
|||||||
kubeClient *kubernetes.Clientset
|
kubeClient *kubernetes.Clientset
|
||||||
executor *executorClient.Client
|
executor *executorClient.Client
|
||||||
resolver *functionReferenceResolver
|
resolver *functionReferenceResolver
|
||||||
crdClient rest.Interface
|
|
||||||
triggers []fv1.HTTPTrigger
|
triggers []fv1.HTTPTrigger
|
||||||
triggerInformer k8sCache.SharedIndexInformer
|
triggerInformer k8sCache.SharedIndexInformer
|
||||||
functions []fv1.Function
|
functions []fv1.Function
|
||||||
@@ -59,7 +57,7 @@ type HTTPTriggerSet struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func makeHTTPTriggerSet(logger *zap.Logger, fmap *functionServiceMap, fissionClient *crd.FissionClient,
|
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{
|
httpTriggerSet := &HTTPTriggerSet{
|
||||||
logger: logger.Named("http_trigger_set"),
|
logger: logger.Named("http_trigger_set"),
|
||||||
@@ -68,21 +66,19 @@ func makeHTTPTriggerSet(logger *zap.Logger, fmap *functionServiceMap, fissionCli
|
|||||||
fissionClient: fissionClient,
|
fissionClient: fissionClient,
|
||||||
kubeClient: kubeClient,
|
kubeClient: kubeClient,
|
||||||
executor: executor,
|
executor: executor,
|
||||||
crdClient: crdClient,
|
|
||||||
updateRouterRequestChannel: make(chan struct{}, 10), // use buffer channel
|
updateRouterRequestChannel: make(chan struct{}, 10), // use buffer channel
|
||||||
tsRoundTripperParams: params,
|
tsRoundTripperParams: params,
|
||||||
isDebugEnv: isDebugEnv,
|
isDebugEnv: isDebugEnv,
|
||||||
svcAddrUpdateThrottler: actionThrottler,
|
svcAddrUpdateThrottler: actionThrottler,
|
||||||
unTapServiceTimeout: unTapServiceTimeout,
|
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()
|
informerFactory := genInformer.NewSharedInformerFactory(fissionClient, time.Second*30)
|
||||||
httpTriggerSet.addFunctionHandlers()
|
httpTriggerSet.triggerInformer = informerFactory.Core().V1().HTTPTriggers().Informer()
|
||||||
}
|
httpTriggerSet.funcInformer = informerFactory.Core().V1().Functions().Informer()
|
||||||
|
|
||||||
|
httpTriggerSet.addTriggerHandlers()
|
||||||
|
httpTriggerSet.addFunctionHandlers()
|
||||||
return httpTriggerSet
|
return httpTriggerSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ func Start(logger *zap.Logger, port int, executorURL string) {
|
|||||||
zap.Bool("default", displayAccessLog))
|
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,
|
timeout: timeout,
|
||||||
timeoutExponent: timeoutExponent,
|
timeoutExponent: timeoutExponent,
|
||||||
disableKeepAlive: disableKeepAlive,
|
disableKeepAlive: disableKeepAlive,
|
||||||
|
|||||||
+85
-87
@@ -1,103 +1,101 @@
|
|||||||
####################################
|
####################################
|
||||||
# This file can be used with Skaffold (https://github.com/GoogleContainerTools/skaffold) to
|
# This file can be used with Skaffold (https://github.com/GoogleContainerTools/skaffold) to
|
||||||
# build and deploy Fission to Kubernetes cluster.
|
# 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 ##############
|
############## Usage ##############
|
||||||
# Skaffold CLI should be installed on your machine.
|
# Skaffold CLI should be installed on your machine.
|
||||||
# For building & deploying to Cloud Provider
|
# For building & deploying to Cloud Provider
|
||||||
# $ skaffold config set default-repo vishalbiyani (vishalbiyani - should be your registry/Dockerhub handle)
|
# $ skaffold config set default-repo vishalbiyani (vishalbiyani - should be your registry/Dockerhub handle)
|
||||||
# $ `skaffold run`
|
# $ `skaffold run`
|
||||||
# For building & deploying to Kind cluster use Kind profile
|
# For building & deploying to Kind cluster use Kind profile
|
||||||
# $ `skaffold run -p kind`
|
# $ `skaffold run -p kind`
|
||||||
####################################
|
####################################
|
||||||
apiVersion: skaffold/v2beta4
|
apiVersion: skaffold/v2beta17
|
||||||
kind: Config
|
kind: Config
|
||||||
build:
|
build:
|
||||||
artifacts:
|
artifacts:
|
||||||
- image: fission
|
- image: fission
|
||||||
context: .
|
context: .
|
||||||
docker:
|
docker:
|
||||||
dockerfile: cmd/fission-bundle/Dockerfile.fission-bundle
|
dockerfile: cmd/fission-bundle/Dockerfile.fission-bundle
|
||||||
- image: fetcher
|
- image: fetcher
|
||||||
docker:
|
docker:
|
||||||
dockerfile: cmd/fetcher/Dockerfile.fission-fetcher
|
dockerfile: cmd/fetcher/Dockerfile.fission-fetcher
|
||||||
- image: preupgradechecks
|
- image: preupgradechecks
|
||||||
docker:
|
docker:
|
||||||
dockerfile: cmd/preupgradechecks/Dockerfile.fission-preupgradechecks
|
dockerfile: cmd/preupgradechecks/Dockerfile.fission-preupgradechecks
|
||||||
- image: reporter
|
- image: reporter
|
||||||
docker:
|
docker:
|
||||||
dockerfile: cmd/reporter/Dockerfile.reporter
|
dockerfile: cmd/reporter/Dockerfile.reporter
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
helm:
|
helm:
|
||||||
flags:
|
|
||||||
upgrade:
|
|
||||||
["--timeout=3m", "--install", "--force", "--debug"]
|
|
||||||
install:
|
|
||||||
["--timeout=3m","--debug","--dependency-update"]
|
|
||||||
releases:
|
releases:
|
||||||
- name: fission
|
- name: fission
|
||||||
chartPath: ./charts/fission-all
|
chartPath: ./charts/fission-all
|
||||||
valuesFiles:
|
valuesFiles:
|
||||||
- ./charts/fission-all/values.yaml
|
- ./charts/fission-all/values.yaml
|
||||||
namespace: "fission"
|
artifactOverrides:
|
||||||
artifactOverrides:
|
fetcher.image: fetcher
|
||||||
image: "fission"
|
image: fission
|
||||||
preUpgradeChecksImage: "preupgradechecks"
|
postInstallReportImage: reporter
|
||||||
postInstallReportImage: "reporter"
|
preUpgradeChecksImage: preupgradechecks
|
||||||
fetcher.image: "fetcher"
|
namespace: fission
|
||||||
setValues:
|
setValues:
|
||||||
namespace: fission
|
analytics: "false"
|
||||||
repository: "index.docker.io"
|
debugEnv: "false"
|
||||||
routerServiceType: LoadBalancer
|
fetcher.imageTag: ""
|
||||||
pruneInterval: 60
|
imageTag: ""
|
||||||
fetcher.imageTag: ""
|
namespace: fission
|
||||||
imageTag: ""
|
pprof.enabled: false
|
||||||
prometheus.enabled: false
|
prometheus.enabled: "false"
|
||||||
debugEnv: false
|
pruneInterval: "60"
|
||||||
analytics: false
|
repository: index.docker.io
|
||||||
pprof.enabled: false
|
routerServiceType: LoadBalancer
|
||||||
wait: true
|
wait: true
|
||||||
recreatePods: false
|
flags:
|
||||||
packaged: null
|
install:
|
||||||
imageStrategy:
|
- --timeout=3m
|
||||||
fqn: null
|
- --debug
|
||||||
helm: null
|
- --dependency-update
|
||||||
|
upgrade:
|
||||||
|
- --timeout=3m
|
||||||
|
- --install
|
||||||
|
- --force
|
||||||
|
- --debug
|
||||||
profiles:
|
profiles:
|
||||||
- name: kind
|
- name: kind
|
||||||
patches:
|
patches:
|
||||||
- op: replace
|
- op: replace
|
||||||
path: /deploy/helm/releases/0/setValues/repository
|
path: /deploy/helm/releases/0/setValues/repository
|
||||||
value: ""
|
value: ""
|
||||||
- op: replace
|
- op: replace
|
||||||
path: /deploy/helm/releases/0/setValues/routerServiceType
|
path: /deploy/helm/releases/0/setValues/routerServiceType
|
||||||
value: "NodePort"
|
value: NodePort
|
||||||
- name: kind-debug
|
- name: kind-debug
|
||||||
patches:
|
patches:
|
||||||
- op: replace
|
- op: replace
|
||||||
path: /deploy/helm/releases/0/setValues/repository
|
path: /deploy/helm/releases/0/setValues/repository
|
||||||
value: ""
|
value: ""
|
||||||
- op: replace
|
- op: replace
|
||||||
path: /deploy/helm/releases/0/setValues/routerServiceType
|
path: /deploy/helm/releases/0/setValues/routerServiceType
|
||||||
value: "NodePort"
|
value: NodePort
|
||||||
- op: replace
|
- op: replace
|
||||||
path: /deploy/helm/releases/0/setValues/debugEnv
|
path: /deploy/helm/releases/0/setValues/debugEnv
|
||||||
value: true
|
value: true
|
||||||
- op: replace
|
- op: replace
|
||||||
path: /deploy/helm/releases/0/setValues/pprof.enabled
|
path: /deploy/helm/releases/0/setValues/pprof.enabled
|
||||||
value: true
|
value: true
|
||||||
- name: kind-ci
|
- name: kind-ci
|
||||||
patches:
|
patches:
|
||||||
- op: replace
|
- op: replace
|
||||||
path: /deploy/helm/releases/0/setValues/repository
|
path: /deploy/helm/releases/0/setValues/repository
|
||||||
value: ""
|
value: ""
|
||||||
- op: replace
|
- op: replace
|
||||||
path: /deploy/helm/releases/0/setValues/pruneInterval
|
path: /deploy/helm/releases/0/setValues/pruneInterval
|
||||||
value: 1
|
value: 1
|
||||||
- op: replace
|
- op: replace
|
||||||
path: /deploy/helm/releases/0/setValues/routerServiceType
|
path: /deploy/helm/releases/0/setValues/routerServiceType
|
||||||
value: "NodePort"
|
value: NodePort
|
||||||
- op: replace
|
- op: replace
|
||||||
path: /deploy/helm/releases/0/setValues/prometheus.enabled
|
path: /deploy/helm/releases/0/setValues/prometheus.enabled
|
||||||
value: true
|
value: true
|
||||||
|
|||||||
Reference in New Issue
Block a user