This quickstart guide uses the k8s-service
Helm Chart to deploy a sample web app that is configured using environment
variables. In this guide, we will walk through the different ways to set environment variables on the application
container deployed using the k8s-service
Helm Chart.
This guide is meant to demonstrate how you might pass in external values such as dependent resource URLs and various secrets that your application needs.
This guide assumes that you are familiar with the defaults provided in the k8s-service
Helm Chart. Please refer to the
k8s-service-nginx example for an introduction to the core features of the Helm Chart.
In this guide, we will walk through the steps to:
- Deploy a dockerized sample app on a Kubernetes cluster. We will use
minikube
for this guide. - Use the
envVars
input value to set the port that the container listens on. - Create a
ConfigMap
that provides some server text for the application to use. - Use the
configMaps
input value to set the server text returned by the application from theConfigMap
. - Create a
Secret
that provides some server text for the application to use. - Use the
secrets
input value to set the server text returned by the application from theSecret
.
At the end of this guide, you should be familiar with the three ways provided by k8s-service
to configure your
application.
- Install and setup
minikube
- Install and setup
helm
- Package the sample app docker container for
minikube
- Deploy the sample app docker container with
k8s-service
- Setting the server text using a ConfigMap
- Setting the server text using a Secret
NOTE: This guide assumes you are running the steps in this directory. If you are at the root of the repo, be sure to change directory before starting:
cd examples/k8s-service-config-injection
In this guide, we will use minikube
as our Kubernetes cluster. Minikube
is an official tool maintained by the Kubernetes community to be able to provision and run Kubernetes locally your
machine. By having a local environment you can have fast iteration cycles while you develop and play with Kubernetes
before deploying to production.
To setup minikube
:
- Install kubectl
- Install the minikube utility
- Run
minikube start
to provision a newminikube
instance on your local machine. - Verify setup with
kubectl
:kubectl cluster-info
In order to install Helm Charts, we need to have the Helm CLI. First install the helm
client. Make sure the binary is discoverble in your PATH
variable.
See this stackoverflow post
for instructions on setting up your PATH
on Unix, and this
post for instructions on
Windows.
Verify your installation by running helm version
:
$ helm version
version.BuildInfo{Version:"v3.1+unreleased", GitCommit:"c12a9aee02ec07b78dce07274e4816d9863d765e", GitTreeState:"clean", GoVersion:"go1.13.9"}
For this guide, we will need a docker container that provides a web service and is configurable using environment variables.
We provide a sample app built using Sinatra on Ruby that returns some server text set using the
environment variable SERVER_TEXT
. You can see the full code for the server in docker/app.rb.
In order to be able to deploy this on Kubernetes, we will need to package the app into a Docker container. To do so, we
need to first authenticate the docker client to be able to access the Docker Daemon running on minikube
:
eval $(minikube docker-env)
The above step extracts the host information of the Docker Daemon running on your minikube
virtual machine, and
configures the docker
client using environment variables.
You can verify that you can reach the minikube
Docker Daemon by running docker ps
. You should see output similar to
below, listing a bunch of docker containers related to Kubernetes:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5f6131b6b3ca gcr.io/k8s-minikube/storage-provisioner "/storage-provisioner" About an hour ago Up About an hour k8s_storage-provisioner_storage-provisioner_kube-system_2c2465d6-41c3-11e9-af90-0800274e6ff3_0
481b954a22b6 k8s.gcr.io/pause:3.1 "/pause" About an hour ago Up About an hour k8s_POD_storage-provisioner_kube-system_2c2465d6-41c3-11e9-af90-0800274e6ff3_0
8ec108f9948f f59dcacceff4 "/coredns -conf /etc…" About an hour ago Up About an hour k8s_coredns_coredns-86c58d9df4-rr262_kube-system_2a971ff2-41c3-11e9-af90-0800274e6ff3_0
84fd48fa4fa5 f59dcacceff4 "/coredns -conf /etc…" About an hour ago Up About an hour k8s_coredns_coredns-86c58d9df4-kqn7c_kube-system_2a806542-41c3-11e9-af90-0800274e6ff3_0
b1a8616de7f6 98db19758ad4 "/usr/local/bin/kube…" About an hour ago Up About an hour k8s_kube-pro
..... SNIPPED FOR BREVITY ...
Once your docker
client is able to talk to the minikube
Docker Daemon, we can now build our sample app container so
that it is available to minikube
to use:
docker build -t gruntwork-io/sample-sinatra-app ./docker
This will build a container that has the runtime environment for running sinatra in the minikube
virtual machine.
Once the container is created, we tag it as gruntwork-io/sample-sinatra-app
so that it is easy to reference later.
Note that because this is built in the minikube
virtual machine directly, the image will be cached within the VM. This
is why minikube
is able to use the built container when you reference it in k8s-service
.
Now that we have a working Kubernetes cluster with Helm installed and a sample Docker container to deploy, we are ready
to deploy our application using the k8s-service
chart.
This folder contains predefined input values you can use with the k8s-service
chart to deploy the sample app
container. Like the k8s-service-nginx example, these values define the container image to use as
part of the deployment, and augments the default values of the chart by defining a livenessProbe
and readinessProbe
for the main container (which in this case will be gruntwork-io/sample-sinatra-app:latest
, the one we built in the
previous step). Take a look at the provided values.yaml
file to see how the values are defined.
However, the values in this example also sets an environment variable to configure the application. By default the
application listens for web requests on port 8080. However, most of the default values for the k8s-service
helm chart
assumes the container listens for requests on port 80. While we can update the port that the chart uses, here we opt to
update the application container instead to provide an example of how you can hard code environment variables to pass
into the container in the values.yaml
file. We use the envVars
input map to set the SERVER_PORT
to 80
in the
container:
envVars:
SERVER_PORT: 80
Each key in the envVars
input map represents an environment variable, with the keys and values directly mapping to the
environment.
We will now instruct helm to install the k8s-service
chart using these values. To do so, we will use the helm install
command:
helm install -f values.yaml ../../charts/k8s-service --wait
The above command will instruct the helm
client to install the Helm Chart defined in the relative path
../../charts/k8s-service
, merging the input values defined in values.yaml
with the one provided by the chart.
Additionally, we provide the --wait
keyword to ensure the command doesn't exit until the Deployment
resource
completes the rollout of the containers.
At the end of this command, you should be able to access the sample web app via the Service
. To hit the Service
, get
the selected node port and hit the minikube
ip on that port:
# NOTE: you must set RELEASE_NAME to be the chosen name of the release
export NODE_PORT=$(kubectl get --namespace default -o jsonpath="{.spec.ports[0].nodePort}" services "$RELEASE_NAME-sample-sinatra-app")
export NODE_IP=$(minikube ip)
curl "http://$NODE_IP:$NODE_PORT"
The above curl
call should return the default server text set on the application container in JSON format:
{"text":"Hello from backend"}
The previous step showed you how you can hard code environment variable settings into the values.yaml
file. The
disadvantage of hardcoding the environment values is that you will need separate vaules.yaml
file for each deployment
environment (e.g dev vs production), and manage them independently. This can be cumbersome if you have a lot of common
settings you want to share between the two environments.
Kubernetes provides ConfigMaps
to
help decouple application configuration from the deployment settings. ConfigMaps
are objects that hold key value pairs
that can then be injected into the application container at deploy time as environment variables, or as files on the
file system.
The k8s-service
Helm Chart supports both modes of operation. In this guide, we will show you how to set an environment
variable from a ConfigMap
key. To do so, we first need to create the ConfigMap
.
For this example we will update the server text of our application using a ConfigMap
. We will use kubectl
to create
our ConfigMap
on our Kubernetes cluster.
Take a look at the provided resource file in the kubernetes
folder that defines a
ConfigMap
. This resource file will create a ConfigMap
resource named sample-sinatra-app-server-text
containing a
single key server_text
holding the value Hello! I was configured using a ConfigMap!
. We can create this ConfigMap
by using kubectl apply
to apply the resource file:
kubectl apply -f ./kubernetes/config-map.yaml
To verify the ConfigMap
was created, you can use the kubectl get
command to get a list of available ConfigMaps
on
your cluster:
$ kubectl get configmap
NAME DATA AGE
sample-sinatra-app-server-text 1 57s
Now that we have created a ConfigMap
containing the server text config, let's augment our Helm Chart input value to
set the SERVER_TEXT
environment variable from the ConfigMap
. Take a look at the
extensions/config_map_values.yaml file. This values file defines an entry for the
configMaps
input map:
configMaps:
sample-sinatra-app-server-text:
as: environment
items:
server_text:
envVarName: SERVER_TEXT
Each key at the root of the configMaps
map value specifies a ConfigMap
by name. Then, the value is another map that
specifies how that ConfigMap
should be included in the application container. You can either include it as a file
(as: volume
) or environment variable (as: environmet
). Here we include it as an environment variable, setting the
variable SERVER_TEXT
to the value of the server_text
key of the ConfigMap
. You can refer to the documentation in
the chart's values.yaml
for details on how to set the input map.
To deploy this, we will pass it in in addition to the root values.yaml
file to merge the two inputs together. We will
use helm upgrade
here instead of helm install
so that we can update our previous deployment:
helm upgrade "$RELEASE_NAME" ../../charts/k8s-service -f values.yaml -f ./extensions/config_map_values.yaml --wait
When you pass in multiple -f
options, helm
will combine all the yaml files into one, preferring the right value over
the left (e.g if there was overlap, then helm
will choose the value defined in ./extensions/config_map_values.yaml
over the one defined in values.yaml
).
When this deployment completes and you hit the server again, you should get the server text defined in the ConfigMap
:
$ curl "http://$NODE_IP:$NODE_PORT"
{"text":"Hello! I was configured using a ConfigMap!"}
ConfigMaps
and hard coded environment variables are great for application configuration values, but are not very
secure. Hard coding environment variables leak into your code, and thus risk being checked in to source control while
ConfigMaps
are not stored encrypted on the Kubernetes server and reports the value in plain text in the shell.
Kubernetes provides a built in secrets manager in the form of the
Secret
resource. Unlike ConfigMaps
, Secrets
:
- Can be stored in encrypted form in
etcd
. - Is only sent to a node if a pod on that node requires it, and is only available in memory (using
tmpfs
). - Obfuscates the text using
base64
to avoid "shoulder surfing" leakage.
NOTE: Be aware of the risks with using Secrets
as your secrets
manager.
Like ConfigMaps
, Secrets
can be injected into the application container as environment variables or as files, and
the k8s-service
Helm Chart supports both modes of operation.
In this guide, we will use a Secret
to replace the server text config set using a ConfigMap
in the previous step.
Since Secrets
contain sensitive information, it is typically recommended to create Secrets
manually using the command line.
To create a Secret
, we can use kubectl create secret
. Here, we will create a new secret containing the key
server_text
set to Hello! I was configured using a Secret!
:
kubectl create secret generic sample-sinatra-app-server-text --from-literal server_text='Hello! I was configured using a Secret!'
To verify the Secret
was created, you can use the kubectl get
command to get a list of available Secrets
:
$ kubectl get secrets
NAME TYPE DATA AGE
default-token-wmb57 kubernetes.io/service-account-token 3 27m
sample-sinatra-app-server-text Opaque 1 1m
Now that we have created a Secret
containing the server text config, let's try to inject it into the application
container. The settings to inject Secrets
is formulated in a very similar manner to ConfigMaps
. Take a look at the
extensions/secret_values.yaml file. This file defines a single input value secrets
,
which sets the SERVER_TEXT
environment variable to the server_text
key on the sample-sinatra-app-server-text
Secret
:
secrets:
sample-sinatra-app-server-text:
as: environment
items:
server_text:
envVarName: SERVER_TEXT
Compare this configuration with extensions/config_map_values.yaml. Note how the
only thing that differs is the input key is secrets
as opposed to configMaps
. This is because both ConfigMaps
and
Secrets
behave in very similar manners in Kubernetes, and so the k8s-service
Helm Chart intentionally exposes a
similar interface to configure the two.
Deploying this config is very similar to how we deployed the config_map_values.yaml
extension. We need to combine this
with the root values.yaml
file to get a complete input and update our existing release:
helm upgrade "$RELEASE_NAME" ../../charts/k8s-service -f values.yaml -f ./extensions/secret_values.yaml --wait
When this deployment completes and you hit the server again, you should get the server text defined in the config map:
$ curl "http://$NODE_IP:$NODE_PORT"
{"text":"Hello! I was configured using a Secret!"}
Congratulations! At this point, you have:
- Setup
minikube
to have a local dev environment of Kubernetes. - Installed and deployed Helm on
minikube
. - Packaged a sample application using Docker.
- Deployed the dockerized application on to
minikube
using thek8s-service
Helm Chart. - Configured the application using hard coded environment variables in the input values.
- Configured the application using
ConfigMaps
. - Configured the application using
Secrets
.
To learn more about the k8s-service
Helm Chart, refer to the chart documentation.