4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Vagrant + KVMでHAクラスタのkubernetesを構築してみる

Last updated at Posted at 2019-08-22

去年まではオンプレミスのOpenShiftで、GitLabやJenkinsなどなどの開発ツールを手軽に動かしていた。kubernetesを生で使うよりは、HA Proxyを使ったルータ機能がセットになっているなど構築や運用が楽だと思っていた。

しかし、kubernetesの進歩は早く、OpenShiftが使うkubernetesのバージョンがちょっと古かったり、ちょっとしたトラブルでOpenShiftのドキュメントを調べて判らなければkubernetesのドキュメントを調べるというのも手間だったりなので、今回は日本語の書籍も充実してきたkubernetesで構築してみることにした。

  • 構築手順は主に以下を参考にしました。
  • なお、以下は、HAクラスタとして起動してhello worldアプリケーションの動作を確認したレベルの構築手順です。まだOpenShiftのルータ機能相当や、永続化ストレージも用意していないので、今後、大きな落とし穴がある可能性があるので、注意してください。
    • (2019/8/26追記)ルータや永続化の対応するなかで、まだ勘違いしているかもしませんが、おおまかに以下の見直しを行いました。
      • 仮想サーバにはデフォルトディスクサイズが40Gだったので、ストレージに追加はやめました。
      • vagrant-libvirtが使用する管理ネットワークとか、ingressにMetalLBに割り当てるアドレスプールとか、Heketiは固定IPアドレスしか設定できないとかあって、固定IPを割り当てるようにしました。
      • 永続化にGlusterFSを使用することにし、物理ホスト構築時にディスク領域を50G分空けておきました。

今回の到達目標

物理ホスト上に仮想サーバを4台作成し、この4台をマスタノードとワーカーノードとしてKubernetes HAクラスタにします。

ソフト バージョン 備考
CentOS 7.6
Kubernetes 1.15
Docker 18.09 Kubernetes 1.15がサポートしている最新バージョン。

物理ホストの構築

まずCentOS 7.6をVirtualization Hostでインストール。
次に仮想サーバを構築するにあたり、、、

  • Virtual Boxは手軽そうだが性能的なオーバヘッドが大きそう。
  • VMwareはアカウント作成とかがいろいろと面倒。
    ということで、最近はあまり記事を見かけないけど、そこは不具合は枯れている捉えて、性能も良さそうなKVMを選択しました。

で、これ以降の作業でトライ&エラーを繰り返すことが想定されることから、リトライ時間の短縮を図るため、Vagrant + KVMで構築することにしました。

VagrantとKVM連携プラグインのインストール
# yum install -y qemu-kvm libvirt libvirt-devel gcc patch
# systemctl start libvirtd
# systemctl enable libvirtd
# yum install -y https://releases.hashicorp.com/vagrant/2.2.5/vagrant_2.2.5_x86_64.rpm
# vagrant plugin install vagrant-libvirt
仮想サーバを作成するスクリプト
Vagrantfile
Vagrant.configure(2) do |config|
  config.ssh.insert_key = false

  $nodes = [
    { name: "k8s-master1", cpu: "2", memory: "4096", ip: "192.168.122.2"},
    { name: "k8s-master2", cpu: "2", memory: "4096", ip: "192.168.122.3"},
    { name: "k8s-worker1", cpu: "6", memory: "8192", ip: "192.168.122.10"},
    { name: "k8s-worker2", cpu: "6", memory: "8192", ip: "192.168.122.11"}
  ]

  $nodes.each do |param|
    config.vm.define param[:name] do |node|
      node.vm.box = "centos/7"
      node.vm.hostname = param[:name]

      node.vm.provider :libvirt do |v|
        v.cpus = param[:cpu]
        v.memory = param[:memory]
      end

      node.vm.network :private_network, ip: param[:ip], :libvirt__network_name => "default"

      node.vm.provision :shell do |s|
        ssh_insecure_key = File.read("#{Dir.home}/.vagrant.d/insecure_private_key")
        s.inline = <<-SHELL
        mkdir -p /root/.ssh
        echo "#{ssh_insecure_key}" >> /root/.ssh/id_rsa
        chmod 400 /root/.ssh/id_rsa
        cp /home/vagrant/.ssh/authorized_keys /root/.ssh/authorized_keys

        echo "#{ssh_insecure_key}" >> /home/vagrant/.ssh/id_rsa
        chown vagrant /home/vagrant/.ssh/id_rsa
        chmod 400 /home/vagrant/.ssh/id_rsa
        SHELL
      end
      node.vm.provision :shell, inline: $script
    end
  end
end

$script = <<-SCRIPT
# スワップの無効化, ブリッジでのIPv4/IPv6トラフィックを有効
swapoff -a
cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
vm.swappiness = 0
EOF
sysctl --system

# SELinuxの無効化
setenforce 0
sed -i 's/^SELINUX=.*/SELINUX=disabled/g' /etc/selinux/config

