LoginSignup
11
8

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-08-10

はじめに

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
11
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
8