Servicio Azure Kubernetes (AKS)
En este tema, se convierte una aplicación Java independiente heredada en un microservicio en contenedores que se ejecuta en Azure Kubernetes Service (AKS) y se conecta a Oracle Autonomous AI Database mediante una cartera mTLS.
Requisitos
En esta sección se describen los requisitos de Oracle Database y las tablas para que la aplicación Java se conecte a Oracle Autonomous AI Database y acceda a la tabla Producto.
Oracle Autonomous AI Database
- Cartera de Oracle Autonomous AI Database para la conexión.
- Credenciales de usuario de Oracle Database para crear una sesión de base de datos y ejecutar comandos SQL.
- Conectividad del servidor de aplicaciones a Oracle Database.
- Una tabla de productos en Oracle 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;Implantación
- Configuración de máquinas de desarrollo
- Herramientas y bibliotecas: instale las siguientes bibliotecas y herramientas en la máquina de desarrollo:
- Java Development Kit (JDK): JDK 25 o superior.
- Controlador JDBC de Oracle: descargue el archivo ojdbc17.jar independiente.
- Escritorio de Rancher: instale y seleccione el motor de contenedor dockerd (moby) durante la configuración. Esto le proporciona el comando estándar de la CLI
docker.- Puede utilizar otras aplicaciones similares a Rancher Desktop como Docker Desktop, Podman Desktop, Colima, OrbStack.
- CLI de Azure (az): para aprovisionar los recursos en la nube.
- CLI de Kubernetes (kubectl): para interactuar con el cluster de AKS.
- El Código Fuente de Java (
ProductApiApp.java)- Cree el archivo
ProductApiApp.javay copie el siguiente contenido en él.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(); } } }
- Cree el archivo
- La contenedorización (
Dockerfile)- Cree un archivo denominado
Dockerfileen el mismo directorio que el código Java y el archivoojdbc17.jar. Compile el código dentro del contenedor para evitar la instalación de dependencias de compilación en la máquina local.# Use Eclipse Temurin as a highly trusted, industry-standard 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"]
- Cree un archivo denominado
- Herramientas y bibliotecas: instale las siguientes bibliotecas y herramientas en la máquina de desarrollo:
- Entorno de despliegue - Servicio Azure Kubernetes (AKS)Abra PowerShell, el símbolo del sistema o Zsh y, a continuación, conéctese a Azure:
az login- Aprovisionamiento del servicio Azure Kubernetes y Container Registry
- Defina las variables y, a continuación, cree el grupo de recursos de Azure, Azure Container Registry (ACR) y Azure Kubernetes Service (AKS).
RESOURCE_GROUP="oracle-aks-rg" LOCATION="eastus" ACR_NAME="mycompanyacr123" # Must be globally unique AKS_NAME="oracle-aks-cluster" # 1. Create Resource Group az group create --name $RESOURCE_GROUP --location $LOCATION # 2. Create Azure Container Registry az acr create --resource-group $RESOURCE_GROUP --name $ACR_NAME --sku Basic # 3. Create AKS Cluster and attach the ACR (so AKS can pull your images) az aks create \ --resource-group $RESOURCE_GROUP \ --name $AKS_NAME \ --node-count 1 \ --generate-ssh-keys \ --attach-acr $ACR_NAME # 4. Get kubectl credentials to connect to your new cluster az aks get-credentials --resource-group $RESOURCE_GROUP --name $AKS_NAME
- Defina las variables y, a continuación, cree el grupo de recursos de Azure, Azure Container Registry (ACR) y Azure Kubernetes Service (AKS).
- Creación y transferencia del contenedor (mediante el escritorio Rancher)
- Asegúrese de que Rancher Desktop se esté ejecutando y, a continuación, ejecute los siguientes comandos.
# 1. Log into Azure ACR az acr login --name $ACR_NAME # 2. Build the image docker build --platform linux/amd64 -t $ACR_NAME.azurecr.io/product-api:v1 . # 3. Push the image to ACR docker push $ACR_NAME.azurecr.io/product-api:v1
- Asegúrese de que Rancher Desktop se esté ejecutando y, a continuación, ejecute los siguientes comandos.
- Configuración de Secretos de Base de Datos y Oracle Wallet
- Oracle Autonomous AI Database utiliza una cartera mTLS. Descargue el archivo zip de cartera de instancia de la consola de OCI y, a continuación, extráigalo en una carpeta local. Por ejemplo,
./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_Password>"
- Oracle Autonomous AI Database utiliza una cartera mTLS. Descargue el archivo zip de cartera de instancia de la consola de OCI y, a continuación, extráigalo en una carpeta local. Por ejemplo,
- Desplegar en AKS
- Cree un archivo denominado
deployment.yaml. El valorDB_URLutiliza el alias de TNS de Oracle encontrado en el archivo tnsnames.ora y apunta al directorio de cartera (/app/wallet) que monta Kubernetes.- Sustituya el punto final del registro de contenedor.
- Sustituya
my_adb_highpor el nombre de TNS real.
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 image: mycompanyacr123.azurecr.io/product-api:v1 # UPDATE THIS to your ACR name imagePullPolicy: Always # Forces Kubernetes to download the newest image from ACR ports: - containerPort: 8080 env: - name: DB_URL # Replace 'my_adb_high' with the actual alias from your tnsnames.ora 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
- Cree un archivo denominado
- Desplegar la AplicaciónUna vez que aparezca
EXTERNAL-IP, se podrá acceder completamente a la API a través de Internet.kubectl apply -f deployment.yaml # Monitor the deployment until an EXTERNAL-IP is assigned kubectl get services --watch
- Aprovisionamiento del servicio Azure Kubernetes y Container Registry
- Interacción con la API
Ahora que la API está separada de la interfaz de usuario y desplegada en AKS, puede interactuar con ella mediante cualquier cliente REST o un frontend desacoplado.
- Acceso a la aplicación en ejecución
Una vez que aparece
EXTERNAL-IP, por ejemplo,20.124.x.x, se puede acceder a la API a través de Internet.Aunque la aplicación Java sea
EXPOSE 8080en Dockerfile, el servicio de Kubernetes (api-servicedefinido anteriormente) asigna el puerto web estándar 80 al puerto del contenedor 8080. Por lo tanto, no es necesario especificar un puerto en la URL.Formato de URL de acceso:
http://<EXTERNAL-IP>/api/products- Pruébelo desde su terminal:
curl http://<EXTERNAL-IP>/api/products
- Pruébelo desde su terminal:
- Activar especificaciones de OpenAPI (opcional)
Puede utilizar
openai.yamlen Postman u otro cliente REST para interactuar con la interfaz gráfica de usuario.- Guarde el siguiente contenido como
openai.yaml. Importe el archivo en Postman y sustituya<your-aks-api-external-ip>por la dirección IP del paso anterior. AI utiliza este esquema para generar automáticamente cargas útiles de JSON válidas y recuperar los datos actuales de Oracle.openapi: 3.0.0 info: title: Oracle Product API version: 1.0.0 description: Full CRUD API to perform operations on the Product table. servers: - url: http://<your-aks-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
- Guarde el siguiente contenido como
- Acceso a la aplicación en ejecución
- Limpieza
Después de finalizar la prueba, suprima los recursos en la nube para evitar los costos de recursos informáticos de Azure. Debido a que los recursos se encuentran en un único grupo de recursos, puede limpiar los recursos mediante un único comando.
- Ejecute el siguiente comando para limpiar el recurso.
# Delete pods kubectl delete pods -l app=product-api # Delete the entire resource group (AKS, ACR, Disks, Load Balancers, and IPs) az group delete --name oracle-aks-rg --yes --no-wait # Optional: Remove the local kubectl context kubectl config delete-context oracle-aks-cluster
- Ejecute el siguiente comando para limpiar el recurso.