こんにちわ。
寒気きびしき折柄、皆様いかがお過ごしですか。
ブログ書くのも1年ぶりなんですが、開発合宿でHaconiwa + flannelを試してみたので、メモのように書き残しておこうと思います。  
今回使ったHaconiwaですが、udzuraさんというスーパーエンジニアの方が作ったmruby製コンテナエンジンで、ロリポップマネージドクラウドというサーバーサービスでも利用されています。
flannelは、LinuxのVXLAN機能を用いてオーバーレイネットワークを実現するツールで、Kubernetesと組み合わせて利用されています。

今回この2つを組み合わせて実現するのはこのようなネットワークです。
検証環境
  • ゲストOS: Ubuntu 16.04.5 LTS
  • VirtualBox 5.1.18
  • Vagrant 2.1.5
まずは、vagrantを使って検証するためのVMを作成していきます。
Vagrantfileは、こんな感じで行きます。
# coding: utf-8
# -*- mode: ruby -*-
#

Vagrant.configure(2) do |config|
  (1..2).each do |i|
    vm_name = "node#{i}"

    config.vm.define vm_name do |s|
      s.vm.box = "ubuntu/xenial64"
      s.vm.hostname = vm_name

      private_ip = "172.16.10.#{i+10}"
      s.vm.network "private_network", ip: private_ip

      s.vm.provider "virtualbox" do |v|
        v.gui = false
        v.cpus = 2
        v.memory = 1024
      end
    end
  end
end

Vagrantfileの作成が終わったら、vagrant upvagrant statusで起動していることを確認します。
$ vagrant up
$ vagrant status
Current machine states:

node1                     running (virtualbox)
node2                     running (virtualbox)

起動を確認したら、vagrant ssh をしてnode1,2で設定していきます。
hostsを編集しておきます。
# vim /etc/hosts
127.0.0.1       localhost
172.16.10.11    node1
172.16.10.12    node2
127.0.1.1       ubuntu-xenial   ubuntu-xenial

etcdクラスター構築

flannelは、etcdというkvsを使用してサブネットなどのネットワーク情報を共有するため、最初にetcdを入れていきます。
apt install -y etcd

node-1,2 の /etc/default/etcd ファイルを以下のようにします。

node1

ETCD_NAME="node1"
ETCD_LISTEN_PEER_URLS="http://172.16.10.11:2380,http://172.16.10.11:7001"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://172.16.10.11:2380"
ETCD_INITIAL_CLUSTER="node1=http://172.16.10.11:2380,node2=http://172.16.10.12:2380"
ETCD_INITIAL_CLUSTER_STATE-"new"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_ADVERTISE_CLIENT_URLS="http://172.16.10.11:2379"

node2

ETCD_NAME="node2"
ETCD_LISTEN_PEER_URLS="http://172.16.10.12:2380,http://172.16.10.12:7001"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://172.16.10.12:2380"
ETCD_INITIAL_CLUSTER="node1=http://172.16.10.11:2380,node2=http://172.16.10.12:2380"
ETCD_INITIAL_CLUSTER_STATE-"new"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_ADVERTISE_CLIENT_URLS="http://172.16.10.12:2379"

設定が終わったら、etcdを再起動します
# systemctl restart etcd

member listで確認し、node1,2があれば完了です。
# etcdctl member list
e81c3c06663b0f99: name=node1 peerURLs=http://172.16.10.11:2380 clientURLs=http://172.16.10.11:2379
eb58b9faeb1ee792: name=node2 peerURLs=http://172.16.10.12:2380 clientURLs=http://172.16.10.12:2379

