CentOS 6.4 以降の論理ボリュームのシンプロビジョニング

CentOS 6.4 (RHEL 6.4) 以降(CentOS 7はもちろん)は論理ボリュームのシンプロビジョニングが可能になっていた。
実際の運用で使用するかどうかはともかく、大きなボリュームを使用する検証作業を行ううえではとても役に立つ。

# pvcreate /dev/sde
  Physical volume "/dev/sde" successfully created
# vgcreate thinpoolvg /dev/sde
  Volume group "thinpoolvg" successfully created
# lvcreate -L 10G -T --thinpool pool thinpoolvg
  Logical volume "pool" created

# lvdisplay thinpoolvg/pool
  --- Logical volume ---
  LV Name                pool
  VG Name                thinpoolvg
  LV UUID                FNWrpA-Pwi5-uQ1G-ssFm-2QIl-q0M3-V1jKXj
  LV Write Access        read/write
  LV Creation host, time garnet-vm01, 2015-03-03 09:51:47 +0900
  LV Pool transaction ID 0
  LV Pool metadata       pool_tmeta
  LV Pool data           pool_tdata
  LV Pool chunk size     64.00 KiB
  LV Zero new blocks     yes
  LV Status              available
  # open                 0
  LV Size                10.00 GiB
  Allocated pool data    0.00%
  Allocated metadata     0.62%
  Current LE             2560
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           253:6
# lvcreate -T thinpoolvg/pool -V 50G -n volume50g
  Logical volume "volume50g" created

# lvcreate -T thinpoolvg/pool -V 100G -n volume100g
  Logical volume "volume100g" created

# lvdisplay thinpoolvg/volume50g
  --- Logical volume ---
  LV Path                /dev/thinpoolvg/volume50g
  LV Name                volume50g
  VG Name                thinpoolvg
  LV UUID                M99pkt-QnoE-TuPi-wa6M-yyax-M5yU-XP10Px
  LV Write Access        read/write
  LV Creation host, time garnet-vm01, 2015-03-03 09:53:54 +0900
  LV Pool name           pool
  LV Status              available
  # open                 0
  LV Size                50.00 GiB
  Mapped size            0.00%
  Current LE             12800
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           253:7

# lvdisplay thinpoolvg/volume100g
  --- Logical volume ---
  LV Path                /dev/thinpoolvg/volume100g
  LV Name                volume100g
  VG Name                thinpoolvg
  LV UUID                Vu7efq-DuRG-Vw1m-fA0B-g9iY-CU6O-CqMjop
  LV Write Access        read/write
  LV Creation host, time garnet-vm01, 2015-03-03 10:32:36 +0900
  LV Pool name           pool
  LV Status              available
  # open                 0
  LV Size                100.00 GiB
  Mapped size            0.00%
  Current LE             25600
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           253:8

ext4 ファイルシステムのオーバーヘッドを厳密に理解する

ファイルシステムを作成すると、ファイルシステム自体の管理領域などのため、ファイルシステムを作成するデバイス・ボリュームの容量を100%使えるようにはならない。

では何パーセントが減ってしまうのか。10%あれば大丈夫なのか、3%程度でもよいのか、厳密には決まらないのか、そんな疑問・不安を取り除くために検証および論理的な裏取りを行った。

検証環境は CentOS 6.4 (x86_64) で、ファイルシステムext4 である。なお、ブロックサイズは 4KB を前提にする。CentOS 7 (RHEL 7) でも考え方は同じだが、計算の元になる基礎値に差があるため注意が必要(「その他」にて触れる)。

検証結果

128M, 256M, 512M, 1024M, 1.5G, 2G, ・・・ と20GまでのLVを作成し、実際にファイルシステムを作成。マウントした際の df -k の Available 列の値について、LVサイズとの比をグラフ化した結果が以下である(95%の場合は、LVサイズの95%がdf -k の Available の容量だったことになる)。


  • LVのサイズが小さい(ファイルシステムのサイズが小さい)場合には、オーバーヘッドが目立ちやすく、単純にLVのサイズに比例して使用可能(Available)な容量の比率が増えるわけではない。
  • LVのサイズが4GB以上になれば使用可能な容量の比率は単調に増加する。

ext4 ファイルシステムのレイアウト

ブロックグループによる分割

