recursive.codes

recursive.codes

recursive.codes


The Personal Blog of Todd Sharp

Building And Deploying A Helidon Microservice With Hibernate Part 3

Posted By: Todd Sharp on 7/9/2019 12:00 GMT
Tagged: Cloud, Containers, Microservices, APIs, Developers, Java

We've taken quite a journey in this blog series so far - from getting our cloud tenancy ready for our microservice deployment, to writing our first microservice application using Helidon. In this post we'll finally get our microservice deployed to the Oracle Cloud, but if you're new to the series then you might want to catch up on where we've been so far.

The first step in deploying our microservice to the Oracle Cloud is to build a Docker image containing our application and push that image to our OCIR Docker Registry that we configured previously. When we scaffolded the Helidon application from the Maven archetype you may or may not have noticed, but Helidon gave us a Dockerfile and app.yaml out of the box that looked similar to this:

# 1st stage, build the app
FROM maven:3.5.4-jdk-9 as build
WORKDIR /helidon
# Create a first layer to cache the "Maven World" in the local repository.
# Incremental docker builds will always resume after that, unless you update
# the pom
ADD pom.xml .
RUN mvn package -DskipTests
# Do the Maven build!
# Incremental docker builds will resume here when you change sources
ADD src src
RUN mvn package -DskipTests
RUN echo "done!"
# 2nd stage, build the runtime image
FROM openjdk:8-jre-slim
WORKDIR /helidon
# Copy the binary built in the 1st stage
COPY --from=build /helidon/target/user-svc.jar ./
COPY --from=build /helidon/target/libs ./libs
CMD ["java", "-jar", "user-svc.jar"]
EXPOSE 8080
view raw Dockerfile hosted with ❤ by GitHub

If we had made no changes to the application we could have used that Dockerfile to build and push an image, but since we've made some changes we'll need to modify it a bit to work with our application. We're going to add and install the OJDBC dependencies locally so they can be included in our build. The other modification we'll need to make is to ensure that our wallet files make it into the container (alternatively we could mount these as a volume, but since this is more of a "beginner" series I want to keep things as simple as possible) and modify the CMD so that our configuration values get set properly when we launch the JAR.

The modified Dockerfile looks like this:

