Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation for helmfile DRYness lacks...and maybe not even DRY at all... #2036

Closed
notjames opened this issue Jan 2, 2022 · 8 comments
Closed
Labels

Comments

@notjames
Copy link

notjames commented Jan 2, 2022

I had to vent because I'm so frustrated with helmfile.

I'm really struggling to wrap my head around how to implement this project of mine for helmfile. I'm going to try really hard to articulate my problems here, however one of the problems I have with helmfile is the documentation does not do a great job explaining the basics, which I get! This is a project that has developed and changed over time. It's changed and been fixed and features have been added and the documentation is not in just one place...it's all over the place in github issues and I think there are aspects of helmfile which are just not documented at all. These aspects of the tool are where my frustrations seem to come.

Here's my project.

general directory structure

My current directory structure (only directories shown here...no files)

├── charts
│   ├── bcdr-mgmt
│   │   └── chart
│   ├── mycompany-charts
│   │   └── chart
│   ├── cluster-mgmt
│   │   ├── chart
│   │   ├── chart
│   │   └── chart
│   ├── database-mgmt
│   │   ├── chart
│   │   ├── chart
│   │   └── chart
│   ├── logging-and-metrics-mgmt
│   │   ├── chart
│   │   ├── chart
│   │   └── chart
│   ├── network-mgmt
│   │   ├── chart
│   │   ├── chart
│   │   ├── chart
│   │   └── chart
│   ├── policy-mgmt
│   │   ├── chart
│   │   └── chart
│   ├── release-mgmt
│   │   └── chart
│   ├── resource-mgmt
│   │   └── chart
│   ├── sec-mgmt
│   │   ├── company-eks-aux
│   │   ├── chart
│   │   ├── chart
│   │   └── chart
│   └── storage-mgmt
│       └── chart
└── environments
    ├── common
    │   ├── lib
    │   └── templates
    ├── devel
    ├── prod
    └── stage

charts directory structure

each directory has its own helmfile.yamland some contain other necessary files. For instance, chart directories contain respective values.yaml or values.yaml.gotmpl, helmfile.yaml and such files -- an example of a couple of the charts directories looks like:

.                                                                                                                                                                                                                                                                                     
├── charts                                                                                                                                                                                                                                                                            
│   ├── bcdr-mgmt                                                                                                                                                                                                                                                                     
│   │   ├── helmfile.yaml                                                                                                                                                                                                                                                             
│   │   └── chart                                                                                                                                                                                                                                                                
│   │       ├── helmfile.yaml                                                                                                                                                                                                                                                         
│   │       └── values.yaml.gotmpl                                                                                                                                                                                                                                                    
│   ├── secrets-mgmt                                                                                                                                                                                                                                                                
│   │   ├── helmfile.yaml                                                                                                                                                                                                                                                             
│   │   ├── chart                                                                                                                                                                                                                                                     
│   │   │  ├── helmfile.yaml                                                                                                                                                                                                                                                         
│   │   │  └── values.yaml.gotmpl
│   │   └── chart                                                                                                                                                                                                                                                     
│   │       ├── helmfile.yaml                                                                                                                                                                                                                                                         
│   │       └── values.yaml.gotmpl
...
...
...

charts helmfile samples

My charts are broken up into scopes of types of charts under which respective charts exist in their own directories. Let's take a look at a couple of my charts under one of the so-called "tiers" I have set up here...promtail and kube-prometheus-stack

promtail

✦ at 12:54:49 ❯ \cat helmfile.yaml
---
releases:
  - name: promtail
    namespace: {{ .Values.promtail.namespace }}
    createNamespace: true
    labels:
      app: promtail
      tier: {{ .values.promtail.tier }}
    chart: {{ .Values.promtail.chart_repo }}
    version: {{ .Values.promtail.chart_version }}
    values:
      - values.yaml.gotmpl

kube-prometheus-stack