ext4 ファイルシステムは、128MB 単位のブロックグループの集合として構成されている。1GBのボリュームであれば8個のブロックグループに分割される。ファイルシステム自体の情報(Super Block)、ブロックグループの情報(Group Descriptor)はブロックグループ0にマスタ(プライマリ)が格納され、バックアップ(ミラー)が 3, 5, 7 のそれぞれの冪乗の番号のブロックグループに格納される。

ブロックグループ内のレイアウト

各ブロックグループ内のレイアウトを以下に示す。ここで Data Blocks 以外は全て使用可能な容量としてはカウントできないものであり、オーバーヘッドである。Super block, Data Block Bitmap, i-node Bitmap は固定長であるため、ブロックグループ数に単純に比例する。そして Data Blocks のサイズは A, B, C が決まることで自動的に決まってくる。


領域 サイズ 用途
Super block 1ブロック ファイルシステム自体の情報(メタデータ)を格納する領域。
Group Descriptors 可変 ブロックグループの情報を格納する構造(ブロックグループ記述子)の集合。1ブロックグループあたりに1記述子が対応し、1記述子のサイズは32バイトになる(64bitオプションがつく場合は64バイトだが、64bitのCentOSでもこのオプションはつかない)。ファイルシステムのサイズ16GB(4KB÷32byte×128MB(1ブロックグループのサイズ))ごとに1ブロックが必要。
Reserved GDT Blocks 可変 ファイルシステムの拡張により、ブロックグループ数が増加した際に、Group Descriptorsを拡張するための予約領域。ファイルシステム作成時の初期サイズの1024倍まで拡張できるようにこの領域は確保される(8GBのファイルシステムを作成した場合は、8TBまで拡張できるように、Reserved GDT Blocks が確保される)。ただし、CentOS 6 (RHEL 6) における、ext4 ファイルシステムのサイズ上限(16TB)を超えることはないため、Group Descriptors + Reserved GDT Blocks のブロック数は1024(16TB÷16GB)が上限となる。
Data Block Bitmap 1ブロック Data Blocks のブロックの使用状況をビットマップで管理する。1ブロック=4KB=32Kビットのため、ブロックグループ内のブロック数は32Kに限定される。
i-node Bitmap 1ブロック i-node テーブルの使用状況をビットマップで管理する。
i-node Table 可変(実質512ブロック固定) i-node を格納する領域。1 i-node のサイズは256バイトになる。データブロックに対するi-nodeの比率の指定により領域のサイズは変化する。デフォルトでは16KB(4ブロック)ごとに1 i-node であるため、2MB(32K÷4×256byte)=512ブロックになる。
Data Blocks 可変 データブロックを格納する領域。実際にファイルデータを保存する領域。


Group Descriptors と Reserved GDT Blocks のサイズ

Group Descriptors と Reserved GDT Blocks のサイズ関係を以下に示す。

i-node Tables のサイズ

mkfs.ext4 のオプション -i bytes-per-inode と i-node Tables のサイズ関係を以下に示す。

ext4 ファイルシステムのオーバーヘッド

オーバーヘッド要因

ext4 ファイルシステムのオーバーヘッド要因を以下に示す。これまでのブロックグループ内のオーバーヘッドに加えて、ジャーナル領域(J)と予約領域(M)を加えている。

ジャーナル領域

ext4ジャーナルファイルシステムであるため、ジャーナルを書き込む領域が必要である。このジャーナル領域はファイルシステムのサイズ(ボリュームのサイズ)により以下の通りに変化する。

ボリュームサイズ ジャーナル領域のサイズ
1GB未満 16MB
2GB未満 32MB
4GB未満 64MB
4GB以上 128MB


予約領域

ext 2/3/4 ファイルシステムでは root ユーザのみが書き込める予約領域をデフォルトで 5% 確保する。この予約領域は df -k の Available には含まれず、使用可能とは見なされない。厳密にはオーバーヘッドではないが、使用可能な領域としては見なされないため、オーバーヘッドとして考慮する。

mkfs.ext4 コマンドの -m オプションにて予約する割合(%)を指定できる。ファイルシステム作成後でも tune2fs コマンドにて変更できる。

16GB以上のサイズの ext4 のオーバーヘッド

16GB未満の場合、Group Descriptors, Reserved GDT Blocks, ジャーナル領域のサイズが変動する。16GB以上ではそれらが固定値となる。


