はじめに
kubespray
を使うと簡単に Kubernetes のクラスタを構築できます。
AWS や GCE にも対応しています。
本家サイトはこちら https://docs.kubespray.io/
ここでは vSphere ESXi で用意した CentOS 7.2 の VM 3台の Kubernetes クラスタを構築します。
この時点での kubespray
のバージョンは 0.4.5
でした。
ホスト名 | IPアドレス | 別名 | 役割 |
---|---|---|---|
kube01.localdomain | 192.168.100.1 | node1 | Master + Node + Etcd |
kube02.localdomain | 192.168.100.2 | node2 | Master + Node + Etcd |
kube03.localdomain | 192.168.100.3 | node3 | Node + Etcd |
kargo.localdomain | 192.168.100.10 | N/A | kargo 実行ホスト |
テスト用なのでそれぞれ 1 vCPU, 1GB メモリ しか割り当てていません。
準備
kargo
を実行するホストに必要なものをインストールします。
こちらも CentOS 7.2 です。
# yum install -y ansible git gcc python-pip python-devel libffi-devel openssl-devel
# pip install kargo
# kargo -v
kargo 0.4.5
鍵ペアを生成し、VM に公開鍵を配る
# ssh-keygen -b 2048 -N ""
# ssh-copy-id kube01.localdomain
# ssh-copy-id kube02.localdomain
# ssh-copy-id kube03.localdomain
セットアップ
まずは inventory を作るため、prepare コマンドを実行します。
ここでは 3 VM で以下の図の構成になります。
- M ... Master
- N ... Node
- E ... etcd
# kargo prepare --nodes \
node1[ansible_ssh_host=kube01.localdomain] \
node2[ansible_ssh_host=kube02.localdomain] \
node3[ansible_ssh_host=kube03.localdomain]
CLONING KARGO GIT REPO *********************************************************
Cloning into '/root/.kargo'...
kargo repo cloned
WRITTING INVENTORY *************************************************************
Inventory generated : /root/.kargo/inventory/inventory.cfg
[kube-master]
node1
node2
[all]
node1 ansible_ssh_host=kube01.localdomain
node2 ansible_ssh_host=kube02.localdomain
node3 ansible_ssh_host=kube03.localdomain
[k8s-cluster:children]
kube-node
kube-master
[kube-node]
node1
node2
node3
[etcd]
node1
node2
node3
Versions を見ると Kubernetes は 1.3.0
との記載がありますが、
この後に API Server の認可のテストをやる予定なので、Kubernetes のバージョンを 1.3.4
に変更してセットアップします。
playbook の中は読まずに grep であたりをつけて書き換えます。
(勝手に変更しているので、動作保証はありません)
kube_version: v1.3.4
kube_version: v1.3.4
prepare で inventory が作成されたので、deploy します。
ついでに time
コマンドで時間を測ってみます。
# time kargo deploy --verbose -u root -k .ssh/id_rsa -n flannel
Identity added: /root/.ssh/id_rsa (/root/.ssh/id_rsa)
CHECKING SSH CONNECTIONS *******************************************************
node1 | SUCCESS => {
"changed": false,
"ping": "pong"
}
node2 | SUCCESS => {
"changed": false,
"ping": "pong"
}
node3 | SUCCESS => {
"changed": false,
"ping": "pong"
}
All hosts are reachable
/usr/bin/ansible-playbook --ssh-extra-args -o StrictHostKeyChecking=no -u root -b --become-user=root -i /root/.kargo/inventory/inventory.cfg /root/.kargo/cluster.yml -e kube_network_plugin=flannel -vvvv
Run kubernetes cluster deployment with the above command ? [Y/n] y
(省略)
PLAY RECAP *********************************************************************
node1 : ok=406 changed=85 unreachable=0 failed=0
node2 : ok=384 changed=75 unreachable=0 failed=0
node3 : ok=276 changed=66 unreachable=0 failed=0
Kubernetes deployed successfuly
real 16m0.400s
user 4m36.447s
sys 1m33.895s
成功しました。16分ほどかかりました。
(実は最初 network plugin を calico
にして試したのですが、妙な問題が起きたので変更しました)
クラスタの確認
バージョンが 1.3.4
になっているか確認します。
# ssh kube01.localdomain kubectl version
Client Version: version.Info{Major:"1", Minor:"3", GitVersion:"v1.3.4+coreos.0", GitCommit:"be9bf3e842a90537e48361aded2872e389e902e7", GitTreeState:"clean", BuildDate:"2016-08-02T00:54:53Z", GoVersion:"go1.6.2", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"3", GitVersion:"v1.3.4+coreos.0", GitCommit:"be9bf3e842a90537e48361aded2872e389e902e7", GitTreeState:"clean", BuildDate:"2016-08-02T00:54:53Z", GoVersion:"go1.6.2", Compiler:"gc", Platform:"linux/amd64"}
バージョンは良さそうです。どんな環境になったのか見てみます。
以降 node1
に相当する kube01
で作業します。
# kubectl get node
NAME STATUS AGE
node1 Ready 3m
node2 Ready 3m
node3 Ready 3m
3台とも Node として認識されています。
# etcdctl cluster-health
member 54d50575943734fe is healthy: got healthy result from http://192.168.100.1:2379
member a1b38d7fbca44dce is healthy: got healthy result from http://192.168.100.2:2379
member b11d7f87d0d5249d is healthy: got healthy result from http://192.168.100.3:2379
cluster is healthy
etcd は 3台でクラスタになっています。
認証なしで外部から etcd にアクセス可能な状態になっているようなので、本番運用する場合は不足がありますが、テストで使う分には良さそうです。
# kubectl cluster-info
Kubernetes master is running at http://localhost:8080
dnsmasq is running at http://localhost:8080/api/v1/proxy/namespaces/kube-system/services/dnsmasq
kubedns is running at http://localhost:8080/api/v1/proxy/namespaces/kube-system/services/kubedns
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
# kubectl get svc --all-namespaces
NAMESPACE NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default kubernetes 10.233.0.1 <none> 443/TCP 4m
kube-system dnsmasq 10.233.0.2 <none> 53/TCP,53/UDP 3m
kube-system kubedns 10.233.0.3 <none> 53/UDP,53/TCP 2m
クラスタサービスとして API Server 以外にも dnsmasq, kubedns が動いています。
# kubectl get pod --all-namespaces -o wide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE
kube-system dnsmasq-1wt2x 1/1 Running 0 4m 10.233.71.2 node3
kube-system dnsmasq-fazxk 1/1 Running 0 4m 10.233.96.2 node1
kube-system dnsmasq-myvjm 1/1 Running 0 4m 10.233.74.2 node2
kube-system flannel-node1 2/2 Running 2 4m 192.168.100.1 node1
kube-system flannel-node2 2/2 Running 2 4m 192.168.100.2 node2
kube-system flannel-node3 2/2 Running 2 4m 192.168.100.3 node3
kube-system kube-apiserver-node1 1/1 Running 0 4m 192.168.100.1 node1
kube-system kube-apiserver-node2 1/1 Running 0 4m 192.168.100.2 node2
kube-system kube-controller-manager-node1 1/1 Running 0 4m 192.168.100.1 node1
kube-system kube-controller-manager-node2 1/1 Running 0 4m 192.168.100.2 node2
kube-system kube-proxy-node1 1/1 Running 1 4m 192.168.100.1 node1
kube-system kube-proxy-node2 1/1 Running 1 4m 192.168.100.2 node2
kube-system kube-proxy-node3 1/1 Running 1 3m 192.168.100.3 node3
kube-system kube-scheduler-node1 1/1 Running 0 4m 192.168.100.1 node1
kube-system kube-scheduler-node2 1/1 Running 0 4m 192.168.100.2 node2
kube-system kubedns-6mhhn 4/4 Running 0 2m 10.233.71.3 node3
Kubernetes のコンポーネントも Pod としてデプロイされているのが分かります。
node3
は API Server などが動いておらず Master
の機能を持っていません。
API Server について、もう少し詳しく見てみます。
# ps -eo args | grep hyperkube\ apiserve[r] | sed "s/ /\n/g"
/hyperkube
apiserver
--advertise-address=192.168.100.1
--etcd-servers=http://127.0.0.1:2379
--insecure-bind-address=127.0.0.1
--apiserver-count=2
--admission-control=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
--service-cluster-ip-range=10.233.0.0/18
--client-ca-file=/etc/kubernetes/ssl/ca.pem
--basic-auth-file=/etc/kubernetes/users/known_users.csv
--tls-cert-file=/etc/kubernetes/ssl/apiserver.pem
--tls-private-key-file=/etc/kubernetes/ssl/apiserver-key.pem
--token-auth-file=/etc/kubernetes/tokens/known_tokens.csv
--service-account-key-file=/etc/kubernetes/ssl/apiserver-key.pem
--secure-port=443
--insecure-port=8080
--v=2
--allow-privileged=true
2>&1
>>
/var/log/kubernetes/kube-apiserver.log
# cat /etc/kubernetes/users/known_users.csv
changeme,kube,admin
changeme,root,admin
# cat /etc/kubernetes/tokens/known_tokens.csv
lrlochu6hHuzadjegzU4QBaqqxXaswIa,system:kubectl-node1,system:kubectl-node1
4Y9RHeYN6ouIRiFfjucDE6wmDgIJ5Dcn,system:kubectl-node2,system:kubectl-node2
F3FA1y5WKj0tLI7kiw8TJxNe96uMI2tw,system:kubelet-node1,system:kubelet-node1
Ftj02y8Nld2smdiEPEFzTHqCOZ6pUDJO,system:kubelet-node2,system:kubelet-node2
f44IGsNvLVzu1dL7OM2rBfTuGpL8iZjc,system:kubelet-node3,system:kubelet-node3
known_users.csv
は Basic 認証で使われるものですが、本番運用する場合はこれを変更する必要があるでしょう。
token は .kargo/roles/kubernetes/secrets/files/kube-gen-token.sh
で /dev/urandom を使って生成されているようです。
続いて DNS の機能も見てみます。
# kubectl --namespace=kube-system get pod -l k8s-app=dnsmasq
NAME READY STATUS RESTARTS AGE
dnsmasq-1wt2x 1/1 Running 0 5m
dnsmasq-fazxk 1/1 Running 0 5m
dnsmasq-myvjm 1/1 Running 0 5m
# kubectl --namespace=kube-system exec -ti dnsmasq-1wt2x /bin/sh
# cat /etc/dnsmasq.d/01-kube-dns.conf
#Listen on localhost
bind-interfaces
listen-address=0.0.0.0
addn-hosts=/etc/hosts
bogus-priv
#Set upstream dns servers
server=8.8.8.8
server=8.8.4.4
# Forward k8s domain to kube-dns
server=/cluster.local/10.233.0.3
# exit
# kubectl --namespace=kube-system get svc -l k8s-app=dnsmasq
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
dnsmasq 10.233.0.2 <none> 53/TCP,53/UDP 6m
# kubectl --namespace=kube-system get ep -l k8s-app=dnsmasq
NAME ENDPOINTS AGE
dnsmasq 10.233.71.2:53,10.233.74.2:53,10.233.96.2:53 + 3 more... 6m
# kubectl --namespace=kube-system get pod -l k8s-app=kubedns
NAME READY STATUS RESTARTS AGE
kubedns-6mhhn 4/4 Running 0 5m
# kubectl --namespace=kube-system get svc -l k8s-app=kubedns
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubedns 10.233.0.3 <none> 53/UDP,53/TCP 6m
# kubectl --namespace=kube-system get ep -l k8s-app=kubedns
NAME ENDPOINTS AGE
kubedns 10.233.71.3:53,10.233.71.3:53 6m
10.233.0.2
は dnsmasq
の ClusterIP です。
kubedns
がレコードを持っている .cluster.local
も 10.233.0.2
に問い合わせて応答が得られます。
dnsmasq
の方を確認すると分かりますが、.cluster.local
のみを kubedns
に転送し、それ以外は 8.8.8.8
と 8.8.4.4
に転送しています。
ただ、ホストからの名前解決にはちょっと問題がありました。
まずは VM に元々設定していた nameserver がある場合は、共存に問題があるので 3台とも コメントアウトしておきます。
## kube01 のホスト上で実行
# cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.233.0.2
# Generated by NetworkManager
#nameserver 192.168.37.10 ## kargo deploy 前に設定されていた値
options timeout:2
options attempts:2
さらに、Reverse Path Filter が有効になっていると受信した NIC と経路情報との不一致が起きたと見なしてパケットが捨てられてしまいます。
(CentOS 7.2 ではデフォルトで有効)
ここでは単に Reverse Path Filter を無効化して対応します。
# cp -ip /usr/lib/sysctl.d/50-default.conf /tmp
# vi /usr/lib/sysctl.d/50-default.conf
--- /tmp/50-default.conf 2015-11-20 13:49:03.000000000 +0900
+++ /usr/lib/sysctl.d/50-default.conf 2016-08-11 01:26:51.845307320 +0900
@@ -21,8 +21,8 @@
kernel.core_uses_pid = 1
# Source route verification
-net.ipv4.conf.default.rp_filter = 1
-net.ipv4.conf.all.rp_filter = 1
+net.ipv4.conf.default.rp_filter = 0
+net.ipv4.conf.all.rp_filter = 0
# Do not accept source routing
net.ipv4.conf.default.accept_source_route = 0
# sysctl -a | grep '\.rp_filter'
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.docker0.rp_filter = 1
net.ipv4.conf.eno16777984.rp_filter = 1
net.ipv4.conf.flannel/1.rp_filter = 1
net.ipv4.conf.lo.rp_filter = 0
net.ipv4.conf.veth5039f11.rp_filter = 1
# systemctl restart systemd-sysctl
# sysctl -a | grep '\.rp_filter'
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.docker0.rp_filter = 1
net.ipv4.conf.eno16777984.rp_filter = 1
net.ipv4.conf.flannel/1.rp_filter = 1
net.ipv4.conf.lo.rp_filter = 0
net.ipv4.conf.veth5039f11.rp_filter = 1
## NIC が初期化された時点では default=1 だったので全て書き換え
# sysctl -a | grep '\.rp_filter' | while read line; do set -- $line; sysctl -w ${1}=0; done
# sysctl -a | grep '\.rp_filter'
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.docker0.rp_filter = 0
net.ipv4.conf.eno16777984.rp_filter = 0
net.ipv4.conf.flannel/1.rp_filter = 0
net.ipv4.conf.lo.rp_filter = 0
net.ipv4.conf.veth5039f11.rp_filter = 0
これでコンテナだけでなく、ホストからも名前解決できるようになりました。
# yum install -y bind-utils
# dig +short kubernetes.default.svc.cluster.local.
10.233.0.1
# dig +short kubernetes.io.
192.30.252.153
192.30.252.154
試しに適当な Pod も作って動かしてみます。
# kubectl run test --image=busybox --restart=Never --command -- /bin/sh -c 'while :; do sleep 100; done'
# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
test 1/1 Running 0 14s 10.233.96.3 node1
# kubectl exec -ti test /bin/sh
# uname -n
test
# ps
PID USER TIME COMMAND
1 root 0:00 /bin/sh -c while :; do sleep 100; done
5 root 0:00 sleep 100
6 root 0:00 /bin/sh
10 root 0:00 ps
# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue
link/ether 02:42:0a:e9:60:03 brd ff:ff:ff:ff:ff:ff
inet 10.233.96.3/24 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::42:aff:fee9:6003/64 scope link tentative flags 08
valid_lft forever preferred_lft forever
# ip r
default via 10.233.96.1 dev eth0
10.233.96.0/24 dev eth0 src 10.233.96.3
# cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.233.0.2
options attempts:2
options ndots:5
# nslookup kubernetes.io
Server: 10.233.0.2
Address 1: 10.233.0.2
Name: kubernetes.io
Address 1: 192.30.252.153 pages.github.com
Address 2: 192.30.252.154 pages.github.com
# nslookup kubernetes.default.svc.cluster.local
Server: 10.233.0.2
Address 1: 10.233.0.2
Name: kubernetes.default.svc.cluster.local
Address 1: 10.233.0.1
# exit
ちゃんと動いているようですね。
ところで・・・
3台目の VM では Master の機能を持たせていませんでした。
3台目の VM はどちらの VM にいる Master を参照しているのでしょうか。
## node3に相当するkube03にてコマンド実行
# ps -eo args | grep ^./hyperkube\ kubelet | sed "s/ /\n/g"
./hyperkube
kubelet
--v=2
--api_servers=https://192.168.100.1:443
--address=0.0.0.0
--hostname-override=node3
--allow-privileged=true
--cluster_dns=10.233.0.2
--cluster_domain=cluster.local
--kubeconfig=/etc/kubernetes/node-kubeconfig.yaml
--config=/etc/kubernetes/manifests
--resolv-conf=/etc/resolv.conf
## node1に相当するkube01にてコマンド実行
# ps -eo args | grep ^./hyperkube\ kubelet | sed "s/ /\n/g"
./hyperkube
kubelet
--v=2
--api_servers=http://127.0.0.1:8080
--address=0.0.0.0
--hostname-override=node1
--allow-privileged=true
--cluster_dns=10.233.0.2
--cluster_domain=cluster.local
--kubeconfig=/etc/kubernetes/node-kubeconfig.yaml
--config=/etc/kubernetes/manifests
--resolv-conf=/etc/resolv.conf
## node2に相当するkube02にてコマンド実行
# ps -eo args | grep ^./hyperkube\ kubelet | sed "s/ /\n/g"
./hyperkube
kubelet
--v=2
--api_servers=http://127.0.0.1:8080
--address=0.0.0.0
--hostname-override=node2
--allow-privileged=true
--cluster_dns=10.233.0.2
--cluster_domain=cluster.local
--kubeconfig=/etc/kubernetes/node-kubeconfig.yaml
--config=/etc/kubernetes/manifests
--resolv-conf=/etc/resolv.conf
API Server の参照先は 192.168.100.1
になっています。
この場合 1台目がダウンした場合に 3台目の kubelet も Not Ready 状態になるでしょう。
ここでは触れませんが、本番運用する時には対応が必要でしょう。
他にも 3台目から 1台目の API Server を参照している設定があるので、そちらも注意が必要です。
root 9817 0.0 0.1 115240 1480 ? Ss 01:14 0:00 /bin/bash /usr/local/bin/kubelet --v=2 --api_servers=https://192.168.100.1:443 --address=0.0.0.0 --hostname-override=node3 --allow-privileged=true --cluster_dns=10.233.0.2 --cluster_domain=cluster.local --kubeconfig=/etc/kubernetes/node-kubeconfig.yaml --config=/etc/kubernetes/manifests --resolv-conf=/etc/resolv.conf
root 9819 0.0 0.9 119152 9076 ? Sl 01:14 0:00 /usr/bin/docker run --privileged --rm --net=host --pid=host --name=kubelet -v /etc/cni:/etc/cni:ro -v /opt/cni:/opt/cni:ro -v /etc/kubernetes:/etc/kubernetes -v /sys:/sys -v /dev:/dev -v /var/lib/docker:/var/lib/docker -v /var/run:/var/run -v /var/lib/kubelet:/var/lib/kubelet quay.io/coreos/hyperkube:v1.3.4_coreos.0 nsenter --target=1 --mount --wd=. -- ./hyperkube kubelet --v=2 --api_servers=https://192.168.100.1:443 --address=0.0.0.0 --hostname-override=node3 --allow-privileged=true --cluster_dns=10.233.0.2 --cluster_domain=cluster.local --kubeconfig=/etc/kubernetes/node-kubeconfig.yaml --config=/etc/kubernetes/manifests --resolv-conf=/etc/resolv.conf
root 9866 1.1 4.6 464388 46192 ? Ssl 01:14 0:13 ./hyperkube kubelet --v=2 --api_servers=https://192.168.100.1:443 --address=0.0.0.0 --hostname-override=node3 --allow-privileged=true --cluster_dns=10.233.0.2 --cluster_domain=cluster.local --kubeconfig=/etc/kubernetes/node-kubeconfig.yaml --config=/etc/kubernetes/manifests --resolv-conf=/etc/resolv.conf
root 10250 0.9 2.7 363300 27500 ? Ssl 01:15 0:11 /hyperkube proxy --v=2 --master=https://192.168.100.1:443 --kubeconfig=/etc/kubernetes/node-kubeconfig.yaml --bind-address=192.168.100.3 --proxy-mode=iptables