add release orchestrator docs and sprints gaps fills

This commit is contained in:
2026-01-11 01:05:17 +02:00
parent d58c093887
commit a62974a8c2
37 changed files with 6061 additions and 0 deletions

View File

@@ -0,0 +1,348 @@
# Router Plugins
> Traffic router plugins for progressive delivery (Nginx, AWS ALB, and custom implementations).
**Status:** Planned (not yet implemented)
**Source:** [Architecture Advisory Section 11.4](../../../product/advisories/09-Jan-2026%20-%20Stella%20Ops%20Orchestrator%20Architecture.md)
**Related Modules:** [Progressive Delivery Module](../modules/progressive-delivery.md), [Plugin System](../modules/plugin-system.md)
**Sprint:** [110_004 Router Plugins](../../../../implplan/SPRINT_20260110_110_004_PROGDL_nginx_router.md)
## Overview
Router plugins enable traffic shifting for progressive delivery. The orchestrator ships with an Nginx router plugin for v1, with HAProxy, Traefik, and AWS ALB available as additional plugins.
---
## Router Plugin Interface
All router plugins implement the `TrafficRouterPlugin` interface:
```typescript
interface TrafficRouterPlugin {
// Configuration
configureRoute(config: RouteConfig): Promise<void>;
// Traffic operations
shiftTraffic(from: string, to: string, percentage: number): Promise<void>;
getTrafficDistribution(): Promise<TrafficDistribution>;
// Health
validateConfig(): Promise<ValidationResult>;
reload(): Promise<void>;
}
interface RouteConfig {
upstream: string;
serverName: string;
variations: Variation[];
splitType: "weight" | "header" | "cookie";
headerName?: string;
headerValueB?: string;
stickySession?: boolean;
stickyDuration?: number;
}
interface Variation {
name: string;
targets: string[];
weight: number;
}
interface TrafficDistribution {
variations: {
name: string;
percentage: number;
targets: string[];
}[];
}
```
---
## Nginx Router Plugin (v1 Built-in)
The Nginx plugin generates and manages Nginx configuration for traffic splitting.
### Implementation
```typescript
class NginxRouterPlugin implements TrafficRouterPlugin {
async configureRoute(config: RouteConfig): Promise<void> {
const upstreamConfig = this.generateUpstreamConfig(config);
const serverConfig = this.generateServerConfig(config);
// Write configuration files
await this.writeConfig(
`/etc/nginx/conf.d/upstream-${config.upstream}.conf`,
upstreamConfig
);
await this.writeConfig(
`/etc/nginx/conf.d/server-${config.upstream}.conf`,
serverConfig
);
// Validate configuration
const validation = await this.validateConfig();
if (!validation.valid) {
throw new Error(`Nginx config validation failed: ${validation.error}`);
}
// Reload nginx
await this.reload();
}
}
```
### Upstream Configuration
```typescript
private generateUpstreamConfig(config: RouteConfig): string {
const lines: string[] = [];
for (const variation of config.variations) {
lines.push(`upstream ${config.upstream}_${variation.name} {`);
for (const target of variation.targets) {
lines.push(` server ${target};`);
}
lines.push(`}`);
lines.push(``);
}
// Combined upstream with weights (for percentage-based routing)
if (config.splitType === "weight") {
lines.push(`upstream ${config.upstream} {`);
for (const variation of config.variations) {
const weight = variation.weight;
for (const target of variation.targets) {
lines.push(` server ${target} weight=${weight};`);
}
}
lines.push(`}`);
}
return lines.join("\n");
}
```
### Server Configuration
```typescript
private generateServerConfig(config: RouteConfig): string {
if (config.splitType === "header" || config.splitType === "cookie") {
// Split block based on header/cookie
return `
map $http_${config.headerName || "x-variation"} $${config.upstream}_backend {
default ${config.upstream}_A;
"${config.headerValueB || "B"}" ${config.upstream}_B;
}
server {
listen 80;
server_name ${config.serverName};
location / {
proxy_pass http://$${config.upstream}_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
`;
} else {
// Weight-based (default)
return `
server {
listen 80;
server_name ${config.serverName};
location / {
proxy_pass http://${config.upstream};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
`;
}
}
```
### Traffic Shifting
```typescript
async shiftTraffic(from: string, to: string, percentage: number): Promise<void> {
const config = await this.getCurrentConfig();
// Update weights
for (const variation of config.variations) {
if (variation.name === to) {
variation.weight = percentage;
} else {
variation.weight = 100 - percentage;
}
}
await this.configureRoute(config);
}
async getTrafficDistribution(): Promise<TrafficDistribution> {
// Parse current nginx config to get weights
const config = await this.parseCurrentConfig();
return {
variations: config.variations.map(v => ({
name: v.name,
percentage: v.weight,
targets: v.targets,
})),
};
}
```
---
## AWS ALB Router Plugin
The AWS ALB plugin manages weighted target groups for traffic splitting.
### Implementation
```typescript
class AWSALBRouterPlugin implements TrafficRouterPlugin {
private alb: AWS.ELBv2;
async configureRoute(config: RouteConfig): Promise<void> {
const listenerArn = config.listenerArn;
// Create/update target groups for each variation
const targetGroupArns: Record<string, string> = {};
for (const variation of config.variations) {
const tgArn = await this.ensureTargetGroup(
`${config.upstream}-${variation.name}`,
variation.targets
);
targetGroupArns[variation.name] = tgArn;
}
// Update listener rule with weighted target groups
await this.alb.modifyRule({
RuleArn: config.ruleArn,
Actions: [{
Type: "forward",
ForwardConfig: {
TargetGroups: config.variations.map(v => ({
TargetGroupArn: targetGroupArns[v.name],
Weight: v.weight,
})),
TargetGroupStickinessConfig: {
Enabled: config.stickySession || false,
DurationSeconds: config.stickyDuration || 3600,
},
},
}],
}).promise();
}
async shiftTraffic(from: string, to: string, percentage: number): Promise<void> {
const rule = await this.getRule();
const forwardConfig = rule.Actions[0].ForwardConfig;
// Update weights
for (const tg of forwardConfig.TargetGroups) {
if (tg.TargetGroupArn.includes(`-${to}`)) {
tg.Weight = percentage;
} else {
tg.Weight = 100 - percentage;
}
}
await this.alb.modifyRule({
RuleArn: rule.RuleArn,
Actions: rule.Actions,
}).promise();
}
async getTrafficDistribution(): Promise<TrafficDistribution> {
const rule = await this.getRule();
const forwardConfig = rule.Actions[0].ForwardConfig;
const variations = [];
for (const tg of forwardConfig.TargetGroups) {
const targets = await this.getTargetGroupTargets(tg.TargetGroupArn);
const name = tg.TargetGroupArn.split("-").pop();
variations.push({
name,
percentage: tg.Weight,
targets: targets.map(t => t.Id),
});
}
return { variations };
}
}
```
---
## Router Plugin Catalog
| Plugin | Status | Description |
|--------|--------|-------------|
| Nginx | v1 Built-in | Configuration-based weight/header routing |
| HAProxy | Plugin | Runtime API for traffic management |
| Traefik | Plugin | Dynamic configuration via API |
| AWS ALB | Plugin | Weighted target groups |
| Envoy | Planned | xDS API integration |
---
## Creating Custom Router Plugins
To create a custom router plugin:
1. **Implement Interface:** Create a class implementing `TrafficRouterPlugin`
2. **Register Plugin:** Add to plugin registry with capabilities
3. **Configuration Schema:** Define JSON Schema for plugin config
4. **Health Checks:** Implement connection testing
5. **Rollback Support:** Handle traffic reversion on failures
### Example Plugin Manifest
```yaml
plugin:
name: my-router
version: 1.0.0
type: router
capabilities:
- traffic-routing
- weight-based
- header-based
config:
type: object
properties:
endpoint:
type: string
description: Router API endpoint
auth:
type: object
properties:
type:
enum: [basic, token]
credentialRef:
type: string
```
---
## See Also
- [Progressive Delivery Module](../modules/progressive-delivery.md)
- [Plugin System](../modules/plugin-system.md)
- [Canary Controller](canary.md)
- [A/B Release Models](ab-releases.md)