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 で実行できる内容について、紹介しました。

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

nginx 1.15.9 の ssl_certificate, ssl_certificate_key の挙動確認

nginx ngx_http_ssl_module 関連記事

 

この記事の概要

nginx 1.15.9 で ssl_certificate, ssl_certificate_key に変数が使えるようになりました。

この変更により証明書・秘密鍵の動的な読み込みが可能になったのでは?と話題になっていました。

この件について、ちょうど挙動を確認したので、書き留めておきます。
深追いはできてないので、参考までに。

 

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

nginx.org

そのなかで下記の忠告が書かれていました。

Note that using variables implies that a certificate will be loaded for each SSL handshake, and this may have a negative impact on performance.
It should be kept in mind that due to the HTTPS protocol limitations for maximum interoperability virtual servers should listen on different IP addresses.

変数を使用すると、SSLハンドシェイク毎に 都度証明書(と鍵)がロードされ、パフォーマンスにはネガティブな影響を与えるようです。

パフォーマンスについては未だ計測できてないのですが、この都度ロードされるという挙動について確認してみました。

 

検証の準備

■serverディレクティブの記述

server {
listen 443 ssl;
server_name _ default;

access_log /var/log/nginx/default_access.log ltsv;
error_log /var/log/nginx/default_error.log notice;

set $ssl_server_name $http_host;

ssl_certificate /etc/nginx/cert/$ssl_server_name.crt;
ssl_certificate_key /etc/nginx/cert/$ssl_server_name.key;

~略~
}

・[set $(任意の変数名) $(http_hostなどの識別名としてつかう組込変数)]を定義
・定義した任意の変数名を ssl_certificate や ssl_certificate_key にパスの一部として渡します

※ssl_certificate や ssl_certificate_keyに直接$(http_hostなどの識別名としてつかう組込変数)を書くのはNGでした。(file open対象がnil値になります)

 

■Let's Encryptで証明書&鍵を準備(2セット)

# ls -l /etc/nginx/cert/
total 20
drwxr-xr-x 2 root root 4096 Mar 4 12:18 old
-rw-r--r-- 1 root root 1907 Mar 4 12:58 /etc/nginx/cert/xxx.1773.work.crt
-rw-r----- 1 root root 1704 Mar 4 12:58 /etc/nginx/cert/xxx.1773.work.key
-rw-r--r-- 1 root root 1907 Mar 4 12:51 /etc/nginx/cert/yyy.1773.work.crt
-rw-r----- 1 root root 1704 Mar 4 12:51 /etc/nginx/cert/yyy.1773.work.key

片系を退避 (動作確認を分かりやすくするため)

# mv yyy.* old/

# ls -l /etc/nginx/cert/
total 12
drwxr-xr-x 2 root root 4096 Mar 4 12:18 old
-rw-r--r-- 1 root root 1907 Mar 4 12:58 /etc/nginx/cert/xxx.1773.work.crt
-rw-r----- 1 root root 1704 Mar 4 12:58 /etc/nginx/cert/xxx.1773.work.key

 

検証の内容と結果

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

この間、 nginx の reload, restart は行わず、動的更新に対しての動作を確認しています。

 

① https://xxx.1773.work/ への初回アクセス

挙動:https://xxx.1773.work/が表示され 適切な証明書が表示される

■straceの抜粋

# strace -tt -s 1024 -p 21462
strace: Process 22157 attached
20:04:04.693471 epoll_wait(9,

[{EPOLLIN, {u32=2378772744, u64=139893758570760}}], 512, -1) = 1
20:04:08.085823 accept4(7, {sa_family=AF_INET, sin_port=htons(60941), sin_addr=inet_addr("<接続元アドレス>")}, [16], SOCK_NONBLOCK) = 3
20:04:08.085989 epoll_ctl(9, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=2378773240, u64=139893758571256}}) = 0
20:04:08.086055 epoll_wait(9, [{EPOLLIN, {u32=2378772744, u64=139893758570760}}, {EPOLLIN, {u32=2378773240, u64=139893758571256}}], 512, 60000) = 2
20:04:08.086118 accept4(7, {sa_family=AF_INET, sin_port=htons(60942), sin_addr=inet_addr("<接続元アドレス>")}, [16], SOCK_NONBLOCK) = 12
20:04:08.086197 epoll_ctl(9, EPOLL_CTL_ADD, 12, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=2378773488, u64=139893758571504}}) = 0
20:04:08.086266 recvfrom(3, "\26", 1, MSG_PEEK, NULL, NULL) = 1
20:04:08.086334 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0
20:04:08.086609 brk(NULL) = 0x5621533bf000
20:04:08.086673 brk(0x5621533e4000) = 0x5621533e4000
20:04:08.086754 read(3, "\26\3\1\2\0\1\0\1\374\3\3", 11) = 11
20:04:08.086869 read(3, "\366~~~略~~~", 506) = 506

