Kubernetes v1.10 クラスタをVagrantで構築したメモ 


~~自宅のMac上の~~Vagrant環境で、K8s最新版 v1.10(2018年4月15日現在)をマスターノード１台とワーカーノード２台で構築したメモです。
macOSやWindows10の両方で動作する様に、自宅環境と会社のネット環境でも利用できる様に、改良を加えました。
   


## k8s v1.10 システム構成

MacやWindwosの上で動かすと言えば、minikube?と思われのですが、そうではなく、本格的なK8sクラスタの構成にチャレンジします。 環境を作るためのハードウェアのスペック、ソフトウェアの名前とバージョン、そして、ネットワークについて、以下に書いておきます。


### 主な実行環境のスペックとバージョン

* パソコン環境
  * MacOS: macOS High Sierra 10.13.4  RAM 16GB, 3.4GHz Intel Core i5
  * Windwos10 RAM 16GB, 3.4GHz Intel Core i5
* Vagrant: 2.0.3
* VirutalBox: 5.1.10
* box: ubuntu/xenial64 (virtualbox, 20180413.0.0)
* Kubernetes: 1.10 amd64


### システム構成

Vagrant上の作るK8sのクラスタ環境は、マスターノード１台、ワーカーノード２台の構成で、全ノード横断でポッドネットワーク(10.244.0.0/16)を構成して、異なるノードにデプロイされたポッドでも、お互いに疎通できるものを目指します。

