tech memo

調べたこと、試したことの覚書です

nginx + lua-nginx-module で Basic認証時だけ 特定のCache-Controlヘッダの値を付与する処理の実装メモ

 

この記事の概要

下記の2パターンにて Basic認証時だけ 特定のCache-Controlヘッダの値を付与する処理を試しました。

  • nginx の ngx_http_auth_basic_module で Basic認証を実装するパターン
  • nginx の プロキシ先のアプリケーションで Basic認証を実装するパターン ( = nginx で リクエストヘッダ: Authorization 、レスポンスヘッダ: WWW-Authenticate を含む要求応答をプロキシする )

 ユースケースとしては、前段(ブラウザ, CDN, キャッシュサーバ等) で、Basic認証対象のコンテンツだけはキャッシュさせたくないので、特定のCache-Controlの値を付与したいときを想定しています。

Cache-Controlの値によるキャッシュ制御の挙動は、各々(ブラウザ,CDN,キャッシュサーバ等)の仕様を確認してください。

今回は一例として、Basic認証時だけ no-store を付与するという内容で試しています。

 

実装その1. nginx の ngx_http_auth_basic_module で Basic認証を実装するパターン

このパターンでは nginx 自体で Basic認証対象のURLパス を指定しているため、その配下に ヘッダ付与を記述します。( auth_basic, auth_basic_user_file の記述方法については本題からそれるのでこの記事では省略しています。 )

 

デフォルトでは、ngx_http_headers_module の add_header では 一部のステータスコードにヘッダを付与することができない仕様です。

Adds the specified field to a response header provided that the response code equals 200, 201 (1.3.10), 204, 206, 301, 302, 303, 304, 307 (1.1.16, 1.0.13), or 308 (1.13.0). Parameter value can contain variables.

※ 抜粋:Module ngx_http_headers_module

なので、Basic認証のレスポンスの一部 :  401 Unauthorized のときには add_header ではヘッダ付与ができません。クライアントとサーバの中間(CDN,プロキシ) の仕様で 401 レスポンスを ネガティブキャッシュする可能性がある場合など、401 にも 明示的にヘッダ付与したいときには add_header の always パラメタを付与する必要があります。

1 - 1. add_header で 実装するパターン ( 401 は対象外 )

付与対象ステータスコードが、200, 204, 206, 301, 302, 303, 304(すべてのバージョン) または 201(1.3.10) または 307 (1.1.16, 1.0.13) または 308 (1.13.0) のみでよく、

401 レスポンスには付与しなくてよい場合に add_header で書くと下記のような記述ができます。

location /basic/ {
add_header Cache-Control "no-store";
auth_basic "test";
auth_basic_user_file .htpasswd;
}

 応答としては、下記になります。

【Authorization ヘッダ無しでリクエスト の 結果抜粋】
# curl -iv http://******.1773.work/basic/

(リクエストヘッダ)
GET /basic/ HTTP/1.1
User-Agent: curl/7.29.0
Host: ******.1773.work
Accept: */*

(レスポンスヘッダ)
HTTP/1.1 401 Unauthorized
Date: Sun, 18 Aug 2019 02:59:34 GMT
Content-Type: text/html
Content-Length: 195
Connection: keep-alive
WWW-Authenticate: Basic realm="test"

※ Cache-Control が付与されない

 

【Authorization ヘッダありでリクエスト の 結果抜粋】
# curl -iv http://******.1773.work/basic/ -u test:******

(リクエストヘッダ)
GET /basic/ HTTP/1.1
Authorization: Basic ********(hash値)
User-Agent: curl/7.29.0
Host: ******.1773.work
Accept: */*

(レスポンスヘッダ)
HTTP/1.1 200 OK
Date: Sun, 18 Aug 2019 02:59:34 GMT
Content-Type: text/html
Content-Length: 15
Last-Modified: Thu, 06 Dec 2018 08:16:29 GMT
Connection: keep-alive
ETag: "5c08dadd-f"
Cache-Control: no-store ⇒ Cache-Control が付与される
Accept-Ranges: bytes

 

1 - 2. add_header で 実装するパターン ( すべてのレスポンスコードが対象 )

If the always parameter is specified (1.7.5), the header field will be added regardless of the response code.

※ 抜粋:Module ngx_http_headers_module

401 レスポンスを含む、すべての ステータスコードに も付与したい場合には、add_header の always パラメタを付与する必要があります。( 2019/09/30 修正 : こちらは元々 lua-nginx-module で記述していましたが、alwaysパラメタで設定可能という内容を知り、修正しました。)

location /basic/ {
add_header Cache-Control "no-store" always;
auth_basic "test";
auth_basic_user_file .htpasswd;
}

1.7.5 未満のバージョンのnginxで always が使えないが、lua-nginx-module は利用可能な場合は下記でも同様に全てのステータスコードに付与可能になります。

location /basic/ {
auth_basic "test";
auth_basic_user_file .htpasswd;

header_filter_by_lua_block {
ngx.header["Cache-Control"] = 'no-store';
}
}

401 の 応答にも 付与ができるようになります。

【Authorization ヘッダ無しでリクエスト の 結果抜粋】
# curl -iv http://******.1773.work/basic/

(リクエストヘッダ)
GET /basic/ HTTP/1.1
User-Agent: curl/7.29.0
Host: ******.1773.work
Accept: */*

(レスポンスヘッダ)
HTTP/1.1 401 Unauthorized
Date: Sun, 18 Aug 2019 03:13:05 GMT
Content-Type: text/html
Content-Length: 195
Connection: keep-alive
WWW-Authenticate: Basic realm="test"
Cache-Control: no-store ⇒ Cache-Control が付与される

 

実装その2. nginx の プロキシ先のアプリケーションで Basic認証を実装するパターン

このパターンでは nginx では Basic認証対象のURLパスを管理していません。要求応答ヘッダ(リクエストヘッダ: Authorization 、レスポンスヘッダ: WWW-Authenticate)有無で判定して Cache-Control を付与する例です。

location / {
proxy_pass http://127.0.0.1:****/;

header_filter_by_lua_block {
if ngx.req.get_headers()["Authorization"] ~= nil or ngx.resp.get_headers()["WWW-Authenticate"] ~= nil then
ngx.header["Cache-Control"] = 'no-store'
end
}
}

 応答としては、下記になります。

【Authorization ヘッダ無しでリクエスト の 結果抜粋】
# curl -iv http://******.1773.work/basic/

(リクエストヘッダ)
GET /basic/ HTTP/1.1
User-Agent: curl/7.29.0
Host: ******.1773.work
Accept: */*

(レスポンスヘッダ)
HTTP/1.1 401 Unauthorized
Date: Sun, 18 Aug 2019 04:11:48 GMT
Content-Type: text/html
Content-Length: 180
Connection: keep-alive
WWW-Authenticate: Basic realm="test"
Cache-Control: no-store ⇒ Cache-Control が付与される

 

【Authorization ヘッダありでリクエスト の 結果抜粋】
# curl -iv http://******.1773.work/basic/ -u test:******

(リクエストヘッダ)
GET /basic/ HTTP/1.1
Authorization: Basic ********(hash値)
User-Agent: curl/7.29.0
Host: ******.1773.work
Accept: */*