固定値になったことで以下の単純な式でオーバーヘッドが計算できる。

変数
V ファイルシステムを作成するボリュームのサイズ(MB単位)
N ブロックグループの数
S プライマリ Super block およびバックアップ Super block を持つブロックグループの数
m 予約領域の割合(デフォルトは5%)
O オーバーヘッド


その他

検証した環境の /etc/mke2fs.conf
# cat /etc/mke2fs.conf 
[defaults]
	base_features = sparse_super,filetype,resize_inode,dir_index,ext_attr
	blocksize = 4096
	inode_size = 256
	inode_ratio = 16384

[fs_types]
	ext3 = {
		features = has_journal
	}
	ext4 = {
		features = has_journal,extent,huge_file,flex_bg,uninit_bg,dir_nlink,extra_isize
		inode_size = 256
	}
	ext4dev = {
		features = has_journal,extent,huge_file,flex_bg,uninit_bg,dir_nlink,extra_isize
		inode_size = 256
		options = test_fs=1
	}
	small = {
		blocksize = 1024
		inode_size = 128
		inode_ratio = 4096
	}
	floppy = {
		blocksize = 1024
		inode_size = 128
		inode_ratio = 8192
	}
	news = {
		inode_ratio = 4096
	}
	largefile = {
		inode_ratio = 1048576
		blocksize = -1
	}
	largefile4 = {
		inode_ratio = 4194304
		blocksize = -1
	}
	hurd = {
	     blocksize = 4096
	     inode_size = 128
	}
16GBのファイルシステムを作成した際の tune2fs -l 結果

ファイルシステムのオプションなどの確認用に。

# cat /tmp/overheadtest/tune2fs.txt 
tune2fs 1.41.12 (17-May-2010)
Filesystem volume name:   <none>
Last mounted on:          <not available>
Filesystem UUID:          cfcb2085-2fd2-49d5-be17-349db7559e2c
Filesystem magic number:  0xEF53
Filesystem revision #:    1 (dynamic)
Filesystem features:      has_journal ext_attr resize_inode dir_index filetype needs_recovery extent
                          flex_bg sparse_super large_file huge_file uninit_bg dir_nlink extra_isize
Filesystem flags:         signed_directory_hash 
Default mount options:    (none)
Filesystem state:         clean
Errors behavior:          Continue
Filesystem OS type:       Linux
Inode count:              1048576
Block count:              4194304
Reserved block count:     209715
Free blocks:              4084463
Free inodes:              1048565
First block:              0
Block size:               4096
Fragment size:            4096
Reserved GDT blocks:      1023
Blocks per group:         32768
Fragments per group:      32768
Inodes per group:         8192
Inode blocks per group:   512
Flex block group size:    16
Filesystem created:       Sun Mar  1 09:25:30 2015
Last mount time:          Sun Mar  1 09:25:40 2015
Last write time:          Sun Mar  1 09:25:40 2015
Mount count:              1
Maximum mount count:      30
Last checked:             Sun Mar  1 09:25:30 2015
Check interval:           15552000 (6 months)
Next check after:         Sun Aug 30 09:25:30 2015
Lifetime writes:          388 MB
Reserved blocks uid:      0 (user root)
Reserved blocks gid:      0 (group root)
First inode:              11
Inode size:               256
Required extra isize:     28
Desired extra isize:      28
Journal inode:            8
Default directory hash:   half_md4
Directory Hash Seed:      9011fc60-1cb7-4fcf-b6d1-0ef3f4d72ee0
Journal backup:           inode blocks
オーバーヘッド要因の漏れが無いことの確認

mkfs.ext4 -b 4096 -m 5

mkfs.ext4 -b 4096 -m 0

どこに収束するのか

CentOS 6(RHEL 6)の ext4 の最大サイズである16TBまでのオーバーヘッドを上記式に基づき計算してグラフ化したものを示す。

結局のところ log を含む S はV が 16TB となっても 25 にしかならない。そのため、S をファクターに持つ項は次第に無視可能になる。ジャーナル領域も固定値であり、無視できる。予約領域を除くと、2つのBitmapとi-node の領域のみが目立つオーバーヘッドである。128MB のブロックグループにおいて 514×4KB がオーバーヘッドであるとすれば、オーバーヘッドは約 1.569% となる。つまり、使用可能な容量は 98.431% に近付く。