![スクリーンショット 2018-05-14 22.43.29.png](https://qiita-image-store.s3.amazonaws.com/0/42829/c7ee7cdf-3c8b-0709-bfdd-3f6d3bd337c3.png "スクリーンショット 2018-05-14 22.43.29.png")


### ネットワークの説明
このK8sクラスタでは、3種類のネットワークがあり、以下に、それぞれの役割と概要を書きます。

* ~~Public Network(192.168.1.0/24)は、自宅のブロードバンドルータが接続されるプライベートネットワークで、NATによってインターネットへアクセスできます。Vagrantの各ノードは個々にIPアドレスを与えて、ブリッジ接続で、自宅プライベートネットワークに接続します。 ノードにアドレスを付与する事で、同じプライベートネットワークのPCなどからアクセスできる様にして、NodePortで開いたサービスを利用できる様に設定します。~~ Public Network上のIPアドレスの付与は、企業内LANでの利用を想定して廃止しました。 家庭内のLANで利用する場合には、コメントを外せば利用できる様にしてあります。
* ~~Private Network(172.42.42.0/24)は、ノード間の通信だけに利用するネットワークです。これは、ポッドネットワークの仮想回線の通信経路として利用します。~~ 
* Private Network(172.16.20.0/24)は、ホストオンリー(パソコン内部のみ）の通信に利用できるネットワークです。ポッドネットワークの仮想回線の通信経路としても利用します。
* NAT 10.0.2.15 は、それぞれのノード（仮想サーバー）に対して、vagrant ssh でホストからアクセスするための経路になります。
* pod-network (10.244.0.0/16) ノード横断で、ポッド同士を繋ぐネットワークです。今回は、flannel というCNIプラグインを利用して構成します。


### 参考にした資料

* Installing kubeadm https://kubernetes.io/docs/setup/independent/install-kubeadm/
* Using kubeadm to Create a Cluster https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/
* Troubleshooting kubeadm https://kubernetes.io/docs/setup/independent/troubleshooting-kubeadm/
* kubeadm init https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-init/
* kubeadm reset https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-reset/
* kubelet https://kubernetes.io/docs/reference/generated/kubelet/
* CoreOS flannel https://github.com/coreos/flannel
* Pod Overview https://kubernetes.io/docs/concepts/workloads/pods/pod-overview/
* Services https://kubernetes.io/docs/concepts/services-networking/service/
* Customizing DNS Service https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/



## Vagrantfileの説明

３台の仮想サーバーと各ネットワークは、次のVagrantfileを使って作成します。 このVagrantfileは、https://github.com/takara9/vagrant-k8s/blob/master/Vagrantfile に置いてありますので、ご自由にご利用ください。

* ~~3行目　このVagrantfileを作る際に参考したURLです。 原型を殆ど残さないくらい、変わってしまいました。~~
* ~~8行目以降  ３回のループで３台の仮想サーバーを起動します。 10行目でOSを指定で、https://app.vagrantup.com/ubuntu/boxes/xenial64 を参照すれば、Ubuntu16.04であることが解ります。 public_ipとprivate_ipの開始アドレスからループ回数を加えてアドレスをアサインします。 混乱しそうですが、13行目パブリックIPは、自宅のブロードバンドルータの繋がったプライベートIPアドレスです。そして、 16行目のprivate_ipは、仮想サーバー間で利用するネットワークです。~~
* 5行目〜70行目まで、ループで３台の仮想サーバーを起動します。 8行目でOSを指定は、Ubuntu16.04です。次のURL https://app.vagrantup.com/ubuntu/boxes/xenial64 にバージョンが記載されています。18行目がホストオンリーのネットワークです。
* 21行目 ホスト名 node-1〜node-3をそれぞれ付与します。マスターにRAM 2GB、ワーカーノードにRAM 1GBを与えました。 もう少し少なくても動作すると思いますが、Macのホスト環境が許す限り、大きな値を与えると良いと思います。
* 32行目以降 起動した仮想サーバーのセットアップのシェルで、初回の起動時に一回だけ実行されます。 このセットアップは、このリストの後に、詳しく開設したいと思います。

~~~rb:Vagrantfile
     1	# -*- mode: ruby -*-
     2	# # vi: set ft=ruby :
     3	#
     4	
     5	Vagrant.configure(2) do |config|
     6	  (1..3).each do |i|
     7	    config.vm.define "node-#{i}" do |s|
     8	      s.vm.box = "ubuntu/xenial64"
     9	      s.vm.hostname = "node-#{i}"
    10	
    11	      #public_ip = "192.168.1.#{i+90}"
    12	      #s.vm.network :public_network, ip: public_ip, bridge: "en0: Ethernet"
    13	      #if i == 1 then
    14	      #  s.vm.network :forwarded_port, host: 8001, guest: 8001
    15	      #end
    16	
    17	      private_ip = "172.16.20.#{i+10}"
    18	      s.vm.network "private_network", ip: private_ip
    19	      
    20	      s.vm.provider "virtualbox" do |v|
    21	        v.cpus = 1
    22	        v.gui = false        
    23	        if i == 1 then
    24	          v.memory = 2048
    25	        else
    26	          v.memory = 1024
    27	          #v.memory = 2048
    28	        end
    29	      end
    30	
    31	      s.vm.provision "shell", inline: <<-EOF
    32	echo net.bridge.bridge-nf-call-iptables = 1 >> /etc/sysctl.conf
    33	sysctl -p
    34	
    35	#
    36	apt-get update
    37	apt-get install -y apt-transport-https ca-certificates curl software-properties-common
    38	
    39	#
    40	# add repo Docker-CE
    41	curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
    42	add-apt-repository "deb https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") $(lsb_release -cs) stable"
    43	curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
    44	
    45	#
    46	# add repo Kubernetes
    47	cat <<EOF2 >/etc/apt/sources.list.d/kubernetes.list
    48	deb http://apt.kubernetes.io/ kubernetes-xenial main
    49	EOF2
    50	
    51	#
    52	# install Docker-CE
    53	apt-get update
    54	apt-get install -y docker-ce=$(apt-cache madison docker-ce | grep 17.03 | head -1 | awk '{print $3}')
    55	usermod -aG docker vagrant
    56	
    57	#
    58	# install Kubernetes
    59	#apt-get install -y kubelet=1.9.6-00 kubeadm=1.9.6-00 kubectl=1.9.6-00
    60	apt-get install -y kubelet kubeadm kubectl
    61	#
    62	EOF
    63	    end
    64	  end
    65	
    66	  if Vagrant.has_plugin?("vagrant-cachier")
    67	    config.cache.scope = :box
    68	  end
    69	
    70	end

        
~~~

### セットアップ・シェル部分の解説

何度も実行する様な事は、Vagrantfileのs.vm.provision "shell"に書いて置くと便利です。 Kubernetesのセットアップから抜粋してきたコマンドも、このファイルに書いておき、設定のスピードアップを図ります。

* 32行目 ポッドネットワークの構成に資料する flannel が動作するための設定です。 https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt によれば、デフォルト値が1なので、設定不要かもしれませんが、意図的に設定を入れておきます。
* 40〜43行目 これは https://kubernetes.io/docs/setup/independent/install-kubeadm/#installing-docker に書かれた Docker-CEをインストールするために、リポジトリを追加するものです。
* 46〜49行目 Kubernetesのリポジトリを追加します。 これは、https://kubernetes.io/docs/setup/independent/install-kubeadm/#installing-kubeadm-kubelet-and-kubectl に記載されています。
* 52行目 Docker-CEをインストールします。 Kubernetesの最新版が確認しているDockerのバージョンは17.03 ですから、指定してインストールします。
* 58行目 Kubernetesのインストールです。　バージョンを指定したい場合は、59行目の様にパッケージ名の後にバージョンを指定します。

リポジトリに登録されたバージョンを確認するには、次のコマンドを実行します。

~~~
# apt-cache madison kubeadm
   kubeadm |  1.10.0-00 | http://apt.kubernetes.io/ kubernetes-xenial/main armhf Packages
   kubeadm |   1.9.6-00 | http://apt.kubernetes.io/ kubernetes-xenial/main armhf Packages
   kubeadm |   1.9.5-00 | http://apt.kubernetes.io/ kubernetes-xenial/main armhf Packages
   kubeadm |   1.9.4-00 | http://apt.kubernetes.io/ kubernetes-xenial/main armhf Packages
   kubeadm |   1.9.3-00 | http://apt.kubernetes.io/ kubernetes-xenial/main armhf Packages
   kubeadm |   1.9.2-00 | http://apt.kubernetes.io/ kubernetes-xenial/main armhf Packages
   kubeadm |   1.9.1-00 | http://apt.kubernetes.io/ kubernetes-xenial/main armhf Packages
   kubeadm |   1.9.0-00 | http://apt.kubernetes.io/ kubernetes-xenial/main armhf Packages
省略
   kubeadm |   1.6.4-00 | http://apt.kubernetes.io/ kubernetes-xenial/main armhf Packages
   kubeadm |   1.6.3-00 | http://apt.kubernetes.io/ kubernetes-xenial/main armhf Packages
   kubeadm |   1.6.2-00 | http://apt.kubernetes.io/ kubernetes-xenial/main armhf Packages
   kubeadm |   1.6.1-00 | http://apt.kubernetes.io/ kubernetes-xenial/main armhf Packages
   kubeadm |   1.6.0-00 | http://apt.kubernetes.io/ kubernetes-xenial/main armhf Packages
   kubeadm |   1.5.7-00 | http://apt.kubernetes.io/ kubernetes-xenial/main armhf Packages
   kubeadm |   1.5.6-00 | http://apt.kubernetes.io/ kubernetes-xenial/main armhf Packages
~~~   


このVagrantファイルは、次の様に利用します。

次のコマンドで、GitHubからローカルにクローン（複製）を作ります。

~~~
$ git clone https://github.com/takara9/vagrant-k8s
~~~

次にディレクトリを移動して、`vagrant up` とするだけで、仮想サーバーが３台、Docker-CE v17.03 と Kubernetes v1.10が導入された状態で起動します。　Vagrantは本当に便利ですよね。　

~~~
$ cd vagrant-k8s
$ vagrant up
~~~


## 仮想マシンの起動と個別設定

もし、`vagrant box list`して、該当の仮想マシン(Box)のテンプレートが、ローカル環境になければ、　次のコマンでダウンロードします。

~~~
$ vagrant box add ubuntu/xenial64
~~~


`git clone https://github.com/takara9/vegrant-k8s` で作成されたディレクトリで、次のコマンドで、仮想サーバーを起動します。

~~~
maho$ vagrant up
Bringing machine 'node-1' up with 'virtualbox' provider...
Bringing machine 'node-2' up with 'virtualbox' provider...
Bringing machine 'node-3' up with 'virtualbox' provider...
==> node-1: Importing base box 'ubuntu/xenial64'...
==> node-1: Matching MAC address for NAT networking...
==> node-1: Checking if box 'ubuntu/xenial64' is up to date...
==> node-1: Setting the name of the VM: node-1
==> node-1: Clearing any previously set network interfaces...
==> node-1: Preparing network interfaces based on configuration...
    node-1: Adapter 1: nat
    node-1: Adapter 2: bridged
    node-1: Adapter 3: intnet
==> node-1: Forwarding ports...
    node-1: 22 (guest) => 2222 (host) (adapter 1)
~~~

仮想マシンが３台起動するので、node-1にログインして、マスターノードになる様に設定します。


## マスターノード node-1の個別設定

node-1仮想マシンへログインするには、sshの後にホスト名を付与します。

~~~
$ vagrant ssh node-1
~~~

ノードの起動IPを設定ます。

~~~
vi /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
~~~

次の`Environment="KUBELET_DNS_ARGS=`から始まる行に、dnsのIPアドレス、`--node-ip`を追加します。 この仮想サーバーのIPアドレスですが、`--node-ip=` に`172.16.20.11`を設定しないと、vagrantがホストと接続する際に利用する 10.0.2.15が最初のインターフェースなので、 Kubernetesのデーモンが、このアドレスを掴んでしまい、正しく動作しません。 この10.0.2.15は、各仮想サーバーに同じアドレスで存在し、仮想サーバー間の通信のために利用できないためです。

~~~
Environment="KUBELET_DNS_ARGS=--cluster-dns=10.244.0.10 --cluster-domain=cluster.local --node-ip=172.16.20.11"
~~~

この変更を反映させるために、以下のコマンドを実行します。

~~~
systemctl daemon-reload
systemctl restart kubelet
~~~

## マスターノードのセットアップ実行

Using kubeadm to Create a Cluster https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/#14-installing-kubeadm-on-your-hosts に従って、セットアップを進めるのですが、簡単そうで、上手く行かないので、補足します。

ノード横断で、ポッド・ネットワークを構成するには、Calico, Canal, Fannelが良く利用される様ですが、今回は、一番実績が多いとされるFannelを利用します。  Fannelにはネットワークアドレス(10.244.0.0/16)が、ハードコーディングされていて、それ以外を指定すると、動作しないので、`--pod-network-cidr=10.244.0.0/16` と `--service-cidr=10.244.0.0/16`は、変更してはいけません。 それから、`--apiserver-advertise-address=172.16.20.11`は、マスターノードのprivateアドレスを指定しておきます。 ~~これはマスターノードのIPアドレスであれば、良いのでパブリック側からアクセスしたければ、そちらのアドレスを設定します。~~

このコマンドは、ルートで実行します。

~~~
# kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=172.16.20.11 --service-cidr=10.244.0.0/16
~~~


実行結果のアウトプットを以下に添付します。 このアウトプットには、kubectlの環境設定、kubeadm joinのコマンドが表示されているので、メモ帳などにコピペして、保管しておきます。


~~~
root@node-1:~# kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=172.16.20.11 --service-cidr=10.244.0.0/16
[init] Using Kubernetes version: v1.10.1
[init] Using Authorization modes: [Node RBAC]
[preflight] Running pre-flight checks.
	[WARNING FileExisting-crictl]: crictl not found in system path
Suggestion: go get github.com/kubernetes-incubator/cri-tools/cmd/crictl
[certificates] Generated ca certificate and key.
[certificates] Generated apiserver certificate and key.
[certificates] apiserver serving cert is signed for DNS names [node-1 kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.244.0.1 172.16.20.11]
[certificates] Generated apiserver-kubelet-client certificate and key.
[certificates] Generated etcd/ca certificate and key.
[certificates] Generated etcd/server certificate and key.
[certificates] etcd/server serving cert is signed for DNS names [localhost] and IPs [127.0.0.1]
[certificates] Generated etcd/peer certificate and key.
[certificates] etcd/peer serving cert is signed for DNS names [node-1] and IPs [172.16.20.11]
[certificates] Generated etcd/healthcheck-client certificate and key.
[certificates] Generated apiserver-etcd-client certificate and key.
[certificates] Generated sa key and public key.
[certificates] Generated front-proxy-ca certificate and key.
[certificates] Generated front-proxy-client certificate and key.
[certificates] Valid certificates and keys now exist in "/etc/kubernetes/pki"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/admin.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/kubelet.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/controller-manager.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/scheduler.conf"
[controlplane] Wrote Static Pod manifest for component kube-apiserver to "/etc/kubernetes/manifests/kube-apiserver.yaml"
[controlplane] Wrote Static Pod manifest for component kube-controller-manager to "/etc/kubernetes/manifests/kube-controller-manager.yaml"
[controlplane] Wrote Static Pod manifest for component kube-scheduler to "/etc/kubernetes/manifests/kube-scheduler.yaml"
[etcd] Wrote Static Pod manifest for a local etcd instance to "/etc/kubernetes/manifests/etcd.yaml"
[init] Waiting for the kubelet to boot up the control plane as Static Pods from directory "/etc/kubernetes/manifests".
[init] This might take a minute or longer if the control plane images have to be pulled.
[apiclient] All control plane components are healthy after 50.002958 seconds
[uploadconfig] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[markmaster] Will mark node node-1 as master by adding a label and a taint
[markmaster] Master node-1 tainted and labelled with key/value: node-role.kubernetes.io/master=""
[bootstraptoken] Using token: c5b771.jeqtelo0ux27upvk
[bootstraptoken] Configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstraptoken] Configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstraptoken] Configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstraptoken] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[addons] Applied essential addon: kube-dns
[addons] Applied essential addon: kube-proxy