(レスポンスヘッダ)
HTTP/1.1 200 OK
Date: Sun, 18 Aug 2019 04:11:41 GMT
Content-Type: text/html
Content-Length: 612
Connection: keep-alive
Last-Modified: Wed, 27 Mar 2019 02:38:45 GMT
ETag: "5c9ae235-264"
Accept-Ranges: bytes
Cache-Control: no-store ⇒ Cache-Control が付与される

 

【Basic認証以外のURLへのリクエスト の 結果抜粋】
# curl -iv http://******.1773.work/

(リクエストヘッダ)
GET / HTTP/1.1
User-Agent: curl/7.29.0
Host: ******.1773.work
Accept: */*

(レスポンスヘッダ)
HTTP/1.1 200 OK
Date: Sun, 18 Aug 2019 04:21:28 GMT
Content-Type: text/html
Content-Length: 612
Connection: keep-alive
Last-Modified: Wed, 27 Mar 2019 02:38:45 GMT
ETag: "5c9ae235-264"
Accept-Ranges: bytes

※ Cache-Control は付与されない

 

Basic認証時 の Cache-Controlヘッダ付与についてのメモでした。

Apache Traffic Server の gold_tests をはじめて書いたときに試行錯誤したメモ

 

はじめに

Apache Traffic Server ( GitHub - apache/trafficserver: Apache Traffic Server ) の tests/gold_tests 配下にある 各テストを参考に はじめて gold_tests を作成するのを試みた際に、悩んだ点・気づいた点などを、覚えているうちに書き留めておきます。

 

実行環境のつくりかた

今回、私は、gold_tests 実行環境については hnakamur さんの記事を参考に Dockerにて作成しました。

また、追加の手順として、自分のつくったテストだけ実行したいときには、gold_tests ディレクトリ配下を autest-site と 自分の作成したテストディレクトリのみ にして実行するのがお手軽だったので、そちらの方法でやってました。

【gold_tests配下の全件実行 のイメージ】
cd ~build/dev/trafficserver/tests
env-test/bin/autest -D gold_tests --ats-bin /usr/local/bin

【自分のgold_testsのみ実行 のイメージ】
mkdir ~build/tmp
mv ~build/dev/trafficserver/tests/gold_tests/* ~build/tmp/
mv ~build/tmp/autest-site ~build/dev/trafficserver/tests/gold_tests/
mv ~build/tmp/my-tests ~build/dev/trafficserver/tests/gold_tests/
cd ~build/dev/trafficserver/tests
env-test/bin/autest -D gold_tests --ats-bin /usr/local/bin
  

gold_tests での試行錯誤① Hierarchical Caching 構成にしたい

tests/gold_tests 配下を見回った限り Hierarchical Caching が無さそうでしたが、gold_tests 内で traffic server のプロセスを2つ作成して、各々の config を設定したところ 1コンテナ内に Child用プロセス と Parent用プロセス を動作させても問題なさそうでした。
私が試した方法では、なぜか DNS が timeout するタイミングがあるという問題があったので、コンテナ内の /etc/hosts にダミーの名前を追記しつつ、config に proxy.config.hostdb.host_file.path で呼び出すという方法で試していました (この方法だと動いたので...) 。
【gold_tests / master(9.0.0) で動かしたミニマムに記述の例】

## プロセスの作成
## ts = Child用 , ts2 = Parent用 として 2つ作成します。

ts = Test.MakeATSProcess("ts")
ts2 = Test.MakeATSProcess("ts2")

## Child の config
ts.Disk.plugin_config.AddLine('xdebug.so')
ts.Disk.remap_config.AddLine(
'map . http://origin.example.com'
)
ts.Disk.parent_config.AddLine(
'dest_domain=. parent="127.0.0.1:{port}"'.format(port=ts2.Variables.port)
)
ts.Disk.records_config.update({
'proxy.config.http.parent_proxy.self_detect' : 0,
'proxy.config.http.no_dns_just_forward_to_parent' : 1,
'proxy.config.http.request_via_str' : 'ApacheTrafficServerChild',
'proxy.config.http.response_via_str' : 'ApacheTrafficServerChild',
'proxy.config.log.logging_enabled' : 3,
'proxy.config.diags.debug.enabled': 1,
'proxy.config.diags.debug.tags': 'http|dns',
'proxy.config.diags.output.debug': 'L',
'proxy.config.hostdb.host_file.path' : '/etc/hosts',
})

## Parent の config
ts2.Disk.plugin_config.AddLine('xdebug.so')
ts2.Disk.remap_config.AddLine(
'map / http://127.0.0.1:{0}'.format(server.Variables.Port)
)
ts2.Disk.records_config.update({
'proxy.config.http.request_via_str' : 'ApacheTrafficServerParent',
'proxy.config.http.response_via_str' : 'ApacheTrafficServerParent',
'proxy.config.log.logging_enabled' : 3,
'proxy.config.diags.debug.enabled': 1,
'proxy.config.diags.debug.tags': 'http|dns',
'proxy.config.diags.output.debug': 'L',
'proxy.config.hostdb.host_file.path' : '/etc/hosts',
})
  
【DockerFileに追加】
USER root
RUN echo "127.0.0.1 origin.example.com" >> /etc/hosts
 

gold_tests での試行錯誤② 同一URLへのリクエストに対してのオリジンからのレスポンスを、違うものにしたい

例えば URI "/default" に対して 200 OK , 304 Not Modified の 2パターンを応答させるようにしたい、といったときのアプローチ方法です。

オリジンプロセスの作成 MakeOriginServer にて、lookup_key を指定しないときはデフォルトの lookup_key は PATH のみの挙動となっていました。

【lookup_key を指定しないとき】
server = Test.MakeOriginServer("server")

request_header1 = {"headers":
"GET /default HTTP/1.1\r\n" +
"Host: www.example.com\r\n" +
"\r\n",
"timestamp": "12345678",
"body" : "",
}
response_header6b = {"headers":
"HTTP/1.1 200 OK\r\n" +
"Content-Length: 4\r\n" +
"Connection: close\r\n" +
"Cache-Control: max-age=0\r\n" +
"ETag: \"5ca41161-1a\"\r\n" +
"\r\n",
"timestamp": "12345678",
"body": "test"
}
server.addResponse("sessionlog.json", request_header1, response_header1)

request_header2 = {"headers":
"GET /default HTTP/1.1\r\n" +
"Host: www.example.com\r\n" +
"\r\n",
"timestamp": "12345678",
"body" : "",
}
response_header2 = {"headers":
"HTTP/1.1 304 Not Modified\r\n" +
"Connection: close\r\n" +
"ETag: \"5ca41161-1a\"\r\n" +
"\r\n",
"timestamp": "12345678",
"body": None
}
server.addResponse("sessionlog.json", request_header2, response_header2)
 ⇒ この場合 "GET /default HTTP/1.1" でリクエストすると最後に定義した response_header2 が常にかえってくるという挙動になってしまいました...

 

などをみていて、HTTPヘッダを lookup_key に使えそうだったので、フック用のダミーのヘッダ X-Update を用意してみました。

【lookup_key をカスタマイズする】
server = Test.MakeOriginServer("server",lookup_key="{%X-Update}{PATH}")

request_header1 = {"headers":
"GET /default HTTP/1.1\r\n" +
"Host: www.example.com\r\n" +
"X-Update: no\r\n" +
"\r\n",
"timestamp": "12345678",
"body" : "",
}
response_header1 = {"headers":
"HTTP/1.1 200 OK\r\n" +
"Content-Length: 4\r\n" +
"Connection: close\r\n" +
"Cache-Control: max-age=0\r\n" +
"ETag: \"5ca41161-1a\"\r\n" +
"\r\n",
"timestamp": "12345678",
"body": "test"
}
server.addResponse("sessionlog.json", request_header1, response_header1)

request_header2 = {"headers":
"GET /default HTTP/1.1\r\n" +
"Host: www.example.com\r\n" +
"X-Update: yes\r\n" +
"\r\n",
"timestamp": "12345678",
"body" : "",
}
response_header2 = {"headers":
"HTTP/1.1 304 Not Modified\r\n" +
"Connection: close\r\n" +
"ETag: \"5ca41161-1a\"\r\n" +
"\r\n",
"timestamp": "12345678",
"body": None
}
server.addResponse("sessionlog.json", request_header2, response_header2)

  ⇒ この場合 "GET /default HTTP/1.1" + "X-Update: no" でリクエストするとresponse_header1 が, "GET /default HTTP/1.1" + "X-Update: yes" でリクエストするとresponse_header2かえってくるという挙動をさせることができました。

同一URLに対してオリジンからのレスポンスを変えたいときはこの手の仕込みが必要そうでした。

 

PowerDNS ALIASレコード / TTL の 挙動確認

はじめに

先日、DNS ANAMEレコード / PowerDNS ALIASレコードについて下記の記事を投稿しました。本記事内で一部は専門用語などで記述していますが、それらの専門用語の説明は下記の記事にて確認していただけます。

本記事においては PowerDNS ALIASレコード の TTLについての挙動確認を記録しています。

 

ANAMEレコード の TTLについての 仕様確認

Internet Draft にて「Address-specific DNS aliases (ANAME)」として仕様提案されている TTL周りの仕様を、現時点での最新である draft-03 にて確認しました。

【5.3. TTLs】にて記述されている仕様は下記の内容でした。

  • Sibling Address Record は、固定TTLを持つ権威サーバから提供される。
  • 通常 Sibling Address RecordTTLは、Target Address Record TTL (Target Address RecordのTTLが ANAMEのTTLより小さい場合は、ANAME の TTL)と同じであると予想される。
  • ただし 正確なメカニズムは指定されていないため、 Sibling Address RecordTTLが小さくなる場合がある。 

 

PowerDNS ALIASレコード の TTLについての動作確認

正確な挙動は、実際に試してみないと分からなさそうだったので確認しました。

何パターンか試してみた内容です。

① Sibling Address Record が Aレコード , ANAME's TTL > Sibling's TTL 

【ANAME @ DNS-1】
(owner) (ttl) (class) (TYPE) (target)
example.1773.work. 60 IN ALIAS origin.1773.work.

【Sibling Address @ DNS-2】
(owner) (ttl) (class) (TYPE) (target)
origin.1773.work. 10 IN A 192.168.254.100

 
【事前確認、DNS-2に直接確認】
origin.1773.work. 10 IN A 192.168.254.100

【ANAME確認、DNS-1に確認】
exsample.1773.work. 10 IN A 192.168.254.100

 ⇒ TTLは、Sibling Address(かつTarget Address) Record のTTLに置き換わった。

 

② Sibling Address Record が Aレコード , ANAME's TTL < Sibling's TTL 

【ANAME @ DNS-1】
(owner) (ttl) (class) (TYPE) (target)
example.1773.work. 60 IN ALIAS origin.1773.work.

【Sibling Address @ DNS-2】
(owner) (ttl) (class) (TYPE) (target)
origin.1773.work. 100 IN A 192.168.254.100

 
【事前確認、DNS-2に直接確認】
origin.1773.work. 100 IN A 192.168.254.100

【ANAME確認、DNS-1に確認】
exsample.1773.work. 100 IN A 192.168.254.100

 ⇒ TTLは、Sibling Address(かつTarget Address) Record のTTLに置き換わった。

 

③ Sibling Address Record が CNAMEレコード , ANAME's TTL > Sibling's TTL > Target's TTL 

【ANAME @ DNS-1】
(owner) (ttl) (class) (TYPE) (target)
example.1773.work. 60 IN ALIAS cname.1773.work.

【Sibling Address @ DNS-2】
(owner) (ttl) (class) (TYPE) (target)
cname.1773.work. 10 IN CNAME origin.1773.work.

【Target Address @ DNS-3】
(owner) (ttl) (class) (TYPE) (target)
origin.1773.work. 5 IN A 192.168.254.10
origin.1773.work. 5 IN A 192.168.254.20
origin.1773.work. 5 IN A 192.168.254.30
 
【事前確認、DNS-3に直接確認】
origin.1773.work. 5 IN A 192.168.254.10
origin.1773.work. 5 IN A 192.168.254.20
origin.1773.work. 5 IN A 192.168.254.30

【事前確認、DNS-2に直接確認】
cname.1773.work. 10 IN CNAME origin.1773.work.
origin.1773.work. 5 IN A 192.168.254.10
origin.1773.work. 5 IN A 192.168.254.20
origin.1773.work. 5 IN A 192.168.254.30

【ANAME確認、DNS-1に確認】
exsample.1773.work. 5 IN A 192.168.254.20
exsample.1773.work. 5 IN A 192.168.254.10
exsample.1773.work. 5 IN A 192.168.254.30
  ⇒ TTLは、Target Address Record のTTLに置き換わった。
 

④ Sibling Address Record が CNAMEレコード , ANAME's TTL > Sibling's TTL < Target's TTL

【ANAME @ DNS-1】
(owner) (ttl) (class) (TYPE) (target)
example.1773.work. 60 IN ALIAS cname.1773.work.

【Sibling Address @ DNS-2】
(owner) (ttl) (class) (TYPE) (target)
cname.1773.work. 10 IN CNAME origin.1773.work.

【Target Address @ DNS-3】
(owner) (ttl) (class) (TYPE) (target)
origin.1773.work. 90 IN A 192.168.254.10
origin.1773.work. 90 IN A 192.168.254.20
origin.1773.work. 90 IN A 192.168.254.30
 
【事前確認、DNS-3に直接確認】
origin.1773.work. 90 IN A 192.168.254.10
origin.1773.work. 90 IN A 192.168.254.20
origin.1773.work. 90 IN A 192.168.254.30

【事前確認、DNS-2に直接確認】
cname.1773.work. 10 IN CNAME origin.1773.work.
origin.1773.work. 90 IN A 192.168.254.10
origin.1773.work. 90 IN A 192.168.254.20
origin.1773.work. 90 IN A 192.168.254.30

【ANAME確認、DNS-1に確認】
exsample.1773.work. 90 IN A 192.168.254.30
exsample.1773.work. 90 IN A 192.168.254.20
exsample.1773.work. 90 IN A 192.168.254.10
  ⇒ TTLは、Target Address Record のTTLに置き換わった。

 

PowerDNS ALIASレコード TTL関連の feature request の動向

現時点では未実装ではありますが、機能要望としては、下記の feature request がありました。 

この request においては CDN RR の TTL が 1 などの短期間に設定されている場合においても、一定時間キャッシュさせたいなどのケースにおいて利用する、ALIASレコード側のTTLをつかってTTLを上書き(調整)するオプションが要望されていました。

 

PowerDNS ALIASレコード の TTL を確認してみた 紹介でした。 

DNS ANAMEレコード (PowerDNS の ALIASレコード) を試してみる

DNS ANAMEレコード / PowerDNS ALIASレコード 関連記事

 

 

はじめに

Address-specific DNS aliases (ANAME) 」というDNS関連の Internet Draft が、2017年よりIETFに提出されており、つい先日 draft-03 が提出されました 🎉 

  • draft-00 ( 2017-03-24 提出, 2017-11-25 Expire )
  • draft-01 ( 2018-01-11 提出, 2018-07-15 Expire )
  • draft-02 ( 2018-10-15 提出, 2019-04-22 Expire )
  • draft-03 ( 2019-04-15 提出, 2019-10-17 Expire )  ← NEW

普段DNS関連技術は触れていないのですが、諸用により ANAMEレコード関連の情報を確認したり、draft-03 を確認したりなどをしていたため、微々たる情報量ではありますが、得た情報をこちらに書き留めておきます。

PowerDNS では ANAMEレコードの一部の機能を「ALIASレコード」として実装しているため、この機能を試してみた内容と併せて紹介しておきます。

なお、この記事においては、ANAMEレコードについての情報の一部分の紹介になります。すべての情報は書いていませんので、障りとしての参考情報程度にしていただけたらと思います。

 

CNAMEレコードの制約、ANAME レコードがなぜ必要なのか?

CNAMEレコードの制約

DNSプロバイダの DNS Made Easy が作成されている ANAMEレコードの紹介動画 ( Introducing the ANAME Record - YouTube ) がとても分かりやすかったのでお勧めです。英語が問題ない方はこちらの動画を確認していただくと、4分程度でざっと雰囲気を把握できるかと思います。

DNSではCNAMEレコードを使ってドメイン名の別名を定義することができます。しかし、CNAMEレコードには仕様上の制約があり、下記のことができません。

① Zone Apex(別名: Root Domain / Naked Domain) では CNAMEレコードを 作成することができない

私が所有している"1773.work"というドメインの例

(NG) 1773.work ← これはZone ApexなのでCNAME作成できない
(OK) example.1773.work ← これはサブドメインなのでCNAME作成可能

② CNAMEレコードはユニークでなければならない (同一の名前を他のレコードと共有することができない)

<NG その1> CNAMEを複数書いたりはできない

 examle.1773.work.    IN    CNAME    sv1.1773.work.
 examle.1773.work.    IN    CNAME    sv2.1773.work.

<NG その2> MXなど他のリソースレコードタイプと重複したりもできない

 example.1773.work.   IN    MX    10    mail.1773.work.
 example.1773.work.   IN    CNAME    origin.1773.work.

また、一般的なCNAMEレコードの参照では、DNS lookup を2度実行する必要があるため、参照速度が遅くなります。1度目のlookupでCNAMEレコードを見つけ、2度目のlookupで参照先のIPアドレスを見つけます。

これらの制約を解決するANAMEレコード

ANAMEレコードでは、CNAMEレコードのようにドメイン名の別名を定義できますが、上記のCNAMEレコードの仕様上の制約が取り除かれています

 

ANAME レコード 表示形式 と 専門用語

draft-03 より確認した  ANAMEレコードの表示形式 および 専門用語を紹介します。

表示形式

ANAMEレコードの表示形式はCNAMEレコードと同様です。

(owner)    (ttl)    (class)    ANAME    (target)
専門用語

Address Record : リソースレコードタイプが A または AAAA の DNSリソースレコード(名前やTTLなどを含むRRset全体)

Address Type : リソースレコードタイプ が A または AAAA のもの

Address Query : 任意の Address Type に対する DNSクエリ

Sibling Address Record : ANAMEと同じowner名のAddress Recordで、ANAMEの置き換えの対象

Target Address Record : ANAMEの最尾の対象 を解決して取得した Address Record

 

ANAMEレコードと、PowerDNS の ALIAS レコード について

PowerDNS は OSS の DNSサーバです。

Internet Draft 内 の Appendix では PowerDNS での実装について下記のように述べられています。

Appendix A. Implementation status

PowerDNS currently implements a similar authoritative-only feature using "ALIAS" records, which are expanded by the primary server and transfered as address records to secondaries.
======
PowerDNSは、現在、"ALIAS"レコードを使用して同様の権威のみの機能を実装しています。
プライマリサーバによって拡張され、アドレスレコードとしてセカンダリに転送されます。

 ( Address-specific DNS aliases (ANAME)  - draft-03 より抜粋)

 

PowerDNS Authoritaive Server の ALIAS レコードを試してみる

PowerDNS Authoritative Server ( 以降、PowerDNS と略 ) の 導入およびセットアップについては、公式ドキュメント  を参考に実施しました。

 PowerDNS での ALIASレコードの制御には pdns.conf に下記 3つのパラメタを記述しました。

  • expand-alias=yes : ALIASレコード拡張を有効にする。(v4.1.0以降)
  • resolver=(任意のアドレス):53 : ALIAS(と内部スタブリゾルバ)のリゾルバアドレス。(v4.1.0以降) 今回は自分の個人運用しているDNSサーバに向けています。
  • outgoing-axfr-expand-alias=yes : AXFRでのALIASレコード拡張を有効にする。(この内容についてはまだ検証できていませんが、一旦投入しています)
DNSレコードの準備

PowerDNSではいくつかのバックエンドの選択があります。私は PostgreSQL を選択しましたので、そちらの形式でレコードを準備します。

今回問い合わせ対象の name を "example.1773.work" とします。

■ ALIAS(ANAME)レコードの準備 (DNS-1 にて準備)

pdns=# SELECT * FROM domains ; 
id | name | master | last_check | type | notified_serial | account
----+--------------------+--------+------------+--------+-----------------+---------
1 | example.1773.work | | | NATIVE | |
pdns=# SELECT id, domain_id, name, type, content, ttl FROM records ;
id | domain_id | name | type | content | ttl
----+-----------+-------------------+-------+------------------+------
1 | 1 | example.1773.work | SOA | (略) | 60
2 | 1 | example.1773.work | NS | (略) | 60
3 | 1 | example.1773.work | ALIAS | origin.1773.work | 60

 ALIAS(ANAME)に利用するnameはSOA,NSと共用しており、CNAMEでは制限のため利用できないパターンになります。

■ Sibling Address Record の準備 (DNS-2 にて準備)

pdns.conf にてresolverとして指定したDNSサーバにレコードを準備します。こちらはNSDなので、その形式で準備しました。

origin.1773.work.    IN    A    10.20.100.10
DNS lookupの実行

この状態で DNS-1 に対して lookup を実行します。結果は下記のようになりました。

 # dig example.1773.work
 
 ; <<>> DiG 9.11.3-1ubuntu1.5-Ubuntu <<>> example.1773.work
 ;; global options: +cmd
 ;; Got answer:
 ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 35817
 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
 
 ;; OPT PSEUDOSECTION:
 ; EDNS: version: 0, flags:; udp: 512
 ;; QUESTION SECTION:
 ;example.1773.work.                        IN      A
 
 ;; ANSWER SECTION:
 example.1773.work.         358     IN      A       10.20.100.10
 
 ;; Query time: 190 msec
 ;; SERVER: (問い合わせ先)#53(問い合わせ先)
 ;; WHEN: Thu Apr 26 01:32:03 JST 2019
 ;; MSG SIZE  rcvd: 59
 

 

ANSWER SECTION にて 解決された Target Address Record が参照できることを確認しました。

 

DNS ANAMEレコード (PowerDNS ALIASレコード) を試してみた 紹介でした。

TimescaleDB + Grafana で 時系列データを可視化する

TimescaleDB と Grafana

TimescaleDB とは?

  • PostgreSQL の EXTENSION の 時系列データベース
  • Apache License v2 で公開されているOSS

Grafana とは?

  • ログ・データなどの可視化ツール
  • Apache License v2 で公開されているOSS

TimescaleDB と Grafana の組み合わせ

Grafana v5.3 で TimescaleDB への対応が機能追加されました。

この機能追加により TimescaleDB に格納した時系列データを Grafana を使って可視化できるようになりました。

こちらの内容を触ってみたので、書き留めておきます。

 

TimescaleDB、Grafana の セットアップログ

TimescaleDB/Grafanaともに、公式ドキュメントに、ディストリビューション毎のセットアップ手順が記載されていつので、そちらを確認してください。

私は Ubuntu Server 18.04 LTS 上にオールインワンで構築してみました。そのログを置いておきます。

(インストールバージョン)

PostgreSQL 10.5
TimescaleDB 0.12.1
Grafana 5.3.0-beta

※ 2018年10月頃にセットアップしたので、現在の最新バージョンより古め(TimescaleDBもv0.xx)です。手順は基本的には一緒だと思うのですが、正しくは公式ドキュメントを参照してください。

 

① PostgreSQL の インスト―ル

# apt-get update

# apt-get install postgresql

# psql --version
psql (PostgreSQL) 10.5 (Ubuntu 10.5-0ubuntu0.18.04)

# systemctl status postgresql
● postgresql.service - PostgreSQL RDBMS
Loaded: loaded (/lib/systemd/system/postgresql.service; enabled; vendor preset: enabled)
Active: active (exited) since Fri 2018-09-28 17:25:06 JST; 27s ago
Main PID: 2725 (code=exited, status=0/SUCCESS)
Tasks: 0 (limit: 1118)
CGroup: /system.slice/postgresql.service

 

② TimescaleDB の インスト―ル

## add-apt-repository を使うための事前準備
# apt install software-properties-common

# add-apt-repository ppa:timescale/timescaledb-ppa
# apt-get update

## PG 10.2+はこのバージョンをインストールする
# apt install timescaledb-postgresql-10

 

③ postgresql.conf の設定変更

# cd /etc/postgresql/10/main/
# vi postgresql.conf

## shared_preload_libraries に timescaledb を追加する
==============================
<変更前>
#shared_preload_libraries = '' # (change requires restart)

<変更後>
shared_preload_libraries = 'timescaledb' # (change requires restart)
==============================

## synchronous_commit を無効にする
==============================
<変更前>
#synchronous_commit = on # synchronization level;

<変更後>
synchronous_commit = off # synchronization level;
==============================

## max_locks_per_transaction を とりあえず最小にする
==============================
<変更前>
#max_locks_per_transaction = 64 # min 10

<変更後>
max_locks_per_transaction = 10 # min 10
==============================

## 反映
# service postgresql restart

 

④ postgresユーザのパスワード設定しておく ユーザ/postgresqlの両方

## ユーザパスワードの設定
# passwd postgres

# su - postgres

## posgresqlユーザパスワードの設定
postgres$ psql

postgres=# ALTER ROLE postgres with password 'postgres';

postgres=# SELECT * FROM pg_shadow;
usename | usesysid | usecreatedb | usesuper | userepl | usebypassrls | passwd | valuntil | useconfig
----------+----------+-------------+----------+---------+--------------+-------------------------------------+----------+-----------
postgres | 10 | t | t | t | t | md53175bce1d3201d16594cebf9d7eb3f9d | |
(1 row)

postgres=# \q

試用なので、postgresユーザ&パスワードpostgresで接続しています。

 

⑤ TimescaleDB用 の データベース 作成

# psql -U postgres -h localhost
Password for user postgres:

postgres=# CREATE database tutorial;
CREATE DATABASE

postgres=# \c tutorial

# CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;
WARNING:
WELCOME TO
_____ _ _ ____________
|_ _(_) | | | _ \ ___ \
| | _ _ __ ___ ___ ___ ___ __ _| | ___| | | | |_/ /
| | | | _ ` _ \ / _ \/ __|/ __/ _` | |/ _ \ | | | ___ \
| | | | | | | | | __/\__ \ (_| (_| | | __/ |/ /| |_/ /
|_| |_|_| |_| |_|\___||___/\___\__,_|_|\___|___/ \____/
Running version 0.12.1
For more information on TimescaleDB, please visit the following links:
1. Getting started: https://docs.timescale.com/getting-started
2. API reference documentation: https://docs.timescale.com/api
3. How TimescaleDB is designed: https://docs.timescale.com/introduction/architecture
Note: TimescaleDB collects anonymous reports to better understand and assist our users.
For more information and how to disable, please see our docs https://docs.timescaledb.com/using-timescaledb/telemetry.
CREATE EXTENSION

## 作成したDBへの接続確認
postgres$ psql -d tutorial
psql (10.5 (Ubuntu 10.5-0ubuntu0.18.04))
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

tutorial=# \dt
Did not find any relations.

 

⑥ Grafana のインストール

# wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.3.0-beta1_amd64.deb

# dpkg -i grafana_5.3.0-beta1_amd64.deb

# systemctl status grafana-server
● grafana-server.service - Grafana instance
Loaded: loaded (/usr/lib/systemd/system/grafana-server.service; disabled; ven
Active: inactive (dead)
Docs: http://docs.grafana.org

## HTTPポート番号の確認
# grep http_port /etc/grafana/grafana.ini
;http_port = 3000

# systemctl start grafana-server

 

⑦ Grafana のセットアップ

  • http://(対象サーバ):3000 にログイン 
  • http://(対象サーバ):3000/datasources よりデータソースを登録する

(設定例)

f:id:nozomi1773:20190414232500p:plain

(標準)

Name: 任意のデータソース名、Default: お好みで設定、Type: PostgreSQL

(PostgreSQL接続)

Host: サーバ+ポート、Database: 対象のDB名、User: 接続ユーザ、Password: 接続ユーザのパスワード、SSL Mode: 接続時のSSL利用

(PostgreSQL詳細)

Version: PostgreSQLのバージョン、TimescaleDB: チェックする、Min time interval: お好みで設定

 

TimescaleDB に デモ用のログを投入

Webサーバへのアクセスログを想定したデモ用のログを生成してみます。

(保有カラム)

  • time: 日時
  • http_host:  { first.com, second.com, third.com }
  • schema: { http, https }
  • http_status: { 1xx, 2xx, 3xx, 4xx, 5xx }

 

TimescaleDB用のテーブルの作成

データ投入するテーブルを作成します。

# psql -U postgres -d tutorial
Password for user postgres:
psql (10.5 (Ubuntu 10.5-0ubuntu0.18.04))
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

tutorial=# CREATE TABLE demo (
tutorial(# time TIMESTAMPTZ NOT NULL,
tutorial(# http_host TEXT NOT NULL,
tutorial(# schema TEXT NULL,
tutorial(# http_status TEXT NULL
tutorial(# );
CREATE TABLE

tutorial=# EXPLAIN SELECT * FROM demo ;
QUERY PLAN
---------------------------------------------------------
Seq Scan on demo (cost=0.00..16.10 rows=610 width=104)
(1 row)

HyperTableを作成します。(テーブルが空でないと作成できません)

tutorial=# SELECT create_hypertable('demo', 'time') ;
create_hypertable
--------------------
(31,public,demo,t)
(1 row)

tutorial=# EXPLAIN SELECT * FROM demo ;
QUERY PLAN
---------------------------------------------------------------
Append (cost=0.00..16.10 rows=610 width=104)
-> Seq Scan on demo (cost=0.00..16.10 rows=610 width=104)
(2 rows)

 

デモ用ログ投入実行

雑にランダムなデータを作成 して 投入しました。

 

テーブルに格納されたログを確認

tutorial=# SELECT * FROM demo ORDER BY time ;
time | http_host | schema | http_status
-------------------------------+------------+--------+-------------
2019-04-15 19:01:00.123457+09 | second.com | http | 3xx
2019-04-15 19:01:00.123457+09 | third.com | http | 4xx
2019-04-15 19:01:00.123457+09 | second.com | https | 4xx
2019-04-15 19:01:00.123457+09 | third.com | http | 2xx
2019-04-15 19:01:00.123457+09 | first.com | https | 2xx
2019-04-15 19:01:00.123457+09 | second.com | https | 3xx
2019-04-15 19:01:00.123457+09 | second.com | http | 2xx
(略)

 

Grafanaで可視化

ダッシュボード の Edit で 登録します。デフォルトのMetricではGUIベースで登録できます。

f:id:nozomi1773:20190416000337p:plain

SQLで記述する場合 Toggle Edit Mode を選択します。

f:id:nozomi1773:20190416000531p:plain

f:id:nozomi1773:20190416000613p:plain

上記は、通常の関数countを使っています。こちらでグラフを描画すると、下記のようになりした。

f:id:nozomi1773:20190416000704p:plain

 

TimescaleDBの関数を用いて可視化

 TimescaleDBには強力な関数が複数存在します。その一部を試してみます。

 ① 時系列最後尾のデータを取得

SELECT http_host, 
last(http_status, time) AS last_status
FROM demo
GROUP BY http_host

f:id:nozomi1773:20190416002817p:plain

 ② 丸め時間 15分で集計

SELECT 
time_bucket('15 minutes', time) AS "time",
count(http_status) AS "2xx"
FROM demo
WHERE http_status = '2xx'
GROUP BY time
ORDER BY 1

f:id:nozomi1773:20190416004707p:plain

 

  TimescaleDB と Grafana を組わせて使う紹介でした。

 

nginx 1.15.10 + njs で ssl_certificate, ssl_certificate_key に変数内のデータをロードする

nginx ngx_http_ssl_module 関連記事

 

この記事の概要

nginx 1.15.10Changes に 下記の Feature が 書かれていました 。

*) Feature: loading of SSL certificates and secret keys from variables.

http://nginx.org/en/CHANGES より抜粋 ) 

ssl_certificate, ssl_certificate_key に変数内のデータが利用できようになりました。この件について、動作検証した結果、njs との組み合わせで動作可能なことを確認したので、その内容について、書き留めておきます。

nginx.org のドキュメント に 下記の記述が追記されていました。

The value data:$variable can be specified instead of the file (1.15.10), 
which loads a certificate from a variable without using intermediate files.
Note that inappropriate use of this syntax may have its security implications,
such as writing secret key data to error log.

It should be kept in mind that due to the HTTPS protocol limitations for
maximum interoperability virtual servers should listen on different IP addresses.

http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate より抜粋 ) 

これまでの nginx バージョンの ssl_certificate, ssl_certificate_key においては、ファイル名 を指定して ファイル経由でデータをロードしていましたが、1.15.10 からは data:$変数名 という記述を使って変数内のデータをロードできるようになりました。

ssl_certificate  data:$変数名 ;
ssl_certificate_key data:$変数名 ;

 

変数の定義方法について、njs で試した理由

この件の ユースケース について、 nginx forum 内で Maxim Dounin さんが下記の内容を発言されていました。

This is intended to be used with some external means of providing 
certificates and keys, such as perl or njs code, or a keyval
database (http://nginx.org/r/keyval).

https://forum.nginx.org/read.php?2,283522,283536#msg-283536 より抜粋 )

  • ngx_http_perl_module
  • ngx_http_js_module
  • ngx_http_keyval_module (このmoduleに関してはcommercial subscriptionです)

などと組み合わせて使うことが意図されているとのことです。

ここに挙げられている 3つ以外でも nginx変数に値がセットできるなら動作するのでは?と思ったので、set (http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#set) を試してみたのですが、

set $(変数名) "-----BEGIN CERTIFICATE-----\n (証明書の中身をPEM形式で記述, 改行は\nとして記述) \n-----END CERTIFICATE-----\n";

SSLハンドシェイク時点ではnginx変数内に値が設定できていないという旨の下記のようなエラーが出力されました。この挙動からは、この記述パターンにおける set は処理順序がSSLハンドシェイクより後だと思われます。(個人的な推定です)

2019/04/02 20:32:00 [warn] 20231#20231: *14 using uninitialized "(変数名)" variable while SSL handshaking, client: (接続元アドレス), server: 0.0.0.0:443

2019/04/02 20:32:00 [error] 20231#20231: *14 cannot load certificate "data:": PEM_read_bio_PrivateKey() failed (SSL: error:0906D06C:PEM routines:PEM_read_bio:no start line:Expecting: ANY PRIVATE KEY) while SSL handshaking, client: (接続元アドレス), server: 0.0.0.0:443

※ "data:" なので nil値になっているようです。

また、lua_nginx_modulengx.var.VARIABLE を使ってnginx変数の設定するということも試みてみましたが、こちらも同様に、SSLハンドシェイクより前にnginx変数に値を設定することができませんでした。

こちらについては下記のドキュメントベースの情報としても、ngx.var.VARIABLE が利用可能な directive としては、SSLハンドシェイク前にフック可能なタイミングに利用できるなものが(現時点では)存在しないことを確認しました。

 という経緯から、ユースケースとして挙げられていたものの1つである  njs (ngx_http_js_module) を使って、今回の検証を実施しました。

 

検証の準備

① ngx_http_js_module の取得&配置

ngx_http_js_module は デフォルトではビルドされていません。公式の手順などを参考に取得&配置しました。

 

② moduleのロードの記述

ngx_http_js_module は、動的モジュールのため下記のような記述でロードします。

# nginx.conf上部に記述
# 配置場所が"modules/ngx_http_js_module.so"の場合

load_module modules/ngx_http_js_module.so;

 

③ httpディレクティブの記述

# 呼び出すjsファイルを指定します
# js_include ファイル名
js_include http.js ;

# 変数名に、呼び出す関数を紐づけます
# js_set $変数名 関数名
js_set $cert cert;
js_set $pkey pkey;

 

④ httpディレクティブ または serverディレクティブ の記述

# data:$変数名 で呼び出します
ssl_certificate data:${cert} ;
ssl_certificate_key data:${pkey} ;

 

⑤ jsファイルの用意

// 呼び出す関数を記述しておきます

function cert() {
var c ;
c = "-----BEGIN CERTIFICATE-----\n (証明書の中身をPEM形式で記述, 改行は\nとして記述) \n-----END CERTIFICATE-----\n";
return c ;
}

function pkey() {
var p ;
p = "-----BEGIN PRIVATE KEY----\n (秘密鍵の中身をPEM形式で記述, 改行は\nとして記述) \n-----END PRIVATE KEY----\n" ;
return p ;
}

 

動作検証の内容と結果

今回も、workerを1つにし、そのworkerに対して straceで挙動を確認しつつ、ログを確認しつつ、接続確認を行いました。

① nginx reload 時 に jsファイルがロードされることを確認

# strace nginx -s reload
execve("/sbin/nginx", ["nginx", "-s", "reload"], [/* 18 vars */]) = 0
brk(NULL) = 0x55db33d1d000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe50d6e5000