✦ at 12:50:41 ❯ \cat helmfile.yaml 
---
releases:
  - name: k8s-prometheus-adapter
    namespace: {{ .Values.k8s_prometheus_adapter.namespace }}
    createNamespace: true
    labels:
      app: k8s-prometheus-adapter
      tier: {{ .Values.k8s_prometheus_adapter.tier }}
    chart: {{ .Values.k8s_prometheus_adapter.chart_repo }}
    version: {{ .Values.k8s_prometheus_adapter.chart_version }}
    values:
      - values.yaml.gotmpl

environment directories and helmfile samples

In order to mildly understand what I have here, it's prudent I give some data into part of the environment directory structure I'm using. The intention is that I run helmfile from any given environment directory such as test-cluster to set up helm charts for that cluster.

starting from <repo>/environments (the top directory of environments):

✦2 at 09:16:05 ❯ pwd
<repo>/helmfile-project/environments

✦2 at 09:16:06 ❯ ls -ltr
drwxrwxr-x jimconn jimconn 4.0 KB Tue Jan  4 15:32:14 2022  prod
drwxrwxr-x jimconn jimconn 4.0 KB Tue Jan  4 15:32:18 2022  stage
drwxrwxr-x jimconn jimconn 4.0 KB Wed Jan  5 15:40:19 2022  devel
drwxrwxr-x jimconn jimconn 4.0 KB Thu Jan  6 08:33:04 2022  common
.rw-rw-r-- jimconn jimconn 171 B  Thu Jan  6 09:04:48 2022  helmfile.yaml

✦2 at 09:16:09 ❯ \cat helmfile.yaml
---
bases:
  - common/global-default.env.yaml.gotmpl
  - common/helm-defaults.yaml
  - common/repos.yaml
  - common/versions.yaml

helmfiles:
  - path: "../helmfile.yaml"



## traverse one directory down into 'devel':

✦2 at 09:13:51 ❯ pwd
<repo>/projects/helmfile-project/environments/devel

## the directory/file structure

✦2 at 09:14:07 ❯ ls -altr
drwxrwxr-x jimconn jimconn 4.0 KB Tue Jan  4 11:30:01 2022  cluster2
drwxrwxr-x jimconn jimconn 4.0 KB Tue Jan  4 11:30:01 2022  cluster1
drwxrwxr-x jimconn jimconn 4.0 KB Wed Jan  5 15:40:19 2022  .
drwxrwxr-x jimconn jimconn 4.0 KB Thu Jan  6 08:32:54 2022  test-cluster
drwxrwxr-x jimconn jimconn 4.0 KB Thu Jan  6 09:04:48 2022  ..
.rw-rw-r-- jimconn jimconn  72 B  Wed Jan  5 15:40:19 2022  helmfile.yaml

## the helmfile

✦2 at 09:14:15 ❯ \cat helmfile.yaml
---
environments:
  default:

---
helmfiles:
- path: "../helmfile.yaml"


## traverse one directory down into my current test-cluster

✦2 at 09:11:28 ❯ cd test-cluster
✦2 at 09:11:28 ❯ pwd
<repo>/helmfile-project/environments/devel/test-cluster

## the directory/file structure

✦2 at 09:11:33 ❯ ls -ltr
.rw-rw-r-- jimconn jimconn 109 B Wed Jan  5 15:38:16 2022  helmfile.yaml
.rw-rw-r-- jimconn jimconn 247 B Thu Jan  6 08:32:54 2022  env-toggles.yaml


## the helmfile

✦2 at 09:11:34 ❯ \cat helmfile.yaml 
---
environments:
  default:
    values:
    - "env-toggles.yaml"

---
helmfiles:
- path: "../helmfile.yaml"


## the current file from which I desire to set a bunch of stuff for a release for this cluster. This file is in a test-status so the stuff in it here is just gobbledy-gook.

✦2 at 09:12:51 ❯ \cat env-toggles.yaml
---
cluster:
  domain_name: jimtest.dev.company
  id: jimconn

### more stuff here but it's commented out

DRY but not DRY

