Helm chart repositories for everyone. With auto-generated clear instructions on how to add, search, and remove the repository. Right from the repository. Because user documentation matters. Readily available as a Terraform module on GitHub. With a live demo.

Kubernetes and Helm: Release with Ease

Helm charts are great. They allow to deploy even the most complex application into a Kubernetes cluster with a single command and still leave sufficient room for customisation. Once the application is deployed, Helm keeps track of all the assets and makes it easy to upgrade, roll back, and delete releases.

Helm Logo

As the Helm website states

Helm helps you manage Kubernetes applications — Helm Charts helps you define, install, and upgrade even the most complex Kubernetes application.

Charts are easy to create, version, share, and publish — so start using Helm and stop the copy-and-paste madness.

Note that the above quote refers to an earlier version of the Helm website. It seems that the "madness" quote has been removed in recent weeks. However, the point about copy-and-paste is still valid.

Writing Your Own Helm Charts

Another great feature of Helm is that writing your own Helm chart is fairly easy; helm even ships with a command that creates a starter chart.

Container Cranes

It does have its quirks and gotchas, as I've experienced first hand writing Helm charts for an application with a microservice architecture. However, the overall experience was very positive and I'd use Helm and Helm charts again for orchestrating Kubernetes releases.

Helm also comes with built-in commands to package and index charts so that they can be directly made available in a Helm chart repository; the output is a collection of .tgz files containing the packaged Helm charts alongside an index.yaml file containing information about the packaged Helm charts and where to find them. This release process is a natural fit for automation and makes it fairly easy to integrate into a CI/CD pipeline.

Hosting Your Own Helm Chart Repository

With the Helm charts packaged, indexed, and ready to be made available to the world, one question remains: Where do you host the Helm chart repository?

As described in the Helm chart repository guide, a Helm chart repository is in essence a web server capable of serving the repository's index.yaml file at its root along with the corresponding packaged artefacts. That's it.

Bucket

The official Helm chart repository guide also lists several options for hosting a Helm chart repository, including Google Cloud Storage (note that this is where the stable and incubator repositories are hosted), JFrog Artefactory, GitHub pages, and ordinary web servers.

There's also an interesting article on HackerNoon that describes how to use GitHub as a Helm chart repository. While this introduces the versioning of binary artefacts in GitHub, it also allows to keep the source code as well as all packaged assets under the same GitHub control mechanisms.

User Documentation Matters

However, all of the repositories I could find have the user documentation outside of the repository. So, anyone visiting the repository with a browser gets to see a YAML file. While it contains a plethora of information that's of great use to helm, it doesn't necessarily tell me how to add the repository to my helm CLI. Most likely that information is documented somewhere else to which there may not be a link from the Helm chart repository.

Sure, it's easy enough to add a Helm chart repository to your helm CLI once you've done it once or twice but having the user documentation right on the doorstep of the Helm chat repository might make it that bit easier for first time users. For example, how complicated is it to add the Helm chart repository http://dumrauf-helm-chart-repo.s3-website-us-east-1.amazonaws.com/ to your helm CLI?!

Generated User Documentation

