Nutanix Calm and Karbon: CI/CD

The estimated time to complete this lab is ?? minutes.

Overview

Countless studies have shown that reducing the amount of time for developers to receive feedback on code changes improves software quality. Automating the build, test, and deployment of software with tools such as Jenkins is one of the best ways to accelerate software development. If you’re unfamiliar with Jenkins you can learn more on their website here.

In this lab, we’ll utilize Nutanix Calm to build the infrastructure required to create a Continuous Integration / Continuous Delivery (CI/CD) Pipeline, which includes deploying Jenkins, a Gitea git server, a developer workstation, and configuring that workstation to manage a Karbon Kubernetes cluster. Once the infrastructure is deployed via Nutanix Calm, we’ll go through configuring Jenkins and Gitea to create a fully function CI/CD pipeline. The end result will be a developer running a “git push”, which triggers Jenkins to build a docker container based on the new code, publishes that container to DockerHub, and then deploys that container onto the Nutanix Karbon Kubernetes cluster.

CI/CD with Gitea, Jenkins, and Nutanix Karbon

Pre-requisites

This lab requires:

  • a running Nutanix Karbon Kubernetes cluster. When the Calm blueprint is launched in the next step, a runtime variable will prompt you for the Karbon Kubernetes cluster name. If this bootcamp environment was staged, then use the default value of karbon_bootcamp_cluster. Otherwise, please specify the name of an existing Karbon Kubernetes cluster.

  • a DockerHub account. If you do not already have one, go ahead and sign up for an account (it’s free).

All other required components are deployed via a Calm blueprint.

Creating our CI/CD Infrastructure with Nutanix Calm

Building a CI/CD pipeline generally involves connecting a large number of disparate tools. To streamline this lab, we’ll be deploying many of these tools via a Nutanix Calm blueprint. We’ll first launch the blueprint, and then in the ~20 minutes it takes to deploy, we’ll cover the blueprint architecture to familiarze ourselves with the tools involved.

  1. In Prism Central, select > Services > Calm.

  2. Select blueprints Blueprints in the left hand toolbar to view and manage Calm blueprints.

    Note

    Mousing over an icon will display its title.

  3. Find the blueprint titled CICD_Infra, select its checkbox, and from the Action dropdown, click Launch.

    Nutanix Calm CI/CD Infrastructure Blueprint Launch 1
  4. On the launch page, fill in the following fields.

    • Name of the Application - initials-cicd-infra

    • gitea_password - Any password desired, which will be set as the Gitea admin user password

    • karbon_cluster_name - The name of the Karbon cluster to use for this lab (it must already be depoyed). If the cluster was staged, leave the default of karbon_bootcamp_cluster.

    Nutanix Calm CI/CD Infrastructure Blueprint Launch 2
  5. Click the blue Create button, and ensure you’re redirected to the application page.

Now that we’re waiting for our CI/CD Infrastructure to deploy, let’s review the architecture of the blueprint. If desired, open the blueprint in Calm and view the Services and their underlying scripts as they’re covered. Alternatively, here’s an image of the blueprint canvas.

Nutanix Calm CI/CD Infrastructure Blueprint Architecture

In approximate order (approximate as Calm deploys Services in parallel, unless there is a dependency), the blueprint deploys the following Services:

  • Kubernetes (Existing machine) - this Service utilizes VM Pre-create eScript tasks to make API calls into Prism Central, to find a Karbon Kubernetes cluster matching the karbon_cluster_name variable defined at launch. It also sets the content of the cluster’s kubeconfig as a variable, which will be later applied to the Workstation VM.

  • Gitea (AHV) - this Service installs Gitea, which is a community managed lightweight code hosting solution. It first installs MySQL, as Gitea requires a backend DB to operate. It then creates self signed certificates, installs the Gitea service, and configures the repo which stores our application code.

  • Jenkins_Master (AHV) - this Service installs Jenkins, a popular Continuous Integration server. It also trusts Certificate Authority (CA) generated during the Package Install of the Gitea Service.

  • Jenkins_Slave (AHV) - this Service installs a Jenkins Slave, which is used for builds in our Jenkins pipeline. Meaning this is the node that is responsible for building a docker container based on the new application code, publishing tha container to DockerHub, and then deploying the new container to the Kubernetes cluster.

  • Workstation (AHV) - this Service represents a “developer workstation,” and is where we’ll be making changes to our application code later in this lab. It first installs necessary software (like git and kubectl), and then configures the kubeconfig based on the variable set in the Kubernetes Service. Finally, it clones the git repo configured in the Gitea Service.

