Amazon Elastic Kubernetes-Service (Amazon EKS)

In diesem Thema wird eine Legacy-Standalone-Java-Anwendung zu einem containerisierten Microservice modernisiert, der auf Amazon Elastic Kubernetes Service (Amazon EKS) ausgeführt wird und über ein mTLS-Wallet eine Verbindung zu Oracle Autonomous AI Database auf dedizierter Infrastruktur 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 (Dedicated) 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. AWS-CLI und eksctl: Installieren Sie die AWS-CLI für grundlegende AWS-Interaktionen, und eksctl, um das Cluster-Provisioning zu vereinfachen.
      5. Kubernetes-CLI (kubectl): So interagieren Sie mit dem Amazon EKS-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);
                
                // 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. 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 – Amazon Elastic Kubernetes Service (Amazon EKS)
    Öffnen Sie PowerShell, die Eingabeaufforderung oder Zsh, und melden Sie sich bei AWS an:
    aws sts get-caller-identity
    1. Amazon Elastic Kubernetes-Service und Container Registry bereitstellen
      1. Definieren Sie die Variablen, und erstellen Sie dann das Amazon Elastic Container Registry-(ECR-)Repository und das 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. 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 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. Oracle Wallet und Datenbank-Secrets konfigurieren
      1. Oracle Autonomous AI Database on Dedicated Infrastructure verwendet ein mTLS-Wallet, das der autonomen KI-Datenbank ähnelt. Sie entsprechen jedoch einer bestimmten dedizierten Datenbankinstanz. Laden Sie die ZIP-Datei des Instanz-Wallets herunter, und extrahieren Sie sie lokal.
        
        # 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 Amazon EKS 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-aws-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 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. 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 UI getrennt und auf Amazon EKS 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-EKS-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-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. Bereinigen

    Löschen Sie nach Abschluss des Tests die Cloud-Ressourcen, um AWS-Compute-Kosten zu vermeiden.

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