Your Kubernetes master has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of machines by running the following on each node
as root:

  kubeadm join 172.16.20.11:6443 --token c5b771.jeqtelo0ux27upvk --discovery-token-ca-cert-hash sha256:f1fa8b0f197792f7b7a97d2798a262573a7d058959aa7aff42e65d3b2a679450
~~~


## kubectlの環境設定

上記のkubeadm initのアウトプットを参考にしながら、vagrantユーザーで以下を実行します。

~~~
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
~~~

`vagrant ssh node-1`でログインして、上記コマンドを実行後に、`kubectl get node`でマスターノードが NotReadyの状態で表示されたら完了です。

~~~
imac:k8s-cluster maho$ vagrant ssh node-1
省略
vagrant@node-1:~$ mkdir -p $HOME/.kube
vagrant@node-1:~$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
vagrant@node-1:~$ sudo chown $(id -u):$(id -g) $HOME/.kube/config
vagrant@node-1:~$ kubectl get node
NAME      STATUS     ROLES     AGE       VERSION
node-1    NotReady   master    8m        v1.10.1
~~~


## ポッド・ネットワークの設定

Flannelを利用してポッドネットワークを設定するのですが、Vagrantの環境では一つの障害があります。 https://kubernetes.io/docs/setup/independent/troubleshooting-kubeadm/ の Default NIC When using flannel as the pod network in Vagrant に記載されいる通り、もし、設定を追加しなければ、Vagrantのホストからvagrant ssh でログインするためのNICが Flannelのインターフェースとして割り当てられてしまい、ポッドネットワークがノードで分断された状態になります。