# dockerインストール
yum remove docker docker-client docker-client-latest docker-common \
           docker-latest docker-latest-logrotate docker-logrotate docker-engine
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum makecache fast
yum update -y
yum install -y docker-ce-18.09.8-3.el7.x86_64 docker-ce-cli-18.09.8-3.el7.x86_64 containerd.io
mkdir -p /etc/docker
cat <<EOF > /etc/docker/daemon.json
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2",
  "storage-opts": ["overlay2.override_kernel_check=true"]
}
EOF
systemctl enable docker
systemctl daemon-reload
systemctl start docker

# kubectl, kubelet, kubeadmインストール
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
exclude=kube*
EOF
yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes

# GlusterFSクライアントのインストール
yum install -y centos-release-gluster
yum install -y glusterfs-fuse
SCRIPT
  • 仮想サーバのCPU, メモリ, ディスクサイズは適当な値です。
  • Vagrantで構築した仮想サーバは、パスワード認証が無効化されており、秘密鍵を使ったssh接続しかできない。Kubernetesの構築手順では各ノード間をsshで接続できる必要があるそうなので、パスワード認証を有効にするとか各仮想サーバの秘密鍵を交換するかしなければならない。
    そこで「config.ssh.insert_key = false」により仮想サーバ毎に秘密鍵を生成されるのを無効化し、Vagrantが仮想サーバ構築時に使用している物理ホスト上の秘密鍵(${HOME}/.vagrant.d/insecure_private_key)を、各仮想サーバの秘密鍵としてしまう。各仮想サーバに公開鍵は登録されているので、これにより各仮想サーバ同士でssh接続が可能になる。
仮想サーバを作成
# vagrant up

また、仮想サーバで使用しているDNSを物理ホストのresolve.confに登録。
(2019/8/26追記)仮想サーバのアドレスを物理ホストのhostsファイルに追記。

# cat <<EOF >> /etc/hosts
192.168.122.2 k8s-master1
192.168.122.3 k8s-master2
192.168.122.10 k8s-worker1
192.168.122.11 k8s-worker2
EOF

これで物理ホストから仮想サーバの名前解決ができるようになる。

kube-apiserver用ロードバランサーを作成
# cat <<EOF > /etc/yum.repos.d/nginx.repo
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/7/\$basearch/
gpgcheck=0
enabled=1
EOF
# yum install -y nginx
# yum install -y policycoreutils-python
# semanage port -a -t http_port_t -p tcp 6443
# firewall-cmd --add-port=6443/tcp --zone=public --permanent
# firewall-cmd --reload
# cat <<EOF >> /etc/nginx/nginx.conf
stream {
    upstream backend {
        least_conn;
        server k8s-master1:6443;
        server k8s-master2:6443;
    }
    server {
        listen     6443;
        proxy_pass backend;
    }
}
EOF
# systemctl enable nginx
# systemctl start nginx
  • nginxに慣れてなくて、ここは比較的ハマりました。Kubernetes実践ガイドにある設定例を真似して/etc/nginx/conf.d配下に記述したのですが、これだとデフォルトのnginx.confだとhttpの設定として扱われてしまうんですね。そこでnginx.confで直接stream {} で括って記述しました。
  • 物理ホストですがselinuxやfirewalldを無効化してません。なのでkube-apiserverと同じポートを使えるようにしています。

マスタノード1の構築

ここからはマスタノード1にログインして作業します。

Kubernetesの初期構築

CNIはcalicoを使うとして、calicoのデフォルトネットワークである192.168.0.0/16だとKVM側(192.168..121)と被ってしまうので、calico側を変更しました。

# cat <<EOF > kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
kubernetesVersion: stable
apiServer:
  certSANs:
  - "api.kubernetes.local"
controlPlaneEndpoint: "api.kubernetes.local:6443"
networking:
  podSubnet: "192.168.128.0/17"
EOF
# kubeadm init --config=kubeadm-config.yaml
... 中略 ...
Your Kubernetes control-plane 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 control-plane nodes by copying certificate authorities 
and service account keys on each node and then running the following as root:

  kubeadm join api.kubernetes.local:6443 --token nogauk.ges57vw6ilmj8k9t \
    --discovery-token-ca-cert-hash sha256:c40c63e6c1b973c6918506b9bf7a6dd76d38d0a50ea2f9106ca9b9459c754159 \
    --control-plane 	  

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join api.kubernetes.local:6443 --token nogauk.ges57vw6ilmj8k9t \
    --discovery-token-ca-cert-hash sha256:c40c63e6c1b973c6918506b9bf7a6dd76d38d0a50ea2f9106ca9b9459c754159 
