AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a comprehensive, three-phase framework for migrating self-managed Apache Kafka clusters to Amazon MSK Express. It guides users through inventory discovery, compatibility assessment, and performance simulation to ensure a smooth transition. By analyzing existing infrastructure, the skill identifies potential configuration gaps, calculates target sizing, and provides cost projections, while also offering support for data replication using MSK Replicator.
What it does
- Inventories source Kafka clusters from IaC files, CLI output, or manual input.
- Assesses compatibility across topology, Kafka versions, configurations, auth, and quotas.
- Generates target MSK Express specifications, including broker counts and cost estimates.
- Facilitates optional load-testing via temporary MSK Express trial clusters.
- Provides guidance on using MSK Replicator for migration cutovers.
- Produces standardized artifacts for cluster configuration and compatibility findings.
When to use it
- When planning a migration from self-managed Kafka to Amazon MSK Express.
- When you need to assess the compatibility of your current Kafka setup with MSK Express.
- When you require an accurate sizing and cost projection for an MSK Express target.
- When you want to validate performance through a proof-of-concept load test before migrating.
- When you need a structured approach to inventorying your existing streaming infrastructure.
Advantages
- Reduces migration risk through automated compatibility and topology assessment.
- Provides data-driven cost and sizing recommendations based on actual workload metrics.
- Supports informed decision-making with optional pre-migration performance simulation.
- Standardizes the migration process across discovery, assessment, and execution phases.
- Integrates with existing IaC workflows for efficient inventory collection.
Usage examples
- “I need to migrate my on-premises Kafka cluster to MSK Express. Where do I start?”
- The agent will explain the three-phase migration process (Discovery, Assessment, Simulation) and ask for your infrastructure details or IaC files to begin the discovery phase.
- “Here is my Terraform directory for our Kafka cluster. Can you assess if it is compatible with MSK Express?”
- The agent will perform the discovery phase to generate a cluster configuration and then proceed to the assessment phase to validate compatibility and provide sizing recommendations.
- “I have a cluster-config.json file. How do I get a cost estimate for moving to MSK Express?”
- The agent will run the assessment phase using your configuration file to produce a compatibility report and a pricing summary for the recommended MSK Express setup.
- “How do I test MSK Express performance with my current workload?”
- The agent will guide you through the simulation phase, helping you deploy a temporary MSK Express cluster to run load tests and review performance metrics.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/aws/migrate-to-msk/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip migrate-to-msk.zip -d .agents/skills
Documentation by AWS
Migrating to MSK Express
Overview
This skill helps customers migrate self-managed Apache Kafka workloads to Amazon MSK Express. It provides three phases — Discovery, Assessment, and an optional Simulation — that can be run end-to-end or individually depending on the customer's needs.
Scope
This skill covers migrations from self-managed Apache Kafka (on-premises, EC2, Docker, Kubernetes, or other non-MSK deployments) to MSK Express. Migrations from MSK Standard (Provisioned) to MSK Express are out of scope.
Prerequisites
The AWS MCP server is recommended for documentation lookups and informational questions, but is not required. The assessment scripts are pure file processors with no AWS API calls.
Intent Routing
Route the customer's request based on their intent:
1. Open/exploratory question ("How do I migrate to MSK?")
Explain what this skill offers:
This skill helps you migrate to MSK Express in three phases:
Phase 1 — Discovery: Inventory your source Kafka cluster — brokers, topics, partition counts, configs, authentication, and workload metrics — plus two target decisions that drive cost: consumer rack affinity and any negotiated AWS pricing. I can discover this from IaC files (Terraform, CDK, Docker Compose, Kubernetes manifests), provide commands for you to run on your cluster, or you can provide the information manually. Output:
migrate-to-msk-skill-artifacts/<cluster_name>/cluster-config.json.Phase 2 — Assessment: Validate your cluster against MSK Express across 5 compatibility pillars (topology, Kafka version, configs, auth, quotas) and produce a target Express specification using the managing-amazon-msk Skill's pricing logic. I'll flag what Express will refuse vs what Express will silently convert. Outputs:
compatibility.<cluster_name>.json, the pricing results in Markdownmsk_sizing_pricing.md, andmsk-sizing-inputs.<cluster_name>.json.Phase 3 — Simulation: Spin up an MSK Express cluster with load-testing infrastructure to see how Express performs on your own workload, then run a vended test (End-to-End Latency or Broker Restart Under Load) and review the results on a CloudWatch dashboard.
Data replication: For migrating data to your Express cluster, you can use MSK Replicator. I can provide guidance on setup and configuration.
Where would you like to start? I can begin with discovery if you point me to your infrastructure code or describe your cluster, or jump to assessment if you already have a
cluster-config.jsonfile, or go straight to simulation if you already know your target Express configuration.
Guardrails for this overview response:
This response is an overview and a routing question only. Do NOT begin, simulate, or pre-empt any phase.
Do NOT produce or estimate assessment output here — no verdicts, pillar findings, compatibility conclusions, broker counts, instance recommendations, or cost figures. Those values exist only after you run the Phase 2 scripts against a real
cluster-config.json.Do NOT open, read, or summarize the internals of
compatibility.py,simulation_load_test_config.py, or the reference files to explain how a phase works. Describe the phases at the level shown above; do not walk the customer through the implementation.When the customer chooses a phase, run that phase's scripts or flow to produce real results. Always operate the skill to answer — never answer from having read its source. For the exact commands, see "Running the assessment" in references/assessment-compatibility.md for Phase 2, and references/simulation.md for Phase 3.
2. Discovery intent (DEFAULT when IaC files are provided)
If the customer provides a directory path, IaC files, or says "here's our infra" —
this is discovery intent. Run ONLY Phase 1 (Discovery). Do NOT run assessment,
do NOT suggest migration steps, do NOT mention blockers or compatibility.
Produce the migrate-to-msk-skill-artifacts/<cluster_name>/cluster-config.json file and stop.
3. Assessment intent
Customer explicitly asks to assess or has a migrate-to-msk-skill-artifacts/<cluster_name>/cluster-config.json file
already produced. Run Phase 2 (Assessment) only.
4. Simulation intent
Customer wants to test MSK Express with their workload. They can provide cluster sizing directly (instance type, broker count, Kafka version) or reference an earlier assessment. Proceed directly to Phase 3 — Simulation. An assessment is helpful but not required — the simulation asks for sizing inputs directly.
5. Informational questions
Customer asks about Express capabilities, constraints, configuration differences,
authentication support, pricing, or compaction behavior without providing
cluster-specific data. Use AWS documentation tools (aws___search_documentation,
aws___read_documentation) if available to look up the answer from MSK Express
documentation. If MCP tools are not available, reference the
MSK Express documentation
and answer based on knowledge of AWS MSK.
6. Migration strategy questions
Customer asks about MSK Replicator compatibility, version upgrade paths, MirrorMaker 2,
or migration strategies. MSK Replicator is the native AWS-supported solution for data
replication and works for both MSK-to-MSK and non-MSK-to-MSK migrations. Use AWS
documentation tools (aws___search_documentation, aws___read_documentation) if
available to retrieve current requirements and supported configurations. If MCP tools
are not available, reference the
MSK Replicator documentation
and answer based on knowledge of AWS MSK.
Phase 1 — Discovery
Purpose: Inventory the source cluster to build a migration profile.
Input: One of:
A directory path containing IaC files (CDK, CloudFormation, Docker Compose, Kubernetes manifests, Terraform)
Output from Kafka CLI commands the customer runs on their cluster
Manual information provided by the customer in conversation
Output: migrate-to-msk-skill-artifacts/<cluster_name>/cluster-config.json — saved to the working directory.
Discovery rules
Before doing ANYTHING else in discovery, you MUST read references/discovery.md in full. It defines the input methods, the REQUIRED response template, the forbidden content, and the
cluster-config.jsonschema. Do NOT respond until you have read it, and follow its template EXACTLY.ALWAYS save
migrate-to-msk-skill-artifacts/<cluster_name>/cluster-config.jsonin the working directory.Do NOT proceed to Phase 2 without explicit customer confirmation.
Phase 2 — Assessment
Purpose: Assess the cluster against MSK Express requirements and produce a target Express specification (instance type, broker count, monthly cost projection).
Input: migrate-to-msk-skill-artifacts/<cluster_name>/cluster-config.json from Phase 1.
Outputs:
migrate-to-msk-skill-artifacts/<cluster_name>/compatibility.<cluster_name>.json— five-pillar verdict.migrate-to-msk-skill-artifacts/<cluster_name>/msk_sizing_pricing.md— the managing-amazon-msk Skill's pricing report with broker count and cost recommendations.migrate-to-msk-skill-artifacts/<cluster_name>/msk-sizing-inputs.<cluster_name>.json— a record of the six input values for sizing logic.
Assessment has two independent halves; run them in either order, and a failure in one does not block the other:
Compatibility —
scripts/compatibility.py, a pure file processor (no live AWS API calls) run viauv runwith PEP 723 inline dependencies. It validates the source across five pillars — topology, Kafka version, configs, auth, and quotas — and emits one verdict per pillar (INFO,ADVISORY, orACTION_REQUIRED, worst-of for the overall). Use those three strings verbatim.Sizing — not a script in this skill. Load the managing-amazon-msk Skill and run its
scripts/msk_sizing.pywith the workload inputs derived fromcluster-config.json, passing--broker-classes expressand--regionfor the target Region. The script prices from the live AWS Pricing API when credentials are available and from an embeddedus-east-1snapshot when they are not.
Assessment rules
Read references/assessment-compatibility.md before responding. It carries the invocation commands, the per-pillar thresholds and evidence codes, the verdict definitions, the full forbidden-behavior list, and the required response template covering both artifacts. Do not freestyle the post-script summary.
Read references/assessment-sizing.md before running sizing. It carries the input derivations (several are not one-to-one — getting them wrong silently produces a wrong broker count), the
target-block flags for rack affinity and negotiated discounts, the Region and pricing-source handling, the Express-only presentation rule, and the source-footprint comparison.Surface any
ACTION_REQUIREDevidence to the user for awareness, but do not gate further phases on it. Express may still accept the workload with mitigations.Do NOT pivot back into discovery. Assessment operates on the existing
cluster-config.jsonas-is. Partial data is fine — the scripts emit ADVISORY evidence (METRICS_MISSING,AZ_COUNT_UNKNOWN, etc.) for missing fields; surface those findings and stop. Do not propose Kafka CLI commands, IaC walks, scripts, or questionnaires to fill the gaps.Report broker counts and costs only as read verbatim from the sizing script output. Never round, re-derive, or estimate them. Report the
pricing sourcethe script used alongside the costs; when it isstatic, the rates are an embedded snapshot, not confirmed current pricing.
Phase 3 — Simulation (optional)
Deploy a temporary, isolated MSK Express cluster and client fleet in the customer's account so they can see how Express performs on their own workload, then run one of two vended tests (End-to-End Latency, Broker Restart Under Load) and hand over a CloudWatch dashboard. Follow the 12-step conversational flow and all deploy, sizing, and guardrail details in references/simulation.md; the deterministic artifacts it drives are scripts/simulation_load_test_config.py and the static assets/simulation-stack.yaml.
Execution model
Scripts run on the customer's local machine via uv run. They declare their own
dependencies (PEP 723) and are pure file processors — no AWS API calls, no
network access, and no third-party dependencies (standard library only).
Security Considerations
Apply these controls at every phase. For additional detail, see MSK Security best practices and MSK IAM access control.
Encryption in transit (mandatory). Enforce TLS for client-broker traffic on the MSK Express target (
EncryptionInTransit.ClientBroker = TLS).Encryption at rest (mandatory). Provision the target cluster with a customer-managed KMS key (or AWS-managed if your compliance posture allows).
Authentication — prefer IAM over long-lived credentials. Configure the MSK Express target with IAM authentication as the sole client auth method. This gives ephemeral, role-based credentials with full CloudTrail coverage.
Credential storage — use AWS Secrets Manager. Store SASL/SCRAM and TLS credentials for source cluster access in Secrets Manager. Never pass passwords as CLI arguments.
Network isolation. Deploy MSK clusters in private subnets. Use security groups scoped to specific CIDR ranges or security group references. Do NOT use 0.0.0.0/0 ingress rules.
CloudTrail logging and CloudWatch alarms. Ensure CloudTrail is enabled in the target account and covers
kafka.amazonaws.comAPI calls. Configure alarms:ClientAuthenticationFailure— surge indicates credential problems or attackConnectionCloseCount— abnormal spike may indicate connection-floodingCloudTrail metric filters for denied
kafka-cluster:*actionsConnection-rate alarms approaching the 100 conn/sec/broker IAM limit
Sensitive data handling. Discovery and assessment outputs contain broker addresses, auth hints, and broker config values. Treat these as sensitive — do not paste into public channels or ticketing systems without redaction.
Troubleshooting
Per-pillar findings, including the source topology and out-of-range config cases, are explained in references/assessment-compatibility.md.
Files
- ATTRIBUTION.mdAdded by AutoGineer908 B
- LICENSELicence file added by AutoGineer9.9 KiB
- NOTICELicence file added by AutoGineer67 B
- SKILL.md14.0 KiB
- assets/simulation-stack.yaml47.9 KiB
- references/assessment-compatibility.md23.1 KiB
- references/assessment-sizing.md14.8 KiB
- references/discovery.md14.8 KiB
- references/simulation.md38.6 KiB
- scripts/compatibility.pyScript46.7 KiB
- scripts/simulation_load_test_config.pyScript17.7 KiB
references/simulation.md
Phase 3 — MSK Express Load-Test Simulation
Deploy a complete MSK Express load-testing environment in the customer's AWS account so they can validate Express performance before cutover. The simulation is optional and is the final phase of the skill. Declining at any decision point ends the skill — there is no subsequent phase.
You orchestrate the conversation and run AWS CLI commands (CloudFormation, SSM, EC2). The AWS MCP server is recommended for executing these commands but is not required — the skill works with plain AWS CLI as well. Two deterministic artifacts do the rest — never hand-compute or hand-edit them:
scripts/simulation_load_test_config.py— sizing math, template rendering, and guardrail validation (compute,render, andvalidateactions).assets/simulation-stack.yaml— a static parameterized CloudFormation template. It is the internal source: never edit it, and never hand the customer its install path. There are two deploy paths:You deploy it (customer consents): deploy in one
create-stackstraight from this source template, passing the compute-derived--parameters. No render.Customer deploys it themselves (self-guided):
renderreads this template internally and writes a filled local copy (the customer's sizing and the computed fleet baked in as parameterDefaults) to the customer's working directory; the customer deploys from that local copy with no--parameters. To resize, redeploy with new parameters (or re-render, on the self-guided path).
Never expose the skill source path
The customer must never see, depend on, or be told to cd into the skill install
directory. You (the agent) run the scripts and — on the consented path — the
create-stack from wherever the skill lives, but nothing you hand the customer
(a file to keep, a self-guided command, teardown) may name a skill-package path
(anything under the skill directory — assets/…, scripts/…, or the symlinked
~/.claude/skills/…). On the self-guided path, the only template path the customer
sees is the local rendered artifact in their working directory (the absolute
output_path that render reports). The customer should never see or depend on
where the skill is installed.
Contents
Flow
Confirm the target account
Single simulation per account
Constrained choices
Communicating with the customer
Sizing inputs
Install the Kafka client
Env computation
Render the local template
Throughput & fleet reference
Deploy
Test templates
Guardrails
Triggering a test
Teardown
Off-trail handling
Flow
Introduce the simulation and what it does; ask consent. No → end the skill. Mention that only one simulation can exist per account at a time (to avoid duplicate test costs).
Gather the target AWS region. Then confirm the AWS account (see Confirm the target account): run
aws sts get-caller-identity, show the account id + role, and ask the customer to confirm it is the account they intend to deploy into. Do not run any create/deploy/delete action until the customer confirms the account. If they say it is the wrong account, stop and let them switch credentials first.Check for an existing simulation (see Single simulation per account). Only one may exist per account/region. If one already exists, tell the customer and ask whether to reuse it (present its dashboard, then verify the Kafka client before tests — see below) or delete and redeploy (tear it down and wait, then continue). If none exists, continue.
On reuse, a simulation that is
CREATE_COMPLETEdoes not guarantee a working Kafka client — the client is installed as a post-deploy step, so a reused simulation may have an empty/opt/kafka(e.g. install never ran, or ran against a different client). Before offering tests, runValidateClient(see Install the Kafka client). If it passes, go to the test options at step 8. If it fails with a "client not installed/working" (or version) error, run the install + validate loop (have the customer install a Kafka client themselves — akafkaUrl+kafkaSha512they provide; you may point them to Apache in words but don't source the URL or run it yourself — then re-runValidateClientuntil it passes) — then go to step 8. Do not present test options untilValidateClientpasses.
Ask the customer for cluster sizing (see Sizing inputs) —
instance_type,broker_count, andkafka_version+ mode. Offer only supported choices (see Constrained choices). Mention once, in passing, that the skill's assessment phase (Phase 2) inventories their source cluster and produces a sizing/pricing workbook to help pick the right Express size — so if they have not run it and want a data-driven recommendation, they can run the assessment first. Do not block on it; proceed with whatever sizing they give.Run
simulation_load_test_config.py compute→ derive fleet + CloudFormation parameters. Do not render yet — render is only for the self-guided path below. (The stack downloads no Kafka client; the client is installed after deploy — see Install the Kafka client.)Present the stack resource list and inform there will be extra cost and it takes ~1 hour. Ask the customer to confirm deployment.
Yes (you deploy) → go to step 7. Deploy in one
create-stackcommand from the static source template, passing the compute-derived values as--parameters(see Deploy). Do not render a local template on this path — passing parameters to the source template is the single deploy step.No (self-guided) → now run
simulation_load_test_config.py render(see Render the local template) to write a filled, ready-to-deploy template to the customer's working directory, then hand them the self-guidedcreate-stackcommand that deploys from that local file (no--parametersneeded — sizing is baked in) plus the install/validate/test commands. End the skill. Rendering exists so the customer who deploys themselves gets a single self-contained file rather than a long parameterized command pointing at a path they don't control.
Deploy the stack with one
create-stack(source template +--parametersfrom compute); wait forCREATE_COMPLETE. Then install and validate the Kafka client (see Install the Kafka client): install the Kafka client on the fleet (thekafkaUrlcomes from the customer — you don't source the URL), then runValidateClientand loop until it passes. Then create the topics via theCreateTopicsCommandoutput. Once that is done, present the CloudWatch dashboard to the customer once — both theDashboardUrland theDashboardArnstack outputs — and tell them this is where every test's metrics will appear.Present the two test options (below). Before the options, state the deployed cluster's ingress capacity so the customer can pick a sensible throughput: its sustained ingress (the recommended ceiling, no degradation up to here) and its maximum ingress (the hard quota — MSK throttles beyond it). Both come from the
computeoutput (cluster_sustained_ingress_mbpsandcluster_max_ingress_mbps); never hand-compute them.Fill the chosen template with computed defaults + bounds; ask for edits.
Run
simulation_load_test_config.py validate. REJECT → show errors, loop back to 9. PASS → surface any warnings as one sentence each and continue.Trigger the test via SSM and confirm it started, and name the metric(s) it emits. The dashboard was already shared at deploy — point the customer back to it rather than re-posting the URL each run. Tell the customer that after the producers stop, the consumers keep reading and adaptively drain until the consumer-lag metric returns to ~0 (capped at
maxDrainMinutes, default 20) — so the run takes a little longer thanduration_minutesto fully settle, and that trailing drain is expected.Ask the customer which of these they want next:
Keep the stack running and finish — leave the simulation in place and end the skill. Remind them it keeps incurring cost until they tear it down, and give them the teardown command for later.
Run another test → go to 8.
Tear down the stack → Teardown, then end the skill.
Confirm the target account
Before the first AWS action that creates, deploys, modifies, or deletes anything, confirm you are operating in the account the customer intends:
aws sts get-caller-identity --query '{Account:Account,Arn:Arn}' --output jsonShow the customer the account id and role/identity, and ask them to confirm it is the correct account for this simulation. Do not proceed past this point until they explicitly confirm. If it is the wrong account, stop — ask them to switch credentials (profile / refreshed session) and re-run the check. Re-confirm whenever credentials are refreshed mid-session, since the identity may have changed.
Single simulation per account
To avoid paying for duplicate load-test infrastructure, only one simulation may exist
per account/region at a time. The stack name is fixed (msk-express-simulation), so a
second create-stack fails with AlreadyExistsException. Always check before
deploying, and tell the customer up front that only one simulation is allowed at a time:
aws cloudformation describe-stacks --stack-name msk-express-simulation \
--region <region> --query 'Stacks[0].StackStatus' --output text 2>/dev/nullNo stack (command errors / empty) — proceed with a fresh deployment.
CREATE_COMPLETE— a healthy simulation already exists. Inform the customer (and that only one is allowed), then ask which they want:Reuse it — read
DashboardUrl+DashboardArn(andFleetSummary) fromdescribe-stacks --query 'Stacks[0].Outputs'and present the dashboard. No new deploy, no new cost. Then verify the Kafka client before offering tests: aCREATE_COMPLETEstack does not imply an installed client (it is a post-deploy step and may never have run, or ran against a different client), so runValidateClient(see Install the Kafka client). If it passes, go to the test options. If it fails, run the install + validate loop (InstallKafkaClientwith a Kafka client URL, thenValidateClientuntil it passes) first. Do not offer tests until it passes.Delete and redeploy —
delete-stack+wait stack-delete-complete, then continue with sizing + deploy.
Any other state (
ROLLBACK_COMPLETE,*_FAILED,*_IN_PROGRESS) — it cannot be reused. If it is settled in a failed/rollback state, delete and redeploy; if an operation is still in progress, wait for it to finish before deciding.
Constrained choices
When asking the customer to choose cluster parameters, offer only the supported
values as an explicit list. The question UI always appends its own "Other"
free-form option that cannot be suppressed — so do not rely on the prompt to
constrain input. Instead, treat any free-form value as untrusted and run it
through simulation_load_test_config.py compute (which rejects unsupported instance_type,
broker_count, and kafka_version locally) before deploying. Never deploy a
value the script rejected — an unsupported value otherwise only fails later at
create-stack.
Picker cap. The AskUserQuestion tool hard-limits each question to 4
options (plus the auto-appended "Other") and silently drops the rest. There are
7 supported instance_type values, more than the picker can show. You may
still use the picker, but the question text MUST enumerate all 7 instance
types (with their specs) so the customer can see every option even when some are
not rendered as buttons — instruct them to use "Other" to pick a type that is
not shown as a button. Never present instance_type in a way that names fewer
than all 7 types. broker_count and kafka_version have ≤ 4 options each and
fit the picker directly.
instance_type — exactly these Express types (no others), always present all seven:
express.m7g.large,express.m7g.xlarge,express.m7g.2xlarge,express.m7g.4xlarge,express.m7g.8xlarge,express.m7g.12xlarge,express.m7g.16xlarge.broker_count — a multiple of 3, ≥ 3 (Express is always a 3-AZ topology).
kafka_version + metadata mode — present as one combined list (mode is implied by the choice). Express supports only 3.6, 3.8, 3.9. On Express 3.9 is KRaft-only and KRaft is not available below 3.9, so the only valid combos are these three:
Choice Mode CloudFormation KafkaVersion3.9 (KRaft) — recommended KRaft 3.9.x.kraft3.8 (ZooKeeper) ZooKeeper 3.8.x3.6 (ZooKeeper) ZooKeeper 3.6.0Do not offer 3.9 in ZooKeeper mode — it is not a supported Express combination and only fails later at
create-stack. KRaft is not available below 3.9; 3.7 and 4.0 are not Express versions at all. Do not offer any of these.
Treat the official AWS MSK Express documentation as the source of truth for the
supported instance types and versions. If you can access AWS documentation, verify
these values against it — especially if a value here is rejected at create-stack.
If that documentation is not accessible, this list is a trustworthy source to use.
Communicating with the customer
Speak to the customer in plain, outward-facing language. Do not expose internal or process framing — no "per the v1 flow", "the skill says", "I won't interpret results because…", version numbers of this workflow, step numbers, or references to these instructions. Just do the right thing.
Frame the simulation as a way to see MSK Express perform on the customer's own workload — a confidence-building demonstration, not a safeguard. MSK Express is production-ready; present the simulation as confirming expected performance on their specific workload, and avoid phrasing that implies doubt about Express (e.g. "just to be safe before committing" or "in case Express can't keep up"). Let the results speak for themselves.
When presenting test results: point the customer to the dashboard already shared at deploy and name the metric(s) the test emits, and let them read it. If they ask what the numbers mean, you may describe what each metric represents, but do not assert a pass/fail verdict — phrase it as something they evaluate against their own targets.
When you preview or list the upcoming steps, keep each to a short phrase (e.g. Install the Kafka client on the fleet) — do not inline the install options or other details; cover those at the step itself.
Sizing inputs
Always ask the customer for the sizing directly — instance_type, broker_count,
and a kafka_version + metadata mode — offering only the supported values listed
in Constrained choices. Do not look for a local
assessment file; the assessment now hands the customer a sizing/pricing workbook
rather than writing a JSON artifact this skill can read.
When you ask, briefly note that the skill's assessment phase (Phase 2) inventories their source cluster and produces that sizing/pricing workbook, so if they have not run it and want a data-driven Express size recommendation, they can run the assessment first. Keep it to a sentence and do not block on it — if they already have a size in mind (from the workbook or otherwise), take it and proceed.
Validate: instance_type must be one of the supported Express types; broker_count
≥ 3 and a multiple of 3; the version/mode must be one of the three supported
combinations. Map the customer's version + mode choice to the exact CloudFormation
KafkaVersion string from the table. 3.9
is always KRaft on Express (3.9.x.kraft); reject a request for 3.9 in ZooKeeper
mode.
Install the Kafka client
The Kafka client is installed after the stack is created, then validated before any
test. At CREATE_COMPLETE the fleet has Java, an empty /opt/kafka, and a
client.properties with the IAM/TLS settings Express requires; this step installs
the client itself. Two SSM documents handle it:
InstallKafkaClient— installs the Kafka client fromkafkaUrl(passed at invocation, verified against the customer-providedkafkaSha512), plus theaws-msk-iam-authjar (required for IAM auth) from its AWS-owned source (installed automatically — no parameter).ValidateClient— confirms the client runs, its version is>=the cluster's Kafka version, and it can authenticate and list topics on the cluster.
Step 1 — install
Install the Kafka client on the fleet. The tarball URL is the runtime parameter
kafkaUrl, and it comes from the customer — they pick a source they trust and
give you the URL; you plug it in. Help in words: the Apache Software Foundation
publishes Kafka — point them to a source they trust (their org's approved software
channel, their package manager, or the official Apache distribution on an
apache.org domain) and away from third-party mirrors or search-result links. Do
not fabricate, guess, or web-search a URL and present it as authoritative — you may
fill in a URL the customer provides. The customer also gives the tarball's
SHA-512 (kafkaSha512); the install verifies it before use (copy the value Apache
publishes, lowercased/no spaces, or compute sha512sum <file>.tgz). The
aws-msk-iam-auth jar installs automatically from its AWS-owned source (no parameter).
If the customer asks you to pick the URL or run the install for them, don't flatly refuse — briefly explain you can't vouch for a specific link on their behalf, then help: confirm the version, name Apache as the publisher, point to an official channel by description, and show them how to get the checksum.
Pre-flight before invoking: confirm the customer-provided kafkaUrl starts with
https:// (reject and ask for an HTTPS URL otherwise) — catch this before the
send-command, not as a failed run. The install document re-checks the scheme as a
backstop (e.g. for the self-guided path where the customer runs the command directly).
# Single-quote --parameters: a URL with a ?/& query string would otherwise be
# glob-expanded by the shell (e.g. zsh: "no matches found").
aws ssm send-command --document-name msk-express-simulation-InstallKafkaClient \
--targets Key=tag:simulation:role,Values=producer,consumer \
--parameters 'kafkaUrl=<kafka-client-tarball-url>,kafkaSha512=<sha512>' \
--region <region>Wait for it to succeed, then validate.
Step 2 — validate
After install, run ValidateClient and loop until it passes:
aws ssm send-command --document-name msk-express-simulation-ValidateClient \
--targets Key=tag:simulation:role,Values=producer,consumer --region <region>PASS → proceed to topic creation + tests.
FAIL → show the error (version too low, or cannot authenticate/connect). Help the customer fix or re-install the Kafka client by re-running the install command with a different
kafkaUrl+kafkaSha512they provide. Re-runValidateClientuntil it passes.
The test documents also carry a lightweight client check, so a test never runs against a fleet with no client.
If a test hits a client-related error
If a test fails with a client-related error, the cause may be the Kafka client that
was installed. Have the customer re-install with a different client (a kafkaUrl +
kafkaSha512 they provide — you don't source the URL) and re-run the test.
Env computation
uv run scripts/simulation_load_test_config.py compute --instance-type <type> --broker-count <N> --kafka-version <ver>Always pass --kafka-version with the CloudFormation KafkaVersion string you intend to
deploy. The question UI always appends an "Other" free-form option to every
choice prompt, which a customer can use to enter an unsupported instance_type,
broker_count, or version. compute rejects all three locally (non-zero exit
with an error message) so a free-form value fails here in seconds rather than
~40 min into create-stack. On rejection, re-present the supported choices
(supported_kafka_versions is in the output) and do not deploy.
Returns cluster capacity, the auto-sized client fleet, and each test's defaults/bounds. Map the output to CloudFormation parameters:
| CloudFormation parameter | From compute output |
|---|---|
| InstanceType / BrokerCount / KafkaVersion | sizing inputs |
| ClientInstanceType | fleet_instance_type |
| ProducerCount | fleet_producer_count |
| ConsumerCount | fleet_consumer_count |
The fleet is sized to ~1.5× the cluster's maximum ingress quota (its throttle ceiling), so it can drive the cluster all the way to its limit and is never the bottleneck. The customer cannot change fleet size; to change cluster sizing, redeploy.
Render the local template
Self-guided path only. Render exclusively when the customer declines to let
you deploy (flow step 6, "No"). If they consent, skip render entirely and deploy in
one create-stack with --parameters (see Deploy).
On the self-guided path, render a local, ready-to-deploy copy of the template with the
sizing and computed fleet baked in as parameter Defaults. This is what the
customer downloads and deploys from — never the skill's source template.
Pass --output as an absolute path under the customer's working directory (not
the skill directory). render resolves whatever you pass to an absolute path —
a relative path resolves against the current working directory, which is ambiguous
and could land the file inside the skill package — so anchor it explicitly with
$(pwd):
uv run scripts/simulation_load_test_config.py render \
--instance-type <type> --broker-count <N> --kafka-version <ver> \
--output "$(pwd)/migrate-to-msk-skill-artifacts/simulation/simulation-stack.yaml"After the customer deploys the rendered template, they install + validate the Kafka
client exactly like the consented path (via the InstallKafkaClient /
ValidateClient SSM documents — see Install the Kafka client).
Nothing client-related is baked into the rendered template.
render re-runs the same sizing math as compute (so it rejects an unsupported
instance_type, broker_count, or kafka_version the same way, before writing
anything), reads the static source template internally, and bakes these parameters
as Defaults — so a plain create-stack with no --parameters deploys the
customer's exact sizing:
| Baked parameter | Value |
|---|---|
| InstanceType / BrokerCount / KafkaVersion | sizing inputs |
| ClientInstanceType | fleet_instance_type |
| ProducerCount | fleet_producer_count |
| ConsumerCount | fleet_consumer_count |
render returns the resolved absolute output_path (and a ready-to-run
deploy_command that points at it). Use that absolute path verbatim — it is the
only template path you ever show the customer. Tell them where it was saved so
they have the exact artifact being deployed. render refuses to write inside the
skill package, so if you accidentally point --output there it errors instead of
leaking the artifact into the install directory.
Throughput & fleet reference
These tables are the source of the numbers simulation_load_test_config.py computes from. They
are reference only — the script is authoritative; do not hand-compute from them. The figures
reflect published Express performance characteristics. If you can access AWS documentation, treat
the official AWS MSK Express documentation as the source of truth and validate against it; if that
documentation is not accessible, these values are a trustworthy source to use.
Express per-broker throughput throttle limits (MB/s)
sustained = recommended threshold (no degradation up to here). max = hard quota
(MSK throttles read/write traffic beyond it). Cluster totals = per-broker × broker
count. The sizing math uses ingress; egress is listed for reference (it can bind
first when many consumer groups read the stream).
| Instance | Ingress sustained | Ingress max | Egress sustained | Egress max |
|---|---|---|---|---|
| express.m7g.large | 15.6 | 23.4 | 31.2 | 58.5 |
| express.m7g.xlarge | 31.2 | 46.8 | 62.5 | 117 |
| express.m7g.2xlarge | 62.5 | 93.7 | 125 | 234.2 |
| express.m7g.4xlarge | 124.9 | 187.5 | 249.8 | 468.7 |
| express.m7g.8xlarge | 250 | 375 | 500 | 937.5 |
| express.m7g.12xlarge | 375 | 562.5 | 750 | 1406.2 |
| express.m7g.16xlarge | 500 | 750 | 1000 | 1875 |
Client fleet sizing
The fleet runs kafka-producer-perf-test / kafka-e2e-latency — network- and
CPU-bound. All clusters use a fixed fleet instance type (c5.2xlarge); only the
count scales: fleet_producer_count = max(2, ceil(cluster_max_ingress × 1.5 / 400)), and the same count of consumers.
Instance type:
c5.2xlarge(10 Gbps sustained baseline, no burst credits)Per-instance throughput target: 400 MB/s (conservative for Kafka with TLS + acks=all)
Why c5.2xlarge: stable non-burstable network; smaller c5 sizes have burstable baselines unsuitable for sustained load generation.
Deploy
There are two deploy commands depending on which path the customer chose at flow step 6. Both create the same stack; they differ only in where the template comes from and whether sizing is passed as parameters or baked in.
Consented path — you deploy (no render)
Deploy in one create-stack straight from the static source template, passing
the compute-derived values as --parameters. This is the single deploy step; do
not render on this path.
aws cloudformation create-stack --stack-name msk-express-simulation \
--template-body file://<skill>/assets/simulation-stack.yaml \
--parameters ParameterKey=InstanceType,ParameterValue=<type> \
ParameterKey=BrokerCount,ParameterValue=<N> \
ParameterKey=KafkaVersion,ParameterValue=<ver> \
ParameterKey=ClientInstanceType,ParameterValue=<fleet_instance_type> \
ParameterKey=ProducerCount,ParameterValue=<fleet_producer_count> \
ParameterKey=ConsumerCount,ParameterValue=<fleet_consumer_count> \
--capabilities CAPABILITY_IAM --region <region>
aws cloudformation wait stack-create-complete --stack-name msk-express-simulation --region <region>The stack downloads no Kafka client — install it after CREATE_COMPLETE (see
Install the Kafka client).
<skill>/assets/simulation-stack.yaml is the install path — you (the agent) use it
here, but never surface it to the customer. The customer consented to you deploying,
so they never need the template path on this path; you run the command on their
behalf.
Self-guided path — customer deploys (render first)
Only on the self-guided path. After rendering the
filled local template, hand them this command using the absolute output_path
render reported (its deploy_command field is exactly this, minus the region).
Sizing is baked into the parameter Defaults, so no --parameters are needed:
aws cloudformation create-stack --stack-name msk-express-simulation \
--template-body file://<absolute-output_path-from-render> \
--capabilities CAPABILITY_IAM --region <region>
aws cloudformation wait stack-create-complete --stack-name msk-express-simulation --region <region>On this path the customer only ever sees the local rendered artifact path, never the skill's source template path.
The fleet ASGs gate CREATE_COMPLETE on a cfn-signal from each instance's
UserData, so reaching CREATE_COMPLETE means the fleet finished bootstrapping
(Java + dirs + client.properties). The Kafka client is not installed yet —
that's the post-deploy step (Install the Kafka client).
As a belt-and-suspenders check before the first SSM command, confirm the producer
instances pass their EC2 status checks — the SSM agent reports Online before an
instance is through boot, and a command issued too early is silently Terminated
with no output.
Shell-safe instance-ID handling (do not skip).
--output textreturns instance IDs tab-separated on one line. Do not capture them in a plain variable and pass it unquoted to--instance-ids(... --instance-ids $PRODUCERS): the session shell is often zsh, which — unlike bash — does not word-split unquoted parameter expansions, so every ID is passed as a single malformed argument and the call fails withInvalidInstanceID.Malformed. Pipe the IDs throughxargsinstead, which splits on whitespace in any shell. This same rule applies anywhere you feed a list of IDs to a CLI flag (e.g. thesend-command --instance-idscalls when triggering a test) — never rely on the shell to split a captured variable.
aws ec2 describe-instances --region <region> \
--filters Name=tag:simulation:role,Values=producer Name=instance-state-name,Values=running \
--query 'Reservations[].Instances[].InstanceId' --output text \
| tr '\t' '\n' \
| xargs aws ec2 wait instance-status-ok --region <region> --instance-idsBefore creating topics, install and validate the Kafka client (see
Install the Kafka client): install the Kafka client on
the fleet (the kafkaUrl comes from the customer — you don't source the URL), then
run ValidateClient and loop until it
passes. The test documents fail fast without a working client, so install +
validation must succeed first.
Then create the test topics (cluster must be ACTIVE first) using the CreateTopicsCommand
stack output. It creates one topic per test (<base>-e2e, <base>-restart) plus the
e2e probe topic, so each test's traffic stays isolated.
Test templates
Present exactly these two. Show the Description and User Configuration
sections only. Fill defaults/bounds from the compute output for the deployed
cluster (the values below are for the 12× express.m7g.4xlarge default).
Lead in with the deployed cluster's ingress capacity so the customer can
choose a throughput with the right context — its sustained ingress
(cluster_sustained_ingress_mbps, the recommended ceiling with no degradation)
and its maximum ingress (cluster_max_ingress_mbps, the hard quota beyond
which MSK throttles). Phrase it plainly, e.g. "This cluster sustains up to
X MB/s ingress and is throttled at a hard maximum of Y MB/s — pick a test
throughput with those in mind." Take both numbers straight from the compute
output; do not hand-compute them.
1. End-to-End Latency
Description: Measures produce-to-consume round-trip latency under a steady
load. Producers drive the target throughput while one producer samples end-to-end
latency; the value is emitted as the MSKSimulation/E2ELatencyMs custom metric.
Consumers drain the topic (consumer group simulation-e2e) so the consume path is
exercised too — watch BytesOut and consumer lag on the dashboard alongside latency.
User Configuration:
| Parameter | Default | Range |
|---|---|---|
| throughput_mbps | 10 | 1 – fleet_max_mbps |
| duration_minutes | 10 | 5 – 120 |
| num_producers | 1 | 1 – fleet_producer_count |
| num_consumers | 1 | 1 – fleet_consumer_count |
2. Broker Restart Under Load
Description: Sustains target throughput, then reboots one broker mid-test to
observe failover behavior and recovery. Producers emit MSKSimulation/ProduceLatencyMs
over the window, and consumers read the topic (consumer group simulation-restart) so
the consume path is exercised too. The dashboard's ActiveControllerCount, per-cluster
BytesIn/BytesOut, and consumer lag show the dip and recovery on both sides.
User Configuration:
| Parameter | Default | Range |
|---|---|---|
| target_throughput_mbps | default_target_throughput | 1 – fleet_max_mbps |
| duration_minutes | 15 | 10 – 120 |
| reboot_at_minute | 5 | 2 – (duration_minutes − 2) |
| num_producers | fleet_producer_count | 1 – fleet_producer_count |
Guardrails
Always validate before triggering:
uv run scripts/simulation_load_test_config.py validate --test <e2e_latency|broker_restart> \
--config '<json>' --instance-type <type> --broker-count <N>Two severity levels:
REJECT (
decision: REJECT, exit 1) — value exceeds a physical fleet limit or is an invalid timing (e.g.reboot_at_minutenot inside the run). Show the error and loop back to let the customer change the value.WARN (
warnings: [...], stillPASS) — value exceeds a cluster best practice (e.g. above cluster max ingress, or too few producers to sustain the rate). Surface as one sentence at trigger time and continue.
Triggering a test
Select which fleet instances participate by passing their instance IDs to
send-command. Per-instance throughput = total ÷ number of producers targeted.
Set leadInstanceId to the first targeted producer (it samples latency /
reboots the broker). For E2E Latency, num_producers / num_consumers set how
many of each you target. For Broker Restart, target the chosen num_producers
producers and all
consumers — consumers drain the topic for read-health monitoring. Each test uses
its own load topic (E2E -> <base>-e2e, Broker Restart -> <base>-restart),
so the two tests are fully isolated -- an idle test's consumer group never
accumulates phantom lag from the other test writing to a shared topic. On top of
that, each run resets its consumer group's offset to its topic's latest at
start, so every run begins at ~0 lag (ignoring any leftover from a prior run of the
same test) and its consumer-lag / BytesOut reflect only its own traffic. After the
producers stop, the consumer keeps reading and adaptively drains: it polls the
group's lag and runs until the lag reaches ~0 (caught up) or maxDrainMinutes
(default 20) elapses, whichever first — then exits. Without this tail the consumer
would stop with the producers and MaxOffsetLag would freeze at its peak (a stale
flat line that wrongly looks like the cluster can't keep up). The adaptive drain
self-tunes to any cluster size / rate and exits early once caught up (no idle
waste); raise maxDrainMinutes only if a very large backlog needs more than 20 min.
Tell the customer at trigger time: after the producers stop, the consumers keep
reading until the dashboard's consumer-lag drains back to ~0 — so the run takes a
little longer than duration_minutes to fully settle, and that trailing drain is
expected, not a problem.
# E2E Latency (targets both roles; the doc branches on the simulation:role IMDS tag)
aws ssm send-command --document-name msk-express-simulation-RunE2ELatency \
--instance-ids <producer-ids...> <consumer-ids...> \
--parameters perInstanceThroughputMbps=<total/num_producers>,durationMinutes=<d>,leadInstanceId=<first-producer-id> \
--region <region>
# Broker Restart (targets both roles; producers load + reboot, consumers read + drain)
# After producers stop, consumers adaptively drain until lag ~0 (capped at maxDrainMinutes,
# default 20). Only pass maxDrainMinutes to override the cap; the default is usually fine.
aws ssm send-command --document-name msk-express-simulation-RunBrokerRestartTest \
--instance-ids <producer-ids...> <consumer-ids...> \
--parameters perInstanceThroughputMbps=<total/num_producers>,durationMinutes=<d>,rebootAtMinute=<m>,leadInstanceId=<first-producer-id> \
--region <region>The dashboard URL + ARN were already shared once at CREATE_COMPLETE; point the
customer back to that dashboard for results rather than re-posting it each run.
Teardown
aws cloudformation delete-stack --stack-name msk-express-simulation --region <region>Off-trail handling
Asks to change cluster sizing after deploy — not supported in place. Offer to tear down and redeploy with new sizing.
Asks for a third/custom test type — only the two vended tests are available; offer those instead.
Asks to edit the template / infra — the source template is static and the rendered local copy only carries baked sizing; decline hand-edits and offer to re-render + redeploy with different sizing instead.
Asks what the results mean — present the dashboard and explain what the metrics represent, but do not assert a pass/fail verdict; the customer evaluates the numbers against their own targets.
Asks to deploy a second/another simulation while one exists — only one simulation is allowed per account at a time; offer to reuse the existing one or delete it and redeploy (see Single simulation per account).
Asks where the Kafka client comes from — the Kafka client is installed post-deploy via
InstallKafkaClientfrom akafkaUrlthe customer provides (you may point them to Apache in words, but don't fabricate a URL). Theaws-msk-iam-authjar is installed automatically from its AWS-owned source. See Install the Kafka client.Reuses an existing simulation — a
CREATE_COMPLETEstack does not guarantee a working Kafka client (install is a post-deploy step that may never have run). After presenting the dashboard, runValidateClientbefore offering tests; if it fails, run theInstallKafkaClient+ValidateClientloop until it passes. See Install the Kafka client.A test fails with a "Kafka client not installed/working" error — install the client via
InstallKafkaClient, runValidateClientuntil it passes, then retry the test.A test hits a client-related error — it may be the installed Kafka client; have the customer re-install with a different client (a
kafkaUrl+kafkaSha512they provide — you don't source the URL) and re-run the test. See Install the Kafka client.Wants to skip the simulation — the simulation is the last phase, so end the skill.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs an MCP server, Needs Python
Security
What our scanner noticed· Low risk
- Instructions that use the network
- Scripts that your agent may run
This skill includes scripts that your agent may run. Read them before you use it.
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- AWS
- Publisher
- AWS (Official)
- Source
- aws/agent-toolkit-for-aws · plugins/aws-data-analytics/skills/migrate-to-msk · commit 0a218f8
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- NOTICE (added by AutoGineer from NOTICE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
- Also distributed in
- aws/agent-toolkit-for-aws
AutoGineer is not affiliated with or endorsed by AWS or AWS.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download migrate-to-msk.zip