この章では,次の項目について説明します。
クラスタ別名ポートの用語 (8.1 節)
クラスタ別名関数 (8.2 節)
クラスタ・ポート空間 (8.3 節)
予約ポートへバインドするマルチ・インスタンス・サービスの情報 (8.4 節)
クラスタ別名
setsockopt
() のオプション (8.5 節)
ポート属性:
/etc/clua_services
,clua_registerservice
() および
setsockopt
() クラスタ別名ソケット・オプションで設定されるポート属性間の関係 (8.6 節)
UDP アプリケーションとソース・アドレス (8.7 節)
この章では,ポートに関連した以下の用語を使用します。
クライアントとサーバの両方が番号を知っているポート。たとえば,番号が
/etc/services
にリストされているポート。クライアントがこのポートの番号を知っていて,そのポートを介してサーバへ接続しようとするため,サーバは,この既知のポートを明示的にバインドしリッスンします。
OS が割り当てる空きポート。アプリケーションでは,明示的にポート番号を指定しません。オペレーティング・システムが見つけた最初の空きポートが割り当てられます。
一時ポート空間内の動的ポート (1024を超える番号を持つポート。厳密には,IPPORT_RESERVED
と
IPPORT_USERRESERVED
の間のポート)。
特定のクラスタ・メンバが専用に使い,クラスタ単位で使用できないポート。ポートがロックされている場合,アプリケーションがそのポートをバインドしようとすると
EADDRINUSE
で失敗します。ただし,そのアプリケーションがソケットに
SO_REUSEALIASPORT
オプションを設定している場合はこの限りではありません。
クラスタ別名サブシステムで,ポート番号が 512 から
IPPORT_RESERVED
(1024) までのポート。省略時のクラスタの動作では,予約ポートにバインドしようとすると自動的にそのポートはロックされます (番号が 512 以下のポートはどんな場合もロックされません)。
クラスタ別名ライブラリ
libclua.a
および
libclua.so
には,クラスタ別名サブシステムの属性を表示する関数と設定する関数が用意されています。表 8-1
に,クラスタ別名ライブラリの関数を示します。詳細は,セクション 3 の個々のリファレンス・ページを参照してください。
表 8-1: クラスタ別名ライブラリ関数
クラスタ別名関数を使用するプログラムの作成およびコンパイルを行う際には,次の
#include
ファイルとライブラリを使用します。
clua
関数を使用するプログラムは,#include <clua/clua.h>
を記述し
-lclua
でコンパイルする。
次の関数を使用するプログラムは,-lclua -lcfg
でコンパイルする。
clua_getaliasaddress
()
clua_getaliasinfo
()
clua_getdefaultalias
()
clua_isalias
()
clua_registerservice
()
clua_unregisterservice
()
clusvc
関数を使用するプログラムは,#include <netinet/in.h>
を記述し,-lclu
でコンパイルする。
次に,これらの関数をさらに詳しく説明します。
clua_error
() と
print_clua_liberror
()clua_error
() および
print_clua_liberror
() 関数は,クラスタ別名メッセージ ID をそれに対応する印字可能な文字列に変換します。clua_error
() 関数は,呼び出し元にその文字列を返します。print_clua_liberror
() 関数は,stderr
にその文字列を出力します。詳細は,
clua_error
(3)
clua_getaliasaddress
() と
clua_getaliasinfo
()clua_getaliasaddress
関数を呼び出すことで,ローカル・ノードが知っているクラスタ別名の 1 つの IP アドレスを得ることができます。続けて呼び出すたびに別の別名の IP アドレスが返されます。そのノードが知っている別名のリストが尽きると,この関数は,CLUA_NOMOREALIASES
を返します。
sockaddr
構造体の形で別名のアドレスが渡されると,clua_getaliasinfo
() は,clu_info
構造体にその別名の情報を格納します。
プログラムでは,通常,clua_getaliasaddress
() を対話的に呼び出して,別名アドレスを 1 つずつ
clua_getaliasinfo
() に渡し,それぞれの別名についての情報を取得します。以下の呼び出しシーケンスは,この対話型ループの部分です (printf
() を数回呼び出す短い
main
() プログラムを内部に含んでいます)。
/* compile with -lclua -lcfg */ #include <sys/socket.h> /* AF_INET */ #include <clua/clua.h> /* includes <netinet/in.h> */ #include <netdb.h> /* gethostbyaddr() */ #include <arpa/inet.h> /* inet_ntoa() */ main () { int context = 0; struct sockaddr addr; struct clua_info outbuf, *pout; clua_status_t result1, result2; struct hostent *hp; pout=&outbuf; while ((result1=clua_getaliasaddress(&addr, &context)) == CLUA_SUCCESS) { if ((result2=clua_getaliasinfo(&addr, pout)) == CLUA_SUCCESS) { hp = gethostbyaddr((const void *)&pout->addr, sizeof (struct in_addr), AF_INET); printf ("\nCluster alias name:\t\t %s\n", hp->h_name); printf ("Cluster alias IP address:\t %s\n", inet_ntoa(pout->addr))); printf ("Cluster alias ID (aliasid):\t %d\n", pout->aliasid); printf ("Connections rcvd from net:\t %d\n", pout->count_cnx_rcv_net); printf ("Connections forwarded:\t\t %d\n", pout->count_cnx_fwd_clu); printf ("Connections rcvd within cluster: %d\n", pout->count_cnx_rcv_clu); } else { print_clua_liberror(result2); break; } } if (result1 != CLUA_SUCCESS && result1 != CLUA_NOMOREALIASES) print_clua_liberror(result1); }
詳細は,
clua_getaliasaddress
(3)clua_getaliasinfo
(3)
clua_getdefaultalias
()clua_getaliasaddress
() 関数では,ローカル・ノードが知っているすべてのクラスタ別名の IP アドレスが対話的に返されますが,clua_getdefaultalias
()
関数では,省略時のクラスタ別名アドレスしか返されません。詳細は,
clua_getdefaultalias
(3)
clua_isalias
()clua_isalias
() 関数は,IP
アドレスを渡されると,そのアドレスがクラスタ別名のアドレスかどうかを判断します。詳細は,
clua_isalias
(3)
clua_registerservice
() と
clua_unregisterservice
()clua_registerservice
() 関数は,動的ポートを,着信接続要求の受け付け用として登録します。512 〜 1024 の範囲にあるポートでは,CLUSRV_STATIC
オプションを指定します。これを指定しなければ,そのポートをバインドする最初のノードがクラスタ単位でそのポートを予約するため,残りのクラスタ・メンバはそのポートにバインドできなくなります。
clua_unregisterservice
() 関数は,ポートを解放します。
詳細は,
clua_registerservice
(3)
clusvc_getcommport
() と
clusvc_getresvcommport
()RPC を使用するプログラムでは,clusvc_getcommport
() および
clusvc_getresvcommport
() 関数を呼び出して,クラスタ内の共用ポートにバインドすることができます。予約 (特権) 共用ポート (ポート番号が 0 〜 1024) にバインドするときには,clusvc_getresvcommport
() を使用します。予約ポートにバインドする方法の詳細は,8.4 節を参照してください。以下に典型的な呼び出しシーケンスの例を示します (printf
() を数回呼び出す短い
main
() プログラムを内部に含んでいます)。
/* compile with -lclu */ #include <rpc/rpc.h> /* includes <netinet/in.h> */ #include <syslog.h> /* LOG_ERR */ #include <unistd.h> /* gethostname() */ #include <sys/param.h> /* MAXHOSTLEN */ main () { int s, i, namelen; int cluster = 0; uint prog = 100999; /* replace with real program number */ struct sockaddr_in addr; int len = sizeof(struct sockaddr_in); char local_host[MAXHOSTNAMELEN +1]; gethostname (local_host, sizeof (local_host) - 1); cluster = clu_is_member(); printf ("\nSystem %s %s a cluster member\n", local_host, cluster?"is":"is not"); if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { syslog(LOG_ERR, "socket: %m"); exit(1); } bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; if (cluster) { if (clusvc_getcommport(s, prog, IPPROTO_UDP, &addr) < 0) { syslog(LOG_ERR, "clusvc_getcommport: %m"); exit(1); } } else { addr.sin_port = 0; if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { syslog(LOG_ERR, "bind: %m"); exit(1); } if (getsockname(s, (struct sockaddr *)&addr, &len) != 0) { syslog(LOG_ERR, "getsockname: %m"); (void) close(s); exit(1); } } printf (" addr.sin_family: %d\n", addr.sin_family); printf (" addr.sin_port: %u\n", addr.sin_port); printf (" addr.sin_addr: %x\n", addr.sin_addr); printf (" addr.sin_zero: "); for (i = 0; i<8;i++) printf("%d ", (int)addr.sin_zero[i]); putchar('\n'); }
クラスタでは,クラスタ全体で同じポート空間を使用することにより,シングル・システムのポートのセマンティクスをエミュレートします。以下の項では,クラスタ別名サブシステムでどのようにポート空間を扱うかを簡単に説明します。
ポート番号 512 未満のポート (0 < (IPPORT_RESERVED
[脚注 1]
/ 2)):
これらの既知のポートはロックされません。シングル・システムでは,SO_REUSEPORT
ソケット・オプションを使わない限り,複数のプロセスが同じポートにバインドすることはできません。クラスタ・システムでは,番号が 512 未満のポートは,各クラスタ・メンバの 1 つのプロセスが,そのポートにバインドできるようにするために,決してロックされません。
512 〜 1024 の範囲にあるポート ((IPPORT_RESERVED
/ 2) 〜
IPPORT_RESERVED
):
ポートが明示的にバインドされている場合,static
としてマークされていない限り,予約ポートはロックされます。予約ポートのバインドについては,8.4 節を参照してください。
1024 を超える番号のポート (IPPORT_RESERVED
〜
IPPORT_USERRESERVED
):
一時ポートは,sin_port=0
の場合,ロックされます。一時ポートは,ポートに明示的にバインドした場合,ロックされません。
ポート空間には,シングル・インスタンス・アプリケーションとマルチ・インスタンス・アプリケーションで,それぞれ一般的に次のような意味があります。
シングル・インスタンス・アプリケーションでは,1 つのインスタンスだけがネットワーク要求を処理するので,ロック・ポートを使用するようにします。したがって,次の方法のいずれかを使用することができます。
予約ポート (512 〜 1024) に明示的にバインドする。
一時ポート (sin_port=0
) を使用する。
これによりポートはロックされますが,何らかの方法でクライアントにポート番号を通知しなければならないという問題があります。
CAA を使って,アプリケーションの 1 つのインスタンスだけがそのクラスタ内で動作することを保証する。
マルチ・インスタンス・アプリケーションでは,アプリケーションの最初のインスタンスだけが,ポートにバインドするように制限するべきではありません。そこで,次のような方法をとります。
512 未満の番号のポートを使用する。
予約ポート (512 〜 1024) を使用し,SO_REUSEALIASPORT
を設定する。
一時ポートに明示的にバインドする。
一時ポート (sin_port=0
) を使用して,SO_REUSEALIASPORT
を設定する。
これにより,ポートはロックされなくなりますが,何らかの方法でクライアントにそのポート番号を通知しなければならないという問題が残ります。
クラスタ別名ポート空間についての詳細は,『クラスタ概要』 のクラスタ別名の章を参照してください。
8.4 予約ポートへのバインド (512 〜 1024)
次にマルチ・インスタンス・サービスを予約ポートにバインドする場合に役立つ,背景となる情報について説明します。
予約ポートは,既知のポートとしても動的ポートとしても使用できるので,特別に扱われます。省略時の設定では,プロセスが明示的に 512 〜 1024 の範囲のポートにバインドした場合,そのポートは動的ポートと見なされて,クラスタ単位で予約され (ロックされ),他のメンバ上でそのポートにバインドすると失敗します。このようになっている主な理由は,多くのプログラムでは,ポートが使用中であれば失敗するという前提で,この範囲にある空きポートを探して
bind
() を呼び出すためです。
クラスタ別名サブシステムでは,次に示す理由で,予約ポートをこれとは異なる方法で扱います。
動的予約ポートを探すアプリケーションと,使用する既知のポートが分かっているアプリケーションを区別する方法がない。
動的ポートは,しばしば出力接続のローカル・ポートとして使われ,この接続は,ポート番号とアドレスの組み合わせでのみ識別される。
動的ポートが出力接続のために使われ,そのアドレスがクラスタ別名のものであった場合,別名サブシステムは,複数のノードに同じポートを持たせることはできません。これを許すと,クラスタ別名サブシステムは,クラスタ内の複数のノードからの接続,たとえば 1000 と別名のペアを区別できません。したがって,別名サブシステムでは,予約ポートが
static
(クラスタ単位で使用) であることが分からない限り,そのポートをロックしなければなりません。
番号 1024 を超えるポートには,このような問題はありません。これは,動的にポートを割り当てる標準的な方法があるためです。つまり,ポート0 にバインド (sin_port=0
) すると,システムは一時ポート空間からポートを選択します。したがって,クラスタ別名サブシステムは,明示的に 512 未満または 1024 を超える番号にバインドしているプロセスは何をしているかを知っていると想定します。つまり,既知のポートを取得しようとしていると見なします。
512 〜 1024 の範囲の既知のポートを入力接続に使用するマルチ・インスタンス・サービスでは,そのポートを
static
としてクラスタ別名サブシステムに登録するようにします。ポートを
static
として登録するためには,/etc/clua_services
にそのポートのエントリを置くか,CLUASRV_STATIC
オプションを指定して
clua_registerservice
を呼び出すようにプログラムを変更します。これらの関数の詳細は,
clua_registerservice
(3)clua_services
(4)static
オプションは,1024 番を超えるポートでは,別の目的に使用されます。つまり,static
として指定したポートを,一時ポート空間から除外するようにシステムに指示します)。
注意
static
属性は,クラスタ・メンバがブートされるたびに起動されるマルチ・インスタンス・サービスのためにだけ,/etc/clua_services
の中で指定してください。たとえば,/etc/inetd.conf
にエントリのあるサービスに指定します。これに従わなければ,異なるクラスタ・メンバ上の,動的ポートを探す異なるアプリケーションが,同じポートにバインドしてしまいます。
setsockopt
() および
getsockopt
() システム・コールは,以下のクラスタ別名のソケット・オプションをサポートしています。
SO_CLUA_DEFAULT_SRC
ローカル・アドレスが
bind
() 呼び出しを通してまだ設定されていない場合,ソケットは,省略時のクラスタ別名をソース・アドレスとして使用します。
SO_CLUA_IN_NOALIAS
ソケットをクラスタ別名アドレスにバインドしようとすると,失敗します。クラスタ別名を使ってアクセスさせたくないサービスに,このオプションを指定してください。
動的ポート (IPPORT_RESERVED
以上
IPPORT_USERRESERVED
未満のポート) にバインドしても,そのポートはロックされません。
ワイルドカード・アドレス (INADDR_ANY
または
IN6ADDR_ANY
) を使って予約ポートにバインドしても,そのポートはロックされません。
外方向の UDP 送信や TCP 接続要求のソース・アドレスは,ローカル・ホスト・アドレスになります (クラスタ別名アドレスではない)。
SO_CLUA_IN_NOLOCAL
と
SO_CLUA_IN_NOALIAS
オプションは,一緒に使用することはできません。
SO_CLUA_IN_NOLOCAL
ソケットは,クラスタ別名宛のパケットを受信しなければならず,クラスタ別名宛でないパケットはすべて廃棄します。このオプションは,クラスタ別名でのみアクセスさせたいサービスに使用します。
SO_CLUA_IN_NOLOCAL
と
SO_CLUA_IN_NOALIAS
オプションは,一緒に使用することはできません。
SO_RESVPORT
ソケットを予約ポートの範囲 (512 〜 1024) にバインドしようとすると,ポートが,/etc/clua_services
の
static
エントリか,CLUASRV_STATIC
オプションを指定した
clua_registerservice
() 呼び出しによって static としてマークされている場合に失敗します。bind
() の呼び出しでは,EADDRINUSE
が返されます。
SO_REUSEALIASPORT
ソケットは,ロックされたクラスタ別名ポートを再利用することができます。このオプションを設定すると,bind
() はクラスタ全体に分散されます。分散型アプリケーションでは,この副作用を利用してポートが使用中かどうかを判断できます。
8.6 ポート属性: /etc/clua_services,clua_registerservice(),および setsockopt()
/etc/clua_services
ファイルで使用する文字列は,clua_registerservice
() で使用する
CLUASRV_*
オプションと 1 対 1 に対応しています。この文字列とオプションの一部は,さらにクラスタ別名
setsockopt
() のオプションとも対応しています。表 8-2
に,これらの関係を示します。
表 8-2: クラスタ別名ポート属性間の関係
clua_services | clua_registerservice() | setsockopt() |
in_multi |
CLUASRV_MULTI |
|
in_single |
CLUASRV_SINGLE |
|
in_noalias |
CLUASRV_IN_NOALIAS |
SO_CLUA_IN_NOALIAS |
out_alias |
CLUASRV_OUT
[脚注 2]
|
SO_CLUA_DEFAULT_SRC
[脚注 3]
|
in_nolocal |
CLUASRV_IN_NOLOCAL |
SO_CLUA_IN_NOLOCAL |
static |
CLUASRV_STATIC |
|
SO_REUSEALIASPORT |
||
SO_RESVPORT |
UDP (User Datagram Protocol) ベースのアプリケーションでは,サーバからクライアントへ送られるメッセージは,そのクライアントが UDP 要求を発行するときに使用したサーバのアドレスと同じアドレスを使用している必要があります。クラスタ内のシステムでは,通常,外方向 UDP メッセージのソース・アドレスとしてクラスタ別名を使用しないので,これらのアプリケーションがクラスタ別名を使うと,問題が生じる場合があります。
この節では,クラスタ内の UDP ベースのサーバ・アプリケーションが,着信メッセージで使用されていたアドレス (クラスタ別名またはローカル・アドレスのいずれか) を使って応答するようにする方法を説明します。
ソケットを個々のローカル IP アドレスにバインドし,これに加えて,別のソケットを省略時のクラスタ別名アドレスにバインドするのが最善の方法です (別名のアドレスを得るには,clua_getdefaultalias
を使用します)。要求を受け取ったのと同じソケットを使って要求に応答します。アドレスは,自動的にクライアントが使ったアドレスになります。この方法は,ワイルドカードに対して
listen
を 1 回実行する方法を置き換えます。
これより少し単純な方法として,2 つのソケットを使う方法があります。1 つはワイルドカードに対してリッスンし,1 つはクラスタ別名に対してリッスンします (上記の例と同じように,入力ソケットを出力パケットとして再利用します)。同じサブネットに複数のローカル・アドレスがある場合は,この方法では,常に正しいローカル・アドレスを得ることはできませんが,省略時のクラスタ別名アドレスは正しく扱われます。複数のクラスタ別名があってアプリケーションが正しい別名 (入力方向で使われたもの) に応答するようにしたい場合は,複数のソケットの方法で,それぞれのクラスタ別名について
listen
() を行ってください。clua_getaliasaddress
() を使って,定義されたクラスタ別名のリストを作成します。
注意
クラスタ別名でのみアプリケーションにアクセスできるようにして,アプリケーションが常に省略時のクラスタ別名を使って応答するようにしたい場合は,1 つのソケットでワイルドカードに対してリッスンするようにしますが,そのソケットは
SO_CLUA_DEFAULT_SRC
オプションで設定します。アプリケーションは,外方向トラフィックに常に省略時のクラスタ別名アドレスを使うようになります。