23
25

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.

RaspberryPi 4 にUbuntu20.04 をインストールして、Kubernetes を構築してコンテナを動かす

Posted at

はじめに

Kubernetes のテスト環境が欲しくて組みました。
ググるとたくさん出てくるアレです。
3日間クッキング【Kubernetes のラズペリーパイ包み “サイバーエージェント風”】を見て、美味しそうだと思った一人です。

環境

組み上げたRaspberry Pi 4 です。
スイッチングハブとUSB充電器は、マジックテープで固定しています。(100均で買ったものです。)

今回、用意したパーツです。

パーツ名 アドレス
Raspberry Pi Raspberry Pi 4 Model B/4GB x 3台
SDカード Samsung micro SDXC 128GB MB-MC128GA x 3枚
スイッチングハブ エレコム スイッチングハブ ギガビット 5ポート AC電源 小型 EHC-G05PA-SB x 1台
USB充電器 Anker PowerPort I PD - 1 PD & 4 PowerIQ x 1台
※type-c:1口, type-A:4口
ケース GeeekPi Raspberry Pi 4モデルB用Piラックケース 冷却ファンおよびヒートシンク付き x 1台
USBケーブル Mauknci type-c & type-c 30cm x 1本
USBケーブル SUNGUY type-A & type-c 30cm 2本セット x 2セット(4本)
※1本は余ります。別な事に使っています。
LANケーブル ミヨシMCO カテゴリ-6スリムLANケ-ブル 15cm x 3本
  • スイッチングハブから、ルータへはLANケーブルで繋いでいます。無線LANは使っていません。
  • SDカードの書き込みは、SDXC対応のライタを使用してください。
  • Raspberry Pi 4 の電源仕様が3Aで、USB充電器の出力が2.4Aですが動いています。

導入するソフトウェアです。

  • Ubuntu 20.04.1 LTS 64bit
  • Kubernetes v1.19.3
  • Docker 19.03.13

作業用に下記を使用しました。

  • Windows10の PC(SDカードの書き込み、SSH接続用)
  • 15型のモバイルモニター (ラズパイ初期設定用)
  • USBキーボード (ラズパイ初期設定用)

構築するネットワーク環境

IPアドレスは固定しています。

用途 ホスト名 IPアドレス
Master Node master01.example.jp 192.168.100.101/24
Worker Node1 worker01.example.jp 192.168.100.102/24
Worker Node2 worker02.example.jp 192.168.100.103/24

その他に必要なIPアドレス情報です。

用途 IPアドレス
gateway 192.168.100.254
DNS 192.168.100.254
LoadBalancer 用のIPプール 192.168.100.211-192.168.100.215

組み立て

USB充電器が重いので一番下にして組み立てました。
1本だけある「type-c to type-cケーブル」は重要であろうMaster Nodeに使用しています。(意味ないかもですが)
あとは特筆することはないです。
組み上げたら、起動準備です。

Raspberry Pi 4 に Ubuntu 20.04 をインストールする

ラズパイ起動用にSDカードを作ります。
作業PCはWindows10です。

SDカードの書き込みに、「Raspberry Pi Imager」を使用します。
「Raspberry Pi Imager」でフォーマットすると64GB以上のSDカードが使えます。
Raspberry Pi Imager からダウンロードします。

SDカードをライターにセットし、「Raspberry Pi Imager」を起動します。
※SDXCカード対応のライターを使用してください。
rapi01.png

SDカードを使うためのフォーマットを行います。

「CHOOSE OS」をクリックして、「Erase」を選択します。
rapi02.png

「CHOOSE SD CARD」をクリックして、挿入したSDカードを選択します。
rapi03.png

「WRITE」をクリックしてフォーマットを開始します。
rapi04.png

フォーマット完了です。
「CONTINUE」をクリックして、ダイアログを消します。
rapi05.png

次にOSを書き込みます。
「CHOOSE OS」をクリックして、「Ubuntu」を選択します。
rapi06.png

「Ubuntu Server 20.04.1 LTS (RPi 3/4)」を選択します。
64-bitを選択します。一つ上にある32-bitは違いますよ。
rapi07.png

「CHOOSE SD CARD」をクリックして、挿入したSDカードを選択します。
rapi03.png

「WRITE」をクリックして書き込みます。
rapi08.png

OSの書き込み完了です。
rapi09.png

残りのSDカード 2枚も、同じ様に書き込みを行います。

Ubuntu 20.04 の設定

ネットワークの設定を行い、その後はSSHで接続して作業を行います。

OSを書き込んだSDカードをラズパイにセットして起動します。
15型のモバイルモニターとUSBキーボードを使って設定しています。

Ubuntuにログイン

ユーザ名 初期パスワード
ubuntu ubuntu

初回ログインの場合、パスワードの変更を求められます。
※英語キーボード設定になっているので、記号を含める時は注意してください。

OSのバージョンを確認します。

$ cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.1 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.1 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal

デフォルトのubuntuアカウントの無効化と作業用アカウント作成 (3台共通の作業)

