Skip to content

Jenkins X Import

Awesome, by now we have a Kubernetes cluster with Jenkins X, HashiCorp Vault for secrets, a MySQL database, and a Quarkus Application ready to import.

If you don't have a working version of the application coming out of Chapter 03 Quarkus, You can find the source code in the branch 03 Quarkus of my repository.

Jenkins X Basics

Just in case you're relatively new to Jenkins X, let me give you a bit of an introduction.

If you prefer to see it in action and watch a video instead of reading more text, watch one of the video's from Viktor Farcic.

Jenkins X provides pipeline automation, built-in GitOps, and preview environments to help teams collaborate and accelerate their software delivery at any scale.

Ok, that might be a bit too vague.

Jenkins X is built on top of four pillars:

  1. Jenkins X Pipelines: Jenkins X is a full CI/CD engine, which has pipelines to build, test, and deploy your applications on and to Kubernetes. As is popular these days, Jenkins X Pipelines are written in YAML.
  2. Manage Environments By GitOps: GitOps is a way of working, where you apply everything from Configuration as Code to your enviromnents. The assumption is that we all use Git nowadays, so we store this code in Git. We then leverage the Jenkins X Pipelines - environments have their own pipelines - to manage the environment.
  3. Preview Environments: when you make a PR, wouldn't it be awesome if you would get a temporary deployment so you can test if the PR not only works, but works as we want it? That's what preview environments are.
  4. Build Packs: not be confused with other Build Packs, these are Jenkins X's build packs and they ensure that you don't have to write any of the above mentioned workflows yourself. You can off course, or extend them where needed. The idea is that you get a full CI/CD Developer Experience with batteries included, but easily replaced.

Code Start

If you do not have a working version after the previous chapter, you can find the complete working code in the branch 03-quarkus.

Import

Oke, so now we're kinda getting to the point want to start running our new Quarkus application on this shiny new Jenkins X thingy. While Jenkins X has quickstarts (see the list here) to get you up and running fast, we already have an application.

So we're going to use Jenkins X's import feature. This feature uses the mentioned build packs to generate everything our application is currently missing. After that, it imports the application in Jenkins X, by creating the required resources in the cluster to ensure Jenkins X now watches your application. This means, that after this, every push to your github repository will trigger a build.

One of the things generated during the jx import process, is your Jenkins X Pipeline. This will be derived from an existing pipeline, you can see the list here, so the file in your repository will only state buildpack: <name of the build pack>.

Run The Import

In our case, we want to build with Maven and Java 11, there is no Quarkus Java 11 yet, so we will run jx import --pack maven-java11.

jx import --pack maven-java11

The import process also generates - if absent - an appropriate Dockerfile, and our Helm charts. The Helm chart creation is mandatory, so the import only succeeds if there is no charts/ folder present.

Acitvity & Build Logs

Once your application is succesfully imported, you will get a message explaining several Jenkins X commands.

Two of the most important ones, are jx get build log and jx get activity. The message should give you a copyable command, so I'll leave that up to you to copy from there - it includes a filter with the name, which I can only guess.

The activity log gives you an always running overview of pipeline activity for a given filter. If you're used to Jenkins, it's aching to seeing the classic Pipeline Overview page in console form.

The build log gives you the log of the build while it is happening. As soon as a build is started, you can run jx get build log, and you should see your build as the top item.

Once the application's pipeline finishes, it should trigger a promotion to the staging environment. To see that pipeline, you can run jx get build log again, and select its pipeline run.

> joostvdg/env.../master #2 promotion
  joostvdg/env.../pr-23 #1 promotion-build

End Of Successful Build Log

Promoting app quarkus-fruits version 1.0.5 to namespace jx-staging
Created Pull Request: https://github.com/joostvdg/env.../pull/22
Added label updatebot to Pull Request https://github.com/joostvdg/env.../pull/22
got git provider status pending from PR https://github.com/joostvdg/env.../pull/22

Application In Staging

To verify the application landed successfully in the staging environment, you can run jx get applications.

jx get applications

Which should give you a result like this:

APPLICATION    STAGING PODS URL
quarkus-fruits 1.0.1        https://quarkus-fruits-jx-staging.staging.Your.Domain.com

As you can see, the PODS section is empty. This is because the application cannot talk to its Database yet. No worries, we resolve this later.

Dockerfile

This the Dockerfile below, is created by the Quarkus team, designed for a runnable Jar application build with Quarkus. If you did not start from the quickstart, but generated a new Quarkus project, it is available in src/main/docker/Dockerfile.jvm.

Either way, Jenkins X expects a Dockerfile at the root, so update that Dockerfile with the contents below.