そこで、次の順番で修正をしていきます。 まず、ローカルに設定ファイルをダウンロードします。

~~~
vagrant@node-1:~$ curl -O https://raw.githubusercontent.com/coreos/flannel/v0.9.1/Documentation/kube-flannel.yml
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  2801  100  2801    0     0   7374      0 --:--:-- --:--:-- --:--:--  7390
~~~

エディタでkube-flannel.ymlを編集します。 次の127行目の様に、"- --iface=enp0s8"を追加します。

~~~
   119	      containers:
   120	      - name: kube-flannel
   121	        image: quay.io/coreos/flannel:v0.10.0-amd64
   122	        command:
   123	        - /opt/bin/flanneld
   124	        args:
   125	        - --ip-masq
   126	        - --kube-subnet-mgr
   127	        - --iface=enp0s8
   128	        resources:
~~~



インターフェースの名前の取得は、ifconfigコマンドで、172.16.20.0/24 は ~~プライベートネットワーク~~ ホストオンリーに接続されたインタフェースの名前になります。　この名前は、３台の仮想サーバーで同じインターフェース名である必要があります。

~~~
vagrant@node-1:~$ ifconfig
cni0      Link encap:Ethernet  HWaddr 0a:58:0a:f4:00:01  
          inet addr:10.244.0.1  Bcast:0.0.0.0  Mask:255.255.255.0
          inet6 addr: fe80::4c56:9dff:fe21:d857/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1450  Metric:1
          RX packets:139624 errors:0 dropped:0 overruns:0 frame:0
          TX packets:172900 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:34478740 (34.4 MB)  TX bytes:37214713 (37.2 MB)

