[Docker, WLS, Kubernetes, Java] WebLogic Server JTA in a Kubernetes Environment

原文はこちら。
https://blogs.oracle.com/weblogicserver/weblogic-server-jta-in-a-kubernetes-environment

このエントリでは、Kubernetes環境で実行しているWebLogic Serverのグローバルトランザクションについて説明します。まず、WebLogic Server Transaction Manager(TM)が分散トランザクションをどのように処理するかを見ていきます。続いて、WebLogic Kubernetes Operatorを使用して、Kubernetesクラスタで実行されているWebLogic Serverドメインにデプロイされているトランザクションアプリケーションのサンプルを見ていきます。

WebLogic Server Transaction Manager

Introduction

WebLogic Server Transaction Manager (TM) はWebLogic Serverのトランザクション処理の監視実装で、Java Enterprise Edition(Java EE)のJava Transaction API(JTA)をサポートしています。Java EE applicationはJTAを使いグローバルトランザクションを管理して、データベースやメッセージングシステムといったリソースマネージャへの変更がユニットとして完成するか、もしくはその変更を元に戻すかを保証します。

このセクションでは、WebLogic Server Transaction Managerの概要を説明します。具体的には、ネットワーク通信と関連するコンフィグレーションに関する部分で、Kubernetes環境でのトランザクションを調べる際に役立ちます。このエントリでは扱わない多くのTransaction Managerの機能、最適化、および設定オプションがありますので、追加の詳細については、以下のWebLogic Serverドキュメントを参照してください。

How Transactions are Processed in WebLogic Server

WebLogic Server Transaction Managerがトランザクションをどのように処理するかについての基本的な理解を得るために、トランザクションを開始し、データベース表にレコードを挿入し、Java Messaging Service(JMS)キューの宛先にメッセージを送信するというServletで構成されるWebアプリケーションを考えます。JDBCおよびJMSリソースを更新した後、サーブレットはトランザクションをコミットします。下図は、サーバーおよびリソースのトランザクション参加者を示しています。

Transaction Propagation(トランザクションの伝播)
トランザクションコンテキストは、サーバー間およびアプリケーションがリソースにアクセスするときに状態を構築します。このアプリケーションでは、コミット時のトランザクションコンテキストは次のようになります。

ドメイン名とサーバー名で識別されるサーバー参加者には、Transaction Managerとの内部通信に使用される関連URLがあります。これらのURLは通常、サーバーのデフォルトネットワークチャネルまたはデフォルトのセキュアネットワークチャネルから取得されます。

トランザクション・コンテキストには、javax.transaction.Synchronizationコールバックを登録したサーバー参加者に関する情報も含まれています。JTA Synchronization APIは、トランザクションの2フェーズコミット処理を開始する前に、Transaction ManagerがSynchronization.beforeCompletion()メソッドを呼び出すコールバックメカニズムです。トランザクションの最終ステータス(例えばコミット、ロールバックなど)を伴ってトランザクションの処理が完了した後で、Synchronization.afterCompletion(int status)メソッドを呼び出します。

Transaction Completion
トランザクションをコミットするように指示されたTransaction Managerは、トランザクションの完了を調整します。サーバー参加者の1つを、2フェーズコミットプロトコルを駆動するためのトランザクション・コーディネータとして選択します。トランザクション・コーディネータは、残りの従属サーバーに、登録済みの同期コールバックの処理と、リソースの準備(prepare)、コミット、またはロールバックを指示します。下図では、トランザクションの例を調整するために使われるTransaction Managerとの通信チャネルを説明しています。

破線の矢印は、トランザクション・コーディネータと従属サーバ間の非同期RMI呼び出しを表します。Synchronization.beforeCompletion()の通信は、従属サーバー間で直接行うことができる点にご注意ください。Transaction Managerは、トランザクションを伝播するためにアプリケーションが使用しなかったネットワークチャネルを確立する可能性があるため、アプリケーション通信がTransaction Managerの内部通信と概念的に分離されていることも重要です。Transaction Managerは、サーバーのデフォルトネットワークチャネルの構成方法に応じて、異なるプロトコル、アドレス、およびポートを使用できます。

Configuration Recommendations

サーバーネットワークアドレス、永続ストレージ、サーバー命名規則に関連するTransaction Managerの推奨事項があります。

Server Network Addresses
前述のように、サーバー参加者は、トランザクション・コンテキストに含まれているURLを使用して相互に探索します。Transaction ManagerのURLに使用されるネットワークチャネルは、IPアドレスが変更される可能性があるノード、Pod、またはコンテナの再起動後に解決可能なアドレス名で構成することが重要です。また、Transaction Managerは直接のサーバー間通信を必要とするため、複数のIPアドレスに解決されるクラスタまたはロードバランサのアドレスは使用しないでください。

