明日から本気だす

データベース好きなサポートエンジニアのメモです.

VirtualBox 上の CentOS7 に k8s クラスターを構築

はじめに

CKA 受験に向け k8s の理解を深めるため、VirtualBox 上の CentOS7 3 台で K8s クラスターの構築にチャレンジした際の手順メモです。本当は Raspberry-pi を利用した物理構成で試したかったのですが金欠で断念。

物理構成

筐体は ThinkCentre M725s を利用。スペックは以下の通り。Vagrant は利用しなかった…

項目 内容
CPU AMD Ryzen 7 PRO 2700 Eight-Core Processor
Memory 24GiB
ホスト OS Windows 10 Pro
仮想 S/W Oracle VM VirtualBox 6.1

ゲスト OS 構成

k8s クラスターを構成する仮想マシンには CentOS 7.9 をインストール。CentOS を選定したのは単純に検索でヒットした情報が多かったため。CentOS 8 だと flannel が動かないといった情報がちょこちょこ確認できたので CentOS 7.9 を利用した。(あとで調べる) 仮想マシンには、NAT アダプター(enp0s3)とホストオンリーアダプター(enp0s8)をアタッチ。各ホスト名、ノードの役割、IP アドレスは以下の通り。

OS の設定

k8s クラスター構築には必須ではないが、各ノードで OS の基本的な設定は実施しておく。

ユーザ作成

root 以外のユーザを作成し、基本的にはこのユーザで作業を行う。wheel グループに所属させ sudo を許可する。

$ useradd staff -u 10002 -g wheel
$ sed -i.org '6 s/^#//' /etc/pam.d/su

タイムゾーンの変更

タイムゾーンAsia/Tokyo 以外の場合は変更する。

$ cp /etc/localtime /etc/localtime.org
$ ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

SSH 設定

PermitRootLogin no で root ユーザでの ssh 接続を禁止し、UseDNS nossh 接続時に DNS を利用しない。各ノードの作業ユーザ(staff)で鍵交換をしたら、お好みで PasswordAuthentication no もあわせて設定する。

$ sed -i.org 's/^#\(PermitRootLogin\) yes/\1 no/g; s/^#\(UseDNS\) yes/\1 no/g' /etc/ssh/sshd_config
$ sudo systemctl restart sshd

不要サービスの停止

検証環境でリソースが少ないため、使わなそうなサービスは自動起動disable にして停止しておく。今回停止したサービスは以下のとおり。

  • firewalld.service
  • auditd.service
  • mdmonitor.service
  • postfix.service
  • qemu-guest-agent.service
$ systemctl list-unit-files -t service | grep enable
$ for i in auditd.service mdmonitor.service postfix.service qemu-guest-agent.service firewalld.service; do sudo systemctl disable "$i"; sudo systemctl stop "$i"; done

Chrony の設定

お好みで時刻動機の設定も実施しておく。chrony の設定はいまいち慣れない…

$ sudo sed -i.org 's/^server 0\.centos\.pool\.ntp\.org/server ntp\.nict\.jp/; s/^server [0-3]\.centos\.pool\.ntp\.org iburst//' /etc/chrony.conf
$ sudo sed -i.org 's/^#\(minsources 2\)/\1/g' /etc/chrony.conf
$ sudo systemctl restart chronyd.service
$ sudo systemctl enable chronyd.service
$ chronyc sources

IPv6 の無効化

必須ではないはずだが、IPv6 も無効化する。

$ sudo echo "net.ipv6.conf.all.disable_ipv6 = 1" >> /etc/sysctl.conf
$ sudo echo "net.ipv6.conf.default.disable_ipv6 = 1" >> /etc/sysctl.conf

パッケージのアップデート

ここまで設定したら yum update をして、一度 OS を再起動する。

$ sudo yum -y update

前準備

k8s クラスターの構成には kubeadm を利用するが、インストールにはいくつか要件がある。 https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/

Swap の無効化

kubelet を正常に動作させるには SWAP を OFF にする。

$ sudo swapoff -a
$ sudo vi /etc/fstab
#
# /etc/fstab
# Created by anaconda on Thu Mar 11 17:03:33 2021
#
# Accessible filesystems, by reference, are maintained under '/dev/disk'
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
#
UUID=faedf728-2f18-47ce-9f82-6ac41398c691 /                       ext4    defaults        1 1
UUID=8cee5a3d-6dee-4faa-9a51-838ccc56c495 /boot                   ext4    defaults        1 2
## - SWAP の mount をコメントアウト
#UUID=5acd53ca-fe41-4cc9-862e-6d307b697fa5 swap                    swap    defaults        0 0

Bridge Netfilter

iptables がブリッジを通過するトラフィックを処理させるため、net.bridge.bridge-nf-call-ip6tables を設定する。(あとで調べる)

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

/etc/hosts

名前解決は hosts ファイルで行うので 192.168.56.0/24 のネットワークとホスト名を記述。

$ sudo cat <<EOF >> /etc/hosts
192.168.56.171 c7-k8s-node01.localdomain c7-k8s-node01
192.168.56.172 c7-k8s-node02.localdomain c7-k8s-node02
192.168.56.173 c7-k8s-node03.localdomain c7-k8s-node03
EOF

SELinux を Permissive に変更

SELinuxPermissive に変更しておく。

$ sudo sed -i.org 's/\(^SELINUX=\).*/\1permissive/g' /etc/selinux/config
$ getenforce

Docker のインストール

コンテナランタイムとして各ノードに Docker をインストールする。この辺も CentOS 8 だとコンテナエンジンが Podman になっているので面倒そう。

$ sudo yum install -y yum-utils device-mapper-persistent-data lvm2
$ yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
$ sudo yum install -y docker-ce
$ sudo systemctl enable docker

kubeadm、kubelet、kubectl のインストール

各ノードに kubeadm kubelet kubectl をインストールする。

$ 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 kubeadm kubectl --disableexcludes=kubernetes
$ systemctl enable --now kubelet

各パッケージの役割は以下の通り。

パッケージ 説明
kubeadm k8s クラスターを構成・起動するためのコマンド
kubelet クラスター内のすべてのマシンで実行されるコンポーネント。 Pod やコンテナの起動などを行う。
kubectl クラスターにアクセスするためのコマンドラインツール

k8s クラスターの作成

クラスターを構成する場合は kubeadm コマンドを利用する。

コントロールプレーンノードの初期化

まずは Master ノードで kubeadm init コマンドを実行し、コントロールプレーンの初期化を行う。コントロールプレーンノードでは etcdAPIサーバーが実行される。--apiserver-advertise-address には、仮想マシンenp8s インターフェイス192.168.56.0/24 のネットワークを指定する。--pod-network-cidr はデフォルトの 10.244.0.0/16 を指定。

$ sudo kubeadm init --apiserver-advertise-address=192.168.56.171 --pod-network-cidr=10.244.0.0/16

初期が完了すると、以下のように Worker ノードを追加するためのコマンドが表示される。

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

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

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/

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

sudo kubeadm join 192.168.56.171:6443 --token ljtkc2.fcmz3msyoknpy39g \
    --discovery-token-ca-cert-hash sha256:5f1069d6d26fb11cae8e6b85b4b569ee11d884be0c455370cb3e8e05b0a161ca

root ユーザ以外で kubectl コマンドを利用する場合は、以下の設定を行う。

$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config
$ kubectl get node
NAME                        STATUS     ROLES                  AGE   VERSION
c7-k8s-node01.localdomain   NotReady   control-plane,master   90s   v1.20.4

尚、上記の設定を実施しないで kubectl コマンドを実行すると、以下のようなエラーとなる。

$ kubectl get node
The connection to the server localhost:8080 was refused - did you specify the right host or port?

Worker ノードの追加

残り 2台の Worker で kubeadm join コマンドを実行しクラスターに参加させる。

$ sudo kubeadm join 192.168.56.171:6443 --token ljtkc2.fcmz3msyoknpy39g \
    --discovery-token-ca-cert-hash sha256:5f1069d6d26fb11cae8e6b85b4b569ee11d884be0c455370cb3e8e05b0a161ca

ノードが追加されたことを確認。

$ kubectl get node
NAME                        STATUS     ROLES                  AGE   VERSION
c7-k8s-node01.localdomain   Ready    control-plane,master   120s   v1.20.4
c7-k8s-node02.localdomain   Ready    <none>                  90s   v1.20.4
c7-k8s-node03.localdomain   Ready    <none>                  30s   v1.20.4

Flannel のインストール

Pod 間で通信するためには Container Network Interface(CNI)ベースのアドオンをインストールする必要がある。ネットワークアドオンをインストールしないと以下の通り Cluster DNS(CoreDNS)が起動しない。