# 1st stage, build the app
FROM maven:3.5.4-jdk-9 as build
WORKDIR /helidon
ENV MAVEN_OPTS -Dhttp.proxyHost= -Dhttp.proxyPort= -Dhttps.proxyHost= -Dhttps.proxyPort= -Dhttp.nonProxyHosts= -Dmaven.repo.local=/usr/share/maven/ref/repository
ADD pom.xml .
ADD build-resource/libs/* /helidon/build-resource/libs/
RUN ["mvn", "install:install-file", "-Dfile=/helidon/build-resource/libs/ojdbc8.jar", "-DgroupId=com.oracle.jdbc", "-DartifactId=ojdbc8", "-Dversion=18.3.0.0", "-Dpackaging=jar"]
RUN ["mvn", "install:install-file", "-Dfile=/helidon/build-resource/libs/oraclepki.jar", "-DgroupId=com.oracle.jdbc", "-DartifactId=oraclepki", "-Dversion=18.3.0.0", "-Dpackaging=jar"]
RUN ["mvn", "install:install-file", "-Dfile=/helidon/build-resource/libs/osdt_core.jar", "-DgroupId=com.oracle.jdbc", "-DartifactId=osdt_core", "-Dversion=18.3.0.0", "-Dpackaging=jar"]
RUN ["mvn", "install:install-file", "-Dfile=/helidon/build-resource/libs/osdt_cert.jar", "-DgroupId=com.oracle.jdbc", "-DartifactId=osdt_cert", "-Dversion=18.3.0.0", "-Dpackaging=jar"]
ADD src src
RUN mvn package -DskipTests
# 2nd stage, build the runtime image
FROM openjdk:8-jre-slim
WORKDIR /helidon
# Copy the binary built in the 1st stage
COPY --from=build /helidon/target/user-svc.jar ./
COPY --from=build /helidon/target/libs ./libs
RUN mkdir wallet
COPY /build-resource/wallet/* ./wallet/
EXPOSE 8080
CMD ["sh", "-c", "java -jar -Ddatasource.username=$DB_USER -Ddatasource.password=$DB_PASSWORD -Ddatasource.url=$DB_URL -Doracle.net.wallet_location=/helidon/wallet -Doracle.net.authentication_services=\"(TCPS)\" -Doracle.net.tns_admin=/helidon/wallet -Djavax.net.ssl.trustStore=/helidon/wallet/cwallet.sso -Djavax.net.ssl.trustStoreType=SSO -Djavax.net.ssl.keyStore=/helidon/wallet/cwallet.sso -Djavax.net.ssl.keyStoreType=SSO -Doracle.net.ssl_server_dn_match=true -Doracle.net.ssl_version=1.2 user-svc.jar"]
view raw Dockerfile hosted with ❤ by GitHub

You can now build the image with:

docker build -t phx.ocir.io/toddrsharp/cloud-native-microservice/user-svc-helidon:latest .

This command tags the newly built image with the proper format for pushing to our OCIR registry. Note that I'm using the Phoenix region - if you're using a different region then your URL will change. Remember that the OCIR URL format works like so:

[region].ocir.io/[tenancy]/[repository-name]/[docker-image-name]:[tag]

We can run this image locally with:

docker run \                                                                                                                                                                      
--env DB_USER=[username] \
--env DB_URL="jdbc:oracle:thin:@[tnsname]?TNS_ADMIN=/helidon/wallet" \
--env DB_PASSWORD=[password] \
-p 8080:8080 \
-t phx.ocir.io/toddrsharp/cloud-native-microservice/user-svc-helidon:latest
view raw docker-run.sh hosted with ❤ by GitHub

The app is now up and running in a local Docker container just as it did when we ran the JAR file locally in the last post. Stop the local container and let's move on to pushing the image to our OCIR registry. That's pretty simple too:

docker push phx.ocir.io/toddrsharp/cloud-native-microservice/user-svc-helidon:latest 

We can confirm the image in the OCIR dashboard:

Let's move on to deploying the Docker image in our Kubernetes cluster. We'll need two Kubernetes secrets created for our deployment. One for our Docker registry credentials so that Kubernetes can authenticate when pulling the image, and another that contains our application credentials so that we can pass them into our Docker container when the app is deployed instead of hard coding them in the Dockerfile.

Create the registry credentials secret like so (the password in this secret is not Base64 encoded):

kubectl create secret docker-registry regcred \
--docker-server=[region].ocir.io \
--docker-username=[tenancy]/[docker user] \
--docker-password="[docker auth token]" \
--docker-email=[docker email (if set when creating user)]

Now for the application secrets, create a filed called secret.yaml and populate it as follows (using your own Base64 encoded values):

apiVersion: v1
kind: Secret
metadata:
  name: user-svc-helidon-secrets
data:
  dbUser: [base64 encoded username]
  dbPassword: [base64 encoded password]
  dbUrl: [base64 encoded URL]
---
view raw secret.yaml hosted with ❤ by GitHub

To create the secret in the cluster, run:

kubectl create -f secret.yaml

We can now modify the app.yaml file to complete our Kubernetes deployment. We'll modify a few things from the generated file, but it's a pretty simple format. Note that we need to override the Docker container's CMD with our own here to make sure that the values from our secret are properly used when starting the container. Otherwise, what we're doing here is creating a Kubernetes service to expose our service on the cluster and a deployment that represents our Docker container. We tell Kubernetes the location where the image is (in OCIR), point the config to our secrets and put in some placeholders that will be replaced with the secret values when the image is deployed.

Here's my modified app.yaml:

kind: Service
apiVersion: v1
metadata:
  name: user-svc-helidon
  labels:
    app: user-svc-helidon
spec:
  type: LoadBalancer
  selector:
    app: user-svc-helidon
  ports:
  - port: 8080
    targetPort: 8080
    name: http
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: user-svc-helidon
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: user-svc-helidon
        version: v1
    spec:
      containers:
      - name: user-svc-helidon
        image: phx.ocir.io/toddrsharp/cloud-native-microservice/user-svc-helidon:latest
        command: ["java", "-jar"]
        args:
        - "-Doracle.net.wallet_location=/helidon/wallet"
        - "-Doracle.net.authentication_services=(TCPS)"
        - "-Doracle.net.tns_admin=/helidon/wallet"
        - "-Djavax.net.ssl.trustStore=/helidon/wallet/cwallet.sso"
        - "-Djavax.net.ssl.trustStoreType=SSO"
        - "-Djavax.net.ssl.keyStore=/helidon/wallet/cwallet.sso"
        - "-Djavax.net.ssl.keyStoreType=SSO"
        - "-Doracle.net.ssl_server_dn_match=true"
        - "-Doracle.net.ssl_version=1.2"
        - "-Ddatasource.username=$(DB_USER)"
        - "-Ddatasource.password=$(DB_PASSWORD)"
        - "-Ddatasource.url=$(DB_URL)"
        - "user-svc.jar"
        env:
          - name: DB_USER
            valueFrom:
              secretKeyRef:
                name: user-svc-helidon-secrets
                key: dbUser
          - name: DB_PASSWORD
            valueFrom:
              secretKeyRef:
                name: user-svc-helidon-secrets
                key: dbPassword
          - name: DB_URL
            valueFrom:
              secretKeyRef:
                name: user-svc-helidon-secrets
                key: dbUrl
        imagePullPolicy: Always
        ports:
          - containerPort: 8080
      imagePullSecrets:
      - name: regcred
---
view raw app.yaml hosted with ❤ by GitHub

Which we can deploy with:

kubectl create -f app.yaml

And verify the deployment with:

kubectl get deployments

Verify the pod was created:

kubectl get pods

Finally, verify the service was created and assigned an IP address (this will take a minute or two to complete for new services):

kubectl get services

Let's try making a request to see if everything works:

Unfortunately, we've got an error! To figure out what's going on let's take a look at the pod logs:

kubectl logs user-svc-helidon-69688b4fd6-l5gqt 

It looks like our code used a Java 9 feature, but we deployed the application in a Docker image based on Java 8. It's a simple fix, just modify the Dockerfile to use the openjdk:9-jre-slim runtime image, rebuild the Docker image and push it again. To redeploy our app we do not have to create the deployment again, simply delete the existing pod and a new pod will be pulled using the latest image from OCIR:

kubectl delete pod user-svc-helidon-69688b4fd6-l5gqt

Now if we try our request again, everything should be good to go!

And listing users should give us a proper response (assuming we've created users in the DB already) confirming that our connection to ATP is solid:

And that's it!  In this series, we've prepared our Oracle Cloud tenancy for microservices and written and deployed our first service. In future posts I'm going to examine some other interesting options for creating microservices to make life a little easier on the code side of things.

Photo by photo-nic.co.uk nic on Unsplash


Share Facebook Twitter Pinterest LinkedIn Messenger WhatsApp Viber Telegram Tumblr Reddit Pocket Email SMS

Related Posts

Note: Comments are currently closed on this blog. Disqus is simply too bloated to justify its use with the low volume of comments on this blog. Please visit my contact page if you have something to say!