So one problem I'm running into is that these two examples both require a bases: object. I tried putting the bases: object contained in a helmfile.yaml in the parent directory assuming it would/could be inherited because the bases: object is the same for all of my charts. This just didn't work and I had to put bases: object in every. single. helmfile.yaml for each chart. This is NOT DRY and documentation cannot be found for an actual DRY way to accomplish this. This is just one example. I have many but I'm not able to write them up yet. This should be sufficient for now.

global configuration...

So my first attempts at getting something to build was trying to ingest a global manifest for the test-cluster...

✦2 at 09:30:28 ❯ \cat env-toggles.yaml 
---
cluster:
  domain_name: jimtest.dev.company
  id: jimconn
#enable:
#  arm64_support: foobar
#  aws_nlb_with_tls_termination_at_lb: false
#  argocd: true
#  dex: true
#  ambassador: true
#  ingress_nginx: true
#  opa_policy_manager: false

which is pulled in from the bases: object two directories up from the test-cluster directory (see below). When I attempt to build, however, the values in the env-toggles.yaml does not propagate to the bases: object common/global-default.env.yaml.gotmpl which is what consumes the values from env-toggles.yaml.

✦2 at 09:32:58 ❯ \cat ../../helmfile.yaml
---
bases:
# - common/global-default.env.yaml.gotmpl
  - common/helm-defaults.yaml
  - common/repos.yaml
  - common/versions.yaml

helmfiles:
  - path: "../helmfile.yaml"

I'm not really sure how to solve this. I had to comment out the line to continue test building my project...

More DRY-not-DRY I think

Moving on...

I seem to recall in previous conversations with @mumoshu that since I have separate directories separating out aspects of my project that I have to pass values from the top-most configurations down to the charts or else nothing propagates. As I am refactoring my project today, that seems to likely be the issue into which I'm running?

Given my test-cluster from where I'm running helmfile, I have a common/{repos,versions}.yaml and common/global-default.env.yaml.gotmpl which contains global values and is intended to propagate those values across the project based on environment settings per environment (e.g. test-cluster, cluster1, cluster2, etc)...

Now when I run helmfile with this setup so far, I expect that my versions will propagate to my charts. However, I get the following error:

2 at 08:34:07 ❯ helmfile --debug build                                                                                                                                                                                                                                                          
processing file "helmfile.yaml" in directory "."                                                                                                                                                                                                                                                 
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>                                                                                                                                                                                       
first-pass uses: &{default map[] map[]}                                                                                                                                                                                                                                                          
first-pass rendering output of "helmfile.yaml.part.0":                                                                                                                                                                                                                                           
 0: ---                                                                                                                                                                                                                                                                                          
 1: environments:                                                                                                                                                                                                                                                                                
 2:   default:                                                                                                                                                                                                                                                                                   
 3:     values:                                                                                                                                                                                                                                                                                  
 4:     - "env-toggles.yaml"                                                                                                                                                                                                                                                                     
 5:                                                                                                                                                                                                                                                                                              
...
...
...
second-pass rendering result of "common/versions.yaml.part.0":                                                                                                                                                                                                                                   
 0: ---                                                                                                                                                                                                                                                                                          
 1: values:                                                                                                                                                                                                                                                                                      
 2:   -                                                                                                                                                                                                                                                                                          
 3:     semver: 0.0.1                                                                                                                                                                                                                                                                            
 4:     name: somename                                                                                                                                                                                                                                                                           
 5:                                                                                                                                                                                                                                                                                              
 6:     ambassador:                                                                                                                                                                                                                                                                              
 7:       namespace: ambassador                                                                                                                                                                                                                                                                  
 8:       chart_version: 6.6.2                                                                                                                                                                                                                                                                   
 9:       chart_repo: datawire/
