Kubernetes Camp

Learn Kubernetes on your Coffee Break

4. Configuring your Application

So far we’ve installed Kubernetes, run our first app, learned how to scale that app and exposed that application via a service.

You may want to pass configuration settings into your application, you can use Config Maps and Secrets to do this.

Do

Create a basic website

Create a HTML file to display hello world:

echo '<html><head><title>Hello World</title></head><body>Hello World</body></html>' > /tmp/index.html

Create a Config Map

Create a Kubernetes Config Map from that file:

$kubectl create configmap hello --from-file=/tmp/index.html
configmap/hello created

Create a Secret

Create a Kubernetes Secret from that file:

$ kubectl create secret generic hello --from-file=/tmp/index.html
secret/hello created

View the resultant Config Map:

$ kubectl get configmap hello -o yaml
apiVersion: v1
data:
  index.html: |
    <html><head><title>Hello World</title></head><body>Hello World</body></html>
kind: ConfigMap
metadata:
  creationTimestamp: "2019-08-15T20:09:43Z"
  name: hello
  namespace: default
  resourceVersion: "591027"
  selfLink: /api/v1/namespaces/default/configmaps/hello
  uid: a47a6058-a482-40be-99d3-1aaf5390af85

View the resultant Secret:

Note: the secret is unreadable. It is not encrypted, simple base64 encoded.

apiVersion: v1
data:
  index.html: PGh0bWw+PGhlYWQ+PHRpdGxlPkhlbGxvIFdvcmxkPC90aXRsZT48L2hlYWQ+PGJvZHk+SGVsbG8gV29ybGQ8L2JvZHk+PC9odG1sPgo=
kind: Secret
metadata:
  creationTimestamp: "2019-08-15T20:09:49Z"
  name: hello
  namespace: default
  resourceVersion: "591038"
  selfLink: /api/v1/namespaces/default/secrets/hello
  uid: ead44f8c-041f-4db7-bb98-ee56dd1f2e3e
type: Opaque

You can map either of these into your Pod as an environment variable, or as a file in a volume. Since the example application is nginx it makes sense to mount it as a file in a volume.

To mount it as a volume you would need to edit the actual underlying Kubernetes manifest defining the Deployment. You could do this live with the kubectl edit command, however it will be easier if we create a new pod from a simpler manifest.

Create a new deployment

Create the following file as /tmp/hello.yaml:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: hello-world
spec:
  selector:
    matchLabels:
      app: hello-world
  template:
    metadata:
      labels:
        app: hello-world
    spec:
      containers:
      - name: hello
        image: nginx:latest
        volumeMounts:
        - name: html
          mountPath: /usr/share/nginx/html
      volumes:
      - name: html
        configMap:
          name: hello

Apply that manifest using the declarative command kubectl apply:

$ kubectl apply -f /tmp/hello.yaml
deployment.extensions/hello-world created

Test the new deployment

Use kubectl port-forward to enable access to the new deployment:

$ kubectl port-forward deployment/hello-world 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Handling connection for 8080

Access the new port-forward from another terminal (or from a web browser):

$ curl localhost:8080
<html><head><title>Hello World</title></head><body>Hello World</body></html>

Learn

Not only did you create a ConfigMap and a Secret, but you also created a new Deployment using the declarative kubectl apply command using a YAML based Kubernetes manifest.

Config Map

Config Maps are A Kubernetes resource to store key/value pairs that can be used by other Kubernetes resources. These key/value pairs can be loaded into Pods as environment variables or as files in a volume.

This allows you to decouple configuration from image content with is an integral part of building Cloud Native Applications.

You can create a Config Map from a manifest, or you can use the declarative kubectl create configmap. You can also combine the two and build a manifest using the kubectl create configmap command like so:

$ kubectl create configmap example --from-file /tmp/index.html --dry-run -o yaml
apiVersion: v1
data:
  index.html: |
    <html><head><title>Hello World</title></head><body>Hello World</body></html>
kind: ConfigMap
metadata:
  creationTimestamp: null
  name: example

Note: the --dry-run -o yaml flags are incredibly useful, you can create a Kubernetes manifest for almost any type of Kubernetes resource via kubectl create to be used later with kubectl apply -f.

Environment Variables

As well as files, Values in a Config Map can be loaded into environment variables for your application to use inside of a Pod like so:

apiVersion: v1
data:
  username: myApp
kind: ConfigMap
metadata:
  name: mysql
---
apiVersion: v1
kind: Pod
metadata:
  name: mysql
spec:
  containers:
  - name: mysql
    image: mysql
    env:
    - name: MYSQL_USERNAME
      valueFrom:
        configMapKeyRef:
          # The ConfigMap containing the value you want to assign to MYSQL_USERNAME
          name: mysql
          # Specify the key associated with the value
          key: username

You can also load an entire Config Map into environment variables like so:

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql
data:
  username: myApp
  host: my.database.server
---
apiVersion: v1
kind: Pod
metadata:
  name: mysql
spec:
  containers:
  - name: mysql
    image: mysql
    envFrom:
    - configMapRef:
        name: mysql

Secret

Secrets are like Config Maps but with a few differences. Secrets are base64 encoded which means unless you’re a math genius you can’t read them with the naked eye. This is not however for any security reason, its to ensure that the contents of the secret are safe to store in a text based format.

Secrets can be loaded into a Pod just like a Config Map as files in a volume or as environment variables. The syntax is roughly the same as you can see below.

Files in a Volume

In the example above you created a Secret from a index.html file, you could load this into a Pod like so:

apiVersion: v1
kind: Pod
metadata:
  name: mysql
spec:
  containers:
  - name: hello
    image: nginx:latest
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
  volumes:
  - name: html
    secret:
      secretName: hello

Environment Variables

You can load Secrets into a Pod as environment variables much in the same way as you can Config Maps.

Just a specific Key:

apiVersion: v1
data:
  password: YmFjb24taXMtZGVsaWNpb3VzCg==
kind: Secret
metadata:
  name: mysql
---
apiVersion: v1
kind: Pod
metadata:
  name: mysql
spec:
  containers:
  - name: mysql
    image: mysql
    env:
    - name: MYSQL_USERNAME
      valueFrom:
        secretKeyRef:
          # The Secret containing the value you want to assign to MYSQL_USERNAME
          name: mysql
          # Specify the key associated with the value
          key: password

You can also load an entire Secret into environment variables like so:

apiVersion: v1
kind: Secret
metadata:
  name: mysql
data:
  MYSQL_PASSWORD: YmFjb24taXMtZGVsaWNpb3VzCg==
  MYSQL_ROOT_PASSWORD: YnV0LXNvLWlzLXRvZnUK
---
apiVersion: v1
kind: Pod
metadata:
  name: mysql
spec:
  containers:
  - name: mysql
    image: mysql
    envFrom:
    - secretRef:
        name: mysql

Declarative Kubernetes

The best way to create resources with kubectl is to use Declarative commands like kubectl apply. When you use a declarative command you provide a desired state (usually in the form of a YAML based manifest like above) and let Kubernetes sweat the details on how to make the actual state match that desired state.

These manifests are text based artifacts that can (and should) be stored in source control and provide a great foundation on which you can build out Infrastructure as Code and follow devops practices such as gitops.