Once your Jenkins_Master and Jenkins_Slave Services have been fully deployed, move on to the next section.

Accessing Jenkins

Now that our CI/CD Infrastructure has been deployed, we’re ready to start configuration of the various components that make up our Pipeline. First up, is Jenkins.

  1. On the Overview tab of your Application, right click on the Jenkins link, and open the page in a new tab.

    Nutanix Calm CI/CD Infrastructure App Overview
  2. While still within the Calm Application page, navigate to the Services tab, select the Jenkins_Master Service, and in the right column, click Open Terminal.

    CI/CD Infrastructure App Open Terminal
  3. In the Web SSH Terminal that just opened, run the following command to print out Jenkins’ temporary administrator password.

    sudo cat /var/lib/jenkins/secrets/initialAdminPassword
    
  4. Double click the result from the previous step’s command to copy it to your clipboard.

    Jenkins Master Temporary Admin Password
  5. Change to the Sign in [Jenkins] tab that was previously opened. In the Administrator password field, paste in the contents of the previous step, and click Continue.

    Unlock Jenkins
  6. On the next page, click the large Install suggested plugins button.

    Install Jenkins Suggested Plugins
  7. Wait for the suggested plugins to install, after which you’ll be re-directed to create the first admin user. Fill in the following fields, and click Save and Continue.

    • Username - admin

    • Password - any password of your choice

    • Confirm password - matching password

    • Full name - admin

    • Email address - noreply@nutanix.com

    Create Jenkins Admin User
  8. On the Instance Configuration page that appears, leave the Jenkins URL as default, and click Save and Finish.

  9. Jenkins setup is now complete, but first our Jenkins instance needs to be restarted. Click Restart, and then move on to the next section.

    Restart Jenkins

Accessing the Developer Workstation

Throughout this entire lab, we’ll be running a large number of commands from our developer workstation, as it has already been configured with all the necessary software packages, the correct kubeconfig file, and is pointed at our git repository that’s stored in Gitea.

