⌕search⌘K
⌕search the log…
writing
$sysadmin$cybersecurity$devops$thoughts
homelabs
▣brasil homelab⎈kubecraft homelab
site
◈about me✉contact
devops · September 20, 2026 · 8 min read

The wrong cluster.
five things broken on my daily driver, and none of them said so.

For weeks every command I ran went through the control plane over SSH. The first time a checklist asked my own laptop to do the work, five problems showed up in a row.

kubectlk3smacossshhomelab
// what we’re getting into
  1. How this started
  2. git would not run on my own Mac
  3. Three nodes, three usernames
  4. kubectl was talking to the wrong cluster
  5. Getting the kubeconfig off the server
  6. Why none of this showed up sooner
  7. Final checklist: can your daily driver reach the cluster

How this started

Sunday night I sat down with a checklist for the Brasil homelab. Eight blocks, top to bottom, and each one says which machine to run it on.

Block 1 was all server work. Three nodes Ready, three pods Running, Flux green. It took ten minutes and I felt great about it.

Block 2 started with a grep against the repo:

bash
grep -n "ingressClassName\|traefik" clusters/staging/brasil/ingress.yaml
#    -> grep: clusters/staging/brasil/ingress.yaml: No such file or directory

My first guess was that the file was missing. It was not. I was still SSHed into the control plane, and the repo does not live there. It lives on my Mac. The checklist said “(Mac)” right in the heading. I read past it.

a note on names: every machine in this story is a laptop, so here is how I keep them straight. The three running Kubernetes are the nodes. The control plane is the one I SSH into. The Mac I actually sit at is my daily driver.

That grep was the first sign. Once I switched over to the daily driver, it got worse.

git would not run on my own Mac

On the daily driver, in the right folder this time:

bash
git remote -v
#    -> -bash: /usr/local/bin/git: Bad CPU type in executable

I had never seen that one before. It means macOS was asked to run a program built for a different kind of processor.

bash
uname -m
#    -> arm64

file /usr/local/bin/git
#    -> /usr/local/bin/git: Mach-O 64-bit executable x86_64

My daily driver is Apple Silicon. That git was built for Intel.

Homebrew installs to different places depending on the chip. Intel Macs get /usr/local, Apple Silicon gets /opt/homebrew. I had an old Intel install sitting in /usr/local, and it was first in my PATH. The strange part is that I also had a working Apple Silicon Homebrew. My shell was running from /opt/homebrew/bin/bash the whole time. Two Homebrews, and the broken one kept getting asked first.

So I tried to reinstall git with Homebrew:

bash
brew install git
#    -> .../portable-ruby/current/bin/ruby: Bad CPU type in executable
#    -> Error: 'git' must be installed and in your PATH!

Homebrew was the Intel one too, so its own Ruby would not run. And installing a fresh Homebrew needs git. I was stuck in a loop.

The fix was to leave Homebrew out of it. Apple ships a git with the Command Line Tools at /usr/bin/git, and it runs on both chips. xcode-select —install told me the tools were already installed. So a working git had been there the whole time. It was just standing behind the broken one.

bash
# put Apple's directories ahead of the old Intel ones
export PATH="/usr/bin:/bin:/usr/sbin:/sbin:$PATH"

which git
#    -> /usr/bin/git

Now I check the architecture of anything I depend on:

bash
file $(which kubectl) $(which flux) $(which rsync)

If it says x86_64 on an arm64 Mac, it is going to break at some point.

Three nodes, three usernames

My README says you can reach the nodes with ssh dell and ssh biggie. When I opened ~/.ssh/config, there was one entry in it, for a machine that is not even in this cluster. Neither alias existed.

I added the Dell and gave it the same username as the control plane, because I assumed they matched.

bash
ssh dell "hostname"
#    -> Permission denied, please try again.
#    -> Permission denied, please try again.
#    -> Permission denied, please try again.

Then I tried ssh-copy-id to install my key. That needs the password once to get in, so it failed three more times.

ssh biggie failed a different way:

bash
ssh biggie "hostname"
#    -> ssh: Could not resolve hostname biggie: nodename nor servname provided, or not known

That one was simple. I had only added the Dell, so the biggie alias did not exist yet.

For the Dell, I started guessing. I tried the username from my other server, and it worked. On the MacBook Air. The Dell had a third username. Three nodes, three different accounts. I knew that at some point. I did not remember it at 9pm.

Once I had the right username for each box, every node got an entry like this:

ssh config
Host dell
    HostName <dell-ip>
    User op
    IdentityFile ~/.ssh/id_ed25519

Then one ssh-copy-id per node, typing the password one time:

bash
ssh-copy-id -i ~/.ssh/id_ed25519.pub op@<dell-ip>
#    -> Number of key(s) added:        1

ssh dell "hostname"
#    -> dell-node

ssh biggie "hostname"
#    -> biggie-smalls

No password prompts. That matters for the next block, which rsyncs my whole music library to the Dell. I am not typing a password for that.

worth knowing: if your docs say ssh dell works, they should also say what it takes to make that true. The per-node usernames are going into my README.

kubectl was talking to the wrong cluster

This is the one I learned the most from.

