Servizio Amazon Elastic Kubernetes (Amazon EKS)

Questo argomento modernizza un'applicazione Java standalone legacy in un microservizio containerizzato eseguito su Amazon Elastic Kubernetes Service (Amazon EKS) e si connette a Oracle Autonomous AI Database on Dedicated Infrastructure utilizzando un wallet mTLS.

Requisiti indispensabili

In questa sezione vengono descritti i requisiti di Oracle AI Database e le tabelle per l'applicazione Java per connettersi a Oracle Autonomous AI Database (dedicato) e accedere alla tabella Prodotto.

Oracle Autonomous AI Database

Completare i passi riportati di seguito per eseguire il provisioning di Oracle Autonomous AI Database e creare un utente e una tabella.
  • Wallet Oracle Autonomous AI Database per la connessione.
  • Credenziali utente di Oracle Database per creare una sessione del database ed eseguire comandi SQL.
  • Connettività dall'Application Server a Oracle AI Database.
  • Una tabella dei prodotti in Oracle AI Database.
Eseguire il comando riportato di seguito per creare la tabella Product e inserire un record di test.

-- 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;

Implementazione

  1. Impostazione macchina di sviluppo
    1. Strumenti e librerie: installare le seguenti librerie e gli strumenti sul computer di sviluppo:
      1. Java Development Kit (JDK): JDK 25 o versione successiva.
      2. Driver JDBC Oracle: scaricare il file standalone ojdbc17.jar.
      3. Rancher Desktop: installare e selezionare il motore contenitore dockerd (moby) durante l'impostazione. Ciò consente di ottenere il comando CLI standard docker.
        1. È possibile utilizzare altre applicazioni simili a Rancher Desktop come Docker Desktop, Podman Desktop, Colima, OrbStack.
      4. AWS CLI ed eksctl: installare AWS CLI per le interazioni AWS di base e eksctl per semplificare il provisioning dei cluster.
      5. CLI Kubernetes (kubectl): per interagire con il cluster Amazon EKS.
    2. Codice sorgente Java (ProductApiApp.java)
      1. Creare il file ProductApiApp.java e copiarvi il contenuto seguente.
        
        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. La containerzzazione (Dockerfile)
      1. Creare un file denominato Dockerfile nella stessa directory del codice Java e del file ojdbc17.jar. Compilare il codice all'interno del contenitore per evitare di installare dipendenze di build sul computer locale.
        
        # 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. Ambiente di distribuzione - Servizio Amazon Elastic Kubernetes (Amazon EKS)
    Aprire PowerShell, Prompt dei comandi o Zsh, quindi accedere ad AWS:
    aws sts get-caller-identity
    1. Esegui provisioning del servizio Amazon Elastic Kubernetes e del registro container
      1. Definire le variabili, quindi creare il repository ECR (Amazon Elastic Container Registry) e il cluster Amazon EKS.
        
        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. Creazione e push del contenitore (utilizzando Rancher Desktop)
      1. Assicurarsi che Rancher Desktop sia in esecuzione, quindi eseguire i seguenti comandi.
        
        # 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. Configurare Oracle Wallet e i segreti del database
      1. Oracle Autonomous AI Database on Dedicated Infrastructure utilizza un wallet mTLS simile ad Autonomous AI Database. Tuttavia, corrispondono a un'istanza di database dedicata specifica. Scaricare il file zip del wallet dell'istanza, quindi estrarlo localmente.
        
        # 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. Distribuisci su Amazon EKS
      1. Creare un file denominato deployment.yaml. Il valore DB_URL utilizza l'alias Oracle TNS presente nel file tnsnames.ora e punta alla directory wallet (/app/wallet) di cui Kubernetes esegue l'installazione.
        • Aggiornare <your-aws-project-id> all'interno dell'attributo image: riportato di seguito in modo che corrisponda all'ID progetto effettivo.
        • Sostituire my_adb_high con il nome TNS effettivo.
        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. Distribuire l'applicazione
      Una volta visualizzato EXTERNAL-IP, l'API è completamente accessibile tramite Internet.
      
      kubectl apply -f deployment.yaml
      
      # Monitor the deployment until an EXTERNAL-IP is assigned
      kubectl get services --watch
  3. Interazione con l'API

    Ora che l'API è separata dall'interfaccia utente e distribuita su Amazon EKS, puoi interagire con essa utilizzando qualsiasi client REST o un frontend disaccoppiato.

    1. Accesso all'applicazione in esecuzione

      Una volta visualizzato EXTERNAL-IP, ad esempio 20.124.x.x, l'API è accessibile tramite Internet.

      Anche se l'applicazione Java è EXPOSE 8080 nel Dockerfile, il servizio Kubernetes (api-service definito sopra) mappa la porta Web standard 80 alla porta del contenitore 8080. Pertanto, non è necessario specificare una porta nell'URL.

      Formato URL di accesso: http://<EXTERNAL-IP>/api/products

      1. Testarlo dal terminale:
        curl http://<EXTERNAL-IP>/api/products
    2. Abilitazione delle specifiche di OpenAPI (facoltativo)

      È possibile utilizzare openai.yaml in Postman o in un altro client REST per interagire con l'interfaccia utente grafica.

      1. Salvare il contenuto seguente come openai.yaml. Importare il file in Postman e sostituire <your-EKS-api-external-ip> con l'indirizzo IP del passo precedente. L'intelligenza artificiale utilizza questo schema per generare automaticamente payload JSON validi e recuperare i dati Oracle correnti.
        
              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. Esegui cleanup

    Al termine dei test, elimina le risorse cloud per evitare i costi di calcolo AWS.

    1. Eseguire il comando seguente per eseguire il cleanup della risorsa.
      
      # 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)