Skip to content

Commit

Permalink
Merge pull request #482 from garden-io/helm-inheritance
Browse files Browse the repository at this point in the history
feat(k8s): add Helm module inheritance via the `base` field
  • Loading branch information
edvald authored Jan 25, 2019
2 parents 61191ba + 8a7a7e5 commit 5705e5b
Show file tree
Hide file tree
Showing 55 changed files with 693 additions and 572 deletions.
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* [Features and usage](./using-garden/features-and-usage.md)
* [Configuration files](./using-garden/configuration-files.md)
* [Remote Clusters](./using-garden/remote-clusters.md)
* [Using Helm charts](./using-garden/using-helm-charts.md)
* [Hot Reload](./using-garden/hot-reload.md)
* [Example projects](./examples/README.md)
* [Hello world](./examples/hello-world.md)
Expand Down
60 changes: 22 additions & 38 deletions docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,6 @@ module:
# Optional.
repositoryUrl:

# Variables that this module can reference and expose as environment variables.
#
# Example:
# my-variable: some-value
#
# Optional.
variables:
{}

# When false, disables pushing this module to remote registries.
#
# Optional.
Expand Down Expand Up @@ -237,15 +228,6 @@ module:
# Optional.
repositoryUrl:

# Variables that this module can reference and expose as environment variables.
#
# Example:
# my-variable: some-value
#
# Optional.
variables:
{}

# When false, disables pushing this module to remote registries.
#
# Optional.
Expand Down Expand Up @@ -409,15 +391,6 @@ module:
# Optional.
repositoryUrl:

# Variables that this module can reference and expose as environment variables.
#
# Example:
# my-variable: some-value
#
# Optional.
variables:
{}

# When false, disables pushing this module to remote registries.
#
# Optional.
Expand Down Expand Up @@ -801,15 +774,6 @@ module:
# Optional.
repositoryUrl:

# Variables that this module can reference and expose as environment variables.
#
# Example:
# my-variable: some-value
#
# Optional.
variables:
{}

# When false, disables pushing this module to remote registries.
#
# Optional.
Expand Down Expand Up @@ -858,15 +822,29 @@ module:
# Optional.
target:

# A valid Helm chart name or URI. Required if the module doesn't contain the Helm chart itself.
# The name of another `helm` module to use as a base for this one. Use this to re-use a Helm
# chart across multiple services. For example, you might have an organization-wide base chart
# for certain types of services.
# If set, this module will by default inherit the following properties from the base module:
# `serviceResource`, `values`
# Each of those can be overridden in this module. They will be merged with a JSON Merge Patch
# (RFC 7396).
#
# Example: "my-base-chart"
#
# Optional.
base:

# A valid Helm chart name or URI (same as you'd input to `helm install`). Required if the module
# doesn't contain the Helm chart itself.
#
# Example: "stable/nginx-ingress"
#
# Optional.
chart:

# The path, relative to the module path, to the chart sources (i.e. where the Chart.yaml file
# is, if any).
# is, if any). Not used when `base` is specified.
#
# Optional.
chartPath: .
Expand Down Expand Up @@ -942,6 +920,12 @@ module:
hotReloadArgs:
-

# Set this to true if the chart should only be built, but not deployed as a service. Use this,
# for example, if the chart should only be used as a base for other modules.
#
# Optional.
skipDeploy: false

# The task definitions for this module.
#
# Optional.
Expand Down
97 changes: 97 additions & 0 deletions docs/using-garden/using-helm-charts.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,100 @@ module:
For hot-reloading to work you must specify `serviceResource.containerModule`, so that Garden knows which module contains the sources to use for hot-reloading. You can then optionally add `serviceResource.hotReloadArgs` to, for example, start the container with automatic reloading or in development mode.

For the above example, you could then run `garden deploy -w --hot-reload=vote` or `garden dev --hot-reload=vote` to start the `vote` service in hot-reloading mode. When you then change the sources in the _vote-image_ module, Garden syncs the changes to the running container from the Helm chart.

## Re-using charts

Often you'll want to re-use the same Helm charts for multiple modules. For example, you might have a generic template
for all your backend services that configures auto-scaling, secrets/keys, sidecars, routing and so forth, and you don't
want to repeat those configurations all over the place.