検証用のスクリプト
  • /tmp/overheadtest を作成し、/tmp/overheadtest/overheadtest.sh を作成する。
  • 検証用のLVを作成するためのVGとして overheadtestvg を作成しておく。
  • /mnt をマウントポイントとして使用する。

overheadtest.sh

#!/bin/bash

OHTEST_LVSIZE_MB="$1"
OHTEST_MKFS_OPTS="$2"

OHTEST_VG=overheadtestvg
OHTEST_LV=overheadtestlv
OHTEST_MNTPNT=/mnt
OHTEST_TMP=/tmp/overheadtest

lvcreate -L $OHTEST_LVSIZE_MB -n $OHTEST_LV $OHTEST_VG &> /dev/null || exit 99
sleep 1
mkfs.ext4 -b 4096 $OHTEST_MKFS_OPTS /dev/$OHTEST_VG/$OHTEST_LV &> $OHTEST_TMP/mkfs.txt || exit 99
sleep 1
mount /dev/$OHTEST_VG/$OHTEST_LV $OHTEST_MNTPNT || exit 99
sleep 1

lvdisplay /dev/$OHTEST_VG/$OHTEST_LV  > $OHTEST_TMP/lvdisplay.txt
tune2fs -l /dev/$OHTEST_VG/$OHTEST_LV > $OHTEST_TMP/tune2fs.txt
df -k                                 > $OHTEST_TMP/df.txt

umount $OHTEST_MNTPNT || exit 99
sleep 1
lvremove -f /dev/$OHTEST_VG/$OHTEST_LV &> /dev/null || exit 99
sleep 1


function grepcut {
   f="$1"; g="$2"; a="$3"
   grep "$g" $f | awk "{print \$$a}"
}

LVD=$OHTEST_TMP/lvdisplay.txt
  LVD_LV_SIZE=$(grepcut $LVD "LV Size" 3)$(grepcut $LVD "LV Size" 4)
  LVD_CURRENT_LE=$(grepcut $LVD "Current LE" 3)
MFS=$OHTEST_TMP/mkfs.txt
  MFS_BLOCK_GROUPS=$(grepcut $MFS " block group" 1)
  MFS_JOURNAL_BLOCKS=$(grepcut $MFS "^Creating journal " 3 | tr -d '()')
T2F=$OHTEST_TMP/tune2fs.txt
  T2F_INODE_COUNT=$(grepcut $T2F "^Inode count:" 3)
  T2F_BLOCK_COUNT=$(grepcut $T2F "^Block count:" 3)
  T2F_RESERVED_BLOCK_COUNT=$(grepcut $T2F "^Reserved block count:" 4)
  T2F_FREE_BLOCKS=$(grepcut $T2F "^Free blocks:" 3)
  T2F_FREE_INODES=$(grepcut $T2F "^Free inodes:" 3)
  T2F_BLOCK_SIZE=$(grepcut $T2F "^Block size:" 3)
  T2F_INODE_SIZE=$(grepcut $T2F "^Inode size:" 3)
  T2F_RESERVED_GDT_BLOCKS=$(grepcut $T2F "^Reserved GDT blocks:" 4)
  T2F_BLOCKS_PER_GROUP=$(grepcut $T2F "^Blocks per group:" 4)
  T2F_INODES_PER_GROUP=$(grepcut $T2F "^Inodes per group:" 4)
  T2F_INODE_BLOCKS_PER_GROUP=$(grepcut $T2F "^Inode blocks per group:" 5)
DFK=$OHTEST_TMP/df.txt
  DFK_1KBLOCKS=$(grepcut $DFK "$OHTEST_MNTPNT" "(NF-4)")
  DFK_USED=$(grepcut $DFK "$OHTEST_MNTPNT" "(NF-3)")
  DFK_AVAILABLE=$(grepcut $DFK "$OHTEST_MNTPNT" "(NF-2)")

HCSV="LVSIZE_MB,MKFS_OPTS"
CSV="$OHTEST_LVSIZE_MB,\"$OHTEST_MKFS_OPTS\""
for v in $(set | egrep "LVD_|MFS_|T2F_|DFK_")
do
    HCSV="$HCSV,${v%%=*}"
    CSV="$CSV,${v#*=}"