To access the developer workstation, you have two options: 1, use the web SSH client as we did for the Jenkins Service, or 2, use your laptop’s terminal or PuTTY to SSH into the workstation. Either option is perfectly valid, however we recommend you stick with whatever you’re most comfortable with. Since we already covered how to use the web SSH client in the previous step, we’ll cover SSH’ing in from your laptop here.

  1. Back in our Calm application page, navigate to the Services tab, and select the Workstation Service. In the right column that appears, copy the IP address of the service by clicking the button just to the right of the IP.

    CI/CD Infrastructure App Copy Workstation IP
  2. In your laptop’s terminal, run the following commands to SSH into your workstation (be sure to subsitute in your workstation IP).

    echo '-----BEGIN RSA PRIVATE KEY-----
    MIIEowIBAAKCAQEAii7qFDhVadLx5lULAG/ooCUTA/ATSmXbArs+GdHxbUWd/bNG
    ZCXnaQ2L1mSVVGDxfTbSaTJ3En3tVlMtD2RjZPdhqWESCaoj2kXLYSiNDS9qz3SK
    6h822je/f9O9CzCTrw2XGhnDVwmNraUvO5wmQObCDthTXc72PcBOd6oa4ENsnuY9
    HtiETg29TZXgCYPFXipLBHSZYkBmGgccAeY9dq5ywiywBJLuoSovXkkRJk3cd7Gy
    hCRIwYzqfdgSmiAMYgJLrz/UuLxatPqXts2D8v1xqR9EPNZNzgd4QHK4of1lqsNR
    uz2SxkwqLcXSw0mGcAL8mIwVpzhPzwmENC5OrwIBJQKCAQB++q2WCkCmbtByyrAp
    6ktiukjTL6MGGGhjX/PgYA5IvINX1SvtU0NZnb7FAntiSz7GFrODQyFPQ0jL3bq0
    MrwzRDA6x+cPzMb/7RvBEIGdadfFjbAVaMqfAsul5SpBokKFLxU6lDb2CMdhS67c
    1K2Hv0qKLpHL0vAdEZQ2nFAMWETvVMzl0o1dQmyGzA0GTY8VYdCRsUbwNgvFMvBj
    8T/svzjpASDifa7IXlGaLrXfCH584zt7y+qjJ05O1G0NFslQ9n2wi7F93N8rHxgl
    JDE4OhfyaDyLL1UdBlBpjYPSUbX7D5NExLggWEVFEwx4JRaK6+aDdFDKbSBIidHf
    h45NAoGBANjANRKLBtcxmW4foK5ILTuFkOaowqj+2AIgT1ezCVpErHDFg0bkuvDk
    QVdsAJRX5//luSO30dI0OWWGjgmIUXD7iej0sjAPJjRAv8ai+MYyaLfkdqv1Oj5c
    oDC3KjmSdXTuWSYNvarsW+Uf2v7zlZlWesTnpV6gkZH3tX86iuiZAoGBAKM0mKX0
    EjFkJH65Ym7gIED2CUyuFqq4WsCUD2RakpYZyIBKZGr8MRni3I4z6Hqm+rxVW6Dj
    uFGQe5GhgPvO23UG1Y6nm0VkYgZq81TraZc/oMzignSC95w7OsLaLn6qp32Fje1M
    Ez2Yn0T3dDcu1twY8OoDuvWx5LFMJ3NoRJaHAoGBAJ4rZP+xj17DVElxBo0EPK7k
    7TKygDYhwDjnJSRSN0HfFg0agmQqXucjGuzEbyAkeN1Um9vLU+xrTHqEyIN/Jqxk
    hztKxzfTtBhK7M84p7M5iq+0jfMau8ykdOVHZAB/odHeXLrnbrr/gVQsAKw1NdDC
    kPCNXP/c9JrzB+c4juEVAoGBAJGPxmp/vTL4c5OebIxnCAKWP6VBUnyWliFhdYME
    rECvNkjoZ2ZWjKhijVw8Il+OAjlFNgwJXzP9Z0qJIAMuHa2QeUfhmFKlo4ku9LOF
    2rdUbNJpKD5m+IRsLX1az4W6zLwPVRHp56WjzFJEfGiRjzMBfOxkMSBSjbLjDm3Z
    iUf7AoGBALjvtjapDwlEa5/CFvzOVGFq4L/OJTBEBGx/SA4HUc3TFTtlY2hvTDPZ
    dQr/JBzLBUjCOBVuUuH3uW7hGhW+DnlzrfbfJATaRR8Ht6VU651T+Gbrr8EqNpCP
    gmznERCNf9Kaxl/hlyV5dZBe/2LIK+/jLGNu9EJLoraaCBFshJKF
    -----END RSA PRIVATE KEY-----' | tee ~/calmkey
    chmod 600 ~/calmkey
    ssh -i ~/calmkey centos@<workstation-ip>
    
  3. Validate that our kubeconfig and git repo are set up properly by running the following commands. Your output should be similar to the image below, but with different node names and IPs.

    kubectl get nodes
    cd ~/hello-kubernetes/
    git status
    git remote -v
    echo $JENKINS_HOOK_URL
    echo $GIT_REPO_URL
    
    Validate Workstation Configuration

Gitea Webhook Setup