You can achieve this by using the `base` field on the `helm` module type. Staying with our `vote-helm` example project,
let's look at the `base-chart` and `api` modules:

```yaml
# base-chart
module:
description: Base Helm chart for services
type: helm
name: base-chart
serviceResource:
kind: Deployment
skipDeploy: true
```

```yaml
# api
module:
description: The API backend for the voting UI
type: helm
name: api
base: base-chart
serviceResource:
containerModule: api-image
dependencies:
- redis
values:
name: api
image:
repository: api-image
tag: ${modules.api-image.version}
ingress:
enabled: true
paths: [/]
hosts: [api.local.app.garden]
```

Here, the `base-chart` module contains the actual Helm chart and templates. Note the `skipDeploy` flag, which we set
because the module should only be used as a base chart in this case.

The `api` module only contains the `garden.yml` file, but configures the base chart using the `values` field, and also
sets its own dependencies (those are not inherited) and specifies its `serviceResource.containerModule`.

In our base chart, we make certain values like `name`, `image.repository` and `image.tag` required (using the
[required](https://github.com/helm/helm/blob/master/docs/charts_tips_and_tricks.md#using-the-required-function)
helper function) in order to enforce correct usage. We recommend enforcing constraints like that, so that mistakes
can be caught quickly.

The `result` module also uses the same base chart, but sets different values and metadata:

```yaml
module:
description: Helm chart for the results UI
type: helm
name: result
base: base-chart
serviceResource:
containerModule: result-image
hotReloadArgs: [nodemon, server.js]
dependencies:
- db-init
values:
name: result
image:
repository: result-image
tag: ${modules.result-image.version}
ingress:
enabled: true
paths: [/]
hosts: [result.local.app.garden]
tests:
- name: integ
args: [echo, ok]
dependencies:
- db-init
```

This pattern can be quite powerful, and can be used to share common templates across your organization. You could
even have an organization-wide repository of base charts for different purposes, and link it in your project config
with something like this:

```yaml
project:
sources:
- name: base-charts
repositoryUrl: https://github.com/my-org/helm-base-charts.git#v0.1.0
...
```

The base chart can also be any `helm` module (not just "base" charts specifically made for that purpose), so you have
a lot of flexibility in how you organize your charts.
8 changes: 7 additions & 1 deletion examples/vote-helm/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
# Voting example project with Helm charts

This is a clone of the [vote example project](../vote/README.md), modified to use Helm charts to describe
Kubernetes resources, instead of the `container` module type.
Kubernetes resources, instead of the simpler `container` module type.

You'll notice that we still use the `container` module types for building the container images (the corresponding
`*-image` next to each service module), but they do not contain a `service` section.

The `helm` modules only contain the charts, which reference the container images. Garden will build the images
ahead of deploying the charts.

Furthermore, to showcase the chart re-use feature, the `api` and `result` modules use the `base-chart` module
as a base.

For more details on how to use Helm charts, please refer to our
[Helm user guide](https://docs.garden.io/using-garden).

The usage and workflow is the same as in the [vote project](../vote/README.md), please refer to that for usage
instructions.
4 changes: 3 additions & 1 deletion examples/vote-helm/api/garden.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ module:
description: The API backend for the voting UI
type: helm
name: api
base: base-chart
serviceResource:
kind: Deployment
containerModule: api-image
dependencies:
- redis
values:
name: api
image:
repository: api-image
tag: ${modules.api-image.version}
ingress:
enabled: true
Expand Down
32 changes: 0 additions & 32 deletions examples/vote-helm/api/templates/_helpers.tpl

This file was deleted.

44 changes: 0 additions & 44 deletions examples/vote-helm/api/templates/deployment.yaml

This file was deleted.

File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
apiVersion: v1
appVersion: "1.0"
description: A Helm chart for Kubernetes
name: api
name: base-chart
version: 0.1.0
7 changes: 7 additions & 0 deletions examples/vote-helm/base-chart/garden.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module:
description: Base Helm chart for services
type: helm
name: base-chart
serviceResource:
kind: Deployment
skipDeploy: true
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "api.fullname" . }})
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "base-chart.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get svc -w {{ include "api.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "api.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
You can watch the status of by running 'kubectl get svc -w {{ include "base-chart.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "base-chart.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "api.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "base-chart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80
{{- end }}
Loading

0 comments on commit 5705e5b

Please sign in to comment.