デフォルトのubuntuアカウントを使わないようにして、作業用にk8suserアカウントを作成します。
※作業用アカウントが不要なら飛ばしてください。

$ sudo useradd -m -s /usr/bin/bash k8suser
$ sudo passwd k8suser          # パスワードを変更する
New password:
Retype new password:
passwd: password updated successfully

$ sudo adduser k8suser sudo     # sudo グループにk8suserを追加する
Adding user `k8suser' to group `sudo' ...
Adding user k8suser to group sudo
Done.
$ cat /etc/group | grep sudo   # 確認する
# 英語キーボード設定の場合は、Shift + } が「|」になる
sudo:x:27:ubuntu,k8user

デフォルトアカウント(ubuntu)のログインを禁止します。

$ sudo usermod -s /usr/sbin/nologin ubuntu
$ cat /etc/passwd | grep ubuntu   # 確認する
ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/usr/sbin/nologin

Master NodeのOS設定

Master Nodeのネットワーク周りの設定を行います。

ホスト名の変更 (Master Nodeで実行)

$ sudo hostnamectl set-hostname master01.example.jp
$ hostname    # 確認する
master01.example.jp

IPアドレスの変更 (Master Nodeで実行)

# 英語キーボード設定の場合は、下記のようになる
# Shift + ; が「:」
# @ が「[」
# [ が「]」
$ sudo vi /etc/netplan/99-network.yaml
network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: false
      dhcp6: false
      addresses:
        - 192.168.100.101/24
      gateway4: 192.168.100.254
      nameservers:
        addresses:
          - 192.168.100.254

$ sudo netplan apply
$ ip a    # 確認:「eth0: 」の「inet」でIPアドレスが反映されていることを確認する

SSHで、master01.example.jpにログインします。

接続先 ユーザ名 パスワード
192.168.100.101 k8suser 設定したパスワード

Worker Node1のOS設定

Worker Node1のネットワーク周りの設定を行います。

ホスト名の変更 (Worker Node1で実行)

$ sudo hostnamectl set-hostname worker01.example.jp
$ hostname    # 確認
worker01.example.jp

IPアドレスの変更 (Worker Node1で実行)

# 英語キーボード設定の場合は、下記のようになる
# Shift + ; が「:」
# @ が「[」
# [ が「]」
$ sudo vi /etc/netplan/99-network.yaml
network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: false
      dhcp6: false
      addresses:
        - 192.168.100.102/24
      gateway4: 192.168.100.254
      nameservers:
        addresses:
          - 192.168.100.254

$ sudo netplan apply
$ ip a    # 確認:「eth0: 」の「inet」でIPアドレスが反映されていることを確認する

SSHで、worker01.example.jpにログインします。

接続先 ユーザ名 パスワード
192.168.100.102 k8suser 設定したパスワード

Worker Node2のOS設定

Worker Node2のネットワーク周りの設定を行います。

ホスト名の変更 (Worker Node2で実行)

$ sudo hostnamectl set-hostname worker02.example.jp
$ hostname    # 確認する
worker02.example.jp

IPアドレスの変更 (Worker Node2で実行)

# 英語キーボード設定の場合は、下記のようになる
# Shift + ; が「:」
# @ が「[」
# [ が「]」
$ vi /etc/netplan/99-network.yaml
network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: false
      dhcp6: false
      addresses:
        - 192.168.100.103/24
      gateway4: 192.168.100.254
      nameservers:
        addresses:
          - 192.168.100.254

$ sudo netplan apply
$ ip a    # 確認:「eth0: 」の「inet」でIPアドレスが反映されていることを確認する

SSHで、worker02.example.jpにログインします。

接続先 ユーザ名 パスワード
192.168.100.103 k8suser 設定したパスワード

3台共通のOS設定

以降の作業は、SSHで接続してOSの設定を続けます。
Kubernetes を実行する場合、swapは停止する必要があるのですが、swapは動作していませんでした。
ラズパイ向けだからでしょうか?

$ free
              total        used        free      shared  buff/cache   available
Mem:        3884360      961332      152584        5184     2770444     2973200
Swap:             0           0           0      # Swap が0

既存パッケージの更新 (3台共通)

$ sudo apt update
$ sudo apt -y upgrade

hostsファイルの編集 (3台共通)

$ sudo vi /etc/hosts
# 追記する
192.168.100.101       master01 master01.example.jp
192.168.100.102       worker01 worker01.example.jp
192.168.100.103       worker02 worker02.example.jp
$ cat /etc/hosts    # 確認する

time zoneの変更 (3台共通)

$ sudo timedatectl set-timezone Asia/Tokyo
$ timedatectl | grep Time    # 確認する
                Time zone: Asia/Tokyo (JST, +0900)

keymap変更 (3台共通)

$ sudo localectl set-keymap jp106
$ localectl    # 確認する
   System Locale: LANG=C.UTF-8
       VC Keymap: jp106
      X11 Layout: jp
       X11 Model: jp106
     X11 Options: terminate:ctrl_alt_bksp

