はじめに
最近はDockerの話題が多いですが、本番環境では使ってないという方も多いと思います。環境構築手順をDockerfileでコンパクトにまとめておけるのが便利な一方、コンテナではRailsアプリ開発用のDockerコンテナでunicornを起動するにはsupervisord + unicornherderが良い - Qiitaのように通常のLinuxサーバとは違う構成にする必要があるケースもあります。
その点LXCはコンテナ内でも通常のLinuxサーバとほぼ同じ感覚で環境構築できるのが魅力です。
この記事では第5回 コンテナ型仮想化の情報交換会@大阪 - コンテナ型仮想化の情報交換会で紹介したAnsibleプレーブックを少し発展させて、LXCホストからLXCコンテナへのnginxプロキシ設定もdnsmasqで名前解決するようにしたので、それも含めて紹介します。
プレーブックの使い方
プレーブックはhnakamur/lxc-ansible-playbooksにあります。Vagrantfile も含めてあるので vagrant up
するだけでセットアップが完了します。なお、実行にはMacBook Air Mid 2013で約11分と結構かかります。
実行が完了したら、ブラウザで
http://c1.192.168.33.2.xip.io/
にアクセスすると Welcome to nginx at container1!
と表示され、
http://c2.192.168.33.2.xip.io/
にアクセスすると Welcome to nginx at container2!
と表示されます。
サーバ構成
今回構築するサーバ構成図を以下に示します。
Mac OS XまたはWindowsのVagrant上にCentOS6.6またはUbuntu14.04のLXCホストをセットアップし、その上で2つのLXCコンテナを作成します。管理する項目を減らしたいのでコンテナのIPアドレスはDHCPを使います。
そしてLXCホストからコンテナにnginxでプロキシして http://c1.192.168.33.2.xip.io と http://c2.192.168.33.2.xip.io というURLでMac OS XまたはWindowsのマシンからアクセスできるようにします。
VagrantのVMに192.168.33.2というIPアドレスを設定しておいて、http://c1.192.168.33.2.xip.io というURLでアクセスすればxip.ioのDNSサービスによって192.168.33.2に名前解決されるので、リクエストがVagrantのVMに送られます。ちなみにxip.ioはchef-server - Chef Analytics(ChefServer12)のお試しはxip.io利用がおすすめ - Qiitaで知りました。ありがとうございます!
そこでnginxの設定でバーチャルホストがc1.192.168.33.2.xip.ioならコンテナ1、c2.192.168.33.2.xip.ioならコンテナ2にプロキシするというわけです。
Ansibleプレーブックの説明
VagrantのShellプロビジョナーでVagrant VM内にAnsibleをセットアップ
VagrantにはAnsibleプロビジョナが用意されています。また、Mac OS XにAnsibleをインストールしておいてそこからVagrant VMにつないでプレーブックを実行するという方法もあります。
ですが、今回はShellプロビジョナーを使ってVagrant VM内にAnsibleをセットアップして、VM上でAnsibleを実行するようにしてみました。
Ansibleは操作対象としてはWindowsマシンを扱えますが、Windowsマシンで起動することはできません。今回の方式であれば、Mac OS XでもWindowsでも vagrant up
するだけで全てのセットアップを自動で行えるのが利点です。
# -*- mode: ruby -*-
# vi: set ft=ruby :
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "hnakamur/centos6.6-x64"
#config.vm.box = "hnakamur/ubuntu-14.04-x64"
config.vm.network :private_network, ip: "192.168.33.2"
config.vm.boot_timeout = 120
config.vm.provider :virtualbox do |vb|
vb.customize ["modifyvm", :id, "--memory", "2048"]
end
config.vm.provision "shell", path: "install_ansible.sh"
config.vm.provision "shell", path: "setup_lxc_host.sh"
config.vm.provision "shell", path: "setup_containers.sh"
end
Ansibleのインストールはinstall_ansible.shで行っています。 yum
や apt-get
ではなく pip
を使ってインストールしていますので、Ansibleの最新版が使えます。
PYTHONUNBUFFERED環境変数
Ansibleをセットアップ後、以下のシェルスクリプトでLXCホストをセットアップしています。
#!/bin/sh
export PYTHONUNBUFFERED=true
cd /vagrant/provisioning
ansible-playbook lxc_host.yml
ここで export PYTHONUNBUFFERED=true
を設定しておくのがポイントです。これがないとプレーブック実行中のログが表示されないのですが、これを設定すると経過が見られるので快適です。
LXCコネクションプラグインを使用
Ansibleは操作対象のマシンにsshで接続するのが定番ですが、Connection Type Pluginという仕組みが用意されていて、違う方法で接続することも可能です。
今回はサードパーティのAnsible Connection Plugin for lxc containersを使用してみました。
実はAnsibleは標準でlibvirt経由でLXCに接続するプラグインが同梱されています。
ですが、[lxc-devel] LXD an "hypervisor" for containers (based on liblxc)でも言われているように、コンテナは仮想マシンとはいろいろ違うので、仮想マシンに見せかけてlibvirt経由で扱うのはあまりよいアイデアではないと思います。
Ansible Connection Plugin for lxc containersを使えばlibvirt無しで直接LXCコンテナに接続できます。このプラグインを使えばコンテナ内にsshサーバを立ててアカウントを用意する必要が無いので便利です。
ダイナミックインベントリ
Ansibleではインベントリファイルに操作対象のサーバの名前を書いておく必要があります。
今回はダイナミックインベントリの仕組みを使ってこのファイルのメンテナンスを不要にしました。inventory-lxc.pyでLXCホストと起動中のLXCコンテナのリストを返すようにしています。
CentOSとUbuntu両対応にするための仕組み
特定のOSファミリーだったり指定のバージョン以上の場合のみモジュールを実行する例がConditionalsに書かれています。
今回はRedHat用とDebian用のタスクファイルをそれぞれ作ってインクルードするようにしました。
OSファミリーやディストリビューションによって、定義する変数を変えたり値を変えたい場合はinclude_varsの例にあるように、 include_vars
と with_first_found
を組み合わせるのが便利です。
実際の例を以下に示します。
---
- include_vars: "{{ item }}"
with_first_found:
- "../vars/{{ ansible_distribution }}.yml"
- "../vars/{{ ansible_os_family }}.yml"
- "../vars/default.yml"
- include: Debian.yml
when: ansible_os_family == "Debian"
- include: RedHat.yml
when: ansible_os_family == "RedHat"
LXCホストからコンテナをホスト名で参照するようにした
ここが今回少し発展させた部分です。
発表スライドのコンテナのIPアドレス取得では、lxc-attach
でip
コマンドを実行することでコンテナのIPアドレスを取得してnginxの設定ファイルに書くようにしていました。
CentOSでは /etc/dhcp/dhclient-eth0.conf
に prepend domain-name-servers 10.0.3.1;
という行を入れることでLXCホスト上でコンテナ名の名前解決ができていたのですが、Enable dns for containers by hnakamur · Pull Request #4 · hnakamur/lxc-ansible-playbooksの変更でUbuntuでもLXCホスト上でコンテナ名の名前解決ができるようになりました。
nginxのプロキシでバックエンドのサーバをホスト名で指定する部分はNginxでproxy_passにホスト名を書いた時の名前解決のタイミング - (ひ)メモを真似させていただきました。ありがとうございます!今日試したところなので『なぞの「DNS error (32: Unknown error), query id:XXXXX」』現象には遭遇していないのですが、情報をお持ちの方はぜひ元記事にコメントしてください!
今後の課題
iptablesとdnsmasqの設定を理解して最適化したい
Ubuntuでは apt-get
で lxc
をインストールすれば iptables
の設定も dnsmasq
の設定も全てやってくれるので楽です。
CentOSでは epel
から yum
で lxc
をインストールは出来るのですが、 iptables
と dnsmasq
の設定は自前で行う必要があります。
この部分は以前VirtualBox上のCentOS 6.4でLXCをセットアップするAnsible playbook - Qiitaに書いた時から進歩しておらず、よくわからないままとりあえず使えているからまあいいかという状態になっています。
CentOSではLXCホストからコンテナ名でpingするとunknown hostになるのを解消したい
dig
や nslookup
はLXCホストで実行しても名前解決できるのですが、なぜか ping
では unknown host
になってしまいます。
[vagrant@localhost ~]$ dig container1
; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.30.rc1.el6 <<>> container1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 15885
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;container1. IN A
;; ANSWER SECTION:
container1. 0 IN A 10.0.3.149
;; Query time: 0 msec
;; SERVER: 10.0.3.1#53(10.0.3.1)
;; WHEN: Mon Dec 8 02:10:33 2014
;; MSG SIZE rcvd: 44
[vagrant@localhost ~]$ nslookup container1
Server: 10.0.3.1
Address: 10.0.3.1#53
Name: container1
Address: 10.0.3.149
[vagrant@localhost ~]$ ping container1
ping: unknown host container1
tcpdump
を動かしておいて ping container1
を試したときの出力はこんな感じでした。
[vagrant@localhost ~]$ sudo tcpdump -nn 'port 53'
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
01:55:59.668963 IP 10.0.2.15.6559 > 10.0.2.3.53: 46809+ A? container1.bai.ne.jp. (38)
01:55:59.787368 IP 10.0.2.3.53 > 10.0.2.15.6559: 46809 NXDomain* 0/0/0 (38)
試した時の /etc/resolv.conf
は以下の通りです。 search bai.ne.jp
と nameserver 10.0.2.3
は自宅で利用しているISPのDHCPで追加されたもので、 nameserver 10.0.3.1
が dnsmasq
用に追加した設定です。
[vagrant@localhost ~]$ cat /etc/resolv.conf
; generated by /sbin/dhclient-script
search bai.ne.jp
nameserver 10.0.3.1
nameserver 10.0.2.3
tcpdump
の結果を見ると通信が 10.0.2.3.53
に行っていて 10.0.3.1:53
じゃないのと container1.bai.ne.jp
というホスト名を名前解決しようとしているのが、気になるところです。
ただ、これはUbuntuのときも同じなのですが、こちらは dig
も nslookup
も ping
も正常に使えます。
vagrant@ubuntu-1404:~$ dig container1
; <<>> DiG 9.9.5-3-Ubuntu <<>> container1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 51152
;; flags: qr aa rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;container1. IN A
;; ANSWER SECTION:
container1. 0 IN A 10.0.3.189
;; Query time: 5 msec
;; SERVER: 10.0.3.1#53(10.0.3.1)
;; WHEN: Sun Dec 07 17:16:36 UTC 2014
;; MSG SIZE rcvd: 44
vagrant@ubuntu-1404:~$ nslookup container1
Server: 10.0.3.1
Address: 10.0.3.1#53
Name: container1
Address: 10.0.3.189
vagrant@ubuntu-1404:~$ ping container1
PING container1 (10.0.3.189) 56(84) bytes of data.
64 bytes from container1 (10.0.3.189): icmp_seq=1 ttl=64 time=0.039 ms
64 bytes from container1 (10.0.3.189): icmp_seq=2 ttl=64 time=0.073 ms
64 bytes from container1 (10.0.3.189): icmp_seq=3 ttl=64 time=0.067 ms
64 bytes from container1 (10.0.3.189): icmp_seq=4 ttl=64 time=0.108 ms
^C
--- container1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 2999ms
rtt min/avg/max/mdev = 0.039/0.071/0.108/0.026 ms
Ubuntuで ping
を実行した時のtcpdumpの結果と /etc/resolv.conf
の内容は以下の通りです。
vagrant@ubuntu-1404:~$ sudo tcpdump -nn 'port 53'
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
17:03:02.101345 IP 10.0.2.15.13842 > 10.0.2.3.53: 29346+ A? container1.bai.ne.jp. (38)
17:03:02.163127 IP 10.0.2.3.53 > 10.0.2.15.13842: 29346 NXDomain* 0/0/0 (38)
vagrant@ubuntu-1404:~$ cat /etc/resolv.conf
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 10.0.3.1
nameserver 10.0.2.3
search bai.ne.jp
こちらも 10.0.2.3:53
に通信が行っています。試しに tcpdump
の -i eth0
を -i eth1
や -i lxcbr0
にして ping container1
も実行してみたのですが、 tcpdump
では何も出力されませんでした。
CentOSでは dnsmasq
は 0.0.0.0
でリッスンするようにしたのですが、Ubuntuのほうは ps
で確認すると 10.0.3.1
でリッスンするようになっています。
vagrant@ubuntu-1404:~$ ps auxww | grep dnsmasq
lxc-dns+ 6557 0.0 0.0 28208 1072 ? S 12:52 0:00 dnsmasq -u lxc-dnsmasq --strict-order --bind-interfaces --pid-file=/run/lxc/dnsmasq.pid --conf-file= --listen-address 10.0.3.1 --dhcp-range 10.0.3.2,10.0.3.254 --dhcp-lease-max=253 --dhcp-no-override --except-interface=lo --interface=lxcbr0 --dhcp-leasefile=/var/lib/misc/dnsmasq.lxcbr0.leases --dhcp-authoritative
tcpdump
で見ると 10.0.2.3:53
に通信が行っているのに 10.0.3.1
でリッスンしている dnsmasq
で名前解決できているのが不思議です。どういう仕組みになってるんでしょう。
なお、CentOSもUbuntuも ping
と nslookup
では tcpdump
に同様の結果が出るのですが、 dig
では何も出ませんでした。dig
の初回実行時は見逃していたのかもしれませんが、その後はキャッシュされているのでしょうか。
おわりに
vagrant up
だけでLXCホストとLXCコンテナの環境を構築できるAnsibleプレーブックを紹介しました。最近Dockerでの環境構築に時間を取られていて普段利用してないのですが、適材適所でLXCも活用していきたいと思います。
iptables、dnsmasq、pingの名前解決といったネットワーク周りのインフラ環境構築の基本的なところがよくわかってないので、モヤモヤした状態になっています。良い書籍とかあったら是非教えてください!