去年まではオンプレミスの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分空けておきました。
- (2019/8/26追記)ルータや永続化の対応するなかで、まだ勘違いしているかもしませんが、おおまかに以下の見直しを行いました。
今回の到達目標
物理ホスト上に仮想サーバを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
仮想サーバを作成するスクリプト
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で動作を確認しました。