<!ここでjsファイルがOPEN!>
open("/etc/nginx/http.js", O_RDONLY) = 6
fstat(6, {st_mode=S_IFREG|0644, st_size=1994, ...}) = 0
read(6, "function cert() {\n var s;\n "..., 1994) = 1994

 

② 初回アクセスする ( 初回ハンドシェイク )

■ curlで アクセスする

$ curl -k --no-keepalive https://(テストドメイン名)
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>


   

■ straceの抜粋

[{EPOLLIN, {u32=2132214024, u64=140520577237256}}], 512, -1) = 1
accept4(7, {sa_family=AF_INET, sin_port=htons(25509), sin_addr=inet_addr("61.211.224.11")}, [16], SOCK_NONBLOCK) = 3
epoll_ctl(11, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=2132214520, u64=140520577237752}}) = 0
epoll_wait(11, [{EPOLLIN, {u32=2132214520, u64=140520577237752}}], 512, 60000) = 1
recvfrom(3, "\26", 1, MSG_PEEK, NULL, NULL) = 1
setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0
read(3, "\26\3\1\0\323\1\0\0\317\3\3", 11) = 11
read(3, "\211\334\1\202\335\0230}\f\224'\1\245! \221>|\341\231y\236\1\320\204|6|\232ff2"..., 205) = 205



