月別アーカイブ: 2017年1月

IPアドレスの管理方法を考える①

ネットワーク・アドレスの格納を考える

構成管理データベースを考える時、ネットワーク管理情報を適切に格納することは重要である。Oracle以外のデータベースでは以下のように専用のデータ型や関数を提供している。

PostgreSQLの場合

PostgreSQLでは、IPv4アドレス、IPv6アドレス、MACアドレスを格納するデータ型を提供している。(8.9. ネットワークアドレス型

MySQLの場合

MySQLではIPv4 ネットワークアドレスのドット区切り表現を文字列から10進数、あるいはその逆の変換を実行する関数を提供している。(IPv6 ネットワークアドレスの変換関数も用意されている。)
inet_aton()
inet_ntoa()

Oracleでネットワークアドレス変換関数を作ってみる

翻って、Oracleでは悲しいくらいにネットワーク情報の格納手段が乏しい。ネットで検索したら以下の中国語サイトに面白い情報があったので、早速引用し試してみる。
Oracle SQL 模拟MySQL的inet_aton()和inet_ntoa()

inet_aton()

SQL> select
  2   to_number(regexp_replace(ip, '([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})', '\1')) * 16777216 +
  3   to_number(regexp_replace(ip, '([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})', '\2')) * 65536 +
  4   to_number(regexp_replace(ip, '([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})', '\3')) * 256 +
  5   to_number(regexp_replace(ip, '([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})', '\4'))   as IP_NUMBER
  6  from
  7   (select '192.168.11.1' as ip from dual);

 IP_NUMBER
----------
3232238337

dual表に任意のIPアドレスを指定すると、10進数に変換してくれるクエリーである。

さらに、OTN掲示板で見つけた「10進数を2進数に変換する」のスレッドから引用した、n進数に変換するTO_BASE関数(元ネタはAskTomらしい)で2進数に表示し直してみる。
つまり32bitで表現されるIPv4アドレスを32バイトの2進数文字列で表示する。

変換関数TO_BASE定義

CREATE OR REPLACE FUNCTION to_base( p_dec in number, p_base in number )
RETURN VARCHAR2
DETERMINISTIC
IS
  l_str   VARCHAR2(255) DEFAULT NULL;
  l_num   NUMBER        DEFAULT p_dec;
  l_hex   VARCHAR2(50)  DEFAULT '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
BEGIN
  IF ( trunc(p_dec) <> p_dec OR p_dec < 0 ) then
     raise PROGRAM_ERROR;
  END IF;

  LOOP
    l_str := substr( l_hex, mod(l_num,p_base)+1, 1 ) || l_str;
    l_num := trunc( l_num/p_base );
    EXIT WHEN ( l_num = 0 );
  END LOOP;

  RETURN l_str;
END to_base;
/

IPアドレスの10進数表現を2進数に変換

SQL> col "BIN_8bit" for a8
SQL> select 3232238337 as "DEC",to_base(3232238337,2) as "BIN_8bit" from dual;

       DEC BIN_8bit  (参考:オクテットを10進数に変換)
---------- --------
3232238337 11000000   192
           10101000   168
           00001011    11
           00000001     1

inet_ntoa()

同様にIPアドレスの10進数表現をドット区切り表現に変換するクエリーは以下となる。

SQL> select
  2   trunc(ip/16777216)            ||'.'||
  3   trunc(mod(ip,16777216)/65536) ||'.'||
  4   trunc(mod(ip,65536)/256)      ||'.'||
  5   trunc(mod(ip,256))            as IP_ADDRESS
  6  from
  7   (select 3232238337 as ip from dual);

IP_ADDRESS
------------
192.168.11.1

PL/SQLによるinet_aton関数の実装

CREATE OR REPLACE FUNCTION INET_ATON (
  ip_addr IN VARCHAR2)
RETURN NUMBER
DETERMINISTIC
IS
BEGIN
  RETURN(
    to_number(regexp_replace(ip_addr, '([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})', '\1')) * 16777216 +
    to_number(regexp_replace(ip_addr, '([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})', '\2')) * 65536 +
    to_number(regexp_replace(ip_addr, '([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})', '\3')) * 256 +
    to_number(regexp_replace(ip_addr, '([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})', '\4'))
  );
END;
/

実行例

SQL> select INET_ATON('192.168.11.1') IP_NUMBER from dual;

 IP_NUMBER
----------
3232238337

PL/SQLによるinet_ntoa関数の実装

CREATE OR REPLACE FUNCTION INET_NTOA (
  ip_number IN NUMBER)
RETURN VARCHAR2
DETERMINISTIC
IS
BEGIN
  RETURN(
    trunc(ip_number/16777216)            ||'.'||
    trunc(mod(ip_number,16777216)/65536) ||'.'||
    trunc(mod(ip_number,65536)/256)      ||'.'||
    trunc(mod(ip_number,256))
  );
END;
/

実行例

SQL> select INET_NTOA(3232238337) IP_ADDRESS from dual;

IP_ADDRESS
----------------
192.168.11.1

ネットワーク・アドレス管理表を考える

上で作成した関数を利用して、IPアドレスを管理する表の実装を検討する。

CREATE TABLE addr_tbl
(ip_number NUMBER
,ip_binary VARCHAR2(128) GENERATED ALWAYS AS (substr(TO_BASE(ip_number,2),1,32)) VIRTUAL
,ip_addr   VARCHAR2(60)  GENERATED ALWAYS AS (substr(INET_NTOA(ip_number),1,15)) VIRTUAL);

ALTER TABLE ADDR_TBL ADD CONSTRAINT PK_ADDR_TBL PRIMARY KEY (IP_NUMBER);
  • IPアドレスはinet_aton関数で10進数表現した値を「ip_number」列に格納し、この列が主キーとなる。
  • さらに2進数表現した「ip_binary」列を仮想列で作成している。
  • 仮想列をユーザ定義関数で作成する場合、DETERMINISTICな関数である必要がある。
  • 人間に最もなじみの深いドット区切り表現も同様に「ip_addr」列として仮想列で作成する。
  • つまり実際にデータが格納されるのは「ip_number」列のみで、「ip_binary」列と「ip_addr」列は仮想列として関数により導出される。
SQL> CREATE TABLE addr_tbl
  2  (ip_number NUMBER
  3  ,ip_binary VARCHAR2(128) GENERATED ALWAYS AS (substr(TO_BASE(ip_number,2),1,32)) VIRTUAL
  4  ,ip_addr   VARCHAR2(60)  GENERATED ALWAYS AS (substr(INET_NTOA(ip_number),1,15)) VIRTUAL);

Table created.

SQL> ALTER TABLE ADDR_TBL ADD CONSTRAINT PK_ADDR_TBL PRIMARY KEY (IP_NUMBER);

Table altered.

SQL> desc ADDR_TBL
 Name          Null?    Type
 ------------- -------- ----------------------------
 IP_NUMBER     NOT NULL NUMBER
 IP_BINARY              VARCHAR2(128)
 IP_ADDR                VARCHAR2(60)

仮想列の定義内容は以下のように確認する。
2進数表現列は32バイトあれば足りるのだが、仮想列を定義する場合はVARCHAR2型128バイトと大きめに定義する必要がある。(これはいろいろ試行錯誤して確認した。)
同様にドット区切り表現列も15バイトではなく60バイトで定義する必要がある。

SQL> SELECT
  2   TABLE_NAME,COLUMN_NAME,DATA_TYPE||'('||DATA_LENGTH||')' "Type"
  3  ,VIRTUAL_COLUMN, DATA_DEFAULT
  4  FROM USER_TAB_COLS WHERE TABLE_NAME='ADDR_TBL';

TABLE_NAME   COLUMN_NAME  Type            VIR DATA_DEFAULT
------------ ------------ --------------- --- --------------------------------------------------
ADDR_TBL     IP_NUMBER    NUMBER(22)      NO
ADDR_TBL     IP_BINARY    VARCHAR2(128)   YES SUBSTR("TEST"."TO_BASE"("IP_NUMBER",2),1,32)
ADDR_TBL     IP_ADDR      VARCHAR2(60)    YES SUBSTR("TEST"."INET_NTOA"("IP_NUMBER"),1,15)

テストデータの格納

データはIP_NUMBER列のみにInsertするだけでよいことと、INET_ATON関数を使用してドット区切り表現でInsertしていることに注目!

SQL> insert into addr_tbl(IP_NUMBER) values (INET_ATON('192.168.11.1'));

1 row created.

SQL> insert into addr_tbl(IP_NUMBER) values (INET_ATON('192.168.11.2'));

1 row created.

SQL> insert into addr_tbl(IP_NUMBER) values (INET_ATON('192.168.11.3'));

1 row created.

SQL> insert into addr_tbl(IP_NUMBER) values (INET_ATON('192.168.11.4'));

1 row created.

SQL> insert into addr_tbl(IP_NUMBER) values (INET_ATON('192.168.11.5'));

1 row created.

SQL> insert into addr_tbl(IP_NUMBER) values (INET_ATON('192.168.21.1'));

1 row created.

SQL> insert into addr_tbl(IP_NUMBER) values (INET_ATON('192.168.21.2'));

1 row created.

SQL> insert into addr_tbl(IP_NUMBER) values (INET_ATON('192.168.21.3'));

1 row created.

SQL> insert into addr_tbl(IP_NUMBER) values (INET_ATON('192.168.21.4'));

1 row created.

SQL> insert into addr_tbl(IP_NUMBER) values (INET_ATON('192.168.21.5'));

1 row created.

SQL> commit;

Commit complete.

テーブル内容の表示

SQL> col IP_ADDR for a18
SQL> col IP_BINARY for a8
SQL> select IP_NUMBER,IP_ADDR,IP_BINARY from ADDR_TBL;

 IP_NUMBER IP_ADDR            IP_BINAR
---------- ------------------ --------
3232238337 192.168.11.1       11000000
                              10101000
                              00001011
                              00000001

3232238338 192.168.11.2       11000000
                              10101000
                              00001011
                              00000010

3232238339 192.168.11.3       11000000
                              10101000
                              00001011
                              00000011

3232238340 192.168.11.4       11000000
                              10101000
                              00001011
                              00000100

3232238341 192.168.11.5       11000000
                              10101000
                              00001011
                              00000101

3232240897 192.168.21.1       11000000
                              10101000
                              00010101
                              00000001

3232240898 192.168.21.2       11000000
                              10101000
                              00010101
                              00000010

3232240899 192.168.21.3       11000000
                              10101000
                              00010101
                              00000011

3232240900 192.168.21.4       11000000
                              10101000
                              00010101
                              00000100

3232240901 192.168.21.5       11000000
                              10101000
                              00010101
                              00000101


10 rows selected.

指定されたアドレス範囲のIPアドレスを表示させる

SQL> set lines 140
SQL> set autot on
SQL> select IP_ADDR from ADDR_TBL
  2  where IP_NUMBER between INET_ATON('192.168.11.0') 
                         and INET_ATON('192.168.11.255');

IP_ADDR
------------------
192.168.11.1
192.168.11.2
192.168.11.3
192.168.11.4
192.168.11.5

Execution Plan
----------------------------------------------------------
Plan hash value: 1140719244

---------------------------------------------------------------------------------
| Id  | Operation         | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |             |     1 |    45 |     0   (0)| 00:00:01 |
|*  1 |  FILTER           |             |       |       |            |          |
|*  2 |   INDEX RANGE SCAN| PK_ADDR_TBL |     1 |    45 |     0   (0)| 00:00:01 |
---------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("INET_ATON"('192.168.11.0')<="INET_ATON"('192.168.11.255')) 2 - access("IP_NUMBER">="INET_ATON"('192.168.11.0') AND
              "IP_NUMBER"<="INET_ATON"('192.168.11.255'))
Note
-----
   - dynamic sampling used for this statement (level=2)

Statistics
----------------------------------------------------------
         58  recursive calls
          0  db block gets
         18  consistent gets
          0  physical reads
          0  redo size
        676  bytes sent via SQL*Net to client
        519  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          5  rows processed

アドレス範囲条件の指定はドット区切り表現で行っているが、inet_aton()を介して10進数に変換しているのでインデックスレンジ検索となっていることがわかる。

検索条件にドット区切り表現をそのまま使うと…

SQL> select IP_NUMBER,IP_ADDR,IP_BINARY from ADDR_TBL
  2  where IP_ADDR = '192.168.11.5';

 IP_NUMBER IP_ADDR            IP_BINAR
---------- ------------------ --------
3232238341 192.168.11.5       11000000
                              10101000
                              00001011
                              00000101

Execution Plan
----------------------------------------------------------
Plan hash value: 3135829337

------------------------------------------------------------------------------
| Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |          |     1 |   111 |     3   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| ADDR_TBL |     1 |   111 |     3   (0)| 00:00:01 |
------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("IP_ADDR"='192.168.11.5')

Note
-----
   - dynamic sampling used for this statement (level=2)

Statistics
----------------------------------------------------------
         38  recursive calls
          0  db block gets
         22  consistent gets
          0  physical reads
          0  redo size
        740  bytes sent via SQL*Net to client
        519  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

「ip_addr」列にインデックスは設定されていないので、実行計画はフル・テーブル・スキャンとなる。

「ip_addr」列にインデックスを作成してみる

仮想列のよいところは、データがなくてもインデックスが作成できることである。これはファンクション・インデックスに似ている。
そこで、「ip_addr」列にユニーク・インデックスを作成してみる。

SQL> CREATE UNIQUE INDEX IX_IP_ADDR ON ADDR_TBL (IP_ADDR);

Index created.

SQL> select IP_NUMBER,IP_ADDR,IP_BINARY from ADDR_TBL
  2  where IP_ADDR = '192.168.11.5';

 IP_NUMBER IP_ADDR            IP_BINAR
---------- ------------------ --------
3232238341 192.168.11.5       11000000
                              10101000
                              00001011
                              00000101
Execution Plan
----------------------------------------------------------
Plan hash value: 3058182370

------------------------------------------------------------------------------------------
| Id  | Operation                   | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |            |     1 |   111 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| ADDR_TBL   |     1 |   111 |     1   (0)| 00:00:01 |
|*  2 |   INDEX UNIQUE SCAN         | IX_IP_ADDR |     1 |       |     0   (0)| 00:00:01 |
------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("IP_ADDR"='192.168.11.5')

Statistics
----------------------------------------------------------
         96  recursive calls
          0  db block gets
         21  consistent gets
          0  physical reads
          0  redo size
        740  bytes sent via SQL*Net to client
        520  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

今度は、インデックスによる単一行検索となった。

書籍紹介:『アポロ13』に学ぶITサービスマネジメント ~映画を観るだけでITILの実践方法がわかる! ~

書籍紹介:『アポロ13』に学ぶITサービスマネジメント ~映画を観るだけでITILの実践方法がわかる!

アポロ13号でなぜITILを学ぶのか?

アポロ13号の事故は1970年、一方ITIL:Information Technology Infrastructure Library1989年イギリスで初版が公開された。

従ってこれらには直接の関連性はなく、それは本書の中でも再三繰り返されている。
しかし、ITILがアメリカの成功体験を分析することで策定されたという逸話もあるので、「成功した失敗」と呼ばれるアポロ13号の教訓からITILの本質を学ぶという筆者の考えには共感できる。

目次

■第1部 ITサービスマネジメントとアポロ13
●第1章 ITサービスマネジメントとは
-ITサービスの価値を高めるために-
●第2章 『アポロ13』でITSMを学ぶ意義
-アポロ計画とビジネスストラテジの共通点-

■第2部 サービスストラテジ
●第3章 「ニール・アームストロングが月に降り立ちました」
-アポロ計画における戦略-
●第4章 「14号があればだが」
-アポロ計画における「顧客」とは-
●第5章 「月を歩くんだね」
-サービスという単位を考える-

■第3部 サービスオペレーション
●第6章 「ヒューストン、 センターエンジンが停止した」
-インシデント管理-
●第7章 「反応バルブを閉じろ、と伝えろ」
-サービスデスク-
●第8章 「自分の字が読めないんだ。思ったより疲れているみたいだな」
-問題管理-

■第4部 サービスデザイン
●第9章 「絶対に死なせません」
-サービスレベル管理-
●第10章 「チャーリー・デュークが風疹にかかっている」
-可用性管理-
●第11章 「問題は電力だ。電力がすべて」
-キャパシティ管理-
●第12章 「トラブルが発生した」
-ITサービス継続性管理-

■第5部 サービストランジション
●第13章 「なんとかして、この四角をこの筒にはめ込むんだ」
-構成管理-
●第14章 「この飛行計画は忘れよう」
-変更管理-
●第15章 「こちらヒューストン。打ち上げ準備完了です」
-リリース管理-

■第6部 継続的サービス改善
●第16章 アポロ計画は改善のかたまり
-継続的サービス改善-

読後感

「アポロ13号」というタイトルに惹き付けられて手にした本書だったが、予想以上に面白い本だった。

例えば、
●第4章-アポロ計画における「顧客」とは-では

  • 顧客:アメリカ大統領、政府役人
  • ITサービス・プロバイダ:NASAヒューストン管制センターと管制官
  • ユーザ:3名の宇宙飛行士、バックアップ宇宙飛行士
  • インシデント管理マネージャー:主席飛行管理官

のように、具体的な登場人物を映画「アポロ13」の場面から引用することで、ステークホルダーやその他ITILの重要なキーワードをわかりやすく説明している。

これは、各章で一貫しているスタイルなので、DVDを観ながら読み進めると非常に面白い。

実践には概念を現実に昇華させる方法が必要であり、この本の手法はとても参考になる。

なぜ日本ではITILが浸透しないのか?

IT業界に長く身を置いているのだが、ITILを始めて聞いた2004年ごろから現在までITILの考え方を取り入れて成功した例をあまり聞いた実感がない。単純に成功体験に接する機会に恵まれなかった結果なのかもしれないが、行った現場はことごとくITIL的でない状況だった。

ただし
「多くの会社でSLAとして作成された文書を実際に確認してみると、現状そのほとんどは単なる契約書であり、ITILで解説しているSLAとはまったく違っていて、愕然とします。しかし、すでにSLAがあることになっていますので、別途(本来の)SLAが策定されることはありません。SLAという名の契約書(実際には、法務部門のレビューまで受けて押印された、ただの契約書)が存在していますので、SLAを権威づけするような契約書が別途交わされることもありません(本当の意味でのSLAが存在しないのですから、当たり前と言えば当たり前なのですが…)。このような状況ですので、外部プロバイダとの間でUCを取り交わすようなことはあっても、プロバイダ内部でOLAを策定しようと考えるIT部門は、残念ながらもほぼ皆無です(筆者の知る限り、きちんとOLAを策定していたITサービス・プロバイダは1社だけでした)」(読書位置:1672)
とあるように、ITILを正しく実践できている会社がほとんどないというのが現実なのかもしれない。

構成管理データベース(CMDB)から始めよう。

データベースに深く関わっている立場から感じることは、ITILを実践できないのは中核となる構成管理データベース(CMDB)がないからだと思う。本の中から構成管理について述べられた部分を抜粋する。

「この構成管理、実はこれそのものは何の利益もユーザ満足ももたらしません。そのため、つい後回しに、ないがしろにされてしまいがちなプロセスです。しかし、構成管理は前述のとおり、構成管理データベースを常に最新の、正確な状態に保つことを目的としたプロセスです。構成管理データベースは、ほかの重要なプロセスがITサービスの提供に利益をもたらすことを確実にするために、とても重要な情報を提供します。したがって、構成管理は(地味なプロセスなんですが)非常に重要である、と言えるでしょう。」(読書位置:2693)

「構成管理の観点では、ストレージは単なるサーバーの属性の1つ(どのサーバーに何テラバイトの容量のストレージが割り当てられているか)に過ぎないかもしれません。また、物理的なサーバーの中に仮想サーバーが10台存在するなら、構成管理としては、その仮想サーバー10台もそれぞれ構成アイテムとして管理する必要があるでしょう。さらに、SLAや組織図などの文書は資産管理では扱いませんが、構成管理では非常に重要な構成アイテムとして管理対象に含めます。」(読書位置:2706)

「筆者がJAXAに勤めている人から話を伺ったところ、世界初のデータベース管理システムは、このアポロ計画で宇宙船に積み込む備品のリストを作るために作られたのだそうです。ヒューストンの管制センターで全体の管理をしていたコンピュータ・システムはIBM社のSystem/360でしたから、おそらくこの世界初のデータベース管理システムは、System/360上で稼働していたことでしょう。もっとも、IBM社初のリレーショナル・データベースである System R は1977年に初めて売れた、ということですので、この世界初のデータベース管理システムは、現代風のリレーショナル・データベースではなかったのかもしれません。」(読書位置:2731)

以前、某ITサービス・プロバイダに在籍していた時、構成管理データベースの重要性を実感していたにも関わらず日々の業務に追われ結局実現することができなかった。

構成管理データベースはオープンソースのツールが存在したりしているが、出来合いのツールに合せていくのではなく、現状どこにでもあるExcelベースで管理している構成情報をモデリングしCMDB構築のヒントを模索していこうと思う。

これが漠然とした今年のテーマである。