docker0   Link encap:Ethernet  HWaddr 02:42:31:8b:40:ff  
          inet addr:172.17.0.1  Bcast:0.0.0.0  Mask:255.255.0.0
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

enp0s3    Link encap:Ethernet  HWaddr 02:d4:20:7b:c2:aa  
          inet addr:10.0.2.15  Bcast:10.0.2.255  Mask:255.255.255.0
          inet6 addr: fe80::d4:20ff:fe7b:c2aa/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:180913 errors:0 dropped:0 overruns:0 frame:0
          TX packets:85413 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:253978977 (253.9 MB)  TX bytes:5476438 (5.4 MB)

enp0s8    Link encap:Ethernet  HWaddr 08:00:27:b3:d2:58  
          inet addr:172.16.20.11  Bcast:172.16.20.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:253555 errors:0 dropped:0 overruns:0 frame:0
          TX packets:349308 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:26329597 (26.3 MB)  TX bytes:390833376 (390.8 MB)
~~~          


これは、マスターノードに設定すると、後からジョインするワーカーノードへも自動的に反映されます。

~~~
vagrant@node-1:/vagrant/yaml$ kubectl apply -f kube-flannel.yml 
clusterrole.rbac.authorization.k8s.io "flannel" created
clusterrolebinding.rbac.authorization.k8s.io "flannel" created
serviceaccount "flannel" created
configmap "kube-flannel-cfg" created
daemonset.extensions "kube-flannel-ds" created
~~~


このコマンドが完了すると、マスターノードの状態が Readyに変わります。

~~~
vagrant@node-1:/vagrant/yaml$ kubectl get node
NAME      STATUS    ROLES     AGE       VERSION
node-1      Ready     master    26m       v1.10.1
~~~

以上で、マスターノードの設定が完了しました。 ここでK8sのネームスペース kube-system のデーモンが正しく プライベートネットワークのIPアドレスを掴んでいるかを確認しておきます。 マスターノードのIPアドレス 172.16.20.11 にバインドされていれば、正しく設定されています。もしも、10.0.2.15 にバインドされていれば、ポッドネットワークがノードで分断されることになりますので、確認しておきます。 もしも、意図通りでなければ、`kubeadm rest`を実行して、`kubeadm init...` の前の個別設定のステップから再度見直します。

~~~
vagrant@node-1:/vagrant/yaml$ kubectl get po -o wide -n kube-system
NAME                           READY     STATUS    RESTARTS   AGE       IP             NODE
etcd-node-1                    1/1       Running   0          25m       172.16.20.11   node-1
kube-apiserver-node-1          1/1       Running   0          26m       172.16.20.11   node-1
kube-controller-manager-node-1 1/1       Running   0          26m       172.16.20.11   node-1
kube-dns-86f4d74b45-5dckd      3/3       Running   0          27m       10.244.0.2     node-1
kube-flannel-ds-d86bt          1/1       Running   0          2m        172.16.20.11   node-1
kube-proxy-pd9mz               1/1       Running   0          27m       172.16.20.11   node-1
kube-scheduler-node-1          1/1       Running   0          26m       172.16.20.11   node-1
vagrant@node-1:/vagrant/yaml$ kubectl get svc -o wide -n kube-system
NAME       TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)         AGE       SELECTOR
kube-dns   ClusterIP   10.244.0.10   <none>        53/UDP,53/TCP   27m       k8s-app=kube-dns
~~~


## ワーカーノードの設定

次にワーカーノードの２つをマスターノードに接続して、クラスタを構成します。

### ノードIPの設定
node-2 へ次の要領で、ログインします。 そして rootユーザーになって、ファイルを編集します。

~~~
vagrant ssh node-2
~~~


~~~
vagrant@node-2:~$ sudo -s
root@node-2:~# vi vi /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
~~~

