<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.4.3">Jekyll</generator><link href="http://www.monkeylittle.com/feed.xml" rel="self" type="application/atom+xml" /><link href="http://www.monkeylittle.com/" rel="alternate" type="text/html" /><updated>2017-06-19T10:38:36+00:00</updated><id>http://www.monkeylittle.com/</id><title type="html">MonkeyLittle</title><subtitle>Thoughts on Software Engineering, Infrastructure and everything in between.
</subtitle><entry><title type="html">Exploring Kubernetes Init Container, ConfigMap &amp;amp; Secrets</title><link href="http://www.monkeylittle.com/blog/2017/02/22/exploring-kubernetes-init-container-configmap-secret.html" rel="alternate" type="text/html" title="Exploring Kubernetes Init Container, ConfigMap &amp; Secrets" /><published>2017-02-22T00:00:00+00:00</published><updated>2017-02-22T00:00:00+00:00</updated><id>http://www.monkeylittle.com/blog/2017/02/22/exploring-kubernetes-init-container-configmap-secret</id><content type="html" xml:base="http://www.monkeylittle.com/blog/2017/02/22/exploring-kubernetes-init-container-configmap-secret.html">&lt;p&gt;At the end of my last post I created a docker image for Jenkins that extended the official docker image by disabling the setup wizard, installing the default plugins and installing maven.  I skipped over:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;setting up ssh for GitHub.&lt;/li&gt;
  &lt;li&gt;automating the configuration of the Jenkins job(s).&lt;/li&gt;
  &lt;li&gt;creating appropriate Jenkins slave images.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this post I’ll describe how to set up SSH for GitHub.&lt;/p&gt;

&lt;h2 id=&quot;setting-up-ssh-for-github&quot;&gt;Setting Up SSH for GitHub&lt;/h2&gt;

