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
- 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.
-- 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
- Impostazione macchina di sviluppo
- Strumenti e librerie: installare le seguenti librerie e gli strumenti sul computer di sviluppo:
- Java Development Kit (JDK): JDK 25 o versione successiva.
- Driver JDBC Oracle: scaricare il file standalone ojdbc17.jar.
- Rancher Desktop: installare e selezionare il motore contenitore dockerd (moby) durante l'impostazione. Ciò consente di ottenere il comando CLI standard
docker.- È possibile utilizzare altre applicazioni simili a Rancher Desktop come Docker Desktop, Podman Desktop, Colima, OrbStack.
- AWS CLI ed eksctl: installare AWS CLI per le interazioni AWS di base e eksctl per semplificare il provisioning dei cluster.
- CLI Kubernetes (kubectl): per interagire con il cluster Amazon EKS.
- Codice sorgente Java (
ProductApiApp.java)- Creare il file
ProductApiApp.javae 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(); } } }
- Creare il file
- La containerzzazione (
Dockerfile)- Creare un file denominato
Dockerfilenella stessa directory del codice Java e del fileojdbc17.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"]
- Creare un file denominato
- Strumenti e librerie: installare le seguenti librerie e gli strumenti sul computer di sviluppo:
- 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- Esegui provisioning del servizio Amazon Elastic Kubernetes e del registro container
- 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
- Definire le variabili, quindi creare il repository ECR (Amazon Elastic Container Registry) e il cluster Amazon EKS.
- Creazione e push del contenitore (utilizzando Rancher Desktop)
- 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
- Assicurarsi che Rancher Desktop sia in esecuzione, quindi eseguire i seguenti comandi.
- Configurare Oracle Wallet e i segreti del database
- 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!"
- 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.
- Distribuisci su Amazon EKS
- Creare un file denominato
deployment.yaml. Il valoreDB_URLutilizza 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'attributoimage:riportato di seguito in modo che corrisponda all'ID progetto effettivo. - Sostituire
my_adb_highcon 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 - Aggiornare
- Creare un file denominato
- Distribuire l'applicazioneUna 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
- Esegui provisioning del servizio Amazon Elastic Kubernetes e del registro container
- 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.
- Accesso all'applicazione in esecuzione
Una volta visualizzato
EXTERNAL-IP, ad esempio20.124.x.x, l'API è accessibile tramite Internet.Anche se l'applicazione Java è
EXPOSE 8080nel Dockerfile, il servizio Kubernetes (api-servicedefinito 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- Testarlo dal terminale:
curl http://<EXTERNAL-IP>/api/products
- Testarlo dal terminale:
- Abilitazione delle specifiche di OpenAPI (facoltativo)
È possibile utilizzare
openai.yamlin Postman o in un altro client REST per interagire con l'interfaccia utente grafica.- 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
- Salvare il contenuto seguente come
- Accesso all'applicazione in esecuzione
- Esegui cleanup
Al termine dei test, elimina le risorse cloud per evitare i costi di calcolo AWS.
- 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)
- Eseguire il comando seguente per eseguire il cleanup della risorsa.