下記の行を編集して、

~~~
Environment="KUBELET_DNS_ARGS=--cluster-dns=10.96.0.10 --cluster-domain=cluster.local"
~~~

dnsのIPアドレスを10.244.0.10に修正、`--node-ip=172.16.20.12` を追加します。 このIPアドレスは、ifconfig で確認して インタフェース enp0s8 の IPアドレスを設定します。

~~~
Environment="KUBELET_DNS_ARGS=--cluster-dns=10.244.0.10 --cluster-domain=cluster.local --node-ip=172.16.20.12"
~~~

編集が終わったら、次の二つのコマンドを実行して、kubeletの設定を有効にします。

~~~
systemctl daemon-reload
systemctl restart kubelet
~~~

### k8sクラスタへのジョイン

rootユーザーで `kubeadm join ...` コマンドを実行して、クラスタへ参加させます。 このコマンドのパラメータは、`kubeadm init `の実行結果として、アウトプットされた表示をコピペして実行します。

~~~
root@node-2:~# kubeadm join 172.16.20.11:6443 --token c5b771.jeqtelo0ux27upvk --discovery-token-ca-cert-hash sha256:f1fa8b0f197792f7b7a97d2798a262573a7d058959aa7aff42e65d3b2a679450
[preflight] Running pre-flight checks.
	[WARNING FileExisting-crictl]: crictl not found in system path
Suggestion: go get github.com/kubernetes-incubator/cri-tools/cmd/crictl
[discovery] Trying to connect to API Server "172.42.42.11:6443"
[discovery] Created cluster-info discovery client, requesting info from "https://172.16.20.11:6443"
[discovery] Requesting info from "https://172.42.42.11:6443" again to validate TLS against the pinned public key
[discovery] Cluster info signature and contents are valid and TLS certificate validates against pinned roots, will use API Server "172.42.42.11:6443"
[discovery] Successfully established connection with API Server "172.16.20.11:6443"

This node has joined the cluster:
* Certificate signing request was sent to master and a response
  was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the master to see this node join the cluster.
~~~


これでマスターノード側で `kubectl get nodes` を実行して、クラスタの状態を確認します。

~~~
vagrant@node-1:~$ kubectl get node
NAME      STATUS    ROLES     AGE       VERSION
node-1    Ready     master    3h        v1.10.1
node-2    Ready     <none>    1m        v1.10.1
~~~


今回追加したノードのROLESが設定されていないので、ROLESを設定します。 コマンドは、`kubectl label node <ノード名> node-role.kubernetes.io/node=`で、ノード名をホスト名で置き換えて実行します。

~~~
vagrant@node-1:~$ kubectl label node node-2 node-role.kubernetes.io/node=
node "node-2" labeled
vagrant@node-1:~$ kubectl get node
NAME      STATUS    ROLES     AGE       VERSION
node-1    Ready     master    3h        v1.10.1
node-2    Ready     node      2m        v1.10.1
~~~

同じ要領で、node-3もクラスタへジョインします。

~~~
vagrant@node-1:~$ kubectl get node
NAME      STATUS    ROLES     AGE       VERSION
node-1    Ready     master    3h        v1.10.1
node-2    Ready     node      34m       v1.10.1
node-3    Ready     node      1m        v1.10.1
~~~


クラスタが完成した処で、確認しておきます。 

~~~
vagrant@node-1:~$ kubectl get po -o wide -n kube-system
NAME                           READY     STATUS    RESTARTS   AGE       IP             NODE
etcd-node-1                    1/1       Running   0          3h        172.16.20.11   node-1
kube-apiserver-node-1          1/1       Running   0          3h        172.16.20.11   node-1
kube-controller-manager-node-1 1/1       Running   0          3h        172.16.20.11   node-1
kube-dns-86f4d74b45-5dckd      3/3       Running   0          3h        10.244.0.2     node-1
kube-flannel-ds-5lsd4          1/1       Running   0          35m       172.16.20.12   node-2
kube-flannel-ds-d86bt          1/1       Running   0          3h        172.16.20.11   node-1
kube-flannel-ds-r4jmb          1/1       Running   0          2m        172.16.20.13   node-3
kube-proxy-7j4vm               1/1       Running   0          2m        172.16.20.13   node-3
kube-proxy-n82vs               1/1       Running   0          35m       172.16.20.12   node-2
kube-proxy-pd9mz               1/1       Running   0          3h        172.16.20.11   node-1
kube-scheduler-node-1          1/1       Running   0          3h        172.16.20.11   node-1
~~~




---

# k8sクラスタの機能テスト

## デプロイメントのテスト

nginxのウェブサーバーをデプロイするためのYAMLファイルを準備します。 ここではYAMLファイルの概説として、`spec.replicas: 6`によって、６個のウェブサーバーが起動します。 ウェブサーバーのコンテナは、`spec.template.spec.containers.image:`の値フィールド `nginx:apline`となり、このコンテナの詳細な説明は、Docker Hub https://hub.docker.com/_/nginx/ にあります。 