Transaction Logs
コーディネート・サーバーは、障害後のトランザクション・リカバリー処理に使用されるトランザクション・ログ(TLOG)に状態を保持します。サーバー・インスタンスが別のノードに再配置される可能性があるため、TLOGはNetwork File SystemやReplicated File System(NFS、SANなど)またはOracle RACなどの高可用性データベースに存在する必要があります。追加情報については、以下のドキュメントをご覧ください。
Oracle® Fusion Middleware高可用性ガイド 12c (12.2.1.3.0)
JMSおよびJTAの高可用性
https://docs.oracle.com/cd/E92951_01/lcm/ASHIA/GUID-C4750828-F83A-442A-8BFE-EE2F2D3945EE.htm#GUID-C4750828-F83A-442A-8BFE-EE2F2D3945EE
Oracle® Fusion Middleware High Availability Guide 12c (12.2.1.3.0)
JMS and JTA High Availability
https://docs.oracle.com/en/middleware/lifecycle/12.2.1.3/ashia/jms-and-jta-high-availability.html
Cross-Domain Transactions
WebLogic Serverドメインをまたがるトランザクションは、クロスドメイン・トランザクションと呼ばれます。クロスドメイン・トランザクションでは、特にドメインがパブリックネットワークで接続されている場合に、追加の構成要件が導入されます。

Server Naming
Transaction Managerは、ドメイン名とサーバー名の組み合わせを使用してサーバー参加者を識別します。 したがって、名前の衝突を防ぐために、各ドメインの名前を一意にする必要があります。 サーバー参加者名が衝突すると、実行時にトランザクションがロールバックされます。

Security
パブリックネットワークで接続されているサーバー参加者は、セキュアなプロトコル(例:t3s)と認証チェックを使用して、Transaction Manager通信が正当なものであることを確認する必要があります。 このデモではこれらのトピックについて詳しく説明しませんが、Kubernetesのサンプルアプリケーションでは、すべてのTransaction Manager通信はプライベートKubernetesネットワーク上で行われ、非SSLプロトコルを使用します。

クロスドメイン・トランザクションのセキュリティ設定の詳細は、以下のドキュメントをご覧ください。
Oracle® Fusion Middleware Oracle WebLogic Server JTAアプリケーションの開発 12c (12.2.1.3.0)
ドメイン間およびドメイン内トランザクションのセキュアな通信の構成
https://docs.oracle.com/cd/E92951_01/wls/WLJTA/trxsecurity.htm
Oracle® Fusion Middleware Developing JTA Applications for Oracle WebLogic Server 12c (12.2.1.3.0)
Configuring Secure Inter-Domain and Intra-Domain Transaction Communication
https://docs.oracle.com/middleware/12213/wls/WLJTA/trxsecurity.htm

WebLogic Server on Kubernetes

KubernetesとのWebLogic Serverの統合を改善するため、オープンソースのWebLogic Kubernetes OperatorをOracleはリリースしました。WebLogic Kubernetes Operatorは、WebLogic Serverドメインの作成と管理、さまざまなロードバランサとの統合、および追加機能をサポートしています。詳細については、GitHubプロジェクトページと、関連するブログを参照してください。
Oracle Weblogic Server Kubernetes Operator
https://github.com/oracle/weblogic-kubernetes-operator
How to... WebLogic Server on Kubernetes
https://blogs.oracle.com/weblogicserver/how-to-weblogic-server-on-kubernetes
https://orablogs-jp.blogspot.com/2018/01/how-to-weblogic-server-on-kubernetes.html

Example Transactional Application Walkthrough

Kubernetesで分散トランザクションを実行する方法を説明するために、単一のKubernetesクラスタ内で動作し、複数のWebLogic Serverドメインにデプロイされている、単純なトランザクションアプリケーションを考えます。この例で使用した環境は、Kubernetes v1.9.6を含むDocker Edge v18.05.0-ceを実行しているMacです。

Docker Edgeをインストールして起動後、Preferencesページを開き、[Advanced]タブでDockerで使用可能なメモリを増やし(〜8 GB)、[Kubernetes]タブでKubernetesを有効にします。変更を適用すると、DockerとKubernetesが開始します。ファイアウォールの内側にいる場合は、[Proxy]タブで適切な設定を追加する必要があります。実行すると、Kubernetesのバージョン情報を表示できます。
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"9", GitVersion:"v1.9.6", GitCommit:"9f8ebd171479bec0ada837d7ee641dec2f8c6dd1", GitTreeState:"clean", BuildDate:"2018-03-21T15:21:50Z", GoVersion:"go1.9.3", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"9", GitVersion:"v1.9.6", GitCommit:"9f8ebd171479bec0ada837d7ee641dec2f8c6dd1", GitTreeState:"clean", BuildDate:"2018-03-21T15:13:31Z", GoVersion:"go1.9.3", Compiler:"gc", Platform:"linux/amd64"}
サンプルファイルのシステムのパス名を短縮するため、入力ファイル、Operatorのソースとバイナリ、永続ボリュームなどの作業ディレクトリは$HOME/k8sopの下に作成します。環境変数$K8SOPを使用してディレクトリを参照できます。
$ export K8SOP=$HOME/k8sop
$ mkdir $K8SOP

