Not creating app deployments (#78)

* Allowing users to scale an existing deployment

Fixes https://github.com/kedacore/http-add-on/issues/35

Signed-off-by: Aaron Schlesinger <aaron@ecomaz.net>

* checking custom deployment info

Signed-off-by: Aaron Schlesinger <aaron@ecomaz.net>

* Adding tests for new deployment logic

Also generalizing test "infrastructure" code

Signed-off-by: Aaron Schlesinger <aaron@ecomaz.net>

* regenerating code

Signed-off-by: Aaron Schlesinger <aaron@ecomaz.net>

* Remove functionality to auto-create deployments and services

Users provide a scale target ref, which is the name of the
deployment to scale and the service to route to. They are required
to have already deployed these things already

Signed-off-by: Aaron Schlesinger <aaron@ecomaz.net>

* Adding more docs

Signed-off-by: Aaron Schlesinger <aaron@ecomaz.net>

* regenerating with 0.5.0 controller-gen

Signed-off-by: Aaron Schlesinger <aaron@ecomaz.net>

* fixing compile err

Signed-off-by: Aaron Schlesinger <aaron@ecomaz.net>
This commit is contained in:
Aaron Schlesinger 2021-03-19 12:58:29 -07:00 committed by GitHub
parent a0260907a1
commit fd4cdfe718
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 666 additions and 419 deletions

View File

@ -87,6 +87,12 @@ helm-upgrade-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
#####

View File

@ -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: ""

23
charts/xkcd/.helmignore Normal file
View File

@ -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/

24
charts/xkcd/Chart.yaml Normal file
View File

@ -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"

View File

@ -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 }}

View File

@ -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 }}

View File

@ -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

View File

@ -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 }}

View File

@ -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 }}

View File

@ -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 }}

View File

@ -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

38
charts/xkcd/values.yaml Normal file
View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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
<deployment name>-interceptor-proxy
```
>The service will always be a `ClusterIP` type and will be created in the same namespace as the `HTTPScaledObject` you created.

View File

@ -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

2
go.mod
View File

@ -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

19
go.sum
View File

@ -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=

View File

@ -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 {

View File

@ -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

View File

@ -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:
- ""

View File

@ -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)
}

View File

@ -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())

View File

@ -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(

View File

@ -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
}

View File

@ -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{

View File

@ -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,

View File

@ -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))
})
})

View File

@ -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,

View File

@ -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
}

View File

@ -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)))
})
})
})

View File

@ -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,
},
}
}