Skip to content

Rollout Restart

Trigger rolling restarts without downtime.

Annotation Patch Trick

Kubernetes has no native restart. Patch the pod template with a restartedAt annotation to trigger a rolling update.


How It Works

Kubernetes doesn't have a native "restart" operation. Instead, we trigger a rollout by patching the deployment's pod template with a new annotation. This causes the ReplicaSet controller to create new pods with the updated template.

graph LR
    Patch[Patch Annotation] --> NewRS[New ReplicaSet]
    NewRS --> ScaleUp[Scale Up New Pods]
    ScaleUp --> ScaleDown[Scale Down Old Pods]
    ScaleDown --> Done[Restart Complete]

    %% Ghostty Hardcore Theme
    style Patch fill:#fd971e,color:#1b1d1e
    style NewRS fill:#65d9ef,color:#1b1d1e
    style ScaleUp fill:#65d9ef,color:#1b1d1e
    style ScaleDown fill:#65d9ef,color:#1b1d1e
    style Done fill:#a7e22e,color:#1b1d1e

Implementation

package k8s

import (
    "context"
    "fmt"
    "time"

    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/types"
)

// RestartDeployment triggers a rollout restart
func (c *Client) RestartDeployment(ctx context.Context, name string) error {
    // Patch the deployment with a new annotation to trigger restart
    patch := fmt.Sprintf(`{"spec":{"template":{"metadata":{"annotations":{"kubectl.kubernetes.io/restartedAt":"%s"}}}}}`,
        time.Now().Format(time.RFC3339))

    _, err := c.Clientset.AppsV1().Deployments(c.Namespace).Patch(
        ctx,
        name,
        types.StrategicMergePatchType,
        []byte(patch),
        metav1.PatchOptions{},
    )
    return err
}

Why Patches?

Patches vs Updates

Patches are safer than full updates for concurrent modifications. A strategic merge patch only modifies the fields you specify, while an update replaces the entire object and can conflict with other controllers.


Batch Restart

// RestartDeployments restarts multiple deployments
func (c *Client) RestartDeployments(ctx context.Context, names []string) error {
    for _, name := range names {
        if err := c.RestartDeployment(ctx, name); err != nil {
            return fmt.Errorf("failed to restart %s: %w", name, err)
        }
    }
    return nil
}

Command Usage

func runRestart(cmd *cobra.Command, args []string) error {
    ctx := cmd.Context()

    if len(args) == 0 {
        return fmt.Errorf("deployment name required")
    }

    for _, name := range args {
        if err := client.RestartDeployment(ctx, name); err != nil {
            return fmt.Errorf("failed to restart %s: %w", name, err)
        }
        fmt.Printf("Restarted deployment: %s\n", name)
    }

    return nil
}

Use annotation patches to trigger zero-downtime rolling restarts.

Comments