Sure, you can also add an index.html file to your Helm chart repository and document the steps to add, search, or remove the Helm chart repository. But this may involve hand crafting the index.html file which can be time consuming and (if you're anything like me) error prone.

What if the user documentation in the index.html file would be auto-generated and uploaded upon creation of the Helm chart repository?

Now, this is what project https://github.com/dumrauf/s3-helm-chart-repo-with-docs was set out to achieve. Helm chart repositories for everyone with clear instructions on how to add, search, and remove the repository. Right from the repository.

Build Your Own Helm Chart Repository

The repository https://github.com/dumrauf/s3-helm-chart-repo-with-docs contains a Terraform module that creates a Helm chart repository using an AWS S3 bucket. The S3 bucket is configured as a static website and contains an auto-generated index.html file with instructions on using and managing the Helm chart repository.

Welding

A live example can be found at http://dumrauf-helm-chart-repo.s3-website-us-east-1.amazonaws.com.

You Have

Before you can create your own Helm chart repository with auto-generated documentation and start serving Helm charts in a matter of seconds, you need

Moreover, you probably also have a directory containing some packaged Helm charts along with an index.yaml file ready to be made available to the world.

You Want

After running the Terraform module in the repository you get an S3 bucket that contains auto-generated documentation and can be used to host a Helm chart repository.

Setup

The input variables for the module are defined in settings/example.tfvars to be

region = "<your-region>"

shared_credentials_file = "/path/to/.aws/credentials"

profile = "<your-profile>"

bucket_name = "<your-bucket-name>"

repository_name = "<your-repository-name>"

log_bucket_id = "<your-log-bucket-id>"

Here, you need to replace the example values with your settings.

Moreover, note that the S3 bucket will be named ${var.repository_name}-${var.bucket_name}.

Execution

Initialise Terraform by running

terraform init

As a best practice, create a new workspace by running

terraform workspace new example

The Helm chart repository can be planned by running

terraform plan -var-file=settings/example.tfvars

and created by running

terraform apply -var-file=settings/example.tfvars

Terraform Outputs

The module has CLI outputs as well as template files it generates. Here, the template files contain the auto-generated documentation.

CLI Outputs

The module has two outputs, namely helm_chart_repository_bucket_domain_name and helm_chart_repository_website_endpoint which are the corresponding Terraform attributes of the newly created Helm chart repository bucket. Point your browser to the output of helm_chart_repository_website_endpoint by running

echo "Visit http://$(terraform output helm_chart_repository_website_endpoint)"

in order to visit your new Helm chart repository as well as to review the auto-generated documentation.

Generated Template Files

The module also generates a Markdown file as well as an HTML file. Both files are named ${var.repository_name}-${var.bucket_name}.index.{md,html}. Here, the contents of the HTML file are also uploaded to the S3 bucket as index.html.

Deletion

The Helm chart repository can be deleted by running

terraform destroy -var-file=settings/example.tfvars

Under the Covers - Quirks and Gotchas

This section goes into the details of how the Terraform module works and which quirks and gotchas I experienced while authoring it.

Diver

S3 Bucket with Static Website

The main.tf file is fairly straightforward and contains aws_s3_bucket, aws_iam_policy_document, and aws_s3_bucket_policy Terraform resources at its core as suggested in the official AWS documentation. Here, the trick was to enable static website hosting but also to get the minimum bucket and ACL permissions right.

Permissions

With a bucket policy giving everyone in the world read access to the bucket, I was initially puzzled about the

acl = "public-read"

suggestion on many blog posts around the web. However, this became clear when reading the official AWS documentation that states

The bucket policy applies only to objects that are owned by the bucket owner. If your bucket contains objects that aren't owned by the bucket owner, public READ permission on those objects should be granted using the object access control list (ACL).

S3 Server Access Logging

S3 server access logging is enabled by default. However, it should be noted that AWS operates on a best effort server log delivery model.

HTTP(S) Traffic

Note that for the Helm chart repository, the index.html page is served via the S3 static website endpoint using HTTP while the helm commands in the instructions refer to the REST API endpoint for the S3 bucket using HTTPS. Furthermore, note that SSL connections are not supported for website endpoints.

Auto-Generating the Documentation

Auto-generating the documentation was the most interesting and fun part of this project. It taught me a lot about rendering templates in Terraform as well as Markdown in browsers.

Where to Render the Documentation

The Terraform module creates the S3 bucket and is therefore the ultimate source of truth when it comes to the website endpoint as well as the corresponding REST API endpoint of the S3 bucket. So, any automated documentation generation would have to get this information from Terraform first. One option to retrieve values from Terraform is to use the

terraform output <named-output>

command. This would allow to import the values into a templating system and generate the documentation.

However, the outputs would have to be defined in Terraform first before they could be used. Moreover, the goal was to provide a one-stop solution that creates a Helm chart repository including documentation. This would mean importing the generated documentation back into Terraform again.

There has to be a better way...

The template_file Provider

Enter the template_file provider that does what it says on the tin. Given a template file and some vars, a template_file resource renders the file using the vars provided and makes everything available again in attribute rendered.

This was exactly what I needed since it allowed me to create the documentation directly in Terraform. But, which language to use for the documentation?

Generating Markdown

Generating HTML can be a tricky business. Markdown provides a simple lightweight markup language that can be converted into many formats, including HTML. This was the right language to generate the user documentation in index.md.tpl in.

Generating HTML

However, the generated Markdown also needed to be converted to HTML in order to be displayed in the final index.html file. Here, the idea was to embed the rendered markdown in the HTML file and use a pure in-browser markdown converter to produce the final web page.

This is implemented in template index.html.tpl using Showdown to render the generated markdown entirely in the visitor's browser. Moreover, a GitHub like look and feel of the resulting index.html page was achieved by using a minified GitHub markdown CSS.

The final conversion is eventually done in a few lines of Javascript

<script>
    // Inspired by <https://github.com/showdownjs/showdown/wiki/Tutorial:-Markdown-editor-using-Showdown>
    document.addEventListener('DOMContentLoaded', function () {
        var text = document.getElementById('sourceTA').value,
            target = document.getElementById('renderedMarkdown'),
            converter = new showdown.Converter(),
            html = converter.makeHtml(text);
        target.innerHTML = html;
    }, false);
</script>

Here, the Javascript extracts the markdown from sourceTA, converts it, and appends the resulting HTML to renderedMarkdown.

Saving Auto-Generated Files

For archiving purposes, the markdown and HTML files are both written to disk using the local_file provider. Here, the file names are prefixed with the corresponding Helm chart repository name in order to avoid collisions.

Uploading the index.html File

With the documentation generated, all that remained to do was to upload it to the S3 bucket as the shiny new index.html file using the s3_bucket_object resource. How Hard Can It Be?!

Well, a tad more that initially expected. When accessing http://dumrauf-helm-chart-repo.s3-website-us-east-1.amazonaws.com/ via a browser, the page would load, a download would start and then nothing would happen after that. No page was being displayed on screen. That went well then...

This download-instead-of-serving problem is well known and already has its own StackOverflow post. A comment rightfully outlined the root cause of the problem to be a wrong content type as the following curl command shows

$ curl -I http://dumrauf-helm-chart-repo.s3-website-us-east-1.amazonaws.com/
HTTP/1.1 200 OK
x-amz-id-2: XniS/NDsD/DqNWrH6Z8lYLuNm6iHkPIaNIG1v0UthfIuESYM564Joe9TZClqsCR4SZznUufU88M=
x-amz-request-id: 6D7A5448108BC4F4
Date: Fri, 28 Dec 2018 22:41:42 GMT
Last-Modified: Fri, 28 Dec 2018 22:41:32 GMT
x-amz-version-id: T1vazfgb2Xrt13R9iUcNTUonPlvZ1fYE
ETag: "f948b8368bf7861c1a0836073d9ae132"
Content-Type: binary/octet-stream
Content-Length: 4045
Server: AmazonS3

Note that the Content-Type: binary/octet-stream is clearly off and should much rather be Content-Type: text/html.

The solution did lie in another comment that suggested hard coding the content type to text/html in the aws_s3_bucket_object resource used to upload the file to S3, as in

resource "aws_s3_bucket_object" "helm-chart-repository-index-html" {
  ...
  content_type = "text/html"
}

This solved the problem as the following curl command shows

$ curl -I http://dumrauf-helm-chart-repo.s3-website-us-east-1.amazonaws.com/
HTTP/1.1 200 OK
x-amz-id-2: /61H1mM6U1Z/6OxBjPjJt1y2Xym1wIiufSmdAWTQHs1BJtiVul+fT5F5CUXBxS2bei/aYMx+0dk=
x-amz-request-id: 8C8CC8D83FC37FFF
Date: Fri, 28 Dec 2018 22:42:48 GMT
Last-Modified: Fri, 28 Dec 2018 22:42:40 GMT
x-amz-version-id: TkEHVJoH95ehr6l6m4ZeBLa6nUikUVlW
ETag: "f948b8368bf7861c1a0836073d9ae132"
Content-Type: text/html
Content-Length: 4045
Server: AmazonS3

Your browser also agrees most likely when pointing it to http://dumrauf-helm-chart-repo.s3-website-us-east-1.amazonaws.com/. Have a go.

Your Experiences?

While this article only describes my experiences, I am highly interested in hearing from you!

Question

Did you come across similar problems? If no, how did you avoid these problems? If yes, how did you resolve them? Feel free to comment on this article or reach out. All feedback is welcome! As always, prove me wrong and I'll buy you a pint!