IPv6の停止 (3台共通)

$ sudo vi /etc/sysctl.conf
# 追記する
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.eth0.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
$ sudo sysctl -p
$ ip a    # 確認する。inet6が表示されないこと。

iptablesがnftablesバックエンドを使用しないようにする (3台共通)

iptablesがnftablesバックエンドを使用しないようにする を参考に設定します。

$ sudo apt-get -y install iptables arptables ebtables
$ sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
$ sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
$ sudo update-alternatives --set arptables /usr/sbin/arptables-legacy
update-alternatives: using /usr/sbin/arptables-legacy to provide /usr/sbin/arptables (arptables) in manual mode
$ sudo update-alternatives --set ebtables /usr/sbin/ebtables-legacy
update-alternatives: using /usr/sbin/ebtables-legacy to provide /usr/sbin/ebtables (ebtables) in manual mode

Dockerのインストール (3台共通)

Install Docker Engine on Ubuntu を参考にインストールします。

$ sudo apt-get -y install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository "deb [arch=arm64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
$ sudo apt-get update
$ sudo apt-get -y install docker-ce docker-ce-cli containerd.io
$ sudo apt-mark hold docker-ce docker-ce-cli containerd.io
docker-ce set on hold.
docker-ce-cli set on hold.
containerd.io set on hold.

ユーザをdocker グループに割当るのは、セキュリティ的によろしくないですがテスト用と割り切って・・・。

$ sudo adduser k8suser docker
Adding user `k8suser' to group `docker' ...
Adding user k8suser to group docker
Done.
$ cat /etc/group | grep docker    # 確認
docker:x:998:k8suser

グループ割当のため、ログオフ・ログオンを実行します。
Dockerのバージョンを確認します。

$ docker version
Client: Docker Engine - Community
 Version:           19.03.13
 API version:       1.40
 Go version:        go1.13.15
 Git commit:        4484c46
 Built:             Wed Sep 16 17:03:40 2020
 OS/Arch:           linux/arm64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.13
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       4484c46
  Built:            Wed Sep 16 17:02:11 2020
  OS/Arch:          linux/arm64
  Experimental:     false
 containerd:
  Version:          1.3.7
  GitCommit:        8fba4e9a7d01810a393d5d25a3621dc101981175
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

Dockerの動作確認をします。
hellp-worldコンテナを起動します。

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
256ab8fe8778: Pull complete
Digest: sha256:8c5aeeb6a5f3ba4883347d3747a7249f491766ca1caa47e5da5dfcf6b9b717c0
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (arm64v8)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

kubeadm、kubectl、kubeletのインストール (3台共通)

kubeadm、kubelet、kubectlのインストール を参考にインストールします。

$ sudo apt-get update && sudo apt-get -y install apt-transport-https curl
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
$ cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
$ sudo apt-get update
$ sudo apt-get -y install kubelet kubeadm kubectl
$ sudo apt-mark hold kubelet kubeadm kubectl
kubelet set on hold.
kubeadm set on hold.
kubectl set on hold.

各モジュールのバージョンを確認します。

$ kubeadm version -o json
{
  "clientVersion": {
    "major": "1",
    "minor": "19",
    "gitVersion": "v1.19.3",
    "gitCommit": "1e11e4a2108024935ecfcb2912226cedeafd99df",
    "gitTreeState": "clean",
    "buildDate": "2020-10-14T12:47:53Z",
    "goVersion": "go1.15.2",
    "compiler": "gc",
    "platform": "linux/arm64"
  }
}

$ kubectl version -o json
{
  "clientVersion": {
    "major": "1",
    "minor": "19",
    "gitVersion": "v1.19.3",
    "gitCommit": "1e11e4a2108024935ecfcb2912226cedeafd99df",
    "gitTreeState": "clean",
    "buildDate": "2020-10-14T12:50:19Z",
    "goVersion": "go1.15.2",
    "compiler": "gc",
    "platform": "linux/arm64"
  }
}
The connection to the server localhost:8080 was refused - did you specify the right host or port?

$ kubelet --version
Kubernetes v1.19.3

kubectl version -o json だけ実行時にメッセージ(The connection to the server ・・・)が出ていますが、後で対処します。

cgruop でmemoryの有効化 (3台共通)

enableが0になっています。(最後の0がenabledの列)

$ cat /proc/cgroups | grep memory
memory  0       105     0

ラズパイクラスタにKubernetesをインストール(成功編) を参考に設定します。

/boot/firmware/cmdline.txt に追記します。
行の最後に追記です。全一行のファイルです。

$ sudo vi /boot/firmware/cmdline.txt
cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory    # 追記する
$ cat /boot/firmware/cmdline.txt    # 確認する
net.ifnames=0 dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=LABEL=writable rootfstype=ext4 elevator=deadline rootwait fixrtc cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory

再起動します。

$ sudo reboot

OSが起動したら、確認します。