&lt;p&gt;To set up SSH for GitHub I created a ConfigMap containing the ssh config.  I did this by creating the ConfigMap from a file.&lt;/p&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    .ssh/config
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot; data-lang=&quot;plaintext&quot;&gt;Host github.com
    HostName ssh.github.com
    Port 443
    User git
    IdentityFile ~/.ssh/id_jenkins_rsa&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;kubectl create configmap jenkins-ssh-config --from-file&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;.ssh/config --from-file&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;.ssh/known_hosts&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The SSH config specifies the identity to use as &lt;em&gt;~/.ssh/id_jenkins_rsa&lt;/em&gt;.  As the private key is something I want to secure, I will generate the key and store it as a Kubernetes secret.  First, follow the instructions on GitHub to &lt;a href=&quot;https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent&quot;&gt;generate a new ssh key&lt;/a&gt; and &lt;a href=&quot;https://help.github.com/articles/adding-a-new-ssh-key-to-your-github-account&quot;&gt;add it to your Github account&lt;/a&gt;.  Then you can store the private and public key as a Kubernetes secret.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;kubectl create secret generic jenkins-ssh-key --from-file&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;id_jenkins_rsa&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;.ssh/id_jenkins_rsa --from-file&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;id_jenkins_rsa.pub&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;.ssh/id_jenkins_rsa.pub&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now that we have made the SSH config and keys available to Kubernetes we need to configure the Jenkins container.  I’ve done this using the Kubernetes Init Container feature which is in beta as of version 1.5.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    deployment.yaml
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;s&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;extensions/v1beta1&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deployment&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;replicas&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;pod.beta.kubernetes.io/init-containers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;name&quot;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ssh-config&quot;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;image&quot;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;monkeylittle/jenkins:1.0.0&quot;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;imagePullPolicy&quot;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;IfNotPresent&quot;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;command&quot;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[&quot;bash&quot;,&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;-c&quot;,&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-e&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/var/jenkins_home/ssh_config&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-o&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-e&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/var/jenkins_home/ssh_key&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;];&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;do&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;1;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;done;\n&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;mkdir&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-p&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/var/jenkins_home/.ssh\n&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;cp&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/var/jenkins_home/ssh_config/*&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/var/jenkins_home/.ssh/\n&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;cp&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/var/jenkins_home/ssh_key/*&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/var/jenkins_home/.ssh/\n&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;chmod&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/var/jenkins_home/.ssh/id_jenkins*&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;],&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;volumeMounts&quot;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&quot;name&quot;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;jenkins-home&quot;,&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&quot;mountPath&quot;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/var/jenkins_home&quot;&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;},&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&quot;name&quot;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;jenkins-ssh-config&quot;,&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&quot;mountPath&quot;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/var/jenkins_home/ssh_config&quot;&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;},&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&quot;name&quot;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;jenkins-ssh-key&quot;,&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&quot;mountPath&quot;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/var/jenkins_home/ssh_key&quot;&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;]&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;]'&lt;/span&gt;

    &lt;span class=&quot;s&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;monkeylittle/jenkins:1.0.0&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;JAVA_OPTS&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;-Djenkins.install.runSetupWizard=false&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http-port&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;containerPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;8080&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jnlp-port&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;containerPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;50000&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;volumeMounts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins-home&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;mountPath&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/var/jenkins_home&lt;/span&gt;

      &lt;span class=&quot;s&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins-ssh-config&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;configMap&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins-ssh-config&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins-ssh-key&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;secretName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins-ssh-key&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins-home&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;emptyDir&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I have defined 3 volumes as part of the deployment.  The first populates the volume with the contents of the jenkins-ssh-config ConfigMap, the second with the contents of the jenkins-ssh-key Secret and the third is the Jenkins home directory.&lt;/p&gt;

&lt;p&gt;I have also defined an Init Container that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;mounts all 3 volumes.&lt;/li&gt;
  &lt;li&gt;copies the contents of jenkins-ssh-config to /var/jenkins_home/.ssh&lt;/li&gt;
  &lt;li&gt;copies the contents of jenkins-ssh-key to /var/jenkins_home/.ssh&lt;/li&gt;
  &lt;li&gt;changes the permissions of the Jenkins ssh keys to those allowed by the git client.&lt;/li&gt;
  &lt;li&gt;generates the ssh known_hosts file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;One thing of note (and this may be related to minikube itself) but the volumes may or may not be mounted at the start of the command so I needed to insert a statement tat waits until the jenkins-ssh-config and jenkins-ssh-key volumes are mounted before executing the remainder of the script.&lt;/em&gt;&lt;/p&gt;</content><author><name>john_turner</name></author><category term="Kubernetes" /><category term="Docker" /><category term="Jenkins" /><summary type="html">At the end of my last post I created a docker image for Jenkins that extended the official docker image by disabling the setup wizard, installing the default plugins and installing maven. I skipped over: setting up ssh for GitHub. automating the configuration of the Jenkins job(s). creating appropriate Jenkins slave images. In this post I’ll describe how to set up SSH for GitHub. Setting Up SSH for GitHub To set up SSH for GitHub I created a ConfigMap containing the ssh config. I did this by creating the ConfigMap from a file. .ssh/config Host github.com HostName ssh.github.com Port 443 User git IdentityFile ~/.ssh/id_jenkins_rsa kubectl create configmap jenkins-ssh-config --from-file=.ssh/config --from-file=.ssh/known_hosts The SSH config specifies the identity to use as ~/.ssh/id_jenkins_rsa. As the private key is something I want to secure, I will generate the key and store it as a Kubernetes secret. First, follow the instructions on GitHub to generate a new ssh key and add it to your Github account. Then you can store the private and public key as a Kubernetes secret. kubectl create secret generic jenkins-ssh-key --from-file=id_jenkins_rsa=.ssh/id_jenkins_rsa --from-file=id_jenkins_rsa.pub=.ssh/id_jenkins_rsa.pub Now that we have made the SSH config and keys available to Kubernetes we need to configure the Jenkins container. I’ve done this using the Kubernetes Init Container feature which is in beta as of version 1.5.</summary></entry><entry><title type="html">Building a Custom Jenkins Docker Image - Part 2</title><link href="http://www.monkeylittle.com/blog/2017/02/15/building-a-custom-jenkins-docker-image-part-2.html" rel="alternate" type="text/html" title="Building a Custom Jenkins Docker Image - Part 2" /><published>2017-02-15T00:00:00+00:00</published><updated>2017-02-15T00:00:00+00:00</updated><id>http://www.monkeylittle.com/blog/2017/02/15/building-a-custom-jenkins-docker-image-part-2</id><content type="html" xml:base="http://www.monkeylittle.com/blog/2017/02/15/building-a-custom-jenkins-docker-image-part-2.html">&lt;p&gt;In &lt;a href=&quot;{ post_url 2017-02-14-building-a-custom-jenkins-docker-image-part-1}&quot;&gt;Building a Custom Jenkins Docker Image - Part 1&lt;/a&gt; we disabled the Jenkins install wizard and installed the default plugins.  In part 2, I want to focus on ensuring the build tools are installed.  The target end state of our build infrastructure must include the installation and configuration of:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Java&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://jenkins.io/&quot;&gt;Jenkins&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Jenkins plugins&lt;/li&gt;
  &lt;li&gt;Jenkins jobs (using &lt;a href=&quot;https://wiki.jenkins-ci.org/display/JENKINS/Job+DSL+Plugin&quot;&gt;JobDSL&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://git-scm.com/&quot;&gt;Git&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://maven.apache.org/&quot;&gt;Apache Maven&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can review the &lt;a href=&quot;https://hub.docker.com/_/jenkins/&quot;&gt;official Jenkins image&lt;/a&gt; source on the &lt;a href=&quot;https://github.com/jenkinsci/docker&quot;&gt;jenkinsci docker GitHub repository&lt;/a&gt; to understand which of these tools are already provided for.  From the &lt;a href=&quot;https://github.com/jenkinsci/docker/blob/master/Dockerfile&quot;&gt;Dockerfile&lt;/a&gt; we can see the following:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-docker&quot; data-lang=&quot;docker&quot;&gt;FROM openjdk:8-jdk

RUN apt-get update &amp;amp;&amp;amp; apt-get install -y git curl &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This means that the official Jenkins image is based on the openjdk:8-jdk image (Java 8 JDK is installed) and that the Git client is installed.  There is nothing to suggest that Maven is installed and we can verify this by creating a new bash session in the container and listing the installed packages.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;kubectl &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;jenkins-155111175-p72qf -it -- bash
&lt;span class=&quot;gp&quot;&gt;jenkins@jenkins-155111175-p72qf:/$ &lt;/span&gt;apt list --installed
Listing... Done
acl/now 2.2.52-2 amd64 &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;installed,local]
...
lsb-base/now 4.1+Debian13+nmu1 all &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;installed,local]
mawk/now 1.3.3-17 amd64 &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;installed,local]
mercurial/now 3.1.2-2+deb8u3 amd64 &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;installed,local]
mercurial-common/now 3.1.2-2+deb8u3 all &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;installed,local]
mime-support/now 3.58 all &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;installed,local]
mount/now 2.25.2-6 amd64 &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;installed,local]
multiarch-support/now 2.19-18+deb8u7 amd64 &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;installed,local]
ncurses-base/now 5.9+20140913-1 all &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;installed,local]
...
zlib1g/now 1:1.2.8.dfsg-2+b1 amd64 &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;installed,local]
jenkins@jenkins-155111175-p72qf:/&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To install maven we use the &lt;a href=&quot;https://docs.docker.com/engine/reference/builder/#run&quot;&gt;Docker RUN statement&lt;/a&gt; in the Jenkins/Dockerfile to invoke &lt;em&gt;apt-get install&lt;/em&gt;.  This requires us first to become the root user and subsequently to resume as the jenkins user.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-docker&quot; data-lang=&quot;docker&quot;&gt;# install Maven
USER root
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y maven
USER jenkins&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;!-- more --&gt;

&lt;p&gt;I can now build a new Docker image:&lt;/p&gt;
&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    Dockerfile
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot; data-lang=&quot;plaintext&quot;&gt;from jenkins:2.32.2

# install plugins specified in https://github.com/kohsuke/jenkins/blob/master/core/src/main/resources/jenkins/install/platform-plugins.json

# install Organisation and Administration plugins
RUN /usr/local/bin/install-plugins.sh cloudbees-folder
RUN /usr/local/bin/install-plugins.sh antisamy-markup-formatter

# install Build Features plugins
RUN /usr/local/bin/install-plugins.sh build-timeout
RUN /usr/local/bin/install-plugins.sh credentials-binding
RUN /usr/local/bin/install-plugins.sh timestamper
RUN /usr/local/bin/install-plugins.sh ws-cleanup

# install Build Tools plugins
RUN /usr/local/bin/install-plugins.sh ant
RUN /usr/local/bin/install-plugins.sh gradle

# install Pipelines and Continuous Delivery plugins
RUN /usr/local/bin/install-plugins.sh workflow-aggregator:2.0
RUN /usr/local/bin/install-plugins.sh github-organization-folder:1.6
RUN /usr/local/bin/install-plugins.sh pipeline-stage-view:2.0

# install Source Code Management plugins
RUN /usr/local/bin/install-plugins.sh git
RUN /usr/local/bin/install-plugins.sh subversion

# install Distributed Builds plugins
RUN /usr/local/bin/install-plugins.sh ssh-slaves

# install User Management and Security plugins
RUN /usr/local/bin/install-plugins.sh matrix-auth
RUN /usr/local/bin/install-plugins.sh pam-auth
RUN /usr/local/bin/install-plugins.sh ldap

# install Notifications and Publishing plugins
RUN /usr/local/bin/install-plugins.sh email-ext
RUN /usr/local/bin/install-plugins.sh mailer

# install Maven
USER root
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y maven
USER jenkins&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;/div&gt;
&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;docker build -t monkeylittle/jenkins:1.1.0 .&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Note that I have changed the version tag.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As this is a personal exercise in learning about Kubernetes and Docker, I attempted to perform a rolling update of the deployment but this required using hostPath persistent volumes.  Minikube does not yet support running containers with non root users and hostPath volumes (which are provisioned by root user). See this &lt;a href=&quot;https://github.com/kubernetes/minikube/pull/959/commits/b70cac334dd0886681fac84775a0afa956931a2d&quot;&gt;guthub pull request&lt;/a&gt; for more information.&lt;/p&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    deployment.yaml
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;s&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;extensions/v1beta1&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deployment&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;replicas&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;monkeylittle/jenkins:1.1.0&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;JAVA_OPTS&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;-Djenkins.install.runSetupWizard=false&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http-port&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;containerPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;8080&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jnlp-port&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;containerPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;50000&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;volumeMounts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins-home&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;mountPath&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/var/jenkins_home&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins-home&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;emptyDir&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Then recreate the pod.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-docker&quot; data-lang=&quot;docker&quot;&gt;$ kubectl apply -f deployment.yaml
deployment &quot;jenkins&quot; configured&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It’s possible to verify the update by describing the pod:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;kubectl describe pod | grep Image:
    Image:		monkeylittle/jenkins:1.1.0&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To validate that maven is now installed within the container I can create a new bash session in the container and run &lt;em&gt;apt list –installed&lt;/em&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;kubectl &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;jenkins-258199304-4jf60 -it -- bash
&lt;span class=&quot;gp&quot;&gt;jenkins@jenkins-258199304-4jf60:/$ &lt;/span&gt;apt list --installed | grep maven

WARNING: apt does not have a stable CLI interface yet. Use with caution &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;scripts.

libmaven-parent-java/stable,now 21-2 all &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;installed,automatic]
libmaven-scm-java/stable,now 1.3-5 all &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;installed,automatic]
libmaven2-core-java/stable,now 2.2.1-17 all &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;installed,automatic]
maven/stable,now 3.0.5-3 all &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;installed]&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can also go ahead and create a build job via the Jenkins UI for one of your Java maven projects (i used &lt;a href=&quot;https://github.com/monkeylittle/spring-jpa-inheritance.git&quot;&gt;this one&lt;/a&gt;).  A couple of things that I skipped over:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I used the HTTPS protocol for the git repository to avoid having to add github to the known_hosts file.&lt;/li&gt;
  &lt;li&gt;I created the job manually rather than scripting the job creation.&lt;/li&gt;
  &lt;li&gt;I need to create equivalent jenkins slave images with tooling installed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are a few follow up tasks I’ll get to over the next couple of weeks.&lt;/p&gt;</content><author><name>john_turner</name></author><category term="Kubernetes" /><category term="Docker" /><category term="Jenkins" /><summary type="html">In Building a Custom Jenkins Docker Image - Part 1 we disabled the Jenkins install wizard and installed the default plugins. In part 2, I want to focus on ensuring the build tools are installed. The target end state of our build infrastructure must include the installation and configuration of: Java Jenkins Jenkins plugins Jenkins jobs (using JobDSL) Git Apache Maven We can review the official Jenkins image source on the jenkinsci docker GitHub repository to understand which of these tools are already provided for. From the Dockerfile we can see the following: FROM openjdk:8-jdk RUN apt-get update &amp;amp;&amp;amp; apt-get install -y git curl &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/* This means that the official Jenkins image is based on the openjdk:8-jdk image (Java 8 JDK is installed) and that the Git client is installed. There is nothing to suggest that Maven is installed and we can verify this by creating a new bash session in the container and listing the installed packages. $ kubectl exec jenkins-155111175-p72qf -it -- bash jenkins@jenkins-155111175-p72qf:/$ apt list --installed Listing... Done acl/now 2.2.52-2 amd64 [installed,local] ... lsb-base/now 4.1+Debian13+nmu1 all [installed,local] mawk/now 1.3.3-17 amd64 [installed,local] mercurial/now 3.1.2-2+deb8u3 amd64 [installed,local] mercurial-common/now 3.1.2-2+deb8u3 all [installed,local] mime-support/now 3.58 all [installed,local] mount/now 2.25.2-6 amd64 [installed,local] multiarch-support/now 2.19-18+deb8u7 amd64 [installed,local] ncurses-base/now 5.9+20140913-1 all [installed,local] ... zlib1g/now 1:1.2.8.dfsg-2+b1 amd64 [installed,local] jenkins@jenkins-155111175-p72qf:/$ To install maven we use the Docker RUN statement in the Jenkins/Dockerfile to invoke apt-get install. This requires us first to become the root user and subsequently to resume as the jenkins user. # install Maven USER root RUN apt-get update &amp;amp;&amp;amp; apt-get install -y maven USER jenkins</summary></entry><entry><title type="html">Building a Custom Jenkins Docker Image - Part 1</title><link href="http://www.monkeylittle.com/blog/2017/02/14/building-a-custom-jenkins-docker-image-part-1.html" rel="alternate" type="text/html" title="Building a Custom Jenkins Docker Image - Part 1" /><published>2017-02-14T00:00:00+00:00</published><updated>2017-02-14T00:00:00+00:00</updated><id>http://www.monkeylittle.com/blog/2017/02/14/building-a-custom-jenkins-docker-image-part-1</id><content type="html" xml:base="http://www.monkeylittle.com/blog/2017/02/14/building-a-custom-jenkins-docker-image-part-1.html">&lt;p&gt;Last week I spent some time learning how to utilize &lt;a href=&quot;https://kubernetes.io/&quot;&gt;Kubernetes&lt;/a&gt; and &lt;a href=&quot;https://jenkins.io/&quot;&gt;Jenkins&lt;/a&gt; to form the foundation of a build infrastructure.  I documented some of those learnings in the posts below:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/02/06/kubernetes-fundamentals.html&quot;&gt;Kubernetes Fundamentals&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/02/07/deploying-jenkins-with-kubernetes.html&quot;&gt;Deploying Jenkins with Kubernetes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/02/08/adding-persistent-volumes-to-jenkins-with-kubernetes-volumes.html&quot;&gt;Adding Persistent Volumes to Jenkins with Kubernetes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2017/02/09/autoscaling-jenkins-with-kubernetes.html&quot;&gt;Auto-Scaling Jenkins with Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a pretty good start but having built out VM based build infrastructure a number of times in the past I’m well aware that there is a long way to go before I have something I can use, manage and maintain.  When working with VM’s I’ve perviously chosen to use &lt;a href=&quot;https://www.chef.io&quot;&gt;Chef&lt;/a&gt; to manage and maintain the Jenkins master and slave hosts.  Typically, Chef facilitated automation of the installation and configuration of:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Java&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://jenkins.io/&quot;&gt;Jenkins&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Jenkins plugins&lt;/li&gt;
  &lt;li&gt;Jenkins jobs (using &lt;a href=&quot;https://wiki.jenkins-ci.org/display/JENKINS/Job+DSL+Plugin&quot;&gt;JobDSL&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://git-scm.com/&quot;&gt;Git&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://maven.apache.org/&quot;&gt;Apache Maven&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To achieve the same level of automation with &lt;a href=&quot;https://www.docker.com/&quot;&gt;Docker&lt;/a&gt; and &lt;a href=&quot;https://kubernetes.io/&quot;&gt;Kubernetes&lt;/a&gt;, I will need to be able to perform all of the above and distribute as a set of Kubernetes resource definition files and Docker image(s).  Before we start doing anything meaningful, I want to disable the setup wizard because, after all, we will be automating the Jenkins setup.  To do this I modify the Kubernetes deployment resource file to specify a JAVA_OPTS environment variable.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    jenkins-deployment.yaml
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;s&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;extensions/v1beta1&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deployment&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;replicas&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins:2.32.2&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;JAVA_OPTS&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;-Djenkins.install.runSetupWizard=false&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http-port&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;containerPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;8080&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jnlp-port&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;containerPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;50000&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;volumeMounts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins-home&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;mountPath&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/var/jenkins_home&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins-home&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;emptyDir&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;You can test this by creating the Kubernetes deployment and service as described in previous posts.&lt;/p&gt;

&lt;p&gt;The next task is to replicate the installation of the recommended Jenkins plugins.  To do this we must build our own docker image but first we will need to setup our local development environment.&lt;/p&gt;

&lt;p&gt;Install the docker command line tool:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;brew install docker&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Configure your environment to use the Docker Host provided by minikube:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;eval&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;$(&lt;/span&gt;minikube docker-env&lt;span class=&quot;k&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I will create a working directory in which I’ll store source files.  The name is not important but I will call it &lt;em&gt;docker-library/jenkins&lt;/em&gt;.  This is where I will maintain all my docker images (there’s ambition for you!).&lt;/p&gt;

&lt;p&gt;In this directory I’ll create a Dockerfile.  The first statement I’ll add is the &lt;em&gt;FROM&lt;/em&gt; statement which tells Docker which image my image will be based on.  Naturally I will base my image on the &lt;a href=&quot;https://hub.docker.com/_/jenkins/&quot;&gt;official Jenkins image&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-docker&quot; data-lang=&quot;docker&quot;&gt;from jenkins:2.32.2&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The documentation for the official docker image details how to install additional plugins.  I found this not to work with the latest version of the image but found more recent instructions on the associated github &lt;a href=&quot;https://github.com/jenkinsci/docker&quot;&gt;readme&lt;/a&gt; which I verified did behave as expected.  I was able to install additional plugins by adding the statement below to the Dockerfile.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-docker&quot; data-lang=&quot;docker&quot;&gt;RUN /usr/local/bin/install-plugins.sh docker-slaves plugin-name:plugin-version&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In order to mimic exactly the behavior of the install wizard I wanted to install the recommended plugins, versions and their dependencies.  The wizard retrieves the recommended plugins from a &lt;a href=&quot;https://github.com/kohsuke/jenkins/blob/master/core/src/main/resources/jenkins/install/platform-plugins.json&quot;&gt;json file&lt;/a&gt; available from the Jenkins GitHub repository.&lt;/p&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    Dockerfile
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot; data-lang=&quot;plaintext&quot;&gt;from jenkins:2.32.2

# install plugins specified in https://github.com/kohsuke/jenkins/blob/master/core/src/main/resources/jenkins/install/platform-plugins.json

# install Organisation and Administration plugins
RUN /usr/local/bin/install-plugins.sh cloudbees-folder
RUN /usr/local/bin/install-plugins.sh antisamy-markup-formatter

# install Build Features plugins
RUN /usr/local/bin/install-plugins.sh build-timeout
RUN /usr/local/bin/install-plugins.sh credentials-binding
RUN /usr/local/bin/install-plugins.sh timestamper
RUN /usr/local/bin/install-plugins.sh ws-cleanup

# install Build Tools plugins
RUN /usr/local/bin/install-plugins.sh ant
RUN /usr/local/bin/install-plugins.sh gradle

# install Pipelines and Continuous Delivery plugins
RUN /usr/local/bin/install-plugins.sh workflow-aggregator:2.0
RUN /usr/local/bin/install-plugins.sh github-organization-folder:1.6
RUN /usr/local/bin/install-plugins.sh pipeline-stage-view:2.0

# install Source Code Management plugins
RUN /usr/local/bin/install-plugins.sh git
RUN /usr/local/bin/install-plugins.sh subversion

# install Distributed Builds plugins
RUN /usr/local/bin/install-plugins.sh ssh-slaves

# install User Management and Security plugins
RUN /usr/local/bin/install-plugins.sh matrix-auth
RUN /usr/local/bin/install-plugins.sh pam-auth
RUN /usr/local/bin/install-plugins.sh ldap

# install Notifications and Publishing plugins
RUN /usr/local/bin/install-plugins.sh email-ext
RUN /usr/local/bin/install-plugins.sh mailer&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Lets build the docker image specifying the image name and version tag.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;docker build -t monkeylittle/jenkins:1.0.0 .&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Learn about what &lt;a href=&quot;https://docs.docker.com/engine/getstarted/step_four/#/step-3-learn-about-the-build-process&quot;&gt;happens during the build process&lt;/a&gt; by reading the Docker getting started guide.&lt;/p&gt;

&lt;p&gt;Verify the image was created and uploaded to the minikube local registry as follows:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-docker&quot; data-lang=&quot;docker&quot;&gt;$ docker images
REPOSITORY                                            TAG                 IMAGE ID            CREATED             SIZE
monkeylittle/jenkins                                  1.0.0               443d3e0a31f8        9 minutes ago       712 MB
...&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;It’s important that when building the image that a tag is specified.  If not specified Docker will attempt and fail to find the image on &lt;a href=&quot;https://hub.docker.com&quot;&gt;DockerHub&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now that we have our docker image available in the minikube local docker registry we can update our kubernetes deployment resource file to specify our newly created image.&lt;/p&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    jenkins-deployment.yaml
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;s&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;extensions/v1beta1&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deployment&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;replicas&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;monkeylittle/jenkins:1.0.0&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;JAVA_OPTS&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;-Djenkins.install.runSetupWizard=false&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http-port&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;containerPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;8080&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jnlp-port&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;containerPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;50000&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;volumeMounts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins-home&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;mountPath&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/var/jenkins_home&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins-home&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;emptyDir&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    jenkins-service.yaml
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;s&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Service&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;NodePort&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;8080&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;targetPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;8080&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;You can now create the kubernetes deployment (and service) and view the Jenkins UI in a browser to verify the default plugins have been installed.  Next step is to automate the installation of the various required tools.&lt;/p&gt;</content><author><name>john_turner</name></author><category term="Kubernetes" /><category term="Docker" /><category term="Jenkins" /><summary type="html">Last week I spent some time learning how to utilize Kubernetes and Jenkins to form the foundation of a build infrastructure. I documented some of those learnings in the posts below: Kubernetes Fundamentals Deploying Jenkins with Kubernetes Adding Persistent Volumes to Jenkins with Kubernetes Auto-Scaling Jenkins with Kubernetes This is a pretty good start but having built out VM based build infrastructure a number of times in the past I’m well aware that there is a long way to go before I have something I can use, manage and maintain. When working with VM’s I’ve perviously chosen to use Chef to manage and maintain the Jenkins master and slave hosts. Typically, Chef facilitated automation of the installation and configuration of: Java Jenkins Jenkins plugins Jenkins jobs (using JobDSL) Git Apache Maven To achieve the same level of automation with Docker and Kubernetes, I will need to be able to perform all of the above and distribute as a set of Kubernetes resource definition files and Docker image(s). Before we start doing anything meaningful, I want to disable the setup wizard because, after all, we will be automating the Jenkins setup. To do this I modify the Kubernetes deployment resource file to specify a JAVA_OPTS environment variable.</summary></entry><entry><title type="html">Auto-Scaling Jenkins with Kubernetes</title><link href="http://www.monkeylittle.com/blog/2017/02/09/autoscaling-jenkins-with-kubernetes.html" rel="alternate" type="text/html" title="Auto-Scaling Jenkins with Kubernetes" /><published>2017-02-09T00:00:00+00:00</published><updated>2017-02-09T00:00:00+00:00</updated><id>http://www.monkeylittle.com/blog/2017/02/09/autoscaling-jenkins-with-kubernetes</id><content type="html" xml:base="http://www.monkeylittle.com/blog/2017/02/09/autoscaling-jenkins-with-kubernetes.html">&lt;p&gt;&lt;a href=&quot;https://jenkins.io/&quot;&gt;Jenkins&lt;/a&gt; is a great piece of software (ok, it has problems but I couldn’t imagine software development without it).  But one of the challenges with maintaining a Jenkins cluster is capacity management.  It’s fairly typical to start out with a single master instance.  Over time the number and size of Jenkins jobs increases placing more and more demand on the server.  The first fix people apply when this happens is to vertically scale the Jenkins server (In fact I recently interviewed for a position and was told their Jenkins server hardware had 40 cores and 512GB of RAM).  Some of the problems with scaling vertically include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Cost per unit of scale increases with the size of the hardware.&lt;/li&gt;
  &lt;li&gt;Complex software configuration required to support a large variety of job types.&lt;/li&gt;
  &lt;li&gt;Greater risk of ‘noisy neighbours’ impacting:
    &lt;ul&gt;
      &lt;li&gt;job performance.&lt;/li&gt;
      &lt;li&gt;server stability.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The alternatives to vertically scaling the Jenkins master are to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Deploy multiple Jenkins masters allocated:
    &lt;ul&gt;
      &lt;li&gt;per environment.&lt;/li&gt;
      &lt;li&gt;per organisational unit.&lt;/li&gt;
      &lt;li&gt;per product line.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Deploy statically provisioned Jenkins slaves.&lt;/li&gt;
  &lt;li&gt;Deploy dynamicaly provisioned Jenkins slaves.&lt;/li&gt;
  &lt;li&gt;Deploy multiple Jenkins masters with statically or dynamically provisioned Jenkins slaves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are a few things to consider when choosing how to scale your Jenkins infrastructure.  If you choose to deploy multiple master you should have an efficient and effective way to manage them.  At a minimum, you should use configuration management or orchestration tooling to manage the lifecycle of the instances themselves.  You should also consider similar for managing plugins, jobs etc.  In the past I have had great success using tools like &lt;a href=&quot;https://www.chef.io/&quot;&gt;Chef&lt;/a&gt; and &lt;a href=&quot;https://wiki.jenkins-ci.org/display/JENKINS/Job+DSL+Plugin&quot;&gt;Job DSL&lt;/a&gt; to manage build infrastructure.&lt;/p&gt;

&lt;p&gt;If you choose to use slaves, consider if you should provision bloated slaves capable of performing any build Job or if you should provision specialized slaves.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;Time to press on and deploy an auto-scaling Jenkins cluster with Kubernetes.&lt;/p&gt;

&lt;p&gt;First thing we’ll do is to deploy a Jenkins master.  If you haven’t already done this you can follow the instructions in the previous posts &lt;a href=&quot;/blog/2017/02/07/deploying-jenkins-with-kubernetes.html&quot;&gt;Deploying Jenkins with Kubernetes&lt;/a&gt; and &lt;a href=&quot;/blog/2017/02/08/adding-persistent-volumes-to-jenkins-with-kubernetes-volumes.html&quot;&gt;Adding Persistent Volumes to Jenkins with Kubernetes&lt;/a&gt;.  We do however need to make one change to the jenkins-deployment.yaml file so that the JNLP port is exposed.&lt;/p&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    jenkins-deployment.yaml
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;s&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;extensions/v1beta1&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deployment&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;replicas&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins:2.32.2&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http-port&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;containerPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;8080&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jnlp-port&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;containerPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;50000&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;volumeMounts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins-home&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;mountPath&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/var/jenkins_home&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins-home&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;emptyDir&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;With the Jenkins master deployed we next configure Jenkins to auto-scale slave nodes.&lt;/p&gt;

&lt;p&gt;As described in &lt;a href=&quot;/blog/2017/02/08/adding-persistent-volumes-to-jenkins-with-kubernetes-volumes.html&quot;&gt;Adding Persistent Volumes to Jenkins with Kubernetes&lt;/a&gt; you should enter the administrator password, install suggested plugins and create an administrator user.&lt;/p&gt;

&lt;p&gt;For Jenkins to dynamically provision Jenkins slaves as Kubernetes Pods, the &lt;a href=&quot;https://wiki.jenkins-ci.org/display/JENKINS/Kubernetes+Plugin&quot;&gt;Kubernetes Plugin&lt;/a&gt; is required.  To install the Kubernetes Plugin navigate to &lt;em&gt;Manage Jenkins &amp;gt; Manage Plugins &amp;gt; Available&lt;/em&gt;, search for the Kubernetes Plugin, check the install checkbox and press &lt;em&gt;Download now and install after restart&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;With the Kubernetes plugin installed it must be configured by navigating to &lt;em&gt;Manage Jenkins &amp;gt; Configure System&lt;/em&gt; and scrolling to the &lt;em&gt;Cloud&lt;/em&gt; section.  First we configure the &lt;em&gt;Kubernetes Section&lt;/em&gt; as below:&lt;/p&gt;

&lt;figure class=&quot;figure&quot;&gt;
  &lt;a href=&quot;/assets/img/post/2017-02-09-autoscaling-jenkins-with-kubernetes/kubernetes-plugin-configuration.png&quot; data-lightbox=&quot;kubernetes-plugin-configuration&quot; data-title=&quot;Kubernetes Plugin Configuration&quot;&gt;
    &lt;img class=&quot;img-fluid img-thumbnail mx-auto&quot; src=&quot;/assets/img/post/2017-02-09-autoscaling-jenkins-with-kubernetes/kubernetes-plugin-configuration.png&quot; alt=&quot;Kubernetes Plugin Configuration&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption class=&quot;figure-caption text-center&quot;&gt;Kubernetes Plugin Configuration&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;To obtain the Kubernetes URL you should invoke:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;kubectl cluster-info | grep master
Kubernetes master is running at https://192.168.99.100:8443&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To obtain the Jenkins URL you first need to obtain the pod name of the Jenkins master:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;kubectl get pods | grep ^jenkins
jenkins-2559287856-5p145             1/1       Running   0          54m&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And then obtain the IP address of the pod:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;kubectl describe pod jenkins-2559287856-5p145 | grep IP:
IP:		172.17.0.4&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;With these configuration entries the Jenkins Kubernetes plugin can interact with the Kubernetes API server.  Next we need to configure the pod template and container for the slave so that the plugin can provision a pod.&lt;/p&gt;

&lt;figure class=&quot;figure&quot;&gt;
  &lt;a href=&quot;/assets/img/post/2017-02-09-autoscaling-jenkins-with-kubernetes/kubernetes-plugin-pod-template-configuration.png&quot; data-lightbox=&quot;kubernetes-plugin-pod-template-configuration&quot; data-title=&quot;Kubernetes Plugin Pod Template Configuration&quot;&gt;
    &lt;img class=&quot;img-fluid img-thumbnail mx-auto&quot; src=&quot;/assets/img/post/2017-02-09-autoscaling-jenkins-with-kubernetes/kubernetes-plugin-pod-template-configuration.png&quot; alt=&quot;Kubernetes Plugin Pod Template Configuration&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption class=&quot;figure-caption text-center&quot;&gt;Kubernetes Plugin Pod Template Configuration&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;A few things to note here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The docker image is the standard Jenkins slave image from Jenkins CI.  It is probable that you will want to use your own images for Jenkins slaves.&lt;/li&gt;
  &lt;li&gt;Being able to specify labels and docker images makes it possible to use specialized Jenkins slaves.&lt;/li&gt;
  &lt;li&gt;The working directory is specific to the docker image.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’ve followed along so far you should be ready to create a job to test our autoscaling behavior.  Go ahead and create a new &lt;em&gt;Freestyle project&lt;/em&gt;.  You will need to set the &lt;em&gt;Label Expression&lt;/em&gt; field to match that specified in the &lt;em&gt;Pod Template&lt;/em&gt; configuration.  I’ve also created an &lt;em&gt;Execute shell&lt;/em&gt; build step.&lt;/p&gt;

&lt;figure class=&quot;figure&quot;&gt;
  &lt;a href=&quot;/assets/img/post/2017-02-09-autoscaling-jenkins-with-kubernetes/job-general-configuration.png&quot; data-lightbox=&quot;job-general-configuration&quot; data-title=&quot;Job General Configuration&quot;&gt;
    &lt;img class=&quot;img-fluid img-thumbnail mx-auto&quot; src=&quot;/assets/img/post/2017-02-09-autoscaling-jenkins-with-kubernetes/job-general-configuration.png&quot; alt=&quot;Job General Configuration&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption class=&quot;figure-caption text-center&quot;&gt;Job General Configuration&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;hr /&gt;

&lt;figure class=&quot;figure&quot;&gt;
  &lt;a href=&quot;/assets/img/post/2017-02-09-autoscaling-jenkins-with-kubernetes/job-build-step-configuration.png&quot; data-lightbox=&quot;job-build-step-configuration&quot; data-title=&quot;Job Build Step Configuration&quot;&gt;
    &lt;img class=&quot;img-fluid img-thumbnail mx-auto&quot; src=&quot;/assets/img/post/2017-02-09-autoscaling-jenkins-with-kubernetes/job-build-step-configuration.png&quot; alt=&quot;Job Build Step Configuration&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption class=&quot;figure-caption text-center&quot;&gt;Job Build Step Configuration&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;You are now ready to build the job.  Before doing that you should &lt;em&gt;watch&lt;/em&gt; the Kubernetes Pods.  Do this by installing watch (brew install watch) and executing the watch as follows:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;watch kubectl get pod
Every 2.0s: kubectl get pod                                                                                                                                                                                                                                                                                                            Homer.local: Thu Feb  9 17:06:24 2017

NAME                                 READY     STATUS    RESTARTS   AGE
default-jenkins-slave-11f26675ac49   2/2       Running   0          4s
jenkins-2559287856-5p145             1/1       Running   0          1h&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;div style=&quot;border-left: 0.25rem solid #eceeef;&quot;&gt;
&lt;div class=&quot;pl-3&quot;&gt;
&lt;strong&gt;As pointed out by Daniel Vigueras below, a better way to watch the Kubernetes Pods may be to use the command: &lt;/strong&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;kubectl get pods -w&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Then as your build job executes you can watch the pod being created and destroyed.&lt;/p&gt;

&lt;p&gt;Over the past few days I’ve:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;given a high level overview of Kubernetes,&lt;/li&gt;
  &lt;li&gt;demonstrated how to deploy Jenkins,&lt;/li&gt;
  &lt;li&gt;how to add persistent volumes so that the Jenkins data is durable across container restarts&lt;/li&gt;
  &lt;li&gt;and how to configure an autoscaling Jenkins cluster.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, I’d like to build a &lt;em&gt;bespoke&lt;/em&gt; Jenkins image that avoids having to go through the Jenkins initialization steps.  As always, feedback very welcome.&lt;/p&gt;</content><author><name>john_turner</name></author><category term="Kubernetes" /><category term="Docker" /><category term="Jenkins" /><summary type="html">Jenkins is a great piece of software (ok, it has problems but I couldn’t imagine software development without it). But one of the challenges with maintaining a Jenkins cluster is capacity management. It’s fairly typical to start out with a single master instance. Over time the number and size of Jenkins jobs increases placing more and more demand on the server. The first fix people apply when this happens is to vertically scale the Jenkins server (In fact I recently interviewed for a position and was told their Jenkins server hardware had 40 cores and 512GB of RAM). Some of the problems with scaling vertically include: Cost per unit of scale increases with the size of the hardware. Complex software configuration required to support a large variety of job types. Greater risk of ‘noisy neighbours’ impacting: job performance. server stability. The alternatives to vertically scaling the Jenkins master are to: Deploy multiple Jenkins masters allocated: per environment. per organisational unit. per product line. Deploy statically provisioned Jenkins slaves. Deploy dynamicaly provisioned Jenkins slaves. Deploy multiple Jenkins masters with statically or dynamically provisioned Jenkins slaves. There are a few things to consider when choosing how to scale your Jenkins infrastructure. If you choose to deploy multiple master you should have an efficient and effective way to manage them. At a minimum, you should use configuration management or orchestration tooling to manage the lifecycle of the instances themselves. You should also consider similar for managing plugins, jobs etc. In the past I have had great success using tools like Chef and Job DSL to manage build infrastructure. If you choose to use slaves, consider if you should provision bloated slaves capable of performing any build Job or if you should provision specialized slaves.</summary></entry><entry><title type="html">Adding Persistent Volumes to Jenkins with Kubernetes</title><link href="http://www.monkeylittle.com/blog/2017/02/08/adding-persistent-volumes-to-jenkins-with-kubernetes-volumes.html" rel="alternate" type="text/html" title="Adding Persistent Volumes to Jenkins with Kubernetes" /><published>2017-02-08T00:00:00+00:00</published><updated>2017-02-08T00:00:00+00:00</updated><id>http://www.monkeylittle.com/blog/2017/02/08/adding-persistent-volumes-to-jenkins-with-kubernetes-volumes</id><content type="html" xml:base="http://www.monkeylittle.com/blog/2017/02/08/adding-persistent-volumes-to-jenkins-with-kubernetes-volumes.html">&lt;p&gt;Having had a first attempt at &lt;a href=&quot;/blog/2017/02/07/deploying-jenkins-with-kubernetes.html&quot;&gt;Deploying Jenkins with Kubernetes&lt;/a&gt; I’ll next focus on incrementally making the Jenkins deployment more robust.  The first thing that I’ll investigate is how I can preserve the contents of the &lt;em&gt;JENKINS_HOME&lt;/em&gt; directory.  Jenkins stores all of its important information within the &lt;em&gt;JENKINS_HOME&lt;/em&gt; such as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;build server configuration.&lt;/li&gt;
  &lt;li&gt;build jobs.&lt;/li&gt;
  &lt;li&gt;build artifacts.&lt;/li&gt;
  &lt;li&gt;user accounts.&lt;/li&gt;
  &lt;li&gt;user installed plugins.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By default disk files in a container are ephemeral.  This means that when the Jenkins container fails and is recovered (or indeed when an upgrade occurs) that the data within the &lt;em&gt;JENKINS_HOME&lt;/em&gt; directory is lost &lt;strong&gt;forever&lt;/strong&gt;.  It would be desirable to have the &lt;em&gt;JENKINS_HOME&lt;/em&gt; directory preserved across failure, recovery and upgrade processes.  This can be achieved using Kubernetes &lt;em&gt;Volumes&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;container-filesystem&quot;&gt;Container Filesystem&lt;/h2&gt;

&lt;p&gt;We cannot talk about Kubernetes volumes without understanding the Docker filesystem.  The Docker filesystem is composed of a number of layers starting with one or more read only image layers.  When a container is created, a read/write container layer is added to the top of the stack.  When a file is read from the filesystem each layer is inspected from the top of the stack to the bottom until the file is found or the bottom of the stack is reached.  When a file is modified it is first copied to the top of the stack and so the next time it is read the modified file is found first.&lt;/p&gt;

&lt;figure class=&quot;figure&quot;&gt;
  &lt;a href=&quot;/assets/img/post/2017-02-08-adding-persistent-volumes-to-jenkins-with-kubernetes-volumes/container-filesystem.png&quot; data-lightbox=&quot;container-filesystem&quot; data-title=&quot;Container Filesystem&quot;&gt;
    &lt;img class=&quot;img-fluid img-thumbnail mx-auto&quot; src=&quot;/assets/img/post/2017-02-08-adding-persistent-volumes-to-jenkins-with-kubernetes-volumes/container-filesystem.png&quot; alt=&quot;Container Filesystem&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption class=&quot;figure-caption text-center&quot;&gt;Container Filesystem&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;!-- more --&gt;

&lt;p&gt;The top of the stack is the container layer.  The container layer is ephemeral so the modifications to files within the container layer have the same lifecycle as the container.  That is, when the container is destroyed the modifications to the files in the container layer are lost.&lt;/p&gt;

&lt;p&gt;We can demonstrate this using Jenkins as described in ‘&lt;a href=&quot;/blog/2017/02/07/deploying-jenkins-with-kubernetes.html&quot;&gt;Deploying Jenkins with Kubernetes&lt;/a&gt;’.  First, create the Jenkins deployment and service:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;kubectl create -f jenkins-deployment.yaml
kybectl create -f jenkins-service.yaml&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In your browser, navigate to the jenkins service endpoint.  You can discover this URL by running the following command:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;$(&lt;/span&gt;minikube ip&lt;span class=&quot;k&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;$(&lt;/span&gt;kubectl describe service jenkins | grep NodePort: | grep -Eo &lt;span class=&quot;s1&quot;&gt;'[0-9]{1,5}'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
192.168.99.100:32028&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You should be presented with the &lt;em&gt;Unlock Jenkins&lt;/em&gt; screen below.&lt;/p&gt;

&lt;figure class=&quot;figure&quot;&gt;
  &lt;a href=&quot;/assets/img/post/2017-02-08-adding-persistent-volumes-to-jenkins-with-kubernetes-volumes/unlock-jenkins.png&quot; data-lightbox=&quot;unlock-jenkins&quot; data-title=&quot;Unlock Jenkins Screen&quot;&gt;
    &lt;img class=&quot;img-fluid img-thumbnail mx-auto&quot; src=&quot;/assets/img/post/2017-02-08-adding-persistent-volumes-to-jenkins-with-kubernetes-volumes/unlock-jenkins.png&quot; alt=&quot;Unlock Jenkins&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption class=&quot;figure-caption text-center&quot;&gt;Unlock Jenkins Screen&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Lets proceed to configure Jenkins.  To retrieve the administrator password we need to look at the Jenkins log.  First query the name of the Jenkins pod.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;kubectl get pods
NAME                       READY     STATUS    RESTARTS   AGE
jenkins-2843131955-brz6t   1/1       Running   1          1h&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Next, query the log from the Jenkins pod.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;kubectl logs jenkins-2843131955-brz6t
Running from: /usr/share/jenkins/jenkins.war
...
&lt;span class=&quot;k&quot;&gt;*************************************************************&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;*************************************************************&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;*************************************************************&lt;/span&gt;

Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:

2dda09e55aaa4052983aaf78e38eaedf

This may also be found at: /var/jenkins_home/secrets/initialAdminPassword

&lt;span class=&quot;k&quot;&gt;*************************************************************&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;*************************************************************&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;*************************************************************&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now armed with the administrator password you can proceed through the setup screens.  What is happening in the background is that the modifications to the data in JENKINS_HOME is being written to the container layer of the filesystem.  Next, we want to ssh onto the Kubernetes Node and kill the Jenkins docker container.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;minikube ssh
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;docker ps
CONTAINER ID        IMAGE                                                        COMMAND                  CREATED             STATUS              PORTS               NAMES
17f7d403f26b        jenkins:2.32.2                                               &lt;span class=&quot;s2&quot;&gt;&quot;/bin/tini -- /usr/lo&quot;&lt;/span&gt;   About an hour ago   Up About an hour                        
...
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;docker &lt;span class=&quot;nb&quot;&gt;kill &lt;/span&gt;17f7d403f26b
17f7d403f26b&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If you were to run &lt;em&gt;docker ps&lt;/em&gt; again you will notice that the Kubernetes replication controller has recreated the container and the new container has a different CONTAINER ID.  If you return to Jenkins you will be presented with the &lt;em&gt;Unlock Jenkins&lt;/em&gt; screen.  What has happened is that when the container was destroyed so to was the associated container layer.  When the container was recreated a new (empty) container layer was created.  As a result, the previous modifications to the configuration was lost forever.&lt;/p&gt;

&lt;h2 id=&quot;kubernetes-volumes&quot;&gt;Kubernetes Volumes&lt;/h2&gt;

&lt;p&gt;Kubernetes Volumes can help preserve data across container restarts (and indeed Pod restarts depending on the type of volume used).  The lifecycle of a Kubernetes Volume is bound to the same lifecycle as a Pod.  A Volume (and its data) is preserved across container restarts irrespective of the nature of the restart (routine restart or due to failure).  When the Pod is destroyed so too are the associated Volumes.  Because the volume is associated with the Pod it is accessible to all containers within the Pod.&lt;/p&gt;

&lt;p&gt;A volume is mounted on top of the container filesystem and so data is still read in the same way as before except that modifications will occur to the volume.  Because the volume is durable across container restarts the data is preserved.&lt;/p&gt;

&lt;figure class=&quot;figure&quot;&gt;
  &lt;a href=&quot;/assets/img/post/2017-02-08-adding-persistent-volumes-to-jenkins-with-kubernetes-volumes/kubernetes-volumes.png&quot; data-lightbox=&quot;kubernetes-volumes&quot; data-title=&quot;Kubernetes Volumes&quot;&gt;
    &lt;img class=&quot;img-fluid img-thumbnail mx-auto&quot; src=&quot;/assets/img/post/2017-02-08-adding-persistent-volumes-to-jenkins-with-kubernetes-volumes/kubernetes-volumes.png&quot; alt=&quot;Kubernetes Volumes&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption class=&quot;figure-caption text-center&quot;&gt;Kubernetes Volumes&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Lets add a volume to our Jenkins deployment.&lt;/p&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    jenkins-deployment.yaml
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;s&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;extensions/v1beta1&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deployment&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;replicas&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins:2.32.2&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;containerPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;8080&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;volumeMounts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins-home&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;mountPath&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/var/jenkins_home&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins-home&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;emptyDir&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The spec now defines a volume named &lt;em&gt;jenkins-home&lt;/em&gt;.  This volume is mounted within the container at the path &lt;em&gt;/var/jenkins_home&lt;/em&gt; and so modifications to data within &lt;em&gt;/var/jenkins_home&lt;/em&gt; are written to the volume.  If we were to recreate the deployment, configure Jenkins and kill the Jenkins container as before you will not be presented with the &lt;em&gt;Unlock Jenkins&lt;/em&gt; screen a second time.&lt;/p&gt;

&lt;p&gt;Of course, it the Pod is destroyed so too is the volume.  If you need the data to be preserved across Pod failures (node failures etc.) you will need to use remote storage such as AWS EBS or GCE Persistent Disk.&lt;/p&gt;

&lt;p&gt;I’ve definitely learned a bit about the Docker and Kubernetes filesystem today and evolved the previous Jenkins example so that it is a little bit more robust.  I didn’t like the manual setup steps needed to configure Jenkins so next I’ll walk through creating my own Jenkins image.&lt;/p&gt;</content><author><name>john_turner</name></author><category term="Kubernetes" /><category term="Docker" /><category term="Jenkins" /><summary type="html">Having had a first attempt at Deploying Jenkins with Kubernetes I’ll next focus on incrementally making the Jenkins deployment more robust. The first thing that I’ll investigate is how I can preserve the contents of the JENKINS_HOME directory. Jenkins stores all of its important information within the JENKINS_HOME such as: build server configuration. build jobs. build artifacts. user accounts. user installed plugins. By default disk files in a container are ephemeral. This means that when the Jenkins container fails and is recovered (or indeed when an upgrade occurs) that the data within the JENKINS_HOME directory is lost forever. It would be desirable to have the JENKINS_HOME directory preserved across failure, recovery and upgrade processes. This can be achieved using Kubernetes Volumes. Container Filesystem We cannot talk about Kubernetes volumes without understanding the Docker filesystem. The Docker filesystem is composed of a number of layers starting with one or more read only image layers. When a container is created, a read/write container layer is added to the top of the stack. When a file is read from the filesystem each layer is inspected from the top of the stack to the bottom until the file is found or the bottom of the stack is reached. When a file is modified it is first copied to the top of the stack and so the next time it is read the modified file is found first. Container Filesystem</summary></entry><entry><title type="html">Deploying Jenkins with Kubernetes</title><link href="http://www.monkeylittle.com/blog/2017/02/07/deploying-jenkins-with-kubernetes.html" rel="alternate" type="text/html" title="Deploying Jenkins with Kubernetes" /><published>2017-02-07T00:00:00+00:00</published><updated>2017-02-07T00:00:00+00:00</updated><id>http://www.monkeylittle.com/blog/2017/02/07/deploying-jenkins-with-kubernetes</id><content type="html" xml:base="http://www.monkeylittle.com/blog/2017/02/07/deploying-jenkins-with-kubernetes.html">&lt;p&gt;Quick on the heels of my recent overview of the &lt;a href=&quot;/blog/2017/02/06/kubernetes-fundamentals.html&quot;&gt;Kubernetes Fundamentals&lt;/a&gt; I wanted to dive into deploying something familiar (&lt;a href=&quot;https://jenkins.io/&quot;&gt;Jenkins&lt;/a&gt;) so that I could focus in on some of the &lt;a href=&quot;https://kubernetes.io&quot;&gt;Kubernetes&lt;/a&gt; specifics.&lt;/p&gt;

&lt;h2 id=&quot;installing-kubernetes-locally&quot;&gt;Installing Kubernetes Locally&lt;/h2&gt;

&lt;p&gt;Before going any further I’ll describe my local Kubernetes environment.  I’ve previously explored Kubernetes on Google Container Engine and found it effortless.  I’ve also used the community provided kube-up scripts to create a local cluster but it appears that this approach has been deprecated.  The recommended approach to creating a local Kubernetes environment is using &lt;a href=&quot;https://kubernetes.io/docs/getting-started-guides/minikube/&quot;&gt;MiniKube&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are using Mac, it is fairly trivial to install the components required for &lt;a href=&quot;https://kubernetes.io/docs/getting-started-guides/minikube/&quot;&gt;MiniKube&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;brew cask install virtualbox
brew install kubectl
brew cask install minikube&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;em&gt;Note: If you don’t have brew installed you can find the &lt;a href=&quot;http://brew.sh/&quot;&gt;brew installation instructions here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To start minikube simply invoke:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;minikube start&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;At this point there is a single node Kubernates environment running within a VirtualBox VM.  To validate the environment simply invoke:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;kubectl cluster-info&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This command will return URL’s for the Kubernetes Master, DNS service and Dashboard.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;Kubernetes master is running at https://192.168.99.100:8443
KubeDNS is running at https://192.168.99.100:8443/api/v1/proxy/namespaces/kube-system/services/kube-dns
kubernetes-dashboard is running at https://192.168.99.100:8443/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;!-- more --&gt;

&lt;h2 id=&quot;deploying-jenkins&quot;&gt;Deploying Jenkins&lt;/h2&gt;

&lt;p&gt;So now we have a single node Kubernetes environment running locally it’s time to define our Jenkins deployment.  A simple deployment is defined below:&lt;/p&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    jenkins-deployment.yaml
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;s&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;extensions/v1beta1&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deployment&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;replicas&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins:2.32.2&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;containerPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;8080&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Some points of note:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The file is defining a &lt;em&gt;Deployment&lt;/em&gt; as indicated by the &lt;em&gt;kind&lt;/em&gt; field.&lt;/li&gt;
  &lt;li&gt;The &lt;em&gt;Deployment&lt;/em&gt; specifyies a single replica.  This ensures one and only one instance will be maintained by the Replication Controller in the event of failure.&lt;/li&gt;
  &lt;li&gt;The container image name is &lt;em&gt;jenkins&lt;/em&gt; and version is &lt;em&gt;2.32.2&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;The list of ports specified within the &lt;em&gt;spec&lt;/em&gt; are a list of ports to expose from the container on the &lt;em&gt;Pods&lt;/em&gt; IP address.
    &lt;ul&gt;
      &lt;li&gt;Jenkins running on (http) port 8080.&lt;/li&gt;
      &lt;li&gt;The &lt;em&gt;Pod&lt;/em&gt; exposes the port 8080 of the &lt;em&gt;jenkins&lt;/em&gt; container.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To create the deployment execute:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;kubectl create -f jenkins-deployment.yaml&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To validate that creating the deployment was successful you can invoke:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;kubectl get deployments&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It’s worth noting that the Kubernetes command line is very consistent in its naming and syntax.  You can invoke &lt;em&gt;kubectl get RESOURCE&lt;/em&gt; for any Kubernetes resource.  If all has worked as expected you will be presented with output similar to that below:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
jenkins   1         1         1            1           3m&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We can also see that a single Pod has been created by invoking:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;kubectl get pods
NAME                       READY     STATUS    RESTARTS   AGE
jenkins-2843131955-31rh1   1/1       Running   0          7m&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;exposing-jenkins&quot;&gt;Exposing Jenkins&lt;/h2&gt;

&lt;p&gt;As it stands we have a Jenkins instance deployed but it is still not accessible.  The jenkins Pod has been assigned an IP address which is internal to the Kubernetes cluster.  Of course it’s possible to log into the Kubernetes Node and access Jenkins from there but that’s not a very useful way to access the service.&lt;/p&gt;

&lt;p&gt;To make Jenkins accessible outside the Kubernetes cluster the Pod needs to be exposed as a Service.  With a local deployment this means creating a &lt;em&gt;NodePort&lt;/em&gt; service type.  A &lt;em&gt;NodePort&lt;/em&gt; service type exposes a service on a port on each node in the cluster.  It’s then possible to access the service given the Node IP address and the service nodePort.  A simple service is defined below:&lt;/p&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    jenkins-service.yaml
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;s&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Service&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;NodePort&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;8080&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;targetPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;8080&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jenkins&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Some points of note:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The file is defining a &lt;em&gt;Service&lt;/em&gt; as indicated by the &lt;em&gt;kind&lt;/em&gt; field.&lt;/li&gt;
  &lt;li&gt;The &lt;em&gt;Service&lt;/em&gt; is of type &lt;em&gt;NodePort&lt;/em&gt;.  Other options are &lt;em&gt;ClusterIP&lt;/em&gt; (only accessible within the cluster) and &lt;em&gt;LoadBalancer&lt;/em&gt; (IP address assigned by a cloud provider e.g. AWS Elastic IP).&lt;/li&gt;
  &lt;li&gt;The list of ports specified within the &lt;em&gt;spec&lt;/em&gt; are a list of ports exposed by this service.
    &lt;ul&gt;
      &lt;li&gt;The port is the port that will be exposed by the service.&lt;/li&gt;
      &lt;li&gt;The target port is the port to access on the Pods targeted by this service.  A port name may also be specified.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;The selector specifies the selection criteria for the Pods targeted by this service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To create the service execute:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;kubectl create -f jenkins-service.yaml&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To validate that creating the service was successful you can invoke:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;kubectl get services
NAME         CLUSTER-IP   EXTERNAL-IP   PORT&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;S&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;          AGE
jenkins      10.0.0.202   &amp;lt;nodes&amp;gt;       8080:30104/TCP   3m
kubernetes   10.0.0.1     &amp;lt;none&amp;gt;        443/TCP          6h&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;So now we have created a deployment and service how do we access Jenkins?&lt;/p&gt;

&lt;p&gt;From the output above we can see that the service has been exposed on port 30104.  We also know that because the service is of type &lt;em&gt;NodeType&lt;/em&gt; the service will route requests made to any node on this port to the jenkins pod.  All that’s left for us is to determine the IP address of the minikube VM.  Minikube have made this really simple by including a specific command that outputs the IP address of the running cluster:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;minikube ip
192.168.99.100&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now we can access the jenkins instance at &lt;em&gt;http://192.168.99.100:30104/&lt;/em&gt;&lt;/p&gt;

&lt;figure class=&quot;figure&quot;&gt;
  &lt;a href=&quot;/assets/img/post/2017-02-07-deploying-jenkins-with-kubernetes/jenkins-getting-started.png&quot; data-lightbox=&quot;jenkins-unlock-screen&quot; data-title=&quot;Jenkins Unlock Screen&quot;&gt;
    &lt;img class=&quot;img-fluid img-thumbnail mx-auto&quot; src=&quot;/assets/img/post/2017-02-07-deploying-jenkins-with-kubernetes/jenkins-getting-started.png&quot; alt=&quot;Jenkins Unlock Screen&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption class=&quot;figure-caption text-center&quot;&gt;Jenkins Unlock Screen&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;So that is a bit of a world wind tour of deploying Jenkins on Kubernetes.  I plan on developing this use case in future posts so as to explore Kubernetes further so stay tuned if you are finding this useful.  On the other hand, if you are experienced with Kubernetes and spot any rookie mistakes I’d appreciate you drawing my attention to them using the comment section below.&lt;/p&gt;</content><author><name>john_turner</name></author><category term="Kubernetes" /><category term="Docker" /><category term="Jenkins" /><summary type="html">Quick on the heels of my recent overview of the Kubernetes Fundamentals I wanted to dive into deploying something familiar (Jenkins) so that I could focus in on some of the Kubernetes specifics. Installing Kubernetes Locally Before going any further I’ll describe my local Kubernetes environment. I’ve previously explored Kubernetes on Google Container Engine and found it effortless. I’ve also used the community provided kube-up scripts to create a local cluster but it appears that this approach has been deprecated. The recommended approach to creating a local Kubernetes environment is using MiniKube. If you are using Mac, it is fairly trivial to install the components required for MiniKube: brew cask install virtualbox brew install kubectl brew cask install minikube Note: If you don’t have brew installed you can find the brew installation instructions here. To start minikube simply invoke: minikube start At this point there is a single node Kubernates environment running within a VirtualBox VM. To validate the environment simply invoke: kubectl cluster-info This command will return URL’s for the Kubernetes Master, DNS service and Dashboard. Kubernetes master is running at https://192.168.99.100:8443 KubeDNS is running at https://192.168.99.100:8443/api/v1/proxy/namespaces/kube-system/services/kube-dns kubernetes-dashboard is running at https://192.168.99.100:8443/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard</summary></entry><entry><title type="html">Kubernetes Fundamentals</title><link href="http://www.monkeylittle.com/blog/2017/02/06/kubernetes-fundamentals.html" rel="alternate" type="text/html" title="Kubernetes Fundamentals" /><published>2017-02-06T00:00:00+00:00</published><updated>2017-02-06T00:00:00+00:00</updated><id>http://www.monkeylittle.com/blog/2017/02/06/kubernetes-fundamentals</id><content type="html" xml:base="http://www.monkeylittle.com/blog/2017/02/06/kubernetes-fundamentals.html">&lt;p&gt;Over the last few years, I’ve been watching with interest the container space in general and specifically the level of interest being generated by the various orchestration options available.  I still remain quite sceptical about the ease with which most organizations could effectively adopt containers (and the resulting changes to development and operational practices).  However, I do believe that systems like Mesos and Kubernetes will eventually become default deployment environments for all but a few.  With that in mind, I’ve decided to dive into Kubernetes to get a better understanding of the opportunities and challenges ahead.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/post/2017-02-06-kubernetes-fundamentals/kubernetes-logo.png&quot; class=&quot;img-fluid mx-5 pull-left&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://kubernetes.io&quot;&gt;Kubernetes&lt;/a&gt; is a system that has emerged from Google for automating deployment, scaling and management of containerized applications.  It is one of a number of orchestration tools whose emergence has coincided with the increasing popularity and maturity of container runtimes such as &lt;a href=&quot;https://www.docker.com/products/docker&quot;&gt;Docker&lt;/a&gt; and &lt;a href=&quot;https://coreos.com/rkt/&quot;&gt;rkt&lt;/a&gt;.  Other orchestration tools include &lt;a href=&quot;https://www.docker.com/products/docker-swarm&quot;&gt;Docker Swarm&lt;/a&gt;, &lt;a href=&quot;https://coreos.com/fleet/&quot;&gt;Fleet&lt;/a&gt; and &lt;a href=&quot;http://mesos.apache.org/&quot;&gt;Mesos&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Each of the previously mentioned orchestration tools incorporate a number of concepts, some of which are very similar and others which are quite distinctive.  This can make it quite challenging when you come to learn about one or other (and even more so when you come to compare them).  In this post I will briefly describe some of the key concepts you should understand when starting out with &lt;a href=&quot;https://kubernetes.io&quot;&gt;Kubernetes&lt;/a&gt;.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;h2 id=&quot;kubernetes-cluster&quot;&gt;Kubernetes Cluster&lt;/h2&gt;

&lt;figure class=&quot;figure mx-auto w-25 pull-right&quot;&gt;
  &lt;a href=&quot;/assets/img/post/2017-02-06-kubernetes-fundamentals/kubernetes-cluster.png&quot; data-lightbox=&quot;kubernetes-cluster&quot; data-title=&quot;Kubernetes Cluster&quot;&gt;
    &lt;img class=&quot;img-fluid&quot; src=&quot;/assets/img/post/2017-02-06-kubernetes-fundamentals/kubernetes-cluster.png&quot; alt=&quot;Kubernetes Cluster&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption class=&quot;figure-caption text-center&quot;&gt;Kubernetes Cluster&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Kubernetes is a distributed system which consists of a number of connected virtual or physical machines.  At a minimum a Kubernetes cluster consists of two machines; one Master and one Node.  As of v1.5.1, Kubernetes state support for clusters of up to 2000 nodes with the following stipulations:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;No more than 2000 nodes&lt;/li&gt;
  &lt;li&gt;No more than 60,000 total pods&lt;/li&gt;
  &lt;li&gt;No more than 120,000 total containers&lt;/li&gt;
  &lt;li&gt;No more than 100 pods per node&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kubernetes environments can of course grow beyond this scale and that is achieved through cluster federation.  Federation facilitates:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Keeping resources in multiple clusters in sync.&lt;/li&gt;
  &lt;li&gt;Exposing DNS and loadbalancer information across clusters.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;kubernetes-master&quot;&gt;Kubernetes Master&lt;/h3&gt;

&lt;p&gt;The Master is responsible for coordinating all activities in the cluster.  This includes deploying applications, maintaining desired state (e.g. in the event of failure), scaling applications and rolling out updates.  Cluster administration is performed by invoking an API exposed by the Master either directly (if you are building tools), via the Kubernetes command line interface or the Kubernetes dashboard.  The Nodes within the cluster also interact with the Master via the same API.&lt;/p&gt;

&lt;p&gt;In a non-critical environment there may be a single master node but in environments that need to be resilient to failure multiple master nodes are required.  Within the Master node &lt;a href=&quot;https://coreos.com/etcd/&quot;&gt;etcd&lt;/a&gt; is used for data storage so when using multiple masters it is necessary to cluster etcd so that data is replicated across the Master nodes.  It is also necessary to place a loadbalancer in front of the API server running on each Master node.&lt;/p&gt;

&lt;p&gt;The Kubernetes Master contains a number of components for which there may only be a single instance active within the cluster.  These are the &lt;em&gt;scheduler&lt;/em&gt; and the &lt;em&gt;controller-manager&lt;/em&gt;.  For these components a leader election process occurs to ensure a single instance running at any one time.  In the event of failure a new leader will be elected from the available nodes.&lt;/p&gt;

&lt;h3 id=&quot;kubernetes-node&quot;&gt;Kubernetes Node&lt;/h3&gt;

&lt;figure class=&quot;figure mx-auto w-25 pull-right&quot;&gt;
  &lt;a href=&quot;/assets/img/post/2017-02-06-kubernetes-fundamentals/kubernetes-node.png&quot; data-lightbox=&quot;kubernetes-node&quot; data-title=&quot;Kubernetes Node&quot;&gt;
    &lt;img class=&quot;img-fluid&quot; src=&quot;/assets/img/post/2017-02-06-kubernetes-fundamentals/kubernetes-node.png&quot; alt=&quot;Kubernetes Node&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption class=&quot;figure-caption text-center&quot;&gt;Kubernetes Node&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The Kubernetes Node is the workhorse of the cluster.  A Node is managed by the Master and can be either a virtual or physical machine.  Within each node exists an operating system, a container runtime (&lt;a href=&quot;https://www.docker.com/products/docker&quot;&gt;Docker&lt;/a&gt; is supported with experimental support for &lt;a href=&quot;https://coreos.com/rkt/&quot;&gt;rkt&lt;/a&gt;), a proxy server (kube-proxy) and a node agent (kubelet).&lt;/p&gt;

&lt;p&gt;The kubelet executes instructions issued by the master node and provides periodic updates of node and pod health.  Those instructions include locally provisioning pod resources such as containers, volumes etc.  The kubelet also enables log retrieval.&lt;/p&gt;

&lt;p&gt;The kube-proxy is a simple network proxy and loadbalancer for services on the Node.&lt;/p&gt;

&lt;p&gt;The Node runs a process supervisor service that restarts the container runtime or kubelet in the case of failure.&lt;/p&gt;

&lt;h2 id=&quot;deployment&quot;&gt;Deployment&lt;/h2&gt;

&lt;figure class=&quot;figure mx-auto w-25 pull-right&quot;&gt;
  &lt;a href=&quot;/assets/img/post/2017-02-06-kubernetes-fundamentals/kubernetes-deployment.png&quot; data-lightbox=&quot;kubernetes-pod&quot; data-title=&quot;Kubernetes Deployment&quot;&gt;
    &lt;img class=&quot;img-fluid&quot; src=&quot;/assets/img/post/2017-02-06-kubernetes-fundamentals/kubernetes-deployment.png&quot; alt=&quot;Kubernetes Deployment&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption class=&quot;figure-caption text-center&quot;&gt;Kubernetes Deployment&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;A Deployment is a logical component that describes the desired state of Pods and Replica Sets.  Use of a Deployment allows Kubernetes to perform richer orchestration as opposed to fine grained transactional actions.  Examples of this include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Scaling a deployment either explicitly or based on CPU utilization of existing Pods.&lt;/li&gt;
  &lt;li&gt;Performing rolling updates.&lt;/li&gt;
  &lt;li&gt;Rolling back an update to a previous version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given that a deployment specifies the desired state, all the detail relating to how Kubernetes converges the deployment is abstracted.  This makes using deployments easier to learn than the alternative of explicitly writing instructions to achieve the desired state.  It also means that Kubernetes can reason about the current state and act accordingly.&lt;/p&gt;

&lt;h2 id=&quot;pod&quot;&gt;Pod&lt;/h2&gt;

&lt;figure class=&quot;figure mx-auto w-25 pull-right&quot;&gt;
  &lt;a href=&quot;/assets/img/post/2017-02-06-kubernetes-fundamentals/kubernetes-pod.png&quot; data-lightbox=&quot;kubernetes-pod&quot; data-title=&quot;Kubernetes Pod&quot;&gt;
    &lt;img class=&quot;img-fluid&quot; src=&quot;/assets/img/post/2017-02-06-kubernetes-fundamentals/kubernetes-pod.png&quot; alt=&quot;Kubernetes Pod&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption class=&quot;figure-caption text-center&quot;&gt;Kubernetes Pod&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;A Pod exists within a single Node and can consist of a number of application containers with associated volumes.  Each Pod is allocated a dedicated IP address upon creation.  Containers and Volumes cannot be created individually and must be created within a Pod.&lt;/p&gt;

&lt;p&gt;Pods have no orchestrated behavior and so if you chose to deploy Pods outside a Deployment or Replication Controller you will not have any auto-recovery, auto-scaling, update or rollback behaviors.&lt;/p&gt;

&lt;h2 id=&quot;services&quot;&gt;Services&lt;/h2&gt;

&lt;p&gt;A Service provides a stable name and address for a set of Pods.  Each Service gets it’s own virtual IP address which remains constant across Pod failure and recovery (Pods themselves will be assigned a new IP address).&lt;/p&gt;

&lt;p&gt;A Service is associated with Pods via a selector which matches against labels assigned to the Pods during creation.  Traffic is then routed from the Service to the Pods.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Now I’ve a high level understanding of the key Kubernetes concepts I’m in a better place to dive into some specifics.&lt;/em&gt;&lt;/p&gt;</content><author><name>john_turner</name></author><category term="Kubernetes" /><category term="Docker" /><summary type="html">Over the last few years, I’ve been watching with interest the container space in general and specifically the level of interest being generated by the various orchestration options available. I still remain quite sceptical about the ease with which most organizations could effectively adopt containers (and the resulting changes to development and operational practices). However, I do believe that systems like Mesos and Kubernetes will eventually become default deployment environments for all but a few. With that in mind, I’ve decided to dive into Kubernetes to get a better understanding of the opportunities and challenges ahead. Kubernetes is a system that has emerged from Google for automating deployment, scaling and management of containerized applications. It is one of a number of orchestration tools whose emergence has coincided with the increasing popularity and maturity of container runtimes such as Docker and rkt. Other orchestration tools include Docker Swarm, Fleet and Mesos. Each of the previously mentioned orchestration tools incorporate a number of concepts, some of which are very similar and others which are quite distinctive. This can make it quite challenging when you come to learn about one or other (and even more so when you come to compare them). In this post I will briefly describe some of the key concepts you should understand when starting out with Kubernetes.</summary></entry><entry><title type="html">JPA Inheritance Strategies Explained</title><link href="http://www.monkeylittle.com/blog/2015/04/13/jpa-inheritance-strategies-explained.html" rel="alternate" type="text/html" title="JPA Inheritance Strategies Explained" /><published>2015-04-13T00:00:00+00:00</published><updated>2015-04-13T00:00:00+00:00</updated><id>http://www.monkeylittle.com/blog/2015/04/13/jpa-inheritance-strategies-explained</id><content type="html" xml:base="http://www.monkeylittle.com/blog/2015/04/13/jpa-inheritance-strategies-explained.html">&lt;p&gt;Last week, I was speaking to an intern who worked for me while I was at Paddy Power.  He was explaining with some frustration that he had recently had the supervisor for his final year project reassigned.  The guidance from the new supervisor was that the project needed some “wow”.  I was not sure if this meant he needed to present the project while flanked by the Dallas Coyboys Cheerleaders so I asked for some specifics.  This was the rather perplexing response.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/post/2015-04-12-jpa-inheritance-strategies-explained/jpa-inheritance-tweet.png&quot; class=&quot;img-fluid img-thumbnail&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I know Ian is using JPA and I would like to give him a little more information than he received from the above suggestion. So here is my effort at explaining the JPA inheritance strategies.&lt;/p&gt;

&lt;h4 id=&quot;jpa-inheritance-strategies&quot;&gt;JPA Inheritance Strategies&lt;/h4&gt;

&lt;p&gt;The JPA inheritance strategies facilitate mapping of an inheritance hierarchy in 3 different ways. To demonstrate the advantages and disadvantages of each, I will use a class hierarchy comprising a Vehicle, Airplane, Bike and Car.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/post/2015-04-12-jpa-inheritance-strategies-explained/jpa-inheritance-class-diagram.png&quot; class=&quot;img-fluid img-thumbnail&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So you can follow or experiment with the code I’ve made it available on &lt;a href=&quot;https://github.com/monkeylittle/spring-jpa-inheritance&quot;&gt;GitHub&lt;/a&gt;.  Note that there are separate repositories for each class, that the Vehicle class is abstract and that each repository has a corresponding test class.  I’m using Spring Boot to automatically discover the repositories, entity mappings as well as provide an in memory database for testing purposes.&lt;/p&gt;

&lt;p&gt;I’ve enabled the Hibernate SQL logging so that we can see the table DDL as well as the structure of a findAll query executed via each of the repositories.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;h4 id=&quot;inheritancetypejoined&quot;&gt;InheritanceType.JOINED&lt;/h4&gt;

&lt;p&gt;The JOINED inheritance strategy specifies a table for each class within the hierarchy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table Structure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Given the Vehicle hierarchy, there will be 4 tables created as demonstrated by the log output below:&lt;/p&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    Create Tables
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot; data-lang=&quot;plaintext&quot;&gt;[main] DEBUG org.hibernate.SQL - create table airplane (id bigint not null, primary key (id))
[main] DEBUG org.hibernate.SQL - create table bike (id bigint not null, primary key (id))
[main] DEBUG org.hibernate.SQL - create table car (id bigint not null, primary key (id))
[main] DEBUG org.hibernate.SQL - create table vehicle (id bigint not null, colour integer, primary key (id))&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;You will also notice that in this simple example, the properties of each class file correlate directly to columns on each table with the id duplicated across all 4 tables.  The id column on the airplane, bike and car tables act as a foreign key while the id on the vehicle table is the primary key.&lt;/p&gt;

&lt;p&gt;From this we can deduce all the advantages and disadvantages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Changing Class Definitions is Easier&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because there is a direct correlation of class + properties to table + columns there is no duplication of the vehicle definition.  As a result a single change for a class or property definition will result in a single change for a table or column definition.  For example, if I add an ‘isMortorised’ property to vehicle class it will result in the addition of a ‘motorised’ column on the vehicle table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data Integrity at the DBMS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Another side effect of the direct mapping between class and table is that I can manage data integrity at the database layer.  For example, column definitions including null constraints, foreign key constraints etc. can be managed by the DBMS (without resorting to stored procedures).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creating an Airplane, Bike or Car is (more) Expensive&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To save an Airplane requires an insert into the vehicle and airplane tables.  It will also require a sequence number to be generated for the vehicle and a foreign key constraint validation to occur on the airplane table.  We can see this from the log statement below:&lt;/p&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    Insert Airplane
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot; data-lang=&quot;plaintext&quot;&gt;[main] DEBUG org.hibernate.SQL - insert into vehicle (colour, id) values (?, ?)
[main] DEBUG org.hibernate.SQL - insert into airplane (id) values (?)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Retrieving an Airplane, Bike or Car is (more) Expensive&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To retrieve an Airplane (or Airplanes) requires a join between vehicle and airplane.&lt;/p&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    Retrieve Airplane
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot; data-lang=&quot;plaintext&quot;&gt;[main] DEBUG org.hibernate.SQL - select airplane0_.id as id1_3_, airplane0_1_.colour as colour2_3_ from airplane airplane0_ inner join vehicle airplane0_1_ on airplane0_.id=airplane0_1_.id&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Retrieving a Vehicle is (much more) Expensive&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To retrieve a Vehicle (or Vehicles) requires a (left outer) join between vehicle, airplane, bike and car.&lt;/p&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    Retrieve Vehicle
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot; data-lang=&quot;plaintext&quot;&gt;[main] DEBUG org.hibernate.SQL - select vehicle0_.id as id1_3_, vehicle0_.colour as colour2_3_, case when vehicle0_1_.id is not null then 1 when vehicle0_2_.id is not null then 2 when vehicle0_3_.id is not null then 3 when vehicle0_.id is not null then 0 end as clazz_ from vehicle vehicle0_ left outer join airplane vehicle0_1_ on vehicle0_.id=vehicle0_1_.id left outer join car vehicle0_2_ on vehicle0_.id=vehicle0_2_.id left outer join bike vehicle0_3_ on vehicle0_.id=vehicle0_3_.id&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;h4 id=&quot;inheritancetypesingle_table&quot;&gt;InheritanceType.SINGLE_TABLE&lt;/h4&gt;

&lt;p&gt;As the name suggests, the SINGLE_TABLE inheritance strategy specifies a table for the entire class hierarchy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table Structure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Given the Vehicle hierarchy, there will be 1 table created as demonstrated by the log output below:&lt;/p&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    Create Tables
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot; data-lang=&quot;plaintext&quot;&gt;[main] DEBUG org.hibernate.SQL - create table vehicle (dtype varchar(31) not null, id bigint not null, colour integer, primary key (id))&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;You’ll notice that the first column definition does not correspond to a property from any of the class files.  This is a discriminator column that allows JPA to understand the type of entity to create when it retrieves a row from this table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Changing Class Definitions is Harder&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When using the JOINED strategy I only impacted the table corresponding to the class I was modifying.  With the SINGLE_TABLE strategy I am changing table that all vehicles are stored in.  This may or may not be  a big deal but is worth considering especially if you are storing lots of data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data Integrity at the DBMS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Column definitions including null constraints, foreign key constraints etc. can no longer be managed by the DBMS (without resorting to stored procedures).  This can be a problem especially when a database can be access by different applications using different access layers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data Storage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because a single table is stores all vehicles there will necessarily be a lot of null values in columns that do not relate to the specific type.  This may or may not be a problem depending on the size of the tables and how efficient the DBMS is at storing null values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creating an Airplane, Bike or Car is Cheap&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because the SINGLE_TABLE strategy uses a single table, creating an Airplane, Bike or Car is a single insert executed against a single (albeit larger) table.&lt;/p&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    Insert Vehicles
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot; data-lang=&quot;plaintext&quot;&gt;[main] DEBUG org.hibernate.SQL - insert into vehicle (colour, dtype, id) values (?, 'Bike', ?)
[main] DEBUG org.hibernate.SQL - insert into vehicle (colour, dtype, id) values (?, 'Car', ?)
[main] DEBUG org.hibernate.SQL - insert into vehicle (colour, dtype, id) values (?, 'Airplane', ?)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Note that the discriminator value defaults to the class name.  Be careful that whatever discriminator you use is treated efficiently by the DBMS (in terms of both storage and query efficiency.  The discriminator can be changed using the JPA DiscriminatorColumn annotation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieving a Vehicle, Airplane, Bike or Car is Cheap&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Again, because of the use of a single table retrieving a Vehicle, Airplane, Bike or Car (or many of same) is cheap.  It is a single select statement which filters using the discriminator value if a subclass is being queried.  This is the cheapest strategy for executing polymorphic queries (i.e. retrieving Vehicles).&lt;/p&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    Retrieve Vehicles
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot; data-lang=&quot;plaintext&quot;&gt;[main] DEBUG org.hibernate.SQL - select car0_.id as id2_0_, car0_.colour as colour3_0_ from vehicle car0_ where car0_.dtype='Car'
[main] DEBUG org.hibernate.SQL - select bike0_.id as id2_0_, bike0_.colour as colour3_0_ from vehicle bike0_ where bike0_.dtype='Bike'
[main] DEBUG org.hibernate.SQL - select airplane0_.id as id2_0_, airplane0_.colour as colour3_0_ from vehicle airplane0_ where airplane0_.dtype='Airplane'
[main] DEBUG org.hibernate.SQL - select vehicle0_.id as id2_0_, vehicle0_.colour as colour3_0_, vehicle0_.dtype as dtype1_0_ from vehicle vehicle0_&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;h4 id=&quot;inheritancetypetable_per_class&quot;&gt;InheritanceType.TABLE_PER_CLASS&lt;/h4&gt;

&lt;p&gt;The TABLE_PER_CLASS strategy is really a table per concrete class strategy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table Structure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Given the Vehicle hierarchy, there will be 3 tables created as demonstrated by the log output below:&lt;/p&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    Create Tables
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot; data-lang=&quot;plaintext&quot;&gt;[main] DEBUG org.hibernate.SQL - create table airplane (id bigint not null, colour integer, primary key (id))
[main] DEBUG org.hibernate.SQL - create table bike (id bigint not null, colour integer, primary key (id))
[main] DEBUG org.hibernate.SQL - create table car (id bigint not null, colour integer, primary key (id))&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The inherited colour property is defined in the definitions of the airplane, bike and car tables.  There is no vehicle table as Vehicle is an abstract class.  There is also no discriminator as data from each vehicle is stored in its own table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Changing Class Definitions is Easier&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Given that each concrete class has its own corresponding table definition, it is easier to modify these.  However, if I modify the Vehicle definition I have to make corresponding modifications to the airplane, bike and car tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data Integrity at the DBMS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because each concrete class is stored in its own table the DBMS can manage null constraints, foreign key constraints etc. without resorting to stored procedures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creating an Airplane, Bike or Car is Cheap&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because the TABLE_PER_CLASS strategy uses a single table for each concrete class, creating an Airplane, Bike or Car is a single insert executed against a single table.&lt;/p&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    Create Vehicles
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot; data-lang=&quot;plaintext&quot;&gt;[main] DEBUG org.hibernate.SQL - insert into bike (colour, id) values (?, ?)
[main] DEBUG org.hibernate.SQL - insert into car (colour, id) values (?, ?)
[main] DEBUG org.hibernate.SQL - insert into airplane (colour, id) values (?, ?)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;There is no discriminator value in this case!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieving an Airplane, Bike or Car is Cheap&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Again, because of the use of a single table retrieving an Airplane, Bike or Car (or many of same) is cheap.  It is a single select statement.&lt;/p&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;
    Retrieve Airplane, Bike, Car
  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot; data-lang=&quot;plaintext&quot;&gt;[main] DEBUG org.hibernate.SQL - select car0_.id as id1_3_, car0_.colour as colour2_3_ from car car0_
[main] DEBUG org.hibernate.SQL - select bike0_.id as id1_3_, bike0_.colour as colour2_3_ from bike bike0_
[main] DEBUG org.hibernate.SQL - select airplane0_.id as id1_3_, airplane0_.colour as colour2_3_ from airplane airplane0_&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Retrieving a Vehicle is Expensive&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The biggest disadvantage of this strategy is that polymorphic queries are expensive, requiring a union of 3 data sets (from each of the tables).&lt;/p&gt;

&lt;div class=&quot;card mb-3&quot;&gt;
  &lt;div class=&quot;card-header&quot;&gt;

  &lt;/div&gt;
  &lt;div class=&quot;card-block&quot;&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot; data-lang=&quot;plaintext&quot;&gt;[main] DEBUG org.hibernate.SQL - select vehicle0_.id as id1_3_, vehicle0_.colour as colour2_3_, vehicle0_.clazz_ as clazz_ from ( select id, colour, 1 as clazz_ from airplane union all select id, colour, 2 as clazz_ from car union all select id, colour, 3 as clazz_ from bike ) vehicle0_&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;h4 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h4&gt;

&lt;p&gt;So there we have it, some of the considerations to be taken into account when selecting the JPA inheritance strategy.  A lot of these will depend on the particulars of the DBMS you are using but for the most part this will provide a useful guide.&lt;/p&gt;</content><author><name>john_turner</name></author><summary type="html">Last week, I was speaking to an intern who worked for me while I was at Paddy Power. He was explaining with some frustration that he had recently had the supervisor for his final year project reassigned. The guidance from the new supervisor was that the project needed some “wow”. I was not sure if this meant he needed to present the project while flanked by the Dallas Coyboys Cheerleaders so I asked for some specifics. This was the rather perplexing response. I know Ian is using JPA and I would like to give him a little more information than he received from the above suggestion. So here is my effort at explaining the JPA inheritance strategies. JPA Inheritance Strategies The JPA inheritance strategies facilitate mapping of an inheritance hierarchy in 3 different ways. To demonstrate the advantages and disadvantages of each, I will use a class hierarchy comprising a Vehicle, Airplane, Bike and Car. So you can follow or experiment with the code I’ve made it available on GitHub. Note that there are separate repositories for each class, that the Vehicle class is abstract and that each repository has a corresponding test class. I’m using Spring Boot to automatically discover the repositories, entity mappings as well as provide an in memory database for testing purposes. I’ve enabled the Hibernate SQL logging so that we can see the table DDL as well as the structure of a findAll query executed via each of the repositories.</summary></entry><entry><title type="html">Application Performance Monitoring - Back to the Future</title><link href="http://www.monkeylittle.com/blog/2014/03/26/application-performance-monitoring-back-future.html" rel="alternate" type="text/html" title="Application Performance Monitoring - Back to the Future" /><published>2014-03-26T00:00:00+00:00</published><updated>2014-03-26T00:00:00+00:00</updated><id>http://www.monkeylittle.com/blog/2014/03/26/application-performance-monitoring-back-future</id><content type="html" xml:base="http://www.monkeylittle.com/blog/2014/03/26/application-performance-monitoring-back-future.html">&lt;p&gt;In my previous post &lt;a href=&quot;/blog/2014/03/16/application-monitoring-past-present-future.html&quot;&gt;Application Monitoring - Past, Present and Future&lt;/a&gt; I touched on my desire to see Application Performance Monitoring (APM) vendors add log aggregation and analytics to their service offerings. I’d like to offer further suggestions into what I would like to see in future APM offerings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Application Insight&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The design and function of APM solutions is relatively simple and straightforward to implement.  Typically, there exists some form of “Agent” running on the node under observation that relays data to a “Collector”.  The “Collector” then stores the data on some form of storage solution.  The data is then inspected and presented to a number of services including those that provide functions such as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Administration&lt;/li&gt;
  &lt;li&gt;Alerting&lt;/li&gt;
  &lt;li&gt;Analytics&lt;/li&gt;
  &lt;li&gt;Presentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s not consider the actual data that is being collected other than say it should include all data relevant to application health.  Notwithstanding the omission of log data stated earlier, APM solutions from companies such as &lt;a href=&quot;http://www.appdynamics.com&quot;&gt;AppDynamics&lt;/a&gt; and &lt;a href=&quot;http://newrelic.com&quot;&gt;New Relic&lt;/a&gt; already do a good job of collecting the relevant data at a reasonably low cost (operational overhead, configuration etc.).  New Relic has also recently announced the addition of a platform, Rubicon, which allows one to perform in depth analytics on all the data collected (though currently New Relic do not collect distributed tracing data.&lt;/p&gt;

&lt;p&gt;I refer to this capability as “Application Insight” and while this space will continue to evolve it does not exactly provide the next big step forward for APM vendors.&lt;/p&gt;

&lt;p&gt;The leaders in this space are considered to be AppDynamics and New Relic and they both provide APM.  Interesting, they both define APM as application performance management while I would suggest that neither actually perform any management.  They stop short at application performance monitoring.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;&lt;strong&gt;Application Intelligence&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When I consider what I want from an application performance management solution I most certainly want some level of autonomic behaviour.  IBM coined the phrase “Autonomic Computing” way back in 2001 (they did bugger all with the research worth talking about but hey, that’s IBM for you!) and they defined autonomic computing as having four functional areas:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Self-configuration: Automatic configuration of components.&lt;/li&gt;
  &lt;li&gt;Self-healing: Automatic discovery and correction of faults.&lt;/li&gt;
  &lt;li&gt;Self-optimisation: Automatic monitoring and control of resources to ensure the optimal functioning with respect to the defined requirements.&lt;/li&gt;
  &lt;li&gt;Self-protection: Proactive identification and protection from arbitrary attacks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The thrust of the concept is that systems should apply self-* principles to make intelligent runtime adaptations to the system behaviour.  I call this “Application Intelligence”&lt;/p&gt;

&lt;p&gt;Colonel John Boyd was a United States Air Force fighter pilot and consultant to the Pentagon.  Boyd hypothesized that all intelligent organisms and organizations undergo a continuous cycle of interaction with their environment.  Boyd breaks this cycle down to four interrelated and overlapping processes through which one cycles continuously:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Observation: the collection of data by means of the senses&lt;/li&gt;
  &lt;li&gt;Orientation: the analysis and synthesis of data to form one’s current mental perspective&lt;/li&gt;
  &lt;li&gt;Decision: the determination of a course of action based on one’s current mental perspective&lt;/li&gt;
  &lt;li&gt;Action: the physical playing-out of decisions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;“Application Insight” gives us the capability to apply the Observe, Orientate and Decision process while “Application Intelligence” gives us the capability to apply the Action process.  In the context of todays APM solutions, the action process is applied via the same mechanism through which application metrics are collected.  That is, the same runtime code instrumentation that collects application metrics at the “edge of the node” can be used to modify the behaviour.  I’ll explain this by way of a couple of examples.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Timeout&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lets consider a very simple request from a client to a service.  This request represents a client invoking a RESTful service and awaiting a response.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/post/2014-03-26-application-performance-monitoring-back-future/synchronous-client-to-service-request.png&quot; class=&quot;img-fluid img-thumbnail&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It is well understood that in order to prevent the client continuing to wait for a response when the service is not responding in a timely fashion, the client should implement a request timeout.  The request timeout is typically set based on some expectation of service or prior knowledge of service behaviour.  The process of deciding what an appropriate timeout should be can actually be quite difficult.  Indeed, it may also be appropriate that the timeout change over time.&lt;/p&gt;

&lt;p&gt;Now lets consider applying APM and the OODA loop to the problem of deciding an appropriate timeout for the service call.  We already know that APM solutions gather metrics on service response times and error rates.  This gives us the information to allow us to apply some basic heuristics to determine an appropriate timeout for the service call.  The heuristics could be the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Set the timeout to some arbitrarily high value.&lt;/li&gt;
  &lt;li&gt;Observation: Collect metrics on service response time and error rates.&lt;/li&gt;
  &lt;li&gt;Orientation: Analyze the data to determine the optimal timeout.&lt;/li&gt;
  &lt;li&gt;Decision: If the dataset is statistically significant, adjust timeout.&lt;/li&gt;
  &lt;li&gt;Action: Adjust the timeout.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This provides a degree of self-optimization and therefore active management of the application performance.&lt;/p&gt;

&lt;p&gt;Lets continue with a second example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Circuit Breaker&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lets consider again the simple scenario in Figure 1 and apply the concept of a &lt;a href=&quot;http://martinfowler.com/bliki/CircuitBreaker.html&quot;&gt;Circult Breaker&lt;/a&gt; to service requests.  During normal operations the circuit is considered closed and requests are forwarded to the service.  In the event of a number of service failures, the circuit breaker trips and requests are no longer forwarded to the service.  Instead, the circuit breaker automatically returns an error response.  This has the effect of reducing the load on the service allowing some period of time for the service to recover (perhaps through auto-scaling).  After some period of time the circuit breaker is restored and requests are once again forwarded to the service.&lt;/p&gt;

&lt;p&gt;It comes as no surprise the applying APM and the OODA loop to the problem we can implement the Circuit Breaker.  The heuristics could be the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Set the failure count to zero.&lt;/li&gt;
  &lt;li&gt;Observation: Record failure count within a contextual (temporal and request count) window.&lt;/li&gt;
  &lt;li&gt;Orientation: Analyze the data to determine the significance of the failure count given throughput.&lt;/li&gt;
  &lt;li&gt;Decision: If the dataset is statistically significant, trip circuit breaker.&lt;/li&gt;
  &lt;li&gt;Action: Trip circuit breaker.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This again provides a degree of self-optimization and therefore active management of the application performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Handshake&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Handshaking allows the service to throttle requests in order to protect itself.  Based on some measure of load, the service automatically rejects requests with a response such as a 503 service unavailable if the load is beyond what the service can cater for.  When load returns to normal, the service starts to accept requests once more.  The heuristics could be the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Observation: Record load as some function of request count, latency, resource utilization and downstream service performance.&lt;/li&gt;
  &lt;li&gt;Orientation: Analyze the data to determine service health.&lt;/li&gt;
  &lt;li&gt;Decision: If health is deteriorating, reject incoming requests.&lt;/li&gt;
  &lt;li&gt;Action: Reject incoming requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Wrapping Up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So this stuff is not exactly novel.  Michael Nygard has documented the above patterns in his book, Release It!.  Netflix have implemented these patterns in their Hystrix library that they have made open source.&lt;/p&gt;

&lt;p&gt;My take away point is that APM solutions exist where the “M” stands for monitoring.  I would like an APM solution where the “M” stands for management.&lt;/p&gt;</content><author><name>john_turner</name></author><category term="DevOps" /><category term="APM" /><summary type="html">In my previous post Application Monitoring - Past, Present and Future I touched on my desire to see Application Performance Monitoring (APM) vendors add log aggregation and analytics to their service offerings. I’d like to offer further suggestions into what I would like to see in future APM offerings. Application Insight The design and function of APM solutions is relatively simple and straightforward to implement. Typically, there exists some form of “Agent” running on the node under observation that relays data to a “Collector”. The “Collector” then stores the data on some form of storage solution. The data is then inspected and presented to a number of services including those that provide functions such as: Administration Alerting Analytics Presentation Let’s not consider the actual data that is being collected other than say it should include all data relevant to application health. Notwithstanding the omission of log data stated earlier, APM solutions from companies such as AppDynamics and New Relic already do a good job of collecting the relevant data at a reasonably low cost (operational overhead, configuration etc.). New Relic has also recently announced the addition of a platform, Rubicon, which allows one to perform in depth analytics on all the data collected (though currently New Relic do not collect distributed tracing data. I refer to this capability as “Application Insight” and while this space will continue to evolve it does not exactly provide the next big step forward for APM vendors. The leaders in this space are considered to be AppDynamics and New Relic and they both provide APM. Interesting, they both define APM as application performance management while I would suggest that neither actually perform any management. They stop short at application performance monitoring.</summary></entry><entry><title type="html">Application Monitoring - Past, Present and Future</title><link href="http://www.monkeylittle.com/blog/2014/03/16/application-monitoring-past-present-future.html" rel="alternate" type="text/html" title="Application Monitoring - Past, Present and Future" /><published>2014-03-16T00:00:00+00:00</published><updated>2014-03-16T00:00:00+00:00</updated><id>http://www.monkeylittle.com/blog/2014/03/16/application-monitoring-past-present-future</id><content type="html" xml:base="http://www.monkeylittle.com/blog/2014/03/16/application-monitoring-past-present-future.html">&lt;p&gt;In addition to working on PaaS and Continuous Delivery initiatives during 2013, I also had the opportunity to learn a lot about Application Performance Monitoring (APM) tools.  Because I see PaaS and CD as enabling technologies for DevOps it was natural that I also look at shared tooling such as APM and log aggregation.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/post/2014-03-16-application-monitoring-past-present-future/past-present-and-future.jpg&quot; class=&quot;img-fluid img-thumbnail&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Application Monitoring in the Past&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the past, infrastructure monitoring was used as a proxy for the health of an application.  Application health was measured in terms of CPU and memory utilisation, disk and network IO or some other infrastructure metric.  Some form of translation between the behaviour of the infrastructure and the user experience took place to allow the operations team to understand when the user experience may be adversely affected by symptoms within the infrastructure.&lt;/p&gt;

&lt;p&gt;Things changed that meant this approach to application monitoring has become a thing of the past.  Applications leverage their infrastructure in very different ways.  For example, look at the CPU and memory utilisation profile of a reactive (or event based) system against that of a synchronous request-response system.  In one, high CPU utilisation is considered normal while in the other it may signal that the system is overloaded.&lt;/p&gt;

&lt;p&gt;Applications have become increasingly distributed so a user experience may be dictated by the performance of a number of application services residing on different OS, using different language runtimes and storage solutions etc.  Environments are no longer static and systems are capable of dynamically provisioning and un-provisioning nodes to a cluster.  In summary, things just got complicated.&lt;/p&gt;

&lt;p&gt;The increased proliferation of technologies, and the distribution and scale of today’s systems means that it has become increasingly difficult to predict system health by measuring the symptoms.  We’ve got to actually talk to the patient!&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;&lt;strong&gt;Application Monitoring in the Present&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’ve looked at a lot of the APM vendors currently operating in the marketplace and have been really impressed with how application monitoring has evolved to where it is today.  In my opinion, the significant features of todays APM offerings are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Distributed transaction tracing&lt;/li&gt;
  &lt;li&gt;Application topology detection&lt;/li&gt;
  &lt;li&gt;Automatic entry and exit detection&lt;/li&gt;
  &lt;li&gt;Lossless or aggregated metric collection&lt;/li&gt;
  &lt;li&gt;Adaptive alerting thresholds&lt;/li&gt;
  &lt;li&gt;Auto-scaling notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Distributed Transaction Tracing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Way back in April 2010, Google published a paper on ‘Dapper, a Large-Scale Distributed Systems Tracing Infrastructure’.  The subject matter is not new but people tend to listen more attentively when Google speaks.  Dapper facilitates tracing service calls that traverse a graph of distributed servers or containers. The image below shows a distributed trace for a web request as captured by Zipkin, Twitters distributed tracing system.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/post/2014-03-16-application-monitoring-past-present-future/zipkin-screenshot.png&quot; class=&quot;img-fluid img-thumbnail&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As systems become more distributed the ability to visualise distributed call graphs is immeasurable.  From the graph above I can see the touch points involved in a particular request, the latency contributed by each of these touch points and the concurrency of service invocations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Application Topology Discovery&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The collection of distributed transaction tracing data has another nice property or side effect; that is the ability to dynamically build the application topology based on the way in which services communicate with one another.  Below we see an example of such a visualisation as presented by AppDynamics.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/post/2014-03-16-application-monitoring-past-present-future/app-dynamics-application-topology-screenshot.jpg&quot; class=&quot;img-fluid img-thumbnail&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This ‘Flow Map’ is able to present a considerable amount of information in a single view.  With this type of view (note: not necessarily this particular view) I’m able to see any of the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The services involved in responding to a request.&lt;/li&gt;
  &lt;li&gt;The latency within those services.&lt;/li&gt;
  &lt;li&gt;The latency between those services.&lt;/li&gt;
  &lt;li&gt;Variation in latency within service clusters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Comparing how this view evolves over time I might also be able to see:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;New service instances servicing requests.&lt;/li&gt;
  &lt;li&gt;Service instances not servicing requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Automatic Entry and Exit Detection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Distributed tracing has a notion of entry and exit detection.  This is the ability to start and stop a transaction trace.  The easiest way to think about this is to think about a single method trace.  If I want to trace a method it is reasonable for me to expect that I must specify the fully qualified path to this method to allow the tracing tool to instrument the code (either at compile time or runtime).  If this method is well known such as the JMS listener onMessage(…) method I would expect the tracing tool to do this automatically.  The trace would start on method entry and stop on method exit.  Expand this to consider distributed tracing and we have automatic entry and exit detection.&lt;/p&gt;

&lt;p&gt;Turns out that identifying what to trace has quite a big overhead in a heterogeneous environment and so this automatic detection takes a significant burden off your development and operations teams.  If you also consider a dynamic system with releases every day, week or month it does not take long to incur considerable spend just updating your monitoring tools.&lt;/p&gt;

&lt;p&gt;You also want to be able to define the entry and exit detection manually though for situations where the entry and exit points are not well known.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lossless or Aggregated Metric Collection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There is still a debate over collecting and retaining all available metrics as opposed to sampling and/or aggregating metrics.  The trade-offs are quite obvious.&lt;/p&gt;

&lt;p&gt;When sampling and/or aggregating:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The infrastructure requirements for network and storage are reduced.&lt;/li&gt;
  &lt;li&gt;Heuristics are executed on the system being observed so there may be an increased compute (CPU and memory) overhead.&lt;/li&gt;
  &lt;li&gt;When aggregating, segmentation of data is limited.&lt;/li&gt;
  &lt;li&gt;Data is lost and so rare events may not be recorded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point on segmentation of data is worth expanding upon.  Lets consider a user making a request from their browser to a backend service.  The request is tagged on the web server with a unique identifier.  The APM agent then tags this request with the user identifier, browser, location etc.  Each of these tags allows the tracing data to be viewed along a different dimension.  If we want aggregation to result in reduced bandwidth utilisation we must aggregate on the client which in turn increases the compute overhead on the client.  To limit this, you limit the amount of segmentation of the metrics data.&lt;/p&gt;

&lt;p&gt;Limiting the segmentation of data is ok if we are creating operational dashboards from this data but becomes limiting when creating business dashboards.  I’m not in any way saying this is a bad thing but that it is a trade off.&lt;/p&gt;

&lt;p&gt;When collecting all available metrics:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The infrastructure requirements for network and storage are increased.&lt;/li&gt;
  &lt;li&gt;Overhead on the system being observed is limited to the overhead imposed by transporting the data.&lt;/li&gt;
  &lt;li&gt;All data is captured and available for extensive analytics external to the system being observed.&lt;/li&gt;
  &lt;li&gt;All data is captured so even rare event are recorded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Adaptive Thresholds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We spoke about how automatic entry and exit detection reduces the operational overhead (or TCO in sales speak) of owning and managing a monitoring solution.  One of the other duties that go along with deploying monitoring solutions is setting the high and low water marks for alerting.  This can be arduous when the system topology or behaviour is changing rapidly.&lt;/p&gt;

&lt;p&gt;Some of today’s solutions attempt to take the overhead of setting thresholds away by using heuristics on historical data.  The success or otherwise of this approach is therefore based on both the quality of the heuristics and data.  It could be argued that it is always better for a human to set these levels as they can have better knowledge of the system and its behaviours.  It can also be argued that in reality humans are time poor and often just set watermarks to so arbitrary level that ‘seams reasonable’.&lt;/p&gt;

&lt;p&gt;Personally, I would like to be able to set the baseline watermarks using heuristics and manually adjust where necessary.  With this approach I don’t have arbitrary settings that ‘seem reasonable’ but I’m also facilitated in changing watermarks when I have more insight than the heuristics and data could possible have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto-Scaling Trigger&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’ve seen auto scaling become increasingly prevalent in both public and private cloud environments (see &lt;a href=&quot;/blog/2013/07/23/making-a-case-for-iaas.html&quot;&gt;IaaS: Making a Case for Infrastructure as a Service&lt;/a&gt;. Ordinarily auto scaling is triggered by infrastructure metrics like CPU and memory.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/post/2014-03-16-application-monitoring-past-present-future/the-benefits-of-autoscaling.png&quot; class=&quot;img-fluid img-thumbnail&quot; /&gt;&lt;/p&gt;

&lt;p&gt;How much better would it be to base scaling triggers on the response time or throughput of the service rather than the behaviour of the infrastructure it resides upon.  It is certainly a lot more intuitive if I am setting watermarks manually.  If I tell you that at the 99th percentile a user receives a response in 100ms then that is quite meaningful without any knowledge of the system.  If I tell you that the CPU is at 55% then it is a lot less meaningful.  The CPU might be burning cycles in an event loop providing good, adequate or poor response times to the user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Application Monitoring in the Future&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The main thing that I would like to see in APM tools in the future is that they incorporate log aggregation capabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Log Aggregation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you have implemented APM and log aggregation tooling you’ll know that the infrastructure required by both is remarkably similar.   There is typically an agent responsible for streaming data to some form of intermediary.  There is then some for of analytics platform as well as a portal that allows you to manage all that goodness.  Why or why can’t they be the same thing (I fear someone will actually answer this!)?&lt;/p&gt;

&lt;p&gt;Here is a challenge to APM vendors:  I often hear Splunk being proposed as a solution for distributed tracing and application monitoring.  I’d like to see you get into the log aggregation space because I don’t want to deploy two similar infrastructures that allow me to export runtime data from my applications.&lt;/p&gt;</content><author><name>john_turner</name></author><category term="DevOps" /><category term="APM" /><summary type="html">In addition to working on PaaS and Continuous Delivery initiatives during 2013, I also had the opportunity to learn a lot about Application Performance Monitoring (APM) tools. Because I see PaaS and CD as enabling technologies for DevOps it was natural that I also look at shared tooling such as APM and log aggregation. Application Monitoring in the Past In the past, infrastructure monitoring was used as a proxy for the health of an application. Application health was measured in terms of CPU and memory utilisation, disk and network IO or some other infrastructure metric. Some form of translation between the behaviour of the infrastructure and the user experience took place to allow the operations team to understand when the user experience may be adversely affected by symptoms within the infrastructure. Things changed that meant this approach to application monitoring has become a thing of the past. Applications leverage their infrastructure in very different ways. For example, look at the CPU and memory utilisation profile of a reactive (or event based) system against that of a synchronous request-response system. In one, high CPU utilisation is considered normal while in the other it may signal that the system is overloaded. Applications have become increasingly distributed so a user experience may be dictated by the performance of a number of application services residing on different OS, using different language runtimes and storage solutions etc. Environments are no longer static and systems are capable of dynamically provisioning and un-provisioning nodes to a cluster. In summary, things just got complicated. The increased proliferation of technologies, and the distribution and scale of today’s systems means that it has become increasingly difficult to predict system health by measuring the symptoms. We’ve got to actually talk to the patient!</summary></entry></feed>