<!ここでOPEN!>
20:04:08.087083 open("/etc/nginx/cert/xxx.1773.work.crt", O_RDONLY) = 13
20:04:08.087182 fstat(13, {st_mode=S_IFREG|0666, st_size=1907, ...}) = 0
20:04:08.087263 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3b8dce4000
20:04:08.087334 read(13, "-----BEGIN CERTIFICATE-----\n<証明書の内容>"..., 4096) = 1907
20:04:08.087553 read(13, "", 4096) = 0
20:04:08.087622 close(13) = 0
20:04:08.087783 munmap(0x7f3b8dce4000, 4096) = 0

<!ここでOPEN!>
20:04:08.087887 open("/etc/nginx/cert/xxx.1773.work.key", O_RDONLY) = 13
20:04:08.088032 fstat(13, {st_mode=S_IFREG|0666, st_size=1704, ...}) = 0
20:04:08.088189 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3b8dce4000
20:04:08.088298 read(13, "-----BEGIN PRIVATE KEY-----\n<秘密鍵の内容>"..., 4096) = 1704
20:04:08.088525 close(13) = 0
20:04:08.088587 munmap(0x7f3b8dce4000, 4096) = 0
20:04:08.090924 write(3, "~~~略~~~","..., 1809) = 1809
20:04:08.091136 read(3, 0x5621533bbc83, 33093) = -1 EAGAIN (Resource temporarily unavailable)
20:04:08.091276 epoll_wait(9, [{EPOLLIN, {u32=2378773488, u64=139893758571504}}], 512, 59999) = 1
20:04:08.091407 recvfrom(12, "\26", 1, MSG_PEEK, NULL, NULL) = 1
20:04:08.091525 setsockopt(12, SOL_TCP, TCP_NODELAY, [1], 4) = 0
20:04:08.091667 read(12, "\26\3\1\2\0\1\0\1\374\3\3", 11) = 11
20:04:08.091799 read(12, "~~~略~~~", 506) = 506

<!ここでOPEN!>
20:04:08.091990 open("/etc/nginx/cert/xxx.1773.work.crt", O_RDONLY) = 13
20:04:08.092114 fstat(13, {st_mode=S_IFREG|0666, st_size=1907, ...}) = 0
20:04:08.092231 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3b8dce4000
20:04:08.092348 read(13, "-----BEGIN CERTIFICATE-----\n<証明書の内容>"..., 4096) = 1907
20:04:08.092557 read(13, "", 4096) = 0
20:04:08.092676 close(13) = 0
20:04:08.092787 munmap(0x7f3b8dce4000, 4096) = 0

<!ここでOPEN!>
20:04:08.092913 open("/etc/nginx/cert/xxx.1773.work.key", O_RDONLY) = 13
20:04:08.093055 fstat(13, {st_mode=S_IFREG|0666, st_size=1704, ...}) = 0
20:04:08.093288 read(13, "-----BEGIN PRIVATE KEY-----\n<秘密鍵の内容>"..., 4096) = 1704
20:04:08.093582 munmap(0x7f3b8dce4000, 4096) = 0
20:04:08.095721 write(12, "~~~略~~~"..., 1809) = 1809
20:04:08.096013 read(12, 0x5621533bbc83, 33093) = -1 EAGAIN (Resource temporarily unavailable)
20:04:08.096170 epoll_wait(9, [{EPOLLIN, {u32=2378773240, u64=139893758571256}}], 512, 59994) = 1
20:04:08.117737 read(3, "~~~略~~~", 33093) = 126
20:04:08.118427 write(3, "~~~略~~~", 258) = 258

~~~略~~~

20:04:08.130079 write(5, "time:2019-03-04T20:04:08+09:00\thost:xxx.1773.work\thttp_host:xxx.1773.work\tscheme:https\tremote_addr:<接続元アドレス>\tremote_user:-\ttime_local:04/Mar/2019:20:04:08 +0900\trequest:GET / HTTP/1.1\tstatus:200\tbody_bytes_sent:0\thttp_referer:-\thttp_user_agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36\thttp_x_forwarded_for:-\trequest_time:0.000\tupstream_response_time:\tmsec:1551697448.128\tupstream_http_content_type:-\tupstream_status:-\tupstream_cache_status:-\n", 524) = 524

~~~略~~~

 

② https://xxx.1773.work/ への2度目のアクセス(再ハンドシェイク)

挙動:https://xxx.1773.work/が表示され 適切な証明書が表示される

straceのログを見ていると①とフローが一致。

ドキュメント記述の通り、SSLハンドシェイク毎に 都度証明書(と鍵)がロードされる という動作を確認しました。

 

③ https://yyy.1773.work/ へのアクセス (証明書未配置のパス)

 挙動:SSLハンドシェイク失敗

■straceの抜粋

~~~略~~~

20:12:51.337382 open("/etc/nginx/cert/yyy.1773.work.crt", O_RDONLY) = -1 ENOENT (No such file or directory)
20:12:51.337677 gettid() = 22157
20:12:51.337840 write(4, "2019/03/04 20:12:51 [error] 22157#22157: *7 cannot load certificate \"/etc/nginx/cert/yyy.1773.work.crt\": BIO_new_file() failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/etc/nginx/cert/yyy.1773.work.crt','r') error:2006D080:BIO routines:BIO_new_file:no such file) while SSL handshaking, client: <接続元アドレス>, server: 0.0.0.0:443\n", 367) = 367
20:12:51.338061 write(13, "\25\3\3\0\2\2P", 7) = 7
20:12:51.338266 close(13) = 0

~~~略~~~
■error_logへの出力

2019/03/04 20:12:51 [error] 22157#22157: *7 cannot load certificate "/etc/nginx/cert/yyy.1773.work.crt": BIO_new_file() failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/etc/nginx/cert/yyy.1773.work.crt','r') error:2006D080:BIO routines:BIO_new_file:no such file) while SSL handshaking, client: <接続元アドレス>, server: 0.0.0.0:443
2019/03/04 20:12:51 [error] 22157#22157: *6 cannot load certificate "/etc/nginx/cert/yyy.1773.work.crt": BIO_new_file() failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/etc/nginx/cert/yyy.1773.work.crt','r') error:2006D080:BIO routines:BIO_new_file:no such file) while SSL handshaking, client: <接続元アドレス>, server: 0.0.0.0:443
2019/03/04 20:12:51 [error] 22157#22157: *8 cannot load certificate "/etc/nginx/cert/yyy.1773.work.crt": BIO_new_file() failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/etc/nginx/cert/yyy.1773.work.crt','r') error:2006D080:BIO routines:BIO_new_file:no such file) while SSL handshaking, client: <接続元アドレス>, server: 0.0.0.0:443

誘導先の証明書(鍵)パスが存在していないとエラーに。 

 

④ https://yyy.1773.work/ へのアクセス (証明書の動的追加)

証明書追加を行います。

# mv old/yyy* ./

# ls -l /etc/nginx/cert/
total 20
drwxr-xr-x 2 root root 4096 Mar 4 12:18 old
-rw-r--r-- 1 root root 1907 Mar 4 12:58 /etc/nginx/cert/xxx.1773.work.crt
-rw-r----- 1 root root 1704 Mar 4 12:58 /etc/nginx/cert/xxx.1773.work.key
-rw-r--r-- 1 root root 1907 Mar 4 12:51 /etc/nginx/cert/yyy.1773.work.crt
-rw-r----- 1 root root 1704 Mar 4 12:51 /etc/nginx/cert/yyy.1773.work.key

 挙動:https://yyy.1773.work/ が表示され 適切な証明書が表示される

straceのログを見ていると①②とフローが一致。

reloadを伴わない動的追加が可能そうです。

 

⑤ https://yyy.1773.work/ へのアクセス (証明書の動的削除)

今度は、証明書を削除します。

# mv yyy.* old/

# ls -l /etc/nginx/cert/
total 12
drwxr-xr-x 2 root root 4096 Mar 4 12:18 old
-rw-r--r-- 1 root root 1907 Mar 4 12:58 /etc/nginx/cert/xxx.1773.work.crt
-rw-r----- 1 root root 1704 Mar 4 12:58 /etc/nginx/cert/xxx.1773.work.key

挙動:

(1) keepaliveが切れるまではhttps://yyy.1773.work/ が表示され 適切な証明書が表示される

(2) 再SSLハンドシェイク時より ハンドシェイク失敗する

 

今回は、これらのざっくりとした挙動を確認してみました。

 

Ansible で 動的リストをつかって 一部の処理をループ実行する

この記事の概要

Ansible の playbook を使って、

① 対象サーバからリスト(インターフェース一覧など)を取得する
② そのリストの項目毎に一部の処理をループ実行する

ということを、1回のplaybook実行でできる記述方法を模索していたところ、とあるアプローチに至ったので、その手法を書き留めておきます。

変数に定義するのはつらいリスト(流動的 or 膨大な量)に対して処理を実行したいときに使えそうな方法なのではないかと思っています。

 

実行バージョンによる留意事項

私が試した Ansible バージョンは 2.6.2です。

記事内の記述は、バージョン 2.4 以降の Action である import_tasksinclude_tasks をつかって記述しています。

バージョン 2.6.2だとまだ include でも記述可能ですが、下記のような include は 2.8で無くなる という旨の警告が出力されます。

[DEPRECATION WARNING]: 'include' for playbook includes. You should use 'import_playbook' instead. This
feature will be removed in version 2.8. Deprecation warnings can be disabled by setting
deprecation_warnings=False in ansible.cfg. 

バージョン 2.1以降~2.4未満で記述する場合は、逆に include を使わないとsyntax errorになる はずですので、

下記に記述する手法の import_tasksinclude_tasksinclude に置き換えて頂くと、同様の動作をすると思われます。

 

playbook の記述例と解説

さて、本題です。playbook の記述例 です。

<階層>

ansible.cfg
hosts
playbook.yml
roles
L test
L tasks
L main.yml
  L install.yml
  L config.yml
L setup.yml ← こちらがループ対象の処理

※ ansible.cfg、hosts、install.yml については今回の説明に内容の例示が不要ですので割愛させていただきます。

 

<playbook.yml>

---
- hosts: all
remote_user: root
roles:
- role: test

 

<main.yml>

---
- import_tasks: install.yml
tags:
- test
- test_install

- import_tasks: config.yml
tags:
- test
- test_config

 

<config.yml>

- name: config
command: echo "test config"

## ここで何かしらのリストを取得します
- name: get target interface name
shell: |
/sbin/ip -o link | awk -F': ' '{print $2}' | grep -v lo
register: interface_list
check_mode: no
changed_when: false

- include_tasks: setup.yml
with_items: "{{ interface_list.stdout_lines }}"

 わたしが例で示したリスト取得コマンドの実行結果の例はこのような内容です。

$ /sbin/ip -o link | awk -F': ' '{print $2}' | grep -v lo
eth0
eth1

これを、with_items: <レジスタ名>.stdout_lines で呼ぶと リストの各行を 1 item として setup.yml をループ実行させることができます。

 

<setup.yml>

- name: setup test
command: echo "{{ item }}"

 今回は、分かりやすいようにechoするだけにしてみます。

 

playbook の実行結果

さて、このplaybookを実行してみます。

ansible-playbook playbook.yml -v -D -t test

PLAY [all] **********************************************************************

(略)

TASK [test : install] ***********************************************************
changed: [(アドレス)] => {"changed": true, "cmd": ["echo", "install"], "delta": "0:00:00.002680", "end": "2019-02-22 23:58:21.466355", "rc": 0, "start": "2019-02-22 23:58:21.463675", "stderr": "", "stderr_lines": [], "stdout": "install", "stdout_lines": ["install"]}

TASK [test : config] ************************************************************
changed: [(アドレス)] => {"changed": true, "cmd": ["echo", "config"], "delta": "0:00:00.002671", "end": "2019-02-22 23:58:22.044150", "rc": 0, "start": "2019-02-22 23:58:22.041479", "stderr": "", "stderr_lines": [], "stdout": "config", "stdout_lines": ["config"]}

TASK [test : get target interface name] *****************************************
ok: [(アドレス)] => {"changed": false, "cmd": "/sbin/ip -o link | awk -F': ' '{print $2}'", "delta": "0:00:00.004626", "end": "2019-02-22 23:58:22.631947", "rc": 0, "start": "2019-02-22 23:58:22.627321", "stderr": "", "stderr_lines": [], "stdout": "eth0\neth1", "stdout_lines": ["eth0", "eth1"]}

TASK [test : include_tasks] *****************************************************
included: /home/nozomi/work/test-loop/roles/test/tasks/setup.yml for (アドレス)
included: /home/nozomi/work/test-loop/roles/test/tasks/setup.yml for (アドレス)

TASK [test : setup test] ********************************************************
changed: [(アドレス)] => {"changed": true, "cmd": ["echo", "lo"], "delta": "0:00:00.002642", "end": "2019-02-22 23:58:23.403662", "rc": 0, "start": "2019-02-22 23:58:23.401020", "stderr": "", "stderr_lines": [], "stdout": "eth0", "stdout_lines": ["eth0"]}

TASK [test : setup test] ********************************************************
changed: [(アドレス)] => {"changed": true, "cmd": ["echo", "eth0"], "delta": "0:00:00.002678", "end": "2019-02-22 23:58:24.035418", "rc": 0, "start": "2019-02-22 23:58:24.032740", "stderr": "", "stderr_lines": [], "stdout": "eth1", "stdout_lines": ["eth1"]}

PLAY RECAP **********************************************************************

(略)

 実行処理の順序は下記のようになりました。

main.yml
→ install.yml
task: install
→ config.yml
task: config
task: include_tasks
→ setup.yml
task: setup test (1回目/item=eth0)
task: setup test (2回目/item=eth1)

 このように 動的リストをつかって 一部の処理をループ実行させることができました。