$ cat /proc/cgroups | grep memory
memory  10      97      1

enableが1に、つまり有効になっています。

Kubernetes クラスタの作成

いよいよKubernetesの設定です。まずは、Master Nodeを初期化します。

Kubernetesのドキュメント を参考にセットアップします。

Master Nodeの初期化 (Master Nodeで実行)

オプションについて

  • apiserver-advertise-address=192.168.100.101

  • API ServerのIPアドレス。今回の場合は、Master NodeのIPアドレス。

  • 複数のNICある場合は指定する必要あり。

  • 複数無いけど、明示するために指定しています。

  • pod-network-cidr=10.244.0.0/16

  • flannelのための指定です。

初期化を実行します。

$ sudo kubeadm init --apiserver-advertise-address=192.168.100.101 --pod-network-cidr=10.244.0.0/16
W1107 17:43:47.125493    2544 configset.go:348] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io]
[init] Using Kubernetes version: v1.19.3
[preflight] Running pre-flight checks
        [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/
        [WARNING SystemVerification]: missing optional cgroups: hugetlb
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local master01.example.jp] and IPs [10.96.0.1 192.168.100.101]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [localhost master01.example.jp] and IPs [192.168.100.101 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [localhost master01.example.jp] and IPs [192.168.100.101 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Starting the kubelet
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 35.510569 seconds
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.19" in namespace kube-system with the configuration for the kubelets in the cluster
[upload-certs] Skipping phase. Please see --upload-certs
[mark-control-plane] Marking the node master01.example.jp as control-plane by adding the label "node-role.kubernetes.io/master=''"
[mark-control-plane] Marking the node master01.example.jp as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[bootstrap-token] Using token: vpkasj.i7pe42jx57scb3bi
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to get nodes
[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

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/

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

kubeadm join 192.168.100.101:6443 --token vpkasj.i7pe42jx57scb3bi \
    --discovery-token-ca-cert-hash sha256:3646aa901c623280b56d8ec33873263a5e3452a979f594c0f628724ed9fe9cce

最後のkubeadm join ・・・ は、Worker Node追加時に必要なので、どこかに記録しておきます。また、24時間過ぎるとtokenは使用できなくなります。

tokenの残り時間の確認や再発行の方法ですが、この時点でコマンドを実行するとエラーになるので、後述します。

環境変数と入力補完の設定 (Master Nodeで実行)

環境変数を設定します。
kubectl version -o json 実行時のメッセージ(The connection to the server ・・・)も下記を設定することで表示されなくなります。

$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config
$ echo 'KUBECONFIG=$HOME/.kube/config' >> $HOME/.bashrc
$ source $HOME/.bashrc
$ kubectl version -o json
# 「The connection to the server ・・・」が表示されないことを確認する

コマンドの入力補完を設定します。

$ source <(kubectl completion bash)
$ echo "source <(kubectl completion bash)" >> $HOME/.bashrc

kubeadm join のtokenについて (Master Nodeで実行)

先程、後述するとしたtokenについてです。
残り時間を確認する場合は、kubeadm token listコマンドを実行します。
何も表示されない場合は、有効なtokenが存在しません。

$ kubeadm token list
TOKEN                     TTL         EXPIRES                     USAGES                   DESCRIPTION                                                EXTRA GROUPS
2jdf77.0ww7uv0w2hodm99i   23h         2020-10-31T22:09:03+09:00   authentication,signing   The default bootstrap token generated by 'kubeadm init'.

token を再発行する場合は、kubeadm token createコマンドを実行します。

$ sudo kubeadm token create
W1101 14:37:16.210067  508855 configset.go:348] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io]
2jdf77.0ww7uv0w2hodm99i  # これがtoken

CA証明書のhashを確認する場合は、opensslコマンドを実行します。
kubeadmを使用したシングルコントロールプレーンクラスターの作成 に書かれています。

$ openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | \
   openssl dgst -sha256 -hex | sed 's/^.* //'
3646aa901c623280b56d8ec33873263a5e3452a979f594c0f628724ed9fe9cce  # これがCA証明書のhash

Podネットワークアドオンのインストール (Master Nodeで実行)

Pod同士が通信できるようにするためのアドオンとして、flannelを設定します。
実績の多いflannelを選択しました。
下記を参考にしてインストールします。

$ curl https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml -O
$ kubectl apply -f kube-flannel.yml
podsecuritypolicy.policy/psp.flannel.unprivileged created
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.apps/kube-flannel-ds created

起動を確認します。
kube-flannel-ds-XXXXX が確認できます。

$ kubectl get pods -n kube-system
NAME                                          READY   STATUS    RESTARTS   AGE
coredns-f9fd979d6-trgnz                       1/1     Running   0          5m54s
coredns-f9fd979d6-w7zvv                       1/1     Running   0          5m54s
etcd-master01.example.jp                      1/1     Running   0          5m58s
kube-apiserver-master01.example.jp            1/1     Running   0          5m59s
kube-controller-manager-master01.example.jp   1/1     Running   0          5m59s
kube-flannel-ds-bmvz4                         1/1     Running   0          61s
kube-proxy-6rhgr                              1/1     Running   0          5m54s
kube-scheduler-master01.example.jp            1/1     Running   0          5m58s

LoadBalancer のインストール (Master Nodeで実行)

MetalLBを使います。
下記の特徴から決めました。

  • オンプレミス環境でLoadBalancerタイプのServiceを使用できる
  • External IPの割り当てができる

ControllerとSpeakerの2種類のPodで構成されてます。

MetalLB, bare metal load-balancer for Kubernetes を参考にインストールします。

$ curl https://raw.githubusercontent.com/metallb/metallb/v0.9.4/manifests/namespace.yaml -o namespace.yaml
$ curl https://raw.githubusercontent.com/metallb/metallb/v0.9.4/manifests/metallb.yaml -o metallb.yaml
$ kubectl apply -f namespace.yaml
namespace/metallb-system created
$ kubectl apply -f metallb.yaml
podsecuritypolicy.policy/controller created
podsecuritypolicy.policy/speaker created
serviceaccount/controller created
serviceaccount/speaker created
clusterrole.rbac.authorization.k8s.io/metallb-system:controller created
clusterrole.rbac.authorization.k8s.io/metallb-system:speaker created
role.rbac.authorization.k8s.io/config-watcher created
role.rbac.authorization.k8s.io/pod-lister created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker created
rolebinding.rbac.authorization.k8s.io/config-watcher created
rolebinding.rbac.authorization.k8s.io/pod-lister created
daemonset.apps/speaker created
deployment.apps/controller created
$ kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)"
secret/memberlist created

起動を確認します。
controller-XXXXXXXXX-XXXXX と speaker-XXXXX が確認できます。

$ kubectl get pod -n metallb-system
NAME                         READY   STATUS    RESTARTS   AGE
controller-8687cdc65-jgf2g   0/1     Pending   0          54s
speaker-q9ksw                1/1     Running   0          54s

controller がPending で動いていません。
MetalLB, bare metal load-balancer for Kubernetesに書かれていますが、controllerはDeploymentです。
まだWorker Nodeを登録していないため、Podを動かせるWorker Nodeがいないからです。
speakerは、DaemonSetなので全Node(Master、Worker)で起動します。
今はMaster Nodeで起動しています。(Worker Node追加後は、全部で3つ起動します)

Worker Nodeをクラスタに参加させる (Worker Node1、Worker Node2で実行)

Master Nodeの初期化の時に記録しておいたコマンドを実行します。忘れた場合は再発行します。
※メッセージをコピペした場合、sudoがついていないの注意してください。

$ sudo kubeadm join 192.168.100.101:6443 --token vpkasj.i7pe42jx57scb3bi \
    --discovery-token-ca-cert-hash sha256:3646aa901c623280b56d8ec33873263a5e3452a979f594c0f628724ed9fe9cce
[sudo] password for user01:
[preflight] Running pre-flight checks
        [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/
        [WARNING SystemVerification]: missing optional cgroups: hugetlb
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

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

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

Worker Nodeの追加確認 (Master Nodeで実行)

確認は、Master Nodeで行います。
Worker NodeがRunningになるまで、5分弱かかりました。

$ kubectl get nodes
NAME                  STATUS   ROLES    AGE     VERSION
master01.example.jp   Ready    master   14m     v1.19.3
worker01.example.jp   Ready    <none>   4m59s   v1.19.3
worker02.example.jp   Ready    <none>   4m33s   v1.19.3
$ kubectl get pods -A
NAMESPACE        NAME                                          READY   STATUS    RESTARTS   AGE
kube-system      coredns-f9fd979d6-trgnz                       1/1     Running   0          14m
kube-system      coredns-f9fd979d6-w7zvv                       1/1     Running   0          14m
kube-system      etcd-master01.example.jp                      1/1     Running   0          15m
kube-system      kube-apiserver-master01.example.jp            1/1     Running   0          15m
kube-system      kube-controller-manager-master01.example.jp   1/1     Running   0          15m
kube-system      kube-flannel-ds-bmvz4                         1/1     Running   0          10m
kube-system      kube-flannel-ds-mwmt6                         1/1     Running   0          6m9s
kube-system      kube-flannel-ds-zm2fk                         1/1     Running   0          5m43s
kube-system      kube-proxy-6rhgr                              1/1     Running   0          14m
kube-system      kube-proxy-b8fjn                              1/1     Running   0          6m9s
kube-system      kube-proxy-htndc                              1/1     Running   0          5m43s
kube-system      kube-scheduler-master01.example.jp            1/1     Running   0          15m
metallb-system   controller-8687cdc65-jgf2g                    1/1     Running   0          8m7s
metallb-system   speaker-q9ksw                                 1/1     Running   0          8m7s
metallb-system   speaker-vmt52                                 1/1     Running   0          79s
metallb-system   speaker-wkcz4                                 1/1     Running   0          2m16s

PendingになっていたcontrollerがRunningになりました。
kube-flannel-ds-XXXXX、kube-proxy-XXXXX、speaker-XXXXXがWorker Nodeの数だけ増えています。

Worker Nodeにラベルを設定する (Master Nodeで実行)

$ kubectl label node worker01.example.jp node-role.kubernetes.io/worker=worker
node/worker01.example.jp labeled
$ kubectl label node worker02.example.jp node-role.kubernetes.io/worker=worker
node/worker02.example.jp labeled
$ kubectl get nodes  # 確認する
NAME                  STATUS   ROLES    AGE     VERSION
master01.example.jp   Ready    master   16m     v1.19.3
worker01.example.jp   Ready    worker   7m22s   v1.19.3
worker02.example.jp   Ready    worker   6m56s   v1.19.3

Kubernetes で コンテナを動かす

動作確認用に、Docker が動作してるホストのhostnameを返すNginxコンテナ のイメージを使用します。

display-hostname.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: nginx-prod
---
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: pool-ips  # MetallbのIPプール名
      protocol: layer2
      addresses:
      - 192.168.100.211-192.168.100.215
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service-lb # Service(LoadBalancer) の名前
  namespace: nginx-prod
  annotations:
    metallb.universe.tf/address-pool: pool-ips # MetallbのIPプール名
spec:
  type: LoadBalancer
  ports:
    - name: nginx-service-lb
      protocol: TCP
      port: 8080 # ServiceのIPでlistenするポート
      nodePort: 30080 # nodeのIPでlistenするポート(30000-32767)
      targetPort: 80 # 転送先(コンテナ)でlistenしているPort番号のポート
  selector: # service のselctorは、matchLabels 扱いになる
    app: nginx-pod # 転送先の Pod のラベル
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment # Deployment の名前(ReplicaSetの名前もこれになる)
  namespace: nginx-prod
spec:
  selector:
    matchLabels: # ラベルがマッチしたPodを対象とするReplicaSetの作成
      app: nginx-pod
  replicas: 2
  template: # Pod のテンプレート
    metadata:
      name: nginx-pod # Pod の名前
      namespace: nginx-prod
      labels: # Pod のラベル
        app: nginx-pod
    spec:
      containers: # コンテナの設定
        - name: nginx-container # コンテナの名前
          image: yasthon/nginx-display-hostname # イメージの名前
          env:
            - name: nginx-container
          ports:
            - containerPort: 80 # コンテナのポート
          volumeMounts:
            - name: file-hostname
              mountPath: /usr/share/nginx/html/hostname
      volumes:
        - name: file-hostname
          hostPath:
            path: /etc/hostname

リソースを作成します。

$ kubectl apply -f display-hostname.yaml
namespace/nginx-prod created
configmap/config created
service/nginx-service-lb created
deployment.apps/nginx-deployment created
$ kubectl get all -n nginx-prod  # 確認する
NAME                                    READY   STATUS    RESTARTS   AGE
pod/nginx-deployment-7ff4cc65cd-5bkv5   1/1     Running   0          3m33s
pod/nginx-deployment-7ff4cc65cd-xsp76   1/1     Running   0          3m33s

NAME                       TYPE           CLUSTER-IP    EXTERNAL-IP       PORT(S)          AGE
service/nginx-service-lb   LoadBalancer   10.97.27.72   192.168.100.211   8080:30080/TCP   3m33s

NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-deployment   2/2     2            2           3m33s

NAME                                          DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-deployment-7ff4cc65cd   2         2         2       3m33s

$ kubectl get configmap -n metallb-system  # 確認する
NAME     DATA   AGE
config   1      3m57s

Pod、Service、Deployment、ReplicaSet、ConfigMapが作成されています。
ServiceのEXTERNAL-IPは、IPプールから割り当てられました。

Serviceの詳細情報から、LoadBalancer Ingress とPortの値を確認します。

$ kubectl describe svc nginx-service-lb -n nginx-prod
Name:                     nginx-service-lb
Namespace:                nginx-prod
Labels:                   <none>
Annotations:              metallb.universe.tf/address-pool: pool-ips
Selector:                 app=nginx-pod
Type:                     LoadBalancer
IP:                       10.97.27.72
LoadBalancer Ingress:     192.168.100.211
Port:                     nginx-service-lb  8080/TCP
TargetPort:               80/TCP
NodePort:                 nginx-service-lb  30080/TCP
Endpoints:                10.244.1.4:80,10.244.2.3:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:
  Type    Reason        Age                    From                Message
  ----    ------        ----                   ----                -------
  Normal  IPAllocated   6m43s                  metallb-controller  Assigned IP "192.168.100.211"
  Normal  nodeAssigned  6m29s                  metallb-speaker     announcing from node "worker01.example.jp"
  Normal  nodeAssigned  6m15s (x2 over 6m36s)  metallb-speaker     announcing from node "worker02.example.jp"

LoadBalancer IngressのIPアドレスは、EXTERNAL-IPと同じIPアドレスです。
(x2 over 59s) の表示はどういう意味でしょう?2回アサインした?1回目は時間がかかった??
動作には支障ないようです。表示されないときもあります。

接続確認

LoadBalancer IngressのIPアドレスとPortのポート番号へ接続します。

$ curl 192.168.100.211:8080/index.sh
<html><head>
<title>worker01.example.jp</title>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
</head><body>
HOSTNAME : worker01.example.jp
</body></html>

何度かcurlコマンドを繰り返すと、接続先がworker02.example.jpに変わります。
ロードバランスしているということでしょう。
単純なラウンドロビンではないようですが。

Worker Node2を止めてみる

Worker Node2のEtherケーブルを抜いて、curlコマンドを実行しました。
コマンドはMaster Nodeで実行しました。

$ curl 192.168.100.211:8080/index.sh
<html><head>
<title>worker01.example.jp</title>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
</head><body>
HOSTNAME : worker01.example.jp
</body></html>

$ curl 192.168.100.211:8080/index.sh
<html><head>
<title>worker01.example.jp</title>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
</head><body>
HOSTNAME : worker01.example.jp
</body></html>

当然ですが、worker01.example.jpにしかアクセスしなくなりました。

Worker Node1 を止めてみる

Worker Node2 のEtherケーブルを挿し直します。
Worker Node1 のEtherケーブルを抜いて、curlコマンドを実行しました。

$ curl 192.168.100.211:8080/index.sh
<html><head>
<title>worker02.example.jp</title>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
</head><body>
HOSTNAME : worker02.example.jp
</body></html>

$ curl 192.168.100.211:8080/index.sh
<html><head>
<title>worker02.example.jp</title>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
</head><body>
HOSTNAME : worker02.example.jp
</body></html>

Worker Node2 にしか繋がらなくなりました。
Worker Node1 のEtherケーブルを挿し直すと両Worker Nodeに繋がるようになります。

アクセス不能になったWorkerを除いて、アクセスするように動作しています。

ブラウザからも、
http://192.168.100.211:8080/index.sh
でアクセスできます。
ブラウザからアクセスした場合は、ガッチリ固定されてしまっています。
Ctrl + f5 を連打しても接続先が変わらない・・・。
ケーブルを抜くと、もう1台のWorker Nodeに接続されるので、LoadBalancerは効いているようです。

クラスタのクリーンアップ

なんらかの理由でクラスタを作り直すときは、クリーンアップを実行します。
下記を参考にしています。

ノードの削除 (Master Nodeで実行)

$ kubectl get nodes
NAME                  STATUS   ROLES    AGE    VERSION
master01.example.jp   Ready    master   8d     v1.19.3
worker01.example.jp   Ready    worker   2d8h   v1.19.3
worker02.example.jp   Ready    worker   2d8h   v1.19.3

$ kubectl drain worker01.example.jp --delete-local-data --force --ignore-daemonsets
node/worker01.example.jp cordoned
WARNING: ignoring DaemonSet-managed Pods: kube-system/kube-flannel-ds-brt9l, kube-system/kube-proxy-5pch5, metallb-system/speaker-4n2xx
evicting pod metallb-system/controller-8687cdc65-cn48m
pod/controller-8687cdc65-cn48m evicted
node/worker01.example.jp evicted

$ kubectl drain worker02.example.jp --delete-local-data --force --ignore-daemonsets
node/worker02.example.jp cordoned
WARNING: ignoring DaemonSet-managed Pods: kube-system/kube-flannel-ds-tdg4l, kube-system/kube-proxy-k56gw, metallb-system/speaker-qdv85
evicting pod metallb-system/controller-8687cdc65-nkvvn
pod/controller-8687cdc65-nkvvn evicted
node/worker02.example.jp evicted

$ kubectl delete node worker01.example.jp
node "worker01.example.jp" deleted

$ kubectl delete node worker02.example.jp
ode "worker02.example.jp" deleted

$ kubectl get nodes    # 確認する
NAME                  STATUS   ROLES    AGE   VERSION
master01.example.jp   Ready    master   8d    v1.19.3

Worker Nodeのリセット(Worker Node1、Worker Node2で実行)

$ sudo kubeadm reset
[reset] WARNING: Changes made to this host by 'kubeadm init' or 'kubeadm join' will be reverted.
[reset] Are you sure you want to proceed? [y/N]: y    # y を入力する
[preflight] Running pre-flight checks
W1104 20:37:31.440013 1890335 removeetcdmember.go:79] [reset] No kubeadm config, using etcd pod spec to get data directory
[reset] No etcd config found. Assuming external etcd
[reset] Please, manually reset etcd to prevent further issues
[reset] Stopping the kubelet service
[reset] Unmounting mounted directories in "/var/lib/kubelet"
[reset] Deleting contents of config directories: [/etc/kubernetes/manifests /etc/kubernetes/pki]
[reset] Deleting files: [/etc/kubernetes/admin.conf /etc/kubernetes/kubelet.conf /etc/kubernetes/bootstrap-kubelet.conf /etc/kubernetes/controller-manager.conf /etc/kubernetes/scheduler.conf]
[reset] Deleting contents of stateful directories: [/var/lib/kubelet /var/lib/dockershim /var/run/kubernetes /var/lib/cni]

The reset process does not clean CNI configuration. To do so, you must remove /etc/cni/net.d

The reset process does not reset or clean up iptables rules or IPVS tables.
If you wish to reset iptables, you must do so manually by using the "iptables" command.

If your cluster was setup to utilize IPVS, run ipvsadm --clear (or similar)
to reset your system's IPVS tables.

The reset process does not clean your kubeconfig files and you must remove them manually.
Please, check the contents of the $HOME/.kube/config file.

メッセージに書かれている処理を行います。

CNI configuration を削除します。

$ sudo rm -rf /etc/cni/net.d

iptablesにKubernetes関連のルールが残っているので消します。

$ sudo iptables -L -n
# Kubernetes関連のルールが大量に表示されます。
$ sudo iptables -F && sudo iptables -t nat -F && sudo iptables -t mangle -F && sudo iptables -X
$ sudo iptables -L -n    # 確認する
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy DROP)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Master Nodeのリセット (Master Nodeで実行)

$ sudo systemctl restart docker.service
$ sudo kubeadm reset
$ sudo rm -rf /etc/cni/net.d
$ sudo iptables -L -n
$ sudo iptables -F && sudo iptables -t nat -F && sudo iptables -t mangle -F && sudo iptables -X
$ sudo iptables -L -n    # 確認する