...
...
...
merged environment: &{default map[] map[ambassador:map[chart_repo:datawire/ambassador chart_version:6.6.2 ...]]}
processing file "helmfile.yaml" in directory ".."                                                                                                                                                                                                                                                
changing working directory to "<repo>/helmfile-project" 
...
...
...
template syntax error: template: stringTemplate:3:25: executing "stringTemplate" at <.Values.ambassador.namespace>: nil pointer evaluating interface {}.namespace
first-pass rendering output of "helmfile.yaml.part.1":
 0: releases:
 1:   - name: ambassador
 2:     namespace: 

first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile.yaml.part.1": {default map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering failed, input of "helmfile.yaml.part.1":
 0: releases:
 1:   - name: ambassador
 2:     namespace: {{ .Values.ambassador.namespace }}
 3:     createNamespace: true
 4:     labels:
 5:       app: ambassador
 6:       tier: secrets-management
 7:     chart_repo: {{ .Values.ambassador.chart_repo }}
 8:     chart_version: {{ .Values.ambassador.chart_version }}
 9:     values:
10:       - values.yaml.gotmpl
11: 
...
...
...
in ./helmfile.yaml: in .helmfiles[0]: in ../helmfile.yaml: in .helmfiles[0]: in ../helmfile.yaml: in .helmfiles[0]: in ../helmfile.yaml: in .helmfiles[0]: in charts/helmfile.yaml: in .helmfiles[0]: in 10-cluster-mgmt/helmfile.yaml: in .helmfiles[0]: in ambassador/helmfile.yaml: error during helmfile.yaml.part.1 parsing: template: stringTemplate:3:25: executing "stringTemplate" at <.Values.ambassador.namespace>: map has no entry for key "ambassador"

To sum up...what's happening here is that helmfile reads my repos.yaml and versions.yaml manifests. The versions.yaml contains version metadata for all of my charts. When the helmfile build finishes its prelim run through its templates and finally gets to the point where it will build charts, it fails to continue because most if not everything in built is seemingly not available to the charts IE versions (see sample above).

I'm not sure what I'm doing wrong here. However, this is exactly what I'm talking about with helmfile. These kinds of use-cases are not addressed in documentation well enough if at all, as far as I can find. However, I think I have to enter these values in each of the helmfile.yaml manifests in each directory to "pass" the values up the chain. I'll test that now and if that works, this is an aspect I've not found in any documentation anywhere. Moreover, it seems an unfortunate requirement if in fact that is what needs to be done to make this work because such causes, in my judgement, this aspect of helmfile to not be DRY.

I don't understand the inner-workings of helmfile. My supposition is that it tries to do a LOT under the covers but as such the complexity of how to successfully deploy a large project produces an unfortunate barrier to entry or a more complex project for supporting team engineers to manage because there's seemingly a lot of unintuitive stuff required to make helmfile work as expected.


I've been reading through the following:

I cannot track every single sample, example, question/issue, and documented case in these issues. This is extremely frustrating because I really want to find a solution to this problem..

@mumoshu
Copy link
Collaborator

mumoshu commented Jan 10, 2022

bases:
# - common/global-default.env.yaml.gotmpl
  - common/helm-defaults.yaml
  - common/repos.yaml
  - common/versions.yaml

helmfiles:
  - path: "../helmfile.yaml"

This seems problematic as you're basically throwing away all the environment values defined in those base helmfile configs before calling ../helmfile.yaml.

Maybe what you want would be to dynamically build the environment values to be passed to ../helmfile.yaml:

bases:
# - common/global-default.env.yaml.gotmpl
  - common/helm-defaults.yaml
  - common/repos.yaml
  - common/versions.yaml

---

helmfiles:
  - path: "../helmfile.yaml"
    values: {{ .Values | toYaml | nindent 6 }}

In contrast, the below doesn't work:

bases:
# - common/global-default.env.yaml.gotmpl
  - common/helm-defaults.yaml
  - common/repos.yaml
  - common/versions.yaml

helmfiles:
  - path: "../helmfile.yaml"
    values: {{ .Values | toYaml | nindent 6 }}

