Motore Google Kubernetes (GKE)
Questo argomento modernizza un'applicazione Java standalone precedente in un microservizio containerizzato eseguito su Google Kubernetes Engine (GKE) e si connette a Oracle Autonomous AI Database utilizzando un wallet mTLS.
Requisiti indispensabili
Questa sezione descrive i requisiti di Oracle AI Database e le tabelle per l'applicazione Java per connettersi a Oracle Autonomous AI Database (serverless) 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.
- CLI di Google Cloud (gcloud): per eseguire il provisioning delle risorse cloud.
- Kubernetes CLI (kubectl) e Plugin di autenticazione GKE: per interagire con il cluster GKE.
- 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); 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); } static class ProductApiHandler implements HttpHandler { @Override public void handle(HttpExchange exchange) throws IOException { // 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(exchange.getRequestMethod())) { exchange.sendResponseHeaders(204, -1); return; } exchange.getResponseHeaders().add("Content-Type", "application/json"); String method = exchange.getRequestMethod(); StringBuilder jsonResponse = new StringBuilder(); try (Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS)) { 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(); } catch (SQLException e) { 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 // Updated to handle arbitrary spaces and multi-line structures 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 - Google Kubernetes Engine (GKE)Aprire PowerShell, Prompt dei comandi o Zsh, quindi accedere a Google Cloud:
gcloud auth login- Esegui provisioning di Google Kubernetes Engine e Container Registry
- Definire le variabili, quindi creare il GAR (Google Artifact Registry) e il cluster GKE (Google Kubernetes Engine).
PROJECT_ID="your-gcp-project-id" # REPLACE with your actual GCP Project ID REGION="us-central1" REPO_NAME="mycompanyrepo123" CLUSTER_NAME="oracle-gke-cluster" # 1. Set the active project gcloud config set project $PROJECT_ID # 2. Enable Required APIs (Artifact Registry and Kubernetes Engine) gcloud services enable artifactregistry.googleapis.com container.googleapis.com # 3. Create Google Artifact Registry (GAR) for Docker images gcloud artifacts repositories create $REPO_NAME \ --repository-format=docker \ --location=$REGION \ --description="Docker repository for Oracle microservices" # 4. Create GKE Cluster (Standard, 1 node for testing) gcloud container clusters create $CLUSTER_NAME \ --region=$REGION \ --num-nodes=1 # 5. Get kubectl credentials to connect to your new cluster gcloud container clusters get-credentials $CLUSTER_NAME --region=$REGION
- Definire le variabili, quindi creare il GAR (Google Artifact Registry) e il cluster GKE (Google Kubernetes Engine).
- 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 Google Artifact Registry gcloud auth configure-docker $REGION-docker.pkg.dev # 2. Define your full image path IMAGE_PATH="$REGION-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/product-api:v1" # 3. Build the image locally (Enforce AMD64 architecture for cloud compatibility) docker build --platform linux/amd64 -t $IMAGE_PATH . # 4. Push the image to Google Cloud docker push $IMAGE_PATH
- Assicurarsi che Rancher Desktop sia in esecuzione, quindi eseguire i seguenti comandi.
- Configurare Oracle Wallet e i segreti del database
- Autonomous AI Database (Serverless) utilizza un wallet mTLS. Scaricare il file zip del wallet dell'istanza da OCI Console, quindi estrarlo nella cartella locale. Ad esempio,
./adb-wallet.# 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!>"
- Autonomous AI Database (Serverless) utilizza un wallet mTLS. Scaricare il file zip del wallet dell'istanza da OCI Console, quindi estrarlo nella cartella locale. Ad esempio,
- Distribuisci su GKE
- 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-gcp-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 project ID image: us-central1-docker.pkg.dev/<your-gcp-project-id>/mycompanyrepo123/product-api:v1 imagePullPolicy: Always # Forces K8s to download the newest image from GAR ports: - containerPort: 8080 # THIS IS WHERE THE ENVIRONMENT VARIABLES ARE SET FOR JAVA env: - name: DB_URL # The '?TNS_ADMIN=/app/wallet' parameter tells the JDBC driver where to look for the wallet. value: "jdbc:oracle:thin:@my_adb_high?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 di Google Kubernetes Engine e Container Registry
- Interazione con l'API
Ora che l'API è separata dall'interfaccia utente e distribuita su GKE, 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-GKE-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-S Product API version: 1.0.0 description: Full CRUD API to perform operations on the Product table. servers: - url: http://<your-gke-api-external-ip> 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 computazione di Google Cloud.
- Eseguire il comando seguente per eseguire il cleanup della risorsa.
# 1. Delete the GKE Cluster gcloud container clusters delete $CLUSTER_NAME --region=$REGION --quiet # 2. Delete the Artifact Registry repository gcloud artifacts repositories delete $REPO_NAME --location=$REGION --quiet # Optional: Remove the local kubectl context kubectl config delete-context gke_${PROJECT_ID}_${REGION}_${CLUSTER_NAME}
- Eseguire il comando seguente per eseguire il cleanup della risorsa.