Amazon Elastic Kubernetes Service (Amazon EKS)

This topic modernizes a legacy standalone Java application into a containerized microservice that runs on Amazon Elastic Kubernetes Service (Amazon EKS) and connects to Oracle Autonomous AI Database on Dedicated Infrastructure by using an mTLS wallet.

Prerequisites

This section describes the requirements of Oracle AI Database and tables for the Java application to connect to Oracle Autonomous AI Database (Dedicated) and access the Product table.

Oracle Autonomous AI Database

Complete the following steps to provision an Oracle Autonomous AI Database and create a user and table:
  • Oracle Autonomous AI Database Wallet for the connection.
  • Oracle Database user credentials to create a database session and run SQL commands.
  • Connectivity from the application server to Oracle AI Database.
  • A Product table in Oracle AI Database.
Run the following command to create the Product table and insert a test record:

-- Create the Product table
CREATE TABLE Product (
    id NUMBER PRIMARY KEY,
    name VARCHAR2(100) NOT NULL,
    price NUMBER(10, 2) NOT NULL
);

-- Insert a quick test record (optional, so your UI isn't empty on first load)
INSERT INTO Product (id, name, price) 
VALUES (1, 'Test Migration Item', 99.99);

-- Commit the transaction
COMMIT;

Implementation

  1. Development Machine Setup
    1. Tools and Libraries: Install the following libraries and tools on the development machine:
      1. Java Development Kit (JDK): JDK 25 or higher.
      2. Oracle JDBC Driver: Download the standalone ojdbc17.jar.
      3. Rancher Desktop: Install and select the dockerd (moby) container engine during setup. This gives you the standard docker CLI command.
        1. You can use other applications other application similar to Rancher Desktop like Docker Desktop, Podman Desktop, Colima, OrbStack.
      4. AWS CLI and eksctl: Install the AWS CLI for basic AWS interactions, and eksctl to simplify cluster provisioning.
      5. Kubernetes CLI (kubectl): To interact with the Amazon EKS Cluster.
    2. The Java Source Code (ProductApiApp.java)
      1. Create the ProductApiApp.java file and copy the following content into it.
        
        import java.io.*;
        import java.net.InetSocketAddress;
        import java.sql.*;
        import com.sun.net.httpserver.*;
        
        public class ProductApiApp {
            // These environment variables are injected by the Kubernetes deployment.yaml
            private static final String DB_URL = System.getenv("DB_URL");
            private static final String DB_USER = System.getenv("DB_USER");
            private static final String DB_PASS = System.getenv("DB_PASS");
        
            public static void main(String[] args) throws Exception {
                if (DB_URL == null || DB_USER == null || DB_PASS == null) {
                    System.err.println("ERROR: Missing DB_URL, DB_USER, or DB_PASS");
                    System.exit(1);
                }
        
                // Bind to 0.0.0.0 (all interfaces) so the Kubernetes LoadBalancer can route traffic to it
                HttpServer server = HttpServer.create(new InetSocketAddress("0.0.0.0", 8080), 0);
                
                // Root Health Check Context
                server.createContext("/", new RootHandler());
                // API Context
                server.createContext("/api/products", new ProductApiHandler());
                
                server.setExecutor(null); 
                server.start();
                System.out.println("API Microservice running on port 8080...");
                System.out.println("Connecting to database using URL: " + DB_URL);
            }
        
            // A simple handler to verify the LoadBalancer is successfully routing traffic to the Pod
            static class RootHandler implements HttpHandler {
                @Override
                public void handle(HttpExchange exchange) throws IOException {
                    System.out.println("[LOG] Health check ping received at /");
                    String response = "Product API is up and running! Access /api/products for data.";
                    exchange.sendResponseHeaders(200, response.length());
                    OutputStream os = exchange.getResponseBody();
                    os.write(response.getBytes());
                    os.close();
                }
            }
        
            static class ProductApiHandler implements HttpHandler {
                @Override
                public void handle(HttpExchange exchange) throws IOException {
                    String method = exchange.getRequestMethod();
                    System.out.println("[LOG] Incoming " + method + " request to /api/products");
        
                    // Enable CORS so the UI microservice and remote callers can fetch data from this API
                    exchange.getResponseHeaders().add("Access-Control-Allow-Origin", "*");
                    exchange.getResponseHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
                    exchange.getResponseHeaders().add("Access-Control-Allow-Headers", "Content-Type");
                    
                    // Handle preflight requests for CORS
                    if ("OPTIONS".equalsIgnoreCase(method)) {
                        exchange.sendResponseHeaders(204, -1);
                        return;
                    }
        
                    exchange.getResponseHeaders().add("Content-Type", "application/json");
                    StringBuilder jsonResponse = new StringBuilder();
                    
                    System.out.println("[LOG] Opening Oracle Database Connection...");
                    try (Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS)) {
                        System.out.println("[LOG] Database Connection Successful.");
                        
                        if ("GET".equalsIgnoreCase(method)) {
                            // READ: List all products
                            jsonResponse.append("[");
                            try (Statement stmt = conn.createStatement();
                                 ResultSet rs = stmt.executeQuery("SELECT id, name, price FROM Product ORDER BY id")) {
                                boolean first = true;
                                while (rs.next()) {
                                    if (!first) jsonResponse.append(",");
                                    jsonResponse.append("{")
                                                .append("\"id\":").append(rs.getInt("id")).append(",")
                                                .append("\"name\":\"").append(rs.getString("name")).append("\",")
                                                .append("\"price\":").append(rs.getDouble("price"))
                                                .append("}");
                                    first = false;
                                }
                            }
                            jsonResponse.append("]");
                        } 
                        else if ("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method) || "DELETE".equalsIgnoreCase(method)) {
                            // Read the ENTIRE request payload (handles multi-line pretty JSON from Postman)
                            InputStreamReader isr = new InputStreamReader(exchange.getRequestBody(), "utf-8");
                            StringBuilder payloadBuilder = new StringBuilder();
                            int b;
                            while ((b = isr.read()) != -1) {
                                payloadBuilder.append((char) b);
                            }
                            String payload = payloadBuilder.toString();
                            
                            String idStr = extractJsonValue(payload, "id");
                            String name = extractJsonValue(payload, "name");
                            String priceStr = extractJsonValue(payload, "price");
        
                            int id = idStr.isEmpty() ? 0 : Integer.parseInt(idStr);
                            double price = priceStr.isEmpty() ? 0.0 : Double.parseDouble(priceStr);
        
                            if ("POST".equalsIgnoreCase(method)) {
                                // CREATE
                                String sql = "INSERT INTO Product (id, name, price) VALUES (?, ?, ?)";
                                try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
                                    pstmt.setInt(1, id);
                                    pstmt.setString(2, name);
                                    pstmt.setDouble(3, price);
                                    pstmt.executeUpdate();
                                }
                                jsonResponse.append("{\"status\": \"Product created successfully\"}");
                            } else if ("PUT".equalsIgnoreCase(method)) {
                                // UPDATE
                                String sql = "UPDATE Product SET name=?, price=? WHERE id=?";
                                try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
                                    pstmt.setString(1, name);
                                    pstmt.setDouble(2, price);
                                    pstmt.setInt(3, id);
                                    pstmt.executeUpdate();
                                }
                                jsonResponse.append("{\"status\": \"Product updated successfully\"}");
                            } else if ("DELETE".equalsIgnoreCase(method)) {
                                // DELETE
                                String sql = "DELETE FROM Product WHERE id=?";
                                try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
                                    pstmt.setInt(1, id);
                                    pstmt.executeUpdate();
                                }
                                jsonResponse.append("{\"status\": \"Product deleted successfully\"}");
                            }
                        }
                        
                        byte[] responseBytes = jsonResponse.toString().getBytes("UTF-8");
                        exchange.sendResponseHeaders(200, responseBytes.length);
                        OutputStream os = exchange.getResponseBody();
                        os.write(responseBytes);
                        os.close();
                        System.out.println("[LOG] Request completed successfully.");
        
                    } catch (SQLException e) {
                        System.err.println("[ERROR] Database failure: " + e.getMessage());
                        String errorJson = "{\"error\":\"" + e.getMessage().replace("\"", "\\\"") + "\"}";
                        byte[] responseBytes = errorJson.getBytes("UTF-8");
                        exchange.sendResponseHeaders(500, responseBytes.length);
                        OutputStream os = exchange.getResponseBody();
                        os.write(responseBytes);
                        os.close();
                    }
                }
        
                // Lightweight JSON parser helper for zero-dependency constraint
                private String extractJsonValue(String json, String key) {
                    if (json == null) return "";
                    String searchKey = "\"" + key + "\"";
                    int start = json.indexOf(searchKey);
                    if (start == -1) return "";
                    start = json.indexOf(":", start) + 1;
                    int end = json.indexOf(",", start);
                    if (end == -1) end = json.indexOf("}", start);
                    if (end == -1) end = json.length();
                    return json.substring(start, end).replace("\"", "").trim();
                }
            }
        }
    3. The Containerization (Dockerfile)
      1. Create a file named Dockerfile in the same directory as your Java code and the ojdbc17.jar file. Compile the code inside the container to avoid installing build dependencies on the local machine.
        
        # Use Eclipse Temurin base image for Java
        FROM eclipse-temurin:25-jdk-jammy
        
        WORKDIR /app
        COPY ProductApiApp.java /app/
        COPY ojdbc17.jar /app/
        
        # Compile the Java application
        RUN javac -cp ojdbc17.jar ProductApiApp.java
        
        EXPOSE 8080
        CMD ["java", "-cp", ".:ojdbc17.jar", "ProductApiApp"]
  2. Deployment Environment - Amazon Elastic Kubernetes Service (Amazon EKS)
    Open PowerShell, Command Prompt, or Zsh, and then sign in to AWS:
    aws sts get-caller-identity
    1. Provision Amazon Elastic Kubernetes Service and Container Registry
      1. Define the variables, and then create the Amazon Elastic Container Registry (ECR) repository, and Amazon EKS Cluster.
        
        AWS_ACCOUNT_ID="123456789012" # REPLACE with your actual AWS Account ID
        AWS_REGION="us-east-1"
        REPO_NAME="oracle-microservices/product-api"
        CLUSTER_NAME="oracle-eks-cluster"
        
        # 1. Create Amazon ECR for Docker images
        aws ecr create-repository \
            --repository-name $REPO_NAME \
            --region $AWS_REGION
        
        # 2. Create EKS Cluster (using eksctl for a streamlined 1-node setup)
        # Note: eksctl automatically provisions VPCs, Subnets, and IAM roles for you.
        eksctl create cluster \
            --name $CLUSTER_NAME \
            --region $AWS_REGION \
            --nodegroup-name standard-workers \
            --node-type t3.medium \
            --nodes 1
        
        # 3. Get kubectl credentials to connect to your new cluster
        aws eks update-kubeconfig --region $AWS_REGION --name $CLUSTER_NAME
    2. Build and Push the Container (Using Rancher Desktop)
      1. Ensure that Rancher Desktop is running, and then run the following commands.
        
        # 1. Configure local Docker/Rancher CLI to authenticate with Amazon ECR
        aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
        
        # 2. Define your full image path
        IMAGE_PATH="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_[REGION.amazonaws.com/$REPO_NAME:v1](https://REGION.amazonaws.com/$REPO_NAME:v1)"
        
        # 3. Build the image locally (Enforce AMD64 architecture for cloud EC2 compatibility)
        docker build --platform linux/amd64 -t $IMAGE_PATH .
        
        # 4. Push the image to AWS ECR
        docker push $IMAGE_PATH
    3. Configure Oracle Wallet and Database Secrets
      1. Oracle Autonomous AI Database on Dedicated Infrastructure uses an mTLS wallet similar to Autonomous AI Database. However, they correspond to a specific dedicated database instance. Download the instance wallet zip file and then extract it locally.
        
        # 1. Upload the Wallet files into Kubernetes as a Secret
        kubectl create secret generic adb-wallet \
          --from-file=./adb-wallet/cwallet.sso \
          --from-file=./adb-wallet/tnsnames.ora \
          --from-file=./adb-wallet/sqlnet.ora
        
        # 2. Upload your Database Credentials as a Secret
        kubectl create secret generic db-credentials \
          --from-literal=username="ADMIN" \
          --from-literal=password="Your_ADB_Password123!"
    4. Deploy to Amazon EKS
      1. Create a file named deployment.yaml. The DB_URL value uses the Oracle TNS alias found in the tnsnames.ora file and points to the wallet directory (/app/wallet) that Kubernetes mounts.
        • Update <your-aws-project-id> inside the image: attribute below to match your actual project ID.
        • Replace my_adb_high with the actual TNS name.
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: product-api
        spec:
          replicas: 2
          selector:
            matchLabels:
              app: product-api
          template:
            metadata:
              labels:
                app: product-api
            spec:
              containers:
              - name: api
                # UPDATE THIS image string with your actual AWS Account ID and Region
                image: <your-aws-account-id>[.dkr.ecr.us-east-1.amazonaws.com/oracle-microservices/product-api:v1](https://.dkr.ecr.us-east-1.amazonaws.com/oracle-microservices/product-api:v1)
                imagePullPolicy: Always  # Forces K8s to download the newest image from ECR
                ports:
                - containerPort: 8080
                # THIS IS WHERE THE ENVIRONMENT VARIABLES ARE SET FOR JAVA
                env:
                - name: DB_URL
                  # Replace 'my_adbd_service' with the exact alias found in your Dedicated ADB tnsnames.ora file
                  value: "jdbc:oracle:thin:@my_adbd_service?TNS_ADMIN=/app/wallet"
                - name: DB_USER
                  valueFrom:
                    secretKeyRef:
                      name: db-credentials
                      key: username
                - name: DB_PASS
                  valueFrom:
                    secretKeyRef:
                      name: db-credentials
                      key: password
                volumeMounts:
                - name: wallet-volume
                  mountPath: /app/wallet
                  readOnly: true
              volumes:
              - name: wallet-volume
                secret:
                  secretName: adb-wallet
        ---
        apiVersion: v1
        kind: Service
        metadata:
          name: api-service
        spec:
          type: LoadBalancer
          ports:
          - port: 80
            targetPort: 8080
          selector:
            app: product-api
    5. Deploy the Application
      Once the EXTERNAL-IP appears, your API is fully accessible over the internet.
      
      kubectl apply -f deployment.yaml
      
      # Monitor the deployment until an EXTERNAL-IP is assigned
      kubectl get services --watch
  3. Interacting with the API

    Now that API is separated from the UI and deployed on Amazon EKS, you can interact with it using any REST client or a decoupled frontend.

    1. Accessing Your Running Application

      Once the EXTERNAL-IP appears, for example, 20.124.x.x, the API is accessible over the internet.

      Even though your Java application is EXPOSE 8080 in the Dockerfile, the Kubernetes Service (api-service defined above) maps the standard web port 80 to the container's port 8080. Therefore, you do not need to specify a port in your URL.

      Access URL Format: http://<EXTERNAL-IP>/api/products

      1. Test it from your terminal:
        curl http://<EXTERNAL-IP>/api/products
    2. Enable OpenAPI Specifications (Optional)

      You can use openai.yaml in Postman or another REST client to interact with the graphical user interface.

      1. Save the following content as openai.yaml. Import the file into Postman, and replace <your-EKS-api-external-ip> with the IP address from the previous step. The AI uses this schema to automatically generate valid JSON payloads and fetch current Oracle data.
        
              openapi: 3.0.0
        info:
          title: Oracle ADB-D Product API
          version: 1.0.0
          description: Full CRUD API to perform operations on the Product table in ADB Dedicated.
        servers:
          - url: http://<your-elb-dns-name>
        paths:
          /api/products:
            get:
              summary: Read all products
              operationId: getProducts
              responses:
                '200':
                  description: A JSON array of products
                  content:
                    application/json:
                      schema:
                        type: array
                        items:
                          $ref: '#/components/schemas/Product'
            post:
              summary: Create a new product
              operationId: createProduct
              requestBody:
                required: true
                content:
                  application/json:
                    schema:
                      $ref: '#/components/schemas/Product'
              responses:
                '200':
                  description: Product created successfully
            put:
              summary: Update an existing product
              operationId: updateProduct
              requestBody:
                required: true
                content:
                  application/json:
                    schema:
                      $ref: '#/components/schemas/Product'
              responses:
                '200':
                  description: Product updated successfully
            delete:
              summary: Delete a product
              operationId: deleteProduct
              requestBody:
                required: true
                content:
                  application/json:
                    schema:
                      type: object
                      properties:
                        id:
                          type: integer
              responses:
                '200':
                  description: Product deleted successfully
        components:
          schemas:
            Product:
              type: object
              properties:
                id:
                  type: integer
                name:
                  type: string
                price:
                  type: number
  4. Cleanup

    After you finish testing, delete the cloud resources to avoid AWS compute costs.

    1. Run the following command for cleaning up the resource.
      
      # 1. Delete the EKS Cluster (eksctl cleanly destroys the CloudFormation stack)
      eksctl delete cluster --name $CLUSTER_NAME --region $AWS_REGION
      
      # 2. Delete the ECR repository (Force deletes the images inside it)
      aws ecr delete-repository --repository-name $REPO_NAME --region $AWS_REGION --force
      
      # Optional: Remove the local kubectl context
      kubectl config delete-context $(kubectl config current-context)