<?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[How I Kept 8 Microservices From Falling Apart (With Helm Values)]]></title><description><![CDATA[How I Kept 8 Microservices From Falling Apart (With Helm Values)]]></description><link>https://spring-petclinic-microservice.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6a2d76cc6ec234411546bc97/20c7395b-64d3-4d44-a45e-dea3ebcd41c0.jpg</url><title>How I Kept 8 Microservices From Falling Apart (With Helm Values)</title><link>https://spring-petclinic-microservice.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 18 Sep 2026 10:23:08 GMT</lastBuildDate><atom:link href="https://spring-petclinic-microservice.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[My Role as a Helm Values Deployment Engineer: Configuring Microservices for Spring PetClinic on EKS]]></title><description><![CDATA[Introduction
A few weeks ago, my team and I worked on a cloud-native deployment for the Spring PetClinic microservices application as part of a DevOps capstone project. The application consisted of ei]]></description><link>https://spring-petclinic-microservice.hashnode.dev/my-role-as-a-helm-values-deployment-engineer-configuring-microservices-for-spring-petclinic-on-eks</link><guid isPermaLink="true">https://spring-petclinic-microservice.hashnode.dev/my-role-as-a-helm-values-deployment-engineer-configuring-microservices-for-spring-petclinic-on-eks</guid><dc:creator><![CDATA[Jacinta Ezennajiofoeze Chinyere]]></dc:creator><pubDate>Sat, 13 Jun 2026 17:46:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a2d76cc6ec234411546bc97/2483a675-d9b9-48e8-871d-7d75247f1a88.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>Introduction</h3>
<p>A few weeks ago, my team and I worked on a cloud-native deployment for the Spring PetClinic microservices application as part of a DevOps capstone project. The application consisted of eight discrete services: the Config Server (operating on port 8888), the Discovery Server (operating on port 8761), the API Gateway, the Customers Service, the Vets Service, the Visits Service, the GenAI Service, and the Admin Server. All of these services needed to run smoothly within an Amazon Elastic Kubernetes Service (Amazon EKS) cluster.</p>
<p>While my teammates focused on infrastructure provisioning, continuous integration and continuous delivery (CI/CD) pipeline construction, and system monitoring, my role was narrowly but critically defined: configuring Helm values for every microservice. This document provides a detailed account of my responsibilities, the technical challenges I encountered, the solutions I implemented, and the lessons I learned about configuration management in a Kubernetes native environment.</p>
<hr />
<h3>My Role in the Team</h3>
<p>I was responsible for managing the <code>values.yaml</code> files that controlled how each service behaved in Kubernetes. In the Helm packaging system, <code>values.yaml</code> it serves as the primary configuration file that overrides default parameters defined in a Helm chart. My responsibilities included configuring the following elements for every service:</p>
<ul>
<li><p><strong>Ports</strong>: Ensuring that each service listened on the correct internal and external ports. For example, the Config Server needed to run on port 8888, the Discovery Server on port 8761, and the API Gateway on port 8080.</p>
</li>
<li><p><strong>Image tags</strong>: Pinning specific Docker image versions (for example, <code>petclinic-customers:1.2.3</code>) so that updates were intentional, version-controlled, and traceable across deployments.</p>
</li>
<li><p><strong>Environment variables</strong>: Passing service-specific configuration data without hardcoding values into container images or Kubernetes manifest files.</p>
</li>
<li><p><strong>Resource limits</strong>: Setting central processing unit requests and limits as well as memory requests and limits to prevent resource contention, commonly referred to as the "noisy neighbor" problem in multi-tenant clusters.</p>
</li>
<li><p><strong>Database connections</strong>: Configuring the Customers, Vets, and Visits services to point to the correct database endpoints, which included connection strings, authentication credentials, and pool settings.</p>
</li>
<li><p><strong>OpenAI application programming interface integration</strong>: Securely passing application programming interface keys via Kubernetes Secrets for the GenAI service, which provides artificial intelligence-powered features to the PetClinic application.</p>
</li>
</ul>
<p>I also made sure that services could discover one another properly. If the Discovery Server was not configured correctly, the API Gateway could not route traffic to the backend business services. If the database connection strings were wrong, the Customers, Vets, or Visits services would fail to start or would produce runtime errors. Every small misconfiguration broke something, and my role required me to anticipate, prevent, and resolve these issues systematically.</p>
<hr />
<h3>What I Actually Did: A Detailed Breakdown</h3>
<p>For each of the eight microservices, I defined Helm values in a structured <code>values.yaml</code> file. Below is a detailed explanation of what I configured and why each element mattered.</p>
<h4>1. Port Configuration</h4>
<p>Each service required specific container ports and service ports to enable communication within the Kubernetes cluster. I defined these ports in the Helm values so that the associated Kubernetes Service and Deployment objects could reference them consistently.</p>
<p>For example, the Config Server required port 8888 for configuration retrieval. The Discovery Server required port 8761 for service registration and lookup. The API Gateway required port 8080 for accepting external HTTP traffic. The GenAI Service required port 8080 as well, which meant I had to ensure no port conflicts occurred within the same pod or service namespace.</p>
<p>I also configured <code>service.port</code> and <code>containerPort</code> fields separately to distinguish between how the service was exposed internally to the cluster and which port the container actually listened on.</p>
<h4>2. Image Tag Management</h4>
<p>I pinned specific image tags for each service to enable reproducible deployments. Instead of using the <code>latest</code> tag, which introduces non-deterministic behavior, I used semantic version tags such as <code>1.2.3</code> or Git commit hashes.</p>
<p>When a developer pushed new code, the CI/CD pipeline built a new Docker image, pushed it to Amazon Elastic Container Registry (Amazon ECR), and produced a unique image tag. My responsibility was to manually or automatically update that tag in the corresponding Helm values file. This practice ensured that the cluster always ran a known, auditable version of each service.</p>
<h4>3. Environment Variables</h4>
<p>I passed service-specific environment variables through Helm values to avoid hardcoding sensitive or environment-specific data into the application code. For instance, the Customer Service needed a <code>SPRING_DATASOURCE_URL</code> variable pointing to its database. The GenAI Service needed an <code>OPENAI_MODEL</code> variable specifying which artificial intelligence model to use.</p>
<p>Environment variables were defined under a <code>env</code> Key in the Helm values, and the Helm template would translate these into Kubernetes environment variable definitions inside the pod specification.</p>
<h4>4. Resource Limits and Requests</h4>
<p>This was one of the most critical aspects of my role. Kubernetes uses resource requests to schedule pods on nodes with sufficient available capacity. It uses resource limits to enforce caps on resource consumption. Without proper limits, a single misbehaving service could consume all available central processing units or memory on a node, affecting other services.</p>
<p>I configured the following for each service:</p>
<ul>
<li><p><strong>central processing unit requests</strong>: The minimum amount of central processing unit guaranteed to the container.</p>
</li>
<li><p><strong>central processing unit limits</strong>: The maximum central processing unit that the container could consume.</p>
</li>
<li><p><strong>Memory requests</strong>: The guaranteed memory allocation.</p>
</li>
<li><p><strong>Memory limits</strong>: The maximum memory allowed before the container would be terminated by the Kubernetes out-of-memory killer.</p>
</li>
</ul>
<p>For example, the Config Server required modest resources, so I set a central processing unit request of 0.1 core and a memory request of 256 megabytes. The GenAI Service, being more computationally intensive, received higher allocations.</p>
<h4>5. Database Connection Configuration</h4>
<p>The Customers, Vets, and Visits services each needed to connect to a database. In our deployment, these services used a shared MySQL database instance running outside the Kubernetes cluster for demonstration purposes, though in production, they might use separate databases.</p>
<p>I configured the following database-related Helm values:</p>
<ul>
<li><p>Database hostname or endpoint</p>
</li>
<li><p>Database port (typically 3306 for MySQL)</p>
</li>
<li><p>Database name (for example, <code>customer_db</code>, <code>vet_db</code>, <code>visit_db</code>)</p>
</li>
<li><p>Username and password references (via Kubernetes Secrets)</p>
</li>
<li><p>Connection pool settings, such as maximum active connections and connection timeout</p>
</li>
</ul>
<p>I used Helm value references to Kubernetes Secrets so that database credentials never appeared in plain text in the <code>values.yaml</code> file.</p>
<h4>6. OpenAI Application Programming Interface Integration for the GenAI Service</h4>
<p>The GenAI Service required an OpenAI application programming interface key to authenticate requests to OpenAI's models. Initially, this key was hardcoded in the <code>values.yaml</code> file, which posed a security risk because <code>values.yaml</code> Files are often stored in version control systems like Git.</p>
<p>I refactored the Helm templates to reference a Kubernetes Secret using the <code>secretKeyRef</code> construct. This meant the actual application programming interface key lived only in the cluster's Secret resource, never in the <code>values.yaml</code> file. The Helm values contained only the name of the Secret and the key within that Secret to reference.</p>
<p>This change ensured that sensitive information remained outside of version control while still being accessible to the GenAI Service container at runtime.</p>
<hr />
<h3>Key Challenge I Solved</h3>
<h4>Challenge: The GenAI Service Crashed Repeatedly Under Load</h4>
<p>Shortly after deploying the full stack to Amazon EKS, the GenAI Service began crashing intermittently under modest traffic. The pod would start successfully, handle a few requests, and then be terminated by Kubernetes. The logs showed out-of-memory errors.</p>
<p><strong>Diagnosis</strong>: After analyzing the pod events and using <code>kubectl describe pod</code> To inspect the termination reason, I discovered that the default memory limit for the GenAI Service was set to 512 megabytes (512Mi). When processing requests to OpenAI's application programming interface, the service needed to load model responses into memory, parse them, and perform additional processing. Under concurrent requests, memory usage exceeded 512Mi, triggering the out-of-memory killer.</p>
<p><strong>Solution</strong>: I modified the Helm values for the GenAI Service by increasing the memory limit from 512 megabytes to 1 gigabyte (1Gi). I also set a memory request of 512 megabytes to ensure the pod would be scheduled on a node with at least that much memory available. Additionally, I increased the central processing unit limit from 0.5 cores to 1.0 cores to reduce request processing latency.</p>
<p>After reapplying the Helm chart with the updated values, the GenAI Service stabilized immediately. No further crashes occurred during the remainder of the project.</p>
<h4>Secondary Challenge: Hardcoded OpenAI Application Programming Interface Keys</h4>
<p>The original Helm values file contained the OpenAI application programming interface key in plain text under a <code>env</code> section. This was a security vulnerability because the <code>values.yaml</code> file was stored in a Git repository accessible to multiple team members.</p>
<p><strong>Solution</strong>: I performed the following steps:</p>
<ol>
<li><p>Created a Kubernetes Secret manually using <code>kubectl create secret generic openai-secret --from-literal=api-key=&lt;actual-key&gt;</code></p>
</li>
<li><p>Modified the Helm values file to remove the plain text key and instead reference the Secret using <code>secretKeyRef</code></p>
</li>
<li><p>Updated the Helm template for the GenAI Service to conditionally include the Secret reference</p>
</li>
<li><p>Documented the change so that future deployments would create the Secret before installing the Helm chart</p>
</li>
</ol>
<p>This change ensured that the application programming interface key never appeared in version control, logs, or command line histories.</p>
<hr />
<h3>What Went Well</h3>
<h4>Helm Values Became Our Single Source of Truth</h4>
<p>Before using Helm, my team had experimented with manually written Kubernetes YAML files. Any change required editing multiple files and applying them with <code>kubectl apply</code>. This approach was error-prone and difficult to audit.</p>
<p>With Helm values, every configuration parameter for every service lives in a predictable location. When a developer pushed new code, the CI/CD pipeline built an image, pushed it to Amazon Elastic Container Registry, and produced a new image tag. I updated the image tag in the relevant service's <code>values.yaml</code> file, committed the change to Git, and ArgoCD detected the change automatically. ArgoCD then synchronized the deployment to the Amazon EKS cluster without any manual <code>kubectl</code> commands.</p>
<p>This workflow made updates repeatable, auditable, and reversible. If a new image tag introduced a bug, I could revert the <code>values.yaml</code> change, and ArgoCD would roll back to the previous working state.</p>
<h4>Environment Parity Across Development and Production</h4>
<p>Because Helm values were parameterized, we used the same Helm chart for different environments. Only the <code>values.yaml</code> file changed. The development environment used smaller resource limits and development database endpoints, while production used higher limits and production database endpoints. This approach eliminated environment drift and reduced "it works on my machine" problems.</p>
<hr />
<h3>What I Would Do Differently</h3>
<h4>Introduce a Shared Helm Values Library</h4>
<p>Several services reused identical configuration patterns. For example, the Customers, Vets, and Visits services all needed similar database connection settings and resource limits. However, I maintained these configurations separately in each service's <code>values.yaml</code> file. When I needed to change a common setting, such as increasing the database connection timeout, I had to edit three separate files manually. This led to configuration drift over time.</p>
<p><strong>Improvement</strong>: I would create a shared Helm values library or use Helm's <code>_helpers.tpl</code> mechanism to define reusable configuration blocks. Alternatively, I would use a values file hierarchy where a global <code>values.yaml</code> defined common settings and service-specific files overrode only what was necessary.</p>
<h4>Add Schema Validation for Values Files</h4>
<p>Typos in environment variable names caused silent failures that took hours to debug. For instance, I once typed <code>SPRING_DATSOURC_URL</code> instead of <code>SPRING_DATASOURCE_URL</code>. The container started without error because the missing environment variable simply defaulted to an empty string, but the application failed at runtime with a cryptic database connection error.</p>
<p><strong>Improvement</strong>: I would introduce schema validation using tools such as <code>helm lint</code> a custom schema defined via JSON Schema. The schema would enforce that required environment variables are present, that numeric fields such as memory limits contain valid units, and that port numbers fall within acceptable ranges. This validation would run in the CI/CD pipeline before any deployment, catching errors early.</p>
<h4>Automate Image Tag Updates</h4>
<p>In my role, I updated image tags manually. While this worked, it was slow and introduced the possibility of human error. A more robust approach would be to use a tool such as Renovate or Dependabot to automatically propose pull requests when new image tags become available in Amazon Elastic Container Registry. These tools can parse Helm values files, detect image tag changes, and create pull requests for review. After review, merging the pull request would trigger ArgoCD to synchronize the new version.</p>
<hr />
<h3>Final Reflection</h3>
<p>Working in a team taught me that Helm values are not merely configuration files. They are the contract between developers, operations engineers, and the Kubernetes cluster. A single misconfigured port, a missing environment variable, or an overly restrictive memory limit can break service discovery, cause pods to crash, or inadvertently expose secrets to unauthorized parties.</p>
<p>This project gave me hands-on experience with Helm, Kubernetes, Amazon EKS, ArgoCD, and microservice communication patterns. More importantly, I learned that deployment engineers do not simply "set values." We ensure that every service has exactly what it needs to run, communicate, and scale — no more and no less. We translate application requirements into infrastructure configuration. We balance resource efficiency against reliability. And we create systems that are repeatable, auditable, and recoverable.</p>
<hr />
<h3>About the DevOps Micro-Internship</h3>
<p>This project was part of the DevOps Micro-Internship (DMI), a program where participants build practical DevOps skills through real-world projects and teamwork. DMI Cohort 3 is starting on 27 June. If you want to build real DevOps skills, you can apply here:</p>
<p><a href="https://docs.google.com/forms/d/e/1FAIpQLSel7ai7nyb0P1qLW4vEyfB_nEsD4lUF1XG88vmAaFGBOb6hPA/viewform">https://docs.google.com/forms/d/e/1FAIpQLSel7ai7nyb0P1qLW4vEyfB_nEsD4lUF1XG88vmAaFGBOb6hPA/viewform</a></p>
]]></content:encoded></item><item><title><![CDATA[The Educational Value of Local Deployment: A Prerequisite for Cloud Migration]]></title><description><![CDATA[Before deploying the Spring PetClinic microservices application with my team on Amazon Web Services, I first decided to run the project locally using Docker. At first glance, this appeared to be a str]]></description><link>https://spring-petclinic-microservice.hashnode.dev/the-educational-value-of-local-deployment-a-prerequisite-for-cloud-migration</link><guid isPermaLink="true">https://spring-petclinic-microservice.hashnode.dev/the-educational-value-of-local-deployment-a-prerequisite-for-cloud-migration</guid><dc:creator><![CDATA[Jacinta Ezennajiofoeze Chinyere]]></dc:creator><pubDate>Sat, 13 Jun 2026 17:26:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a2d76cc6ec234411546bc97/3ebb605e-784b-4a33-8fb3-40ecf3c96e01.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Before deploying the Spring PetClinic microservices application with my team on Amazon Web Services, I first decided to run the project locally using Docker. At first glance, this appeared to be a straightforward step: clone the repository, build the services, execute Docker Compose, and verify that every component functions correctly.</p>
<p>However, as I progressed through the process, I quickly realized that local deployment involves more than merely starting containers. It is the stage at which one begins to understand how the application is architected, how the services communicate with one another, and what each component requires before it can operate reliably. For me, this local deployment became the foundational experience that later clarified the more complex cloud deployment.</p>
<h3><strong>Understanding the Application</strong></h3>
<p>Spring PetClinic is not a single application running as one unified service. It is a microservices-based application composed of several independent services that work in concert. Some of the main services include:</p>
<ul>
<li><p>Config Server</p>
</li>
<li><p>Discovery Server</p>
</li>
<li><p>API Gateway</p>
</li>
<li><p>Customers Service</p>
</li>
<li><p>Vets Service</p>
</li>
<li><p>Visits Service</p>
</li>
<li><p>Admin Server</p>
</li>
<li><p>GenAI Service</p>
</li>
<li><p>Monitoring components</p>
</li>
</ul>
<p>Each of these services has a distinct role. The Config Server provides centralized configuration for the other services. This means that instead of each service managing all its settings separately, they can pull configuration from one central source. The Discovery Server allows services to register themselves and discover one another, which is essential in a microservices environment where services must communicate without relying on fixed network addresses. The API Gateway acts as the primary entry point into the application. Rather than clients calling each service directly, requests pass through the gateway and are routed to the appropriate backend service. The Customers, Vets, and Visits services handle the core business logic of the PetClinic application. The Admin Server assists with application monitoring, while the GenAI service adds an artificial intelligence-powered feature.</p>
<p>Before this project, I understood microservices in theory. Running them locally, however, helped me observe how these services depend on one another in practice.</p>
<h3><strong>Running the Application with Docker Compose</strong></h3>
<p>To run the full application locally, I used Docker and Docker Compose. Docker allowed each service to execute inside its own container, packaging the application together with its dependencies. Docker Compose made it possible to manage all containers from a single file and start them together using one command.</p>
<p>The primary command I used was:</p>
<pre><code class="language-plaintext">docker compose up -d
</code></pre>
<p>This command started the services in detached mode, allowing the containers to run in the background. After starting the stack, I inspected the running containers with:</p>
<pre><code class="language-plaintext">docker ps
</code></pre>
<p>This helped me confirm which services were running and whether they were healthy. When a service failed to start properly, I examined the logs using:</p>
<pre><code class="language-plaintext">docker logs &lt;container-name&gt;
</code></pre>
<p>When I needed to stop the environment, I used:</p>
<pre><code class="language-plaintext">docker compose down
</code></pre>
<p>These commands, though simple in appearance, proved highly useful for troubleshooting.</p>
<h3><strong>What I Learned About Startup Order</strong></h3>
<p>One of the most significant lessons from the local deployment was understanding startup order. In a microservices application, services do not all start simultaneously and work immediately. Some services must be available before others can function correctly.</p>
<p>For example, the Config Server needs to start early because other services depend on it for configuration. The Discovery Server must also be healthy because services need to register themselves before they can communicate effectively. After those foundational services are running, the business services, such as Customers, Vets, and Visits, can start properly. Only then can the API Gateway route traffic to the services.</p>
<p>This experience helped me understand why health checks and service dependencies are critical. It is not sufficient for a container to be running. The service inside the container must also be ready and healthy. That distinction is very important in DevOps. A container can be running while the application inside it is still failing.</p>
<h3><strong>Troubleshooting Locally</strong></h3>
<p>Local deployment also provided a safe environment for troubleshooting. Instead of waiting until the application was already deployed to the cloud, I could identify issues early from my local machine. I inspected container logs, monitored for unhealthy services, verified port assignments, and paid attention to environment variables.</p>
<p>Some of the elements I learned to check included:</p>
<ul>
<li><p>Whether the container is running</p>
</li>
<li><p>Whether the service is healthy</p>
</li>
<li><p>Whether the required environment variables are set</p>
</li>
<li><p>Whether the Config Server is available</p>
</li>
<li><p>Whether the Discovery Server is healthy</p>
</li>
<li><p>Whether the services are registering correctly</p>
</li>
<li><p>Whether there are port conflicts</p>
</li>
<li><p>Whether the logs show connection errors</p>
</li>
</ul>
<p>This process increased my confidence because I was not running commands blindly. I was learning how to investigate problems systematically.</p>
<h3><strong>Why Local Deployment Matters Before Cloud Deployment</strong></h3>
<p>After completing the local deployment, I understood why this step was important before moving to Amazon Web Services. Cloud deployment adds more layers: networking, Kubernetes, Helm, load balancers, secrets, continuous integration and continuous delivery pipelines, monitoring, and infrastructure provisioning.</p>
<p>If I had not first understood how the application worked locally, it would have been more difficult to troubleshoot issues in the cloud. Running the project locally helped me understand:</p>
<ul>
<li><p>The role of each microservice</p>
</li>
<li><p>The dependencies between services</p>
</li>
<li><p>The importance of configuration</p>
</li>
<li><p>How Docker containers behave</p>
</li>
<li><p>How logs assist with troubleshooting</p>
</li>
<li><p>Why health checks matter</p>
</li>
<li><p>How the application should behave before deployment</p>
</li>
</ul>
<p>By the time my team and I moved to Amazon Web Services, I had a better understanding of what the application needed to run successfully. That made the cloud deployment phase more meaningful for me.</p>
<h3><strong>Preparing for the Amazon Web Services Deployment</strong></h3>
<p>The local Docker deployment was the first step before the larger team deployment on Amazon Web Services. After understanding the application locally, the next stage involved deploying the microservices with tools such as Kubernetes, Helm, ArgoCD, GitHub Actions, Prometheus, Grafana, and Terraform.</p>
<p>The local setup helped me connect the ideas. Docker helped me understand containers. Docker Compose helped me understand multi-service orchestration at a basic level. Kubernetes later expanded that idea into a more powerful orchestration platform. Helm helped package and manage Kubernetes configurations. ArgoCD introduced GitOps, where Git became the source of truth for deployment. Prometheus and Grafana assisted with observability. Terraform helped with infrastructure as code. Yet it all started locally.</p>
<h3><strong>Key Takeaways</strong></h3>
<p>This experience taught me that DevOps does not begin only in the cloud. Sometimes, DevOps starts on your local machine, with a terminal, a Docker Compose file, and logs that reveal what is truly happening. Before automation, you need understanding. Before scaling, you need stability. Before cloud deployment, you need to know how the application behaves.</p>
<p>Running Spring PetClinic locally with Docker gave me that foundation. It helped me understand the application from the ground up and prepared me for the team deployment on Amazon Web Services. For me, this was not just a local setup. It was the first real step in understanding how modern microservices move from local development to cloud native deployment.</p>
<h3><strong>Final Reflection</strong></h3>
<p>Looking back, I am glad I started with local deployment before joining the full Amazon Web Services deployment process. It gave me clarity, confidence, and a stronger technical foundation. I learned that every successful deployment starts with understanding the system. Tools are important, but knowing how the application works is even more important.</p>
<p>This project reminded me that DevOps is not only about using Docker, Kubernetes, or cloud platforms. It is about building reliable workflows, solving problems, improving delivery, and understanding the systems we deploy. For me, running Spring PetClinic locally with Docker was the ideal first step.</p>
]]></content:encoded></item></channel></rss>