stat("/usr/share/nginx/html/index.html", {st_mode=S_IFREG|0644, st_size=612, ...}) = 0
open("/usr/share/nginx/html/index.html", O_RDONLY|O_NONBLOCK) = 9
fstat(9, {st_mode=S_IFREG|0644, st_size=612, ...}) = 0
pread64(9, "<!DOCTYPE html>\n<html>\n<head>\n<t"..., 612, 0) = 612
write(3, "\27\3\3\3k\340\275u+K\376\211\245\23Xg=U!\357v\344\252\242\362\325\220K\330\364k\257"..., 880) = 880
write(5, "61.211.224.11 - - [02/Apr/2019:1"..., 94) = 94
close(9) = 0
epoll_wait(11, [{EPOLLIN, {u32=2132214520, u64=140520577237752}}], 512, 65000) = 1
read(3, "\25\3\3\0\32\3709\262\310\364\350,\251/_A\273\224\36\266\250C\243\306\320u\245W\223\210A", 33093) = 31
close(3) = 0


※ SSLハンドシェイク時に jsファイルは OPENされない ことを確認

 

③ jsファイルを書き換え (reloadしない)、再度アクセスする

故意に不正な証明書/鍵のデータに書き換えます。

function cert() {
var c ;
// 文字列を欠けさせます
c = "EGIN CERTIFICATE-----\n (証明書の中身をPEM形式で記述, 改行は\nとして記述) \n-----END CERTIFICATE-----\n";
return c ;
}

