This post intends to give you enough knowledge on the tools and our philosophy on them to be able to use or fork our Helm StarterKit for yourself. We also strongly recommend reading the documentation for each of the tools in this article, namely Kubernetes and Helm.
Essential concepts for Kubernetes
Kubernetes (K8s), an open-source system developed by Google, is a powerful tool for deploying, scaling, and managing containerized applications. While the learning curve can be steep, understanding the foundational concepts is crucial for effectively leveraging Kubernetes. In this primer, we'll cover the essentials you need to know to begin working with Kubernetes in the context of our StarterKits.
Kubernetes Clusters: The foundation of container orchestration
A Kubernetes cluster is a set of interconnected machines, or "nodes," running the Kubernetes software. It is the environment where your containerized applications are deployed and managed. When you provide Kubernetes with a collection of nodes, it intelligently distributes your applications across available resources and configures the necessary networking based on your specified templates, known as "manifest files," written in the YAML format.
Key Components: Pods, deployments, services, and ingresses
Several key components are fundamental to understanding how Kubernetes works:
- Nodes: These are the physical or virtual machines that constitute your cluster. Each node contributes resources like CPU, memory, and storage to the overall capacity of the cluster.
- Pods: The smallest deployable unit in Kubernetes, a pod typically encapsulates a single Docker container. For most practical purposes you can simply replace the word “pod” in your head with “docker container” it's much easier to understand.
- Deployments: A deployment is a higher-level object that manages a single pod. It ensures the desired number of replicas are running (copies of the same pod running in parallel for load balancing), and automatically replaces any that fail. Deployments also facilitate seamless updates and rollbacks by making sure to only replace one pod at a time, also known as a rolling deployment.
- Services: Services provide a stable network endpoint for accessing a set of pods. They enable communication between different components within your cluster, even as pods are created, destroyed, or moved across nodes.
- Ingresses: Ingresses manage external access to your services. They act as a reverse proxy, routing incoming traffic to the appropriate services based on rules you define. Ingresses operate on standard web ports (80 and 443) and point traffic to a service on the internal network. You can think of it almost like old-timey phone connectors where they had to physically move plugs around to connect you to someone. An ingress sits on the outside and when you ask it for polarlabs.ca and it says “sure, one second, please!”. Then it passes the request onto the service, who plugs you in to the pod.
Example: Pod and Service definition
apiVersion: apps/v1 # Specifies the API version for deployments
kind: Deployment # Declares the object type as a deployment
metadata:
name: my-app-deployment
spec:
replicas: 3 # Number of desired pod replicas
selector:
matchLabels: # Matches the label to select pods managed by this deployment
app: my-app
template: # Template for the pods created by this deployment
metadata:
labels: # Labels for the pods
app: my-app
spec:
containers:
- name: my-app-container
image: your-docker-image:latest # Replace with your actual image
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
selector: # Selects pods based on the label 'app: my-app'
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 80 # Port inside the pod the service should forward to
---
apiVersion: networking.k8s.io/v1 # Specifies the API version for networking resources
kind: Ingress
metadata:
name: my-app-ingress
spec:
rules:
- host: example.com # Replace with your actual domain
http:
paths:
- path: /
pathType: Prefix
backend: # Service to route the traffic to
service:
name: my-app-service
port:
number: 80
Introducing Helm
Prior to Helm's introduction, managing Kubernetes resources often involved directly manipulating raw YAML files using
the kubectl
command-line tool. While this approach is still viable, it can become cumbersome and error
prone, particularly as the complexity of your applications grows. Hardcoded values within configuration files make it
challenging to maintain consistency across different environments, and tracking the evolution of deployed resources
can quickly become a daunting task.
Elevating configuration with templates and values
Helm greatly improves Kubernetes management by introducing a templating system that brings a new level of flexibility and maintainability to your configurations. Helm charts are essentially templates that can be dynamically rendered with specific values, allowing you to reuse the same chart across different environments while tailoring it to their unique requirements.
Think of it as a powerful find-and-replace engine for your Kubernetes YAML. Instead of hardcoding values, you use
placeholders within your chart templates, such as {{ .Values.imageName }}
or
{{ .Values.replicaCount }}
. Then, you provide the actual values in a separate values.yaml
file, specific to each deployment.
Example: Dynamically configured replica count
Let's illustrate with a simple example. Suppose you have a Helm chart for deploying a web application. In your deployment.yaml template, you might have:
apiVersion: apps/v1
kind: Deployment
...
spec:
replicas: {{ .Values.replicaCount }}
...
Now, you can create different values.yaml files for your development, staging, and production environments:
# values-dev.yaml
replicaCount: 1
# values-staging.yaml
replicaCount: 2
# values-prod.yaml
replicaCount: 5
When you install or upgrade your Helm chart with the Helm CLI, you specify the desired values file:
# deploy.sh
# For development environment
helm install my-app ./my-chart -f values-dev.yaml
# For production environment
helm install my-app ./my-chart -f values-prod.yaml
Helm processes the template, replacing {{ .Values.replicaCount }}
with the corresponding value from the
specified file, and then deploys the resulting Kubernetes manifests to your cluster.
Versioned deployments and package management
Helm also introduces the concept of "releases," which represent a specific deployment of a chart to your cluster. Releases are uniquely identified by a name and maintain a version history, allowing you to easily track changes and roll back to previous states if necessary.
For instance, our website, polarlabs.ca , is deployed using a set of Helm charts grouped under the release name
"polar-labs-website-production." This allows us to update or remove the entire website deployment with a single Helm
command, such as helm uninstall polar-labs-website-production
, eliminating the need to manually track
down and manage individual resources.
While it's true that you can achieve some level of resource grouping using Kubernetes namespaces, Helm releases offer additional benefits such as versioning, rollback capabilities, and improved organization.
Helm takes it a step further by acting as a package manager for Kubernetes. Conceptually similar to package managers like npm or pip, Helm simplifies the process of defining, installing, and upgrading applications on your cluster. By adding a “helm repo” to your Helm CLI tool, you can deploy applications using open source or internal charts shared via repos.
The Polar Labs perspective: Why Helm?
For Polar Labs, Helm has been an indispensable tool, enabling us to:
- Utilize reusable templates: The same Helm charts can be deployed across different environments, with values dynamically substituted from our CI/CD pipelines.
- Simplify release management: We can easily deploy, update, and remove environments using Helm's release management features. This has been particularly useful in our management of Preview Environments, which you can read about in [this post].
- Contribute to the community: We can share our Helm charts with the broader open-source community, fostering collaboration and knowledge exchange.
By incorporating Helm into your Kubernetes workflow, you can significantly streamline your configuration management process, enhance reproducibility, and unlock a wealth of community-contributed charts for a wide range of applications.
How the Polar Labs Helm StarterKit works
While we've provided a comprehensive README within our Helm StarterKit repository that details exactly how to leverage our charts for your deployments, let's review the underlying mechanisms that make it all work.
One chart, many applications
In our implementation, our standard Helm chart can be used for almost any application. We embed the pod
definition directly within the deployment chart and dynamically populate several key values within the application's
values.yaml
file, including (but not limited to):
- Docker Container Location: The registry path to your application's Docker image.
- Number of Replicas: How many instances of your pod should run for scaling and redundancy.
- Environment Variables: Configuration values passed into your application at runtime.
- Secrets: Sensitive information like database credentials or API keys, securely stored and injected into your pods.
Here is an example of a values.yaml
file we have in our README at the time of writing this post:
namespace: hello-world
appName: hello-world
replicas: 1
imageLocation: hashicorp/http-echo
containerArgs:
- -text=echo1
containerPorts:
- name: web-ui
port: 80
targetPort: 5678
envVars:
TEST: test
secrets:
- name: test-secret
type: Opaque
data:
TEST: test
clusterIssuerName: letsencrypt-staging
tlsSecretName: hello-world-tls
# disableExternalDNS: true
hosts:
- domainName: test.polarlabs.ca
ingressPaths:
- path: /
type: Prefix
servicePort: 80
- domainName: test2.polarlabs.ca
ingressPaths:
- path: /
type: Prefix
servicePort: 80
# persistentVolumeClaims:
# - name: test-pvc
# storage: 5Gi
# mountPath: /data
More or less, this is all that's necessary to deploy when using the Helm StarterKit.
Services and ingress
We automatically create a default service that maps to the pod defined in your deployment. This allows other components within your Kubernetes cluster to communicate with your application.
In addition, we generate an ingress resource to handle external traffic. However, our ingress configuration goes
beyond the basics, incorporating tools like cert-manager
for automatic SSL certificate generation and
external-dns
for automated DNS management.
DNS and preview environments: A powerful combination
By leveraging DNS tools in conjunction with our ingress configuration, we empower our development workflow with dynamic preview environments. When a pull request is created and passes tests, a button appears to deploy a preview environment.
The pipeline uses the pull request number to create a unique and deterministic identifier for the namespace and Helm
release name. We then substitute environment-specific values into the values.yaml
file, and the
deployment is initiated.
This approach enables each pull request to have its own dedicated environment with a distinct URL, facilitating thorough testing and review before merging changes into production.
For a deeper dive into our deployment philosophy and the intricacies of our process, be sure to check out our deep dive on our deployment philosophy.
ChartMuseum: Sharing and self-hosting Helm charts
ChartMuseum is an open-source tool that enables you to host your own Helm chart repository. Once set up, it integrates with the Helm CLI, allowing you to consume and manage charts from your own repository.
For Polar Labs, ChartMuseum assists in sharing our charts with the open-source community. By hosting our charts in a publicly accessible repository, we make it easy for others to leverage our work without needing to fork and customize our entire setup. Simply adding our repository to your Helm configuration grants you access to the charts defined in our Helm StarterKit.
Effortless deployment with our own charts
At Polar Labs, we employ a range of self-hosted tools, and our Helm charts make deploying them a breeze. Our internal
repository houses charts for each service, along with their corresponding values.yaml
files. These charts
declare our standard deployment charts as a dependency from https://helm.polarlabs.ca and define their necessary
configs within their values.yaml
including a unique domain for the automatic DNS management.
Deploying a new service is then as simple as executing a few commands from the command line. Our charts take care of the rest, from provisioning resources to registering and securing domains, quickly making the service ready for use.
Add our repository to your Helm CLI and use our charts
If your environment is compatible with our charts and you'd like to use them without forking the StarterKit, start by adding our repository to your Helm configuration:
helm repo add polarlabs https://helm.polarlabs.ca
Once added, you can update your local Helm chart cache and search for available charts:
helm repo update
helm search repo polarlabs
To include one of our charts as a dependency in your own chart, simply add it to the dependencies
section of your Chart.yaml
file:
dependencies:
- name: pl-standard-chart
version: x.x.x # replace with the latest version
repository: https://helm.polarlabs.ca
Limitations and considerations
Current cloud provider support
At present, our Helm StarterKit is designed for DigitalOcean Managed Kubernetes, as that's our internal cloud platform. However, we intend to expand support to other providers in the future. If you're using a different provider and adapt our repository, we encourage you to submit a pull request back so others can benefit from your work!
Challenges with external-dns
We've found that external-dns
, while incredibly useful for automating DNS record management, can be a
bit peculiar, especially with DigitalOcean. It writes CNAME records pointing back to your load balancer controller
domain instead of new A records, and any existing TXT records (domain verification for example) can conflict and block
the creation of the new CNAME record.
Additionally, there's a bug with external-dns
's "sync" mode, which is intended to automatically delete
DNS records when the associated ingress is removed. For now, this feature may not work as expected. At least it
doesn't for us, we just clean up the records once in awhile.
If dynamic DNS record generation isn't a priority, we recommend skipping external-dns
as you can simply
create and manage records manually. Our charts will still work without external-dns
installed. We do,
however, strongly recommend using cert-manager
for automated SSL certificate management.
Chart simplicity and extensibility
For more experienced Helm users, you might find our charts a bit basic. We don't support every possible configuration out there, and it's specific to our deployment patterns. However, we like to keep things simple and we feel our design pattern is agnostic enough to be useful in many different contexts.
That being said, if you feel a critical feature is missing or have ideas for improvement, we welcome contributions back to our repository!
In conclusion
We've covered a lot of ground in this post, from core concepts like pods, deployments, services, and ingresses to the power of Helm for streamlined configuration management. You've learned how our Helm StarterKit simplifies deployments with its reusable templates and automated DNS management, and how ChartMuseum enables us to share our charts with the community.
Now it's your turn to explore! Fork our repository, experiment with the charts, and tailor them to your specific needs. We encourage you to contribute back any improvements or enhancements you make, helping to strengthen this tool for others. Happy Helming!