~~~deploy.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 6
  template:
    metadata:
      labels:
        app: nginx-web
    spec:
      containers:
      - name: nginx-container
        image: nginx:alpine
        ports:
        - containerPort: 80
~~~


前述のYAMLファイルを利用して、nginxのサーバーを起動します。

~~~マスターノードShell
vagrant@node-1:/vagrant/yaml$ kubectl create -f deploy.yaml 
deployment.extensions "nginx-deployment" created
~~~



### デプロイメントの結果確認

上記のコマンドが成功すれば、デプロイメント nginx-deployment の配下に、６個のポッドが作成されます。 この確認のために、次のコマンドを実行します。


~~~マスターノードShell
vagrant@node-1:/vagrant/yaml$ kubectl get deployment
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   6         6         6            6           30s
~~~

次に、ワーカーノードに均等に分散配置されている事を確認します。 NODE列を見れば node-2, node-3へ 3個づつ配置されている事が確認できます。

~~~マスターノードShell
vagrant@node-1:/vagrant/yaml$ kubectl get po -o wide
NAME                                READY     STATUS    RESTARTS   AGE       IP           NODE
nginx-deployment-69878cbf8d-2xtqb   1/1       Running   0          34s       10.244.1.5   node-2
nginx-deployment-69878cbf8d-4rhwr   1/1       Running   0          34s       10.244.2.7   node-3
nginx-deployment-69878cbf8d-djv9k   1/1       Running   0          34s       10.244.2.6   node-3
nginx-deployment-69878cbf8d-m7flc   1/1       Running   0          34s       10.244.1.6   node-2
nginx-deployment-69878cbf8d-mkp6z   1/1       Running   0          34s       10.244.1.7   node-2
nginx-deployment-69878cbf8d-r7nsh   1/1       Running   0          34s       10.244.2.5   node-3
~~~



## ポッドへのアクセステスト

これらのポッドは、ポッドネットワーク上に配置されており、k8sクラスタの外部からアクセスする事はできません。そこで、ポッドネットワーク上に対話型のポッドを配置して、それぞれのnginxのポッドへアクセスして、動作を確認します。





### テスト用ポッドの起動

対話型として利用するコンテナは、busybox https://store.docker.com/images/busybox を利用します。コンテナサイズは、1.2MBと軽量で ping, wget, nslookupなどが同梱されており、アクセステストに適したコンテナです。

~~~
vagrant@node-1:/vagrant/yaml$ kubectl run bbox --image=busybox:latest -- tail -f /dev/null
deployment.apps "bbox" created
~~~

次のコマンドで、ポッドの名前を取得します。

~~~
vagrant@node-1:~$ kubectl get po -o wide
NAME                                READY     STATUS    RESTARTS   AGE       IP           NODE
bbox-7ff8d695b4-mprbz               1/1       Running   0          12m       10.244.2.8   node-3
~~~


### ポッドのコンテナへのログイン

コンテナ１個だけのポッドなので、次のポッド名を指定する事で、ログインする事ができます。 このコマンドのオプションは、`kubeclt exec -it <ポッド名> <コマンド>` の様になっています。

~~~
vagrant@node-1:~$ kubectl exec -it bbox-7ff8d695b4-mprbz sh
/ # 
~~~


### 各ポッドへのアクセステスト

busyboxには、curlが入っていないので、wgetを利用します。 node-2とnode-3に配置されたポッドから各１個を選んでアクセステストを実施します。もしも、ポッドネットワークがノードを跨って構成できていなければ、このbusyboxのポッドが配置された node-3 のnginxポッドへはアクセスは出来るが、node-2 へ配置されたポッドとはアクセスできない結果となります。


