8    クラスタ別名アプリケーション・プログラミング・インタフェース

この章では,次の項目について説明します。

8.1    クラスタ別名ポートの用語

この章では,ポートに関連した以下の用語を使用します。

既知のポート

クライアントとサーバの両方が番号を知っているポート。たとえば,番号が /etc/services にリストされているポート。クライアントがこのポートの番号を知っていて,そのポートを介してサーバへ接続しようとするため,サーバは,この既知のポートを明示的にバインドしリッスンします。

動的ポート

OS が割り当てる空きポート。アプリケーションでは,明示的にポート番号を指定しません。オペレーティング・システムが見つけた最初の空きポートが割り当てられます。

一時ポート

一時ポート空間内の動的ポート (1024を超える番号を持つポート。厳密には,IPPORT_RESERVEDIPPORT_USERRESERVED の間のポート)。

ロック・ポート

特定のクラスタ・メンバが専用に使い,クラスタ単位で使用できないポート。ポートがロックされている場合,アプリケーションがそのポートをバインドしようとすると EADDRINUSE で失敗します。ただし,そのアプリケーションがソケットに SO_REUSEALIASPORT オプションを設定している場合はこの限りではありません。

予約ポート

クラスタ別名サブシステムで,ポート番号が 512 から IPPORT_RESERVED (1024) までのポート。省略時のクラスタの動作では,予約ポートにバインドしようとすると自動的にそのポートはロックされます (番号が 512 以下のポートはどんな場合もロックされません)。

8.2    クラスタ別名関数

クラスタ別名ライブラリ libclua.a および libclua.so には,クラスタ別名サブシステムの属性を表示する関数と設定する関数が用意されています。表 8-1 に,クラスタ別名ライブラリの関数を示します。詳細は,セクション 3 の個々のリファレンス・ページを参照してください。

表 8-1:  クラスタ別名ライブラリ関数

関数 説明
clua_error クラスタ別名メッセージ ID を,それに対応する印字可能な文字列に変換し,呼び出し元にその文字列を返す。
clua_getaliasaddress ローカル・ノードが知っているクラスタ別名中の 1 つの IP アドレスを取得する。
clua_getaliasinfo クラスタ別名の 1 つとそのメンバの情報を取得する。
clua_getdefaultalias 省略時のクラスタ別名の IP アドレスを取得する。
clua_isalias IP アドレスがクラスタ別名の IP アドレスかどうかを判断する。
clua_registerservice 動的ポートを,着信接続要求の受け付け用として登録する。
clua_unregisterservice ポートを解放する。
clusvc_getcommport クラスタ内の共用ポートにバインドする。
clusvc_getresvcommport クラスタ内の予約ポートにバインドする。
print_clua_liberror クラスタ別名メッセージ ID を,それに対応する印字可能な文字列に変換し,stderr にその文字列を返す。

クラスタ別名関数を使用するプログラムの作成およびコンパイルを行う際には,次の #include ファイルとライブラリを使用します。

次に,これらの関数をさらに詳しく説明します。

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');
}
 

8.3    クラスタ・ポート空間

クラスタでは,クラスタ全体で同じポート空間を使用することにより,シングル・システムのポートのセマンティクスをエミュレートします。以下の項では,クラスタ別名サブシステムでどのようにポート空間を扱うかを簡単に説明します。

ポート空間には,シングル・インスタンス・アプリケーションとマルチ・インスタンス・アプリケーションで,それぞれ一般的に次のような意味があります。

クラスタ別名ポート空間についての詳細は,『クラスタ概要』 のクラスタ別名の章を参照してください。

8.4    予約ポートへのバインド (512 〜 1024)

次にマルチ・インスタンス・サービスを予約ポートにバインドする場合に役立つ,背景となる情報について説明します。

予約ポートは,既知のポートとしても動的ポートとしても使用できるので,特別に扱われます。省略時の設定では,プロセスが明示的に 512 〜 1024 の範囲のポートにバインドした場合,そのポートは動的ポートと見なされて,クラスタ単位で予約され (ロックされ),他のメンバ上でそのポートにバインドすると失敗します。このようになっている主な理由は,多くのプログラムでは,ポートが使用中であれば失敗するという前提で,この範囲にある空きポートを探して bind() を呼び出すためです。

クラスタ別名サブシステムでは,次に示す理由で,予約ポートをこれとは異なる方法で扱います。

番号 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 にエントリのあるサービスに指定します。これに従わなければ,異なるクラスタ・メンバ上の,動的ポートを探す異なるアプリケーションが,同じポートにバインドしてしまいます。

8.5    setsockopt() のオプション

setsockopt() および getsockopt() システム・コールは,以下のクラスタ別名のソケット・オプションをサポートしています。

SO_CLUA_DEFAULT_SRC

ローカル・アドレスが bind() 呼び出しを通してまだ設定されていない場合,ソケットは,省略時のクラスタ別名をソース・アドレスとして使用します。

SO_CLUA_IN_NOALIAS

ソケットをクラスタ別名アドレスにバインドしようとすると,失敗します。クラスタ別名を使ってアクセスさせたくないサービスに,このオプションを指定してください。

動的ポート (IPPORT_RESERVED 以上 IPPORT_USERRESERVED 未満のポート) にバインドしても,そのポートはロックされません。

ワイルドカード・アドレス (INADDR_ANY または IN6ADDR_ANY) を使って予約ポートにバインドしても,そのポートはロックされません。

外方向の UDP 送信や TCP 接続要求のソース・アドレスは,ローカル・ホスト・アドレスになります (クラスタ別名アドレスではない)。

SO_CLUA_IN_NOLOCALSO_CLUA_IN_NOALIAS オプションは,一緒に使用することはできません。

SO_CLUA_IN_NOLOCAL

ソケットは,クラスタ別名宛のパケットを受信しなければならず,クラスタ別名宛でないパケットはすべて廃棄します。このオプションは,クラスタ別名でのみアクセスさせたいサービスに使用します。

SO_CLUA_IN_NOLOCALSO_CLUA_IN_NOALIAS オプションは,一緒に使用することはできません。

SO_RESVPORT

ソケットを予約ポートの範囲 (512 〜 1024) にバインドしようとすると,ポートが,/etc/clua_servicesstatic エントリか,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

8.7    UDP アプリケーションとソース・アドレス

UDP (User Datagram Protocol) ベースのアプリケーションでは,サーバからクライアントへ送られるメッセージは,そのクライアントが UDP 要求を発行するときに使用したサーバのアドレスと同じアドレスを使用している必要があります。クラスタ内のシステムでは,通常,外方向 UDP メッセージのソース・アドレスとしてクラスタ別名を使用しないので,これらのアプリケーションがクラスタ別名を使うと,問題が生じる場合があります。

この節では,クラスタ内の UDP ベースのサーバ・アプリケーションが,着信メッセージで使用されていたアドレス (クラスタ別名またはローカル・アドレスのいずれか) を使って応答するようにする方法を説明します。

注意

クラスタ別名でのみアプリケーションにアクセスできるようにして,アプリケーションが常に省略時のクラスタ別名を使って応答するようにしたい場合は,1 つのソケットでワイルドカードに対してリッスンするようにしますが,そのソケットは SO_CLUA_DEFAULT_SRC オプションで設定します。アプリケーションは,外方向トラフィックに常に省略時のクラスタ別名アドレスを使うようになります。