Set up a GitOps repository
This directory is the starting point for your own GitOps repository. myriarchd reads all declarative objects from it
(blueprints, fleets, rollout policies, executors, inventory). Git is the source of truth; the web UI only displays
them. The object format is described in docs/reference/gitops-schema.md of the myriarch repository.
The starter is deliberately small and generic: three fleets (linux-hosts, windows-hosts, k8s-gateway), one
optional AWX fleet, a canary-then-all rollout policy for configuration changes and one for version changes. Replace
the fleets, selectors, gateway endpoints and site IDs with your own; keep the structure.
Layout
.gitlab-ci.yml pipeline: validate -> render -> notify-myriarchd (GitLab variant; GitHub follows later)
ci/components-otelcol-contrib-0.162.0.json component list (check "unknown_component")
config/ <- gitops.path of the myriarchd configuration
blueprints/ base, linux-host, windows-host, k8s-gateway
fleets/ linux-hosts, linux-hosts-awx (optional), windows-hosts, k8s-gateway
policies/ canary-then-all (config rollouts), version-canary-then-all (version rollouts)
executors/ awx-linux (optional; the host awx.example.com is a placeholder)
inventory/ waves (canary wave assignment by site.id)
samples/agents/ sample agents for myrctl (not myriarchd objects)
Important: myriarchd reads every
*.yaml/*.ymlfile belowconfig/as a GitOps object (except directories with a leading dot andconfig/samples/). That is why.gitlab-ci.ymlandci/live in the root and never underconfig/. An additional YAML file inconfig/(a CI file or a note, for example) makes the whole state invalid - myriarchd then does not accept it.
Fleets and assignment
| Fleet | Matches | Blueprint | Collector | Update path |
|---|---|---|---|---|
linux-hosts | os.type=linux, site.type datacenter or office | linux-host | 0.161.0 | myriarch-updater |
linux-hosts-awx (optional) | like above + labels.update-via=awx | linux-host | 0.161.0 | Ansible AWX |
windows-hosts | os.type=windows | windows-host | 0.161.0 | myriarch-updater |
k8s-gateway | site.type=k8s-gateway | k8s-gateway | 0.162.0 | none (image digest) |
- Server labels (for example
update-viaor a staging label) are set by the enrollment token (UI Settings -> Enrollment tokens) or by the inventory; a host cannot claim them for itself. Self-reported attributes (os.type,site.*) are fine for coarse selection, not for security-relevant decisions. - Waves of rollouts use the inventory label
wave(inventory/waves.yaml): the wavecanarycontains the first host of each type; after approval in the UI the rest follows. - Two fleets with the same priority that match the same agent leave it unassigned (
fleet_ambiguous). Always give new fleets a unique priority when their selectors can overlap (the higher priority wins).
Start your own GitOps repository
Create an empty repository (no README or ignore file, default branch
main) on your Git server.Copy the contents of this directory (not the directory itself) into the root of the new repository:
cp -R gitops-template/. /path/to/your-gitops-repo/ cd /path/to/your-gitops-repo git add -A && git commit -m "Start from the myriarch GitOps starter" && git push origin mainProtect the branch:
mainis protected and nobody pushes to it directly; everything goes through merge requests with a green pipeline and at least one approval. The protection is also required for the CI variableMYRIARCH_GITOPS_WEBHOOK_SECRET(it is available on protected branches only).CI/CD variables (Settings -> CI/CD -> Variables):
Variable Value Options MYRCTL_IMAGEregistry.example.com/myriarch/myrctl:<version>: an image of your own withmyrctl,shandcurl, built from themyrctlrelease archive (the project publishes nomyrctlimage)- (placeholder default in .gitlab-ci.yml)MYRIARCH_URLhttps://fleet.example.com- MYRIARCH_GITOPS_WEBHOOK_SECRETcontent of gitops-webhook-secretin the myriarchd Secret (openssl rand -hex 32)masked, protected MYRIARCH_CA_FILEPEM of your CA type File, only needed if the runner/image does not trust the CA The runner must be able to reach your registry and pull the
myrctlimage you built.Read-only token for myriarchd: create a deploy token with the scope
read_repositoryonly. User name and token belong into the myriarchd configuration (gitops.auth.usernameand the Secret keygit-token, seedeploy/examples/production). Never write the token into this repository.Check in myriarchd:
GET /api/v1/gitops/statusshowsstate: okand the fleets appear in the UI under Fleets.
Sync: webhook, CI job, polling
myriarchd picks up changes in three ways; the first hit wins and duplicate triggers are harmless:
- CI job
notify-myriarchd(default in this starter): after every successful run onmainthe pipeline callsPOST $MYRIARCH_URL/api/v1/gitops/webhookwith the headerX-Gitlab-Token. HTTP 202 is expected. Advantage: validation has run before, and the Git server needs no webhook setting. - Native webhook of your Git server (alternative): URL
https://fleet.example.com/api/v1/gitops/webhook, secret token = content ofgitops-webhook-secret, trigger “push events” for the branchmain, TLS verification on. The Git server must reach myriarchd and trust its certificate. Then remove the jobnotify-myriarchdfrom.gitlab-ci.yml, otherwise there are two triggers (harmless, but unnecessary). - Polling (fallback): myriarchd polls the repository itself every 60 s. Without a webhook a change takes up to one minute.
myriarchd accepts a state only if it is completely valid. An invalid state on main is never applied, the old one
stays active, and the notification channel reports gitops.sync_failed. The pipeline should prevent that
beforehand; myrctl and myriarchd use the same code.
Working with the repository
branch -> change in config/ -> merge request -> pipeline (validate, render) -> review -> merge to main
-> notify-myriarchd -> myriarchd syncs -> planner creates rollouts -> waves/approval in the UI
- In the merge request the link “Rendered configs” (artifact
rendered/) shows the collector config per sample agent. Reviewers check the difference to the previous state, not only the blueprint source. - A change to a blueprint, fleet parameters or the inventory creates one config rollout per affected fleet
(
canary-then-all: canary wave, then approval in the UI under Rollouts, then the rest). A changedspec.collector.versioncreates a version rollout (version-canary-then-all). - Never change config and version in the same merge request. If a version update restarts the collector while the config target of a host is in its probation time, the config target fails.
- A rollback (
onFailure: rollbackor manual) contradicts the Git state: the agent showsdriftuntil Git is reset or corrected (new commit). git revertis the normal way to take a change back.
Typical changes
| Goal | File | Change |
|---|---|---|
| Change the gateway address | fleets/*.yaml | parameters.gatewayEndpoint |
| Host metric interval | fleets/linux-hosts.yaml | parameters.hostMetricsInterval: 60s |
| Enable journald/log files | fleets/linux-hosts.yaml | parameters.collectJournald: true, logPaths: [...] (host preparation: see the header of blueprints/linux-host.yaml) |
| Collector version | fleets/*.yaml | collector.version: 0.162.0 (a version rollout only, no config change in the same merge request) |
| Backend at the gateway | fleets/k8s-gateway.yaml | parameters.backendEndpoint: https://... |
| Put a host into the canary wave | inventory/waves.yaml | entry with labels: {wave: canary} |
| New fleet | fleets/ | own name, own priority, a matching sample agent in config/samples/agents/ |
Rules for blueprints (GIT-4)
- Never put lists or maps directly into an interpolation.
"http://[[ .params.hosts ]]/x"is atemplate_error. Either write the whole value as one expression ("[[ .params.hosts ]]"- stays a list) or convert it:[[ join "," .params.hosts ]],[[ toJson .params.hosts ]],[[ str .params.hosts ]]. - Values that start with
[[go in quotes (otherwise YAML reads a list:unquoted_template). - Always quote number-like strings:
site.id: "0001", labelswave: "1". - Lists are replaced completely when merging, not extended. Whoever changes
service.pipelines.*.receiverswrites the complete list (see the disjoint fragments inblueprints/linux-host.yaml). - Every component must be in the component list and used in a pipeline or extension list
(
unknown_component/unused_component). The pipeline runs with--strict, warnings are errors. - No secrets in Git: collector secrets as
${env:NAME}/${file:/path}(resolved by the collector on the host), executor secrets by name only (tokenEnv: MYRIARCH_EXEC_AWX_TOKEN).
Validate locally
With the myrctl of a release (archive myrctl_<version>_<os>_<arch>), from the repository root:
myrctl validate --strict --components ci/components-otelcol-contrib-0.162.0.json config # exit 0 = valid
myrctl render --dir config --sample linux-01 # rendered config
myrctl render --dir config --sample linux-01 --param collectJournald=true --param 'logPaths=[/var/log/syslog]'
myrctl render --dir config --sample windows-01 -o json | head # fleet, layers, hash, issues
myrctl render --sample <name> takes the file name without .yaml (= the field name). New sample agents imitate
a real host: attributes as the supervisor and installer report them (site.*, os.type, host.name, host.arch,
k8s.* on Kubernetes) and labels as they would come from the enrollment token. Every fleet needs at least one
matching sample agent, and every sample agent must match a fleet (otherwise sample_unassigned, an error with
--strict).
Switch the collector version
The component list belongs to one collector version. When you change the target version:
- Export the list of the new version from an agent (
GET /api/v1/agents/<id>/components) or take it from the code project (examples/sim/components-otelcol-contrib-<version>.json). - Update
ci/components-...jsonand the variableCOMPONENTSin.gitlab-ci.yml. - Only then set the fleets to the new version.