Skip to content

Instantly share code, notes, and snippets.

@mbtamuli
Created June 25, 2025 15:37
Show Gist options
  • Save mbtamuli/578e4b3409a26b0cfee040d699351c1e to your computer and use it in GitHub Desktop.
Save mbtamuli/578e4b3409a26b0cfee040d699351c1e to your computer and use it in GitHub Desktop.
Kubectl Rsync
#!/bin/bash
#
# krsync - Kubernetes rsync wrapper
#
# This script enables rsync to work with Kubernetes pods by acting as a bridge
# between rsync and kubectl exec. It requires rsync to be installed in the pod
#
# It supports two syntaxes:
#
# 1. Simple pod name (uses default namespace):
# krsync [rsync-options] source pod_name:destination
#
# 2. Pod with namespace:
# krsync [rsync-options] source pod_name@namespace:destination
#
# Example usage:
# ./krsync -avP . my-pod@my-namespace:/app/
# ./krsync -avzhP --delete . my-pod:/app/
#
# The script operates in two phases:
# 1. Initial phase: Launches rsync with this script as the remote shell
# 2. RSH phase: Executes commands in the pod via kubectl exec
#
# Based on: https://serverfault.com/questions/741670/rsync-files-to-a-kubernetes-pod
#
set -euo pipefail # Exit on error, undefined vars, and pipe failures
# Phase 1: Initial execution - launch rsync with this script as remote shell
if [[ -z "${KRSYNC_STARTED:-}" ]]; then
export KRSYNC_STARTED=true
# Validate that we have arguments
if [[ $# -eq 0 ]]; then
echo "Error: No arguments provided" >&2
echo "Usage: $0 [rsync-options] source pod[@namespace]:destination" >&2
exit 1
fi
# Launch rsync with this script as the remote shell handler
# --blocking-io ensures proper I/O handling with kubectl exec
exec rsync --blocking-io --rsh "$0" "$@"
fi
# Phase 2: RSH mode - we're being called by rsync as the remote shell
# At this point, rsync passes us the target and command to execute
# Initialize variables with safe defaults
namespace_flag=""
pod_name=""
# Validate we have at least one argument (the pod specification)
if [[ $# -eq 0 ]]; then
echo "Error: No pod specification provided in RSH mode" >&2
exit 1
fi
pod_name="$1"
shift
# Parse pod specification and extract namespace if provided
if [[ "$pod_name" == *"@"* ]]; then
# Handle pod@namespace syntax: extract namespace and pod separately
namespace_name="${pod_name#*@}" # Everything after @
pod_name="${pod_name%@*}" # Everything before @
# Validate extracted components
if [[ -z "$namespace_name" ]]; then
echo "Error: Empty namespace specified in pod@namespace syntax" >&2
exit 1
fi
if [[ -z "$pod_name" ]]; then
echo "Error: Empty pod name specified in pod@namespace syntax" >&2
exit 1
fi
namespace_flag="--namespace=${namespace_name}"
elif [[ "$pod_name" == "-l" ]]; then
# Handle legacy rsync -l flag format: rsync -l pod namespace ...
# This happens when rsync interprets pod@namespace differently
if [[ $# -lt 2 ]]; then
echo "Error: Insufficient arguments for -l flag format" >&2
exit 1
fi
pod_name="$1"
shift
namespace_name="$1"
shift
# Validate components
if [[ -z "$namespace_name" ]]; then
echo "Error: Empty namespace specified with -l flag" >&2
exit 1
fi
if [[ -z "$pod_name" ]]; then
echo "Error: Empty pod name specified with -l flag" >&2
exit 1
fi
namespace_flag="--namespace=${namespace_name}"
fi
# Validate final pod name
if [[ -z "$pod_name" ]]; then
echo "Error: No valid pod name could be determined" >&2
exit 1
fi
# Execute the command in the specified pod using kubectl
# --stdin: Pass stdin to the container (required for rsync data transfer)
# --tty=false: Don't allocate a TTY (prevents interference with binary data)
# shellcheck disable=SC2086 # namespace_flag needs to expand as separate words
exec kubectl ${namespace_flag} exec --stdin --tty=false "$pod_name" -- "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment