<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Soufiane ODF daily blogs]]></title><description><![CDATA[Hey there! My name is Soufiane, I'm an enthusiastic software engineer, youtuber and blogger, I love solving problems and bring great products to the world.]]></description><link>https://blog.ouddaf.com</link><generator>RSS for Node</generator><lastBuildDate>Tue, 28 Apr 2026 14:39:00 GMT</lastBuildDate><atom:link href="https://blog.ouddaf.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to Deploy a Spring Boot Application with Postgres Database on GKE with Ingress GKE Controller: A Step-by-Step Guide]]></title><description><![CDATA[Note: All the source codes of this tutorial will be available on my GitHub repo.
In this tutorial, we will cover the following:

take a look at the spring boot app we are going to deploy

building/publishing the app docker image to the docker hub

wh...]]></description><link>https://blog.ouddaf.com/how-to-deploy-a-spring-boot-application-with-postgres-database-on-gke-with-ingress-gke-controller-a-step-by-step-guide</link><guid isPermaLink="true">https://blog.ouddaf.com/how-to-deploy-a-spring-boot-application-with-postgres-database-on-gke-with-ingress-gke-controller-a-step-by-step-guide</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[gke]]></category><category><![CDATA[google cloud]]></category><category><![CDATA[PostgreSQL]]></category><dc:creator><![CDATA[Soufiane Ouddaf]]></dc:creator><pubDate>Mon, 01 May 2023 14:59:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1682850320553/cfd2a2d1-187b-4a41-8332-dd6225cc46bc.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Note: All the source codes of this tutorial will be available on my <a target="_blank" href="https://github.com/soufianeodf/deploy-spring-boot-with-k8s">GitHub repo</a>.</p>
<p>In this tutorial, we will cover the following:</p>
<ol>
<li><p>take a look at the spring boot app we are going to deploy</p>
</li>
<li><p>building/publishing the app docker image to the docker hub</p>
</li>
<li><p>why GKE ingress?</p>
</li>
<li><p>how does GKE ingress work?</p>
</li>
<li><p>network endpoint groups</p>
</li>
<li><p>discussing the Kubernetes yaml files</p>
</li>
<li><p>DNS mapping</p>
</li>
<li><p>deploying to the GKE cluster</p>
</li>
</ol>
<p>Let’s dive right in.</p>
<p>Here is the architecture of our deployment:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682945346853/0ace684c-4e21-47ee-a8e2-1ad5f9d2df9c.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-1-spring-boot-application"><strong>1- Spring boot application:</strong></h3>
<p>let's discuss the code of our spring boot application:</p>
<p>First, the content of our <code>application.yml</code></p>
<pre><code class="lang-yaml"><span class="hljs-attr">server:</span>
  <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span>
  <span class="hljs-attr">servlet:</span>
    <span class="hljs-attr">context-path:</span> <span class="hljs-string">/api</span>

<span class="hljs-attr">spring:</span>
  <span class="hljs-attr">application:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">spring-boot-with-k8s</span>
  <span class="hljs-attr">datasource:</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">jdbc:postgresql://${DB_HOST}:5432/${DB_NAME}</span>
    <span class="hljs-attr">username:</span> <span class="hljs-string">${POSTGRES_USER}</span>
    <span class="hljs-attr">password:</span> <span class="hljs-string">${POSTGRES_PASSWORD}</span>
  <span class="hljs-attr">jpa:</span>
    <span class="hljs-attr">database-platform:</span> <span class="hljs-string">org.hibernate.dialect.PostgreSQLDialect</span>
    <span class="hljs-attr">show-sql:</span> <span class="hljs-string">'true'</span>
  <span class="hljs-attr">flyway:</span>
    <span class="hljs-attr">validate-on-migrate:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">encoding:</span> <span class="hljs-string">UTF-8</span>

<span class="hljs-comment"># configure liveness and readiness probes</span>
<span class="hljs-attr">management:</span>
  <span class="hljs-attr">endpoint:</span>
    <span class="hljs-attr">health:</span>
      <span class="hljs-attr">probes:</span>
        <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">show-details:</span> <span class="hljs-string">always</span>
  <span class="hljs-attr">health:</span>
    <span class="hljs-attr">livenessState:</span>
      <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">readinessState:</span>
      <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
</code></pre>
<p>we are just going to discuss the important information in the file.</p>
<p>As we can see, the context path of the application is set to <code>/api</code>, so that we can access the resources as <code>http://host:8080/api/xxx</code>.</p>
<p>The <code>${DB_HOST}</code>, <code>${DB_NAME}</code>, <code>${POSTGRES_USER}</code> and <code>${POSTGRES_PASSWORD}</code> are environment variables that we are going to pass their value to the container as env.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">flyway:</span>
    <span class="hljs-attr">validate-on-migrate:</span> <span class="hljs-literal">true</span>
</code></pre>
<p>We are using Flyway, which is an open-source database migration tool, that helps developers implement automated and version-based database migrations.</p>
<p>and <code>validate-on-migrate: true</code>, means that on application runtime, we will migrate all the SQL files that are present at the path: <code>resources/db/migration/**</code> into the Postgres database.</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># configure liveness and readiness probes</span>
<span class="hljs-attr">management:</span>
  <span class="hljs-attr">endpoint:</span>
    <span class="hljs-attr">health:</span>
      <span class="hljs-attr">probes:</span>
        <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">show-details:</span> <span class="hljs-string">always</span>
  <span class="hljs-attr">health:</span>
    <span class="hljs-attr">livenessState:</span>
      <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">readinessState:</span>
      <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
</code></pre>
<p>and finally, we are going to expose health probes APIs, which are features of <code>Spring Boot Actuator</code>, which allows the monitoring of the health of the application, specifically, liveness and readiness probes to determine if an application is ready to receive traffic or if it is still alive and functioning properly.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-string">"greeting"</span>
(
    <span class="hljs-string">"id"</span>                      <span class="hljs-built_in">serial</span>,
    <span class="hljs-string">"message"</span>                   <span class="hljs-built_in">varchar</span>  <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,
    PRIMARY <span class="hljs-keyword">KEY</span> (<span class="hljs-string">"id"</span>)
);

<span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> <span class="hljs-string">"greeting"</span> (<span class="hljs-string">"message"</span>) <span class="hljs-keyword">VALUES</span> (<span class="hljs-string">'Hello World!'</span>);
</code></pre>
<p>This is the SQL file we are going to execute with Flyway.</p>
<pre><code class="lang-sql">@RequestMapping("/greeting")
public String greeting() {
    return greetingService.getGreeting();
}
</code></pre>
<p>This is the API we are exposing, that will fetch the greeting message from the <code>"greeting"</code> database table.</p>
<h3 id="heading-2-buildingpublishing-docker-image-to-docker-hub">2- Building/Publishing Docker image to Docker Hub</h3>
<pre><code class="lang-plaintext">FROM --platform=linux/amd64 maven:3.8.3-openjdk-17

COPY target/greeting-*.jar /app/
WORKDIR /app
ENV ARG "--server.port=8080"

# Copying the Jar files.
RUN mv /app/greeting-*.jar /app/main.jar
CMD java -jar main.jar $ARG
</code></pre>
<p>This is the <code>Dockerfile</code> content that we are using to package the application as a Docker image.</p>
<p>Now, what we need to do is to package the spring boot application as a jar, using the following command:</p>
<p><code>mvn -DskipTests=true clean install</code></p>
<p>Then we can build the docker image:</p>
<p><code>docker build -t soufianeodf/greeting:v1 .</code> you can change "soufianeodf" by your docker hub username.</p>
<p>and then push it to the docker hub registry:</p>
<p>you can log in to your docker hub account first if you are not already.</p>
<p><code>docker login</code></p>
<p>then push the image:</p>
<p><code>docker push soufianeodf/greeting:v1</code></p>
<p>now our docker image is available publicly to be used.</p>
<p><strong>Note</strong>: for simplicity's sake, we are using the docker hub registry, and we are publishing the docker image publicly, but of course, you are free to use whatever registry you want and also the accessibility of the image, public or private.</p>
<h2 id="heading-3-why-gke-ingress"><strong>3- Why GKE Ingress?</strong></h2>
<p>When dealing with Kubernetes ingress traffic, using Loadbalancer for each service may not be the best approach. Instead, you can rely on a Kubernetes ingress controller to manage all the traffic for the cluster. By using either direct DNS or wildcard DNS mapping, you can effectively route traffic to backend kubernetes services.</p>
<p>One benefit of using an ingress controller is the ability to attach multiple DNS to a single Loadbalancer and then route to different service backends. Additionally, you can use path-based routing rules in the ingress resources to manage traffic to various Kubernetes services.</p>
<p>The great thing is, GKE has an inbuilt GKE ingress controller which makes setting up an ingress controller a breeze with no additional configuration required. However, if your project has specific requirements or if different features are needed, you can set up other ingress controllers like the Nginx ingress controller.</p>
<p>This tutorial will focus on creating an ingress object using the GKE ingress controller.</p>
<h2 id="heading-4-how-does-gke-ingress-work"><strong>4- How Does GKE Ingress Work?</strong></h2>
<p>As you are likely aware, a Kubernetes ingress requires an Ingress controller to function properly. In this case, GKE provides its ingress controller, known as the GKE ingress controller.</p>
<p>When you create an ingress object using the GKE ingress controller, a Load Balancer is launched (either Public or Private) with all the routing rules specified in the ingress resource. However, since the Load Balancer is external to the cluster, the backend service defined in the ingress resources must be of the Nodeport type.</p>
<p>This is in contrast to a typical ingress controller implementation, such as the Nginx ingress controller, where the proxy layer is situated inside the cluster and can communicate with services without <code>Nodeport</code>.</p>
<h2 id="heading-5-network-endpoint-groups"><strong>5- Network Endpoint Groups</strong></h2>
<p>In GKE, there is an important concept known as Network Endpoint Groups (NEGs). Even when the backend service is of the NodePort type, GKE does not simply route traffic to any node within the cluster to reach the pods. Rather, using NEGs, all traffic is sent directly to the nodes where the pods are located.</p>
<p>Without the use of NEGs, traffic from the Load Balancer can be routed to any node within the cluster, resulting in additional network hops before finally reaching the node where the pod resides. This can lead to slower and less efficient routing, highlighting the importance of using NEGs within GKE.</p>
<h2 id="heading-6-kubernetes-yaml-files"><strong>6- Kubernetes yaml files:</strong></h2>
<p>Now let's discuss our Kubernetes yaml files:</p>
<p><code>postgres-configmap.yml</code></p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">ConfigMap</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-config</span>
<span class="hljs-attr">data:</span>
  <span class="hljs-attr">host:</span> <span class="hljs-string">postgres-service</span> <span class="hljs-comment"># database host</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">greeting</span> <span class="hljs-comment"># database name</span>
</code></pre>
<p>those values are going to override the environment variables we have inside the application.yml of the spring boot app.</p>
<p><code>postgres-secret.yml</code></p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Secret</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-secrets</span>
<span class="hljs-attr">type:</span> <span class="hljs-string">Opaque</span>
<span class="hljs-attr">data:</span>
  <span class="hljs-attr">postgres_user:</span> <span class="hljs-string">cG9zdGdyZXM=</span> <span class="hljs-comment"># postgres username encoded in base64 (the original value is postgres)</span>
  <span class="hljs-attr">postgres_password:</span> <span class="hljs-string">cm9vdA==</span> <span class="hljs-comment"># postgres password encoded in base64 (the original value is root)</span>
</code></pre>
<p>This is the terminal command that we can use to encrypt the secret keys in base64:</p>
<pre><code class="lang-yaml"> <span class="hljs-string">echo</span> <span class="hljs-string">-n</span> <span class="hljs-string">'your_secret_key'</span> <span class="hljs-string">|</span> <span class="hljs-string">base64</span>
</code></pre>
<p><code>postgres-volume.yml</code></p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">PersistentVolumeClaim</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-pvc</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">postgres</span>
    <span class="hljs-attr">tier:</span> <span class="hljs-string">database</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">accessModes:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">ReadWriteOnce</span>
  <span class="hljs-attr">resources:</span>
    <span class="hljs-attr">requests:</span>
      <span class="hljs-attr">storage:</span> <span class="hljs-string">1Gi</span>
</code></pre>
<p>We are going to use only the <code>PersistentVolumeClaim</code> without specifying any <code>PersistentVolume</code>, as in GKE, google is going to create it automatically for us when not specified.</p>
<p>As we can see in the <a target="_blank" href="https://cloud.google.com/kubernetes-engine/docs/concepts/persistent-volumes#:~:text=GKE%20creates%20a%20default%20StorageClass,doesn't%20specify%20a%20StorageClassName%20.">Google documentation</a>:</p>
<p><code>PersistentVolume</code> resources can be provisioned dynamically through <code>PersistentVolumeClaims</code>, or they can be explicitly created by a cluster administrator.</p>
<p>GKE creates a default <code>StorageClass</code> for you which uses the balanced persistent disk type (ext4). The default <code>StorageClass</code> is used when a <code>PersistentVolumeClaim</code> doesn't specify a <code>StorageClassName</code></p>
<p><code>java-deployment.yml</code></p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">greeting-deployment</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">2</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">greeting</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">greeting</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">greeting</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">soufianeodf/greeting:v1</span>
        <span class="hljs-attr">imagePullPolicy:</span> <span class="hljs-string">Always</span>
        <span class="hljs-attr">readinessProbe:</span>
          <span class="hljs-attr">httpGet:</span>
            <span class="hljs-attr">path:</span> <span class="hljs-string">/api/actuator/health/readiness</span>
            <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span>
          <span class="hljs-attr">initialDelaySeconds:</span> <span class="hljs-number">180</span>
          <span class="hljs-attr">periodSeconds:</span> <span class="hljs-number">10</span>
        <span class="hljs-attr">livenessProbe:</span>
          <span class="hljs-attr">httpGet:</span>
            <span class="hljs-attr">path:</span> <span class="hljs-string">/api/actuator/health/liveness</span>
            <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span>
          <span class="hljs-attr">initialDelaySeconds:</span> <span class="hljs-number">120</span>
          <span class="hljs-attr">periodSeconds:</span> <span class="hljs-number">30</span>
        <span class="hljs-attr">resources:</span>
          <span class="hljs-attr">limits:</span>
            <span class="hljs-attr">cpu:</span> <span class="hljs-string">500m</span>
            <span class="hljs-attr">memory:</span> <span class="hljs-string">512Mi</span>
          <span class="hljs-attr">requests:</span>
            <span class="hljs-attr">cpu:</span> <span class="hljs-string">200m</span>
            <span class="hljs-attr">memory:</span> <span class="hljs-string">256Mi</span>
        <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">8080</span>
        <span class="hljs-attr">env:</span> <span class="hljs-comment"># Setting Enviornmental Variables</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_HOST</span>   <span class="hljs-comment"># Setting Database host address from configMap</span>
            <span class="hljs-attr">valueFrom:</span>
              <span class="hljs-attr">configMapKeyRef:</span>
                <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-config</span>  <span class="hljs-comment"># name of configMap</span>
                <span class="hljs-attr">key:</span> <span class="hljs-string">host</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_NAME</span>  <span class="hljs-comment"># Setting Database name from configMap</span>
            <span class="hljs-attr">valueFrom:</span>
              <span class="hljs-attr">configMapKeyRef:</span>
                <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-config</span>
                <span class="hljs-attr">key:</span> <span class="hljs-string">name</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_USER</span>  <span class="hljs-comment"># Setting Database username from Secret</span>
            <span class="hljs-attr">valueFrom:</span>
              <span class="hljs-attr">secretKeyRef:</span>
                <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-secrets</span> <span class="hljs-comment"># Secret Name</span>
                <span class="hljs-attr">key:</span> <span class="hljs-string">postgres_user</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_PASSWORD</span> <span class="hljs-comment"># Setting Database password from Secret</span>
            <span class="hljs-attr">valueFrom:</span>
              <span class="hljs-attr">secretKeyRef:</span>
                <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-secrets</span>
                <span class="hljs-attr">key:</span> <span class="hljs-string">postgres_password</span>
</code></pre>
<p><code>postgres-deployment.yml</code></p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-deployment</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">postgres</span>
    <span class="hljs-attr">tier:</span> <span class="hljs-string">database</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">postgres</span>
  <span class="hljs-attr">strategy:</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">Recreate</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">postgres</span>
        <span class="hljs-attr">tier:</span> <span class="hljs-string">database</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">postgres</span>
          <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:13</span>
          <span class="hljs-attr">imagePullPolicy:</span> <span class="hljs-string">Always</span>
          <span class="hljs-attr">env:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_USER</span>
              <span class="hljs-attr">valueFrom:</span>
                <span class="hljs-attr">secretKeyRef:</span>
                  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-secrets</span>
                  <span class="hljs-attr">key:</span> <span class="hljs-string">postgres_user</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_PASSWORD</span>
              <span class="hljs-attr">valueFrom:</span>
                <span class="hljs-attr">secretKeyRef:</span>
                  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-secrets</span>
                  <span class="hljs-attr">key:</span> <span class="hljs-string">postgres_password</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_DB</span> <span class="hljs-comment"># Setting Database Name from a 'ConfigMap'</span>
              <span class="hljs-attr">valueFrom:</span>
                <span class="hljs-attr">configMapKeyRef:</span>
                  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-config</span>
                  <span class="hljs-attr">key:</span> <span class="hljs-string">name</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">PGDATA</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"/var/lib/postgresql/data/pgdata"</span>
          <span class="hljs-attr">ports:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">5432</span>
              <span class="hljs-attr">name:</span> <span class="hljs-string">postgres</span>
          <span class="hljs-attr">volumeMounts:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-storage</span>
              <span class="hljs-attr">mountPath:</span> <span class="hljs-string">/var/lib/postgresql/data/pgdata</span>
      <span class="hljs-attr">volumes:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-storage</span>
          <span class="hljs-attr">persistentVolumeClaim:</span>
            <span class="hljs-attr">claimName:</span> <span class="hljs-string">postgres-pvc</span>
</code></pre>
<p><code>java-service.yml</code></p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">greeting-service</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">NodePort</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">greeting</span>
  <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">http</span>
      <span class="hljs-attr">port:</span> <span class="hljs-number">80</span>
      <span class="hljs-attr">targetPort:</span> <span class="hljs-number">8080</span>
</code></pre>
<p><strong>Note:</strong> For GKE ingress to work, the service type has to be <code>NodePort</code>. It is a requirement.</p>
<p><code>postgres-service.yml</code></p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-service</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">postgres</span>
    <span class="hljs-attr">tier:</span> <span class="hljs-string">database</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">postgres</span>
    <span class="hljs-attr">tier:</span> <span class="hljs-string">database</span>
  <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">postgres</span>
      <span class="hljs-attr">port:</span> <span class="hljs-number">5432</span>
      <span class="hljs-attr">targetPort:</span> <span class="hljs-number">5432</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">ClusterIP</span>
</code></pre>
<p><code>java-ingress.yml</code></p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">networking.k8s.io/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Ingress</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">nginx-ingress</span>
  <span class="hljs-attr">annotations:</span>
    <span class="hljs-attr">kubernetes.io/ingress.class:</span> <span class="hljs-string">"gce"</span>
    <span class="hljs-attr">kubernetes.io/ingress.global-static-ip-name:</span> <span class="hljs-string">"ingress-webapps"</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">rules:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">host:</span> <span class="hljs-string">"&lt;your-custom-domaine.com&gt;"</span>
      <span class="hljs-attr">http:</span>
        <span class="hljs-attr">paths:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">pathType:</span> <span class="hljs-string">Prefix</span>
            <span class="hljs-attr">path:</span> <span class="hljs-string">"/"</span>
            <span class="hljs-attr">backend:</span>
              <span class="hljs-attr">service:</span>
                <span class="hljs-attr">name:</span> <span class="hljs-string">greeting-service</span>
                <span class="hljs-attr">port:</span>
                  <span class="hljs-attr">number:</span> <span class="hljs-number">80</span>
</code></pre>
<p>There is a couple of things we need to discuss here:</p>
<p><code>1-</code><a target="_blank" href="http://kubernetes.io/ingress.class"><code>kubernetes.io/ingress.class</code></a> annotation with value <code>gce</code> tells GKE to create a public Load Balancer. If you don’t specify it, it defaults to public only.</p>
<p>2- To use a custom domain name to access the application, the IP of the load balancer should be static, so for that, we need to reserve a global static IP address and specify its name in the <a target="_blank" href="http://kubernetes.io/ingress.global-static-ip-name"><code>kubernetes.io/ingress.global-static-ip-name</code></a> annotation in the Ingress YAML file, using the following command in the google cloud shell:</p>
<pre><code class="lang-yaml"><span class="hljs-string">gcloud</span> <span class="hljs-string">compute</span> <span class="hljs-string">addresses</span> <span class="hljs-string">create</span> <span class="hljs-string">ingress-webapps</span> <span class="hljs-string">--global</span>
</code></pre>
<p>after the creation, we can see the assigned static IP:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682947798728/b7edae3d-2e94-414d-b9e4-2b08c61c6c3f.png" alt class="image--center mx-auto" /></p>
<p>3- In our ingress yml file, the <code>pathType</code> is set to <code>Prefix</code>, which means that the Ingress should match any URL path that starts with the specified path, and route all the traffic to the greeting-service endpoint.</p>
<h2 id="heading-7-dns-mapping">7- DNS mapping</h2>
<p>For this, you should have already a domain name, if not you can purchase one.</p>
<p>For this example, we are going to take as an example the domain name: <code>example.com</code></p>
<p>In your Google Cloud project, you can look for Cloud DNS and then click on <code>CREATE ZONE</code>, you should have something like the following:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682948425684/a0228255-c9e8-4753-b357-7d297aceff73.png" alt class="image--center mx-auto" /></p>
<p>after that, you can click on the created zone, add an A record and set the previous static IP created.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682948981619/b8e3c942-be0f-410e-b1aa-1f8f744d42a7.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-8-deploying-to-gke-cluster">8- Deploying to GKE cluster:</h2>
<p>Now you can go ahead and create a GKE cluster:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682949357839/0c8d23a2-47cd-4914-8399-77b68d350432.png" alt class="image--center mx-auto" /></p>
<p>I will be creating it using the autopilot mode.</p>
<p>Then you can go to the cloud shell, and clone the project:</p>
<pre><code class="lang-yaml"><span class="hljs-string">git</span> <span class="hljs-string">clone</span> <span class="hljs-string">https://github.com/soufianeodf/deploy-spring-boot-with-k8s.git</span>
<span class="hljs-string">cd</span> <span class="hljs-string">deploye-spring-boot-with-k8s/k8s</span> <span class="hljs-comment"># get inside the k8s folder</span>
<span class="hljs-string">kubectl</span> <span class="hljs-string">apply</span> <span class="hljs-string">--recursive</span> <span class="hljs-string">-f</span> <span class="hljs-string">./</span> <span class="hljs-comment"># execute all yml files recursively</span>
</code></pre>
<p>you can keep watching for the pods in watch mode until everything is ready:</p>
<pre><code class="lang-yaml"><span class="hljs-string">kubectl</span> <span class="hljs-string">get</span> <span class="hljs-string">pods</span> <span class="hljs-string">-w</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682952716643/65392a17-02d0-4be2-b84b-19edb1977871.png" alt class="image--center mx-auto" /></p>
<p>same thing for the ingress:</p>
<pre><code class="lang-yaml"><span class="hljs-string">kubectl</span> <span class="hljs-string">get</span> <span class="hljs-string">ingress</span> <span class="hljs-string">-w</span>
</code></pre>
<p>the IP will take a couple of minutes to appear, and at the end, you will get something like that:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682952640860/8b53949a-27e0-4f98-b053-15a20ebe648f.png" alt class="image--center mx-auto" /></p>
<p>and you need to check also that the health check in the created load balancer is ok:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682952292447/a96546eb-b017-4d11-a040-d123b7990cb9.png" alt class="image--center mx-auto" /></p>
<p>Finally, you can access the application using the domain name, by taping the following URL into a browser:</p>
<p><code>http://your-domaine/api/greeting</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682952466761/97427d50-6610-4f06-98d0-b445b23d6760.png" alt class="image--center mx-auto" /></p>
<p>Congratulations 🥳🎉</p>
<p>Don't forget to delete the cluster at the end, and also the static IP created.</p>
<h2 id="heading-conclusion">Conclusion:</h2>
<p>The article covers the deployment of a Spring Boot application using Docker and Kubernetes, specifically on the Google Kubernetes Engine (GKE). The process involves building and publishing a Docker image of the application to Docker Hub, using GKE Ingress for routing traffic to the application, Network Endpoint Groups (NEGs) for load balancing, and deploying the application to the GKE cluster using Kubernetes YAML files.</p>
<p>The article also explains how GKE Ingress works and how DNS mapping is used to map domain names to the deployed application. Overall, the article provides a comprehensive guide to deploying a Spring Boot application to a GKE cluster using Kubernetes and Docker.</p>
<p>I would be thrilled to hear your thoughts and opinions on the topic. 😊👂 So, please don't hesitate to leave a comment below and share your insights with me. 💬💡</p>
<h2 id="heading-resources">Resources:</h2>
<ul>
<li><p><a target="_blank" href="https://codelabs.developers.google.com/codelabs/cloud-springboot-kubernetes#0">Deploy a Spring Boot Java app to Kubernetes on Google Kubernetes Engine</a></p>
</li>
<li><p><a target="_blank" href="https://devopscube.com/setup-ingress-gke-ingress-controller/">How to Setup Ingress on GKE using GKE Ingress Controller</a></p>
</li>
<li><p><a target="_blank" href="https://www.baeldung.com/spring-liveness-readiness-probes">Liveness and Readiness Probes in Spring Boot</a></p>
</li>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=OulmwTYTauI">Kubernetes Volumes 2: Understanding Persistent Volume (PV) and Persistent Volume Claim (PVC)</a></p>
</li>
<li><p><a target="_blank" href="https://cloud.google.com/kubernetes-engine/docs/concepts/persistent-volumes#:~:text=GKE%20creates%20a%20default%20StorageClass,doesn't%20specify%20a%20StorageClassName%20.">Persistent volumes and dynamic provisioning</a></p>
</li>
</ul>
]]></content:encoded></item></channel></rss>