Skip to content

Support weighted target group canary deployments - #191

Merged
YoungJinJung merged 3 commits into
mainfrom
codex/weighted-canary-bake-time
Jun 29, 2026
Merged

Support weighted target group canary deployments#191
YoungJinJung merged 3 commits into
mainfrom
codex/weighted-canary-bake-time

Conversation

@YoungJinJung

@YoungJinJung YoungJinJung commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

What changed

Closes #189

Weighted target group canary

  • Replaced the old canary-specific load balancer path with weighted target groups on an existing ALB/NLB listener default action.
  • Canary deployment now copies the original target group, creates a canary target group, and attaches the new canary ASG only to that canary target group.
  • After the canary ASG is created, the listener is first updated with stable=100 and canary=0 so the canary target group becomes active for load balancer health checks. Without this, AWS reports the target group as unused.
  • After canary health checks pass, the listener is updated to the configured canary.weight value. The default remains 10.
  • --complete-canary attaches the canary ASG to the original target group, restores the listener to a single original target group forward action, then detaches and deletes the canary target group.
  • Fixed a complete-canary nil panic caused by missing AppliedCapacity during health checks.
  • Fixed the complete cleanup loop so it does not wait on the promoted latest ASG as if it were an old ASG.
  • Fixed Canary.RunAPITest, which previously called itself recursively instead of delegating to Deployer.RunAPITest.

Manifest UX

The manifest no longer needs a long listener ARN. New manifests can use load balancer name plus listener port:

canary:
  load_balancer: demoapp-xyzdapne2-ext
  listener_port: 443
  listener_protocol: HTTPS
  weight: 10
  bake_time: 10m
  • canary.listener_arn is still supported for backward compatibility.
  • If listener_arn is omitted, Goployer resolves the listener with DescribeLoadBalancers and DescribeListeners.
  • listener_protocol is optional and only used to make the listener match explicit.

Bake time

  • Added canary.bake_time.
  • After the canary weight is applied, the deploy command waits for the configured duration before exiting.
  • This is only a time gate. It does not evaluate CloudWatch alarms or API tests.

Docs and examples

  • Added English and Korean docs for weighted canary deployments.
  • Documented ALB/NLB constraints:
    • only listener default actions are supported
    • NLB weighted listeners cannot mix TCP, TLS, and UDP target groups on the same listener
    • weighted target groups must use the same IP address type
    • NLB weight changes apply to new flows, not existing connections
  • Added demo Python userdata scripts and demo manifests.
  • Regenerated the JSON schema.

What was tested

Local checks

  • go test -count=1 ./pkg/... ./cmd/... ./hack/...
  • make linters
  • git diff --check

Unit tests

Added or updated tests for:

  • weighted forward action generation
  • canary weight/default weight calculation
  • listener lookup manifest validation
  • bake time configuration
  • Canary.RunAPITest delegation to Deployer.RunAPITest

Live AWS validation

Validated the full flow in a test AWS account using these existing resources:

  • VPC: vpc-021c0886b3de5a28b
  • ALB: demoapp-xyzdapne2-ext
  • stable target group: demoapp-xyzdapne2-ext
  • listener: HTTPS:443
  • app security group: demoapp-xyzd_apnortheast2-app

Validation results:

  • Stable deploy succeeded: demoapp-xyzd_apnortheast2-v000.
  • Canary deploy succeeded: demoapp-xyzd_apnortheast2-v001.
  • Listener weighted forward was confirmed as stable=90, canary=10.
  • 60 ALB requests returned stable=49, canary=11.
  • --complete-canary restored the listener to a single original target group forward action.
  • Canary target group demoapp-xyzd-canary-v001 was deleted.
  • Final ASG state: demoapp-xyzd_apnortheast2-v001, desired/min/max 1/1/1, target health healthy.
  • Final ALB responses:
{"app":"demoapp","version":"canary","path":"/"}
{"status":"UP"}

Remaining work

Tracked in #190

  • canary.bake_time only waits. It does not decide whether the canary is safe to promote.
  • Add CloudWatch alarm based promotion gates, for example:
    • canary target group UnHealthyHostCount == 0
    • canary target group HTTPCode_Target_5XX_Count == 0
    • canary target group TargetResponseTime below a configured threshold
  • Decide whether the existing API test feature should become a --complete-canary promotion gate.
  • Define rollback behavior when promotion gates fail.
  • NLB support follows the ELBV2 listener/target group API model and AWS documentation, but this PR was not live-tested against an NLB.
  • Host/path listener rule based canary routing is still out of scope. This PR supports listener default actions only.