Install the WebLogic Kubernetes Operator

続いて、weblogic-kubernetes-operatorイメージをビルドしてインストールします。以下の手順を参照してインストールします。
WebLogic Server Kubernetes Operator - Installation
https://github.com/oracle/weblogic-kubernetes-operator/blob/master/site/installation.md
この例では、weblogic-kubernetes-operator GitHubプロジェクトが$K8SOP/srcディレクトリ($K8SOP/src/weblogic-kubernetes-operator)の下に複製されます。また、Dockerイメージを構築するときは、インストールドキュメントで指定されているsome-tagの代わりにlocalタグを使用してください。
$ mkdir $K8SOP/src
$ cd $K8SOP/src
$ git clone <a href="https://github.com/oracle/weblogic-kubernetes-operator.git">https://github.com/oracle/weblogic-kubernetes-operator.git</a>
$ cd weblogic-kubernetes-operator
$ mvn clean install
$ docker login
$ docker build -t weblogic-kubernetes-operator:local --no-cache=true .
Operatorイメージのビルド後、ローカルレジストリで以下の内容を確認できるはずです。
$ docker images weblogic-kubernetes-operator
REPOSITORY                     TAG                 IMAGE ID            CREATED             SIZE
weblogic-kubernetes-operator   local               42a5f70c7287        10 seconds ago      317MB
続いて、OperatorをKubernetesクラスターにデプロイします。この例では、create-weblogic-operator-inputs.yamlファイルを変更し、追加のターゲット名前空間(weblogic)を追加し、正しいOperatorイメージ名を指定します。


AttributeValue
targetNamespacesdefault,weblogic
weblogicOperatorImageweblogic-kubernetes-operator:local
javaLoggingLevelWARNING

修正した入力ファイルを$K8SOP/create-weblogic-operator-inputs.yamlに保存します。

次に、create-weblogic-operator.shスクリプトを実行して、変更したcreate-weblogic-operator.yaml入力ファイルへのパスとOperatorの出力ディレクトリのパスを指定します。
create-weblogic-operator.shスクリプトを実行して、変更したcreate-weblogic-operator.yaml入力ファイルへのパスとOperatorの出力ディレクトリのパスを指定します。
$ cd $K8SOP
$ mkdir weblogic-kubernetes-operator
$ $K8SOP/src/weblogic-kubernetes-operator/kubernetes/create-weblogic-operator.sh -i $K8SOP/create-weblogic-operator-inputs.yaml -o $K8SOP/weblogic-kubernetes-operator
スクリプト完了後、以下のようにOperator Podが実行中であると確認できるでしょう。
$ kubectl get po -n weblogic-operator
NAME                                 READY     STATUS    RESTARTS   AGE
weblogic-operator-6dbf8bf9c9-prhwd   1/1       Running   0          44s

WebLogic Domain Creation