function pkey() {
var p ;
// 文字列を欠けさせます
p = "EGIN PRIVATE KEY----\n (秘密鍵の中身をPEM形式で記述, 改行は\nとして記述) \n-----END PRIVATE KEY----\n" ;
return p ;
}

  

■ curlで アクセスする

$ curl -k --no-keepalive https://(テストドメイン名)
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

挙動:https://(テストドメイン名) が適切に表示される

straceのログを見ていると②とフローが一致している。よって、jsファイルはreload時にのみ読み込まれるという挙動が推定できる。

 

④ reload の実行し、再度アクセスする

# nginx -s reload
$ curl -k --no-keepalive https://(テストドメイン名)
curl: (35) Peer reports it experienced an internal error.

挙動:証明書/鍵が不正だという旨のエラーになる

2019/04/02 21:43:31 [error] 25266#25266: *29 cannot load certificate "data:EGIN CERTIFICATE-----
(証明書の中身)) while SSL handshaking, client: (接続元アドレス), server: 0.0.0.0:443

jsファイルはreload時にのみ読み込まれるという挙動であると推定できる。

また、冒頭に紹介した nginx.org のドキュメントにも記載されていますが、error_logに証明書や鍵の中身が出力されるので扱いに注意が必要そうです。

 

nginx 1.15.10 + njs を使って、ssl_certificate, ssl_certificate_key に変数内のデータをロードする検証をした紹介でした。