# mkdir -p $HOME/.kube
# sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
# sudo chown $(id -u):$(id -g) $HOME/.kube/config
# curl -L -O https://docs.projectcalico.org/v3.8/manifests/calico.yaml
# sed -i 's:192.168.0.0/16:192.168.128.0/17:g' ./calico.yaml
# kubectl apply -f ./calico.yaml
  • kubeadm-config.yamlに指定したapi.kubernetes.localは物理ホストのホスト名。podSubnetがcalicoに使ってもらうネットワーク。
  • kubeadm initが成功すると、クラスタを利用するための初期手順、マスタノードをクラスタに追加するコマンド、ワーカーノードを追加するコマンドが表示されるのでメモしておく。
  • 初期手順を実行したら、calicoのマニフェストをダウンロードして、calicoが使うネットワークを修正して適用。
マスタノード1の証明書をマスタノード2にコピー

Vagrantfileで秘密鍵はコピーしていたけど、それはvagrantユーザ用で、でもvagrantユーザでは証明書にアクセスできない。そこでrootユーザに秘密鍵をコピーしてから証明書の配布を実施。

# cp /home/vagrant/.ssh/id_rsa ~/.ssh
# USER=vagrant
# CONTROL_PLANE_IPS="k8s-master2"
# for host in ${CONTROL_PLANE_IPS}; do
     sudo scp /etc/kubernetes/pki/ca.crt ${USER}@$host:;
     sudo scp /etc/kubernetes/pki/ca.key ${USER}@$host:;
     sudo scp /etc/kubernetes/pki/sa.key ${USER}@$host:;
     sudo scp /etc/kubernetes/pki/sa.pub ${USER}@$host:;
     sudo scp /etc/kubernetes/pki/front-proxy-ca.crt ${USER}@$host:;
     sudo scp /etc/kubernetes/pki/front-proxy-ca.key ${USER}@$host:;
     sudo scp /etc/kubernetes/pki/etcd/ca.crt ${USER}@$host:etcd-ca.crt;
     sudo scp /etc/kubernetes/pki/etcd/ca.key ${USER}@$host:etcd-ca.key;
     sudo scp /etc/kubernetes/admin.conf ${USER}@$host:;
 done

マスタノード2の構築

次にマスタノード2にログインしてrootユーザで以下を実施。

証明書の適用
# USER=vagrant
# mkdir -p /etc/kubernetes/pki/etcd
# mv /home/${USER}/ca.crt /etc/kubernetes/pki/
# mv /home/${USER}/ca.key /etc/kubernetes/pki/
# mv /home/${USER}/sa.pub /etc/kubernetes/pki/
# mv /home/${USER}/sa.key /etc/kubernetes/pki/
# mv /home/${USER}/front-proxy-ca.crt /etc/kubernetes/pki/
# mv /home/${USER}/front-proxy-ca.key /etc/kubernetes/pki/
# mv /home/${USER}/etcd-ca.crt /etc/kubernetes/pki/etcd/ca.crt
# mv /home/${USER}/etcd-ca.key /etc/kubernetes/pki/etcd/ca.key
# mv /home/${USER}/admin.conf /etc/kubernetes/admin.conf
マスタノード2をクラスタに参加
#   kubeadm join api.kubernetes.local:6443 --token nogauk.ges57vw6ilmj8k9t \
    --discovery-token-ca-cert-hash sha256:c40c63e6c1b973c6918506b9bf7a6dd76d38d0a50ea2f9106ca9b9459c754159 \
    --control-plane 

※手元のログだと--control-planeだけど、ドキュメントとかだと--experimental-control-planeなので、別途、要確認。

  • (2019/8/26追記)ドキュメントだと--experimental-control-planeだけど、kubeadm initで出力されるログには --control-plane とあるので、それを使用。

ワーカーノードの構築

各ワーカーノードにログインしてrootユーザで以下を実施。

ワーカノードをクラスタに参加
# kubeadm join api.kubernetes.local:6443 --token nogauk.ges57vw6ilmj8k9t --discovery-token-ca-cert-hash sha256:c40c63e6c1b973c6918506b9bf7a6dd76d38d0a50ea2f9106ca9b9459c754159

hello worldアプリケーションの実行

マスタノード1のrootユーザで以下を実行。

# kubectl create deployment hello-node --image=gcr.io/hello-minikube-zero-install/hello-node
# kubectl expose deployment hello-node --type=LoadBalancer --port=8080

# kubectl get service
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
hello-node   LoadBalancer   10.102.47.188   <pending>     8080:32223/TCP   3h38m
kubernetes   ClusterIP      10.96.0.1       <none>        443/TCP          40h

# kubectl get pod
NAME                          READY   STATUS    RESTARTS   AGE
hello-node-55b49fb9f8-xfxgk   1/1     Running   0          3h38m
# kubectl get po/hello-node-55b49fb9f8-xfxgk -o jsonpath='{.status.hostIP}'
192.168.121.208
# kubectl get po/hello-node -o jsonpath='{.spec.ports[0].nodePort}'
32223

# curl http://192.168.121.208:32223
Hello World!
  • kubectl createでhello worldアプリケーションをデプロイ。
  • kubectl exposeでサービスを設定。ingressとかまだだから、EXTERNAL-IPはpending。なのでワーカーノードに直接curlで動作を確認しました。
4
5
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
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?