feat: easier way to share configuration across helmfiles (#522)

This basically allows to define both `releases` and `helmfiles` within a helmfile.yaml, so that you can start using sub-helmfiles easily, by extracting only reused releases.

Resolves #445
This commit is contained in:
KUOKA Yusuke 2019-03-31 22:51:06 +09:00 committed by GitHub
parent 283dac1531
commit 14a392666e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 175 additions and 13 deletions

View File

@ -746,6 +746,10 @@ Use the [Helmfile Best Practices Guide](/docs/writing-helmfile.md) to write adva
- Default values
- Layering
We also have dedicated documentations for the following topics you might be interested:
- [Shared Configurations Across Teams](/docs/shared-configurations-across-teams.md)
## Using env files
Helmfile itself doesn't have an ability to load env files. But you can write some bash script to achieve the goal:

View File

@ -0,0 +1,160 @@
# Shared Configuration Across Teams
Assume you have a two or more teams, each works for a different internal or external service, like:
- Product 1
- Product 2
- Observability
The simplest `helmfile.yaml` that declares the whole cluster that is composed of the three services would look like the below:
```yaml
releases:
- name: product1-api
chart: product1-charts/api
# snip
- name: product1-web
chart: product1-charts/web
# snip
- name: product2-api
chart: saas-charts/api
# snip
- name: product2-web
chart: product2-charts/web
# snip
- name: observability-prometheus-operator
chart: stable/prometheus-operator
# snip
- name: observability-process-exporter
chart: stable/prometheus-operator
# snip
```
This works, but what if you wanted to a separate cluster per service to achieve smaller blast radius?
Let's start by creating a `helmfile.yaml` for each service.
`product1/helmfile.yaml`:
```yaml
releases:
- name: product1-api
chart: product1-charts/api
# snip
- name: product1-web
chart: product1-charts/web
# snip
- name: observability-prometheus-operator
chart: stable/prometheus-operator
# snip
- name: observability-process-exporter
chart: stable/prometheus-operator
# snip
```
`product2/helmfile.yaml`:
```yaml
releases:
- name: product2-api
chart: product2-charts/api
# snip
- name: product2-web
chart: product2-charts/web
# snip
- name: observability-prometheus-operator
chart: stable/prometheus-operator
# snip
- name: observability-process-exporter
chart: stable/prometheus-operator
# snip
```
You will (of course!) notice this isn't DRY.
To remove the duplication of observability stack between the two helmfiles, create a "sub-helmfile" for the observability stack.
`observability/helmfile.yaml`:
```yaml
- name: observability-prometheus-operator
chart: stable/prometheus-operator
# snip
- name: observability-process-exporter
chart: stable/prometheus-operator
# snip
```
As you might have imagined, the observability helmfile can be reused from the two product helmfiles by declaring `helmfiles`.
`product1/helmfile.yaml`:
```yaml
helmfiles:
- ../observability/helmfile.yaml
releases:
- name: product1-api
chart: product1-charts/api
# snip
- name: product1-web
chart: product1-charts/web
# snip
```
`product2/helmfile.yaml`:
```yaml
helmfiles:
- ../observability/helmfile.yaml
releases:
- name: product2-api
chart: product2-charts/api
# snip
- name: product2-web
chart: product2-charts/web
# snip
```
## Using sub-helmfile as a template
You can go even further by generalizing the product related releases as a pair of `api` and `web`:
`product/helmfile.yaml`:
```yaml
releases:
- name: product{{ env "PRODUCT_ID" }}-api
chart: product{{ env "PRODUCT_ID" }}-charts/api
# snip
- name: product{{ env "PRODUCT_ID" }}-web
chart: product{{ env "PRODUCT_ID" }}-charts/web
# snip
```
And reusing it from the two product helmfiles:
`product1/helmfile.yaml`:
```yaml
helmfiles:
- ../observability/helmfile.yaml
- ../product/helmfile.yaml
```
`product2/helmfile.yaml`:
```yaml
helmfiles:
- ../observability/helmfile.yaml
- ../product/helmfile.yaml
```
Now that we use the environment variable `PRODUCT_ID` to as the parameters of release names, you need to set it before running `helmfile`, so that it produces the differently named releases per product:
```console
$ PRODUCT_ID=1 helmfile -f product1/helmfile.yaml apply
$ PRODUCT_ID=2 helmfile -f product2/helmfile.yaml apply
```

View File

@ -110,6 +110,7 @@ func (a *App) visitStateFiles(fileOrDir string, do func(string) error) error {
func (a *App) VisitDesiredStates(fileOrDir string, converge func(*state.HelmState, helmexec.Interface) (bool, []error)) error {
noMatchInHelmfiles := true
err := a.visitStateFiles(fileOrDir, func(f string) error {
content, err := a.readFile(f)
if err != nil {
@ -153,8 +154,6 @@ func (a *App) VisitDesiredStates(fileOrDir string, converge func(*state.HelmStat
}
}
errs := []error{}
if len(st.Helmfiles) > 0 {
noMatchInSubHelmfiles := true
for _, m := range st.Helmfiles {
@ -170,26 +169,25 @@ func (a *App) VisitDesiredStates(fileOrDir string, converge func(*state.HelmStat
}
}
noMatchInHelmfiles = noMatchInHelmfiles && noMatchInSubHelmfiles
} else {
var err error
st, err = st.ExecuteTemplates()
if err != nil {
return fmt.Errorf("failed executing release templates in \"%s\": %v", f, err)
}
var processed bool
processed, errs = converge(st, helm)
noMatchInHelmfiles = noMatchInHelmfiles && !processed
}
return clean(st, errs)
templated, tmplErr := st.ExecuteTemplates()
if tmplErr != nil {
return fmt.Errorf("failed executing release templates in \"%s\": %v", f, tmplErr)
}
processed, errs := converge(templated, helm)
noMatchInHelmfiles = noMatchInHelmfiles && !processed
return clean(templated, errs)
})
if err != nil {
return err
}
if noMatchInHelmfiles {
return &NoMatchingHelmfileError{selectors: a.Selectors, env: a.Env}
}
return nil
}