GitOps User Guide

This guide helps you configure Nexus GSLB to pull its configuration from a Git repository with mandatory GPG-signed commits.

What you will set up

  • A Git repository (Gitea or GitLab CE recommended)
  • A directory that contains gslbd.yaml for your cluster
  • GPG signing and an allowlist of trusted signer fingerprints
  • The gslbd daemon configured to poll and reconcile changes every 30s (configurable)

Prerequisites

  • A reachable Git server
  • A deploy key (SSH private key) for read-only access
  • GPG public keys installed on the gslbd host and trusted in the local GPG trustdb or explicitly allowlisted in config

Repository layout

infra/gslb-config
└── clusters/
    └── prod-eu-glb/
        └── gslbd.yaml   # full config for the cluster

Signing workflow (GPG)

  1. Generate or use an existing GPG key. Export the public key and distribute to gslbd hosts.
  2. Configure Git to sign commits by default:
git config --global user.signingkey <KEYID>
git config --global commit.gpgsign true
  1. Commit and sign:
# edit clusters/prod-eu-glb/gslbd.yaml
git add clusters/prod-eu-glb/gslbd.yaml
git commit -S -m "Update endpoints"
git push origin main

Configure gslbd for GitOps Add to /etc/gslb/config.yaml:

gitops:
  repoURL: "ssh://gitea@git.example.com/infra/gslb-config.git"
  branch: "main"
  pathPrefix: "clusters/prod-eu-glb"
  pollInterval: "30s"
  requireSignature: true
  allowedSigners: ["ABCD1234EF56...FPR"]
  auth:
    sshKeyPath: "/etc/gslb/gitops_deploy_key"

Notes

  • requireSignature: true rejects unsigned or unverified commits.
  • When allowedSigners is non-empty, only those fingerprints are accepted (case-insensitive match of %GK). %GK emits the long key ID (16 hex), not the 40-char fingerprint, on common gpg builds — pin the value your node actually prints (git log -1 --pretty=%GK), or list both forms.
  • The SSH deploy key must be readable by the gslbd process and have permissions 0600.
  • The signer's public key must be imported (and trusted) into a keyring the gslbd service can reach at runtime. The unit runs ProtectHome=yes, so a keyring under the service user's $HOME (e.g. /home/gslb/.gnupg) is masked and verification fails. Put it under the writable StateDirectory and point the service at it: import with GNUPGHOME=/var/lib/gslbd/gnupg gpg --import <pub.asc> (as the service user), then add GNUPGHOME=/var/lib/gslbd/gnupg to the unit's EnvironmentFile (/etc/gslb/env) and restart. Verify in-context with sudo -u <svcuser> env GNUPGHOME=… git -C <clone> verify-commit HEAD.

Reconciliation behavior (self-restart model)

GitOps applies the full config via a graceful self-restart — there is no in-process hot-apply and no coordinator. Each node reconciles independently:

  1. Fetch the configured repo/branch into a local workdir.
  2. Hash the config file (gslbd.yaml, fallback index.yaml) under pathPrefix and compare to the config currently on disk. If unchanged, do nothing.
  3. Verify the GPG-signed HEAD commit (when requireSignature: true), optionally restricted to allowedSigners.
  4. Validate the new config (parse + cross-field checks). An invalid commit is rejected and the running config is left untouched — it can never crash-loop the daemon.
  5. Apply: atomically write the exact repo bytes to the node's own config file (temp + rename), then request a graceful restart. systemd (Restart=always) brings the daemon back onto the new config.

Because the apply writes the exact repo bytes, after the restart the on-disk config equals the repo, so the next poll is a no-op — no restart loop, no extra persisted state. Every node converges on the git source of truth by the same mechanism.

!!! important "GitOps requires a full restart per change" Unlike data-plane edits (pools/members/services via the API, which are live), a GitOps config change restarts the daemon. This is the intended model for declarative, file-driven config. For changes that should NOT cause a restart, use the API or systemctl reload gslbd (the SIGHUP reload subset).

Metrics:

  • gslbd_gitops_fetch_total{result}, gslbd_gitops_verify_total{result}, gslbd_gitops_apply_total{result}
  • gslbd_gitops_last_apply_info{sha,signer} — last successfully applied commit

Rollback

  • Revert the change in Git with a signed commit (or checkout a signed tag) and push.
  • Each node detects the new commit, verifies it, rewrites its config, and restarts onto it.

Failure modes (running config is always preserved)

  • Fetch failure: keep running; retry next tick.
  • Signature failure: reject; keep running config.
  • Parse/validation failure: reject; keep running config.
  • Config write failure: log and keep running (no restart).

Troubleshooting

  • Signature failures: check logs and ensure the signer fingerprint is in allowedSigners or trusted in GPG.
  • No changes applied: confirm pathPrefix is correct and contains gslbd.yaml (or index.yaml).
  • Access denied: verify the SSH key and repository URL; try a manual clone using GIT_SSH_COMMAND="ssh -i /etc/gslb/gitops_deploy_key".
  • permission denied on /etc/gslb/.gslbd-config-*.tmp (change is fetched + validated but never applied; logs show GitOps: failed to write config): the service user can write the file but not create a temp file in /etc/gslb. The atomic apply is temp-file-then-rename, so the directory must be group-writable by the service user. Fix: chown root:<svcuser> /etc/gslb && chmod 2770 /etc/gslb (setgid keeps the group on the rewritten config). ConfigurationDirectory=gslb does not fix this if /etc/gslb already exists root-owned — systemd won't chown a pre-existing directory.
  • Permission denied (publickey) cloning a Forgejo/Gitea repo: the forge's built-in SSH server usually listens on a non-standard port (often 222), while :22 is the host's own sshd. An scp-style URL (git@host:path) cannot carry a port and the fetcher runs a bare git clone with no -p, so encode the port in an ssh:// URL: repoURL: "ssh://git@host:222/org/repo.git". Confirm the port the forge advertises (its repo "SSH" clone button, or the API ssh_url).

Code references

  • internal/gitops/* (fetcher, verifier)
  • cmd/gslbd/setup_services.go (setupGitOps / reconcileGitOps)
  • internal/config/validator/validate.go
  • deploy/gslbd.service (Restart=always)

Was this article helpful?
© 2026