listener_networks とは何か

複数のネットワークに接続された環境でOracle Databaseを利用する際に、どこからともなく出てくることがあるのが listener_networks というパラメータである。主に、データベースが稼働しているホストとは別のホストでリスナーを稼働させる(リモートリスナー)場合に把握しておくべき設定とされている。

特にReal Application Clusters(RAC)環境では、SCANリスナーがリモートリスナーに相当する動きをするので、複数のネットワークにリスナーを立てている環境では適切な設定が求められる。通常はクラスタウェアのエージェントが自動的に適切な設定を行ってくれているので設定をいじる必要はないが、なぜこのパラメータの設定が必要なのか、設定しないとどんな問題が生じるのか、理解しておく必要はあるだろう。

簡単のためにRACではなく通常のシングルデータベースを用いてその挙動を見ていくことにする。

リモートリスナーとは何か

listener_networks の挙動を確認する前に、リモートリスナーについて理解しておく必要がある。

アプリケーションがネットワーク越しにデータベースに接続する際、まずリスナーに接続リクエストを行う。リスナーはデータベースが稼働しているものと同じホストで起動しており、設定ファイル(listener.ora)に指定されたポートで接続を待ち受けている。そして、接続リクエストを受けたらサーバプロセスを生成し、アプリケーションの接続をインスタンスに引き継ぐ。これが通常のリスナーの動きであり、このようなリスナーを以降ローカルリスナーと呼ぶ。

一方、Oracle Databaseのソフトウェアがインストールされていれば、データベースを作成していないホストでもリスナーを起動させることができる。当然そのホストにはデータベースが存在しないので、サーバープロセスを生成するといった処理はできない。ではこのリスナーは何をするのかというと、データベースが稼働しているホストに接続をリダイレクトする動きになっている。専用サーバ接続であればローカルリスナーに接続をリダイレクトし、共有サーバ接続であればディスパッチャに接続をリダイレクトする。そのような役回りのリスナーをリモートリスナーと呼ぶ。

リスナー自体、接続の受付窓口のような働きをするものだが、リモートリスナーはさらにその手前にある窓口というイメージになる。

リモートリスナーはどうやってリダイレクト先のローカルリスナーを知るのか

リモートリスナーはローカルリスナーやディスパッチャへ接続をリダイレクトすると説明したが、ではその情報はどのようにリモートリスナーに伝わるのだろうか。

そこで出てくるのが、local_listener や remote_listener という初期化パラメータである。

結論としては、remote_listener に設定したリモートリスナーに対して、データベース側からローカルリスナーの情報とインスタンスの情報が通知されることでリモートリスナーはリダイレクト先を知ることになる。

当然、ローカルリスナーも接続先のインスタンスの情報を知る必要があるが、これは local_listener に対してデータベースがインスタンスの情報を登録する動作となっている。

ここでポイントになるのは、リモートリスナーには local_listener の情報も伝わっているという点。リダイレクト先のリスナーを知っている必要があるので当然なのだが、複数のネットワークにそれぞれリスナーを起動している場合、その伝わり方が問題になってくる。

実機検証

以下のような環境を用意する。手持ちの既存環境を使いまわしているので少々わかりづらいが了承いただきたい。

データベースホストとリモートリスナーが存在するホストは両方とも2つのネットワークに接続されており、それぞれのネットワークにデータベースに接続するアプリケーションが存在する想定。

また、各ホストのlistener.ora は以下のように設定されているものとする。

データベース側

LISTENER =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.100.21)(PORT = 1521))
      (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1521))
    )
  )

LISTENER_EX =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.10.1)(PORT = 1521))
    )
  )

リモートリスナー側

LISTENER =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.100.23)(PORT = 1521))
      (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1521))
    )
  )

LISTENER_EX =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.10.3)(PORT = 1521))
    )
  )

データベースを作成した直後の初期化パラメータの設定は以下のようになっており、remote_listenerには何も設定されていない。

SQL> show parameter local_listener

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
local_listener                       string      LISTENER_DB19
SQL> show parameter remote_listener

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
remote_listener                      string

local_listener には接続識別子が設定されているが、tnsnames.ora の該当する箇所には片方のネットワーク(Network 1)のローカルリスナーの情報のみが登録されている。

LISTENER_DB19 =
  (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.100.21)(PORT = 1521))