Our next configuration step is to create a webhook in Gitea, which tells Gitea to inform some server (in our case Jenkins) each time there is a new commit. Many popular git servers have this functionality, including GitHub, GitLab, and Gitea.

  1. We’ll access our Gitea Service in the same manner as Jenkins, by navigating to the Overview tab of our Calm application, right clicking on the Gitea link, and opening it in a new tab.

  2. It is expected to receive a warning from your browser about the site’s security certificate not being trusted by your computer. This is due to the use of self signed SSL certificates during setup (which is not recommended for production workloads). Select the Proceed Anyway option (exact wording may depend on your browser).

  3. On the Gitea homepage, click the Sign In button in the upper right.

    Gitea Homepage
  4. Sign in with the following credentials.

    • Username - gitadmin

    • Password - your password specified when launching the Calm blueprint

    Gitea Sign In
  5. On the page that appears, click the gitadmin/hello-kubernetes repository link, then Settings along the right-hand side, and finally the Webhooks tab.

    Gitea Repository Settings
  6. Click the blue Add Webhook button, in the list that appears click Gitea, and then fill in the following fields.

    • Target URL - The output of the echo $JENKINS_HOOK_URL command from the previous “Developer Workstation” section, should be of the format http://<jenkins-ip>:8080/gitea-webhook/post

    • HTTP Method - Leave the default of POST

    • POST Content Type - Leave the default of application/json

    • Secret - Leave it blank (Jenkins does not require a secret by default)

    • Trigger On - Leave the default of Push Events (any time a user runs “git push” Gitea will send the webhook)

    • Branch filter - Leave the default of * (this means the webhook will be triggered for any branch)

    • Active - Leave the Active checkbox enabled.

    Gitea Add Webhook
  7. Click the green Add Webhook button. You should receive a notification that the webhook has been added.

    Gitea Webhook Added
  8. To validate the webhook is operating as expected, click the pencil to the right of the webhook. Scroll all the way to the bottom of the page, and click the teal Test Delivery button. After a moment, the page should refresh, and there should be a successful test event created. If the Response has a green 200 code, then everything is configured properly.

    Gitea Successful Test Webhook

DockerHub Setup

After a GitHub commit triggers a Jenkins build, and Jenkins successfully builds our new docker image, it needs some place to store the image. In this lab, we’ll be using DockerHub, however there are many free container registries available.

  1. First, login to DockerHub (or create a free account) and click the Create Repository button.

    DockerHub Create Repository Button
  1. Name the repository hello-kubernetes, give it a description of your choice, leave all other fields as default (be sure to leave the repo as Public), and click Create.

    DockerHub Create Repository

Jenkins Credentials Creation

The first step of our Jenkins Setup is to add our various credentials to Jenkins’ credential store, which gives Jenkins the ability to authenticate to other pieces of our pipeline. We’ll first add our DockerHub credentials, which allows Jenkins to push images. TODO: Validate this statement. In many environments, you would also need to add git credentials for Jenkins to be able to read the repository, however in this particular environment, our Gitea server has our git repository marked as public, so no authentication is necessary to read the repo. Lastly, we’ll add our Karbon kubeconfig file to allow Jenkins to deploy our application directly onto our Kubernetes cluster.

  1. Log in to your Jenkins server with the credentials you created earlier (you may need to refresh your browser page due to the Jenkins reboot in a previous section).

  2. In the Jenkins UI, select Credentials along the left, and then in the Stores scoped to Jenkins section, select the global domain.

    Jenkins Global Credentials
  3. Click Add Credentials along the left column.

    Jenkins Add Global Credentials
  4. Fill in the following fields to add your DockerHube credentials, and click OK.

    • Kind - leave as default (Username with password)

    • Scope - leave as default (Global)

    • Username - your DockerHub username (not your email)

    • Password - your DockerHub password

    • ID - leave blank

    • Description - DockerHub Credentials

    Jenkins Add DockerHub Credentials
  5. Lastly, we’ll need to add our kubeconfig file as a credential to allow Jenkins to deploy our updated application onto our Kubernetes cluster. In our Workstation CLI, run the following commands to create a Kubernetes Service Account jenkins, and then create a Role Binding which maps our Service Account the the built-in admin role (each individual command starts with a “$”, they should be run one at a time, and do not include the “$” in the command).

    $ cd ~/
    $ kubectl create serviceaccount jenkins
    $ cat << EOF > jenkins-rb.yaml
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      namespace: default
      name: jenkins-rolebinding
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: admin
    subjects:
    - kind: ServiceAccount
      name: jenkins
      namespace: default
    EOF
    $ kubectl create -f jenkins-rb.yaml
    

    Note

    We’re limiting our jenkins Service Account to a single Kubernetes namespace (default).

  6. We’ll now replace the token in our existing kubeconfig with the token of our newly generated Service Account, which we can do in one line with the following command.

    sed "s/    token:.*/    token: `kubectl get secrets $(kubectl get serviceaccounts jenkins -o jsonpath={.secrets[].name}) -o jsonpath={.data.token} | base64 --decode`/g" ~/.kube/config
    
  7. Copy the long output of that command into your buffer, and head back into the Jenkins UI. Select Add Credentials again, fill in the following fields, and click OK.

    • Kind - Kubernetes configuration (kubeconfig)

    • Scope - leave as default (Global)

    • ID - leave blank

    • Description - Karbon Kubernetes Kubeconfig

    • Kubeconfig - select the Enter directly radio button

    • Content - paste in the output from the previous step

    Jenkins Add Kubeconfig Credential