クリーンアップ後は、sudo kubeadm initコマンドを実行してクラスタを作成します。

Master NodeでPodを動かす (Master Nodeで実行)

Master NodeでPodを動作させることができます。
デフォルトの設定は Master Node上にスケジュールされないようになっています。

$ kubectl describe node master01 | grep Taints
Taints:             node-role.kubernetes.io/master:NoSchedule

NoScheduleの設定を消して、Master Node上でもPodが動作するようにします。
master:NoScheduleの後ろに「-」(ハイフン)をつけます。

$ kubectl taint nodes master01.example.jp node-role.kubernetes.io/master:NoSchedule-
node/master01.example.jp untainted
$ kubectl describe node master01 | grep Taints    # 確認する
Taints:             <none>

kind: Deploymentreplicas: 2を3に変更します・

$ vi display-hostname.yaml
  replicas: 3    # 2 を 3に変更する

$ kubectl apply -f display-hostname.yaml
namespace/nginx-prod unchanged
configmap/config unchanged
service/nginx-service-lb unchanged
deployment.apps/nginx-deployment configured
$ kubectl get pod -n nginx-prod    # 確認する
NAME                                READY   STATUS              RESTARTS   AGE
nginx-deployment-7ff4cc65cd-5f85p   1/1     Running             0          39m
nginx-deployment-7ff4cc65cd-lm782   0/1     ContainerCreating   0          38s
nginx-deployment-7ff4cc65cd-nrhl7   1/1     Running             0          39m

