Import Existing Project¶
Tip¶
As we will have to edit yaml
files, I can recommend using a commandline yaml editor.
One such is mikefarah's yq.
1 | snap install yq |
1 | brew install yq |
Another tip, for testing URL's, instead of using CURL, I would recommend HTTPie.
1 | apt-get install httpie |
1 | yum install httpie |
1 | brew install httpie |
1 2 | pip install --upgrade pip setuptools pip install --upgrade httpie |
Config¶
Replace ?
with your GitHub user where you will be working from.
1 | GH_USER=? |
Fork example project¶
Fork github.com/demomon/jx-micronaut-seed as a start.
And then checkout your version of the project and go into the project's directory.
1 2 | git clone git@github.com:${GH_USER}/jx-micronaut-seed.git \ && cd jx-micronaut-seed |
1 2 | git clone https://github.com/${GH_USER}/jx-micronaut-seed.git \ && cd jx-micronaut-seed |
Import in JX¶
Info
When in doubt, accept the defaults of the prompts asking questions.
1 | jx import |
You should see, among other things, the following logs.
1 2 3 | selected pack: /Users/joostvdg/.jx/draft/packs/github.com/jenkins-x-buildpacks/jenkins-x-kubernetes/packs/gradle
replacing placeholders in directory /Users/joostvdg/Projects/Personal/Github/jx-micronaut-seed
app name: jx-micronaut-seed, git server: github.com, org: joostvdg, Docker registry org: joostvdg
|
Jenkins X - via Draft - automatically detected this application is being build with Gradle
.
As you can see in the highlighted section in the code snippet above.
Confirm the application is building¶
The application will fail to build, as the default Dockerfile
is not correct.
Use the below code to replace the Dockerfile.
1 2 3 4 | FROM openjdk:8u171-alpine3.7 RUN apk --no-cache add curl COPY build/libs/*-all.jar complete.jar CMD java ${JAVA_OPTS} -jar complete.jar |
Commit your change and watch the activity.
1 2 3 4 | git add Dockerfile
git commit -m "fix Dockerfile"
git push
jx get activity -f jx-micronaut-seed -w
|
Once the Promote: staging
is completed successfully, we should be able to test if the application is running!
1 2 | APP_ADDR=$(kubectl get ing -n jx-staging jx-micronaut-seed -o jsonpath="{.spec.rules[0].host}") curl "http://$APP_ADDR" |
You should see something like this:
1 2 3 4 5 6 7 | <html> <head><title>503 Service Temporarily Unavailable</title></head> <body> <center><h1>503 Service Temporarily Unavailable</h1></center> <hr><center>nginx/1.15.8</center> </body> </html> |
Something is wrong...
Find out what is wrong¶
To find out what is wrong, lets check the pod status.
1 | kubectl get pods -n jx-staging -l app=jx-jx-micronaut-seed |
We should see something as follows.
1 2 | NAME READY STATUS RESTARTS AGE jx-jx-micronaut-seed-68f4bffb7b-mpb57 0/1 CrashLoopBackOff 41 1h |
Let's describe the pod and see what is causing the CrashLoopBackOff
.
1 | kubectl describe pods -n jx-staging -l app=jx-jx-micronaut-seed |
If we look at the Events:
section, we will see something like this:
1 2 3 4 5 6 | Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning Unhealthy 13m (x1833 over 16h) kubelet, gke-joostvdg-default-pool-6f512cf8-7l4l Readiness probe failed: HTTP probe failed with statuscode: 404 Normal Pulled 8m43s (x240 over 16h) kubelet, gke-joostvdg-default-pool-6f512cf8-7l4l Container image "10.23.250.118:5000/joostvdg/jx-micronaut-seed:0.0.3" already present on machine Warning BackOff 3m34s (x2866 over 16h) kubelet, gke-joostvdg-default-pool-6f512cf8-7l4l Back-off restarting failed container |
One of the things you can spot, is Readiness probe failed: HTTP probe failed with statuscode: 404
.
Assuming our application is flawless - it passed it build & test phase - it's likely that Kubernetes is looking at a non-existing endpoint for a health check.
Fix Health Check¶
As good as Jenkins X is, it isn't clairvoyant and cannot detect that our Micronaut application has a different health check endpoint.
You might not know what the Micronaut framework gives you, but I can tell you. The health check endpoint is located at /health
, not a bad place to put it.
As the Gradle BuildPack is designed with Spring Boot in mind, it directs Kubernetes health check to /actuator/health
.
So we have to change this.
Our application is packaged by Helm and the values for our Kubernetes Deployment - where the health check is configured - are located in /charts/jx-micronaut-seed/values.yaml
.
We have to change the value of probePath
, from /actuator/health
to /health
.
So please edit /charts/jx-micronaut-seed/values.yaml
to reflect the change or use the below yq
command.
This should be the end result:
1 2 3 4 5 6 7 8 9 10 11 12 13 | ... cpu: 500m memory: 512Mi requests: cpu: 400m memory: 512Mi probePath: /health livenessProbe: initialDelaySeconds: 60 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 ... |
1 | yq w charts/jx-micronaut-seed/values.yaml --inplace probePath /health |
Now commit and push our change to fix our deployment!
1 2 3 4 | git add charts/jx-micronaut-seed/values.yaml
git commit -m "fix health check endpoint"
git push
jx get activity -f jx-micronaut-seed -w
|
Once the applications is successfully promoted to staging, we can try again!
1 | kubectl get pods -n jx-staging -l app=jx-jx-micronaut-seed |
Oh no, the application is still not running!
1 2 | NAME READY STATUS RESTARTS AGE jx-jx-micronaut-seed-d5498679f-55b84 0/1 Running 1 2m |
Still broken¶
Let's describe the pod and see what is wrong this time.
1 | kubectl describe pods -n jx-staging -l app=jx-jx-micronaut-seed |
1 2 3 | Warning Unhealthy 94s (x2 over 3m14s) kubelet, gke-joostvdg-default-pool-6f512cf8-41l4 Readiness probe failed: Get http://10.20.0.24:8080/health: dial tcp 10.20.0.24:8080: connect: connection refused Warning Unhealthy 83s (x2 over 3m3s) kubelet, gke-joostvdg-default-pool-6f512cf8-41l4 Readiness probe failed: Get http://10.20.0.24:8080/health: net/http: request canceled (Client.Timeout exceeded while awaiting headers) Warning Unhealthy 54s (x11 over 2m54s) kubelet, gke-joostvdg-default-pool-6f512cf8-41l4 Readiness probe failed: HTTP probe failed with statuscode: 500 |
It seems our application is not getting into ready state: Readiness probe failed: HTTP probe failed with statuscode: 500
.
Now this is a bit of cheat, because this application actually requires a connection with a Redis database in order to function. It can be build without it fine, and it will run fine, but Micronaut's health check endpoint will incorporate the Redis connection into it's health status.
Configure Redis database¶
This means we must make sure our application can talk to a Redis database!
Add Redis dependency¶
The easiest way to do this with Jenkins X, is to add a dependency to our Helm Chart. If our dependency exists as a health chart, that is.
Just our luck, looking at Helm Stable Charts, there's a Redis chart we can add.
To do so, we add a requirements.yaml
to our Chart.
Create a file charts/jx-micronaut-seed/requirements.yaml
and fill in the below details.
1 2 3 4 5 | dependencies: - alias: jx-micronaut-seed-redis name: redis repository: https://kubernetes-charts.storage.googleapis.com version: 6.1.0 |
1 2 3 4 5 6 | echo "dependencies: - alias: jx-micronaut-seed-redis name: redis repository: https://kubernetes-charts.storage.googleapis.com version: 6.1.0 " | tee charts/jx-micronaut-seed/requirements.yaml |
Application Redis config¶
To be safe that whenever our application gets deployed via Helm it can find our database, we need to make sure the location it looks for is a variable.
We can add this in three places, either in the application itself, in our values.yaml
or in our deployment.yaml
template.
Our application will get deployed via Helm, which means the name it gets and the Redis dependency gets, will depend on the Helm release name.
So in this particular case, it's best to add an environment variable to the deployment template. With a default value, that derives its value from the Helm release name.
This makes the default install from Helm work and allows users of our Helm chart, to use a different Redis instance.
To do so, we have to add the environment variable in charts/jx-micronaut-seed/templates/deployment.yaml
.
Add the below snippet to the spec.template.spec.containers[0]
section between imagePullPolicy: {{ .Values.image.pullPolicy }}
and ports:
.
1 2 3 | env: - name: REDIS_HOST value: {{ template "fullname" . }}-redis-master |
1 2 3 4 5 | cat charts/jx-micronaut-seed/templates/deployment.yaml | sed -e \ 's@env:@env:\ - name: REDIS_HOST\ value: {{ template "fullname" . }}-redis-master@g' \ | tee charts/jx-micronaut-seed/templates/deployment.yaml |
The end result should look like this:
1 2 3 4 5 6 7 8 9 | spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} env: - name: REDIS_HOST value: {{ template "fullname" . }}-redis-master ports: |
Redis Chart config¶
We need to do one last thing. The Redis chart by default generates a unique password on startup.
This is nice and secure, but makes it difficult for our application to connect to it. Let's configure our Redis chart to not use a password for now.
Add the below snippet at the bottom of charts/jx-micronaut-seed/values.yaml
or use the command line magic for automation.
1 2 | jx-micronaut-seed-redis: usePassword: false |
1 2 3 | echo "jx-micronaut-seed-redis: usePassword: false " | tee -a charts/jx-micronaut-seed/values.yaml |
Commit and confirm¶
Let's commit and push our changes and see if this was enough!
1 2 3 4 5 6 | git add charts/jx-micronaut-seed/templates/deployment.yaml
git add charts/jx-micronaut-seed/values.yaml
git add charts/jx-micronaut-seed/requirements.yaml
git commit -m "add and configure redis dependency"
git push
jx get activity -f jx-micronaut-seed -w
|
Confirm it works¶
Once it succeeds, we can see if the applications does run now.
1 | kubectl get pods -n jx-staging -l app=jx-jx-micronaut-seed |
It should now be running:
1 2 | NAME READY STATUS RESTARTS AGE jx-jx-micronaut-seed-75ffd4fbc4-66sgr 1/1 Running 0 9m |
Let's see if we can talk to it.
1 2 | APP_ADDR=$(kubectl get ing -n jx-staging jx-micronaut-seed -o jsonpath="{.spec.rules[0].host}") curl "http://$APP_ADDR/health" |
1 2 | APP_ADDR=$(kubectl get ing -n jx-staging jx-micronaut-seed -o jsonpath="{.spec.rules[0].host}") http "$APP_ADDR/health" |
The answer is:
1 2 3 | { "status": "UP" } |
We've done it!
Now lets use the Redis database in a wholefully inappropriate way.
1 2 3 4 5 | APP_ADDR=$(kubectl get ing -n jx-staging jx-micronaut-seed -o jsonpath="{.spec.rules[0].host}") curl --header "Content-Type: application/json" \ --request POST --data '{"body":"Something curl","sender":"Joost"}' \ "http://$APP_ADDR/message" curl "http://$APP_ADDR/message" |
1 2 3 | APP_ADDR=$(kubectl get ing -n jx-staging jx-micronaut-seed -o jsonpath="{.spec.rules[0].host}") http POST ${APP_ADDR}/message body="Something httpie" sender="Joost" http ${APP_ADDR}/message |
Now, in order to avoid having to this kind of ritual for every Micronaut based application, we should probably make a better starting point. Let's move on to create a BuildPack
Info
For more information on Jenkins X's jx import
command,
please consult the documentation at jenkins-x.io