Operational notes

  • New manifests should prefer load_balancer + listener_port over direct listener_arn.
  • The canary ASG is not attached to the original target group when canary starts. It is attached to the original target group during --complete-canary.
  • .gitignore has an unrelated local .omx/ change in my working tree; it is intentionally not included in this PR.

Summary by CodeRabbit

  • New Features

    • Added weighted canary deployments for ALB/NLB, including start and --complete-canary completion flow.
    • Expanded deployment configuration with canary routing settings plus richer EBS, network interface, and region options.
    • Added example canary/stable manifests and demo startup scripts.
  • Documentation

    • Added new English and Korean guides for weighted canary behavior, constraints, and CLI workflow.
  • Bug Fixes

    • Health-check failures in deployment now properly stop the deploy sequence and surface the first error.
  • Tests

    • Added coverage for weighted listener actions and canary weight/rollback/validation logic.

The canary path now reuses an existing ALB/NLB listener and forwards by target group weight instead of creating a separate canary load balancer. The manifest can reference the listener by load balancer name and port, keeps listener_arn for compatibility, and supports a simple bake_time wait after canary traffic is enabled.

Constraint: Current requirement targets default listener actions, not host/path listener rules
Constraint: Bake time is only a time gate and does not imply health/alarm based promotion
Rejected: Reintroduce separate canary load balancers | existing listener weighted target groups are the requested deployment shape
Rejected: Add a promotion policy engine | CloudWatch/API gates need separate operational criteria
Confidence: high
Scope-risk: moderate
Directive: Do not remove the weight=0 pre-health-check listener attach without verifying ALB/NLB target groups leave unused state
Tested: go test -count=1 ./pkg/... ./cmd/... ./hack/...
Tested: make linters
Tested: live AWS stable/canary/complete flow on demoapp-xyzdapne2-ext
Not-tested: NLB live deployment path
Related: #189
Related: #190
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8857bc60-260c-49cf-9fb9-285ec6c6e98d

📥 Commits

Reviewing files that changed from the base of the PR and between c1fe1c7 and 3ad1679.

📒 Files selected for processing (7)
  • docs/content/en/docs/Concepts/canary.md
  • docs/content/en/schemas/schema.json
  • docs/content/ko/docs/Concepts/canary.md
  • pkg/deployer/canary.go
  • pkg/deployer/canary_test.go
  • pkg/runner/runner.go
  • pkg/schemas/config.go

📝 Walkthrough

Walkthrough

Adds weighted canary deployment support across configuration, docs, examples, AWS ELBv2 helpers, and canary deployment flow. The canary path now uses listener weighted forward actions, validates listener and weight settings, and reattaches target groups during completion.

Changes

Weighted Canary Deployment