--- is important. It is required to avoid chicken-and-egg problem. The existence of {{ .Values }} means helmfile needs to render the helmfile.yaml before parsing it as YAML. But to render it helmfile needs to to read bases, which is impossible.
Just remember to add --- to create a rendering pipeline within a helmfile.yaml file.

It is complex, I see your frustration, but this is what everyone asked us to do!
We can't just make it magically simple to accommodate your mental model.

I hope this clarifies it. Thanks for trying helmfile!

@mumoshu
Copy link
Collaborator

mumoshu commented Jan 10, 2022

Please also see #762 for the discussion about adding inheritValues under helmfiles[] entries to automatically inherit environment values from the parent to children.

@mumoshu
Copy link
Collaborator

mumoshu commented Jan 10, 2022

Okay so I found one more topic in your story.

DRY but not DRY

Helmfile doesn't have a "global" configuration and that limits the level of DRY that Helmfile can achieve.
But that's by design.

Helmfile tries its best to make each helmfile.yaml independently consumable. It becomes impossible Helmfile had a magical convention to load some global state from a random file in the file hierarchy.

So the best thing you can do is to have a single base.yaml that aggregates all the base configs you want, and include that single base.yaml in every helmfile.yaml you use.

@mumoshu
Copy link
Collaborator

mumoshu commented Jan 10, 2022

Trying to complex thing is complex. There isn't magic.

If you're going to outgrow what helmfile provides, I'd recommend using more expressive language for compiling helmfile.yaml files and just use helmfile as an executor. A template language like Helmfile template is definitely not a perfect solution for a large-scale work.

I've used Helmfile happily for an env with dozens of projects. I had to repeat some bases in every helmfile.yaml as you've experienced. But it enabled me to easily track what each leaf helmfile.yaml is depending on, which helped code reading. I consider this restriction is a nice thing to have.

I believe our documentation already covers what it can. It's missing information to set a correct expectation, though?

I closed this issue as I've answered your questions and this issue itself is too big to be actionable if took as a documentation enhancement request.

But if you're really willing to contribute to our documentation, I'd appreciate it if you could create smaller hence more actionable issues, like the one that helps setting a correct expectation on what helmfile can do or not. Thanks.

@notjames
Copy link
Author

I appreciate the feedback and assistance @mumoshu. Let me read through these more to grok what you're saying. The more I can grasp more fully the points you're making then the better I can articulate actionable requests and then perhaps even assist in documentation.

@mumoshu
Copy link
Collaborator

mumoshu commented Jan 11, 2022

@notjames Thanks for confirming! I hope it works for you now and I'm looking forward to your contributions ☺️

@notjames
Copy link
Author

I think this is where I get lost. The documentation for helmfile indicates a great deal of flexibility, but mostly referentially. That makes sense looking at things from your perspective. From the consumer perspective, it's quite complex and unclear in places.

So, the biggest question I have which could clear a lot of FUD for me is how the --- is "understood" or from a data model perspective, how does it affect helmfile's runtime. I've never seen that spelled out anywhere.

@mumoshu
Copy link
Collaborator

mumoshu commented Jan 12, 2022

@notjames --- originates from a standard YAML document separator. Helmfile extended it to be a separator within helmfile.yaml.

Helmfile splits the target helmfile.yaml by ---, so that each split becomes a "part" of helmfile.yaml. You can see the word part in the logs if you run helmfile with helmfile --debug [apply|template|etc].

Helmfile executes each part one by one. Execution of each Helmfile part is done by rendering it as a Go template first, and then parsing the render output as a YAML document, converting the YAML document into a Helmfile config.

The previous Helmfile config is available to the next part when rendering the Go template. That's why separating bases part and other parts with --- can be useful in some cases. By separation, Helmfile can use the bases part to build the render context for the subsequent parts. This nearly impossible without ---(To make it even more complex, helmfile has a historical artifact that is called double-rendering. But it isn't recommended and you shouldn't rely on it. Just use --- when you need to dynamically build render context).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants