10
17

More than 3 years have passed since last update.

Kubernetes 1.16をオンプレ環境にインストールしてみた

Posted at

Kubernetes v1.16.0がリリースされて1週間ほど経ちましたが、手元の環境でインストールしてみました。折角なので記事として残してみようと思います。

構成

パブリッククラウド上で簡単にKubernetesを利用できる昨今ではありますが、それだとあまり面白くないので、家のPC上に仮想マシンを建てて作りたいと思います。

クラスタ構成

今回は折角なのでMulti-Master構成で構築します。複数ノードにしても物理ホストが1台では意味ないよねとか言う天使の囁きは無視します。

Masterノード: 3台
Workerノード: 3台

今回のレベル感であればSingle-Master構成やMaster/Worker同居の完全1台構成に変更することも難しくはないので、補足的に随所に記載しておきます (v1.15.3の時の実績からなので、v1.16.0で手順が変わっていたらすみません)。

ソフトウェア構成

今回はCNIにflannelを利用します。Workerが複数あるのにサービスの外部公開がNodePortでは残念過ぎるので、MetalLBを利用してLoadBalancerを利用できるようにします。
本来であればMasterノードはHAProxyなどを利用してロードバランスされるべきですが、今回は(面倒だったので)keepaliveでAct-Stb-Stb状態で構築します。
また本当はDynamic Volume Provisioningを利用できるようにGlusterFSも導入しようと思ったのですが、時間の都合により割愛します。その内、別記事で記載するかもしれません。

Module Version
CentOS 7.6
Docker 19.03.2
kubelet 1.16.0
kubeadm 1.16.0
kubectl 1.16.0
keepalive 1.3.5
flannel v0.11.0
MetalLB 0.8.1

サイジング

作って動けば良いレベルなので、ホスト側のリソース量と相談して決めました。物理ホスト側は5〜6年くらい前に作った自作PCなので、そんなに高いスペックは必要ありません。ちなみに公式の推奨スペックはこちらです。

  • Master/Worker共通
    • CPU: 2コア
    • メモリ: 4GB
    • ディスク: 64GB

ちなみにv1.15.3ですがMaster/Worker同居でもこのスペックでそこそこ動きます。

ネットワーク構成

IPアドレス構成は以下の通りです。ホスト名の連番がバラバラなのは我が家の他のクラスタとの兼ね合いなので気にしないでください。k8s-master02を最初に構築するMasterとして、k8s-master*でMaserクラスタを構築します。k8s-worker*がWorkerノード用のVMです。
flannelのIPはCNIによって要件が変わるので、他のCNIを利用する場合はドキュメントを参照して変更してください。

役割 IPアドレス
k8s-master02 192.168.2.15
k8s-master03 192.168.2.16
k8s-master04 192.168.2.17
MasterノードのクラスタIP 192.168.2.18
k8s-worker04 192.168.2.24
k8s-worker05 192.168.2.25
k8s-worker06 192.168.2.26
flannelのIP 10.244.0.0/16
ClusterIP 10.96.0.0/12
LoadBalancerIP 192.168.2.64/27

インストール

CentOSのインストール (k8s-master02)

まずは最初のMasterノードとなるk8s-master02のCentOSをminimalインストールします。他のノードはk8s-master02をクローンして作成するので、この時点では構築不要です。インストール時にネットワーク設定とユーザ作成を行っています。ユーザはrltnm7というユーザにしました。

OSの設定 (k8s-master02)

既にSSH出来るはずなので、SSHで外部から繋いで設定しています。(面倒だったので)しばらくはrootユーザで設定しています。ユーザ設定などは各自必要に応じて設定してください。

firewallを無効化します。真面目にやる時はちゃんと設定しましょう。

systemctl stop firewalld
systemctl disable firewalld

selinuxも無効化します。

setenforce 0
sed -i "s/SELINUX=enforcing/SELINUX=disabled/" /etc/selinux/config

swapを無効化します。これは必須です。やらないと設定時のpreflightで失敗します。

swapoff -a
sed -i "s/\/dev\/mapper\/centos-swap/#\/dev\/mapper\/centos-swap/" /etc/fstab

hostsの追加をします。ちゃんとDNSがある環境であれば不要な設定かと思います。

cat << EOF >> /etc/hosts
192.168.2.15 k8s-master02
192.168.2.16 k8s-master03
192.168.2.17 k8s-master04
192.168.2.24 k8s-worker04
192.168.2.25 k8s-worker05
192.168.2.26 k8s-worker06
EOF

yumのアップデートもしておきます。OSのイメージが古いので300以上updateが走りました。

yum update -y
yum upgrade -y

Dockerのインストール (k8s-master02)

DockerのCommunity Editionを使います。yum install dockerするとEnterprise Edtionがインストールされるので気をつけてください。エディションの違いはこちらを参照してください。

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 install -y docker-ce

rltnm7ユーザをdockerグループに追加します。これをしないとrltnm7ユーザがdockerコマンドを実行する際に毎回sudoをしなければなりません。Kubernetesを利用する上で必須の設定ではありませんが、トラブルシュートする際に設定されていると便利です。

usermod -g docker rltnm7

こちらの設定も必須ではありませんが、ローカル環境にPrivate Registryを構築し、かつセキュリティ設定が面倒でやらない場合に設定する必要がある項目です。このRegistryはHTTPSではなくHTTPでアクセスしてねっていう設定です。Docker Hubしか使わないのであれば不要です。

cat << EOF > /etc/sysconfig/docker
INSECURE_REGISTRY='--insecure-registry registry:5000'
EOF

最後にDockerを起動します。

systemctl start docker
systemctl enable docker

Kubernetesのインストール (k8s-master02)

インストール前にネットワーク設定をしておきます。

cat << EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF

sysctl --system

ついにKubernetesをインストールします。Kubernetes用のyumレポジトリを追加して、インストールします。あえてバージョン指定しましたが、最新版を入れるのであれば指定は不要です。

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
EOF

yum install -y kubelet-1.16.0-0.x86_64 kubeadm-1.16.0-0.x86_64 kubectl-1.16.0-0.x86_64

最後にkubeletの自動起動設定を入れて、シャットダウンします。すぐに落としてしまうので起動はしていませんが、systemctl start kubeletして動作確認してみても良いかもしれませんね。Kubenetes自体はまだ機能していないので何が見れるか分かりませんが。。。

systemctl enable kubelet
shutdown -h now

VMのクローン (k8s-master02以外全て)

ここまで構築したVMをクローンします。Master用に2つ、Worker用に3つクローンします。Master用はもう少しだけ共通の設定をしますが、本当に少しなのでこの時点でクローンしてしまいます。クローン手順は省略します。VirtualBoxであれば右クリックでクローン選ぶだけです。
もちろんSingle-Masterにする場合はMasterのクローンは不要ですし、1台構成にするのであればWorkerのクローンも不要です。

各VMの設定 (k8s-master02以外全て)

クローンしたVMのOS設定をしていきます。最初はIP競合を起こしてしまうので、各VMを1台ずつ起動し、以下の設定をして落としていきます。ホスト名/IPアドレスは対象マシンごとに変更してください。

sed -i "s/k8s-master02/k8s-master03/" /etc/hostname
sed -i "s/192.168.2.15/192.168.2.16/" /etc/sysconfig/network-scripts/ifcfg-enp0s3
shutdown -h now

Keepaliveの設定 (全てのMasterノード)

Masterノードに仮想IPを設定します。kubectlなどが接続するIPアドレスになります。priorityは優先順位が高い順に変更してください。私はk8s-master02, 03, 04の順に100, 90, 80としました。
Single-Master/1台構成の場合は不要な設定です。

yum install -y keepalived
mv /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.org

cat <<EOF > /etc/keepalived/keepalived.conf
global_defs {
}

vrrp_instance KUBE_APISERVER {
    state MASTER
    interface enp0s3
    virtual_router_id 51
    priority 100
    advert_int 1
    virtual_ipaddress {
        192.168.2.18/24
    }
}
EOF

systemctl start keepalived
systemctl enable keepalived

最初のMasterの設定 (k8s-master02)

ようやくMasterノードの構築に入ります。kubeadm initコマンドを実行するだけの簡単なお仕事です。
--apiserver-advertise-addressおよび--control-plane-endpointに対してはkeepaliveで設定したクラスタIPを渡します。--pod-network-cidrにはflannelのIPを渡します。こちらはCNIによって異なるので利用するCNIのドキュメントを参照してください。あるいは不要な場合もあるかもしれません。--service-cidrはClusterIPのIPアドレス範囲になります。デフォルトで10.96.0.0/12になるので、省略しても大丈夫なはずです(試したことないですが)。変えたい場合は設定してください。
またSingle-Masterや1台構成の場合は--control-plane-endpointの引数が不要になります。

kubeadm init --apiserver-advertise-address 192.168.2.18  --control-plane-endpoint 192.168.2.18 --pod-network-cidr 10.244.0.0/16 --service-cidr 10.96.0.0/12

最初にpreflightでシステム要件の確認をされます。何か足りなければここでエラーが出てインストールが失敗します。
kubeadm initの結果にkubeadm joinコマンドが2つ(--control-plane-endpointを指定していない場合は1つ)記載されているのでメモしておいてください

Master間の鍵交換

Kubernetesの鍵情報を送りつけるために、まずはSSHの鍵を作成し、公開鍵を送ります。これはk8s-master02でのみ実行します。

ssh-keygen -t rsa -b 4096 -C "admin@example.com"

USER=rltnm7
scp .ssh/id_rsa.pub "${USER}"@192.168.2.16:
scp .ssh/id_rsa.pub "${USER}"@192.168.2.17:

受け取り手側で公開鍵を受け入れます。これはk8s-master03k8s-master04の一般ユーザrltnm7で実行します。全部rootでやり取りでも良かったのですが、何となくrootでダイレクトにSSHするのは気が引けたので。。。

mkdir .ssh
chmod 700 .ssh
cd .ssh
cat ~/id_dsa.pub >> authorized_keys
chmod 600 authorized_keys

sudo systemctl restart sshd

k8s-master02に戻ってSCPでファイルを送付します。一般ユーザだとファイルにアクセス権がないのでrootで実行します。実行する前にSSHでパスワードなしでアクセスできるか試しておいた方が幸せになれると思います。SSHの鍵交換は最悪やらなくても、下記SCPの際に(9*台数)回パスワード入力すれば大丈夫です笑

USER=rltnm7
CONTROL_PLANE_IPS="192.168.2.16 192.168.2.17"
for host in ${CONTROL_PLANE_IPS}; do
    scp /etc/kubernetes/pki/ca.crt "${USER}"@$host:
    scp /etc/kubernetes/pki/ca.key "${USER}"@$host:
    scp /etc/kubernetes/pki/sa.key "${USER}"@$host:
    scp /etc/kubernetes/pki/sa.pub "${USER}"@$host:
    scp /etc/kubernetes/pki/front-proxy-ca.crt "${USER}"@$host:
    scp /etc/kubernetes/pki/front-proxy-ca.key "${USER}"@$host:
    scp /etc/kubernetes/pki/etcd/ca.crt "${USER}"@$host:etcd-ca.crt
    scp /etc/kubernetes/pki/etcd/ca.key "${USER}"@$host:etcd-ca.key
    scp /etc/kubernetes/admin.conf "${USER}"@$host:
done

kubectlの設定

kubectlを利用できるように設定します。ここからはrootユーザではなく、一般ユーザで設定を行います。今回は既に作成済みのrltnm7を利用します。
この時点ではk8s-master02でしか出来ませんが、後で他のMasterでもやっておいた方が良いと思います。

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

さて、kubectlコマンドが利用できるようになったので、試しにいくつか実行してみましょう。

kubectl get nodes

k8s-master02NotReady状態になっていると思います。

kubectl get pod --namespace kube-system

CoreDNSがPending状態で、その他のPodはRunningになっていると思います。まだなっていなければ少し待ってからもう一度確認してみてください。CoreDNSはCNIを展開しない限りは永遠にPendingのままなので先に進んでください。

CNIの設定 (k8s-master02)

クラスタ上へflannelを展開します。github上のmanifestファイルを直接指定しています。
注意点としてKubernetes v1.16.0からDaemonSetのAPIがapps/v1beta1からapps/v1へ変更になっているので、1.15以前に使えていたmanifestファイルは使えません。

kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/2140ac876ef134e0ed5af15c65e414cf26827915/Documentation/kube-flannel.yml

参考までにv1.15.3の場合は、以下のコミットハッシュのファイルで展開していました。(最初にこちらを実行して失敗したのはナイショ)

kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/62e44c867a2846fefb68bd5f178daf4da3095ccb/Documentation/kube-flannel.yml

環境に依るとも思いますが、5〜10分くらい待ってからもう一度kubectl get pod --namespace kube-systemを実行すると全てのPodがRunningになっていると思います。たまにRestartしてたりしますが、その内収束するので焦らずお待ち下さい。
待っている間は折角なのでv1.16.0からの新機能kubectl get node -n kube-system -w --output-watch-eventsなんか使って、Podの作成状況なんか眺めてみると面白いかもしれませんね。
最後にkubectl get nodesを実行してk8s-master02ReadyになっていればOKです。

さて、ここまで来ればk8s-master02は完成です。

残りのMasterの設定 (k8s-master03, k8s-master04)

先程SCPした鍵を所定の場所に移動します。こちらはrootで実行するか、sudoでやってください。
Single-Master/1台構成の場合はこの説は飛ばしてしまって大丈夫です。

USER=rltnm7
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

最後に、k8s-master02にて構成したクラスタに参加します。kubeadm initした際にメモしたkubeadm joinコマンドを実行します。Masterでは--control-plane引数が付いている方を実行します。付いていない方を実行してしまうと、ただのWorkerになってしまうのでご注意ください。

sudo kubeadm join 192.168.2.18:6443 --token lpm579.cxre2a1xzucdp547 --discovery-token-ca-cert-hash sha256:6c2d80e1b4d26f12c48ba83b8a7b9d502a0e3877dc0cd29130b562925588e1be --control-plane

k8s-master03k8s-master04でそれぞれ完了したら、kubectl get nodesを実行し、全てのNodeがReadyになればMulti-Master構成は完成です。前述の通り、各Masterでkubectlの設定をしておくと良いと思います。続いてWorkerの設定を行います。

Workerの設定 (全てのWorker)

1台構成の場合はこの節も飛ばして大丈夫です。代わりにMaster/Worker同居1台構成の場合を実施してください。

まずはIPv4の転送設定を行います。

sudo sh -c "echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.d/k8s.conf"
sudo sysctl --system

次にk8s-master02にて構成したクラスタに参加します。kubeadm initした際にメモしたkubeadm joinコマンドを実行します。Workerでは--control-plane引数が付いていない方を実行します。

sudo kubeadm join 192.168.2.18:6443 --token lpm579.cxre2a1xzucdp547 --discovery-token-ca-cert-hash sha256:6c2d80e1b4d26f12c48ba83b8a7b9d502a0e3877dc0cd29130b562925588e1be

kubectl get nodesを実行し、対象のノードがReadyになったらOKです。全てのWorkerノードの追加が完了したらKubernetesクラスタの完成です。

Master/Worker同居1台構成の場合

Master上では通常、ユーザが作成したコンテナは実行できないので、Master/Workerを同居させる場合には追加の設定が必要になります。ちなみに同居させることは、あまり推奨できる構成ではないので開発や検証程度にしておいた方が良いでしょう。
MasterとWorkerを分けた構成にする場合は不要な設定です。

まずはIPv4の転送設定を行います。

sudo sh -c "echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.d/k8s.conf"
sudo sysctl --system

次にMasterノードでもコンテナを実行できるようにします。

kubectl taint node k8s-master02 node-role.kubernetes.io/master:NoSchedule-

これでMaster/Worker同居のクラスタが完成しました。

MetalLBの導入

さて、ここまでで無事にKubernetesクラスタは完成し、Kubernetesとして最低限利用できるようにはなりました。しかしまだLoadBalancerサービスは利用できません。そこでクラスタ上にMetalLBをデプロイしてLoadBalancerサービスを利用できるようにします。

まずはデプロイします。

kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.8.1/manifests/metallb.yaml

MetalLBにはL2モードとBGPモードががありますが、今回はL2モードで実行させます。違いはドキュメントを参照してください。addressesに設定されているIPアドレスがLoadBalancerExternalIPとして設定されるIPアドレス範囲になります。

cat << EOF > metallb-l2.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: config
  namespace: metallb-system
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - 192.168.2.64/27
EOF

kubectl apply -f metallb-l2.yaml

その他

その他、IngressやIstio、Dynamic Provisioningなどで遊びたいところではありますが、今回の構築範囲としてはここまでとし、またいつか別記事にしたいと思います。

動作確認

だいぶ長くなっていますが、作って終わりも面白くないので、少し遊んでみたいと思います。

コンテナのデプロイ

とても雑にtype: LoadBalancerのサービスとreplica: 3nginxDeploymentを展開したいと思います。ただのnginxだと面白くないのでimage: stenote/nginx-hostname:latestとして、ホスト名が出るようにします。

cat << EOF > nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: stenote/nginx-hostname:latest
        ports:
        - name: http
          containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  type: LoadBalancer
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
EOF

kubectl apply -f nginx.yaml
kubectl get pod -o wide -w --output-watch-events

全てRunningになったのを確認したら、以下を実行します。IPはkubectl get svcを実行して、nginxExternal-IPを指定してください。

for i in `seq 0 100`; do curl http://192.168.2.64; done

3台に分散されてホスト名 (= Pod名) が表示されると思います。
最後に掃除しておきます。

kubectl delete -f nginx.yaml
rm -f nginx.yaml

Multi-Masterの動作を見る

特に記載できる内容もないのですが、、、
通常時はk8s-master02をAct機として動作していますが、k8s-master02をシャットダウンしても、k8s-master03をAct機として動作するのが分かると思います。
ただし今回はkeepaliveでのIP付け替えという雑なやり方しかしていないので、特に復旧時の切り替えには比較的長めの接続断が発生してしまいます。きれいな切り替えがやりたい場合はHAProxyを利用するなど、ロードバランス機能を持たせるべきでしょう。

感想

やはり自分で構築するとKubernetesへの理解が深まりますね。パブリッククラウドでは手軽に利用できる反面、上っ面だけでも使えてしまうので、こう言った自分の手を動かす機会も積極的に持ちたいですね。
Ephemeral Containersとかも遊びたかったのですが、気力が尽きたので終わります。メモリ24GBとか普通に邪魔なので、このクラスタは消して1台構成で作り直して遊んでみたいと思います。
Kubernetesインストール系の記事も最近はだいぶ多くなっているので、今更需要があるのかも分かりませんが、どなたかの一助となれば幸いです。今後も色々Kubernetesだけでなく、色々な記事を書いてみようと思います。

10
17
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
10
17