kubernetes
kubespray

kubespray (kargo-cli) で20分で Kubernetes クラスタを作る

More than 1 year has passed since last update.


はじめに

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 で以下の図の構成になります。

68747470733a2f2f7333322e706f7374696d672e6f72672f387137676e733875742f336e6f6465732e706e67.png

( kubespray/kargo-cli から引用 )


  • 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


/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 であたりをつけて書き換えます。

(勝手に変更しているので、動作保証はありません)


/root/.kargo/roles/download/vars/kube_versions.yml

kube_version: v1.3.4



/root/.kargo/roles/uploads/vars/kube_versions.yml

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 の機能も見てみます。


dnsmasqについて確認

# 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



kubednsについて確認

# 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.2dnsmasq の ClusterIP です。

kubedns がレコードを持っている .cluster.local10.233.0.2 に問い合わせて応答が得られます。

dnsmasq の方を確認すると分かりますが、.cluster.local のみを kubedns に転送し、それ以外は 8.8.8.88.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

ちゃんと動いているようですね。

ところで・・・

68747470733a2f2f7333322e706f7374696d672e6f72672f387137676e733875742f336e6f6465732e706e67.png

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 を参照している設定があるので、そちらも注意が必要です。


kube03にてコマンド実行

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