この状態ではリモートリスナーを経由した接続はおろか、Network 2 のローカルリスナーを経由した接続すらできない状態である。アプリケーションからリモートリスナーを経由してデータベースに接続できるようにするため、まずは listener_networks の設定は考慮せず、remote_listener とlocal_listener のみ設定するとどうなるか、確認してみることにする。

listener_networks を設定しない場合

local_listener と remote_listener には、その名前の通りリスナーのリスニングしているIPアドレスとポート番号を指定する。listener_networks を考慮しない場合、以下のように全てのネットワークのリスナーについて列挙することになる。

alter system set local_listener= '(ADDRESS_LIST=
  (ADDRESS=(PROTOCOL=TCP)(HOST=192.168.100.21)(PORT=1521))
  (ADDRESS=(PROTOCOL=TCP)(HOST=192.168.10.1)(PORT=1521)))';
  
alter system set remote_listener='(ADDRESS_LIST=
  (ADDRESS=(PROTOCOL=TCP)(HOST=192.168.100.23)(PORT=1521))
  (ADDRESS=(PROTOCOL=TCP)(HOST=192.168.10.3)(PORT=1521)))';

シングルインスタンスデータベースでは、リモート側のlistener.oraで別ホストからのサービス登録を許可しておく必要がある。

VALID_NODE_CHECKING_REGISTRATION_リスナー名=OFF

この設定を行わないと、remote_listener によるサービス登録が以下のように拒否される(リスナーログより)。

2023-08-07T19:39:46.975962+09:00
リスナー(VNCRオプション1)が宛先192.168.100.21からの登録リクエストを拒否しました
07-8月 -2023 19:39:46 * service_register_NSGR * 1182
TNS-01182: リスナーからサービス""の登録を拒否されました

設定後、リモートリスナーのサービス登録状況を見てみると、サービスのハンドラに「REMOTE SERVER」と記載された接続記述子が追加されるはずである。これがリモートリスナーが認識しているリダイレクト先のローカルリスナーのアドレスであり、データベース側からローカルリスナーの情報が伝わってきていることが確認できる。

しかし、この情報には実は問題がある。

$ lsnrctl services LISTENER
:
(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.100.23)(PORT=1521)))に接続中
サービスのサマリー...
サービス"db19"には、1件のインスタンスがあります。
  インスタンス"db19"、状態READYには、このサービスに対する2件のハンドラがあります...
    ハンドラ:
      "DEDICATED" 確立:0 拒否:0 状態:ready
         REMOTE SERVER
         (ADDRESS=(PROTOCOL=TCP)(HOST=192.168.10.1)(PORT=1521))         ← これは不要
      "DEDICATED" 確立:0 拒否:0 状態:ready
         REMOTE SERVER
         (ADDRESS=(PROTOCOL=TCP)(HOST=192.168.100.21)(PORT=1521))
:
コマンドは正常に終了しました。

$ lsnrctl services LISTENER_EX
:
(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.10.3)(PORT=1521)))に接続中
サービスのサマリー...
サービス"db19"には、1件のインスタンスがあります。
  インスタンス"db19"、状態READYには、このサービスに対する2件のハンドラがあります...
    ハンドラ:
      "DEDICATED" 確立:0 拒否:0 状態:ready
         REMOTE SERVER
         (ADDRESS=(PROTOCOL=TCP)(HOST=192.168.10.1)(PORT=1521))
      "DEDICATED" 確立:0 拒否:0 状態:ready
         REMOTE SERVER
         (ADDRESS=(PROTOCOL=TCP)(HOST=192.168.100.21)(PORT=1521))       ← これは不要
:
コマンドは正常に終了しました。

見てわかる通り、そのリモートリスナーがリスニングしていないネットワークのローカルリスナーの情報までハンドラとして表示されている。本来これは不要である。

リモートリスナーは、一つのサービスに対して複数のハンドラが登録されている場合、ロードバランスされるようにどちらかを選んでリダイレクトする。そのため、Network 1 のみに接続されているアプリケーション(AP1)からリモートリスナーを経由してデータベースに接続しようとすると、以下のような現象が起こる。

$ sqlplus system/oracle@192.168.100.23/db19

SQL*Plus: Release 19.0.0.0.0 - Production on 火 8月 8 00:17:27 2023
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle.  All rights reserved.

ERROR:
ORA-12543: TNS: 接続先ホストに接続できません。
:

$ sqlplus system/oracle@192.168.100.23/db19

