{"id":1005,"date":"2024-09-25T03:51:00","date_gmt":"2024-09-25T03:51:00","guid":{"rendered":"https:\/\/blog.ngocha.biz\/?p=1005"},"modified":"2024-09-25T03:51:00","modified_gmt":"2024-09-25T03:51:00","slug":"deploy-java-app-kubernetes","status":"publish","type":"post","link":"https:\/\/blog.ngocha.biz\/?p=1005","title":{"rendered":"How to Deploy Java App With MySQL on Kubernetes"},"content":{"rendered":"<p>In this blog, we will look at the steps to build and deploy a Java application with the MySQL database on the Kubernetes cluster.<\/p><h2 id=\"prerequisites\">Prerequisites<\/h2><p>The following are prerequisites to follow this guide.<\/p><ol><li><a href=\"https:\/\/devopscube.com\/setup-kubernetes-cluster-kubeadm\/\">Kubernetes Cluster<\/a><\/li><li>kubectl<\/li><li><a href=\"https:\/\/devopscube.com\/install-maven-guide\/\">Maven <\/a>(3.9.6) and Java-17 installed in your system<\/li><li><a href=\"https:\/\/devopscube.com\/how-to-install-and-configure-docker\/\">Install Docker<\/a><\/li><li>You should have access to Docker Hub repo from your system to push the docker image.<\/li><li>MySQL client (Optional) &#8211; If you will log in to your MySQL database through CLI.<\/li><\/ol><h2 id=\"setup-architecture\">Setup Architecture<\/h2><p>The workflow of this setup is given in the below diagram.<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/java-applicationmysql-1.gif\" class=\"kg-image\" alt=\"Java App With MySQL on Kubernetes\" loading=\"lazy\" width=\"774\" height=\"969\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/java-applicationmysql-1.gif 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/java-applicationmysql-1.gif 774w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><p>You will practically use the following key Kubernetes objects. It will help you understand how these objects can be used in real-world project implementations:<\/p><ol><li>Deployment<\/li><li>HPA<\/li><li>ConfigMap<\/li><li>Secrets<\/li><li>StatefulSet<\/li><li>Service<\/li><li>Namespace<\/li><\/ol><p>It also covers key concepts such as:<\/p><ol><li>Creating an application properties file from ConfigMap to be consumed by the app<\/li><li>Startup, readiness, and liveness probes<\/li><li>Consuming secret and configmap data using Environment Variables<\/li><li>MySQL initialization script from ConfigMap to create tables<\/li><\/ol><p>Lets get started with the setup.<\/p><h2 id=\"build-and-create-java-application-docker-image\">Build and Create Java Application Docker Image<\/h2><blockquote><strong>Note: <\/strong>You can skip this section if you only want to try the Kubernetes deployment. Just use the image provided in the guide.<\/blockquote><p>Given below is the step-by-step guide to building and deploying Java Apps with MySQL on Kubernetes.<\/p><p>Clone the Git repository below, which contains the source code to build the application. We will using the publicly available pet clinic spring boot app for this setup.<\/p><pre><code>https:\/\/github.com\/spring-projects\/spring-petclinic.git<\/code><\/pre><h3 id=\"step-1-build-the-java-application\">Step 1: Build the Java Application<\/h3><p>Now, CD into the <strong>spring-petclinic<\/strong> directory and run the below command to <a href=\"https:\/\/devopscube.com\/build-java-application-using-maven\/\">build the application using maven.<\/a><\/p><pre><code>mvn clean install<\/code><\/pre><p>If you want to skip the test during the build, use the <strong><code>-DskipTests<\/code><\/strong> flag .<\/p><p>After the build is finished, you can find the application JAR file inside the <strong>spring-petclinic\/target<\/strong> folder<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-149-4.png\" class=\"kg-image\" alt=\"Jar file location\" loading=\"lazy\" width=\"918\" height=\"710\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-149-4.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-149-4.png 918w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><h3 id=\"step-2-build-a-docker-image-of-the-application\">Step 2: Build a Docker Image of the Application<\/h3><p>To <a href=\"https:\/\/devopscube.com\/build-docker-image\/\">build the docker image<\/a> of the application, create a Dockerfile using the below content inside the <strong>spring-petclinic<\/strong> directory.<\/p><pre><code>FROM techiescamp\/jre-17:1.0.0\nCOPY \/target\/*.jar \/app\/java.jar\nEXPOSE 8080<\/code><\/pre><p>This dockerfile uses <strong><code>techiescamp\/jre-17:1.0.0<\/code><\/strong> as the base image which has JRE installed in it and it copies the JAR file from the target folder and pastes it inside the docker image inside the <strong>\/app<\/strong> folder as <strong>java.jar<\/strong>.<\/p><p>It also exposes port 8080, because the Java application runs on port 8080.<\/p><p>Run the below command from the same directory where the <a href=\"https:\/\/devopscube.com\/create-dockerfile-using-docker-init\/\">Dockerfile<\/a> is present to build the docker image<\/p><pre><code>docker build -t kube-petclinic-app:3.0.0 .<\/code><\/pre><p><strong>kube-petclinic-app:3.0.0<\/strong> is the name and tag I have given to my docker image.<\/p><p>Run the <code>docker images<\/code> command to check if your docker image has been created successfully.<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-258-1.png\" class=\"kg-image\" alt=\"Checking the available docker images on the system\" loading=\"lazy\" width=\"1208\" height=\"422\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-258-1.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-258-1.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-258-1.png 1208w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><h3 id=\"step-3-push-the-image-to-dockerhub\">Step 3: Push the image to DockerHub<\/h3><p>Before pushing the image to the DockerHub repository, you have to tag the image that you want to push, use the below to tag the image<\/p><pre><code>docker tag kube-petclinic-app:3.0.0 techiescamp\/kube-petclinic-app:3.0.0<\/code><\/pre><p>In this command <strong>kube-petclinic-app:3.0.0<\/strong> is the name of the image I build and <strong>techiescamp\/kube-petclinic-app:3.0.0<\/strong> is my DockerHub repository and image tag.<\/p><p>Now, push the image to DockerHub using the command. Before proceeding, ensure you have <strong>logged in to docker hub<\/strong> from the CLI.<\/p><pre><code>docker push techiescamp\/kube-petclinic-app:3.0.0<\/code><\/pre><p>Check if your image is pushed into DockerHub as shown below<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-257-1.png\" class=\"kg-image\" alt=\"Viewing the docker image pushed to a registry\" loading=\"lazy\" width=\"1432\" height=\"780\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-257-1.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-257-1.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-257-1.png 1432w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><h2 id=\"deploy-java-mysql-on-kubernetes\">Deploy Java &amp; MySQL On Kubernetes<\/h2><p>Clone the Git repository given below which contains all the YAML manifest we used in this guide under <strong>03-java-app-deployment<\/strong> folder.<\/p><pre><code>git clone https:\/\/github.com\/techiescamp\/kubernetes-projects.git<\/code><\/pre><p>In the <strong>03-java-app-deployment<\/strong> folder, you can see the YAML manifests in the below structure.<\/p><pre><code>.\n\u251c\u2500\u2500 java-app\n\u2502   \u251c\u2500\u2500 configmap.yml\n\u2502   \u251c\u2500\u2500 hpa.yml\n\u2502   \u2514\u2500\u2500 java.yml\n\u251c\u2500\u2500 mysql\n\u2502   \u251c\u2500\u2500 configmap.yml\n\u2502   \u2514\u2500\u2500 mysql.yml\n\u2514\u2500\u2500 secret.yml<\/code><\/pre><p>Given below are the steps to deploy Java and MySQL on Kubernetes<\/p><h3 id=\"step-1-create-namespaces\">Step 1: Create Namespaces<\/h3><p>We are going to deploy the app and MySQL on two different namespaces so create two new namespaces <strong>pet-clinic-app<\/strong> and <strong>pet-clinic-db<\/strong>.<\/p><p>Run the following commands to create both namespace<\/p><pre><code>kubectl create ns pet-clinic-app\n\nkubectl create ns pet-clinic-db<\/code><\/pre><h3 id=\"step-2-create-secrets\">Step 2: Create Secrets<\/h3><p>CD into the <strong>03-java-app-deployment <\/strong>directory and run the <strong>secret.yml<\/strong> in both <strong>pet-clinic-app<\/strong> and <strong>pet-clinic-db<\/strong> namespaces because the secret contains the MySQL login credentials which are needed in both MySQL and app deployment process.<\/p><pre><code>apiVersion: v1\nkind: Secret\nmetadata:\n  name: mysql-cred\n  namespace: pet-clinic-app\ntype: Opaque\ndata:\n  username: Y3J1bmNob3Bz\n  password: Y3J1bmNob3BzQDEyMzQ=<\/code><\/pre><p>Just change the namespace and run the <strong>secret.yml<\/strong> file two times to create a secret in both <strong>pet-clinic-app<\/strong> and <strong>pet-clinic-db<\/strong> namespaces.<\/p><pre><code>data:\n  username: Y3J1bmNob3Bz\n  password: Y3J1bmNob3BzQDEyMzQ=<\/code><\/pre><p>You can see the data in the above block is given in base64-encoded values because the secret won&#8217;t be created unless you specify your username and password in <strong>base64-encoded<\/strong> values.<\/p><p>You can use the <strong>echo<\/strong> command with <strong>base64<\/strong> to encode your data, the echo command will give the encoded values of your data. The command to encode your data is given below<\/p><pre><code>echo -n '&lt;data&gt;' | base64<\/code><\/pre><p>For example, I set my <strong>username<\/strong> as <strong>crunchops<\/strong>, then the echo command will be<\/p><pre><code>echo -n 'crunchops' | base64<\/code><\/pre><p>This command will give the encoded values of my data.<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-269.png\" class=\"kg-image\" alt=\"Encoding MySQL username and password\" loading=\"lazy\" width=\"790\" height=\"444\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-269.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-269.png 790w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><p>Now, create the secret in both namespaces using the following command.<\/p><pre><code>kubectl apply -f secret<\/code><\/pre><p>Run the following command to verify the secret is created in both namespace<\/p><pre><code>kubectl get secret -n pet-clinic-app\n\nkubectl get secret -n pet-clinic-db<\/code><\/pre><p>You will get the following output<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-259.png\" class=\"kg-image\" alt=\"verifying if the secrets are created in both namespace\" loading=\"lazy\" width=\"924\" height=\"612\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-259.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-259.png 924w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><h3 id=\"step-3-create-configmap-for-mysql\">Step 3: Create ConfigMap for MySQL<\/h3><p>CD into the <strong>03-java-app-deployment\/mysql <\/strong>directory and run the <strong>configmap.yml<\/strong>.<\/p><pre><code>apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: mysql-configmap\n  namespace: pet-clinic-db\ndata:\n  init.sql: |\n    CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD';\n    GRANT ALL PRIVILEGES ON petclinic.* TO '$MYSQL_USER'@'%';\n    FLUSH PRIVILEGES;\n\n    USE petclinic;\n\n    CREATE TABLE IF NOT EXISTS vets (\n      id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,\n      first_name VARCHAR(30),\n      last_name VARCHAR(30),\n      INDEX(last_name)\n    ) engine=InnoDB;\n\n....... View Complete File content on the mysql\/configmap.yml file ......<\/code><\/pre><p>This creates a configmap <strong>mysql-configmap<\/strong> on the <strong>pet-clinic-db<\/strong> namespace, that contains the SQL command to create a user, password, and tables on the database, and to insert data on the table.<\/p><p>It gets the username and password from env variables, which are retrieved from the secret.<\/p><p>Run the below command to create a configmap.<\/p><pre><code>kubectl apply -f configmap.yml<\/code><\/pre><p>Now, run the following command to verify if the ConfigMap is created<\/p><pre><code>kubectl get cm -n pet-clinic-db<\/code><\/pre><p>You will get the following output<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-262-1.png\" class=\"kg-image\" alt=\"verifying if the configmap is created\" loading=\"lazy\" width=\"890\" height=\"378\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-262-1.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-262-1.png 890w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><h3 id=\"step-4-deploy-mysql-database\">Step 4: Deploy MySQL Database<\/h3><p>CD into the <strong>03-java-app-deployment\/mysql <\/strong>directory and run the <strong>mysql.yml<\/strong>.<\/p><pre><code>apiVersion: apps\/v1\nkind: StatefulSet\nmetadata:\n  name: mysql\n  namespace: pet-clinic-db\nspec:\n  serviceName: \"mysql\"\n  replicas: 1\n  selector:\n    matchLabels:\n      app: mysql\n  template:\n    metadata:\n      labels:\n        app: mysql\n    spec:\n      containers:\n      - name: mysql\n        image: mysql:latest\n        env:\n        - name: MYSQL_RANDOM_ROOT_PASSWORD\n          value: \"yes\"\n        - name: MYSQL_USER\n          valueFrom:\n            secretKeyRef:\n              name: mysql-cred\n              key: username\n        - name: MYSQL_PASSWORD\n          valueFrom:\n            secretKeyRef:\n              name: mysql-cred\n              key: password\n        - name: MYSQL_DATABASE\n          value: petclinic\n        volumeMounts:\n        - name: mysql-config\n          mountPath: \/docker-entrypoint-initdb.d\n        resources:\n            requests:\n              memory: \"256Mi\"\n              cpu: \"100m\"\n            limits:\n              memory: \"512Mi\"\n              cpu: \"200m\"\n      volumes:\n      - name: mysql-config\n        configMap:\n          name: mysql-configmap\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: mysql-service\n  namespace: pet-clinic-db\nspec:\n  type: NodePort\n  selector:\n    app: mysql\n  ports:\n    - protocol: TCP\n      port: 3306\n      targetPort: 3306\n      nodePort: 30244<\/code><\/pre><p>This file deploys a <strong>MySQL<\/strong> statefulset database on the <strong>pet-clinic-db<\/strong> namespace and also a nodeport service <strong>mysql-service<\/strong> on the <strong>pet-clinic-db<\/strong> namespace so we can log in to MySQL from outside the cluster.<\/p><p>It gets the database username and password from the secret you created before.<\/p><p>The <strong><code>docker-entrypoint-initdb.d<\/code><\/strong> directory is part of the MySQL image. Any MySQL scripts placed in this folder will be executed during MySQL startup. It is primarily used for running database initialization scripts.<\/p><p>In our setup, we placed an init script (<strong><code>init.sql<\/code><\/strong>) in this folder through a ConfigMap (<strong>mysql-configmap<\/strong>). <strong><code>init.sql <\/code><\/strong>creates the username, password, and tables for our application. The script retrieves the username and password from environment variables set by the secret object using <strong><code>secretKeyRef<\/code><\/strong><\/p><p>Run the below command to deploy the MySQL database.<\/p><pre><code>kubectl apply -f mysql.yml<\/code><\/pre><p>Now, run the following command to check if the MySQL StatefulSet has been created.<\/p><pre><code>kubectl get sts -n pet-clinic-db<\/code><\/pre><p>You will get the following output<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-263-1.png\" class=\"kg-image\" alt=\"verifying if the mysql statefulset has been created\" loading=\"lazy\" width=\"752\" height=\"378\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-263-1.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-263-1.png 752w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><h3 id=\"step-5-create-configmap-for-java-application\">Step 5: Create ConfigMap for Java Application<\/h3><p>CD into the <strong>03-java-app-deployment\/java-app<\/strong> folder and run the <strong>configmap.yml<\/strong>.<\/p><pre><code>apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: java-app-config\n  namespace: pet-clinic-app\ndata:\n  application.properties: |\n    # database init, supports mysql too\n    database=mysql\n    spring.datasource.url=jdbc:mysql:\/\/mysql-service.pet-clinic-db.svc.cluster.local:3306\/petclinic\n    spring.datasource.username=${DB_USERNAME}\n    spring.datasource.password=${DB_PASSWORD}\n    # SQL is written to be idempotent so this is safe\n    spring.sql.init.mode=always\n\n    # Web\n    spring.thymeleaf.mode=HTML\n\n    # JPA\n    spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect\n    spring.jpa.open-in-view=true\n\n    # Internationalization\n    spring.messages.basename=messages\/messages\n\n    # Actuator\n    management.endpoints.web.exposure.include=*\n    # Logging\n    #logging.config=classpath:logback-spring.xml\n    logging.level.org.springframework=INFO\n    # logging.level.org.springframework.web=DEBUG\n    # logging.level.org.springframework.context.annotation=TRACE\n\n    # Maximum time static resources should be cached\n    spring.web.resources.cache.cachecontrol.max-age=12h<\/code><\/pre><p>Run the below command to create a configmap.<\/p><pre><code>kubectl apply -f configmap.yml<\/code><\/pre><p>This creates a configmap <strong>java-app-config<\/strong> on the <strong>pet-clinic-app<\/strong> namespace, which contains content of the application.properties file which helps the application to connect with the MySQL database.<\/p><p>The username and password are given as a variable so that it can get the username and password from the env variables, which are retrieved from the secret.<\/p><p>Run the following command to check if the configmap is created<\/p><pre><code>kubectl get cm -n pet-clinic-app<\/code><\/pre><p>You will get the following output<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-264-1.png\" class=\"kg-image\" alt=\"verifying if the configmap is created\" loading=\"lazy\" width=\"764\" height=\"386\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-264-1.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-264-1.png 764w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><h3 id=\"step-6-deploy-java-application\">Step 6: Deploy Java Application<\/h3><pre><code>apiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: java-app\n  namespace: pet-clinic-app\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: java-app\n  template:\n    metadata:\n      labels:\n        app: java-app\n    spec:\n      containers:\n      - name: java-app-container\n        image: techiescamp\/kube-petclinic-app:3.0.0\n        env:\n            - name: DB_USERNAME\n              valueFrom:\n                  secretKeyRef:\n                    name: mysql-cred\n                    key: username\n            - name: DB_PASSWORD\n              valueFrom:\n                  secretKeyRef:\n                    name: mysql-cred\n                    key: password\n        resources:\n            requests:\n              memory: \"256Mi\"\n              cpu: \"100m\"\n            limits:\n              memory: \"512Mi\"\n              cpu: \"200m\"\n        ports:\n        - containerPort: 8080\n        volumeMounts:\n        - name: java-app-config\n          mountPath: \"\/opt\/config\"\n        command: [\"java\", \"-jar\", \"\/app\/java.jar\", \"--spring.config.location=\/opt\/config\/application.properties\", \"--spring.profiles.active=mysql\"]\n        startupProbe:\n          httpGet:\n            path: \/actuator\/health\n            port: 8080\n          failureThreshold: 30\n          periodSeconds: 20\n          initialDelaySeconds: 180\n        readinessProbe:\n          httpGet:\n            path: \/actuator\/health\/readiness\n            port: 8080\n          periodSeconds: 10\n          failureThreshold: 3\n          timeoutSeconds: 10\n        livenessProbe:\n          httpGet:\n            path: \/actuator\/health\/liveness\n            port: 8080\n          periodSeconds: 15\n          failureThreshold: 3\n          timeoutSeconds: 10\n      volumes:\n      - name: java-app-config\n        configMap:\n          name: java-app-config\n\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: java-app-service\n  namespace: pet-clinic-app\nspec:\n  selector:\n    app: java-app\n  type: NodePort\n  ports:\n  - protocol: TCP\n    port: 8080\n    targetPort: 8080\n    nodePort: 32000<\/code><\/pre><p>This file deploys the Java application <strong>java-app<\/strong> on the <strong>pet-clinic-app<\/strong> namespace and also a nodeport service <strong>java-app-service<\/strong> on the <strong>pet-clinic-app<\/strong> namespace so we can access the application on the web browser.<\/p><p>It gets the database username and password from the secret you created before and the <strong>application.properties<\/strong> file gets the username and password from the <strong>env<\/strong> during startup.<\/p><p>The command is used to run the application <strong>JAR<\/strong> file along with the <strong>application.properties<\/strong> file so that the application can access the MySQL database.<\/p><p>Also, I have added health checks for this application:<\/p>\n<!--kg-card-begin: html-->\n<ol class=\"wp-block-list is-style-cnvs-list-styled\">\n<li><strong>startupProbe<\/strong> &#8211;&nbsp;This keeps the&nbsp;readinessProbe&nbsp;and&nbsp;livenessProbe&nbsp;on hold until the application is started and configured to check the path <strong>\/actuator\/health<\/strong>.<\/li>\n\n\n<li><strong>readinessProbe<\/strong> &#8211; This checks if the application is ready to accept traffic using the readiness path <strong>\/actuator\/health\/readiness<\/strong>. If this probe fails, it won&#8217;t receive any traffic.<\/li>\n\n\n<li><!--kg-card-begin: html--><span style=\"box-sizing: border-box; margin: 0px; padding: 0px;\"><strong>livenessProbe<\/strong>&nbsp;&#8211; Checks if the application is up and running using the liveness path <strong>\/actuator\/health\/liveness<\/strong>. If the probe fails, Kubernetes will restart the po<\/span><!--kg-card-end: html-->d.<\/li>\n<\/ol>\n<!--kg-card-end: html-->\n<p>Run the below command to deploy the Java application.<\/p><pre><code>kubectl apply -f java.yml<\/code><\/pre><p>After deploying the application, run the following command to check if the deployment pods are up and running. It takes minimum 2min for the pods to get ready.<\/p><pre><code>kubectl get deploy -n pet-clinic-app<\/code><\/pre><p>You will get the following output.<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-265-1.png\" class=\"kg-image\" alt=\"verifying if the java app deployment is up and running\" loading=\"lazy\" width=\"984\" height=\"390\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-265-1.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-265-1.png 984w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><h3 id=\"step-7-deploy-horizontalpodautoscaler-hpa\">Step 7: Deploy HorizontalPodAutoscaler (HPA)<\/h3><p>For HPA to work, you need to have a metrics server running in the cluster.<\/p><p>If you don&#8217;t have a metrics server, you can install the <strong>Metrics server<\/strong> using the following command.<\/p><pre><code>kubectl apply -f https:\/\/raw.githubusercontent.com\/techiescamp\/kubeadm-scripts\/main\/manifests\/metrics-server.yaml<\/code><\/pre><p>Now, run the top command to check if the metrics server is installed properly<\/p><pre><code>kubectl top po -n pet-clinic-app<\/code><\/pre><p>You will get the following output.<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-266-1.png\" class=\"kg-image\" alt=\"verifying if the metrics server is installed properly\" loading=\"lazy\" width=\"992\" height=\"398\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-266-1.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-266-1.png 992w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><p>Now, deploy the <strong><code>HorizontalPodAutoscaler<\/code><\/strong> in your cluster.<\/p><p>You can find the below <strong>HPA<\/strong> file inside the <strong>03-java-app-deployment\/java-app<\/strong> directory.<\/p><pre><code>apiVersion: autoscaling\/v2\nkind: HorizontalPodAutoscaler\nmetadata:\n  name: java-app-hpa\n  namespace: pet-clinic-app\nspec:\n  scaleTargetRef:\n    apiVersion: apps\/v1\n    kind: Deployment\n    name: java-app\n  minReplicas: 1\n  maxReplicas: 3\n  metrics:\n  - type: Resource\n    resource:\n      name: cpu\n      target:\n        type: Utilization\n        averageUtilization: 50<\/code><\/pre><p>This will deploy the HPA in the pet-clinic-app namespace.<\/p><p>It is configured to scale the target deployment<strong> Java app<\/strong>&nbsp;if the pod&#8217;s CPU utilization is 50% and its maximum replica count is 3.<\/p><p>Run the following command to deploy HPA<\/p><pre><code>kubectl apply -f hpa.yml<\/code><\/pre><p>Now, run the following command to check if the HPA is deployed<\/p><pre><code>kubectl get hpa -n pet-clinic-app<\/code><\/pre><p>You will get the following output<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-267-1.png\" class=\"kg-image\" alt=\"verifying if the hpa has been installed\" loading=\"lazy\" width=\"1350\" height=\"388\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-267-1.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-267-1.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-267-1.png 1350w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><p>You can see the CPU usage of the target deployment and the number of pods available.<\/p><h3 id=\"step-8-access-the-java-application\">Step 8: Access the Java Application<\/h3><p>Once the pod is up and running, check if the application is deployed properly by trying to access it on the browser, search on the browser as<strong> <code>{node-IP:nodeport}<\/code><\/strong> you will get the following<\/p><figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-49-7.png\" class=\"kg-image\" alt=\"\" loading=\"lazy\" width=\"770\" height=\"502\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-49-7.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-49-7.png 770w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure><h3 id=\"step-9-clean-up\">Step 9: Clean Up<\/h3><p>It&#8217;s important to clean up the setup if it&#8217;s no longer needed; run the following commands to clean up the setup.<\/p><p><strong>1. Delete Java App Deployment<\/strong><\/p><p>Let&#8217;s start with deleting the Java application, CD into the <strong>03-java-app-deployment\/java-app<\/strong> folder and run the following command<\/p><pre><code>kubectl delete -f .<\/code><\/pre><p>This command will delete all the resources created by the manifest files inside the java-app directory, it deletes the Java app deployment, configmap, and HPA.<\/p><p><strong>2. Delete MySQL<\/strong><\/p><p>The next step is to delete MySQL, CD into the <strong>03-java-app-deployment\/mysql<\/strong> folder and run the following command<\/p><pre><code>kubectl delete -f .<\/code><\/pre><p>This command will delete all the resources created by the manifest files inside the mysql directory, it deletes the MySQL statefulset and configmap.<\/p><p><strong>3. Delete Secret<\/strong><\/p><p>To delete the secret in both namespace, CD into the <strong>03-java-app-deployment<\/strong> and run the following commands<\/p><p>Just change the namespace in the <strong>secret.yml<\/strong> file to <strong>pet-clinic-app<\/strong> and <strong>pet-clinic-db<\/strong> then run the following command twice, once for each namespace.<\/p><pre><code>kubectl delete -f secret.yml<\/code><\/pre><p><strong>4. Delete Namespaces<\/strong><\/p><p>Finally, delete the namespaces, run the following command to delete both namespaces<\/p><pre><code>kubectl delete ns pet-clinic-app\n\nkubectl delete ns pet-clinic-db<\/code><\/pre><h2 id=\"conclusion\">Conclusion<\/h2><p>In summary, we have built the pet clinic Java application using maven, pushed it to the docker hub, used the image to deploy the application on Kubernetes, and configured a MySQL database to it.<\/p><p>I believe you find this blog helpful in understanding deploying applications and configuring the database to the application on Kubernetes.<\/p>\n<hr><p><strong>Ngu\u1ed3n:<\/strong> <a href=\"https:\/\/devopscube.com\/deploy-java-app-kubernetes\/\" target=\"_blank\" rel=\"noopener noreferrer\">How to Deploy Java App With MySQL on Kubernetes \u2014 DevOpsCube<\/a><\/p>","protected":false},"excerpt":{"rendered":"<p>Source: https:\/\/devopscube.com\/deploy-java-app-kubernetes\/<\/p>\n","protected":false},"author":1,"featured_media":1006,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1005","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops"],"_links":{"self":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/1005","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1005"}],"version-history":[{"count":0,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/1005\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/media\/1006"}],"wp:attachment":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1005"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1005"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1005"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}