Kubernetes Tutorial: Using Secrets In Your Application

- By Ambarish Chitnis on November 02, 2017

Applications deployed to a Kubernetes cluster often need access to sensitive information such as credentials to access a database and authentication tokens to make authenticated API calls to services. Kubernetes allows you to specify such sensitive information cleanly in an object called a Secret. This avoids putting sensitive data in a Pod defintion or a docker image. In this blog, we demonstrate how you can easily hookup Kubernetes Secrets to your pod using Shippable.

 

Creating a Kubernetes Secret

Secrets are defined in a yml file in a Secret object. A Secret object can specifiy multiple secrets in name-value pairs. Each secret has to be base64 encoded before specifying it in the yml.

Let's define an API token as a secret for a fake token xxx-xxx-xxx.

1. Base 64 encode the token.

ambarishs-MacBook-Pro:sources ambarish$ echo -n "xxx-xxx-xxx" | base64
eHh4LXh4eC14eHg=

2. Create the secrets yml called create-secret.yml.

apiVersion: v1
kind: Secret
metadata:
  name: auth-token-secret
type: Opaque
data:
  AUTH_TOKEN_VALUE: eHh4LXh4eC14eHg=

3. Create the secret in the kubernetes cluster using kubectl.

$ kubectl create -f secrets.yml
secret "auth-token" created

 

Accessing a Secret in your application

Once a Secret is created, the next step is to access it in your application running in the container. Secrets can be mounted as data volumes or be exposed as environment variables to be used by a container in a pod. In this blog, we will mount an authentication token Secret as a data volume and consume the secret to make API calls. We will also define the secret as an environment variable for demonstartion purposes.

 

Changing Secrets dynamically in different environments

A typical application is deployed to multiple environments such as Dev, Test and Prod and you would want different values for the same secret in those environments as a good security best practice. Typically, organizations will create multiple secret files for each environment and hard code the credentials in each file. 

Shippable offers a much cleaner approach that avoids the nightmare of creating and maintaining multiple secrets yml file for an application per environment. Instead of specifying the secret directly in a secrets yml file, we will only define a placeholder value in the yml file, thereby creating a flexible, reusbale template. The actual secret key-value will be defined in a key-value integration. In doing so we separate data from configuration allowing flexible data binding. Thereafter, before creating the secret, we dynamically databind the template with the key-value pair defined in the integration and then create the secret. After reading this blog, you will be able to easily adapt this powerful and simple approach to all your Kubernetes environments.

 

Scenario

Our scenario is a single container Node.js application which consumes an authentication token Kubernetes secret. The authentication token is a Shippable API token. Shippable API is exposed to all paid users of Shippable and steps to create a token can be found here. The secret is volume mounted and the application consumes the secret in an exposed shipciprojects API. This API invokes Shippable API to return all repositories enabled for CI by making a HTTPS GET request on the Shippable Projects API.  

We will build the scenario using the following steps using a Shippable workflow -

  • Define the container packaged in the pod. The container runs a Node.js application that can be found in the sample repository.
  • Create the Shippable authentication token secret in the cluster using a key-value pair integration data bound to the secrets template that exists in our sample repository.
  • Define the secret volume. 
  • Create the pod and the secret volume in the Kubernetes cluster.
  • Create a Kubernetes load balancer/service for the application.
  • Test the secret volume mount using an exposed route in the Node.js application that makes the Shippable API call.

 

Deployment Workflow

This is a pictorial representation of the workflow we're going to configure. The green boxes are jobs and the grey boxes are the input resources for the jobs. The workflow is defined across two configuration files: shippable.jobs.yml and shippable.resources.yml.

 

dks-workflow-4.png

 

Resources (grey boxes)

  • dks_app_img is a required image resource that represents the docker image of your application.
  • dks_gitRepo is a required gitRepo resource which is a pointer to the git repository that contains your source code and config files.
  • dks_cliConfig is a required cliConfig resource which is a pointer to the private key of your service account needed to initialize the gcloud CLI.
  • dks_secrets is a required integration resource which is a pointer to the key-value pair integration that stores the secrets.
  • dks_secret_opts is an required dockerOptions resource where we define the secrets volume mount configuration.
  • dks_kube_cluster is a required cluster resource that represents the Kubernetes cluster.
  • dks_lb is an optional loadBalancer resource that defines the loadbalancer properties such as labels, port, cluster etc.