Jenkins Pipeline Creation

It’s now time to create our Jenkins Pipeline. The pipeline is the crux of this entire CI/CD workload: our Gitea webhook calls this pipeline, which is then responsible for building our docker container, uploading the container to DockerHub, and deploying the new container to our Karbon Kubernetes cluster.

  1. In the Jenkins UI, click New Item in the upper left, enter hello-kubernetes as the name, select Pipeline, and click OK.

    Jenkins Create Pipeline 1
  2. Under the General section, give your pipeline a description, and leave all checkboxes as unselected.

    Jenkins Create Pipeline 2
  3. Under the Build Triggers section, select Poll SCM, and leave the Schedule blank. Without a schedule, Jenkins will only run this pipeline from a Webhook, which is desired for this setup. Leave all other checkboxes as unselected.

    Jenkins Create Pipeline 3
  4. Skip the Advanced Project Options section.

  5. Under the Pipeline section, fill in the following fields.

    • Definition - Change the dropdown to Pipeline script from SCM, which allows us to store our Jenkinsfile in the same source code repository as our application

    • SCM - Change the dropdown to git

    • Repositories

      • Repository URL - Fill in your Gitea repository URL, which can be found by running echo $GIT_REPO_URL from your Workstation, and should be of the format https://<gitea-ip>:3000/gitadmin/hello-kubernetes

      • Credentials - Leave as default none (if your git repository is private, you would need to specify your git credentials here)

    • Branches to build - Leave all as default

    • Repository browser - Leave as default of Auto

    • Additional Behaviours - Leave default of none

    • Script Path - Leave as default of Jenkinsfile

    • Lightweight checkout - Leave as default checked

    Jenkins Create Pipeline 4
  6. Click Save to save the pipeline configuration.

Jenkins Pipeline Snippet Generator

We’ll now use the Jenkins Pipeline Syntax Snippet Generator to assist us when we go to create our Jenkinsfile in the upcoming section. Since the result of this section is a text string which will be included in our Jenkinsfile (and will be provided in the next section), it’s not required to perform the same steps on your system. However, it is good practice as it’s something you’ll likely need to do if you expand upon this example.

  1. Within your pipeline homepage, click the Pipeline Syntax button in the left column, and fill out the following fields.

    • Sample Step - change the dropdown to kubernetesDeploy: Deploy to Kubernetes

    • Kubeconfig - select the Kubeconfig that was added in a previous section

    • Config Files - enter hello-kubernetes-dep.yaml (we have not created this file yet, but will in an upcoming section)

    • Leave all other options as defaults

    Jenkins Generate Pipeline Script
  2. Click Generate Pipeline Script. In the text box that appears, you should see a string like this, however your kubeconfigId will be different. When this string is placed in a Jenkinsfile, it instructs Jenkins to deploy a certain configuration (hello-kubernetes-dep.yaml) against a particular Kubernetes cluster (in our case the cluster config is stored in the kubeconfig credential we created in an earlier section).

    kubernetesDeploy configs: 'hello-kubernetes-dep.yaml', kubeConfig: [path: ''], kubeconfigId: '4ee7aa78-d810-43a3-804c-7cbaf717d225', secretName: '', ssh: [sshCredentialsId: '*', sshServer: ''], textCredentials: [certificateAuthorityData: '', clientCertificateData: '', clientKeyData: '', serverUrl: 'https://']
    

    Note

    The serverUrl field does not need an actual URL as that information is stored in our Kubeconfig.

  3. Optionally copy this script for later use.

