Consolidates the five cogarchhubgitops-* source skills into one skill with scope routing: ArgoCD Applications, Copier templates, Helm values, environment answer files, and the end-to-end resource-addition walkthrough. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
266 lines
8.4 KiB
Markdown
266 lines
8.4 KiB
Markdown
# Environment Configuration
|
|
|
|
Per-environment `copier-answers.yaml` files: feature flags, image tags, global application
|
|
settings, drift between environments, and re-rendering with `copier update`.
|
|
|
|
Read this when enabling or disabling a resource for one environment, onboarding a new
|
|
environment, auditing what is switched on, or investigating why two environments differ.
|
|
|
|
---
|
|
|
|
## 1. How environments work
|
|
|
|
Each deployed environment is a rendered copy of the template repository. The rendering is
|
|
driven by an answer file supplying environment-specific values for the variables declared in
|
|
`copier.yaml`:
|
|
|
|
```text
|
|
Template repo
|
|
+ <env>/copier-answers.yaml
|
|
─────────────────────────────
|
|
→ rendered cluster state (what ArgoCD reads)
|
|
```
|
|
|
|
The rendered state normally lives on a separate branch or repository per environment. The
|
|
answer files themselves live at `<env>/copier-answers.yaml` in the template repo.
|
|
|
|
---
|
|
|
|
## 2. Answer file structure
|
|
|
|
Copier maintains two metadata fields automatically; everything else is user-defined:
|
|
|
|
```yaml
|
|
_src_path: ../ # relative path to the template source
|
|
_commit: <git-commit-sha> # template commit used for the last render
|
|
|
|
gitops:
|
|
repo_url: https://github.com/org/repo
|
|
branch: main
|
|
|
|
operators:
|
|
is_enabled: true
|
|
eso_is_enabled: true
|
|
cnpg_is_enabled: false
|
|
redis_is_enabled: false
|
|
|
|
configuration:
|
|
is_enabled: true
|
|
cluster_secret_store_is_enabled: true
|
|
|
|
applications:
|
|
is_enabled: true
|
|
global:
|
|
is_internal: "false"
|
|
ca_env: production
|
|
host: myapp.example.com
|
|
```
|
|
|
|
Keys must match `copier.yaml` exactly. Copier **silently ignores unknown keys** — a typo does
|
|
not error, it just leaves the variable at its default, which is the most common cause of a
|
|
"why is this environment misconfigured" investigation.
|
|
|
|
---
|
|
|
|
## 3. Variable categories
|
|
|
|
### Feature flags
|
|
|
|
Flags follow `<domain>.is_enabled` and `<domain>.<sub>_is_enabled`:
|
|
|
|
| Variable | Effect when true |
|
|
|---|---|
|
|
| `operators.is_enabled` | Renders the `200-operators` band |
|
|
| `operators.eso_is_enabled` | Renders ESO operator group, subscription, configuration |
|
|
| `operators.cnpg_is_enabled` | Renders CNPG operator group and subscription |
|
|
| `operators.redis_is_enabled` | Renders Redis operator group and subscription |
|
|
| `configuration.is_enabled` | Renders the `300-configuration` band |
|
|
| `configuration.cluster_secret_store_is_enabled` | Renders ClusterSecretStore |
|
|
| `databases.is_enabled` | Renders the `400-databases` band |
|
|
| `advantage_mcp.is_enabled` | Renders the `700-advantage-mcp` band |
|
|
| `applications.is_enabled` | Renders the `800-applications` band |
|
|
|
|
**Parent flags gate whole bands.** With `operators.is_enabled: false`, every sub-flag under
|
|
`operators` is irrelevant — the entire `200-operators` directory is omitted regardless of
|
|
what the sub-flags say. When a resource unexpectedly fails to render, check the parent flag
|
|
before the specific one.
|
|
|
|
### Image variables
|
|
|
|
```yaml
|
|
<domain>:
|
|
<service>:
|
|
image:
|
|
address: <registry>/<image-name>
|
|
tag: <semver-or-sha>
|
|
```
|
|
|
|
Every enabled workload needs non-empty values for both.
|
|
|
|
### Global application settings
|
|
|
|
`applications.global` carries settings shared by all services in the release:
|
|
|
|
```yaml
|
|
applications:
|
|
global:
|
|
is_internal: "true" # string bool
|
|
enable_instana: "false" # string bool
|
|
ca_secret_name: ca-secret
|
|
ca_env: <env-name> # dev | staging | production
|
|
maintenance: "false" # string bool
|
|
host: <public-hostname>
|
|
ca_host: <internal-hostname>
|
|
suffix_url: <path-suffix>
|
|
node_selector_app: <node-label>
|
|
mongo:
|
|
host: <mongodb-host>
|
|
port: "27017"
|
|
user: <mongodb-user>
|
|
redis:
|
|
host: <redis-host>
|
|
port: "6379"
|
|
max_retries: "3"
|
|
neo4j:
|
|
url: <neo4j-bolt-url>
|
|
user: <neo4j-user>
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Booleans: two different kinds
|
|
|
|
This trips people up constantly:
|
|
|
|
| Kind | Written as | Why |
|
|
|---|---|---|
|
|
| Copier feature flag (`*_is_enabled`) | YAML boolean `true` / `false` | Evaluated as a Jinja2 conditional deciding whether a directory renders |
|
|
| Helm string-bool (`is_internal`, `maintenance`, `enable_instana`) | Quoted string `"true"` / `"false"` | The values templates emit them as strings and the chart reads them as strings |
|
|
|
|
A feature flag written as `"false"` is a **non-empty string** — truthy in Jinja2 — so the
|
|
resource renders when you meant to disable it. This fails silently and deploys something.
|
|
|
|
---
|
|
|
|
## 5. Rules
|
|
|
|
### Answer files
|
|
|
|
- `_src_path` and `_commit` are Copier-managed; never edit them by hand.
|
|
- Keep answer files minimal — only override what differs from the `copier.yaml` default.
|
|
- Never commit real secrets. Use placeholders and inject at runtime via ExternalSecret.
|
|
|
|
### Multi-environment
|
|
|
|
- One answer file per environment; never share a file across environments.
|
|
- When adding a variable to `copier.yaml`, update every answer file needing a non-default
|
|
value — the rest correctly fall back to the default.
|
|
|
|
### Enabling a resource
|
|
|
|
1. Set the flag to `true`.
|
|
2. Supply **all** required image and configuration variables for that resource.
|
|
3. Re-render the environment.
|
|
4. Review the rendered diff before committing.
|
|
|
|
Skipping step 2 renders a values file with empty fields; Helm then fails at sync with an
|
|
error that names the chart, not the answer file, which makes it slow to trace back.
|
|
|
|
### Disabling a resource
|
|
|
|
1. Set the flag to `false`.
|
|
2. Re-render.
|
|
3. ArgoCD prunes the Application and its cluster resources on the next sync, because
|
|
`prune: true` is set. This is destructive and immediate — confirm the resource is really
|
|
meant to leave the cluster, not just be paused.
|
|
|
|
---
|
|
|
|
## 6. Workflows
|
|
|
|
### New environment
|
|
|
|
```bash
|
|
mkdir <new-env>
|
|
|
|
# Option A — interactive prompts
|
|
copier copy . /tmp/gitops-<new-env> --answers-file <new-env>/copier-answers.yaml
|
|
|
|
# Option B — start from the nearest existing environment
|
|
cp <base-env>/copier-answers.yaml <new-env>/copier-answers.yaml
|
|
# then edit for environment-specific values
|
|
```
|
|
|
|
Option B is faster but carries over every override from the source environment; walk the
|
|
whole file rather than only the fields you came to change.
|
|
|
|
### Re-render after a template change
|
|
|
|
```bash
|
|
copier update /path/to/rendered-<env> \
|
|
--answers-file <env>/copier-answers.yaml \
|
|
--overwrite
|
|
|
|
git -C /path/to/rendered-<env> diff
|
|
```
|
|
|
|
### Compare two environments
|
|
|
|
```bash
|
|
copier copy . /tmp/env-a --overwrite --answers-file env-a/copier-answers.yaml
|
|
copier copy . /tmp/env-b --overwrite --answers-file env-b/copier-answers.yaml
|
|
diff -rq /tmp/env-a/cluster /tmp/env-b/cluster
|
|
```
|
|
|
|
### Audit which resources are enabled
|
|
|
|
```bash
|
|
python3 -c "
|
|
import yaml, sys
|
|
a = yaml.safe_load(open(sys.argv[1]))
|
|
def walk(d, prefix=''):
|
|
for k, v in (d.items() if isinstance(d, dict) else []):
|
|
if k.endswith('_is_enabled') or k == 'is_enabled':
|
|
print(f'{prefix}{k}: {v}')
|
|
elif isinstance(v, dict):
|
|
walk(v, prefix + k + '.')
|
|
walk(a)
|
|
" <env>/copier-answers.yaml
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Validation
|
|
|
|
```bash
|
|
# Render the environment
|
|
copier copy . /tmp/gitops-validate --overwrite --answers-file <env>/copier-answers.yaml
|
|
|
|
# No .jinja may leak through
|
|
find /tmp/gitops-validate -name "*.jinja" | wc -l # must be 0
|
|
|
|
# Every rendered YAML parses
|
|
find /tmp/gitops-validate -name "*.yaml" -exec \
|
|
python3 -c "import yaml, sys; yaml.safe_load(open(sys.argv[1])); print('OK:', sys.argv[1])" {} \;
|
|
|
|
# Every Application has its resources directory
|
|
for app in $(find /tmp/gitops-validate/cluster/argocd -name "application.yaml" -exec \
|
|
python3 -c "import yaml,sys; print(yaml.safe_load(open(sys.argv[1]))['spec']['source']['path'])" {} \;); do
|
|
[ -d "/tmp/gitops-validate/$app" ] || echo "MISSING resources dir: $app"
|
|
done
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Mistakes to avoid
|
|
|
|
- Editing `_src_path` or `_commit` — breaks `copier update`.
|
|
- Enabling a flag without supplying the resource's image and configuration variables.
|
|
- Writing a feature flag as `"true"` instead of `true` — truthy either way, so it renders
|
|
when disabled was intended.
|
|
- Editing an answer file without re-rendering and reviewing the diff.
|
|
- Setting `applications.global` addresses (host, mongo, redis, neo4j) without confirming the
|
|
environment's real infrastructure — wrong values misconfigure every service silently.
|
|
- Putting environment secrets in the answer file rather than an external secret manager.
|
|
- Assuming a sub-flag controls a resource when its parent band flag is `false`.
|