PlatformEngr® Fleet GitOps course

Module 01 · Platform foundation · 45 min · You are: Platform team

Your fleet repo and the change workflow

In this module

  • You learn: Why a platform team protects the process before it builds anything: required checks (validate, plan), review rules (CODEOWNERS) and a protected main. And the journey every change takes.
  • You do: Create your fleet repo, push the guardrails in one bootstrap commit, protect main, then make your first real change through issue, pull request, checks and merge: the list of clusters.
  • You end with: A repo with CI, issue forms and review rules; main only accepts pull requests that pass validate and plan; and clusters/dev.yaml and clusters/prod.yaml merged through that process. No cluster exists yet.

A platform team protects the process before it builds the platform. If validation and review rules arrive late, the first months of history are unreviewed changes. So the first commit brings the guardrails, and the first real change, the cluster inventory, already goes through them.

Open this module in the interactive course to track progress and switch tracks.

Change journey practised: Requested → Proposed → Validated → Previewed → Approved → Merged → Reconciled → Healthy

Concepts

One bootstrap commit
An empty repo cannot receive a pull request, so the guardrails (.github/, scripts/) go in with one direct push. After that, main is protected and every change is a pull request.
Required checks
validate runs scripts/validate.sh: YAML, schemas, references, ownership, policies, Terraform. plan comments what the change does to the fleet. Branch protection makes both a merge requirement.
CODEOWNERS
Maps folders to owners: the platform team owns everything by default, product teams own their deployment folders. With "require review from code owners", ownership becomes enforcement.
The inventory
clusters/*.yaml lists the workload clusters. It is the platform team's first decision, and from module 02 on it drives Terraform and every ApplicationSet.

Files to know

Open them in your editor or on GitHub; the bullets say what to look at.

Steps

1. Create an empty repository on GitHub

Name it fleet-gitops, make it public (Argo CD then needs no credentials), and do not add a README or licence. Create it on GitHub.

Create the repo with the GitHub CLI (or use the web page above):

gh repo create YOUR_GITHUB_USER/fleet-gitops --public

2. Clone it and add the reference as a second remote

origin is your repo. reference is the finished implementation you will copy folders from. You will never push to it.

Clone your repo. A warning that you cloned an empty repository is expected.

mkdir -p "$(dirname ~/work/fleet-gitops)"
git clone https://github.com/YOUR_GITHUB_USER/fleet-gitops.git ~/work/fleet-gitops

Add the reference as a second remote and download it, so you can copy folders from it:

cd ~/work/fleet-gitops
git remote add reference https://github.com/platformengr/fleet-gitops.git
git fetch -q reference

Check both remotes. Expect origin (your repo) and reference (the finished one), each listed twice, for fetch and push:

git remote -v

3. Bootstrap commit: guardrails first

Bring the workflows, issue forms, CODEOWNERS and validation scripts. set-repo-url.sh --codeowners makes you the owner of everything (a personal account has no teams). This is the only direct push to main in the course.

Copy the guardrails from the reference into your working folder:

cd ~/work/fleet-gitops
git checkout reference/main -- .github scripts .yamllint renovate.json

Make the files yours. This script points them at your repo and makes you the code owner of everything:

scripts/set-repo-url.sh --codeowners @YOUR_GITHUB_USER

Run the checks locally. There is almost nothing to check yet, so expect them to pass:

scripts/validate.sh

Commit and push straight to main. This is the only direct push in the course:

git add -A && git commit -m "Bootstrap: CI, issue forms, review rules"
git push -u origin main

4. Protect main and create the production environment

In branch settings add a rule for main: require a pull request and require status checks validate and plan (they appear in the list after they have run once, so come back after your first pull request), and do not allow bypassing the above settings, so the rule applies to you as the owner too. In general settings allow squash merging only: one commit per change keeps reverts simple. In a team, also require one approval and code owner review. Then create an environment named production with required reviewers: you. You need it later for production deploys.

5. Request the first change

As the platform team, open an issue with the Add or change a cluster form: "Add dev and prod to the fleet". Note its number.

Open the issue form ↗

6. Propose it in a pull request

Open the pull request with Closes #<issue> in the description, so merging it closes your issue.

Start a branch for the change:

cd ~/work/fleet-gitops && git switch main && git pull -q
git switch -c inventory

Copy the cluster list from the reference. It is two small files, clusters/dev.yaml and clusters/prod.yaml:

git checkout reference/main -- clusters

Run the inventory checks the same way CI will. Expect no errors:

python3 scripts/fleet_check.py

Commit and push the branch:

git add -A && git commit -m "Add dev and prod to the fleet inventory"
git push -u origin HEAD

Open the pull request ↗

7. Watch the checks, merge, follow the record

On the pull request: validate goes green, plan comments "2 cluster(s), 0 Applications" (nothing deploys clusters yet), production-gate is skipped. Merge it. The issue closes itself. Now add the required checks to the branch rule.

Get the merged result onto your machine. Expect your inventory commit at the top of the log:

cd ~/work/fleet-gitops && git switch main && git pull -q
git log --oneline -3

Simplified for the workshop

You are the platform team, the product team and the reviewer at once. GitHub does not let you approve your own pull request, so the course uses the production environment approval (which you can give yourself) to demonstrate approvals. In a team, require one approving review as well.

Break it on purpose: Propose an invalid cluster

Someone copies the API address from their own kubeconfig into the inventory. It works from their laptop, so it must be right.

You practise: reading a failed required check, reproducing it on your laptop, and why the hub needs a different address than you do.

1. Break it

cd ~/work/fleet-gitops && git switch main && git pull -q
git switch -c broken-inventory

Change in ~/work/fleet-gitops/clusters/dev.yaml

- server: https://dev-control-plane:6443
+ server: https://127.0.0.1:6443
git add -A && git commit -m "Use the kubeconfig address for dev"
git push -u origin HEAD

Open the pull request.

2. What you see

The validate check turns red and Merge stays blocked, because the rule on main requires it. Expected (by the author): a green check.

3. Fix it yourself

Find the exact error in the check's log and reproduce it on your laptop. Fix the branch until the check is green, the way you would rescue a real pull request. Then close the pull request without merging and delete the branch: the inventory was right all along.

Hint 1

On the pull request, click Details next to the failed check and look for the red lines. The same script runs locally: python3 scripts/fleet_check.py.

Hint 2

127.0.0.1 works from your laptop, but Argo CD uses this address from the hub, which runs in its own container: there, 127.0.0.1 is the hub itself. kind clusters reach each other as https://<name>-control-plane:6443.

The fix

Change in ~/work/fleet-gitops/clusters/dev.yaml

- server: https://127.0.0.1:6443
+ server: https://dev-control-plane:6443

Prove the fix locally. Expect no output and no errors:

python3 scripts/fleet_check.py --quiet
git commit -am "dev: the address the hub can reach" && git push

When the check is green, click Close pull request on GitHub, then clean up:

git switch main && git branch -D broken-inventory && git push origin --delete broken-inventory
After you fixed it: the five questions
What failed?
A cluster entry whose server the hub cannot reach.
How was it detected?
The required validate check, before merge: fleet_check.py knows kind servers must be https://<name>-control-plane:6443.
Who responds?
Whoever opened the pull request; the platform team owns the check.
How is it corrected?
Push a fix to the same branch, or close the pull request.
How is it prevented?
Required checks on main: a broken inventory cannot merge, so it never reaches Terraform or Argo CD.

Quiz

  1. Why does the bootstrap commit bring .github/ and scripts/ before any manifest?
    • So that every later change, starting with the inventory, is validated and reviewed
    • GitHub refuses to run workflows unless the .github folder is committed before any other file
    • The scripts in scripts/ create the clusters, so they have to exist before any manifest does
  2. What does the plan check add on top of validate?
    • It applies the change to a throwaway test cluster and reports whether the sync succeeds
    • It comments which Argo CD Applications the change adds, removes or re-targets
    • It reformats the YAML and pushes a fixup commit onto the pull request branch
  3. Where do you add a third cluster?
    • In every ApplicationSet, adding the new cluster's name to each list generator by hand
    • In the Argo CD UI, with the add cluster button, which then records it in the repo
    • A new file in clusters/, through a pull request
Answers
  1. So that every later change, starting with the inventory, is validated and reviewed Guardrails first: the first real change already goes through the journey.
  2. It comments which Argo CD Applications the change adds, removes or re-targets Reviewers see the effect on the fleet, not just a YAML diff.
  3. A new file in clusters/, through a pull request The inventory is the single source of truth; Terraform and the ApplicationSets read it.

Field note

The number one snag in every workshop: Argo CD reading someone else's repo, so pushes "do nothing". fleet_check.py now fails when a manifest still points at the reference repo. Run scripts/set-repo-url.sh after every copy from the reference and you will never see it.

Report a problem with this module