WebLogic Serverドメインを作成する手順は、以下のURLに記載されています。
Creating a WebLogic domain
https://github.com/oracle/weblogic-kubernetes-operator/blob/master/site/creating-domain.md
WebLogic Serverドメインを作成する手順は、以下のURLに記載されています。
手順に従い、WebLogic ServerのDockerイメージをDocker StoreからローカルレジストリにPullします。Docker Storeで使用許諾契約書に同意した後、DockerイメージをPullできます。
$ docker login
$ docker pull store/oracle/weblogic:12.2.1.3
ドメイン管理者の資格情報(weblogic/weblogic1)を保持するKubernetesシークレットを作成します。
$ kubectl -n weblogic create secret generic domain1-weblogic-credentials --from-literal=username=weblogic --from-literal=password=weblogic1
このドメイン用の永続ボリュームの場所は$K8SOP/volumes/domain1とします。
$ mkdir -m 777 -p $K8SOP/volumes/domain1
次に、$K8SOP/src/weblogic-kubernetes-operator/kubernetes/create-weblogic-domain-inputs.yamlというサンプル入力ファイルをカスタマイズし、以下の属性を変更します。
AttributeValue
weblogicDomainStoragePath{full path of $HOME}/k8sop/volumes/domain1
domainNamedomain1
domainUIDdomain1
t3PublicAddress{your-local-hostname}
exposeAdminT3Channeltrue
exposeAdminNodePorttrue
namespaceweblogic
$ $K8SOP/src/weblogic-kubernetes-operator/kubernetes/create-weblogic-domain.sh -i $K8SOP/create-domain1.yaml -o $K8SOP/weblogic-kubernetes-operator
create-weblogic-domain.shスクリプトが完了すると、Kubernetesは管理サーバとクラスタ化された管理対象サーバインスタンスを起動します。しばらくすると、実行中のPodを確認できます。
$ kubectl get po -n weblogic
NAME                                        READY     STATUS    RESTARTS   AGE
domain1-admin-server                        1/1       Running   0          5m
domain1-cluster-1-traefik-9985d9594-gw2jr   1/1       Running   0          5m
domain1-managed-server1                     1/1       Running   0          3m
domain1-managed-server2                     1/1       Running   0          3m
WebLogic Server管理コンソールを使って、実行中の管理サーバにアクセスし、ドメインの状態を確認します(http://localhost:30701/console、資格証明はweblogic/weblogic1)。 下図は[サーバー]のページです。

管理コンソールの[サーバー]ページでは、domain1のすべてのサーバが表示されます。各サーバには、特定のサーバ・インスタンスに対して定義されたKubernetesサービス名に対応するリスンアドレスがあります。サービス名は、domainUID(domain1)とサーバ名から導出されます。

これらのアドレス名はKubernetes名前空間内で解決可能であり、リスンポートとともに各サーバのデフォルトのネットワーク・チャネルを定義するために使用されます。前述のように、デフォルトのネットワーク・チャネルURLはトランザクション・コンテキストとともに伝播され、Transaction Managerによって分散トランザクションの調整のために内部的に使用されます。

Example Application

WebLogic ServerドメインがKubernetesで動作するようになったので、分散トランザクション処理を検証するためのサンプルアプリケーションを見ていきます。できるだけシンプルにするために、サーバー間のトランザクション伝播と同期コールバック処理の範囲をサンプルでは制限しています。これにより、リソースマネージャーの設定、JDBCやJMSクライアントコードを記述するという複雑な作業をしなくても、サーバー間のトランザクション通信を検証できます。

アプリケーションは、ServletフロントエンドとRMIリモートオブジェクトの2つの主要コンポーネントで構成されています。Servletは、URLのリストを含むGETリクエストを処理します。グローバルトランザクションを開始し、各URLでリモートオブジェクトを呼び出します。リモートオブジェクトは、単純に、beforeCompletionおよびafterCompletionコールバックメソッドで標準出力にメッセージを出力する同期コールバックを登録します。最後に、Servletはトランザクションをコミットし、各RMI呼び出しに関する情報とグローバルトランザクションの結果を含むレスポンスを送信します。

下図は、Kubernetesクラスタ内のdomain1のサーバ上のサンプルアプリケーションの実行の様子を示しています。Servletは、管理サーバの外部ポートを使って呼び出されます。Servletは、トランザクションを開始し、ローカル同期オブジェクトを登録し、Kubernetesの内部URL t3://domain1-managed-server1:8001 と t3://domain1-managed-server2:8001を使用して、管理対象サーバ上のregisterメソッドを呼び出します 。

TxPropagate Servlet
前述の通り、Servletはトランザクションを開始し、指定された各サーバーURLでRemoteSync.register()リモートメソッドを呼び出します。 その後、トランザクションはコミットされ、結果が呼び出し元に返されます。
package example;

import java.io.IOException;
import java.io.PrintWriter;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;

import weblogic.transaction.Transaction;
import weblogic.transaction.TransactionHelper;
import weblogic.transaction.TransactionManager;

@WebServlet("/TxPropagate")
public class TxPropagate extends HttpServlet {
  private static final long serialVersionUID = 7100799641719523029L;
  private TransactionManager tm = (TransactionManager)
      TransactionHelper.getTransactionHelper().getTransactionManager();

  protected void doGet(HttpServletRequest request,
      HttpServletResponse response) throws ServletException, IOException {
    PrintWriter out = response.getWriter();

    String urlsParam = request.getParameter("urls");
    if (urlsParam == null) return;
    String[] urls = urlsParam.split(",");

    try {
      RemoteSync forward = (RemoteSync)
          new InitialContext().lookup(RemoteSync.JNDINAME);
      tm.begin();
      Transaction tx = (Transaction) tm.getTransaction();
      out.println("<pre>");
      out.println(Utils.getLocalServerID() + " started " +
          tx.getXid().toString());
      out.println(forward.register());
      for (int i = 0; i < urls.length; i++) {
        out.println(Utils.getLocalServerID() + " " + tx.getXid().toString() +
            " registering Synchronization on " + urls[i]);
        Context ctx = Utils.getContext(urls[i]);
        forward = (RemoteSync) ctx.lookup(RemoteSync.JNDINAME);
        out.println(forward.register());
      }
      tm.commit();
      out.println(Utils.getLocalServerID() + " committed " + tx);
    } catch (NamingException | NotSupportedException | SystemException |
        SecurityException | IllegalStateException | RollbackException |
        HeuristicMixedException | HeuristicRollbackException e) {
      throw new ServletException(e);
    }
  }
Remote Object
RemoteSyncリモートオブジェクトには、伝播されたトランザクションコンテキストを使ってjavax.transaction.Synchronizationコールバックを登録するメソッドregisterが含まれています。

RemoteSync Interface
The following is the example.RemoteSync remote interface definition.
package example;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface RemoteSync extends Remote {
  public static final String JNDINAME = "propagate.RemoteSync";
  String register() throws RemoteException;
}
RemoteSyncImpl Implementation
example.RemoteSyncImplクラスはexample.RemoteSyncリモートインターフェイスを実装し、SynchronizationImplという名前の内部同期実装クラスを含んでいます。beforeCompletionメソッドとafterCompletionメソッドは、単純にサーバーID(ドメイン名とサーバー名)と伝播されたトランザクションのXid文字列表現を含むメッセージを標準出力に書き出します。

staticなmainメソッドは、RemoteSyncImplオブジェクトをインスタンス化し、それをサーバーのローカルJNDIコンテキストにバインドします。 後述のApplicationLifecycleListenerを使用してアプリケーションがデプロイされると、mainメソッドが呼び出されます。
package example;

import java.rmi.RemoteException;

import javax.naming.Context;
import javax.transaction.RollbackException;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;

import weblogic.jndi.Environment;
import weblogic.transaction.Transaction;
import weblogic.transaction.TransactionHelper;

public class RemoteSyncImpl implements RemoteSync {

  public String register() throws RemoteException {
    Transaction tx = (Transaction)
        TransactionHelper.getTransactionHelper().getTransaction();
    if (tx == null) return Utils.getLocalServerID() +
        " no transaction, Synchronization not registered";
    try {
      Synchronization sync = new SynchronizationImpl(tx);
      tx.registerSynchronization(sync);
      return Utils.getLocalServerID() + " " + tx.getXid().toString() +
          " registered " + sync;
    } catch (IllegalStateException | RollbackException |
        SystemException e) {
      throw new RemoteException(
          "error registering Synchronization callback with " +
      tx.getXid().toString(), e);
    }
  }

  class SynchronizationImpl implements Synchronization {
    Transaction tx;
  
    SynchronizationImpl(Transaction tx) {
      this.tx = tx;
    }
  
    public void afterCompletion(int arg0) {
      System.out.println(Utils.getLocalServerID() + " " +
          tx.getXid().toString() + " afterCompletion()");
    }

    public void beforeCompletion() {
      System.out.println(Utils.getLocalServerID() + " " +
          tx.getXid().toString() + " beforeCompletion()");
    }
  }

  // create and bind remote object in local JNDI
  public static void main(String[] args) throws Exception {
    RemoteSyncImpl remoteSync = new RemoteSyncImpl();
    Environment env = new Environment();
    env.setCreateIntermediateContexts(true);
    env.setReplicateBindings(false);
    Context ctx = env.getInitialContext();
    ctx.rebind(JNDINAME, remoteSync);
    System.out.println("bound " + remoteSync);
  }
}

Utility Methods
Utilsクラスには、ローカルサーバーIDを取得する静的メソッドとURLを指定して初期コンテキストルックアップを実行する静的メソッドの2つが含まれています。 初期コンテキストルックアップのメソッドは、匿名ユーザーの下で呼び出されます。これらのメソッドは、Servletとリモートオブジェクトの両方で使用されます。
package example;

import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class Utils {

  public static Context getContext(String url) throws NamingException {
    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY,
        "weblogic.jndi.WLInitialContextFactory");
    env.put(Context.PROVIDER_URL, url);
    return new InitialContext(env);
  }

  public static String getLocalServerID() {
    return "[" + getDomainName() + "+"
        + System.getProperty("weblogic.Name") + "]";
  }

  private static String getDomainName() {
    String domainName = System.getProperty("weblogic.Domain");
    if (domainName == null) domainName = System.getenv("DOMAIN_NAME");
    return domainName;
  }
}

ApplicationLifecycleListener
アプリケーションがWebLogic Serverインスタンスにデプロイされると、ApplicationLifecycleListenerのpreStartメソッドが呼び出され、RemoteSyncリモートオブジェクトが初期化され、バインドされます。
package example;

import weblogic.application.ApplicationException;
import weblogic.application.ApplicationLifecycleEvent;
import weblogic.application.ApplicationLifecycleListener;

public class LifecycleListenerImpl extends ApplicationLifecycleListener {

  public void preStart (ApplicationLifecycleEvent evt)
      throws ApplicationException {
    super.preStart(evt);
    try {
      RemoteSyncImpl.main(null);
    } catch (Exception e) {
      throw new ApplicationException(e);
    }
  }
}
Application Deployment Descriptor
アプリケーション・アーカイブには、ApplicationLifecycleListenerを登録するための以下のweblogic-application.xmlデプロイメント記述子が含まれています。
<?xml version = '1.0' ?>
<weblogic-application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bea.com/ns/weblogic/weblogic-application http://www.bea.com/ns/weblogic/weblogic-application/1.0/weblogic-application.xsd" xmlns="http://www.bea.com/ns/weblogic/weblogic-application">
   <listener>
    <listener-class>example.LifecycleListenerImpl</listener-class>
    <listener-uri>lib/remotesync.jar</listener-uri>
  </listener>
</weblogic-application>
Deploying the Application
このサンプルアプリケーションは、サポートされている数多くのデプロイ方式を使用してデプロイすることができますが、この例では、WebLogic Server管理コンソールを使用してアプリケーションをデプロイします。
Best Practices for Application Deployment on WebLogic Server Running on Kubernetes
https://blogs.oracle.com/weblogicserver/best-practices-for-application-deployment-on-weblogic-server-running-on-kubernetes-v2https://orablogs-jp.blogspot.com/2018/01/best-practices-for-application.html
アプリケーションがtxpropagate.earという名前のアプリケーションアーカイブにパッケージされているとします。まず、txpropagate.earをdomain1の永続的なボリュームの下にあるapplicationsディレクトリ($K8SOP/volumes/domain1/applications)にコピーすると、管理コンソールの[デプロイメント]ページからアプリケーションをデプロイできます。

EARファイルのパスは、管理サーバのコンテナ内の/shared/applications/txpropagate.earです。ここで/sharedは、$K8SOP/volumes/domain1に作成した永続ボリュームにマップされます。

EARをアプリケーションとしてデプロイし、管理サーバおよびクラスタをデプロイ先として設定します。

次のページで、Finishをクリックしてアプリケーションをデプロイします。アプリケーションのデプロイ後、デプロイメント表にエントリを確認できます。


Running the Application
domain1のサーバーにアプリケーションをデプロイしたので、分散トランザクションのテストを実行できます。以下のcURL操作では、クラスタに属する管理対象サーバのロード・バランサ・ポート30305を使用してServletを起動しています。その際、managed-server1のURLを指定します。
$ curl http://localhost:30305/TxPropagate/TxPropagate?urls=t3://domain1-managed-server1:8001

[domain1+managed-server2] started BEA1-0001DE85D4EE[domain1+managed-server2] BEA1-0001DE85D4EEC47AE630 registered example.RemoteSyncImpl$SynchronizationImpl@562a85bd
[domain1+managed-server2] BEA1-0001DE85D4EEC47AE630 registering Synchronization on t3://domain1-managed-server1:8001
[domain1+managed-server1] BEA1-0001DE85D4EEC47AE630 registered example.RemoteSyncImpl$SynchronizationImpl@585ff41b
[domain1+managed-server2] committed Xid=BEA1-0001DE85D4EEC47AE630(844351585),Status=Committed,numRepliesOwedMe=0,numRepliesOwedOthers=0,seconds since begin=0,seconds left=120,useSecure=false,SCInfo[domain1+managed-server2]=(state=committed),SCInfo[domain1+managed-server1]=(state=committed),properties=({ackCommitSCs={managed-server1+domain1-managed-server1:8001+domain1+t3+=true}, weblogic.transaction.partitionName=DOMAIN}),OwnerTransactionManager=ServerTM[ServerCoordinatorDescriptor=(CoordinatorURL=managed-server2+domain1-managed-server2:8001+domain1+t3+ CoordinatorNonSecureURL=managed-server2+domain1-managed-server2:8001+domain1+t3+ coordinatorSecureURL=null, XAResources={WSATGatewayRM_managed-server2_domain1},NonXAResources={})],CoordinatorURL=managed-server2+domain1-managed-server2:8001+domain1+t3+)
下図がそのアプリケーションフローです。