# SSLハンドシェイクより前に nginx変数に設定できるのであれば njs 以外の他の方法でも試せそうです。

PreseedによるUbuntu Server自動インストール / early_command で実行可能な内容の調査メモ

Kickstartの%pre と Preseedのearly_command

事前設定ファイルを用いて、OS自動インストールをする仕組みとして、

が利用できます。

その設定ファイル内でコマンド・スクリプト実行を記述することができます。
そのなかでもパーティショニング前のスクリプト実行を定義する記述は下記になります。

  %pre-%end セクション

  preseed/early_command : preseed読み込み後できるだけ早いタイミングで実行

  partman/early_command : パーティショニング直前に実行

このタイミングでのコマンド実行が活躍するシーンの例としては、

  • 動的なパーティショニング定義を設定しておくことができる
  • 物理筐体のRAIDコントローラ対応のCLIを導入/実行する手法でのRAID構成を自動化できる

などが挙げられます。

 

early_commandで実行可能なコマンドの確認方法

Kickstartの%pre-%endセクションについては下記の RedHatのドキュメントに実行可能なコマンドのリストが記述されていました。標準的なコマンド一式が実行可能です。


一方Preseedのearly_commandについては、ドキュメントを確認したものの、なかなか実行可能なコマンドに言及している記述を見つけることができませんでした。

