CircleCI+ dockerでサーバCIの検証したときにiptablesでハマった
概要
CircleCIでサーバCIをしようと調査していたときに、ローカル環境とCircleCI環境で違う事象が起きたのでまとめた。
CircleCI上のコンテナではiptablesの停止するレシピを流した際に特にエラーにならないが、ローカル上のコンテナに対し同じレシピを流すとエラーが発生しテストが進まない
前提
- ローカル環境:OSX10.10 boot2docker v1.4.1
- リモート:CircleCI
- 起動するdockerには同じDockerfileを使っている。
- chef/serverspecでCIしている
切り分け
- boot2docker上でcookした際にiptablesのレシピでエラー発生。
/sbin/service iptables stop
でエラーになっている。
[docker@8d3a8827187b ~]$ sudo /sbin/service iptables stop iptables: Setting chains to policy ACCEPT: nat filter [ OK ] iptables: Flushing firewall rules: [ OK ] iptables: Unloading modules: iptable_nat [FAILED]
- CircleCI上で同じレシピを実行しても問題は起きなかった。
調査ログ
カーネルの情報を確認
- boot2docker上のカーネルモジュールを確認
docker@boot2docker:~$ lsmod Module Size Used by Tainted: G veth 12288 0 xt_nat 12288 2 xt_addrtype 12288 2 xt_conntrack 12288 1 ipt_MASQUERADE 12288 1 iptable_nat 12288 1 nf_conntrack_ipv4 16384 2 nf_defrag_ipv4 12288 1 nf_conntrack_ipv4 nf_nat_ipv4 12288 1 iptable_nat nf_nat 16384 4 xt_nat,ipt_MASQUERADE,iptable_nat,nf_nat_ipv4 nf_conntrack 57344 6 xt_conntrack,ipt_MASQUERADE,iptable_nat,nf_conntrack_ipv4,nf_nat_ipv4,nf_nat bridge 61440 0 stp 12288 1 bridge llc 12288 2 bridge,stp ipv6 229376 69 bridge,[permanent] vboxsf 28672 1 cpufreq_userspace 12288 0 cpufreq_stats 12288 0 cpufreq_powersave 12288 0 cpufreq_conservative 12288 0 squashfs 28672 0 loop 20480 1 ppdev 12288 0 parport_pc 24576 0 parport 28672 2 ppdev,parport_pc intel_agp 12288 0 battery 12288 0 intel_gtt 16384 1 intel_agp i2c_piix4 12288 0 agpgart 28672 2 intel_agp,intel_gtt pcspkr 12288 0 ac 12288 0 vboxguest 139264 1 vboxsf
- CircleCI上でも確認
ubuntu@box81:~$ lsmod Module Size Used by xt_addrtype 12713 2 xt_nat 12726 20 veth 13331 0 xt_CHECKSUM 12549 11 iptable_mangle 12734 11 ipt_MASQUERADE 12880 12 iptable_nat 13151 11 nf_nat_ipv4 13316 1 iptable_nat nf_nat 26190 4 xt_nat,ipt_MASQUERADE,iptable_nat,nf_nat_ipv4 bridge 116375 0 stp 12976 1 bridge llc 14441 2 bridge,stp dm_crypt 23456 0 ip6table_filter 12815 0 ip6_tables 27503 1 ip6table_filter xt_tcpudp 12924 97 nf_conntrack_ipv4 14857 16 nf_defrag_ipv4 12758 1 nf_conntrack_ipv4 xt_conntrack 12760 15 nf_conntrack 97139 6 ipt_MASQUERADE,iptable_nat,nf_nat_ipv4,nf_nat,nf_conntrack_ipv4,xt_conntrack iptable_filter 12810 11 ip_tables 27717 3 iptable_mangle,iptable_nat,iptable_filter x_tables 34194 11 xt_addrtype,xt_nat,xt_CHECKSUM,iptable_mangle,ipt_MASQUERADE,ip6table_filter,ip6_tables,xt_tcpudp,xt_conntrack,iptable_filter,ip_tables ppdev 17711 0 i2c_piix4 22299 0 serio_raw 13462 0 parport_pc 32866 0 xen_fbfront 17552 0 parport 42481 2 ppdev,parport_pc fb_sys_fops 12703 1 xen_fbfront mac_hid 13253 0 isofs 40287 0 btrfs 908260 1 raid10 48666 0 raid456 92012 0 async_raid6_recov 17389 1 raid456 async_memcpy 12689 2 raid456,async_raid6_recov async_pq 13456 2 raid456,async_raid6_recov async_xor 13233 3 raid456,async_raid6_recov,async_pq async_tx 13509 5 raid456,async_raid6_recov,async_memcpy,async_pq,async_xor xor 21411 2 btrfs,async_xor raid6_pq 97812 3 btrfs,async_raid6_recov,async_pq raid1 40017 0 raid0 17969 0 multipath 13145 0 linear 12894 0 crct10dif_pclmul 14250 0 crc32_pclmul 13160 0 cirrus 25194 1 ghash_clmulni_intel 13216 0 aesni_intel 152634 0 syscopyarea 12633 2 xen_fbfront,cirrus sysfillrect 12901 2 xen_fbfront,cirrus sysimgblt 12806 2 xen_fbfront,cirrus aes_x86_64 17131 1 aesni_intel lrw 13323 1 aesni_intel ttm 90162 1 cirrus gf128mul 14951 1 lrw glue_helper 14095 1 aesni_intel drm_kms_helper 53224 1 cirrus ablk_helper 13597 1 aesni_intel cryptd 20531 3 ghash_clmulni_intel,aesni_intel,ablk_helper drm 308197 3 cirrus,ttm,drm_kms_helper psmouse 113121 0 pata_acpi 13038 0 floppy 74335 0
- エラーメッセージからboot2docker側ではカーネルモジュールのアンインストールに失敗している。スクリプトを見ると確かにモジュールのアンインストールをしている。(※rmmod_rが関数になっており、その中で
modprobe -r
している。)しかし、docker側からカーネルの変更はできないのでエラーになっている。
$ cat /etc/init.d/iptables stop(){ (中略) if [ "x$IPTABLES_MODULES_UNLOAD" = "xyes" ]; then echo -n $"${IPTABLES}: Unloading modules: " ret=0 for mod in ${NF_MODULES[*]}; do rmmod_r $mod let ret+=$?; done (中略)
- CircleCI側で
/sbin/service iptables start
を実行しても何も出力されず、返り値としては"7"が返却されている。そこでstatusコマンドを実行すると次のメッセージが出た。
$ sudo /sbin/service iptables status
iptables: Firewall is not running.
- これはスクリプトを見ると、
$VAR_SUBSYS_IPTABLES
にファイルがないこと、かつ$NF_TABLES
が空である場合は起動していないと見做しているようだった。
status() { if [ ! -f "$VAR_SUBSYS_IPTABLES" -a -z "$NF_TABLES" ]; then echo $"${IPTABLES}: Firewall is not running." return 3 fi
このメッセージからCircleCI上コンテナのiptablesはそもそも起動していない可能性と予想。
- start()のスクリプトを読むと、はじめに$IPTABLES_DATAの存在チェックをしていて、これは
/etc/sysconfig/iptables
を指しており、どちらのコンテナ上にも存在しない。なので、コンテナ起動時のiptablesの状態に差異がありそう。
[docker@778027d23fff ~]$ sudo service iptables stop iptables: Unloading modules: iptable_mangle iptable_nat iptable_filter iptable_mangle iptable_nat iptable_filter ip_tables [FAILED]
- statusの実行ログを比較すると一部変数の設定が違うことがわかった。先ほどのstatus()の$NF_TABLESを確認するとboot2docker上のコンテナでは'nat filter'という変数があるが、CircleCI上では何も設定されていなかった。
# Get active tables NF_TABLES=$(cat "$PROC_IPTABLES_NAMES" 2>/dev/null)
[ "$IPV" = "ip" ] && _IPV="ipv4" || _IPV="ipv6" PROC_IPTABLES_NAMES=/proc/net/${IPV}_tables_names
boot2docker側を見ると、
[docker@cec732bdd885 ~]$ sudo cat /proc/net/ip_tables_names nat filter
となっており、値が存在している。CircleCI側のコンテナをで同じコマンドを実行してみたところPermissionエラーになった。sudo
つけても確認できなかったので権限絞られてるのかなーという感じ。scriptないではパーミッションエラー時のエラー処理がされておらず結果的に値が入らず、たまたま成功しているようだった。
設定を変更してみる
- boot2dockerのコンテナにログインし、
/etc/sysconfig/iptables-config
を確認
$ sudo vi /etc/sysconfig/iptables-config ~~ # Unload modules on restart and stop # Value: yes|no, default: yes # This option has to be 'yes' to get to a sane state for a firewall # restart or stop. Only set to 'no' if there are problems unloading netfilter # modules. IPTABLES_MODULES_UNLOAD="yes"
[docker@ci-maker ~]$ sudo /sbin/service iptables stop iptables: Setting chains to policy ACCEPT: nat filter [ OK ] iptables: Flushing firewall rules: [ OK ]
とりあえず、エラーは吐かなくなったがstatusを見ると設定が残っているようで、serverspecで失敗してしまう。
まとめ
dockerに使うコンテナのカーネルモジュールを頑張れば、CircleCI上と同等の状態にできるのかな。それは違う気もするので、dockerを使う際にはiptablesのテストは考えないという結論に落ち着きそう。