出力を見ると、ServletリクエストがBEA1-0001DE85D4EEトランザクションを開始したmanaged-server2にディスパッチされたことがわかります。
[domain1+managed-server2] started BEA1-0001DE85D4EE
ローカルのRemoteSync.register()メソッドが呼び出され、コールバックオブジェクトSynchronizationImpl@562a85bdが登録されました。
[domain1+managed-server2] BEA1-0001DE85D4EEC47AE630 registered example.RemoteSyncImpl$SynchronizationImpl@562a85bd
Servletはmanaged-server1上のRemoteSyncオブジェクトのregisterメソッドを呼び出し、同期オブジェクトSynchronizationImpl@585ff41bを登録しました。
[domain1+managed-server2] BEA1-0001DE85D4EEC47AE630 registering Synchronization on t3://domain1-managed-server1:8001
[domain1+managed-server1] BEA1-0001DE85D4EEC47AE630 registered example.RemoteSyncImpl$SynchronizationImpl@585ff41b
最後に、Servletはトランザクションをコミットし、トランザクションの文字列表現(通常はTransaction Managerのデバッグログに使用)を返します。
[domain1+managed-server2] committed Xid=BEA1-0001DE85D4EEC47AE630(844351585),Status=Committed,numRepliesOwedMe=0,numRepliesOwedOthers=0,seconds since begin=0,seconds left=120,useSecure=false,SCInfo[domain1+managed-server2]=(state=committed),SCInfo[domain1+managed-server1]=(state=committed),properties=({ackCommitSCs={managed-server1+domain1-managed-server1:8001+domain1+t3+=true}, weblogic.transaction.partitionName=DOMAIN}),OwnerTransactionManager=ServerTM[ServerCoordinatorDescriptor=(CoordinatorURL=managed-server2+domain1-managed-server2:8001+domain1+t3+ CoordinatorNonSecureURL=managed-server2+domain1-managed-server2:8001+domain1+t3+ coordinatorSecureURL=null, XAResources={WSATGatewayRM_managed-server2_domain1},NonXAResources={})],CoordinatorURL=managed-server2+domain1-managed-server2:8001+domain1+t3+)
出力からは以下のことがわかります。
  • トランザクションがコミットされた
  • 2個のサーバー参加者(managed-server1およびmanaged-server2)
  • t3://domain1-managed-server2:8001を使用してコーディネート・サーバ(managed-server2)にアクセス可能