done
echo $HCSV
echo $CSV

exit 0

実行例・・・

# /tmp/overheadtest/overheadtest.sh 1024 "-m 5"
LVSIZE_MB,MKFS_OPTS,DFK_1KBLOCKS,DFK_AVAILABLE,DFK_USED,LVD_CURRENT_LE,LVD_LV_SIZE,MFS_BLOCK_GROUPS,
MFS_JOURNAL_BLOCKS,T2F_BLOCKS_PER_GROUP,T2F_BLOCK_COUNT,T2F_BLOCK_SIZE,T2F_FREE_BLOCKS,
T2F_FREE_INODES,T2F_INODES_PER_GROUP,T2F_INODE_BLOCKS_PER_GROUP,T2F_INODE_COUNT,T2F_INODE_SIZE,
T2F_RESERVED_BLOCK_COUNT,T2F_RESERVED_GDT_BLOCKS
1024,"-m 5",1032088,945608,34052,256,1.00GiB,8,8192,32768,262144,4096,249509,65525,8192,512,65536,256,13107,63
ブロックグループ0の内部を覗く方法

dumpe2fs コマンドを使えば良い。ブロックグループが多くなると出力が増えるので適当に head で切り捨てる。

# dumpe2fs /dev/testoverheadvg/testoverheadlv | head -n 100
・・・ (中略) ・・・
Group 0: (Blocks 0-32767) [ITABLE_ZEROED]
  Checksum 0xb51a, unused inodes 8181
  Primary superblock at 0, Group descriptors at 1-1
  Reserved GDT blocks at 2-256
  Block bitmap at 257 (+257), Inode bitmap at 273 (+273)
  Inode table at 289-800 (+289)
  24281 free blocks, 8181 free inodes, 2 directories, 8181 unused inodes
  Free blocks: 8487-32767
  Free inodes: 12-8192
・・・ (中略) ・・・
CentOS 7 (RHEL 7) における差

CentOS 6.4 と CentOS 7.0 では以下の差が確認できた。基本的な計算の考え方は変わらないが、以下の差を反映すること。特に Group Descriptors + Reserved GDT Blocks の部分は元々のルール(1024倍の拡張余地を確保するルール)を守らない拡張になっている。

比較観点 CentOS 6.4 CentOS 7.0 備考
Group Descriptorのサイズ 32バイト 64バイト CentOS 7.0 では Filesystem features: 64bit が指定されており、ブロックグループ記述子は64バイトに拡張されている。
bytes-per-inode(inode_ratio) 16KB 32KBや64KBもある(規模が大きい場合) mke2fs.conf 内に huge や big が追加されている。
ext4 ファイルシステムの最大サイズ 16TB 50TB RHEL社が公式にサポートするサイズが増加。Group Descriptors + Reserved GDT Blocks が 50TB では 6400 ブロックまで伸長する。ファイルシステムのサイズごとの Group Descriptors と Reserved GDT Blocks のブロック数を別表に示す。

CentOS 7.0 での検証結果。Reserved GDT Blocks は 1024 を超えないように制限されているように見える。

ファイルシステムのサイズ Group Descriptors ブロック数 Reserved GDT Blocks ブロック数
4GB 1 511
16GB 2 1024
50GB 7 1024
100G 13 1024
4TB 512 1024
12TB 1536 512
16TB 2048 0
50TB 6400 0

ansible で1サーバでも処理が失敗したらそこで処理を打ち切る方法

ansible ではすべての対象ホストで処理が失敗しない限り次に処理を進める。
再実行を考えるとこの動作が嫌な場合がある。もちろん冪等性が完全にあれば、単純な再実行でも問題は無いはずだ。しかし、冪等性がない処理や、完全に横並びで処理を進めたい場合には1サーバが失敗しただけでも処理を止めたい場合もある。
特に変更処理前の「事前検証」を実施している場合はそうありたい(事前検証をクリアした環境に対して何らかのインストール処理を継続するとか)。

そんな場合は max_fail_percentage を 0 にすればよい(0%のサーバの失敗を許容する=失敗を一切許容しない)。

check.yml を以下のとおりに作成する。

- hosts: '{{hosts}}'
  gather_facts: false
  max_fail_percentage: 0
  tasks:
    - name: check reachability
      ping:

    - name: check reachability
      ping:

garnet-vm10, garnet-vm11 のみが起動しており、garnet-vm12 が起動していない状態で実行すると、最初の ping 失敗で全体が停止する。

# ansible-playbook check.yml -e hosts=all

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

TASK: [check reachability] **************************************************** 
fatal: [garnet-vm12] => SSH encountered an unknown error during the connection.
We recommend you re-run the command using -vvvv, which will enable SSH debugging output to help diagnose the issue
ok: [garnet-vm11]
ok: [garnet-vm10]

FATAL: all hosts have already failed -- aborting

PLAY RECAP ******************************************************************** 
           to retry, use: --limit @/root/check.retry

garnet-vm10                : ok=1    changed=0    unreachable=0    failed=0   
garnet-vm11                : ok=1    changed=0    unreachable=0    failed=0   
garnet-vm12                : ok=0    changed=0    unreachable=1    failed=0   

なお、上記の check.yml で gather_facts: false としているところを削除する、もしくは gather_facts: true にすると以下の実行結果になる。
GATHERING FACTS できないサーバが自動的に無視されてしまっているように見えるので、希望の対象サーバが全台チェックされるためには注意したほうがよさそう。GATHERING FACTS 関連のマニュアルを読めば謎が解けるのだろうか。

# ansible-playbook check.yml  -e hosts=all

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

GATHERING FACTS *************************************************************** 
fatal: [garnet-vm12] => SSH encountered an unknown error during the connection.
We recommend you re-run the command using -vvvv, which will enable SSH debugging output to help diagnose the issue
ok: [garnet-vm11]
ok: [garnet-vm10]

TASK: [check reachability] **************************************************** 
ok: [garnet-vm10]
ok: [garnet-vm11]

TASK: [check reachability] **************************************************** 
ok: [garnet-vm10]
ok: [garnet-vm11]

PLAY RECAP ******************************************************************** 
           to retry, use: --limit @/root/check.retry

garnet-vm10                : ok=3    changed=0    unreachable=0    failed=0   
garnet-vm11                : ok=3    changed=0    unreachable=0    failed=0   
garnet-vm12                : ok=0    changed=0    unreachable=1    failed=0   

ansible の GATHERING FACTS を実行しない

GATHERING FACTSで取得した変数に基づいた処理を行わないのであれば、GATHERING FACTSを実行しないことで多少の時間短縮がはかれる。

プレイブック上で実行しないことを明示する

gather_facts: False を指定すればよい。

---
- hosts: '{{ hosts }}'
  gather_facts: False
  tasks:
    - name: connection test
      ping:

実行すると GATHERING FACTS が表示されなくなった。

# ansible-playbook ping.yml -e hosts=all

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

TASK: [connection test] ******************************************************* 
ok: [garnet-vm10]
ok: [garnet-vm11]

PLAY RECAP ******************************************************************** 
garnet-vm10                : ok=1    changed=0    unreachable=0    failed=0   
garnet-vm11                : ok=1    changed=0    unreachable=0    failed=0   

デフォルトで実行しなくする

設定ファイル /etc/ansible/ansible.cfg 上で gathering: explicit 指定すればよい。

[defaults]
gathering: explicit

wget と curl

wgetFTP も扱えるとは恥ずかしながら知らなかった。

ちょこっと wgetcurl の比較記事などをメモ。

curl の対応プロトコル

FTP, FTPS, HTTP, HTTPS, SCP, SFTP, TFTP, TELNET, DICT, LDAP, LDAPS, FILE, POP3, IMAP, SMTP, RTMP, RTSP

wget の対応プロトコル

HTTP, HTTPS, FTP

ansible でノーパスログイン(公開鍵認証)設定する

公開鍵認証をするために、対象のサーバの指定ユーザの authorized_keys を設定するモジュール「authorized_key」があるのでそれを使用する。

前提

  • authorized_key モジュール実行時はパスワード認証でログインする
  • root ユーザでログインする
  • root ユーザの RSA鍵ファイルを作成(ssh-keygen)してあること
  • /dev/cdrom に CentOS メディアが接続されていること

手順

プレイブック作成

setup.root.authorized_keys.yml を作成する。

---

# 対象サーバの root ユーザの authorized_keys に /root/.ssh/id_rsa.pub の公開鍵を追加する。