Jobs (green boxes)

  • dks-app-def is a required manifest job that defines all the containers than run in the pod. This definition is versioned and each version is immutable.
  • create_secret is a required runSh job that uses kubectl to create the secret in the cluster using the key-value pair integration data bound to the secrets template.
  • dks-app-deploy is a required deploy job which builds the Deployment spec for our application and deploys it to the Kubernetes cluster.
  • dks-provision-lb is a optional provision job used to create the load balancer for the Kubernetes cluster.

 

Prerequisites

  • An existing Kubernetes cluster where you will deploy this sample application.
  • Any Supported Docker registry with a repository for your application. We have used Docker hub as the Docker registry in this sample.
  • A GitHub account where you will fork and run this sample.
  • Sign in with GitHub to create a Shippable account

If you're not familiar with Shippable, it is also recommended that you read the Platform overview doc to understand the overall structure of Shippable's DevOps Assembly Lines platform.blo

 

 

Sample project

The code for this example is in a GitHub repository called devops-recipes/deploy-kubernetes-secrets. You can fork the repository to try out this sample yourself or just follow instructions to add Shippable configuration files to your existing repository.

  • The Node.js application source code and Dockerfile can be found here in the repository.
  • This repository also has the Shippable configuration files to create the workflow. 

 

1. Define the containers in a pod

A. Create an account integration using your Shippable account for your Docker registry.

 Instructions to create an integration can be found here. Copy the friendly name of the integration, which we have set as drship_dockerhub. 

 

B. Define dks_app_img

dks_app_img is an image resource that represents the docker image of your application. In our example, we're using an image hosted on Docker Hub.

Add the following yml block to your shippable.resources.yml file.

resources:

  - name: dks_app_img
    type: image
    # replace drship_dockerhub with your docker hub integration name
    integration: drship_dockerhub
    pointer:
      # replace devopsrecipes/dks_node_sample_app with your docker registry
      # repository
      sourceName: devopsrecipes/dks_node_sample_app
    seed:
# replace latest with your image tag versionName: "latest"

 

C. Define dks-app-def

dks-app-def is a manifest job that defines all the containers than run in the pod. This definition is versioned and each version is immutable.

Add the following yml block to your shippable.jobs.yml file.

jobs:

  - name:dks-app-def
    type: manifest
    steps:
      - IN: dks_app_img
      - TASK: managed

 

D. Commit config files and add them to your Shippable account.

Once you have these configuration files as described above, commit them to your repository. The shippable.jobs.yml and shippable.resources.yml can be committed to the same app repository, or to a separate repository.

The repository containing your jobs and resources ymls is called a Sync repository and represents your workflow configuration.

Follow these instructions to import your configuration files into your Shippable account.

 

2. Create the secret

A. Add the subscription integration for your SCM account integration

By default, you will already have an account integration with whichever SCM provider you've used to log into Shippable. If your source code repository is on that SCM account, you should use that account integration as is. 

If your source code repository however is on another SCM account, create an account integration for it first by using one of the following supported SCM docs:

Check if you have the SCM subscription integration for your account integration. If you do not have one, add the account integration to your Subscription by following the steps here: Adding an account integration to a Subscription

 

B. Add the Key-value integration using instructions found here.

  • Name the integration drship_kube_secrets. If you change the name, change it also in the yml in Step C.
  • Base64 eccode your Shippable API token and specify it in a key called AUTH_TOKEN_VALUE.
  • Ensure you give access to the organization that your repository exists in Subscription scopes.
Screen Shot 2017-11-02 at 12.39.56 PM.png

 

C. Define resources needed to create the Secret

Add the following to your shippable.resources.yml file: 

resources:

  - name: dks_secrets
    type: integration
    integration: drship_kube_secrets

  - name: dks_gitRepo
    type: gitRepo
    # replace dr_github with your GitHub integration name
    integration: dr_github
    pointer:
      # replace with source code location (e.g. GitHub) where you cloned this
      # sample project.
      sourceName: devops-recipes/deploy-kubernetes-secrets
      branch: master

  - name: dks_kube_cliConfig
    type: cliConfig
    #replace with your Kubernetes integration name
    integration: drship_kube
    pointer:
      # replace us-central1-a with your availability zone
      region: us-central1-a

 