admin-serverおよびmanaged-server1の出力を調べると、登録された同期コールバックが呼び出されたことを確認することもできます。サーバの.outファイルは、ドメインの永続ボリュームの下にあります。
$ cd $K8SOP/volumes/domain1/domain/domain1/servers
$ find . -name '*.out' -exec grep -H BEA1-0001DE85D4EE {} ';'
./managed-server1/logs/managed-server1.out:[domain1+managed-server1] BEA1-0001DE85D4EEC47AE630 beforeCompletion()
./managed-server1/logs/managed-server1.out:[domain1+managed-server1] BEA1-0001DE85D4EEC47AE630 afterCompletion()
./managed-server2/logs/managed-server2.out:[domain1+managed-server2] BEA1-0001DE85D4EEC47AE630 beforeCompletion()
./managed-server2/logs/managed-server2.out:[domain1+managed-server2] BEA1-0001DE85D4EEC47AE630 afterCompletion()
まとめると、何も変更せずに、Kubernetesクラスタで実行されているWebLogic Serverドメイン内で分散トランザクションを処理できました。 WebLogic Kubernetes Operatorのドメイン作成プロセスは、作成に必要なKubernetesネットワークとWebLogic Serverのすべての設定を提供しました。

以下のコマンドは、weblogic名前空間で定義されたKubernetesサービスを表示します。
$ kubectl get svc -n weblogic
NAME                                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)           AGE
domain1-admin-server                        NodePort    10.102.156.32    <none>        7001:30701/TCP    11m
domain1-admin-server-extchannel-t3channel   NodePort    10.99.21.154     <none>        30012:30012/TCP   9m
domain1-cluster-1-traefik                   NodePort    10.100.211.213   <none>        80:30305/TCP      11m
domain1-cluster-1-traefik-dashboard         NodePort    10.108.229.66    <none>        8080:30315/TCP    11m
domain1-cluster-cluster-1                   ClusterIP   10.106.58.103    <none>        8001/TCP          9m
domain1-managed-server1                     ClusterIP   10.108.85.130    <none>        8001/TCP          9m
domain1-managed-server2                     ClusterIP   10.108.130.92    <none>        8001/TCP
localhost上のポート30305を使用してTraefik NodePortサービスを通じてServletにアクセスできました。 Kubernetesクラスタ内から、Servletはサービス名とポートを使用して他のWebLogic Serverインスタンスにアクセスできます。各サーバーのリスンアドレスは対応するKubernetesサービス名に設定されているため、サーバーのPodが再起動され、別のIPアドレスが割り当てられても、Kubernetes名前空間からアドレスを解決できます。

