Google Kubernetes-Engine (GKE)

In diesem Thema wird eine Legacy-Standalone-Java-Anwendung zu einem containerisierten Microservice modernisiert, der auf Google Kubernetes Engine (GKE) ausgeführt wird und über ein mTLS-Wallet eine Verbindung zu Oracle Autonomous AI Database herstellt.

Voraussetzungen

In diesem Abschnitt werden die Anforderungen von Oracle AI Database und Tabellen für die Java-Anwendung beschrieben, um eine Verbindung zu Oracle Autonomous AI Database (serverlos) herzustellen und auf die Tabelle Produkt zuzugreifen.

Oracle Autonomous AI Database

Gehen Sie folgendermaßen vor, um eine Oracle Autonomous AI Database bereitzustellen und einen Benutzer und eine Tabelle zu erstellen:
  • Oracle Autonomous AI Database Wallet für die Verbindung.
  • Oracle Database-Benutzerzugangsdaten zum Erstellen einer Datenbanksession und Ausführen von SQL-Befehlen.
  • Konnektivität vom Anwendungsserver zu Oracle AI Database.
  • Eine Produkttabelle in Oracle AI Database.
Führen Sie den folgenden Befehl aus, um die Tabelle Produkt zu erstellen und einen Testdatensatz einzufügen:

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

Implementierung

  1. Entwicklungsmaschinen einrichten
    1. Tools und Librarys: Installieren Sie die folgenden Librarys und Tools auf dem Entwicklungsrechner:
      1. Java Development Kit (JDK): JDK 25 oder höher.
      2. Oracle JDBC-Treiber: Laden Sie die eigenständige Datei ojdbc17.jar herunter.
      3. Rancher Desktop: Installieren Sie die Container-Engine dockerd (moby), und wählen Sie sie beim Setup aus. Dadurch erhalten Sie den standardmäßigen CLI-Befehl docker.
        1. Sie können andere Anwendungen verwenden, die Rancher Desktop ähnlich sind, wie Docker Desktop, Podman Desktop, Colima, OrbStack.
      4. Google Cloud-CLI (gcloud): So stellen Sie Cloud-Ressourcen bereit.
      5. Kubernetes-CLI (kubectl) und GKE-Authentifizierungs-Plugin: So interagieren Sie mit dem GKE-Cluster.
    2. Der Java-Quellcode (ProductApiApp.java)
      1. Erstellen Sie die Datei ProductApiApp.java, und kopieren Sie den folgenden Inhalt in die Datei.
        
        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();
                }
            }
        }
    3. Die Containerisierung (Dockerfile)
      1. Erstellen Sie eine Datei namens Dockerfile in demselben Verzeichnis wie Ihr Java-Code und die Datei ojdbc17.jar. Kompilieren Sie den Code im Container, um Build-Abhängigkeiten auf dem lokalen Rechner zu vermeiden.
        # 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. Bereitstellungsumgebung – Google Kubernetes Engine (GKE)
    Öffnen Sie PowerShell, Eingabeaufforderung oder Zsh, und melden Sie sich dann bei Google Cloud an:
    gcloud auth login
    1. Google Kubernetes Engine und Container Registry bereitstellen
      1. Definieren Sie die Variablen, und erstellen Sie dann das Google Artifact Registry-(GAR-) und das Google Kubernetes Engine-(GKE-)Cluster.
        
        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
    2. Container erstellen und übertragen (mit Rancher Desktop)
      1. Stellen Sie sicher, dass Rancher Desktop ausgeführt wird, und führen Sie dann die folgenden Befehle aus.
        
        # 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
    3. Oracle Wallet und Datenbank-Secrets konfigurieren
      1. Die autonome KI-Datenbank (serverlos) verwendet ein mTLS-Wallet. Laden Sie die ZIP-Datei des Instanz-Wallets über die OCI-Konsole herunter, und extrahieren Sie sie in den lokalen Ordner. Beispiel: ./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!>"
    4. Für GKE bereitstellen
      1. Erstellen Sie eine Datei mit dem Namen deployment.yaml. Der Wert DB_URL verwendet den Oracle TNS-Alias in der Datei tnsnames.ora und verweist auf das Wallet-Verzeichnis (/app/wallet), das von Kubernetes gemountet wird.
        • Aktualisieren Sie <your-gcp-project-id> im nachstehenden Attribut image:, damit es mit Ihrer tatsächlichen Projekt-ID übereinstimmt.
        • Ersetzen Sie my_adb_high durch den tatsächlichen TNS-Namen.
        
        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
    5. Anwendung bereitstellen
      Sobald die EXTERNAL-IP angezeigt wird, ist Ihre API über das Internet vollständig zugänglich.
      
      kubectl apply -f deployment.yaml
      
      # Monitor the deployment until an EXTERNAL-IP is assigned
      kubectl get services --watch
  3. Mit der API interagieren

    Nachdem die API von der Benutzeroberfläche getrennt und in GKE bereitgestellt wurde, können Sie mit jedem REST-Client oder einem entkoppelten Frontend damit interagieren.

    1. Auf Ihre laufende Anwendung zugreifen

      Sobald die EXTERNAL-IP angezeigt wird, z.B. 20.124.x.x, ist die API über das Internet zugänglich.

      Auch wenn Ihre Java-Anwendung in der Dockerfile EXPOSE 8080 lautet, ordnet der Kubernetes-Service (api-service, wie oben definiert) den Standard-Webport 80 dem Containerport 8080 zu. Daher müssen Sie keinen Port in Ihrer URL angeben.

      Format für Zugriffs-URL: http://<EXTERNAL-IP>/api/products

      1. Testen Sie es von Ihrem Terminal aus:
        curl http://<EXTERNAL-IP>/api/products
    2. OpenAPI-Spezifikationen aktivieren (optional)

      Sie können openai.yaml in Postman oder einem anderen REST-Client verwenden, um mit der grafischen Benutzeroberfläche zu interagieren.

      1. Speichern Sie den folgenden Inhalt als openai.yaml. Importieren Sie die Datei in Postman, und ersetzen Sie <your-GKE-api-external-ip> durch die IP-Adresse aus dem vorherigen Schritt. Die KI verwendet dieses Schema, um automatisch gültige JSON-Payloads zu generieren und aktuelle Oracle-Daten abzurufen.
        
        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
              
  4. Bereinigen

    Löschen Sie nach dem Testen die Cloud-Ressourcen, um die Google Cloud-Compute-Kosten zu vermeiden.

    1. Führen Sie den folgenden Befehl zum Bereinigen der Ressource aus.
      
      # 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}