SQL*Plus: Release 19.0.0.0.0 - Production on 火 8月 8 00:17:32 2023
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle.  All rights reserved.

最終正常ログイン時間: 月 8月  07 2023 23:59:50 +09:00


Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.18.0.0.0
に接続されました。
SQL> 

物理的につながっていないネットワークのローカルリスナーにリダイレクトされてしまい、そんなホストには到達できないというエラーが発生してしまうというわけだ。しかも選択されたリダイレクト先によっては接続できるという何とも厄介な現象に遭遇することになり、挙動を知らない人間が見ると困惑すること必至である。

listener_networks を設定する場合

上記現象は、remote_listener に設定したリスナーには全てのローカルリスナーの情報が登録されてしまうという挙動に起因している。

逆に言えば remote_listener と local_listener に対してネットワークの情報を関連付けて設定できれば良いわけで、listener_networks はこの動作のために存在するパラメータということになる。

基本的に listener_networks には追加したネットワークに対する設定を行うため、remote_listener と local_listener には片方のネットワーク(Network 1)の情報のみを設定する。

alter system set local_listener='LISTENER_DB19';
alter system set remote_listener='(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.100.23)(PORT=1521))';

そして listener_networks にはもう片方のネットワーク(Network 2) の情報を登録していく。NAMEに指定する名前は任意のものでよいようだ。

alter system set listener_networks=
  '((NAME=network2)
     (LOCAL_LISTENER=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.10.1)(PORT=1521)))
     (REMOTE_LISTENER=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.10.3)(PORT=1521))))';

これで、リモートリスナーにはリスニングしているネットワーク内のローカルリスナーのみが登録されるようになる。

RACにおけるlistener_networks

冒頭で記載した通り、RAC環境ではSCANリスナーがremote_listenerに登録されている。

シングルインスタンスデータベースと異なり、クラスタウェアがネットワークも含めて管理するため、remote_listener と local_listener の紐づけをどのように行うべきか、Oracle側が事前に把握できる。

そのため複数のパブリックネットワークが構成されている場合はクラスタウェア側で自動的にlistener_networksを設定してくれる。基本的に手動で設定を変更する必要はない。

パブリックネットワークを一つ追加し、そこにSCANとSCANリスナーを追加した環境では、以下のような設定が自動的に行われている様子がアラートログから確認できる。

2023-08-10T10:02:22.027628+09:00
ALTER SYSTEM SET local_listener=' (ADDRESS=(PROTOCOL=TCP)(HOST=192.168.100.42)(PORT=1521))' SCOPE=MEMORY SID='db11';
2023-08-10T10:02:22.131242+09:00
ALTER SYSTEM SET remote_listener=' rac19scan.dn.home:1521' SCOPE=MEMORY SID='db11';
2023-08-10T10:02:22.158988+09:00
ALTER SYSTEM SET listener_networks='(( NAME=net2)(LOCAL_LISTENER=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.10.42)(PORT=1522))))','(( NAME=net2)(REMOTE_LISTENER=rac19scane1.ex.home:1522))' SCOPE=MEMORY SID='db11';

上記の通り、local_listener と remote_listener にはそれぞれデフォルトのネットワークに存在するローカルリスナーとSCANリスナーが、listener_networks にはあとから追加したネットワークで起動しているローカルリスナーとSCANリスナーが設定されている。

この挙動が問題となるのは、クラスタ上にデータベースが複数存在し、ネットワーク毎に接続可能なデータベースを制限したい時である。何も設定を行わなければ全てのネットワークのSCANリスナーに対してデータベースのサービス情報が登録されてしまうので、どのネットワークからも両方のデータベースに接続できてしまうことになる。

以下は2つのデータベース(db1, db2)が存在する環境のSCANリスナーのサービス登録状況。両方のデータベースのサービスを認識していることがわかる。

$ lsnrctl services LISTENER_2_SCAN_SCAN2_NET2

LSNRCTL for Linux: Version 19.0.0.0.0 - Production on 10-8月 -2023 09:49:00

Copyright (c) 1991, 2022, Oracle.  All rights reserved.

(DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=LISTENER_2_SCAN_SCAN2_NET2)))に接続中
サービスのサマリー...
サービス"db1"には、2件のインスタンスがあります。
  インスタンス"db11"、状態READYには、このサービスに対する1件のハンドラがあります...
    ハンドラ:
      "DEDICATED" 確立:0 拒否:0 状態:ready
         REMOTE SERVER
         (ADDRESS=(PROTOCOL=TCP)(HOST=192.168.10.42)(PORT=1522))
  インスタンス"db12"、状態READYには、このサービスに対する1件のハンドラがあります...
    ハンドラ:
      "DEDICATED" 確立:0 拒否:0 状態:ready
         REMOTE SERVER
         (ADDRESS=(PROTOCOL=TCP)(HOST=192.168.10.43)(PORT=1522))
:
サービス"db2"には、2件のインスタンスがあります。
  インスタンス"db21"、状態READYには、このサービスに対する1件のハンドラがあります...
    ハンドラ:
      "DEDICATED" 確立:0 拒否:0 状態:ready
         REMOTE SERVER
         (ADDRESS=(PROTOCOL=TCP)(HOST=192.168.10.42)(PORT=1522))
  インスタンス"db22"、状態READYには、このサービスに対する1件のハンドラがあります...
    ハンドラ:
      "DEDICATED" 確立:0 拒否:0 状態:ready
         REMOTE SERVER
         (ADDRESS=(PROTOCOL=TCP)(HOST=192.168.10.43)(PORT=1522))
:
コマンドは正常に終了しました。

これを避けるのであれば、各インスタンスの local_listener と remote_listener にリスニングさせたいネットワークのローカルリスナーとSCANリスナーを手動で設定する必要がある。

db1 → デフォルトのネットワークからのみ接続できるようにする

alter system set local_listener = '(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.100.42)(PORT=1521))' SID='db11';
alter system set remote_listener = 'rac19scan.dn.home:1521' SID='db11';

alter system set local_listener = '(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.100.43)(PORT=1521))' SID='db12';
alter system set remote_listener = 'rac19scan.dn.home:1521' SID='db12';

db2 → 追加した2番目のネットワークからのみ接続できるようにする

alter system set local_listener = '(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.10.42)(PORT=1522))' SID='db21';
alter system set remote_listener = 'rac19scane1.ex.home:1522' SID='db21';

alter system set local_listener = '(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.10.43)(PORT=1522))' SID='db22';
alter system set remote_listener = 'rac19scane1.ex.home:1522' SID='db22';

すると、SCANリスナーはどちらか片方のデータベースのサービスのみを認識した状態となる。

$ lsnrctl services LISTENER_SCAN1

LSNRCTL for Linux: Version 19.0.0.0.0 - Production on 10-8月 -2023 12:33:22

Copyright (c) 1991, 2022, Oracle.  All rights reserved.

(DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=LISTENER_SCAN1)))に接続中
サービスのサマリー...
サービス"db1"には、2件のインスタンスがあります。
  インスタンス"db11"、状態READYには、このサービスに対する1件のハンドラがあります...
    ハンドラ:
      "DEDICATED" 確立:0 拒否:0 状態:ready
         REMOTE SERVER
         (ADDRESS=(PROTOCOL=TCP)(HOST=192.168.100.42)(PORT=1521))
  インスタンス"db12"、状態READYには、このサービスに対する1件のハンドラがあります...
    ハンドラ:
      "DEDICATED" 確立:0 拒否:0 状態:ready
         REMOTE SERVER
         (ADDRESS=(PROTOCOL=TCP)(HOST=192.168.100.43)(PORT=1521))
:
コマンドは正常に終了しました。

$ lsnrctl services LISTENER_2_SCAN_SCAN2_NET2

LSNRCTL for Linux: Version 19.0.0.0.0 - Production on 10-8月 -2023 12:33:50

Copyright (c) 1991, 2022, Oracle.  All rights reserved.

(DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=LISTENER_2_SCAN_SCAN2_NET2)))に接続中
サービスのサマリー...
サービス"db2"には、2件のインスタンスがあります。
  インスタンス"db21"、状態READYには、このサービスに対する1件のハンドラがあります...
    ハンドラ:
      "DEDICATED" 確立:0 拒否:0 状態:ready
         REMOTE SERVER
         (ADDRESS=(PROTOCOL=TCP)(HOST=192.168.10.42)(PORT=1522))
  インスタンス"db22"、状態READYには、このサービスに対する1件のハンドラがあります...
    ハンドラ:
      "DEDICATED" 確立:0 拒否:0 状態:ready
         REMOTE SERVER
         (ADDRESS=(PROTOCOL=TCP)(HOST=192.168.10.43)(PORT=1522))
:
コマンドは正常に終了しました。

コメント