Skip to main content
Version: development

Aperture Policies and Blueprints as a Jsonnet Mixins Library

In addition to CLI-based generation of Aperture Policies and Grafana Dashboard, Aperture Policies & Blueprints can be imported into your Jsonnet project as a library. This can also be integrated with other Kubernetes deployment tools like Tanka.

For example, to create a latency-based AIMD concurrency limiting policy that can be loaded by the controller, you need to install Aperture blueprints library with Jsonnet Bundler in your project:

jb install github.com/fluxninja/aperture/blueprints@main
tip

You can use a specific Aperture GitHub repository branch instead of main, for example, stable/v0.2.x, or even a specific release tag, for example, v0.2.2 to match your Aperture Controller installation version.

You can then create a Policy resource using Jsonnet definitions:

local aperture = import 'github.com/fluxninja/aperture/blueprints/main.libsonnet';

local policy = aperture.spec.v1.Policy;
local component = aperture.spec.v1.Component;
local query = aperture.spec.v1.Query;
local selector = aperture.spec.v1.Selector;
local circuit = aperture.spec.v1.Circuit;
local port = aperture.spec.v1.Port;
local resources = aperture.spec.v1.Resources;
local flowControlResources = aperture.spec.v1.FlowControlResources;
local fluxMeter = aperture.spec.v1.FluxMeter;
local promQL = aperture.spec.v1.PromQL;
local ema = aperture.spec.v1.EMA;
local emaParameters = aperture.spec.v1.EMAParameters;
local combinator = aperture.spec.v1.ArithmeticCombinator;
local decider = aperture.spec.v1.Decider;
local alerter = aperture.spec.v1.Alerter;
local alerterParameters = aperture.spec.v1.AlerterParameters;

local svcSelectors = [
selector.new()
+ selector.withControlPoint('ingress')
+ selector.withService('service1-demo-app.demoapp.svc.cluster.local')
+ selector.withAgentGroup('default'),
];

local policyDef =
policy.new()
+ policy.withResources(
resources.new()
+ resources.withFlowControl(flowControlResources.new()
+ flowControlResources.withFluxMetersMixin(
{ test: fluxMeter.new() + fluxMeter.withSelectors(svcSelectors) }
))
)
+ policy.withCircuit(
circuit.new()
+ circuit.withEvaluationInterval('10s')
+ circuit.withComponents([
component.withQuery(
query.new()
+ query.withPromql(
local q = 'sum(increase(flux_meter_sum{decision_type!="DECISION_TYPE_REJECTED", flow_status="OK", flux_meter_name="test"}[30s]))/sum(increase(flux_meter_count{decision_type!="DECISION_TYPE_REJECTED", flow_status="OK", flux_meter_name="test"}[30s]))';
promQL.new()
+ promQL.withQueryString(q)
+ promQL.withEvaluationInterval('10s')
+ promQL.withOutPorts({ output: port.withSignalName('LATENCY') }),
),
),
component.withEma(
ema.withParameters(
emaParameters.new()
+ emaParameters.withEmaWindow('1500s')
+ emaParameters.withWarmupWindow('10s')
)
+ ema.withInPortsMixin(ema.inPorts.withInput(port.withSignalName('LATENCY')))
+ ema.withOutPortsMixin(ema.outPorts.withOutput(port.withSignalName('LATENCY_EMA')))
),
component.withArithmeticCombinator(combinator.mul(port.withSignalName('LATENCY_EMA'),
port.withConstantSignal(1.1),
output=port.withSignalName('LATENCY_SETPOINT'))),
component.withDecider(
decider.new()
+ decider.withOperator('gt')
+ decider.withInPortsMixin(
decider.inPorts.withLhs(port.withSignalName('LATENCY'))
+ decider.inPorts.withRhs(port.withSignalName('LATENCY_SETPOINT'))
)
+ decider.withOutPortsMixin(decider.outPorts.withOutput(port.withSignalName('IS_OVERLOAD_SWITCH')))
),
component.withAlerter(
alerter.new()
+ alerter.withInPorts({ signal: port.withSignalName('IS_OVERLOAD_SWITCH') })
+ alerter.withParameters(
alerterParameters.new()
+ alerterParameters.withAlertName('overload')
+ alerterParameters.withSeverity('crit')
)
),
]),
);

local policyResource = {
kind: 'Policy',
apiVersion: 'fluxninja.com/v1alpha1',
metadata: {
name: 'signal-processing',
labels: {
'fluxninja.com/validate': 'true',
},
},
spec: policyDef,
};

policyResource

And then, render it with Jsonnet:

jsonnet -J vendor <example_file>.jsonnet  | yq -P

After running this command, you should see the following contents in the YAML file:

flowchart LR
subgraph root.0[<center>PromQL<br/>every 10s</center>]
subgraph root.0_outports[ ]
style root.0_outports fill:none,stroke:none
root.0output[output]
end
end
subgraph root.1[<center>EMA<br/>win: 150</center>]
subgraph root.1_inports[ ]
style root.1_inports fill:none,stroke:none
root.1input[input]
end
subgraph root.1_outports[ ]
style root.1_outports fill:none,stroke:none
root.1output[output]
end
end
subgraph root.2[<center>ArithmeticCombinator<br/>mul</center>]
subgraph root.2_inports[ ]
style root.2_inports fill:none,stroke:none
root.2lhs[lhs]
root.2rhs[rhs]
end
subgraph root.2_outports[ ]
style root.2_outports fill:none,stroke:none
root.2output[output]
end
end
root.2_rhs_FakeConstantout((1.10))
subgraph root.3[<center>Decider<br/>gt for 0s</center>]
subgraph root.3_inports[ ]
style root.3_inports fill:none,stroke:none
root.3lhs[lhs]
root.3rhs[rhs]
end
subgraph root.3_outports[ ]
style root.3_outports fill:none,stroke:none
root.3output[output]
end
end
subgraph root.4[<center>Alerter<br/>overload/crit</center>]
subgraph root.4_inports[ ]
style root.4_inports fill:none,stroke:none
root.4signal[signal]
end
end
root.0output --> |LATENCY| root.1input
root.0output --> |LATENCY| root.3lhs
root.1output --> |LATENCY_EMA| root.2lhs
root.2output --> |LATENCY_SETPOINT| root.3rhs
root.2_rhs_FakeConstantout --> root.2rhs
root.3output --> |IS_OVERLOAD_SWITCH| root.4signal

The generated policy can be applied to the running instance of aperture-controller using kubectl as follows:

kubectl apply --namespace aperture-controller --filename <example_file>.yaml