$ kubectl get pod --all-namespaces
NAMESPACE     NAME                                                READY   STATUS    RESTARTS   AGE
kube-system   coredns-74ff55c5b-kqpxl                             0/1     Pending   0          88s
kube-system   coredns-74ff55c5b-lcncq                             0/1     Pending   0          88s
kube-system   etcd-c7-k8s-node01.localdomain                      1/1     Running   0          101s
kube-system   kube-apiserver-c7-k8s-node01.localdomain            1/1     Running   0          101s
kube-system   kube-controller-manager-c7-k8s-node01.localdomain   1/1     Running   0          101s
kube-system   kube-proxy-wsfzp                                    1/1     Running   0          88s
kube-system   kube-scheduler-c7-k8s-node01.localdomain            1/1     Running   0          101s

アドオンには Calico などいくつか選択肢があるようだが、今回は Flannel をインストールした。Flannel の導入自体は YAML ファイルで Pod をデプロイするだけだが、VirtualBox の NATアダプターとホストオンリーアダプターを利用した構成の場合、enp3s10.0.2.0/24 のネットワークに Flannel の Pod がデプロイされてしまう。そのため、Yaml ファイルに --iface=enp0s8 の追記が必要。

kube-flannel.yml をダウンロード。

$ curl -O https://raw.githubusercontent.com/coreos/flannel/62e44c867a2846fefb68bd5f178daf4da3095ccb/Documentation/kube-flannel.yml

kube-flannel.yml--iface=enp0s8 の追記。利用する image だけで良い気もするが…念の為 5箇所全てに追記した。

 -- (中略) --
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-amd64
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        - --iface=enp0s8
 -- (中略) --
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-arm64
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        - --iface=enp0s8
 -- (中略) --
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-arm
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        - --iface=enp0s8
 -- (中略) --
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-ppc64le
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        - --iface=enp0s8
 -- (中略) --
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-s390x
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        - --iface=enp0s8

coredns-***kube-flannel-ds-*** Pod が Running になれば OK。

$ kubectl apply -f kube-flannel.yml
$ kubectl get pods --all-namespaces -o wide
NAMESPACE     NAME                                                READY   STATUS      RESTARTS   AGE     IP               NODE                        NOMINATED NODE   READINESS GATES
default       curl                                                0/1     Completed   0          3d2h    10.244.1.5       c7-k8s-node02.localdomain   <none>           <none>
kube-system   coredns-74ff55c5b-d92d2                             1/1     Running     0          4d      10.244.0.2       c7-k8s-node01.localdomain   <none>           <none>
kube-system   coredns-74ff55c5b-qjgm9                             1/1     Running     0          4d      10.244.0.3       c7-k8s-node01.localdomain   <none>           <none>
kube-system   etcd-c7-k8s-node01.localdomain                      1/1     Running     0          4d      192.168.56.171   c7-k8s-node01.localdomain   <none>           <none>
kube-system   kube-apiserver-c7-k8s-node01.localdomain            1/1     Running     0          4d      192.168.56.171   c7-k8s-node01.localdomain   <none>           <none>
kube-system   kube-controller-manager-c7-k8s-node01.localdomain   1/1     Running     0          4d      192.168.56.171   c7-k8s-node01.localdomain   <none>           <none>
kube-system   kube-flannel-ds-amd64-m29tr                         1/1     Running     0          3d23h   192.168.56.172   c7-k8s-node02.localdomain   <none>           <none>
kube-system   kube-flannel-ds-amd64-s22sh                         1/1     Running     0          3d23h   192.168.56.173   c7-k8s-node03.localdomain   <none>           <none>
kube-system   kube-flannel-ds-amd64-xwcnx                         1/1     Running     0          3d23h   192.168.56.171   c7-k8s-node01.localdomain   <none>           <none>
kube-system   kube-proxy-5jznk                                    1/1     Running     0          4d      192.168.56.171   c7-k8s-node01.localdomain   <none>           <none>
kube-system   kube-proxy-bzj4k                                    1/1     Running     0          3d23h   192.168.56.172   c7-k8s-node02.localdomain   <none>           <none>
kube-system   kube-proxy-vvf74                                    1/1     Running     0          3d23h   192.168.56.173   c7-k8s-node03.localdomain   <none>           <none>
kube-system   kube-scheduler-c7-k8s-node01.localdomain            1/1     Running     0          4d      192.168.56.171   c7-k8s-node01.localdomain   <none>           <none>

metrics-server のインストール

kubectl top コマンドを利用するために metrics-server をインストールする。こちらも YAML ファイルで Deployment を作成するだけだが、YAML ファイルの修正が必要。

$ git clone https://github.com/kubernetes-sigs/metrics-server
$ vi metrics-server/manifests/base/deployment.yaml

command--kubelet-insecure-tls--kubelet-preferred-address-types=InternalIP を追記。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: metrics-server
  namespace: kube-system
spec:
  strategy:
    rollingUpdate:
      maxUnavailable: 0
  template:
    spec:
      serviceAccountName: metrics-server
      volumes:
      # mount in tmp so we can safely use from-scratch images and/or read-only containers
      - name: tmp-dir
        emptyDir: {}
      priorityClassName: system-cluster-critical
      containers:
      - name: metrics-server
        image: gcr.io/k8s-staging-metrics-server/metrics-server:master
        imagePullPolicy: IfNotPresent
        command:
        - /metrics-server
        - --kubelet-insecure-tls
        - --kubelet-preferred-address-types=InternalIP
        args:
          - --cert-dir=/tmp
          - --secure-port=4443
          - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
          - --kubelet-use-node-status-port
          - --metric-resolution=15s
        resources:
          requests:
            cpu: 100m
            memory: 300Mi
 -- (中略) --

YAML ファイルから Deployment をデプロイすると kubectl top コマンドが利用できる。

$ kubectl apply -k metrics-server/manifests/base/
$ kubectl top node
NAME                        CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
c7-k8s-node01.localdomain   112m         5%     1555Mi          40%
c7-k8s-node02.localdomain   39m          1%     663Mi           17%
c7-k8s-node03.localdomain   31m          1%     610Mi           15%

エラーとなる場合は CNI の設定を確認する。例えば、今回の構成だと enp8s インターフェイスのネットワーク(192.168.56.0/24)に Flannel がデプロイされていないと、ノードのコンテナ間で通信ができないため metric-server はデプロイされるが kubectl top コマンドは利用できない。

Pod をデプロイ

helloworld Pod をデプロイ

試しに helloworld Pod をデプロイ。kubectl get pod -o wide コマンドで確認すると Worker ノード c7-k8s-node03.localdomain で稼働していることがわかる。

$ kubectl run --image gcr.io/google-samples/hello-app:1.0 --restart Never helloworld
$ kubectl get pod -o wide
NAME         READY   STATUS    RESTARTS   AGE   IP           NODE                        NOMINATED NODE   READINESS GATES
helloworld   1/1     Running   0          6s    10.244.2.5   c7-k8s-node03.localdomain   <none>           <none>

ノードを指定して curl image Pod をデプロイ

helloworld Pod と疎通確認をするためにノード c7-k8s-node02.localdomaincurl image Pod をデプロイする。ノードを指定する場合は YAML ファイルに nodeSelectorkubernetes.io/hostname を追記する。

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: curl
  name: curl
spec:
  containers:
  - name: curl
    image: curlimages/curl:7.68.0
    command:
     - "bin/sh"
     - "-c"
     - "sleep 3000"
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Never
  nodeSelector:
    kubernetes.io/hostname: c7-k8s-node02.localdomain

curl image Pod から helloworld Pod(10.244.2.5)にアクセスできれば OK。

$ kubectl apply -f curlimages.yml
$ kubectl get pod -o wide
NAME         READY   STATUS    RESTARTS   AGE     IP           NODE                        NOMINATED NODE   READINESS GATES
curl         1/1     Running   0          8s      10.244.1.6   c7-k8s-node02.localdomain   <none>           <none>
helloworld   1/1     Running   0          9m30s   10.244.2.5   c7-k8s-node03.localdomain   <none>           <none>

$ kubectl exec -it curl sh
$ curl 10.244.2.5:8080
Hello, world!
Version: 1.0.0
Hostname: helloworld

[おまけ]NFS サーバの導入

仮想マシンを 1台追加し k8s クラスタ間で利用する PVC 領域用の NFS サーバを用意する。

  • NFS Server IP:192.168.56.174

NFS Server

rpcbindnfs-utils をインストール

$ sudo yum install -y rpcbind nfs-utils
$ sudo mkdir -p /var/share/nfs
$ sudo echo "/var/share/nfs 192.168.56.0/24(rw,no_root_squash)" > /etc/exports

サービスを起動する

$ sudo systemctl start rpcbind
$ sudo systemctl start nfs-lock
$ sudo systemctl start nfs-idmap
$ sudo systemctl start nfs-server
$ sudo systemctl enable rpcbind
$ sudo systemctl enable nfs-lock
$ sudo systemctl enable nfs-idmap
$ sudo systemctl enable nfs-server

NFS Client

k8s クラスターの各ノードから作成した NFS 領域を mount する。

$ sudo mkdir /mnt/nfs
$ sudo chown staff:wheel /mnt/nfs
$ sudo mount -t nfs 192.168.56.174:/var/share/nfs /mnt/nfs

参考 URL

参考にさせていただいた記事.