I ran a Flux check from the daily driver:

bash
kubectl -n flux-system get gitrepository flux-system
#    -> error: the server doesn't have a resource type "gitrepository"

My cluster has that resource. Flux made it. Earlier that night, on the server, Flux was green. So I tried the Flux CLI instead:

bash
flux get sources git -A
#    -> -bash: flux: command not found

Two problems. The Flux CLI was not installed, and something was off with kubectl. I checked which cluster it was talking to:

bash
kubectl config current-context
#    -> rancher-desktop

Rancher Desktop. A small local cluster that lives on the laptop itself. The kubeconfig on my daily driver had never pointed at the real cluster.

kubectl did not fail. It connected to a real Kubernetes API and gave me a correct answer about a different cluster. If I had asked for my pods, I would have gotten No resources found and could have spent a long time looking for pods that were perfectly fine.

kubectl config current-context is now the first thing I run when something looks wrong.

Getting the kubeconfig off the server

k3s keeps its admin kubeconfig at /etc/rancher/k3s/k3s.yaml, and only root can read it. I tried to pull it over in one line:

bash
ssh op@<server-ip> "sudo cat /etc/rancher/k3s/k3s.yaml" > ~/.kube/k3s-config
#    -> sudo: a terminal is required to read the password

sudo wants a terminal to ask for a password, and a command sent over SSH does not get one by default. Adding -t gives it one.

With -t, it asked for my SSH password and then just sat there. Nothing moved. I thought it had frozen.

It had not. It was waiting for my sudo password, but the prompt was going into the file because of the > redirect, so I never saw it.

I opened the file in vim to see what landed. This was the whole file:

text
[sudo] password for op:
sudo: a password is required

My own redirect had replaced the kubeconfig with an error message. I went to quit without saving and typed :q1. That is a one, not an exclamation mark. Vim had no idea what I meant. :q! on the second try.

Next I copied the kubeconfig text out of another window and pasted it in. YAML depends on indentation, and the paste lost all of it. Every - list marker came through as a bullet point too. kubectl was never going to read that.

What worked was doing it in plain steps.

bash
# on the server: make a copy your user can read
sudo cp /etc/rancher/k3s/k3s.yaml ~/k3s.yaml
sudo chown op ~/k3s.yaml

# on the daily driver: copy it across, then delete the copy
scp op@<server-ip>:k3s.yaml ~/.kube/k3s-config
ssh op@<server-ip> "rm ~/k3s.yaml"
worth knowing: that file is a cluster-admin credential. Do not leave the copy sitting in a home directory, and keep it out of every repo.

Two edits after that. The file says server: https://127.0.0.1:6443, which only works when you are on the server itself, so it gets the server’s real address. k3s also names everything default, and I renamed that to brasil so I always know which context I am on.

bash
sed -i '' 's|https://127.0.0.1:6443|https://<server-ip>:6443|' ~/.kube/k3s-config
sed -i '' 's|: default|: brasil|g; s|name: default|name: brasil|g' ~/.kube/k3s-config
chmod 600 ~/.kube/k3s-config

export KUBECONFIG=~/.kube/config:~/.kube/k3s-config
kubectl config use-context brasil
#    -> Switched to context "brasil".

kubectl get nodes
#    -> biggie-smalls    Ready    worker
#    -> craventhegreat   Ready    control-plane
#    -> dell-node        Ready    worker

Three nodes, all Ready. The same thing the server showed me at the start of the night, but from my daily driver this time. I added the KUBECONFIG line to my shell profile so a new terminal does not drift back to Rancher Desktop.

Last, the missing Flux CLI:

bash
curl -s https://fluxcd.io/install.sh | sudo bash
#    -> Downloading binary .../flux_2.9.5_darwin_arm64.tar.gz

flux version --client
#    -> flux: v2.9.5

It picked the Apple Silicon build on its own. After the rest of that night, I appreciated it.

Why none of this showed up sooner

For weeks I ran every command on the control plane over SSH. On the server, kubectl reads the right kubeconfig and every binary matches the CPU. My daily driver was only a way to get a terminal on the server, so none of its own tools ever had to work.

The first time the checklist asked the daily driver to do the work itself, five problems showed up one after another. None of them were new.

And almost none of them failed in a way that pointed at the cause. kubectl answered. The SSH config was valid. The redirect did what I told it to do. Every time, the fix started with checking where I actually was: which machine, which cluster, which user.

Final checklist: can your daily driver reach the cluster

If all of these pass, your daily driver can do the work on its own.

bash
# 1. The tools match your CPU
uname -m
file $(which kubectl) $(which flux)
#    -> arm64 for both, not x86_64

# 2. kubectl is pointed at the right cluster
kubectl config current-context
#    -> brasil

# 3. The nodes answer from here
kubectl get nodes
#    -> three nodes, all Ready

# 4. SSH uses the right account and your key
ssh -G dell | grep -E '^(user|hostname) '
ssh dell "hostname"
#    -> dell-node, no password prompt

# 5. Flux is installed
flux version --client

Next is back on the checklist: two Navidrome settings that have been quietly doing nothing, and finally getting my music onto the Dell.