Cross-Domain Transactions

それでは、このサンプルを2個のWebLogic Serverドメインで実行するように拡張します。Transaction Manager概要の章で説明したように、クロスドメイン・トランザクションでは、Transaction Manager通信を適切に保護するために追加の設定が必要になることがあります。ただし、この例では、構成をできるだけシンプルに保つことを目的として、アプリケーションと内部Transaction Manager通信の両方に対して、非セキュアプロトコル(t3)と匿名ユーザーを引き続き使用します。

まず、domain1(weblogic)と同じKubernetes名前空間に新しいドメイン(domain2)を作成する必要があります。 domain2を生成する前に、weblogic名前空間のdomain2資格証明のためのシークレット(domain2-weblogic-credentials)と、永続的ボリュームのディレクトリ($K8SOP/volumes/domain2)を作成する必要があります。

続いて、 create-domain1.yaml ファイルを変更します。以下の属性値を変更後、create-domain2.yaml としてファイルを保存します。
AttributeValue
domainNamedomain2
domainUIDdomain2
weblogicDomainStoragePath{full path of $HOME}/k8sop/volumes/domain2
weblogicCredentialsSecretNamedomain2-weblogic-credentials
t3ChannelPort32012
adminNodePort32701
loadBalancerWebPort32305
loadBalancerDashboardPort32315
これで、create-domain2.yamlファイルを使ってをcreate-weblogic-domain.shスクリプトを起動できる準備が整いました。
$ $K8SOP/src/weblogic-kubernetes-operator/kubernetes/create-weblogic-domain.sh -i $K8SOP/create-domain2.yaml -o $K8SOP/weblogic-kubernetes-operator
createスクリプトが正常終了すると、domain2のサーバが開始し、readinessプローブを使ってRUNNING状態に到達したことを報告します。
$ kubectl get po -n weblogic
NAME                                         READY     STATUS    RESTARTS   AGE
domain1-admin-server                         1/1       Running   0          27m
domain1-cluster-1-traefik-9985d9594-gw2jr    1/1       Running   0          27m
domain1-managed-server1                      1/1       Running   0          25m
domain1-managed-server2                      1/1       Running   0          25m
domain2-admin-server                         1/1       Running   0          5m
domain2-cluster-1-traefik-5c49f54689-9fzzr   1/1       Running   0          5m
domain2-managed-server1                      1/1       Running   0          3m
domain2-managed-server2                      1/1       Running   0          3m
domain2のサーバにアプリケーションをデプロイ後、アプリケーションを呼び出し、domain2の管理対象サーバのURLを含めることができます。
$ curl http://localhost:30305/TxPropagate/TxPropagate?urls=t3://domain2-managed-server1:8001,t3://domain2-managed-server2:8001

