Orchestrating fleets¶
For large-scale infrastructure operations, managing hosts individually becomes impractical. Starkite combines the inventory module with the concurrent execution model of the ssh module to let you discover, filter, and automate changes across entire server fleets.
The Fleet Automation Workflow¶
Fleet automation in Starkite follows a structured five-step pipeline:
- Load (
inventory.file()): Load host metadata and attributes from structured configuration files like YAML or JSON. - Filter (
inventory.filter()): Query and subset the loaded fleet based on attributes such as environment, region, or role. - Extract (
inventory.addresses()): Extract the network addresses or hostnames from the filtered subset of hosts. - Configure (
ssh.config()): Initialize a concurrent SSH client using the extracted host addresses and appropriate credentials. - Execute (
client.exec()/client.upload()): Dispatch remote commands or distribute files concurrently across the targeted servers.
Step 1: Defining the Host Inventory¶
Create a structured file (e.g., fleet.yaml) containing your infrastructure metadata. The inventory module expects a list of dictionaries, where each entry represents a host and its attributes:
# fleet.yaml
- name: web-prod-1
address: 192.168.10.11
env: production
role: web
region: us-east
cpu: 8
- name: web-prod-2
address: 192.168.10.12
env: production
role: web
region: us-east
cpu: 8
- name: web-stage-1
address: 192.168.20.11
env: staging
role: web
region: us-west
cpu: 4
- name: db-prod-1
address: 192.168.10.21
env: production
role: database
region: us-east
cpu: 16
Step 2: Loading and Filtering Hosts¶
Use the inventory module to load and query the fleet data. You can filter hosts using direct keyword arguments or custom predicate functions.
def load_fleet():
# Load all hosts from the inventory file
fleet = inventory.file("fleet.yaml")
print("Loaded", fleet.count, "total hosts")
# Filter by exact attribute match
prod_web = inventory.filter(fleet, env="production", role="web")
print("Production web servers:", prod_web.count)
# Filter using a predicate function (e.g., hosts with at least 8 CPUs)
large_nodes = inventory.filter(fleet, lambda h: h.get("cpu", 0) >= 8)
for host in large_nodes.items:
print(host["name"], "cpus:", host["cpu"])
Step 3: Concurrent Fleet Execution¶
Once you have filtered the desired subset of hosts, extract their network addresses using inventory.addresses() and pass them to the SSH client to dispatch commands concurrently.
def deploy_fleet_patch():
# 1. Load and filter the target fleet
fleet_data = inventory.file("fleet.yaml")
target_hosts = inventory.filter(fleet_data, env="production", role="web")
# 2. Extract network addresses
addresses = inventory.addresses(target_hosts, key="address")
# 3. Configure the concurrent SSH client
client = ssh.config(
hosts = addresses,
user = "alice",
key = "~/.ssh/id_ed25519",
timeout = "15s",
)
# 4. Dispatch the command concurrently across the fleet
results = client.exec("uptime -p")
# 5. Process and display results in a table
t = table.new(["HOST", "STATUS", "OUTPUT"])
for r in results:
status = "OK" if r.ok else "FAIL"
output = r.stdout.strip() if r.ok else r.stderr[:60]
t.add_row(r.host, status, output)
print(t.render())
Managing Execution Strategies¶
By default, the SSH client executes commands concurrently across all hosts. If you need to limit execution rate or run commands sequentially (e.g., to perform rolling updates), set the exec_policy parameter in ssh.config():
concurrent(default): Dispatches commands to all hosts in parallel.linear: Executes commands on one host at a time, halting if any host fails.
def rolling_update():
fleet_data = inventory.file("fleet.yaml")
addresses = inventory.addresses(fleet_data)
# Configure client to run commands sequentially
client = ssh.config(
hosts = addresses,
user = "alice",
key = "~/.ssh/id_ed25519",
exec_policy = "linear",
)
# Will stop at the first failing host to prevent spreading issues
results = client.exec("systemctl restart alice-app")
Fleet-Wide File Distribution¶
In addition to executing commands, you can distribute files across a fleet concurrently. By combining the inventory module with client.upload(), you can push configuration files, security policies, or application payloads to multiple remote hosts in parallel.
The following script loads the host inventory, filters for the production web servers, and uploads a security policy file to all of them concurrently:
def distribute_security_policy():
# 1. Load and filter the target fleet from inventory
fleet_data = inventory.file("fleet.yaml")
target_hosts = inventory.filter(fleet_data, env="production", role="web")
# 2. Extract host network addresses
addresses = inventory.addresses(target_hosts)
# 3. Configure the concurrent SSH client
client = ssh.config(
hosts = addresses,
user = "alice",
key = "~/.ssh/id_ed25519",
timeout = "15s",
)
# 4. Upload the security policy concurrently to all target hosts
results = client.upload(
src = "policies/security-audit.json",
dst = "/etc/alice-app/security-audit.json",
mode = "0600",
)
# 5. Verify the transfer status across the fleet
for r in results:
if r.ok:
print("Uploaded successfully to:", r.host)
else:
log.warn("Failed to distribute to:", r.host)