- hosts: '{{ hosts }}'
  tasks:
    - name: setup authorized_keys
      authorized_key: user=root
                      key="{{ lookup('file', '/root/.ssh/id_rsa.pub') }}"
実行

garnet-vm11 に対して実行する。-k オプションをつけてパスワードを入力する。

# ansible-playbook setup.root.authorized_keys.yml -k --extra-vars="hosts=garnet-vm11"
SSH password: (password)

PLAY [garnet-vm11] ************************************************************ 

GATHERING FACTS *************************************************************** 
ok: [garnet-vm11]

TASK: [setup authorized_keys] ************************************************* 
failed: [garnet-vm11] => {"failed": true}
msg: Aborting, target uses selinux but python bindings (libselinux-python) aren't installed!

FATAL: all hosts have already failed -- aborting

PLAY RECAP ******************************************************************** 
           to retry, use: --limit @/root/setup.root.authorized_keys.retry

garnet-vm11                : ok=1    changed=0    unreachable=0    failed=1   

失敗。どうやら SELinux が有効な環境では libselinux-python パッケージが対象サーバ側に入っている必要があるようだ。

libselinux-python パッケージインストール

パッケージインストールには yum モジュールを使ってみる。yum がちゃんと動かないとお話しにならないのだが、ネット接続しないしばりで、あえて CentOS 6.4 のメディアからインストールする。

プレイブック install.libselinux-python.yml を作成する。

  • mount モジュールは fstab をいじるためのもののようなので使わず。
---

# /dev/cdrom にセットされている CentOS のメディアを使用して libselinux-python パッケージをインストールする。
#  ・/media/CentOS にマウントする。同ディレクトリを作成・削除する。
#  ・外部通信ができないとエラーになるためリポジトリ base,extras,updates は disablerepo 扱いにする。

- hosts: '{{ hosts }}'
  tasks:
    - name: mkdir /media/CentOS
      shell: "test -d /media/CentOS || mkdir -p -m 755 /media/CentOS"

    - name: mount CentOS media
      shell: "mountpoint /media/CentOS &>/null || mount /dev/cdrom /media/CentOS"

    - name: install libselinux-python
      yum: disablerepo=base,extras,updates enablerepo=c6-media name=libselinux-python state=present

    - name: umount CentOS media
      shell: "! mountpoint /media/CentOS || umount /media/CentOS"

    - name: rmdir /media/CentOS
      shell: "! test -d /media/CentOS || rmdir /media/CentOS"

実行!

# ansible-playbook install.libselinux-python.yml -k --extra-vars="hosts=garnet-vm11"
SSH password: 

PLAY [garnet-vm11] ************************************************************ 

GATHERING FACTS *************************************************************** 
ok: [garnet-vm11]

TASK: [mkdir /media/CentOS] *************************************************** 
changed: [garnet-vm11]

TASK: [mount CentOS media] **************************************************** 
changed: [garnet-vm11]

TASK: [install libselinux-python] ********************************************* 
changed: [garnet-vm11]

TASK: [umount CentOS media] *************************************************** 
changed: [garnet-vm11]

TASK: [rmdir /media/CentOS] *************************************************** 
changed: [garnet-vm11]

PLAY RECAP ******************************************************************** 
garnet-vm11                : ok=6    changed=5    unreachable=0    failed=0   
再実行

libselinux-python パッケージを入れた後に再実行して無事成功した。

# ansible-playbook setup.root.authorized_keys.yml -k --extra-vars="hosts=garnet-vm11"
SSH password: (password)

PLAY [garnet-vm11] ************************************************************ 

GATHERING FACTS *************************************************************** 
ok: [garnet-vm11]

TASK: [setup authorized_keys] ************************************************* 
changed: [garnet-vm11]

PLAY RECAP ******************************************************************** 
garnet-vm11                : ok=2    changed=1    unreachable=0    failed=0 

パスワードなしでログインできることを確認する。
前の接続から60秒以内だと接続が再利用されるので、/root/.ansible/cp の下のファイルがなくなってから実施する。

# ls -l /root/.ansible/cp
total 0

# ansible garnet-vm11 -m command -a uptime
garnet-vm11 | success | rc=0 >>
 03:22:55 up  5:31,  2 users,  load average: 0.00, 0.00, 0.00