[domain1+managed-server1] started BEA1-0001144553CC
[domain1+managed-server1] BEA1-0001144553CC5D73B78A registered example.RemoteSyncImpl$SynchronizationImpl@2e13aa23
[domain1+managed-server1] BEA1-0001144553CC5D73B78A registering Synchronization on t3://domain2-managed-server1:8001
[domain2+managed-server1] BEA1-0001144553CC5D73B78A registered example.RemoteSyncImpl$SynchronizationImpl@68d4c2d6
[domain1+managed-server1] BEA1-0001144553CC5D73B78A registering Synchronization on t3://domain2-managed-server2:8001
[domain2+managed-server2] BEA1-0001144553CC5D73B78A registered example.RemoteSyncImpl$SynchronizationImpl@1ae87d94
[domain1+managed-server1] committed Xid=BEA1-0001144553CC5D73B78A(1749245151),Status=Committed,numRepliesOwedMe=0,numRepliesOwedOthers=0,seconds since begin=0,seconds left=120,useSecure=false,SCInfo[domain1+managed-server1]=(state=committed),SCInfo[domain2+managed-server1]=(state=committed),SCInfo[domain2+managed-server2]=(state=committed),properties=({ackCommitSCs={managed-server2+domain2-managed-server2:8001+domain2+t3+=true, managed-server1+domain2-managed-server1:8001+domain2+t3+=true}, weblogic.transaction.partitionName=DOMAIN}),OwnerTransactionManager=ServerTM[ServerCoordinatorDescriptor=(CoordinatorURL=managed-server1+domain1-managed-server1:8001+domain1+t3+ CoordinatorNonSecureURL=managed-server1+domain1-managed-server1:8001+domain1+t3+ coordinatorSecureURL=null, XAResources={WSATGatewayRM_managed-server1_domain1},NonXAResources={})],CoordinatorURL=managed-server1+domain1-managed-server1:8001+domain1+t3+)
アプリケーションフローを下図に示します。

この例では、トランザクションにはdomain1とdomain2の両方からのサーバー参加者が含まれ、同期コールバックが全ての参加サーバで処理されたことを確認できます。
$ cd $K8SOP/volumes
$ find . -name '*.out' -exec grep -H BEA1-0001144553CC {} ';'
./domain1/domain/domain1/servers/managed-server1/logs/managed-server1.out:[domain1+managed-server1] BEA1-0001144553CC5D73B78A beforeCompletion()
./domain1/domain/domain1/servers/managed-server1/logs/managed-server1.out:[domain1+managed-server1] BEA1-0001144553CC5D73B78A afterCompletion()
./domain2/domain/domain2/servers/managed-server1/logs/managed-server1.out:[domain2+managed-server1] BEA1-0001144553CC5D73B78A beforeCompletion()
./domain2/domain/domain2/servers/managed-server1/logs/managed-server1.out:[domain2+managed-server1] BEA1-0001144553CC5D73B78A afterCompletion()
./domain2/domain/domain2/servers/managed-server2/logs/managed-server2.out:[domain2+managed-server2] BEA1-0001144553CC5D73B78A beforeCompletion()
./domain2/domain/domain2/servers/managed-server2/logs/managed-server2.out:[domain2+managed-server2] BEA1-0001144553CC5D73B78A afterCompletion()

Summary

このエントリでは、WebLogic ServerのTransaction Managerがグローバルトランザクションをどのように処理するかを確認し、いくつかの基本的な設定要件について説明しました。続いて、サンプルアプリケーションを使って、Kubernetesクラスタでクロスドメイントランザクションがどのように処理されるかを見てきました。今後は、マルチノード、クロスKubernetesクラスタのトランザクション、フェールオーバーなど、より複雑なトランザクションのユースケースを見ていきます。

0 件のコメント:

コメントを投稿