Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: strip out raw HTML from docs #1414

Merged
merged 2 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 98 additions & 74 deletions content/docs/1.4/concepts/external-scalers.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ type Scaler interface {
```

The `Scaler` interface defines 4 methods:

- `IsActive` is called on `pollingInterval` defined in the ScaledObject/ScaledJob CRDs and scaling to 1 happens if this returns true.
- `Close` is called to allow the scaler to clean up connections or other resources.
- `GetMetricSpec` returns the target value for the HPA definition for the scaler. For more details refer to [Implementing `GetMetricSpec`](#4-implementing-getmetricspec).
- `GetMetrics` returns the value of the metric referred to from `GetMetricSpec`. For more details refer to [Implementing `GetMetrics`](#5-implementing-getmetrics).
> Refer to the [HPA docs](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/) for how HPA calculates replicaCount based on metric value and target value.
> Refer to the [HPA docs](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/) for how HPA calculates replicaCount based on metric value and target value.

### External Scaler GRPC interface

Expand All @@ -51,6 +52,7 @@ service ExternalScaler {
- `IsActive` maps to the `IsActive` method on the `Scaler` interface.

Few things to notice:

- `IsActive`, `StreamIsActive`, and `GetMetricsSpec` are called with a `ScaledObjectRef` that contains the scaledObject name/namespace as well as the content of `metadata` defined in the trigger.

For example the following `ScaledObject`:
Expand Down Expand Up @@ -85,6 +87,7 @@ KEDA will attempt a connection to `service-address.svc.local:9090` and calls `Is
}
}
```

## Implementing KEDA external scaler GRPC interface

### Implementing an external scaler:
Expand All @@ -110,6 +113,7 @@ go mod init example.com/external-scaler/sample
mkdir externalscaler
protoc externalscaler.proto --go_out=plugins=grpc:externalscaler
```

{{< /collapsible >}}

{{< collapsible "C#" >}}
Expand Down Expand Up @@ -146,9 +150,8 @@ mkdir Services
```bash
npm install --save grpc request
```
{{< /collapsible >}}

<br />
{{< /collapsible >}}

#### 3. Implementing `IsActive`

Expand Down Expand Up @@ -178,6 +181,7 @@ spec:
Full implementation can be found here: https://github.com/kedacore/external-scaler-samples

`main.go`

```golang
func (e *ExternalScaler) IsActive(ctx context.Context, scaledObject *pb.ScaledObjectRef) (*pb.IsActiveResponse, error) {
// request.Scalermetadata contains the `metadata` defined in the ScaledObject
Expand Down Expand Up @@ -224,12 +228,14 @@ func (e *ExternalScaler) IsActive(ctx context.Context, scaledObject *pb.ScaledOb
}, nil
}
```

{{< /collapsible >}}

{{< collapsible "C#" >}}
Full implementation can be found here: https://github.com/kedacore/external-scaler-samples

`Services/ExternalScalerService.cs`

```csharp
public class ExternalScalerService : ExternalScaler.ExternalScalerBase
{
Expand Down Expand Up @@ -262,79 +268,84 @@ public class ExternalScalerService : ExternalScaler.ExternalScalerBase
}
}
```

{{< /collapsible >}}

{{< collapsible "Javascript" >}}
`index.js`

```js
const grpc = require('grpc')
const request = require('request')
const externalScalerProto = grpc.load('externalscaler.proto')
const grpc = require("grpc");
const request = require("request");
const externalScalerProto = grpc.load("externalscaler.proto");

const server = new grpc.Server()
const server = new grpc.Server();
server.addService(externalScalerProto.externalscaler.ExternalScaler.service, {
isActive: (call, callback) => {
const longitude = call.request.scalerMetadata.longitude
const latitude = call.request.scalerMetadata.latitude
const longitude = call.request.scalerMetadata.longitude;
const latitude = call.request.scalerMetadata.latitude;
if (!longitude || !latitude) {
callback({
code: grpc.status.INVALID_ARGUMENT,
details: 'longitude and latitude must be specified',
})
details: "longitude and latitude must be specified",
});
} else {
const now = new Date()
const yesterday = new Date(new Date().setDate(new Date().getDate()-1));

const startTime = `${yesterday.getUTCFullYear()}-${yesterday.getUTCMonth()}-${yesterday.getUTCDay()}`
const endTime = `${now.getUTCFullYear()}-${now.getUTCMonth()}-${now.getUTCDay()}`
const radiusKm = 500
const query = `format=geojson&starttime=${startTime}&endtime=${endTime}&longitude=${longitude}&latitude=${latitude}&maxradiuskm=${radiusKm}`

request.get({
url: `https://earthquake.usgs.gov/fdsnws/event/1/query?${query}`,
json: true,
}, (err, resp, data) => {
if (err) {
callback({
code: grpc.status.INTERNAL,
details: err,
})
} else if (resp.statusCode !== 200) {
callback({
code: grpc.status.INTERNAL,
details: `expected status 200, got ${resp.statusCode}`
})
} else {
// count how many earthquakes with mag > 1.0
let count = 0
data.features.forEach(i => {
if (i.properties.mag > 1.0) {
count++
}
})
callback(null, {
result: count > 2,
})
const now = new Date();
const yesterday = new Date(new Date().setDate(new Date().getDate() - 1));

const startTime = `${yesterday.getUTCFullYear()}-${yesterday.getUTCMonth()}-${yesterday.getUTCDay()}`;
const endTime = `${now.getUTCFullYear()}-${now.getUTCMonth()}-${now.getUTCDay()}`;
const radiusKm = 500;
const query = `format=geojson&starttime=${startTime}&endtime=${endTime}&longitude=${longitude}&latitude=${latitude}&maxradiuskm=${radiusKm}`;

request.get(
{
url: `https://earthquake.usgs.gov/fdsnws/event/1/query?${query}`,
json: true,
},
(err, resp, data) => {
if (err) {
callback({
code: grpc.status.INTERNAL,
details: err,
});
} else if (resp.statusCode !== 200) {
callback({
code: grpc.status.INTERNAL,
details: `expected status 200, got ${resp.statusCode}`,
});
} else {
// count how many earthquakes with mag > 1.0
let count = 0;
data.features.forEach((i) => {
if (i.properties.mag > 1.0) {
count++;
}
});
callback(null, {
result: count > 2,
});
}
}
})
);
}
}
})
},
});