という経緯から調査していたので、ここに確認した情報を書き留めておきます。

 

① debian-installerの処理順序の確認

Debian Installerの内部処理については下記のドキュメントに纏められています。

 

debian-installerではパーティショナ起動前の処理では、メモリ上にロードしているのはdebian-installerの追加コンポーネントぐらいなので、early_commandで実行可能なコマンドは、メモリ上に展開されているinitrd(bootイメージ)に梱包されているコマンドと推測できます。

② initrdに梱包されているコマンドを確認

というわけで、梱包されているコマンドを確認するため、initrd(bootイメージ)を展開します。以降はUbuntu Server LTS 18.04のイメージで確認しています。

次項でコマンドの一覧をまとめましたが、実際に自分でinitrdを開封して詳細な中身を確認したいという方は、下記の内容を参考に、開封してください。

## imageダウンロード
# cd /tmp
# wget http://archive.ubuntu.com/ubuntu/dists/bionic-updates/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64/initrd.gz

## 開封
# mkdir ubuntu_initrd
# cd ubuntu_initrd
# zcat ../initrd.gz | cpio -i
235300 blocks
# ls -l
total 64
drwxr-xr-x 2 root root 4096 Mar 18 01:16 bin
drwxr-xr-x 2 root root 4096 Mar 18 01:16 dev
drwxr-xr-x 14 root root 4096 Mar 18 01:16 etc
-rwxr-xr-x 1 root root 456 Mar 18 01:16 init
drwxr-xr-x 2 root root 4096 Mar 18 01:16 initrd
drwxr-xr-x 17 root root 4096 Mar 18 01:16 lib
drwxrwxr-x 2 root root 4096 Mar 18 01:16 lib64
drwxr-xr-x 2 root root 4096 Mar 18 01:16 media
drwxr-xr-x 2 root root 4096 Mar 18 01:16 mnt
drwxr-xr-x 2 root root 4096 Mar 18 01:16 proc
drwxr-xr-x 2 root root 4096 Mar 18 01:16 run
drwxr-xr-x 2 root root 4096 Mar 18 01:16 sbin
drwxr-xr-x 2 root root 4096 Mar 18 01:16 sys
drwxrwxr-x 2 root root 4096 Mar 18 01:16 tmp
drwxrwxr-x 6 root root 4096 Mar 18 01:16 usr
drwxrwxr-x 7 root root 4096 Mar 18 01:16 var