Jenkinsfile and Yaml Creation

We’ll now create our Jenkinsfile, which is the script Jenkins uses to run our Pipeline, and our Kubernetes YAML, which is what defines our application. We’ll first grab some information from our Jenkins and DockerHub UIs, and then head over into our workstation to create our files.

  1. In the Jenkins UI, click the Jenkins icon in the upper left to navigate home, and then select Credentials along the left column.

  2. Take note of the ID column in the Credentials table. These values will be unique on every system, and your specific values are needed when we create our Jenkinsfile.

    Jenkins Credentials IDs
  3. In your DockerHub UI, select your hello-kubernetes repository, and along the right side, take note of the docker push <your-username>/hello-kubernetes:tagname field. Your username will be needed in the next step when we create our Jenkinsfile.

    DockerHub Username
  4. Head over into your Workstation SSH session, and run the following commands to create our Jenkinsfile, substituting your unique credential IDs in the second and third commands, and DockerHub username in the fourth (each individual command starts with a “$”, they should be run one at a time, and do not include the “$” in the command).

    $ cd ~/hello-kubernetes/
    $ DOCKER_ID=<your-dockerhub-cred-id>
    $ KUBE_ID=<your-kubernetes-kubeconfig-id>
    $ DOCKER_USER=<your-dockerhub-username>
    $ cat << EOF > Jenkinsfile
    node("docker") {
        docker.withRegistry("", "${DOCKER_ID}") {
    
            git url: "${GIT_REPO_URL}"
            env.GIT_COMMIT = sh(script: "git rev-parse HEAD", returnStdout: true).trim()
    
            stage "Build"
            def helloK8s = docker.build "${DOCKER_USER}/hello-kubernetes"
    
            stage "Publish"
            helloK8s.push 'latest'
            helloK8s.push "\${env.GIT_COMMIT}"
    
            stage "Deploy"
            kubernetesDeploy configs: 'hello-kubernetes-dep.yaml', kubeConfig: [path: ''], kubeconfigId: "${KUBE_ID}", secretName: '', ssh: [sshCredentialsId: '*', sshServer: ''], textCredentials: [certificateAuthorityData: '', clientCertificateData: '', clientKeyData: '', serverUrl: 'https://']
    
        }
    }
    EOF
    $ cat Jenkinsfile
    
    Create Jenkinsfile
  5. We’ll now create our two Yaml files which will define our application. The first is a Service to expose the application outside of the Karbon Kubernetes cluster, and the second is a Deployment which defines the application containers. We’ll create both files within the hello-kubernetes/ directory, but we’ll only apply the service yaml, as Jenkins will apply the deployment yaml (each individual command starts with a “$”, they should be run one at a time, and do not include the “$” in the command).

    $ cd ~/hello-kubernetes/
    $ cat << EOF > hello-kubernetes-svc.yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: hello-kubernetes
    spec:
      type: LoadBalancer
      ports:
      - port: 80
        targetPort: 8080
      selector:
        app: hello-kubernetes
    EOF
    $ cat << EOF > hello-kubernetes-dep.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: hello-kubernetes
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: hello-kubernetes
      template:
        metadata:
          labels:
            app: hello-kubernetes
        spec:
          containers:
          - name: hello-kubernetes
            image: ${DOCKER_USER}/hello-kubernetes:\${GIT_COMMIT}
            ports:
            - containerPort: 8080
    EOF
    $ kubectl apply -f hello-kubernetes-svc.yaml
    
    Create Application YAML

    Note

    Take note of the ${GIT_COMMIT} value in the deployment YAML. Jenkins will automatically substitute in the git commit ID, so each time the deployment is applied, the image tag is incremented, and the pods are re-deployed.

  1. Now that our Service is deployed, and our local files are written, it’s time to commit and push changes to our repository with the following commands.

    git add Jenkinsfile hello-kubernetes-dep.yaml hello-kubernetes-svc.yaml
    git commit -m 'Added Jenkinsfile and Application yaml'
    git push
    
    Git Add and Commit Jenkinsfile and App YAML Files