上手くいかない場合は、/var/lib/etcd/default/* を一度削除し、再度etcdの再起動をお試しください。
etcdクラスターの構築は、こちらの記事を参考にさせていただきました。

flannel

ここからflannelの構築を行っていきます。
etcdにflannel用のサブネットを登録します。
root@node1:~# etcdctl set /coreos.com/network/config '{ "Network": "10.254.0.0/16", "Backend": {"Type": "vxlan"}}'
{ "Network": "10.254.0.0/16", "Backend": {"Type": "vxlan"}}

node-2からetcdに登録されていることを確認してみます。
root@node2:~# etcdctl get /coreos.com/network/config
{ "Network": "10.254.0.0/16", "Backend": {"Type": "vxlan"}}

flannelと必要なパッケージ類をインストールします。
今回flannelは、v0.10.0 を使用します。
node-1,2 で作業を行います。
# apt-get update
# apt-get install -y linux-libc-dev golang gcc
# wget https://github.com/coreos/flannel/releases/download/v0.10.0/flanneld-amd64
# mv flanneld-amd64 /usr/local/bin/flanneld
# chmod 755 /usr/local/bin/flanneld

/etc/systemd/system/flanneld.service というファイルを作成し、flannelをsystemdに登録します。
[Unit]
Description=Flannel daemon
Requires=network-online.target
After=network-online.target

[Service]
Restart=on-failure
ExecStart=/usr/local/bin/flanneld -iface enp0s8

[Install]
WantedBy=multi-user.target

flannelを起動させます。
# systemctl start flanneld.service
# systemctl enable flanneld.service

flannelが起動したことを確認します。
ip a コマンドを打つとflannelのIFができているのを確認できます。
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
    link/ether da:28:b8:4a:88:03 brd ff:ff:ff:ff:ff:ff
    inet 10.254.68.0/32 scope global flannel.1
       valid_lft forever preferred_lft forever
    inet6 fe80::d828:b8ff:fe4a:8803/64 scope link
       valid_lft forever preferred_lft forever

また、 etcdctl ls /coreos.com/network/subnets/ でサブネットの確認も可能です。
# etcdctl ls /coreos.com/network/subnets/
/coreos.com/network/subnets/10.254.68.0-24
/coreos.com/network/subnets/10.254.96.0-24
これで、flannelの準備は完了です。

Haconiwa

ここから、Haconiwaを使用してコンテナを作成していきます。
Haconiwaの使い方は、udzuraさんのこちらの記事を参考にさせていただきました。
Haconiwaのバージョンは、0.9.5 を使用します。
まずは、Haconiwaをインストールします。
node-1,2 で作業を行います。
# wget --content-disposition https://packagecloud.io/udzura/haconiwa/packages/ubuntu/xenial/haconiwa_0.9.5-1_amd64.deb/download.deb
# apt install -y ./haconiwa_0.9.5-1_amd64.deb

今回、bootstrapを使用するため、先にインストールしておきます。
# apt-get install -y debootstrap ssh

Haconiwaが使えるようなったので、hacofileという設定ファイルを作成していきます。
# haconiwa new flannel.haco
assign  new haconiwa name = haconiwa-052a9cc3
assign  rootfs location = /var/lib/haconiwa/052a9cc3
create  flannel.haco

作られた flannel.haco は文法としては普通のRubyのスクリプトです。
bootstrap/provisionのブロックを以下のように変更します。
  config.bootstrap do |b|
    b.strategy = "debootstrap"
    b.variant = "minbase"
    b.debian_release = "stretch"
  end

  config.provision do |p|
    p.run_shell <<-SHELL
apt -y update
apt -y install procps iproute2 ruby iputils-ping net-tools
    SHELL
  end

最後のendの前の行に以下を追加します。
container_ip については、flannelのセグメントと同じにしてください。
  config.cgroup["cpu.cfs_period_us"] = 100000
  config.cgroup["cpu.cfs_quota_us"]  =  30000
  config.cgroup["pids.max"] = 128

  config.network.container_ip = "10.254.68.2"
  config.network.namespace = config.name
  config.network.bridge_name = 'haconiwa0'
  config.network.veth_host = veth = "veth#{::SHA1.sha1_hex(config.name)[0, 4]}"
  config.network.veth_guest = 'eth0'

  config.capabilities.allow 'cap_sys_chroot'
  config.capabilities.allow 'cap_net_bind_service'

設定ファイルの編集が終わったら、Haconiwa用のブリッジを作成します。
# haconiwa init --bridge --bridge-ip=10.254.68.1/24

haconiwa create でDSLに沿ってコンテナのrootfsの作成とプロビジョニングを行います。
# haconiwa create flannel.haco

ここでコンテナ用のforwarding設定を行います。
# /sbin/sysctl -w net.ipv4.ip_forward=1
# iptables -t nat -A POSTROUTING -s 10.254.0.0/16 -j MASQUERADE

では、最後にコンテナを起動してみましょう。
$ haconiwa run flannel.haco -T -- /bin/bash
Create lock: #<Lockfile path=/var/lock/.haconiwa-052a9cc3.hacolock>
Container fork success and going to wait: pid=11130
root@haconiwa-052a9cc3:/#

動作確認

最後にICMPレベルでの動作確認をしてみます。
まずは、コンテナ内から外部ともう一台のコンテナに対してpingを打ってみて、そのパケットをtcpdumpで確認してみます。
root@haconiwa-052a9cc3:/# ping 8.8.8.8 -c 3
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=61 time=17.5 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=61 time=17.3 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=61 time=17.2 ms

root@node1:/home/ubuntu# tcpdump -tt -n -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on haconiwa0, link-type EN10MB (Ethernet), capture size 262144 bytes
1544288778.536551 IP 10.254.68.2 > 8.8.8.8: ICMP echo request, id 33, seq 1, length 64
1544288778.554063 IP 8.8.8.8 > 10.254.68.2: ICMP echo reply, id 33, seq 1, length 64
1544288779.539363 IP 10.254.68.2 > 8.8.8.8: ICMP echo request, id 33, seq 2, length 64
1544288779.556688 IP 8.8.8.8 > 10.254.68.2: ICMP echo reply, id 33, seq 2, length 64
1544288780.541221 IP 10.254.68.2 > 8.8.8.8: ICMP echo request, id 33, seq 3, length 64
1544288780.558376 IP 8.8.8.8 > 10.254.68.2: ICMP echo reply, id 33, seq 3, length 64

root@haconiwa-052a9cc3:/# ping 10.254.96.2 -c 3
PING 10.254.96.2 (10.254.96.2) 56(84) bytes of data.
64 bytes from 10.254.96.2: icmp_seq=1 ttl=62 time=0.871 ms
64 bytes from 10.254.96.2: icmp_seq=2 ttl=62 time=1.00 ms
64 bytes from 10.254.96.2: icmp_seq=3 ttl=62 time=0.849 ms

root@node1:/home/ubuntu# tcpdump -tt -n -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on haconiwa0, link-type EN10MB (Ethernet), capture size 262144 bytes
1544289287.677196 IP 10.254.68.2 > 10.254.96.2: ICMP echo request, id 34, seq 1, length 64
1544289287.678028 IP 10.254.96.2 > 10.254.68.2: ICMP echo reply, id 34, seq 1, length 64
1544289288.676404 IP 10.254.68.2 > 10.254.96.2: ICMP echo request, id 34, seq 2, length 64
1544289288.677360 IP 10.254.96.2 > 10.254.68.2: ICMP echo reply, id 34, seq 2, length 64
1544289289.677979 IP 10.254.68.2 > 10.254.96.2: ICMP echo request, id 34, seq 3, length 64
1544289289.678692 IP 10.254.96.2 > 10.254.68.2: ICMP echo reply, id 34, seq 3, length 64

この通り問題なくパケット通信ができていることが確認できます。
次に、VXLAN(OTV)のポートである8472のUDPで確認してみます。
UDPで確認するのは、VXLANがL2パケットをL3パケット(UDPパケット)にカプセル化して通信するプロトコルだからです。
root@haconiwa-052a9cc3:/# ping 8.8.8.8 -c 3
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=61 time=18.0 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=61 time=17.2 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=61 time=19.7 ms

root@node1:/home/ubuntu# tcpdump -i enp0s8 udp port 8472
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp0s8, link-type EN10MB (Ethernet), capture size 262144 bytes

root@haconiwa-052a9cc3:/# ping 10.254.96.2 -c 3
PING 10.254.96.2 (10.254.96.2) 56(84) bytes of data.
64 bytes from 10.254.96.2: icmp_seq=1 ttl=62 time=0.721 ms
64 bytes from 10.254.96.2: icmp_seq=2 ttl=62 time=0.903 ms
64 bytes from 10.254.96.2: icmp_seq=3 ttl=62 time=0.416 ms

root@node1:/home/ubuntu# tcpdump -i enp0s8 udp port 8472
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp0s8, link-type EN10MB (Ethernet), capture size 262144 bytes
17:26:38.048623 IP node1.40725 > node2.8472: OTV, flags [I] (0x08), overlay 0, instance 1
IP 10.254.68.0 > 10.254.96.2: ICMP echo request, id 39, seq 1, length 64
17:26:38.049108 IP node2.42908 > node1.8472: OTV, flags [I] (0x08), overlay 0, instance 1
IP 10.254.96.2 > 10.254.68.0: ICMP echo reply, id 39, seq 1, length 64
17:26:39.050556 IP node1.40725 > node2.8472: OTV, flags [I] (0x08), overlay 0, instance 1
IP 10.254.68.0 > 10.254.96.2: ICMP echo request, id 39, seq 2, length 64
17:26:39.051274 IP node2.42908 > node1.8472: OTV, flags [I] (0x08), overlay 0, instance 1
IP 10.254.96.2 > 10.254.68.0: ICMP echo reply, id 39, seq 2, length 64
17:26:40.051365 IP node1.40725 > node2.8472: OTV, flags [I] (0x08), overlay 0, instance 1
IP 10.254.68.0 > 10.254.96.2: ICMP echo request, id 39, seq 3, length 64
17:26:40.051637 IP node2.42908 > node1.8472: OTV, flags [I] (0x08), overlay 0, instance 1
IP 10.254.96.2 > 10.254.68.0: ICMP echo reply, id 39, seq 3, length 64

最後に

今回は、Haconiwaというコンテナとflannelを使ってオーバーレイネットワークを構築し通信が出来ることを確認するところまでやってみました。
今後できれば、flannelを使ったオーバーレイネットワークの構築とHaconiwaの起動を自動化しコンテナ同士の専用ネットワーク環境を提供できるようにしてみたいと思っています。
ではでは。