![スクリーンショット 2018-05-14 22.34.05.png](https://qiita-image-store.s3.amazonaws.com/0/42829/fd917cc8-e5d2-013c-5beb-b146432c21b1.png "スクリーンショット 2018-05-14 22.34.05.png")



#### node-2 へ配置されたポッドへのアクセス

~~~ポッドのシェル
/ # wget -q -O - http://10.244.1.5/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
省略
~~~

#### node-3 へ配置されたポッドへのアクセス

~~~ポッドのシェル
/ # wget -q -O - http://10.244.2.5/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
~~~

上記の結果から、ノードを跨るポッドネットワークの構成に成功している事が確認できました。



## サービス ClusterIP

nginxのポッド群を代表するクラスタIPのサービスを設定して、アクセスのテストを実施します。 このサービスを定義するためのYAMLファイルは、次の様になります。  先にデプロイしたデプロイメントとの対応づけは、`spec.selector:` の値フィールド `app: nginx-web` が一致するラベルを持ったデプロイメントに対応します。 




~~~service_cluster_ip.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-cluster-ip-service
spec:
  type: ClusterIP
  selector:
    app: nginx-web
  ports:
  - protocol: TCP
    port: 80
~~~

次のコマンドでサービスを作成します。

~~~
vagrant@node-1:/vagrant/yaml$ kubectl create -f service_cluster_ip.yaml 
service "nginx-cluster-ip-service" created
~~~

デプロイの結果、10.244.220.233 にクラスタIPが作成され、セレクターが、app=nginx-webとなっています。

~~~
vagrant@node-1:/vagrant/yaml$ kubectl get svc -o wide
NAME                       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE       SELECTOR
省略
nginx-cluster-ip-service   ClusterIP   10.244.220.233   <none>        80/TCP    9s        app=nginx-web
~~~



### ClusterIP サービスへのアクセス

ClusterサービスIPは、Kube-Proxyによって、セレクターで連携するポッドに均等にアクセスが分散されます。 このクラスタIPをターゲットにして、アクセスして結果が返させる事を確認します。 連続してアクセスして、node-2,node-3のノードからそれぞれ返ってくる事を確認します。 もし、何度かに一回アクセスが失敗する様な事があれば、ポッドネットワークの疎通に問題があるかもしれません。その時は、前章の各ポッドのアクセスを確認します。



![スクリーンショット 2018-04-15 22.28.54.png](https://qiita-image-store.s3.amazonaws.com/0/42829/21436051-6c2e-d570-4341-36251b5d84cb.png "スクリーンショット 2018-04-15 22.28.54.png")




~~~ポッドのシェル
/ # wget -q -O - http://10.244.220.233/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
省略
~~~

DNS名でのアクセスを試みましたが、現在の設定だけでは、サービス名がkube-dnsへ登録されておらず、アドレスを解決できませんでした。


## サービス NodePort

k8sクラスタの外部からアクセスためにNodePortのサービスを定義して、アクセステストを実施します。 `spec.type: NodePort` を指定すると、各ノードのIPアドレスの spec.ports.nodeport で指定するポート番号に、サービスが開きます。　このnodePortは、Kube-Proxyによって、均一にアクセスが分散されます。


~~~service_nodeport.yaml 
apiVersion: v1
kind: Service
metadata:
  name: nginx-nodeport-service
spec:
  type: NodePort
  selector:
    app: nginx-web
  ports:
  - protocol: TCP
    port: 80
    nodePort: 31514
~~~

上記のYAMLファイルを適用すると、ワーカーノードのIPアドレスに `nodePort: 31514` で指定される TCP 31514が開きます。内部に対しては Kube-Proxyで分散して 80番ポートへ中継します。

![スクリーンショット 2018-05-14 22.37.39.png](https://qiita-image-store.s3.amazonaws.com/0/42829/bc5e3107-d675-92c6-bf0f-eefc95bde667.png "スクリーンショット 2018-05-14 22.37.39.png")



### NodePortサービスの作成

前述のYAMLファイルを適応して、NodePortサービスを作成します。

~~~
vagrant@node-1:/vagrant/yaml$ kubectl create -f service_nodeport.yaml 
service "nginx-nodeport-service" created
vagrant@node-1:/vagrant/yaml$ kubectl get svc -o wide
NAME                       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE       SELECTOR
省略
nginx-nodeport-service     NodePort    10.244.60.160    <none>        80:31514/TCP   8s        app=nginx-web
~~~


### ノードIPからのアクセス

MacOSのターミナルから各ノードのIPアドレスとポート番号に向けて、curlでアクセスして結果を確認します。 ワーカーノードだけでなく、マスターノードにもkubeproxyが動作していますから、全てのノードで、Nginxのポッドへアクセスできる事が確認できました。

#### node-1 のNodePortへのアクセス

~~~MacOSターミナル
imac:~ maho$ curl http://172.16.20.11:31514/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
~~~


#### node-2 のNodePortへのアクセス

~~~MacOSターミナル
imac:~ maho$ curl http://172.16.20.12:31514/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
~~~


#### node-3 のNodePortへのアクセス

~~~MacOSターミナル
imac:~ maho$ curl http://172.16.20.13:31514/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
~~~


#### ポッドネットワークからのアクセス

NodePortを作成すると、クラスタIPも開設しますので、ポッドからアクセスを試してみました。 

~~~BusyBoxのシェル
vagrant@node-1:/vagrant/yaml$ kubectl exec -it bbox-7ff8d695b4-mprbz sh
/ # wget -q -O - http://10.244.60.160/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
~~~



# まとめ

Vagrantの仮想サーバー環境で、K8s v1.10が動作する事が確認できました。 パブリッククラウドのKubernetesでは kubeadmを利用して、内部構造に関わる部分を触って学ぶ事ができないですし、minikubeでは限定的になります。 そして、ラズパイを使っても、部品代や場所など、そこそこコストがかかりますから、無料でパソコンの仮想環境上で、クラスタが検証できるのは、本当に助かりますね。

パブリック・クラウドのサービスやK8sをコアにしたプライベート・クラウドのソフトウェア製品では、Kubernetesのコア部分だけでなく、コンテナ・リポジトリ、Grafana, Prometheus, ElasticSearch, Kibana, Helm, ストレージインタフェース、ロードバランサーインタフェースなど、運用するために必要な全てが、必要最小限の操作で、自動的に構成されるので、改めて便利さが良くわかりました。