Manual Build and Application Deployment

Typically, running git push will trigger a Jenkins build through the GitHub webhook, however this will not work until we manually trigger a build. This is because the SCM details (including the project URL) in the Jenkins pipeline are not initialized until the first build, and without those details Jenkins is not able to determine the correlation between the webhook and the pipeline. Let’s manually kick off a build to get things started.

  1. In the Jenkins UI, navigate to our hello-kubernetes Pipeline, and click the Build Now link in the left column.

    Jenkins Manual Build
  2. Build #1 should appear in the Build History section in the left column. Click the #1 link, and then select Console Output in the left column. This allows us to monitor the status of the Jenkins build. At the top of the build, we should see a successful login to DockerHub.

    Jenkins Build #1 Console Output 1
  3. In the middle of our console output we should see the docker image being successfully built.

    Jenkins Build #1 Console Output 2
  4. At the bottom of the console output we should see our image being tagged, pushed to DockerHub, and then finally our kubernetesDeploy task deploying our containers to our Karbon Kubernetes cluster.

    Jenkins Build #1 Console Output 3
  5. In DockerHub, we can validate that our newly pushed container is present, with both our GIT_COMMIT and latest labels.

    DockerHub Repository Tags
  6. We can also validate through the command line that our pods have been deployed, and our application Service has an IP by running the following commands from our Workstation.

    kubectl get pods
    kubectl get svc
    
    Kubectl Get Pods / Svc
  7. We can then access our application via the External-IP value of the hello-kubernetes service (10.45.100.46 in my case). Be sure to refresh the page several times to see the pod change.

    Hello Nutanix Application

Automated Application Deployment Through a Git Push

If you’ve made it this far, congratulations! We’re finally at a point where we can kick off fully automated builds and deployments. To do so, we need to commit and push a change in our application code. So our change is obvious, we’ll change the Hello Nutanix! message to Hello CI/CD!.

  1. From within the hello-kubernetes/ directory on your workstation, run the following commands to change the code, add the change, commit the change, and finally push the change.

    sed -i 's|Nutanix|CI/CD|g' server.js
    git add server.js
    git commit -m 'Modified Hello message'
    git push
    
    Git Push New Application Code
  2. As soon as you run git push, you should see an automated build started in your Jenkins project.

    Jenkins Automatic Build 2
  3. Once the build is complete, let’s first verify we have new pods deployed via the command line.

    kubectl get pods
    
    Kubectl Get Pods after Git Push
  4. Finally, refresh our application page to view the updated message.

    Hello CI/CD Application

Takeaways

While setting up a CI/CD pipeline can be quite a bit of effort, the value it brings to your organization makes it well worth it. Once configured, a simple git push – an operation your developers likely run several times a day – results in a brand new application, with minimal to no effort on your or the developers part. This can be further expanded into advanced techniques like Canary releases or A/B testing. Thanks for reading!

(Optional) Use Nutanix Calm Jenkins Plugin

In this lab, we utilized the Kubernetes-Continuous-Deploy Jenkins Plugin to deploy our new docker containers. Another option would be to utilize the Nutanix-Calm Jenkins Plugin to call a Calm application action to deploy our new docker containers. Can you change the existing Jenkins Pipeline to utilize the Nutanix Calm plugin instead?

Hints

  • You’ll first want to define our hello-kubernetes application as a Calm Application Blueprint, rather than the YAML we were using.

  • Once you successfully build the blueprint, create a profile action which accepts a runtime variable (which represents the docker label / tag) and makes an API call into the Kubernetes API to update the containers.

  • Once that is built, utilize the Jenkins syntax generator to create the relevant Jenkinsfile snippet to call your Calm Application Action.

  • Substitute out the Kubernetes Deploy snippet with the Calm Application Action snippet in your Jenkinsfile.