diff --git a/Makefile b/Makefile index f8dec2d..c922da3 100644 --- a/Makefile +++ b/Makefile @@ -86,6 +86,12 @@ helm-upgrade-operator: .PHONY: helm-delete-operator helm-delete-operator: helm delete -n ${NAMESPACE} kedahttp + +.PHONY: generate-operator +generate-operator: + cd operator && \ + make manifests && \ + cp config/crd/bases/http.keda.sh_httpscaledobjects.yaml ../charts/keda-http-operator/crds/httpscaledobjects.http.keda.sh.yaml ##### # universal targets diff --git a/charts/keda-http-operator/crds/httpscaledobjects.http.keda.sh.yaml b/charts/keda-http-operator/crds/httpscaledobjects.http.keda.sh.yaml index 15f5e5a..5f21ac6 100644 --- a/charts/keda-http-operator/crds/httpscaledobjects.http.keda.sh.yaml +++ b/charts/keda-http-operator/crds/httpscaledobjects.http.keda.sh.yaml @@ -16,119 +16,130 @@ spec: singular: httpscaledobject scope: Namespaced versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: HTTPScaledObject is the Schema for the scaledobjects API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: HTTPScaledObjectSpec defines the desired state of HTTPScaledObject - properties: - app_image: - description: The image this application will use. - type: string - app_name: - description: (optional) The name of the application to be created. - type: string - max_replicas: - description: Maximum amount of replicas to have in the deployment (Default 100) - format: int32 - type: integer - min_replicas: - description: Minimum amount of replicas to have in the deployment (Default 0) - format: int32 - type: integer - port: - description: The port this application will serve on. - format: int32 - type: integer - required: - - app_image + - name: v1alpha1 + schema: + openAPIV3Schema: + description: HTTPScaledObject is the Schema for the scaledobjects API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: HTTPScaledObjectSpec defines the desired state of HTTPScaledObject + properties: + replicas: + description: (optional) Replica information + properties: + max: + description: Maximum amount of replicas to have in the deployment (Default 100) + format: int32 + type: integer + min: + description: Minimum amount of replicas to have in the deployment (Default 0) + format: int32 + type: integer + type: object + scaleTargetRef: + description: The name of the deployment to route HTTP requests to (and to autoscale). Either this or Image must be set + properties: + deployment: + description: The name of the deployment to scale according to HTTP traffic + type: string + port: + description: The port to route to + format: int32 + type: integer + service: + description: The name of the service to route to + type: string + required: + - deployment - port - type: object - status: - description: HTTPScaledObjectStatus defines the observed state of HTTPScaledObject - properties: - conditions: - description: List of auditable conditions of the operator - items: - description: Condition to store the condition state - properties: - message: - description: A human readable message indicating details about the transition. - type: string - reason: - description: The reason for the condition's last transition. - enum: - - ErrorCreatingExternalScaler - - ErrorCreatingExternalScalerService - - CreatedExternalScaler - - ErrorCreatingAppDeployment - - AppDeploymentCreated - - ErrorCreatingAppService - - AppServiceCreated - - ErrorCreatingScaledObject - - ScaledObjectCreated - - ErrorCreatingInterceptor - - ErrorCreatingInterceptorAdminService - - ErrorCreatingInterceptorProxyService - - InterceptorCreated - - TerminatingResources - - AppDeploymentTerminationError - - AppDeploymentTerminated - - InterceptorDeploymentTerminated - - InterceptorDeploymentTerminationError - - InterceptorAdminServiceTerminationError - - InterceptorAdminServiceTerminated - - InterceptorProxyServiceTerminationError - - InterceptorProxyServiceTerminated - - ExternalScalerDeploymentTerminationError - - ExternalScalerDeploymentTerminated - - ExternalScalerServiceTerminationError - - ExternalScalerServiceTerminated - - AppServiceTerminationError - - AppServiceTerminated - - ScaledObjectTerminated - - ScaledObjectTerminationError - - PendingCreation - - HTTPScaledObjectIsReady - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - timestamp: - description: Timestamp of the condition - type: string - type: - description: Type of condition - enum: - - Created - - Error - - Pending - - Unknown - - Terminating - - Terminated - - Ready - type: string - required: - - status - - type - type: object - type: array - type: object - type: object - served: true - storage: true - subresources: - status: { } + - service + type: object + required: + - scaleTargetRef + type: object + status: + description: HTTPScaledObjectStatus defines the observed state of HTTPScaledObject + properties: + conditions: + description: List of auditable conditions of the operator + items: + description: Condition to store the condition state + properties: + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + enum: + - ErrorCreatingExternalScaler + - ErrorCreatingExternalScalerService + - CreatedExternalScaler + - ErrorCreatingAppDeployment + - AppDeploymentCreated + - ErrorCreatingAppService + - AppServiceCreated + - ErrorCreatingScaledObject + - ScaledObjectCreated + - ErrorCreatingInterceptor + - ErrorCreatingInterceptorAdminService + - ErrorCreatingInterceptorProxyService + - InterceptorCreated + - TerminatingResources + - AppDeploymentTerminationError + - AppDeploymentTerminated + - InterceptorDeploymentTerminated + - InterceptorDeploymentTerminationError + - InterceptorAdminServiceTerminationError + - InterceptorAdminServiceTerminated + - InterceptorProxyServiceTerminationError + - InterceptorProxyServiceTerminated + - ExternalScalerDeploymentTerminationError + - ExternalScalerDeploymentTerminated + - ExternalScalerServiceTerminationError + - ExternalScalerServiceTerminated + - AppServiceTerminationError + - AppServiceTerminated + - ScaledObjectTerminated + - ScaledObjectTerminationError + - PendingCreation + - HTTPScaledObjectIsReady + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + timestamp: + description: Timestamp of the condition + type: string + type: + description: Type of condition + enum: + - Created + - Error + - Pending + - Unknown + - Terminating + - Terminated + - Ready + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} status: acceptedNames: kind: "" diff --git a/charts/xkcd/.helmignore b/charts/xkcd/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/charts/xkcd/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/xkcd/Chart.yaml b/charts/xkcd/Chart.yaml new file mode 100644 index 0000000..cf05f89 --- /dev/null +++ b/charts/xkcd/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: xkcd +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/charts/xkcd/templates/NOTES.txt b/charts/xkcd/templates/NOTES.txt new file mode 100644 index 0000000..dcc3ac1 --- /dev/null +++ b/charts/xkcd/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "xkcd.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "xkcd.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "xkcd.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "xkcd.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/charts/xkcd/templates/_helpers.tpl b/charts/xkcd/templates/_helpers.tpl new file mode 100644 index 0000000..d83258f --- /dev/null +++ b/charts/xkcd/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "xkcd.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "xkcd.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "xkcd.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "xkcd.labels" -}} +helm.sh/chart: {{ include "xkcd.chart" . }} +{{ include "xkcd.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "xkcd.selectorLabels" -}} +app.kubernetes.io/name: {{ include "xkcd.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "xkcd.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "xkcd.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/xkcd/templates/deployment.yaml b/charts/xkcd/templates/deployment.yaml new file mode 100644 index 0000000..3b17ebb --- /dev/null +++ b/charts/xkcd/templates/deployment.yaml @@ -0,0 +1,44 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "xkcd.fullname" . }} + labels: + {{- include "xkcd.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "xkcd.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "xkcd.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "xkcd.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 8080 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http diff --git a/charts/xkcd/templates/hpa.yaml b/charts/xkcd/templates/hpa.yaml new file mode 100644 index 0000000..455b709 --- /dev/null +++ b/charts/xkcd/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "xkcd.fullname" . }} + labels: + {{- include "xkcd.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "xkcd.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/charts/xkcd/templates/service.yaml b/charts/xkcd/templates/service.yaml new file mode 100644 index 0000000..ef13047 --- /dev/null +++ b/charts/xkcd/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "xkcd.fullname" . }} + labels: + {{- include "xkcd.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "xkcd.selectorLabels" . | nindent 4 }} diff --git a/charts/xkcd/templates/serviceaccount.yaml b/charts/xkcd/templates/serviceaccount.yaml new file mode 100644 index 0000000..7b0e265 --- /dev/null +++ b/charts/xkcd/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "xkcd.serviceAccountName" . }} + labels: + {{- include "xkcd.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/xkcd/templates/tests/test-connection.yaml b/charts/xkcd/templates/tests/test-connection.yaml new file mode 100644 index 0000000..6e3357f --- /dev/null +++ b/charts/xkcd/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "xkcd.fullname" . }}-test-connection" + labels: + {{- include "xkcd.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "xkcd.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/charts/xkcd/values.yaml b/charts/xkcd/values.yaml new file mode 100644 index 0000000..ec6b98e --- /dev/null +++ b/charts/xkcd/values.yaml @@ -0,0 +1,38 @@ +replicaCount: 1 + +image: + repository: arschles/xkcd + pullPolicy: Always + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 8080 + diff --git a/docs/install.md b/docs/install.md index 51d4f1d..47db53c 100644 --- a/docs/install.md +++ b/docs/install.md @@ -31,20 +31,22 @@ git clone https://github.com/kedacore/http-add-on.git cd http-add-on ``` -Next, install the HTTP add on: +Next, install the HTTP add on. The below command will install the add on if it doesn't already exist: ```shell make helm-upgrade-operator ``` ->The above command will install KEDA HTTP if it doesn't already exist. +>Installing the HTTP add on won't affect any running workloads in your cluster. You'll need to install an `HTTPScaledObject` for each individual `Deployment` you want to scale. For more on how to do that, please see the [walkthrough](./walkthrough.md). -There are two environment variables in the above command that you can set to customize how it behaves: +There are a few environment variables in the above command that you can set to customize how it behaves: - `NAMESPACE` - which Kubernetes namespace to install KEDA-HTTP. This should be the same as where you installed KEDA itself (required) -- `OPERATOR_DOCKER_IMG` - the name of the operator's Docker image (optional - falls back to a sensible default) -- `SCALER_DOCKER_IMG` - the name of the scaler's Docker image (optional - falls back to a sensible default) -- `INTERCEPTOR_DOCKER_IMG` - the name of the interceptor's Docker image (optional - falls back to a sensible default) +- `OPERATOR_DOCKER_IMG` - the name of the operator's Docker image (optional - falls back to the latest release) +- `SCALER_DOCKER_IMG` - the name of the scaler's Docker image (optional - falls back to the latest release) +- `INTERCEPTOR_DOCKER_IMG` - the name of the interceptor's Docker image (optional - falls back to the latest release) + +>I recommend using [direnv](https://direnv.net/) to store your environment variables ### If You're Installing into a [Microk8s](https://microk8s.io) Cluster diff --git a/docs/ref/http_scaled_object.md b/docs/ref/http_scaled_object.md new file mode 100644 index 0000000..9caa664 --- /dev/null +++ b/docs/ref/http_scaled_object.md @@ -0,0 +1,33 @@ +# The `HTTPScaledObject` + +Each `HTTPScaledObject` looks approximately like the below: + +```yaml +kind: HTTPScaledObject +apiVersion: http.keda.sh/v1alpha1 +metadata: + name: xkcd +spec: + scaleTargetRef: + deployment: xkcd + service: xkcd + port: 8080 +``` + +This document is a narrated reference guide for the `HTTPScaledObject`, and we'll focus on the `spec` field. + +## `scaleTargetRef` + +This is the primary and most important part of the `spec` because it describes (1) what `Deployment` to scale and (2) where and how to route traffic. + +### `deployment` + +This is the name of the `Deployment` to scale. It must exist in the same namespace as this `HTTPScaledObject` and shouldn't be managed by any other autoscaling system. This means that there should not be any `ScaledObject` already created for this `Deployment`. The HTTP add on will manage a `ScaledObject` internally. + +### `service` + +This is the name of the service to route traffic to. The add on will create autoscaling and routing components that route to this `Service`. It must exist in the same namespace as this `HTTPScaledObject` and should route to the same `Deployment` as you entered in the `deployment` field. + +### `port` + +This is the port to route to on the service that you specified in the `service` field. It should be exposed on the service and should route to a valid `containerPort` on the `Deployment` you gave in the `deployment` field. diff --git a/docs/walkthrough.md b/docs/walkthrough.md index 2d0361b..520a6c2 100644 --- a/docs/walkthrough.md +++ b/docs/walkthrough.md @@ -1,17 +1,43 @@ # Getting Started With The HTTP Add On -One of the primary goals of this project is a simple common-case developer experience. After you've installed KEDA and the HTTP Add On (this project), this document will show you how to get started with an example app. +After you've installed KEDA and the HTTP Add On (this project, we'll call it the "add on" for short), this document will show you how to get started with an example app. ->If you haven't installed KEDA and this project, please do so first. Follow instructions [install.md](./install.md) to complete your installation. +If you haven't installed KEDA and the HTTP Add On (this project), please do so first. Follow instructions [install.md](./install.md) to complete your installation. Before you continue, make sure that you have your `NAMESPACE` environment variable set to the same value as it was when you installed. -## Submitting an `HTTPScaledObject` +## Creating An Application -You interact with the operator via a CRD called `HTTPScaledObject`. To get an example app up and running, read the notes below and then run the subsequent command from the root of this repository. +You'll need to install a `Deployment` and `Service` first. You'll tell the add on to begin scaling it up and down after this step. Use this [Helm](https://helm.sh) command to create the resources you need: -- Make sure that your `NAMESPACE` environment variable is set to the same value as what you [install](./install.md)ed with - - I recommend using [direnv](https://direnv.net/) to store your environment variables -- This command will install a simple server that is exposed to the internet using a `LoadBalancer` `Service`. _Do not use this for a production deployment_. Support for `Ingress` is forthcoming in [issue #33](https://github.com/kedacore/http-add-on/issues/33) +```shell +helm install xkcd ./charts/xkcd -n ${NAMESPACE} +``` + +>To remove the app, run `helm delete xkcd -n ${NAMESPACE}` + +## Creating an `HTTPScaledObject` + +You interact with the operator via a CRD called `HTTPScaledObject`. This CRD object points the To get an example app up and running, read the notes below and then run the subsequent command from the root of this repository. ```shell kubectl create -f -n $NAMESPACE examples/httpscaledobject.yaml ``` + +>If you'd like to learn more about this object, please see the [`HTTPScaledObject` reference](./ref/http_scaled_object.md). + +## Testing Your Installation + +You've now installed a web application and activated autoscaling by creating an `HTTPScaledObject` for it. For autoscaling to work properly, HTTP traffic needs to route through the `Service` that the add on has set up. You can use `kubectl port-forward` to quickly test things out: + +```shell +k port-forward svc/xkcd-interceptor-proxy -n ${NAMESPACE} 8080:80 +``` + +### Routing to the Right `Service` + +As said above, you need to route your HTTP traffic to the `Service` that the add on has created. If you have existing systems - like an ingress controller - you'll need to anticipate the name of these created `Service`s. Each one will be named consistently like so, in the same namespace as the `HTTPScaledObject` and your application (i.e. `$NAMESPACE`): + +```shell +-interceptor-proxy +``` + +>The service will always be a `ClusterIP` type and will be created in the same namespace as the `HTTPScaledObject` you created. diff --git a/examples/httpscaledobject.yaml b/examples/httpscaledobject.yaml index 3c82ee9..cf8f677 100644 --- a/examples/httpscaledobject.yaml +++ b/examples/httpscaledobject.yaml @@ -3,9 +3,10 @@ apiVersion: http.keda.sh/v1alpha1 metadata: name: xkcd spec: - app_name: xkcd - app_image: arschles/xkcd - port: 8080 + scaleTargetRef: + deployment: xkcd + service: xkcd + port: 8080 replicas: min: 5 max: 10 diff --git a/go.mod b/go.mod index cb8efeb..341d807 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/golang/protobuf v1.4.3 github.com/labstack/echo/v4 v4.2.1 github.com/magefile/mage v1.11.0 + github.com/mattn/go-colorable v0.1.8 // indirect github.com/onsi/ginkgo v1.15.2 github.com/onsi/gomega v1.11.0 github.com/pkg/errors v0.9.1 @@ -14,6 +15,7 @@ require ( google.golang.org/grpc v1.33.2 google.golang.org/protobuf v1.25.0 k8s.io/api v0.20.2 + k8s.io/apiextensions-apiserver v0.20.2 // indirect k8s.io/apimachinery v0.20.2 k8s.io/client-go v0.20.2 sigs.k8s.io/controller-runtime v0.8.1 diff --git a/go.sum b/go.sum index 2a59d14..ff27b95 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,5 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= @@ -50,7 +49,6 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -256,8 +254,9 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -288,7 +287,6 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -298,7 +296,6 @@ github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4= github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.15.2 h1:l77YT15o814C2qVL47NOyjV/6RbaP7kKdrvZnxQ3Org= github.com/onsi/ginkgo v1.15.2/go.mod h1:Dd6YFfwBW84ETqqtL0CPyPXillHgY6XhQH3uuCCTr/o= @@ -306,7 +303,6 @@ github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGV github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs= github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug= github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= @@ -376,7 +372,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -487,7 +482,6 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -543,7 +537,6 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -558,7 +551,6 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -602,7 +594,6 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200616133436-c1934b75d054 h1:HHeAlu5H9b71C+Fx0K+1dGgVFN1DM1/wz4aoGOA5qS8= golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -694,7 +685,6 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= @@ -713,16 +703,19 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.2 h1:y/HR22XDZY3pniu9hIFDLpUCPq2w5eQ6aV/VFQ7uJMw= k8s.io/api v0.20.2/go.mod h1:d7n6Ehyzx+S+cE3VhTGfVNNqtGc/oL9DCdYYahlurV8= -k8s.io/apiextensions-apiserver v0.20.1 h1:ZrXQeslal+6zKM/HjDXLzThlz/vPSxrfK3OqL8txgVQ= k8s.io/apiextensions-apiserver v0.20.1/go.mod h1:ntnrZV+6a3dB504qwC5PN/Yg9PBiDNt1EVqbW2kORVk= +k8s.io/apiextensions-apiserver v0.20.2 h1:rfrMWQ87lhd8EzQWRnbQ4gXrniL/yTRBgYH1x1+BLlo= +k8s.io/apiextensions-apiserver v0.20.2/go.mod h1:F6TXp389Xntt+LUq3vw6HFOLttPa0V8821ogLGwb6Zs= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.2 h1:hFx6Sbt1oG0n6DZ+g4bFt5f6BoMkOjKWsQFu077M3Vg= k8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= +k8s.io/apiserver v0.20.2/go.mod h1:2nKd93WyMhZx4Hp3RfgH2K5PhwyTrprrkWYnI7id7jA= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.2 h1:uuf+iIAbfnCSw8IGAv/Rg0giM+2bOzHLOsbbrwrdhNQ= k8s.io/client-go v0.20.2/go.mod h1:kH5brqWqp7HDxUFKoEgiI4v8G1xzbe9giaCenUWJzgE= k8s.io/code-generator v0.20.1/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg= +k8s.io/code-generator v0.20.2/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.2 h1:LMmu5I0pLtwjpp5009KLuMGFqSc2S2isGw8t1hpYKLE= k8s.io/component-base v0.20.2/go.mod h1:pzFtCiwe/ASD0iV7ySMu8SYVJjCapNM9bjvk7ptpKh0= diff --git a/operator/api/v1alpha1/httpscaledobject_types.go b/operator/api/v1alpha1/httpscaledobject_types.go index aad1187..c91b2c9 100644 --- a/operator/api/v1alpha1/httpscaledobject_types.go +++ b/operator/api/v1alpha1/httpscaledobject_types.go @@ -113,18 +113,23 @@ type ReplicaStruct struct { // HTTPScaledObjectSpec defines the desired state of HTTPScaledObject type HTTPScaledObjectSpec struct { - // (optional) The name of the application to be created. - AppName string `json:"app_name,omitempty"` - // The image this application will use. - Image string `json:"app_image"` - // The port this application will serve on. - Port int32 `json:"port"` + // The name of the deployment to route HTTP requests to (and to autoscale). Either this + // or Image must be set + ScaleTargetRef *ScaleTargetRef `json:"scaleTargetRef"` // (optional) Replica information //+optional Replicas ReplicaStruct `json:"replicas,omitempty"` } -// TODO: Add ingress configurations +// ScaleTargetRef contains all the details about an HTTP application to scale and route to +type ScaleTargetRef struct { + // The name of the deployment to scale according to HTTP traffic + Deployment string `json:"deployment"` + // The name of the service to route to + Service string `json:"service"` + // The port to route to + Port int32 `json:"port"` +} // HTTPScaledObjectStatus defines the observed state of HTTPScaledObject type HTTPScaledObjectStatus struct { diff --git a/operator/config/crd/bases/http.keda.sh_httpscaledobjects.yaml b/operator/config/crd/bases/http.keda.sh_httpscaledobjects.yaml index d698f5f..5f21ac6 100644 --- a/operator/config/crd/bases/http.keda.sh_httpscaledobjects.yaml +++ b/operator/config/crd/bases/http.keda.sh_httpscaledobjects.yaml @@ -32,16 +32,6 @@ spec: spec: description: HTTPScaledObjectSpec defines the desired state of HTTPScaledObject properties: - app_image: - description: The image this application will use. - type: string - app_name: - description: (optional) The name of the application to be created. - type: string - port: - description: The port this application will serve on. - format: int32 - type: integer replicas: description: (optional) Replica information properties: @@ -54,9 +44,26 @@ spec: format: int32 type: integer type: object + scaleTargetRef: + description: The name of the deployment to route HTTP requests to (and to autoscale). Either this or Image must be set + properties: + deployment: + description: The name of the deployment to scale according to HTTP traffic + type: string + port: + description: The port to route to + format: int32 + type: integer + service: + description: The name of the service to route to + type: string + required: + - deployment + - port + - service + type: object required: - - app_image - - port + - scaleTargetRef type: object status: description: HTTPScaledObjectStatus defines the observed state of HTTPScaledObject diff --git a/operator/config/rbac/role.yaml b/operator/config/rbac/role.yaml index 1249e91..be834f7 100644 --- a/operator/config/rbac/role.yaml +++ b/operator/config/rbac/role.yaml @@ -4,7 +4,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: creationTimestamp: null - name: keda-http-manager-role + name: keda-http-addon-manager-role rules: - apiGroups: - "" diff --git a/operator/controllers/config/app_info.go b/operator/controllers/config/app_info.go index 8927f70..068172d 100644 --- a/operator/controllers/config/app_info.go +++ b/operator/controllers/config/app_info.go @@ -1,36 +1,57 @@ package config -import "fmt" +import ( + "fmt" + "github.com/kedacore/http-add-on/operator/api/v1alpha1" +) + +// DeploymentName is a convenience function for +// a.HTTPScaledObject.Spec.ScaleTargetRef.Deployment +func DeploymentName(httpso v1alpha1.HTTPScaledObject) string { + return httpso.Spec.ScaleTargetRef.Deployment +} + +// AppInfo contains configuration for the Interceptor and External Scaler, and holds +// data about the name and namespace of the scale target. type AppInfo struct { Name string - Port int32 - Image string Namespace string InterceptorConfig Interceptor ExternalScalerConfig ExternalScaler } +// ExternalScalerServiceName is a convenience method to get the name of the external scaler +// service in Kubernetes func (a AppInfo) ExternalScalerServiceName() string { return fmt.Sprintf("%s-external-scaler", a.Name) } +// ExternalScalerDeploymentName is a convenience method to get the name of the external scaler +// deployment in Kubernetes func (a AppInfo) ExternalScalerDeploymentName() string { return fmt.Sprintf("%s-external-scaler", a.Name) } +// InterceptorAdminServiceName is a convenience method to get the name of the interceptor +// service for the admin endpoints in Kubernetes func (a AppInfo) InterceptorAdminServiceName() string { return fmt.Sprintf("%s-interceptor-admin", a.Name) } +// InterceptorProxyServiceName is a convenience method to get the name of the interceptor +// service for the proxy in Kubernetes func (a AppInfo) InterceptorProxyServiceName() string { return fmt.Sprintf("%s-interceptor-proxy", a.Name) } +// InterceptorDeploymentName is a convenience method to get the name of the interceptor +// deployment in Kubernetes func (a AppInfo) InterceptorDeploymentName() string { return fmt.Sprintf("%s-interceptor", a.Name) } +// ScaledObjectName is a convenience method to get the name of the scaled object in Kubernetes func (a AppInfo) ScaledObjectName() string { return fmt.Sprintf("%s-scaled-object", a.Name) } diff --git a/operator/controllers/external_scaler_test.go b/operator/controllers/external_scaler_test.go index 05234f6..2dddf8c 100644 --- a/operator/controllers/external_scaler_test.go +++ b/operator/controllers/external_scaler_test.go @@ -1,52 +1,36 @@ package controllers import ( - "context" "time" - logrtest "github.com/go-logr/logr/testing" "github.com/kedacore/http-add-on/operator/api/v1alpha1" - "github.com/kedacore/http-add-on/operator/controllers/config" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" ) var _ = Describe("ExternalScaler", func() { Context("Creating the external scaler", func() { + var testInfra *commonTestInfra + BeforeEach(func() { + testInfra = newCommonTestInfra("testns", "testapp") + }) It("Should properly create the Deployment and Service", func() { - const name = "testapp" - const namespace = "testns" - ctx := context.Background() - cl := fake.NewFakeClient() - cfg := config.AppInfo{ - Name: name, - Port: 8081, - Image: "arschles/testimg", - Namespace: namespace, - } - logger := logrtest.NullLogger{} - httpso := &v1alpha1.HTTPScaledObject{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Spec: v1alpha1.HTTPScaledObjectSpec{ - AppName: name, - Image: "arschles/testapp", - Port: 8081, - }, - } - err := createExternalScaler(ctx, cfg, cl, logger, httpso) + err := createExternalScaler( + testInfra.ctx, + testInfra.cfg, + testInfra.cl, + testInfra.logger, + &testInfra.httpso, + ) Expect(err).To(BeNil()) // // make sure that httpso has the right conditions on it - Expect(len(httpso.Status.Conditions)).To(Equal(1)) - cond1 := httpso.Status.Conditions[0] + Expect(len(testInfra.httpso.Status.Conditions)).To(Equal(1)) + cond1 := testInfra.httpso.Status.Conditions[0] cond1ts, err := time.Parse(time.RFC3339, cond1.Timestamp) Expect(err).To(BeNil()) Expect(time.Now().Sub(cond1ts) >= 0).To(BeTrue()) @@ -56,17 +40,17 @@ var _ = Describe("ExternalScaler", func() { // check that the external scaler deployment was created deployment := new(appsv1.Deployment) - err = cl.Get(ctx, client.ObjectKey{ - Name: cfg.ExternalScalerDeploymentName(), - Namespace: cfg.Namespace, + err = testInfra.cl.Get(testInfra.ctx, client.ObjectKey{ + Name: testInfra.cfg.ExternalScalerDeploymentName(), + Namespace: testInfra.cfg.Namespace, }, deployment) Expect(err).To(BeNil()) // check that the external scaler service was created service := new(corev1.Service) - err = cl.Get(ctx, client.ObjectKey{ - Name: cfg.ExternalScalerServiceName(), - Namespace: cfg.Namespace, + err = testInfra.cl.Get(testInfra.ctx, client.ObjectKey{ + Name: testInfra.cfg.ExternalScalerServiceName(), + Namespace: testInfra.cfg.Namespace, }, service) Expect(err).To(BeNil()) diff --git a/operator/controllers/httpscaledobject_controller.go b/operator/controllers/httpscaledobject_controller.go index d08c0a1..221c344 100644 --- a/operator/controllers/httpscaledobject_controller.go +++ b/operator/controllers/httpscaledobject_controller.go @@ -76,14 +76,8 @@ func (rec *HTTPScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.R }, err } - appName := httpso.Spec.AppName - image := httpso.Spec.Image - port := httpso.Spec.Port - appInfo := config.AppInfo{ - Name: appName, - Port: port, - Image: image, + Name: httpso.Spec.ScaleTargetRef.Deployment, Namespace: req.Namespace, InterceptorConfig: rec.InterceptorConfig, ExternalScalerConfig: rec.ExternalScalerConfig, @@ -114,7 +108,13 @@ func (rec *HTTPScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.R } // httpso is updated now - logger.Info("Reconciling HTTPScaledObject", "Namespace", req.Namespace, "App Name", appName, "image", image, "port", port) + logger.Info( + "Reconciling HTTPScaledObject", + "Namespace", + req.Namespace, + "DeploymentName", + appInfo.Name, + ) // Create required app objects for the application defined by the CRD if err := rec.createOrUpdateApplicationResources( diff --git a/operator/controllers/httpscaledobject_logic.go b/operator/controllers/httpscaledobject_logic.go index d49bcfe..351d6d1 100644 --- a/operator/controllers/httpscaledobject_logic.go +++ b/operator/controllers/httpscaledobject_logic.go @@ -206,11 +206,6 @@ func (rec *HTTPScaledObjectReconciler) createOrUpdateApplicationResources( // set initial statuses httpso.AddCondition(*v1alpha1.CreateCondition(v1alpha1.Pending, v1.ConditionUnknown, v1alpha1.PendingCreation).SetMessage("Identified HTTPScaledObject creation signal")) - // CREATING THE USER APPLICATION - if err := createUserApp(ctx, appInfo, rec.Client, logger, httpso); err != nil { - return err - } - // CREATING INTERNAL ADD-ON OBJECTS // Creating the dedicated interceptor if err := createInterceptor(ctx, appInfo, rec.Client, logger, httpso); err != nil { @@ -231,7 +226,5 @@ func (rec *HTTPScaledObjectReconciler) createOrUpdateApplicationResources( } - // TODO: Create a new ingress resource that will point to the interceptor - return nil } diff --git a/operator/controllers/interceptor.go b/operator/controllers/interceptor.go index 427dd2f..67d2a71 100644 --- a/operator/controllers/interceptor.go +++ b/operator/controllers/interceptor.go @@ -28,7 +28,7 @@ func createInterceptor( }, { Name: "KEDA_HTTP_APP_SERVICE_PORT", - Value: fmt.Sprintf("%d", appInfo.Port), + Value: fmt.Sprintf("%d", httpso.Spec.ScaleTargetRef.Port), }, { Name: "KEDA_HTTP_PROXY_PORT", @@ -77,7 +77,7 @@ func createInterceptor( appInfo.Namespace, appInfo.InterceptorProxyServiceName(), publicPorts, - corev1.ServiceTypeLoadBalancer, + corev1.ServiceTypeClusterIP, k8s.Labels(appInfo.InterceptorDeploymentName()), ) adminPorts := []corev1.ServicePort{ diff --git a/operator/controllers/scaled_object.go b/operator/controllers/scaled_object.go index 0b79305..6ebfc0d 100644 --- a/operator/controllers/scaled_object.go +++ b/operator/controllers/scaled_object.go @@ -30,10 +30,12 @@ func createScaledObject( logger.Info("Creating scaled object", "external_scaler", externalScalerHostname) + deploymentName := httpso.Spec.ScaleTargetRef.Deployment + coreScaledObject := k8s.NewScaledObject( appInfo.Namespace, appInfo.ScaledObjectName(), - appInfo.Name, + deploymentName, externalScalerHostname, httpso.Spec.Replicas.Min, httpso.Spec.Replicas.Max, diff --git a/operator/controllers/scaled_object_test.go b/operator/controllers/scaled_object_test.go index 6e00ae1..2e5fc36 100644 --- a/operator/controllers/scaled_object_test.go +++ b/operator/controllers/scaled_object_test.go @@ -1,59 +1,39 @@ package controllers import ( - "context" "time" - logrtest "github.com/go-logr/logr/testing" "github.com/kedacore/http-add-on/operator/api/v1alpha1" - "github.com/kedacore/http-add-on/operator/controllers/config" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" ) var _ = Describe("UserApp", func() { Context("Creating a ScaledObject", func() { + var testInfra *commonTestInfra + BeforeEach(func() { + testInfra = newCommonTestInfra("testns", "testapp") + }) It("Should properly create the ScaledObject for the user app", func() { - const name = "testapp" - const namespace = "testns" - ctx := context.Background() - cl := fake.NewFakeClient() - cfg := config.AppInfo{ - Name: name, - Port: 8081, - Image: "arschles/testimg", - Namespace: namespace, - } - logger := logrtest.NullLogger{} - httpso := &v1alpha1.HTTPScaledObject{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Spec: v1alpha1.HTTPScaledObjectSpec{ - AppName: name, - Image: "arschles/testapp", - Port: 8081, - Replicas: v1alpha1.ReplicaStruct{ - Min: 5, - Max: 10, - }, - }, - } - err := createScaledObject(ctx, cfg, cl, logger, httpso) + err := createScaledObject( + testInfra.ctx, + testInfra.cfg, + testInfra.cl, + testInfra.logger, + &testInfra.httpso, + ) Expect(err).To(BeNil()) // make sure that httpso has the right conditions on it - Expect(len(httpso.Status.Conditions)).To(Equal(1)) + Expect(len(testInfra.httpso.Status.Conditions)).To(Equal(1)) - cond1 := httpso.Status.Conditions[0] + cond1 := testInfra.httpso.Status.Conditions[0] cond1ts, err := time.Parse(time.RFC3339, cond1.Timestamp) Expect(err).To(BeNil()) - Expect(time.Now().Sub(cond1ts) >= 0).To(BeTrue()) + Expect(time.Since(cond1ts) >= 0).To(BeTrue()) Expect(cond1.Type).To(Equal(v1alpha1.Created)) Expect(cond1.Status).To(Equal(metav1.ConditionTrue)) Expect(cond1.Reason).To(Equal(v1alpha1.ScaledObjectCreated)) @@ -65,23 +45,23 @@ var _ = Describe("UserApp", func() { Kind: "ScaledObject", Version: "v1alpha1", }) - err = cl.Get(ctx, client.ObjectKey{ - Namespace: cfg.Namespace, - Name: cfg.ScaledObjectName(), + err = testInfra.cl.Get(testInfra.ctx, client.ObjectKey{ + Namespace: testInfra.cfg.Namespace, + Name: testInfra.cfg.ScaledObjectName(), }, u) Expect(err).To(BeNil()) metadataIface, found := u.Object["metadata"] metadata, ok := metadataIface.(map[string]interface{}) Expect(found).To(BeTrue()) Expect(ok).To(BeTrue()) - Expect(metadata["namespace"]).To(Equal(namespace)) - Expect(metadata["name"]).To(Equal(cfg.ScaledObjectName())) + Expect(metadata["namespace"]).To(Equal(testInfra.ns)) + Expect(metadata["name"]).To(Equal(testInfra.cfg.ScaledObjectName())) specIFace, found := u.Object["spec"] spec, ok := specIFace.(map[string]interface{}) Expect(found).To(BeTrue()) Expect(ok).To(BeTrue()) - Expect(spec["minReplicaCount"]).To(BeNumerically("==", httpso.Spec.Replicas.Min)) - Expect(spec["maxReplicaCount"]).To(BeNumerically("==", httpso.Spec.Replicas.Max)) + Expect(spec["minReplicaCount"]).To(BeNumerically("==", testInfra.httpso.Spec.Replicas.Min)) + Expect(spec["maxReplicaCount"]).To(BeNumerically("==", testInfra.httpso.Spec.Replicas.Max)) }) }) diff --git a/operator/controllers/suite_test.go b/operator/controllers/suite_test.go index 8e49de6..e1b69ca 100644 --- a/operator/controllers/suite_test.go +++ b/operator/controllers/suite_test.go @@ -16,16 +16,24 @@ package controllers import ( + "context" "testing" + "github.com/go-logr/logr" + logrtest "github.com/go-logr/logr/testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/envtest/printer" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" + "github.com/kedacore/http-add-on/operator/api/v1alpha1" httpv1alpha1 "github.com/kedacore/http-add-on/operator/api/v1alpha1" + "github.com/kedacore/http-add-on/operator/controllers/config" // +kubebuilder:scaffold:imports ) @@ -44,6 +52,53 @@ func TestAPIs(t *testing.T) { []Reporter{printer.NewlineReporter{}}) } +type commonTestInfra struct { + ns string + appName string + ctx context.Context + cl client.Client + cfg config.AppInfo + logger logr.Logger + httpso v1alpha1.HTTPScaledObject +} + +func newCommonTestInfra(namespace, appName string) *commonTestInfra { + ctx := context.Background() + cl := fake.NewFakeClient() + cfg := config.AppInfo{ + Name: appName, + Namespace: namespace, + } + logger := logrtest.NullLogger{} + httpso := v1alpha1.HTTPScaledObject{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: appName, + }, + Spec: v1alpha1.HTTPScaledObjectSpec{ + ScaleTargetRef: &v1alpha1.ScaleTargetRef{ + Deployment: appName, + Service: appName, + Port: 8081, + }, + Replicas: httpv1alpha1.ReplicaStruct{ + Min: 0, + Max: 20, + }, + }, + } + + return &commonTestInfra{ + ns: namespace, + appName: appName, + ctx: ctx, + cl: cl, + cfg: cfg, + logger: logger, + httpso: httpso, + } +} + var _ = BeforeSuite(func(done Done) { // The commented code in this function connects to a test Kubernetes cluster. // We don't currently have tests that exercise functionality that needs a cluster, diff --git a/operator/controllers/user_app.go b/operator/controllers/user_app.go deleted file mode 100644 index 53f4d11..0000000 --- a/operator/controllers/user_app.go +++ /dev/null @@ -1,66 +0,0 @@ -package controllers - -import ( - "context" - - "github.com/go-logr/logr" - "github.com/kedacore/http-add-on/operator/api/v1alpha1" - "github.com/kedacore/http-add-on/operator/controllers/config" - "github.com/kedacore/http-add-on/pkg/k8s" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -func createUserApp( - ctx context.Context, - appInfo config.AppInfo, - cl client.Client, - logger logr.Logger, - httpso *v1alpha1.HTTPScaledObject, -) error { - deployment := k8s.NewDeployment( - appInfo.Namespace, - appInfo.Name, - appInfo.Image, - []int32{appInfo.Port}, - []corev1.EnvVar{}, - k8s.Labels(appInfo.Name), - ) - logger.Info("Creating app deployment", "deployment", *deployment) - if err := cl.Create(ctx, deployment); err != nil { - if errors.IsAlreadyExists(err) { - logger.Info("User app deployment already exists, moving on") - } else { - logger.Error(err, "Creating deployment") - condition := v1alpha1.CreateCondition(v1alpha1.Error, v1.ConditionFalse, v1alpha1.ErrorCreatingAppDeployment).SetMessage(err.Error()) - httpso.AddCondition(*condition) - return err - } - } - httpso.AddCondition(*v1alpha1.CreateCondition(v1alpha1.Created, v1.ConditionTrue, v1alpha1.AppDeploymentCreated).SetMessage("App deployment created")) - - servicePorts := []corev1.ServicePort{ - k8s.NewTCPServicePort("http", 8080, appInfo.Port), - } - service := k8s.NewService( - appInfo.Namespace, - appInfo.Name, - servicePorts, - corev1.ServiceTypeClusterIP, - k8s.Labels(appInfo.Name), - ) - if err := cl.Create(ctx, service); err != nil { - if errors.IsAlreadyExists(err) { - logger.Info("User app service already exists, moving on") - } else { - logger.Error(err, "Creating service") - condition := v1alpha1.CreateCondition(v1alpha1.Error, v1.ConditionFalse, v1alpha1.ErrorCreatingAppService).SetMessage(err.Error()) - httpso.AddCondition(*condition) - return err - } - } - httpso.AddCondition(*v1alpha1.CreateCondition(v1alpha1.Created, v1.ConditionTrue, v1alpha1.AppServiceCreated).SetMessage("App service created")) - return nil -} diff --git a/operator/controllers/user_app_test.go b/operator/controllers/user_app_test.go deleted file mode 100644 index 8a7b52d..0000000 --- a/operator/controllers/user_app_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package controllers - -import ( - "context" - "time" - - logrtest "github.com/go-logr/logr/testing" - "github.com/kedacore/http-add-on/operator/api/v1alpha1" - "github.com/kedacore/http-add-on/operator/controllers/config" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" -) - -var _ = Describe("UserApp", func() { - Context("Creating a user app", func() { - It("Should properly create a deployment and a service", func() { - ctx := context.Background() - cl := fake.NewFakeClient() - cfg := config.AppInfo{ - Name: "testapp", - Port: 8081, - Image: "arschles/testimg", - Namespace: "testns", - } - logger := logrtest.NullLogger{} - httpso := &v1alpha1.HTTPScaledObject{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "testns", - Name: "testapp", - }, - Spec: v1alpha1.HTTPScaledObjectSpec{ - AppName: "testname", - Image: "arschles/testapp", - Port: 8081, - }, - } - err := createUserApp(ctx, cfg, cl, logger, httpso) - Expect(err).To(BeNil()) - // make sure that httpso has the right conditions on it - Expect(len(httpso.Status.Conditions)).To(Equal(2)) - - cond1 := httpso.Status.Conditions[0] - cond1ts, err := time.Parse(time.RFC3339, cond1.Timestamp) - Expect(err).To(BeNil()) - Expect(time.Now().Sub(cond1ts) >= 0).To(BeTrue()) - Expect(cond1.Type).To(Equal(v1alpha1.Created)) - Expect(cond1.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond1.Reason).To(Equal(v1alpha1.AppDeploymentCreated)) - - cond2 := httpso.Status.Conditions[1] - cond2ts, err := time.Parse(time.RFC3339, cond2.Timestamp) - Expect(err).To(BeNil()) - Expect(time.Now().Sub(cond2ts) >= 0).To(BeTrue()) - Expect(cond2.Type).To(Equal(v1alpha1.Created)) - Expect(cond2.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond2.Reason).To(Equal(v1alpha1.AppServiceCreated)) - - // check the deployment that was created - deployment := &appsv1.Deployment{} - err = cl.Get(ctx, client.ObjectKey{ - Name: cfg.Name, - Namespace: cfg.Namespace, - }, deployment) - Expect(err).To(BeNil()) - Expect(deployment.Name).To(Equal(cfg.Name)) - Expect(len(deployment.Spec.Template.Spec.Containers)).To(Equal(1)) - Expect(deployment.Spec.Template.Spec.Containers[0].Image).To(Equal(cfg.Image)) - - // check the service that was created - svc := &corev1.Service{} - err = cl.Get(ctx, client.ObjectKey{ - Name: cfg.Name, - Namespace: cfg.Namespace, - }, svc) - Expect(err).To(BeNil()) - Expect(svc.Name).To(Equal(cfg.Name)) - Expect(len(svc.Spec.Ports)).To(Equal(1)) - Expect(svc.Spec.Ports[0].Protocol).To(Equal(corev1.ProtocolTCP)) - Expect(svc.Spec.Ports[0].TargetPort.IntVal).To(Equal(cfg.Port)) - Expect(svc.Spec.Ports[0].TargetPort.Type).To(Equal(intstr.Int)) - Expect(svc.Spec.Ports[0].Port).To(Equal(int32(8080))) - }) - }) -}) diff --git a/pkg/k8s/service.go b/pkg/k8s/service.go index d4612d8..d1f4467 100644 --- a/pkg/k8s/service.go +++ b/pkg/k8s/service.go @@ -25,6 +25,8 @@ func DeleteService(ctx context.Context, name string, cl k8scorev1.ServiceInterfa return cl.Delete(ctx, name, metav1.DeleteOptions{}) } +// NewService creates a new Service object in memory according to the input parameters. +// This function operates in memory only and doesn't do any I/O whatsoever. func NewService( namespace, name string, @@ -43,11 +45,8 @@ func NewService( }, Spec: corev1.ServiceSpec{ Ports: servicePorts, - Selector: selector, //labels(name), - // TODO: after switching to Ingress + Ingress controller, switch - // this back to ClusterIP - // Type: corev1.ServiceTypeClusterIP, - Type: svcType, + Selector: selector, + Type: svcType, }, } }