early_commandで実行可能なコマンド一覧

前項の方法で調べた内容の一覧です。

【Kickstart(%pre)/Preseed(early_command)共通で実行可能】
arping、awk、basename、bzcat、cat、chmod、chown、chroot、chvt、cp、cut、date、dd、df、dirname、dmesg、echo、egrep、env、expr、false、fgrep、find、grep、gunzip、head、hostname、hwclock、ip、kill、killall、ln、losetup、ls、lsmod、md5sum、mkdir、mknod、mkswap、mktemp、modprobe、more、mount、mv、pidof、ping、ps、pwd、readlink、rm、rmdisk、rmmod、route、sed、sh、sha1sum、sleep、swqpoff、swapon、sort、sync、tail、tar、touch、true、umount、uniq、vconfig、wc、wget、zcat
【Kickstart(%pre)でのみ実行可能】
bash、bunzip2、chattr、chgrp、clear、cpio、du、e2fsck、e2label、eject、fdisk、fsck、 fsck.ext2、fsck.ext3、ftp、gzip、hdparm、ifconfig、insmod、ipcalc、less、load_policy、login、lsattr、lvm、mke2fs、mkfs.ext2、mkfs.ext3、mt、nslookup、opend、rpm、tee、telnet、top、tune2fs、vi、wipefs、xargs
【Preseed(early_command)でのみ実行可能】
anna、anna-install、apt-install、ar、archdetect、ash、blkid、blkdiscard、blockdev、block-attr、brltty、brltty-setup、bterm、check-missing-firemware、choose-mirror、ckcomp、ckcomp-mini、cmp、console-type、debian-installer、debian-installer-startup、debconf、debconf-copydb、debconf-dumpdb、debconf-disconect、debconf-get、debconf-set、debconf-set-selections、debconf-loadtemplate、dhclient、dh-client-script、dnsdomainname、env2debconf、ethdetect、fallocate、freeramdisk、fstrim、fetch-url、free、getopt、get-real-console-linux、gpgv、groups、halt、hotplug-pcmcia、hw-detect、httpd、id、in-target、init、insmod、kbd_mode、kill-all-dhcp、klogd、kmod、languagemap、list-devices、loadkeys、localechooser、logger、log-output、lowmem_debconf、lspci、lsscsi、main-menu、modinfo、mountmedia、nano、nc、netcfg、od、pidof、ping6、pivot_root、poweroff、preseed_command、preseed_fetch、printf、ptom、rdisc6、rdnssd、realpath、reboot、register-module、reopen-console、resolve、report-hw、seach-path、setfront、setupcon、setvtrgb、seq、shutdown、steal-ctty、swicth_root、syslogd、start-shell、stat、sysfs-update-devnames、sha256sum、sha512sum、test、tftp、tr、translation-check、trimtemplates、tty、udevadm、udhcpc、uname、udpkg、update-dev、user-params、unxz、usb-list、wpa_supplicant、xzcat

 

パーティショナ起動前のスクリプト実行(手動コマンド発行)

early_commandで記述する前にパーティショナ起動前のinitrd環境にログインして手動コマンド発行で動作確認しておくと便利そうでした。

  • pxeboot で preseedを指定しない状態でbootする
  • debian-installer の load conponent 直後に<Go back>でmenuへ抜ける
  • この状態で Execute a shell を選択
  • <Continue>でshellログイン
BusyBox v1.27.2 (Ubuntu 1:1.27.2-2ubuntu3) built-in shell (ash)
~ #

ここでスクリプト化する内容の動作確認が試せて、便利そうでした。

なお、この環境下においては、エディタはnano、シェルはashです。

 

パーティショナ起動前のスクリプト実行(Preseedのearly_command)

 Preseed内でのearly_commandでの動作確認をしていたところとして、最終行が正常終了(ステータス0)だと正常終了とみなされてしまうという挙動を確認しました。

なので、直接スクリプトを記述せずに、スクリプトダウンロードののち最終行で実行という流れで記述すると良さそうでした。

<記述例>
d-i partman/early_command string \
wget http://{{ 配布元Server }}scripts/prescript.sh -O /tmp/prescript.sh; \
chmod 777 /tmp/prescript.sh ; \
ash /tmp/prescript.sh

 

 またスクリプトについてはashスクリプトとしてのエラーハンドリングの仕方を意識する必要があります。

<記述例>
#!/bin/ash

RC=0

## 何かしらの処理
## 略
RC="$?"
if [ $RC -ne 0 ] ; then
exit $RC
fi

## 何かしらの処理
## 略
RC="$?"
if [ $RC -ne 0 ] ; then
exit $RC
fi

## 略

exit $RC

 

今回は Preseed の early_command で実行できる内容について、紹介しました。

実行可能なコマンドを移植するという内容についても現在実験中です。こちらについても、うまく動作できれば、別途紹介しようと思います。