Azure Kubernetesサービス(AKS)
このトピックでは、レガシー・スタンドアロンJavaアプリケーションを、Azure Kubernetes Service (AKS)上で実行され、mTLSウォレットを使用してOracle Autonomous AI Databaseに接続するコンテナ化されたマイクロサービスに変換します。
前提条件
この項では、Oracle Databaseの要件と、JavaアプリケーションがOracle Autonomous AI Databaseに接続し、「製品」表にアクセスするための表について説明します。
Oracle Autonomous AI Database
- 接続用のOracle Autonomous AI Database Wallet。
- データベース・セッションを作成してSQLコマンドを実行するためのOracle Databaseユーザー資格証明。
- アプリケーション・サーバーからOracle Databaseへの接続。
- 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;実装
- 開発マシン設定
- ツールとライブラリ:次のライブラリとツールを開発マシンにインストールします。
- Java Development Kit (JDK): JDK 25以上。
- Oracle JDBCドライバ:スタンドアロンojdbc17.jarをダウンロードします。
- Rancher Desktop:設定中にdockerd (moby)コンテナ・エンジンをインストールして選択します。これにより、標準の
dockerCLIコマンドが提供されます。- Rancher Desktopに似た他のアプリケーション(Docker Desktop、Podman Desktop、Colima、OrbStackなど)を使用できます。
- Azure CLI (az): クラウド・リソースをプロビジョニングします。
- Kubernetes CLI (kubectl): AKSクラスタと対話します。
- Javaソース・コード(
ProductApiApp.java)ProductApiApp.javaファイルを作成し、そのファイルに次の内容をコピーします。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(); } } }
- コンテナ化(
Dockerfile)- Javaコードおよび
ojdbc17.jarファイルと同じディレクトリにDockerfileという名前のファイルを作成します。ローカル・マシンにビルド依存性をインストールしないように、コンテナ内のコードをコンパイルします。# 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"]
- Javaコードおよび
- ツールとライブラリ:次のライブラリとツールを開発マシンにインストールします。
- デプロイメント環境- Azure Kubernetesサービス(AKS)PowerShell、Command PromptまたはZshを開き、Azureにサインインします。
az login- Azure Kubernetesサービスおよびコンテナ・レジストリのプロビジョニング
- 変数を定義し、Azureリソース・グループ、Azure Container Registry (ACR)および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
- 変数を定義し、Azureリソース・グループ、Azure Container Registry (ACR)およびAzure Kubernetes Service (AKS)を作成します。
- コンテナの構築およびプッシュ(Rancher Desktopの使用)
- Rancher Desktopが実行されていることを確認してから、次のコマンドを実行します。
# 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
- Rancher Desktopが実行されていることを確認してから、次のコマンドを実行します。
- Oracle Walletおよびデータベース・シークレットの構成
- Oracle Autonomous AI DatabaseはmTLSウォレットを使用します。インスタンス・ウォレット・チップ・ファイルをOCIコンソールからダウンロードし、ローカル・フォルダに抽出します。たとえば、
./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はmTLSウォレットを使用します。インスタンス・ウォレット・チップ・ファイルをOCIコンソールからダウンロードし、ローカル・フォルダに抽出します。たとえば、
- AKSへのデプロイ
deployment.yamlという名前のファイルを作成します。DB_URL値は、tnsnames.oraファイルにあるOracle TNS別名を使用し、Kubernetesがマウントするウォレット・ディレクトリ(/app/wallet)を指します。- コンテナ・レジストリ・エンドポイントを置換します。
my_adb_highを実際のTNS名に置き換えます。
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
- アプリケーションの導入
EXTERNAL-IPが表示されたら、APIにインターネット経由で完全にアクセスできます。kubectl apply -f deployment.yaml # Monitor the deployment until an EXTERNAL-IP is assigned kubectl get services --watch
- Azure Kubernetesサービスおよびコンテナ・レジストリのプロビジョニング
- APIとの対話
APIがUIから分離され、AKSにデプロイされたため、任意のRESTクライアントまたは分離されたフロントエンドを使用してAPIと対話できます。
- 実行中のアプリケーションへのアクセス
EXTERNAL-IP(20.124.x.xなど)が表示されると、APIはインターネットを介してアクセス可能になります。JavaアプリケーションがDockerfileの
EXPOSE 8080であっても、Kubernetesサービス(前述で定義したapi-service)は標準のWebポート80をコンテナのポート8080にマップします。したがって、URLにポートを指定する必要はありません。アクセスURL形式:
http://<EXTERNAL-IP>/api/products- ターミナルからテストします。
curl http://<EXTERNAL-IP>/api/products
- ターミナルからテストします。
- OpenAPI指定の有効化(オプション)
Postmanまたは別のRESTクライアントで
openai.yamlを使用して、グラフィカル・ユーザー・インタフェースと対話できます。- 次のコンテンツを
openai.yamlとして保存します。ファイルをPostmanにインポートし、<your-aks-api-external-ip>を前のステップのIPアドレスに置き換えます。AIは、このスキーマを使用して、有効なJSONペイロードを自動的に生成し、現在の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
- 次のコンテンツを
- 実行中のアプリケーションへのアクセス
- 消去
テストが終了したら、クラウド・リソースを削除してAzureのコンピュート・コストを回避します。リソースは単一のリソースグループ内にあるため、単一のコマンドを使用してリソースをクリーンアップできます。
- 次のコマンドを実行してリソースをクリーンアップします。
# 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
- 次のコマンドを実行してリソースをクリーンアップします。