Wilson Mar bio photo

Wilson Mar

Hello. Hire me!

Email me Calendar Skype call 310 320-7878

LinkedIn Twitter Gitter Google+ Youtube

Github Stackoverflow Pinterest

How to pass them through Jenkins and Kubernetes

The nice thing about operating system environment variables is they can be reached from practically anywhere (in script files, within Java program, within a Python program, etc.).

One of the factors for a 12 Factor App is Config “Store config in the environment”, as in the operating system (CoreOS Linux, Windows, MacOS, etc.):

“The twelve-factor app stores config in environment variables (often shortened to env vars or env). Env vars are easy to change between deploys without changing any code; unlike config files, there is little chance of them being checked into the code repo accidentally; and unlike custom config files, or other config mechanisms such as Java System Properties, they are a language- and OS-agnostic standard.”

Information likely to vary between deploys (in staging, production, developer environments, etc) include:

  • Different resource handles to the database, Memcached, and other backing services
  • Credentials to external services such as Amazon S3 or Twitter
  • Per-deploy values such as the canonical hostname for the deploy
  • Within app programs, flags that have one set of pods serve one type of customer while another set servers another with an alternatve functionality.

Alternative: static files

An alternative to operating system enviornment variables is writing program code to read a file such as properties.xml. But that means values from them are static across multiple instances, or a mechnism needs to be crafted to change the contents of the file before use.

MacOS

  1. On MacOS Terminals, the path of folders where the operating system searches for executables is defined in an environment variables, listed by this command:

    
    echo $PATH
    echo $HOME
    

    There are other default environment variables.

  2. To set a custom environment variable, edit the script executed whenever a Terminal window is created:

    
    subl ~/.bash_profile
    
  3. To add an environment variable in front of the existing $PATH:

    
    export PATH="$HOME/gits:$PATH"
    

Windows

On Windows 7, the GUI displaying system variables are MyComputer -> Properties -> Advanced -> Environment Variables -> System Variables

On Windows 10:

  1. Click the Windows Start logo on the keyboard or on the bottom of the screen.
  2. In Search, search for and then select: System (Control Panel)
  3. Click the Advanced system settings link.
  4. Click Environment Variables. In the section System Variables, find the PATH environment variable and select it.
  5. Click Edit.
  6. If the PATH environment variable does not exist, click New.
  7. In the Edit System Variable (or New System Variable) window, specify the value of the PATH environment variable. Click OK.
  8. Close all remaining windows by clicking OK.
  9. Reopen Command prompt window, and run your java code.

Unlike PowerShell, the traditional cmd.exe CLI window doesn’t have its own variables. It only knows about environment variables. To display the environment path:


   echo %PATH%
   

PowerShell also provides access to environment variables through the Env: drive.


   Get-ChildItem Env:
   

PROTIP: (PowerShell extends the concept of drive to store more kinds of data than just files.) PowerShell doesn’t have a built-in way to run a Cmd.exe shell script (batch file) and retain the environment variables set or changed by the script. In addition, in PowerShell there’s no equivalent to the Setlocal command to revert environment variable changes. The Invoke-CmdScript, Get-Environment, and Save-Environment functions remove these limitations and give PowerShell more power over environment variables.

See http://www.itprotoday.com/management-mobility/take-charge-environment-variables-powershell

In Java programs

  1. In Java programs, obtain the name of the variable to read system variables:

    System.getenv(String);
    

    The code returns an unmodifiable string map view of the current system environment.

  2. Print to stdout the contents of default environment variable JAVA_HOME where Java is installed:

    System.out.println(System.getenv("JAVA_HOME"));
    

    PROTIP: A null value is shown if a custom system variable does not exist:

  3. If the program should not run unless a custom variable was set, throw an exception:

    String ENV_VAR1 = Optional.ofNullable(System.getenv("ENV_VAR1")).orElseThrow(
      () -> new CustomCheckedException("ENV_VAR1 is not set in the environment"));
    

    See https://docs.oracle.com/javase/tutorial/essential/environment/env.html for Java coding to use environment variables.

    PROTIP: Java has both System Environment variables and JVM-based variables.

  4. Pass a Java environment variable pagingDirectory (containing “C:\temp”) from the command line excuting Java program myApp.jar:

    
    java.exe -DmaxInMemory=100M -DpagingDirectory=c:\temp -jar myApp.jar
    
  5. PROTIP: A different method is used to read Java system properties:

    System.getProperties();
    

Within Jenkins

The sample Jenkinsfile template from GCP at https://github.com/GoogleCloudPlatform/continuous-deployment-on-kubernetes/blob/master/sample-app/Jenkinsfile

node {
  def project = 'REPLACE_WITH_YOUR_PROJECT_ID'
  def appName = 'gceme'
  def feSvcName = "${appName}-frontend"
  def imageTag = "gcr.io/${project}/${appName}:${env.BRANCH_NAME}.${env.BUILD_NUMBER}"
   

The value for project is specified when the file is adapted for use.

The value for {appName} is also defined as static values.

The value for env.BRANCH_NAME is pulled in from environment variables.

Further down the file:

  stage "Deploy Application"
  switch (env.BRANCH_NAME) {
    // Roll out to canary environment
    case "canary":
        // Change deployed image in canary to the one we just built
        sh("sed -i.bak 's#gcr.io/cloud-solutions-images/gceme:1.0.0#${imageTag}#' ./k8s/canary/*.yaml")
        sh("kubectl --namespace=production apply -f k8s/services/")
        sh("kubectl --namespace=production apply -f k8s/canary/")
        sh("echo http://`kubectl --namespace=production get service/${feSvcName} --output=json | jq -r '.status.loadBalancer.ingress[0].ip'` > ${feSvcName}")
        break
   

The sed utility replaces text. The sed utility is available on practically all Linux and MacOS machines.
The s is for stream. The ed is for editor. For example, to substitute the word “day” once each line in the “old” file to “night” in the “new” file:


   sed s/day/night/ old new
   

See http://www.grymoire.com/Unix/Sed.html

The kubectl is the Kubernetes command line utility.

Kubernetes deploy files

For example, in https://github.com/GoogleCloudPlatform/continuous-deployment-on-kubernetes/tree/master/sample-app/k8s a different folder is used to hold a different set of yaml deployment files for each environment (canary, dev, services, production): frontend-canary.yaml file and backend-canary.yaml file.

Within https://github.com/GoogleCloudPlatform/continuous-deployment-on-kubernetes/blob/master/sample-app/k8s/production/frontend-production.yaml note the shell command

command: ["sh", "-c", "app -frontend=true -backend-service=http://gceme-backend:8080 -port=80"]
   

It contains variable “backend-service” with value “http://gceme-backend:8080”.

Kubernetes Pod files

Within specific Kubernetes kind: Pod files, page https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/ provides an example of how environment variables are specified under the env: field header:

apiVersion: v1
kind: Pod
metadata:
  name: dapi-envars-fieldref
spec:
  containers:
    - name: test-container
      image: gcr.io/google_containers/busybox
      command: [ "sh", "-c"]
      args:
      - while true; do
          echo -en '\n';
          printenv MY_NODE_NAME MY_POD_NAME MY_POD_NAMESPACE;
          printenv MY_POD_IP MY_POD_SERVICE_ACCOUNT;
          sleep 10;
        done;
      env:
        - name: MY_NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        - name: MY_POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: MY_POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: MY_POD_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        - name: MY_POD_SERVICE_ACCOUNT
          valueFrom:
            fieldRef:
              fieldPath: spec.serviceAccountName
  restartPolicy: Never