D. Define create_secret

Add the create_secret job to your shippable.jobs.yml file.

It is a runSh job that lets you run any shell script. The script databinds the dks_secrets integration to the create-secret.yml secret template file, that exists in the repository in the kubernetes-secrets directory, using the shipctl replace command.

To access the repository, we have specified dks_gitRepo as an input. Also, to automatically inject the kubernetes configuration file into the job reqired by kubectl, we have specified dks_kube_cliConfig as an input. 

Since it needs to run after the pod definition job in the workflow, dks-app-def is specified as an input.

jobs:

  - name: create_secret
    type: runSh
    steps:
      - IN: dks_gitRepo
        # manually trigger the job and not on every commit to the repository
        switch: off
      - IN: dks_kube_cliConfig
      - IN: dks-app-def
      - IN: dks_secrets
      - TASK:
        - script: |
            pushd $(shipctl get_resource_state "dks_gitRepo")/kubernetes-secrets
              # Replace placeholders in the secret yml with environment variables
              # injected by the key-value pair integration
              shipctl replace ./create-secret.yml
              cat ./create-secret.yml

              # Delete secret if it exists and create the secret
              kubectl delete secret shipsecret 2>/dev/null || echo "secret does not exist"
              kubectl create -f ./create-secret.yml
            popd

 

E. Commit config files and add them to your Shippable account.

Once you have these configuration files as described above, commit them to your repository.

 

3. Define the secret volume

dks_secret_opts is a dockerOptions resource where we define the secret volume mount configuration. 

Add the following yml block to your shippable.resources.yml file and commit the file.

resources:

  - name: dks_secret_opts
    type: dockerOptions
    version:
      volumeMounts:
        - name: secret-volume
          mountPath: "/etc/secrets"
          readOnly: true
      pod:
        volumes:
          - name: secret-volume
            secret:
              secretName: auth-token-secret

 

4. Create a load balancer for the application

This is an optional step and the configuration required to create the load balancer can be found in this document. The sample application also has the load balancer configuration.

 

5. Deploy the pod 

A. Create an account integration for Kubernetes in your Shippable UI.

Instructions to create an integration are here:

Copy the friendly name of the integration. We have set it to drship_kube and use in the next step.

B. Define dks_kube_cluster

dks_kube_cluster is a cluster resource that represents the Kubernetes cluster.

Add the following yml block to your shippable.resources.yml file.

resources:

  - name: dks_kube_cluster
    type: cluster
    #replace with your Kubernetes integration name
    integration: drship_kube
    pointer:
      # replace devops-test-cluster with your google container engine cluster name
      sourceName: "devops-test-cluster"
      # replace us-central1-a with your availability zone
      region: us-central1-a
 

 

C. Create deployment job
dks-app-deploy is a deploy job which builds the Deployment spec for our application and deploys it to the Kubernetes cluster. Since it needs to run after the secret is created in the workflow, create_secret is specified as an input.

Add the following yml block to your shippable.jobs.yml file.

jobs:

  - name: dks-app-deploy
    type: deploy
    method: replace
    steps:
      - IN: create_secret
      - IN: dks_secret_opts
      - IN: dks-app-def
        switch: off
      - IN: dks_kube_cluster

 

D. Commit the shippable.resources.yml and shippable.jobs.yml file to your repository. 

Your pipeline should now look like this in the SPOG view.

pipeline-1.png

5. Trigger your pipeline

Right click on dks-app-def in the SPOG and click on Build Job. This will trigger the entire pipeline.

runjob.png

If you have created the load balancer configuration , you will first need to right click on your load balance job dks_provision_lb and click Build Job to create the load balancer.

 

Screen shot of a run of the create_secret job

Screen Shot 2017-11-02 at 1.20.43 PM.png

 

Screen shot of a run of the dks-app-deploy job

deployjob.png

 

6. Testing the secret  volume

Screenshot of the load balancer created in Google Cloud, since the Kubernetes cluster that we used runs in Google cloud.

lb-1.png

 

Screenshot of the shipciprojects API returning the projects enabled for CI.

api.png

 

Try the sample above to automate your deployment pipeline for your Kubernetes application using secrets. You sign can in for free:

Try Shippable

 

Topics: Docker, continuous integration (CI), Kubernetes, Google Cloud Platform, Docker Hub