I'd recommend using it, as it is well tested and does everything we need in minimal fashion and according to Docker's best practices.

Dockerfile

FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1

ARG JAVA_PACKAGE=java-11-openjdk-headless
ARG RUN_JAVA_VERSION=1.3.5

ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'

# Install java and the run-java script
# Also set up permissions for user `1001`
RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \
    && microdnf update \
    && microdnf clean all \
    && mkdir /deployments \
    && chown 1001 /deployments \
    && chmod "g+rwX" /deployments \
    && chown 1001:root /deployments \
    && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \
    && chown 1001 /deployments/run-java.sh \
    && chmod 540 /deployments/run-java.sh \
    && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security

# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size.
ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"

COPY target/lib/* /deployments/lib/
COPY target/*-runner.jar /deployments/app.jar

EXPOSE 8080
USER 1001

ENTRYPOINT [ "/deployments/run-java.sh" ]

Health Check

One of the most important aspects of your application running in Kubernetes, is giving Kubernetes enough information about the state of your application. Kubernetes does this via Liveness and Readiness probes.

What Are These Probes

If you don't know much about these probes, or want to understand them better, read the Kubernetes docs. For the lazy, I've added quotes for this docs page.

livenessProbe: Indicates whether the Container is running. If the liveness probe fails, the kubelet kills the Container, and the Container is subjected to its restart policy. If a Container does not provide a liveness probe, the default state is Success.

readinessProbe: Indicates whether the Container is ready to service requests. If the readiness probe fails, the endpoints controller removes the Pod’s IP address from the endpoints of all Services that match the Pod. The default state of readiness before the initial delay is Failure. If a Container does not provide a readiness probe, the default state is Success.

We recommend servicing each probe with a distinct endpoint if you can.

Quarkus Health Checks

Luckily, Quarkus has a dependency that gives us exactly that: quarkus-smallrye-health. quarkus-smallrye-health is an implementation of MicroProfile's Health specification, and they have a guide on using and extending it.

For those wanting to skip the line, for the minimal implementation we add one dependency to our pom.xml, namely, the mentioned quarkus-smallrye-health. This dependency automatically gives us three endpoints for health checks:

  • /health/live: The application is up and running.
  • /health/ready: The application is ready to serve requests.
  • /health: Accumulating all health check procedures in the application.

Add Dependency

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-smallrye-health</artifactId>
</dependency>

Update Our Kubernetes Configuration

We now have the ability to give Kubernetes the information it needs to understand the status of our application. We still have to ensure Kubernetes knows how to get this information.

To do so, we have two choices.

  1. Update the values.yaml of our Chart, it has one entry for both checks, so not optimal, but minimal effort
  2. Update the templates/deployment.yaml of our Chart, where we have to set both endpoints

Update Values

By default, the Helm chart that is generated has one variable for both the liveness and readyness probes: probePath.

The value is specified between resources and livenessProbe:

We set the value of probePath to /health:

charts/Name-Of-Your-App/values.yaml

resources:
  limits:
    cpu: 250m
    memory: 64Mi
  requests:
    cpu: 250m
    memory: 64Mi
probePath: /health
livenessProbe:
  initialDelaySeconds: 60
  periodSeconds: 10
  successThreshold: 1
  timeoutSeconds: 1

Update Deployment

The second way of make the required change, is to update our Deployment definition. You can choose to set the values directly in the template, or better, change the variables used in the probe paths and set the values of these variables in the values.yaml.

We will do the latter here:

charts/Name-Of-Your-App/templates/deployment.yaml

livenessProbe:
  httpGet:
    path: {{ .Values.livenessProbePath }}
    port: {{ .Values.service.internalPort }}
  initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
  periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
  successThreshold: {{ .Values.livenessProbe.successThreshold }}
  timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
readinessProbe:
  httpGet:
    path: {{ .Values.readinessProbePath }}
    port: {{ .Values.service.internalPort }}
  periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
  successThreshold: {{ .Values.readinessProbe.successThreshold }}
  timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}

charts/Name-Of-Your-App/values.yaml

resources:
  limits:
    cpu: 250m
    memory: 64Mi
  requests:
    cpu: 250m
    memory: 64Mi
livenessProbePath: /health/live
readinessProbePath: /health/ready
livenessProbe:
  initialDelaySeconds: 60
  periodSeconds: 10
  successThreshold: 1
  timeoutSeconds: 1

Next Steps

We are going to do a lot more things, but this concludes the initial steps to import our application in Jenkins X. Our application still can't run, because it doesn't have the information to connect to the database. This is our next stop.


Last update: 2020-06-11 13:51:44