server.bind('127.0.0.1:9090', grpc.ServerCredentials.createInsecure())
console.log('Server listening on 127.0.0.1:9090')
server.bind("127.0.0.1:9090", grpc.ServerCredentials.createInsecure());
console.log("Server listening on 127.0.0.1:9090");

server.start()
server.start();
```
{{< /collapsible >}}

<br />
{{< /collapsible >}}

#### 4. Implementing `GetMetricSpec`

`GetMetricSpec` returns the `target` value for [the HPA definition for the scaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/). This scaler will define a static target of 10, but the threshold value is often specified in the metadata for other scalers.

{{< collapsible "Golang" >}}

```golang
func (e *ExternalScaler) GetMetricSpec(context.Context, *pb.ScaledObjectRef) (*pb.GetMetricSpecResponse, error) {
return &pb.GetMetricSpecResponse{
Expand All @@ -345,9 +356,11 @@ func (e *ExternalScaler) GetMetricSpec(context.Context, *pb.ScaledObjectRef) (*p
}, nil
}
```

{{< /collapsible >}}

{{< collapsible "C#" >}}

```csharp
public override async Task<GetMetricSpecResponse> GetMetricSpec(ScaledObjectRef request, ServerCallContext context)
{
Expand All @@ -362,31 +375,35 @@ public override async Task<GetMetricSpecResponse> GetMetricSpec(ScaledObjectRef
return Task.FromResult(resp);
}
```

{{< /collapsible >}}

{{< collapsible "Javascript" >}}

```js
server.addService(externalScalerProto.externalscaler.ExternalScaler.service, {
// ...
getMetricSpec: (call, callback) => {
callback(null, {
metricSpecs: [{
metricName: 'earthquakeThreshold',
targetSize: 10,
}]
})
}
})
metricSpecs: [
{
metricName: "earthquakeThreshold",
targetSize: 10,
},
],
});
},
});
```
{{< /collapsible >}}

<br />
{{< /collapsible >}}

#### 5. Implementing `GetMetrics`

`GetMetrics` returns the value of the metric referred to from `GetMetricSpec`, in this example it's `earthquakeThreshold`.

{{< collapsible "Golang" >}}

```golang
func (e *ExternalScaler) GetMetrics(_ context.Context, metricRequest *pb.GetMetricsRequest) (*pb.GetMetricsResponse, error) {
longitude := metricRequest.ScaledObjectRef.ScalerMetadata["longitude"]
Expand All @@ -409,9 +426,11 @@ func (e *ExternalScaler) GetMetrics(_ context.Context, metricRequest *pb.GetMetr
}, nil
}
```

{{< /collapsible >}}

{{< collapsible "C#" >}}

```csharp
public override async Task<GetMetricsResponse> GetMetrics(GetMetricsRequest request, ServerCallContext context)
{
Expand All @@ -436,38 +455,43 @@ public override async Task<GetMetricsResponse> GetMetrics(GetMetricsRequest requ
return resp;
}
```

{{< /collapsible >}}

{{< collapsible "Javascript" >}}

```js
server.addService(externalScalerProto.externalscaler.ExternalScaler.service, {
// ...
getMetrics: (call, callback) => {
const longitude = call.request.scaledObjectRef.scalerMetadata.longitude
const latitude = call.request.scaledObjectRef.scalerMetadata.latitude
const longitude = call.request.scaledObjectRef.scalerMetadata.longitude;
const latitude = call.request.scaledObjectRef.scalerMetadata.latitude;
if (!longitude || !latitude) {
callback({
code: grpc.status.INVALID_ARGUMENT,
details: 'longitude and latitude must be specified',
})
details: "longitude and latitude must be specified",
});
} else {
getEarthquakeCount((err, count) => {
if (err) {
callback({
code: grpc.status.INTERNAL,
details: err,
})
});
} else {
callback(null, {
metricValues: [{
metricName: 'earthquakeThreshold',
metricValue: count,
}]
})
metricValues: [
{
metricName: "earthquakeThreshold",
metricValue: count,
},
],
});
}
})
});
}
}
})
},
});
```

{{< /collapsible >}}
Loading