---
title: Request an Instance migration
description: Move one OMENative Instance to another node, with its replacement serving before the old one drains, using the alpha kubectl ome migration start command.
since: v1.3
---
A migration moves one [Instance](../../concepts/omenative/instances.md) of an [OMENative](../../concepts/omenative/overview.md) component to another node, without a dip in serving capacity. OME starts a replacement Instance elsewhere, waits until it serves, then drains the old one, so you can empty a node for maintenance. A multi-node Instance, a leader and its workers, moves as one unit. [How a migration works](../../concepts/omenative/migration-and-transient-scale.md#surge-migration) explains each step.
!!! note "Alpha"
`kubectl ome migration start` is alpha. Its flags and behavior can change between releases.
- An [InferenceService](../../concepts/serving/inference-services.md) with a component [on OMENative](../../concepts/architecture/deployment-modes.md#opt-in-to-omenative), and an Instance of it that's `Ready` on the current revision, with no rollout or other operation in progress.
- A migration mode of `Auto`, the default, or `Surge`, as [Keep a component in place](#keep-a-component-in-place) explains.
- Room for one more whole Instance outside the node you empty, since the replacement starts before the old one drains.
- The kubectl-ome plugin, from [kubectl-ome overview and install](../../reference/kubectl-ome/overview.md).
- Permission to read the InferenceService and what it uses, and to patch it. [Required RBAC](../../reference/kubectl-ome/guarded-actions.md#required-rbac) lists the rules.
- Migration caps in the controller's settings, which the ome-resources chart sets. On a kustomize install, add them first, as [The request stays Accepted](#the-request-stays-accepted) shows.
The examples move Instance 3 of `chat`'s engine off the node `node-a`, in the namespace `prod`. [Keep a component in place](#keep-a-component-in-place) shows `chat`, whose engine runs Instances 0 to 3. To list the Instances on a node, run `kubectl get pods -n prod -l ome.io/inferenceservice=chat --field-selector spec.nodeName=node-a -L component,ome.io/instance-index`.
## Step 1: Preview the request
A client dry run runs every check and shows the request, without sending it:
```bash
kubectl ome migration start chat -n prod --component engine --instance 3 --reason "node-a maintenance" --dry-run client --yes
```
The preview, on stderr:
```output
ALPHA guarded migration preview (not controller convergence)
FIELD VALUE
Action migration start
Context prod-us-east
Workload NS prod
OME NS ome
Target InferenceService/chat
UID 5f0c8a52-3b1e-4c8d-9f27-6a1d0e4b7c93
ResourceVersion 48213
Dry-run client
Request UUID 6ff01fa3-2a47-4725-9c85-786ed59ac166
Schema v1
Component engine
Instance 3
From node node-a
Hint nodes
Reason "node-a maintenance"
Requested at 2026-09-28T10:12:04Z
Requested by kubectl-ome
Lookup only false
Source IR chat-engine
IR UID 0b6e2f41-9d3c-4a57-8e12-c4f5a6b7d890
IR version 48190
Migration mode Auto
Running revision chat-engine-5d8f7c9b
Incarnation 1
Current nodes node-a
Node hints are soft preferences; capacity, scheduling and controller convergence
are not guaranteed.
Parent UID/resourceVersion CAS is not an IR/Pod/runtime transaction.
```
Check `From node`, `Migration mode` and `Current nodes`. Without `--from-node`, the CLI takes the one node that the Instance's pods run on. The result on stdout shows the message `Validated locally; no patch sent.` Each run generates a new `Request UUID`, so the real request gets a different one.
With `--dry-run server`, the API server and OME's webhook check the request too. When they accept it, the message is `API dry-run accepted; no changes persisted.`
## Step 2: Request the migration
Choose the flags for the request:
| Flag | What it does |
| --- | --- |
| `--from-node` | The node to leave. Needed when the Instance's pods span several nodes, as a leader and its workers can. |
| `--hint-node` | Up to eight nodes that the replacement should prefer, comma-separated. The scheduler can still pick any node but the source. |
| `--reason` | Why you're moving the Instance, kept in the migration record. Plain text, without URLs or credentials. |
| `--yes` | Skips the confirmation prompt. Needed when the command runs without a terminal. |
[Flags](../../reference/kubectl-ome/migration.md#start-flags) lists the rest, with their limits.
Send the request, and answer `y` at the prompt `Confirm this exact action? [y/N]`:
```bash
kubectl ome migration start chat -n prod --component engine --instance 3 --reason "node-a maintenance"
```
The command prints the preview to stderr, then the result to stdout:
```output
FIELD VALUE
action migration start
target InferenceService/prod/chat
dry-run none
accepted Yes
applied Yes
request-id 7c9e6679-7425-40de-944b-e07fc1f90ae7
revision-hash -
message API accepted annotation request; delivery and converg...
follow-up kubectl ome migration status chat --component=engine ...
hint Use -o json or -o yaml for full values.
```
`applied` means that the API server stored the request on the InferenceService. Nothing has moved yet. Keep the `request-id`, which you need to wait for the migration.
## Step 3: Follow the request
Check the request with `migration status`:
```bash
kubectl ome migration status chat -n prod --component engine
```
Once OME has started the replacement, which it calls the surge, the output is:
```output
SUBJECT/COMP STATUS DETAIL
7c9e6679/engine SurgePending/Active/Current MSG: surge allocated; w...
```
SUBJECT/COMP shows the start of the request ID, and the component. STATUS shows the phase, whether the migration is `Active` or `Terminal`, and `Current` when the InferenceReplica's status has caught up with its spec. DETAIL shows the start of OME's message, here `surge allocated; waiting for surge pods`, which `-o yaml` prints in full.
The request appears once OME accepts it, in the phase `Accepted`. It then moves through `SurgePending`, `SurgeReady` and `Draining` to `Completed`, or ends in `Failed`.
To block until the migration ends, run `kubectl ome wait chat -n prod --for=migration=terminal --request-id=7c9e6679-7425-40de-944b-e07fc1f90ae7 --timeout=30m`. Its `--timeout` should cover the migration's [deadline](../../concepts/omenative/migration-and-transient-scale.md#deadline), 30 minutes by default: without it, the command stops after 60 seconds. It exits `0` when the migration ends, even when it fails, so check the outcome:
```bash
kubectl ome migration status chat -n prod --component engine
```
```output
SUBJECT/COMP STATUS DETAIL
7c9e6679/engine Completed/Terminal/Current MSG: migrated to instan...
```
The full message is `migrated to instance=4`: Instance 4, the replacement, serves on another node, and Instance 3 is gone. The record stays in `status` for the caps' `window`, one hour with the chart. For older migrations, use `history`:
```bash
kubectl ome migration history chat -n prod
```
```output
EVID REQUEST/COMP PHASE/STATE WHEN DETAIL
AUTH 7c9e6679/E Completed/T 09-28T10:31Z migrated to insta...
AUDIT 7c9e6679/E Completed/T 09-28T10:31Z migrated
AUDIT 3f2a9c1e/E Failed/T 09-28T09:20Z deadline exceeded...
```
EVID says where each row comes from: `AUTH` for the InferenceReplica's status, and `AUDIT` for the audit ConfigMap `chat-ome-migration-audit`, which keeps the last 200 migrations that ended. The earlier request `3f2a9c1e` failed when its deadline passed. [Output fields](../../reference/kubectl-ome/migration.md#history-output-fields) explains the other columns.
## Keep a component in place
Each component has its own migration mode, `lifecycle.migrationPolicy.mode`: `Auto`, the default, `Surge`, which acts the same, or `Never`. `chat` serves a Llama model in prefill-decode mode. Its engine runs prefill in four Instances that can move, and its decoder stays where it is:
```yaml
apiVersion: ome.io/v1beta1
kind: InferenceService
metadata:
name: chat
namespace: prod
spec:
deploymentMode: OMENative
model:
name: llama-3-2-1b-instruct
runtime:
name: srt-llama-3-2-1b-instruct-pd
engine:
minReplicas: 4
maxReplicas: 4
lifecycle:
migrationPolicy:
mode: Auto
decoder:
minReplicas: 1
maxReplicas: 1
lifecycle:
migrationPolicy:
mode: Never
router:
minReplicas: 1
maxReplicas: 1
```
The runtime needs GPU nodes with RDMA networking, as [PD mode](../../reference/operate-ome/ome-serving-values.md#pd-mode) explains. With `Never`, `migration start` refuses a request for the decoder, and OME fails one written another way. `Never` also turns off [automatic relocation](../../concepts/omenative/migration-and-transient-scale.md#automatic-relocation). You can change the mode on a running component, and OME applies it without a rollout.
## Troubleshooting
### `migration start` refuses the request
A refusal prints `error:` and a message, changes nothing, and exits `1`, or `3` for a conflict. The messages you're most likely to see:
| Message | What to do |
| --- | --- |
| `required Kubernetes API request failed; check access and connectivity` | A read failed, often for lack of a permission in [Before you begin](#before-you-begin). |
| `action refused: active runtime is unavailable, inconsistent or unbound` | The service needs a component [on OMENative](../../concepts/architecture/deployment-modes.md#opt-in-to-omenative) and a serving runtime that the CLI can resolve. |
| `migration precondition conflicts or is stale; inspect migration status and retry explicitly` | The state of the service blocks the move now: see the causes below. |
| `action not confirmed; noninteractive input requires --yes` | Answer `y` at the prompt, or pass `--yes` when the command runs without a terminal. |
| `context deadline exceeded` | The command has 45 seconds in all, prompt included. Answer sooner, or pass `--yes`. |
| `guarded migration rejected; inspect migration status using preview UUID` | The InferenceService changed after the CLI read it, often through a status update. Run the command again. |
The usual causes of the precondition conflict:
- The InferenceReplica's status lags a recent change. Try again in a moment.
- The service is paused. [Pause and resume a rollout](../roll-out-changes/pause-and-resume-a-rollout.md) shows how to resume.
- The service has the `ome.io/rollout-promote` or `ome.io/rollout-rollback` annotation. The rollback annotation stays until a different target revision appears, as [`promote` and `rollback`](../../reference/kubectl-ome/rollout.md#promote-and-rollback) describes.
- The Instance isn't `Ready`, has an operation in progress, or already has a request or a migration. [`kubectl ome instance list`](../../reference/kubectl-ome/instance.md) shows its state.
- The component's migration mode is `Never`.
- The Instance's pods run on more than one node, and you didn't pass `--from-node`.
[Refusals](../../reference/kubectl-ome/migration.md#start-refusals) lists the messages and their causes.
### The outcome is unknown
`migration request failed or outcome unknown; inspect migration status using the preview UUID:`, followed by a cause, means that the patch failed. The API server may have refused it, for example for lack of permission, or stored it despite a server error, a timeout or a network error. Before you send a new request, look up the first one with the same flags and the `Request UUID` from its preview:
```bash
kubectl ome migration start chat -n prod --component engine --instance 3 --reason "node-a maintenance" --request-id 7c9e6679-7425-40de-944b-e07fc1f90ae7
```
When the request is still on the InferenceService, waiting for OME, the command sends nothing, exits `0` and prints:
```output
FIELD VALUE
action migration start
target InferenceService/prod/chat
dry-run none
accepted No
applied No
request-id 7c9e6679-7425-40de-944b-e07fc1f90ae7
revision-hash -
message Identical retained mailbox observed; no replay sent; ...
follow-up kubectl ome migration status chat --component=engine ...
hint Use -o json or -o yaml for full values.
```
Don't send it again: follow it with `migration status`. When the lookup exits `3` instead, OME has taken the request, or it never arrived. A row in `migration status` that starts with `7c9e6679` means that OME took it. Otherwise, send a new request without `--request-id`.
### The request stays Accepted
When `status` keeps showing the request as `Accepted` with this message:
```bash
kubectl ome migration status chat -n prod --component engine
```
```output
SUBJECT/COMP STATUS DETAIL
7c9e6679/engine Accepted/Active/Current MSG: waiting for migrat...
```
The full message is `waiting for migration capacity policy (no lifecycle.audit configured)`. The controller's migration caps are missing or invalid, so it holds every request and records a `MigrationPolicyUnconfigured` warning event.
The ome-resources chart sets the caps, and the kustomize manifests in `config/` leave them out. With the chart, set them in your values, then upgrade the release:
```yaml
ome:
controller:
lifecycle:
audit:
maxInFlightMigrations: 3
maxMigrationsPerWindow: 10
window: 1h
```
All three fields are required and must be greater than zero. Without the chart, add the same `audit` block to the JSON under the `lifecycle` key of the `inferenceservice-config` ConfigMap in the OME namespace, beside `instanceReadyTimeout`. Add the chart's `requeue` block there too, `"requeue": {"operation": "5s", "gate": "3s"}`. Without it, OME can take many minutes to retry a held request. OME picks up the change without a restart, and a request still held at its deadline fails with `deadline exceeded in phase Accepted: surge never allocated`.
A message that starts with `waiting for source` instead means the source Instance is `Failed` on the revision it runs, and the move waits for it to be `Ready` again. Nothing in flight on the Instance ends that wait by itself, so OME names it on the record, and in a Warning `MigrationDeferred` event, once per cause:
```output
waiting for source component=engine instance=3, Failed on its running revision chat-engine-5d8f7c9b (pod chat-engine-3-default-0 container ome-container stuck (CrashLoopBackOff)), to be Ready again: its repair is parked until the retry ladder re-arms it
```
The parenthesis is the failure that parked the Instance, and the end of the message is what holds its repair:
| The message ends with | What to do |
| --- | --- |
| `its rebuild waits on the revision's retry block Backoff after 1 failed attempt(s): CrashLoopBackOff` | Nothing: OME rebuilds the Instance when the revision's retry block allows. `kubectl ome instance retry-blocks chat -n prod --component engine` shows it. |
| `its repair is parked, waiting for the configuration or image the pod needs; the kubelet retries in place` | Fix the configuration or image the pods name, such as the missing tag behind an `ImagePullBackOff`. The pods start on their own once it's there. |
| `its repair is parked until the retry ladder re-arms it` | Nothing: OME rebuilds the pods when the ladder's next attempt comes. |
| `its repair is parked and no retry ladder is configured to re-arm it; reset the Instance or publish a corrected revision`, or `its repair is parked after 3 re-arm(s) with every retry spent; reset the Instance or publish a corrected revision` | Nothing automatic releases this hold. Fix the cause, then [reset the Instance](../omenative/reset-failed-instances.md), or roll out a corrected revision. |
| `no repair is open on it` | OME retries the Instance when its backoff allows. [`kubectl ome instance list`](../../reference/kubectl-ome/instance.md) shows what holds it, and a [reset](../omenative/reset-failed-instances.md) starts it over. |
The same request proceeds once the Instance is `Ready` again, but its deadline keeps running while it waits, so a repair that outlasts it still fails the request.
Without one of those messages, the request is waiting its turn, for one of the causes in [When a request waits or fails](../../concepts/omenative/migration-and-transient-scale.md#what-refuses-a-migration-request). Pausing the service holds its migrations, but their deadlines keep running, so one whose deadline passes fails when you resume.
### The migration fails
When `status` shows `Failed`, run it with `-o yaml` to read the full message. OME also records a warning event on the InferenceService, which `kubectl get events -n prod --field-selector involvedObject.name=chat` lists. The usual messages are:
| Message | Event | What happened |
| --- | --- | --- |
| `migration rate cap reached (10/10 in the last 1h0m0s)` | `RateLimited` | `maxMigrationsPerWindow` migrations started within `window`. Wait for the oldest to age out of the window, then send the request again. |
| `deadline exceeded in phase SurgePending: surge pods never became ready` | `MigrationExpired` | The migration missed its deadline while waiting for what the message names, here the replacement's pods. Check them with `kubectl describe pod`. |
| `deadline exceeded in phase Accepted: surge never allocated`, right after the request | `MigrationExpired` | No Instance readiness timeout is set, or the controller's `lifecycle` settings are invalid. Set a timeout, as [Set Instance readiness deadlines](../omenative/set-instance-readiness-deadlines.md) shows. This is a known bug. |
| `surge pod wedged in past the stuck-pod grace` | `MigrationSurgeWedged` | A replacement pod is stuck in `ImagePullBackOff`, `CrashLoopBackOff` or a similar state. |
| `source instance has no live pods`, or `source pods span multiple nodes (node-a, node-b)` | `MigrationFromNodeMismatch` | The source names no single node to leave: its pods are gone, or leftover pods of a single-pod Instance sit on two nodes. Wait for the Instance to settle on one node, then send a new request. |
| `source component=engine instance=3 is Failed on revision chat-engine-6c4f8b1d, which has not started (retry block Backoff after 1 failed attempt(s): CrashLoopBackOff); correct or release the revision, then request the move again` | `MigrationRequestRejected` | The source failed moving to a revision it never ran, so only a corrected or released revision gets it going again; a wait would just park the request until its deadline. The parenthesis says where the revision's retries stand. Fix the revision or [release it](../roll-out-changes/release-a-held-revision.md), then send a new request. |
| `source component=engine instance=3 runs revision chat-engine-7f9c4d2b, which is held after its attempts (retry block Held after 3 failed attempt(s): ImagePullBackOff); the move would rebuild that revision elsewhere; correct or release the revision, then request the move again` | `MigrationRequestRejected` | The source's running revision is [held](../roll-out-changes/release-a-held-revision.md#how-holds-and-releases-work), so the replacement would rebuild a revision OME has given up on. The request fails even when the Instance reads `Ready` between crashes. Release the revision, or roll out a corrected one, then send a new request. |
| `migrations disabled by MigrationPolicy Mode=Never` | `MigrationRequestRejected` | The component's migration mode is `Never`. |
A `MigrationFromNodeMismatch` event isn't a failure when its type is Normal. The message `request.FromNode=node-a does not match observed source node=node-b; component=engine instance=3 leaves node-b` means the source Instance moved to another node after you sent the request: OME migrates it anyway, off the node it occupies, and the record's `from_node` follows.
When a migration fails after its replacement started, OME removes the replacement and keeps the source Instance. [Watch migrations](../../concepts/omenative/migration-and-transient-scale.md#watch-migrations) lists every migration event.
## Next steps
- [kubectl ome migration](../../reference/kubectl-ome/migration.md): the flags, refusals and output fields.
- [kubectl ome wait](../../reference/kubectl-ome/wait.md): what `wait` can block on, and its exit codes.
- [Migration and transient scale](../../concepts/omenative/migration-and-transient-scale.md): deadlines, caps and automatic relocation.
- [Set Instance readiness deadlines](../omenative/set-instance-readiness-deadlines.md): the timeout behind each migration's deadline.
- [Let Alfred migrate Instances](../scheduling/let-alfred-migrate-instances.md): Alfred, OME's alpha GPU cluster caretaker, recommends Instance moves when free GPUs fragment or a node turns unhealthy and, when you let it, asks OMENative to carry them out.
- [Request a transient scale](request-a-transient-scale.md): send a transient replica request to an OMENative component.