Layer / File(s) Summary
Schema and config contracts
pkg/schemas/config.go, docs/content/en/schemas/schema.json
The config types and schema add canary settings plus related block-device, ENI, subnet, hop-limit, and ordering fields.
ELBV2 weighted listener primitives
pkg/aws/elbv2.go, pkg/aws/elbv2_test.go
ELBV2 helpers add load balancer lookup by name and weighted listener-forward modification, and the test validates the weighted action builder output.
Examples and documentation
examples/scripts/*, examples/manifests/*, docs/content/en/docs/Concepts/canary.md, docs/content/ko/docs/Concepts/canary.md
New userdata scripts, stable and canary manifests, and English and Korean docs describe the weighted canary flow and sample configuration.
Health-check and deploy flow
pkg/runner/runner.go, pkg/deployer/canary.go
The runner now returns health-check failures, and the canary deploy path uses the updated error flow to reach rollback and completion handling.
Canary state, validation, and weights
pkg/deployer/canary.go
The Canary state tracks target-group ARNs, validates listener and weight settings, computes listener weights, applies weighted routing, and focuses cleanup on canary target groups.
Complete canary promotion
pkg/deployer/canary.go
CompleteCanaryDeployment resolves original target-group ARNs, reattaches the latest ASG to them, and updates applied capacity and ASG tracking.
Canary tests
pkg/deployer/canary_test.go
Tests cover weight calculation, bake time, rollback decisions, ASG name selection, deployment validation, and RunAPITest delegation.

Sequence Diagram(s)

sequenceDiagram
  participant Runner
  participant Canary
  participant ELBV2Client

  Runner->>Canary: health checking / deploy
  Canary->>Canary: ValidateCanaryDeployment
  Canary->>ELBV2Client: GetLoadBalancerByName(...)
  Canary->>ELBV2Client: ModifyListenerWeightedForward(...)
  Canary->>Canary: wait bake_time
  Canary->>ELBV2Client: ModifyListenerWeightedForward(... 100/0)
  Canary->>Canary: CompleteCanaryDeployment
  Canary->>Canary: DetachLatestCanaryResources
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

Hop, hop, the weights go by 🐰
A canary breeze beneath the sky
Stable stays, then traffic sways
Bake-time nibbles make the day
Soft ears twitch: deploys are nigh

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: weighted target group canary deployments.
Linked Issues check ✅ Passed The PR appears to satisfy #189 by adding weighted listener forwarding, listener lookup options, bake time, docs, examples, and canary cleanup.
Out of Scope Changes check ✅ Passed The changes stay focused on canary deployment support and related docs, schema, examples, and tests.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/weighted-canary-bake-time

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@amazon-q-developer amazon-q-developer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

This PR successfully implements weighted target group canary deployments as a replacement for the previous dedicated load balancer approach. The implementation is generally solid with comprehensive changes across deployment logic, AWS service integration, and configuration schemas.

Key Changes

  • Replaced dedicated canary load balancer/security group creation with weighted target group forwarding on existing ALB/NLB listeners
  • Added manifest fields for specifying listener by load balancer name + port instead of requiring ARN
  • Implemented bake time support to allow deployments to wait after applying canary weights
  • Fixed recursive call bug in Canary.RunAPITest by properly delegating to Deployer.RunAPITest
  • Fixed health check panic from empty AppliedCapacity in complete canary path
  • Fixed cleanup loop that incorrectly waited for the latest ASG

Critical Finding

1 issue requiring attention: The weight validation allows values up to 99, which creates an extreme traffic imbalance (1% stable, 99% canary). While technically functional, this may cause operational issues. Consider if a lower maximum (e.g., 90) would provide better safety margins.

Testing

The PR includes comprehensive unit tests and real AWS environment validation with successful end-to-end verification including:

  • Stable and canary deployments
  • Weighted traffic distribution
  • Complete canary promotion
  • Resource cleanup

Documentation

Both Korean and English documentation have been updated with the new weighted canary concepts, manifest examples, and NLB-specific constraints.

Overall, this is a well-implemented feature with proper testing and documentation. The single finding noted above should be evaluated but does not necessarily block merge.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

Comment thread pkg/deployer/canary.go Outdated
Comment on lines +311 to +312
if regionConfig.Canary.Weight < 0 || regionConfig.Canary.Weight > 99 {
return errors.New("canary.weight must be between 0 and 99")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Logic Error: The validation logic allows Weight=99 which would cause a crash when calculating stableWeight (100-99=1, canaryWeight=99, both non-zero). When weight is 99, the stable target group receives only 1% traffic. However, AWS ELB weighted target groups require that at least one target group weight must be greater than 0. While this technically works, a weight of 99 creates an extreme imbalance that could cause operational issues. Consider whether 99 should be the maximum allowed value or if a lower maximum (e.g., 90) would be more appropriate for safety.

@YoungJinJung YoungJinJung self-assigned this Jun 27, 2026
The canary weight field is a percentage, so allowing 99 leaves only 1 percent stable traffic. That is technically valid for ELB weighted target groups, but too aggressive for the safe canary default this feature is intended to provide.

Constraint: Canary weight is modeled as a percentage, not raw ELB 0-999 weight values
Rejected: Keep 99 as the upper bound | it permits near-full traffic shift before complete-canary
Confidence: high
Scope-risk: narrow
Directive: Keep percentage validation aligned with docs and schema comments
Tested: go test ./pkg/deployer -run 'TestValidateCanaryDeployment|TestCanaryWeights'
Tested: go test -count=1 ./pkg/... ./cmd/... ./hack/...
Tested: make linters
Related: #191

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
pkg/aws/elbv2.go (1)

191-203: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Remove the unreachable fallback. DescribeLoadBalancers with Names raises LoadBalancerNotFoundException for a missing load balancer, so the len(result.LoadBalancers) == 0 branch won’t handle that case. Remove it or map that error here if you want nil, nil to mean not found.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/aws/elbv2.go` around lines 191 - 203, In GetLoadBalancerByName on
ELBV2Client, the empty result fallback is unreachable because
DescribeLoadBalancers with Names returns an error for a missing load balancer.
Remove the len(result.LoadBalancers) == 0 branch, or explicitly handle
LoadBalancerNotFoundException in this method if you want not-found to return
nil, nil; keep the main error path in e.Client.DescribeLoadBalancers as the
source of truth.
docs/content/en/docs/Concepts/canary.md (1)

19-19: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor hyphenation nits. Use "rule-based" (Line 19) and "API test-based" (Line 50) as compound modifiers.

Also applies to: 50-50

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/content/en/docs/Concepts/canary.md` at line 19, The doc text has
compound-modifier hyphenation issues in the canary content. Update the phrasing
in the canary description to use “rule-based” in the listener routing sentence,
and also adjust the later “API test-based” wording plus the “50-50” phrasing as
needed for consistent compound-modifier style. Use the canary markdown content
as the source and revise the affected sentences directly.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/deployer/canary.go`:
- Around line 311-312: The canary weight handling is ambiguous because
`Validate` allows `regionConfig.Canary.Weight` to be 0 while `CanaryWeights`
later interprets 0 as “unset” and falls back to a 10% canary. Update the
`CanaryWeights`/canary config flow so `weight: 0` is either explicitly preserved
via a nullable/pointer field or clearly rejected/documented as unsupported; make
the validation and weight-selection logic consistent, and apply the same fix in
the other referenced canary-weight paths so zero is never silently converted
into default canary traffic.

---

Nitpick comments:
In `@docs/content/en/docs/Concepts/canary.md`:
- Line 19: The doc text has compound-modifier hyphenation issues in the canary
content. Update the phrasing in the canary description to use “rule-based” in
the listener routing sentence, and also adjust the later “API test-based”
wording plus the “50-50” phrasing as needed for consistent compound-modifier
style. Use the canary markdown content as the source and revise the affected
sentences directly.

In `@pkg/aws/elbv2.go`:
- Around line 191-203: In GetLoadBalancerByName on ELBV2Client, the empty result
fallback is unreachable because DescribeLoadBalancers with Names returns an
error for a missing load balancer. Remove the len(result.LoadBalancers) == 0
branch, or explicitly handle LoadBalancerNotFoundException in this method if you
want not-found to return nil, nil; keep the main error path in
e.Client.DescribeLoadBalancers as the source of truth.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d41f700d-99c5-416c-bbc5-5eb78de4bf02

📥 Commits

Reviewing files that changed from the base of the PR and between 18ab1bb and c1fe1c7.

📒 Files selected for processing (13)
  • docs/content/en/docs/Concepts/canary.md
  • docs/content/en/schemas/schema.json
  • docs/content/ko/docs/Concepts/canary.md
  • examples/manifests/canary-example.yaml
  • examples/manifests/demoapp-xyzd-canary.yaml
  • examples/manifests/demoapp-xyzd-stable.yaml
  • examples/scripts/demoapp-userdata-canary.sh
  • examples/scripts/demoapp-userdata.sh
  • pkg/aws/elbv2.go
  • pkg/aws/elbv2_test.go
  • pkg/deployer/canary.go
  • pkg/deployer/canary_test.go
  • pkg/schemas/config.go

Comment thread pkg/deployer/canary.go Outdated
* Rollback failed canaries before promotion

Health check failures previously returned from the canary deployer only as a local error path, while the runner did not stop the deployment after health errors. The canary rollback now restores listener traffic, removes canary-only target group resources, scales the failed canary ASG down, waits for termination, and deletes the ASG and launch template before returning the original health failure.

The live rollback test also exposed that the stable ASG is stored in LatestAsg while the newly created canary ASG is stored in AsgNames. Rollback and canary target group detach now select the active canary ASG first to avoid touching the stable group.

Constraint: Canary rollback must reuse the existing listener default action and target group cleanup helpers from the weighted target group implementation

Rejected: Leave failed canary ASGs scaled to zero | operators would still need manual cleanup after every failed canary

Rejected: Roll back using LatestAsg only | live AWS test showed this targets the stable ASG during initial canary deploy

Confidence: high

Scope-risk: moderate

Directive: Do not replace AsgNames with LatestAsg in rollback paths without re-running an unhealthy canary against a real or mocked ASG/TG lifecycle

Tested: env -u GOROOT go test -count=1 ./pkg/... ./cmd/... ./hack/...

Tested: env -u GOROOT make linters

Tested: AWS ap-northeast-2 unhealthy canary deploy returned GOPLOYER_EXIT:1, restored listener to stable TG, left v001 1/1/1 healthy, deleted canary TG, and removed v002 launch templates

Not-tested: CloudWatch alarm based post-deploy rollback, which is not implemented by this change

* Address canary rollback review comments
@YoungJinJung
YoungJinJung merged commit 1dab322 into main Jun 29, 2026
0 of 2 checks passed
@YoungJinJung
YoungJinJung deleted the codex/weighted-canary-bake-time branch June 29, 2026 01:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement weighted target group canary deployment

1 participant