• 31
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに

最近は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! と表示されます。

サーバ構成

今回構築するサーバ構成図を以下に示します。

ansible-lxc-server-diagram.png

Mac OS XまたはWindowsのVagrant上にCentOS6.6またはUbuntu14.04のLXCホストをセットアップし、その上で2つのLXCコンテナを作成します。管理する項目を減らしたいのでコンテナのIPアドレスはDHCPを使います。

そしてLXCホストからコンテナにnginxでプロキシして http://c1.192.168.33.2.xip.iohttp://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 するだけで全てのセットアップを自動で行えるのが利点です。

//github.com/hnakamur/lxc-ansible-playbooks/blob/master/Vagrantfile
# -*- 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で行っています。 yumapt-get ではなく pip を使ってインストールしていますので、Ansibleの最新版が使えます。

PYTHONUNBUFFERED環境変数

Ansibleをセットアップ後、以下のシェルスクリプトでLXCホストをセットアップしています。

//github.com/hnakamur/lxc-ansible-playbooks/blob/master/setup_lxc_host.sh
#!/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_varswith_first_found を組み合わせるのが便利です。

実際の例を以下に示します。

//github.com/hnakamur/lxc-ansible-playbooks/blob/539282879aa978552accd1b631b308b5c4243140/provisioning/lxc/tasks/main.yml
---
- 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-attachipコマンドを実行することでコンテナのIPアドレスを取得してnginxの設定ファイルに書くようにしていました。

CentOSでは /etc/dhcp/dhclient-eth0.confprepend 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-getlxc をインストールすれば iptables の設定も dnsmasq の設定も全てやってくれるので楽です。

CentOSでは epel から yumlxc をインストールは出来るのですが、 iptablesdnsmasq の設定は自前で行う必要があります。

この部分は以前VirtualBox上のCentOS 6.4でLXCをセットアップするAnsible playbook - Qiitaに書いた時から進歩しておらず、よくわからないままとりあえず使えているからまあいいかという状態になっています。

CentOSではLXCホストからコンテナ名でpingするとunknown hostになるのを解消したい

dignslookup は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.jpnameserver 10.0.2.3 は自宅で利用しているISPのDHCPで追加されたもので、 nameserver 10.0.3.1dnsmasq 用に追加した設定です。

[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のときも同じなのですが、こちらは dignslookupping も正常に使えます。

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では dnsmasq0.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も pingnslookup では tcpdump に同様の結果が出るのですが、 dig では何も出ませんでした。dig の初回実行時は見逃していたのかもしれませんが、その後はキャッシュされているのでしょうか。

おわりに

vagrant up だけでLXCホストとLXCコンテナの環境を構築できるAnsibleプレーブックを紹介しました。最近Dockerでの環境構築に時間を取られていて普段利用してないのですが、適材適所でLXCも活用していきたいと思います。

iptables、dnsmasq、pingの名前解決といったネットワーク周りのインフラ環境構築の基本的なところがよくわかってないので、モヤモヤした状態になっています。良い書籍とかあったら是非教えてください!