起動しようとしている真ん中のPodが、Master Node上で動作しているPodです。
詳細情報でも、master01.example.jpで起動していることが確認できます。

$ kubectl describe pod/nginx-deployment-7ff4cc65cd-lm782 -n nginx-prod | grep ^Node:
Node:         master01.example.jp/192.168.128.193

Master Node上でPodが動作しないように戻す場合は、まずPodの数を2つに戻します。
``display-hostname.yamlreplicas:```を2に戻します。

$ vi display-hostname.yaml
  replicas: 2    # 3 を 2に変更する

$ kubectl apply -f display-hostname.yaml
namespace/nginx-prod unchanged
configmap/config unchanged
service/nginx-service-lb unchanged
deployment.apps/nginx-deployment configured
$ kubectl get pod -n nginx-prod    # 確認する
NAME                                READY   STATUS        RESTARTS   AGE
nginx-deployment-7ff4cc65cd-5f85p   1/1     Running       0          47m
nginx-deployment-7ff4cc65cd-lm782   1/1     Terminating   0          8m32s
nginx-deployment-7ff4cc65cd-nrhl7   1/1     Running       0          47m

先程、起動した真ん中のPodが、終了しようとしています。

NoSchedule を設定します。

$ kubectl taint nodes master01.example.jp node-role.kubernetes.io/master:NoSchedule
node/master01.example.jp tainted
$ kubectl describe node master01 | grep Taints    # 確認する
Taints:             node-role.kubernetes.io/master:NoSchedule

NoScheduleが設定され、Master NodeでPodが動作しないようになりました。

最後に

ラズパイでKubernetesクラスタ環境が構築できました。良い時代です。
サーバ・PCを買い足すことに比べれば、かなりお得になります。
後は遊ぶだけです。

参考URL

参考にさせていただいた記事です。

本文中にリンクがある参考記事です。

23
25
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
23
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?