はじめに
KubernetesクラスタをVMware Workstationで構築する際の備忘録。ディプロイ環境はオンプレ想定で構成してみる。セットアップオプションの選択はRHEL/Red Hat寄りな感じですすめる。
詳細はこちらを参照。
利用するソフトウェア
- VMware Workstation 17 Pro (Windows 11 / X86_64)
- RHEL 9.5 (VM)
- Dnsmasq (2.79)
- HA-Proxy (1.8.27)
- Squid Cache (4.15)
- Kubernetes (v1.32)
- CRI-O (v1.32)
- Calico (v3.29.3)
- MetalLB (v0.14.9)
- kubernetes-dashboard (7.12.0)
- Helm (v3.17.3)
- nginx (1.27.5)
※ RHEL 8系についてはKubernetesがサポートするカーネルバージョンかどうかを要確認
関連記事
ネットワーク構成
以下のようにコントロールプレーンノード(3台)およびワーカーノード(2台)の構成で構築する(後ほどワーカーノードを1台追加する)。
-
ドメイン名: test.k8s.local
-
クラスタ名: test
-
Kubernetes API通信(コントロールプレーンノード)はHA-Proxyで負荷分散 (L4分散)
- アプリケーション通信(ワーカーノード)はMetalLB(L2モード)で冗長化
Kubernetesクラスタ
ホスト名 (FQDN) | 種類 | OS | 備考 |
---|---|---|---|
k8s-master0.test.k8s.local | コントロールプレーン | RHEL 9.5 | |
k8s-master1.test.k8s.local | コントロールプレーン | RHEL 9.5 | |
k8s-master2.test.k8s.local | コントロールプレーン | RHEL 9.5 | |
k8s-worker0.test.k8s.local | ワーカーノード | RHEL 9.5 | |
k8s-worker1.test.k8s.local | ワーカーノード | RHEL 9.5 | |
k8s-worker2.test.k8s.local | ワーカーノード | RHEL 9.5 | 後で追加 |
外部ノード
ホスト名 (FQDN) | 種類 | OS | 備考 |
---|---|---|---|
lb.test.k8s.local | ロードバランサー/DNS/DHCP | RHEL 9.5 | HA-Proxy / Dnsmasq |
proxy.test.k8s.local | プロキシ/デフォルトゲートウェイ | RHEL 9.5 | Squid Cache |
mng.test.k8s.local | 管理端末 | RHEL 9.5 | kubectl |
アドレス設定
vmnet2 (VMware Workstation仮想ネットワーク )
設定 | 値 |
---|---|
名前 | vmnet2 |
種別 | ホストオンリー |
ネットワークアドレス | 10.0.0.0/24 |
DHCP | 無効 |
ホスト仮想アダプタ | 接続する |
lb.test.k8s.local
DNSサービス (Dnsmasq)
サーバIP | ポート | 備考 |
---|---|---|
10.0.0.50 | 53/udp |
DHCPサービス (Dnsmasq)
サーバIP | アドレス範囲 | 備考 |
---|---|---|
10.0.0.50 | 10.0.0.100 - 10.0.0.250 |
Load Balancerサービス (HA-Proxy)
(1) コントロールプレーンノードの仮想IP (Kubernetes API)
仮想IP | ポート | 分散先ノード | 備考 |
---|---|---|---|
10.0.0.51 | Kubernetes API用 | ||
6443/tcp | k8s-master0, k8s-master1, k8s-master2 | Kubernetes API Server |
(2) NodePortタイプ構成: アプリケーションの仮想IP
仮想IP | ポート | 分散先ノード | 備考 |
---|---|---|---|
10.0.0.52 | |||
80/tcp | k8s-worker0, k8s-worker1 | HTTPアプリケーション | |
443/tcp | k8s-worker0, k8s-worker1 | HTTPSアプリケーション |
proxy.test.k8s.local
Proxyサービス (Squid Cache)
IP | ポート | 備考 |
---|---|---|
10.0.0.53 | 3128/tcp | HTTP/HTTPS |
デフォルトゲートウェイ
IP | 備考 |
---|---|
10.0.0.53 | NATルーター |
Kubernetesノード
コントロールプレーンノード
本構成ではDHCPサービスによりStatic IPアドレスを割り当てる (OKDっぽく)。
ホスト名 | IP | 備考 |
---|---|---|
k8s-master0.test.k8s.local | 10.0.0.150 | |
k8s-master1.test.k8s.local | 10.0.0.151 | |
k8s-master2.test.k8s.local | 10.0.0.152 |
ワーカーノード
本構成ではDHCPサービスによりStatic IPアドレスを割り当てる。
ホスト名 | IP | 備考 |
---|---|---|
k8s-worker0.test.k8s.local | 10.0.0.155 | |
k8s-worker1.test.k8s.local | 10.0.0.156 |
LoadBalancerタイプ構成 (MetalLB): アプリケーションの仮想IP
仮想IP | ポート | 分散先ノード | 備考 |
---|---|---|---|
10.0.0.52 | |||
80/tcp | k8s-worker0, k8s-worker1 | HTTPアプリケーション | |
443/tcp | k8s-worker0, k8s-worker1 | HTTPSアプリケーション |
Kubernetesクラスタ
MetalLB
仮想VIPの割り当てアドレス範囲 | 備考 |
---|---|
10.0.0.60 - 10.0.0.70 |
Loadbalancerタイプ構成 (MetalLB): アプリケーションの仮想IP
ホスト名 | IP | 備考 |
---|---|---|
apps.test.k8s.local | 10.0.0.60 | MetalLBによる割り当て |
Podネットワークのアドレス範囲
Pod CIDR | 備考 |
---|---|
172.16.0.0/12 |
インフラ設定
proxy.test.k8s.local (コントロールプレーンノード)
$ sudo hostnamectl set-hostname proxy.test.k8s.local
$ sudo nmcli con add type ethernet ifname ens192 con-name k8s ipv4.address 10.0.0.53/24 ipv4.method manual
NATルーター (デフォルトゲートウェイ)
$ cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.ipv4.ip_forward = 1
EOF
$ sudo sysctl --system
$ sudo firewall-cmd --zone=public --add-interface=ens160 --permanent
$ sudo firewall-cmd --zone=public --add-masquerade --permanent
$ sudo firewall-cmd --zone=internal --add-interface=ens192 --permanent
$ sudo firewall-cmd --reload
Squid Cache (プロキシサービス)
$ sudo dnf update -y
$ sudo dnf install squid -y
$ sudo systemctl enable --now squid
$ sudo systemctl status squid
$ sudo firewall-cmd --permanent --add-port=3128/tcp
$ sudo firewall-cmd --reload
$ sudo firewall-cmd --list-ports
lb.test.k8s.local (コントロールプレーンノード)
$ sudo hostnamectl set-hostname lb.test.k8s.local
$ sudo nmcli con add type ethernet ifname ens192 con-name k8s ipv4.address 10.0.0.50/24 ipv4.method manual ipv4.gateway 10.0.0.53
$ sudo nmcli con modify k8s +ipv4.addresses 10.0.0.51/24
$ sudo nmcli con modify k8s +ipv4.addresses 10.0.0.52/24
$ sudo nmcli con up k8s
Dnsmasq (DNSサービス/DHCPサービス)
$ sudo dnf update -y
$ sudo dnf install -y dnsmasq
$ sudo vi /etc/dnsmasq.d/k8s.conf
domain=test.k8s.local
#
# DNS
#
server=8.8.8.8
address=/lb.test.k8s.local/10.0.0.50
address=/proxy.test.k8s.local/10.0.0.53
address=/api.test.k8s.local/10.0.0.51
address=/apps-via-haproxy.test.k8s.local/10.0.0.52
# VIP by MetalLB
address=/apps.test.k8s.local/10.0.0.60
address=/dashboard.test.k8s.local/10.0.0.62
address=/k8s-master0.test.k8s.local/10.0.0.150
address=/k8s-master1.test.k8s.local/10.0.0.151
address=/k8s-master2.test.k8s.local/10.0.0.152
address=/k8s-worker0.test.k8s.local/10.0.0.155
address=/k8s-worker1.test.k8s.local/10.0.0.156
address=/k8s-worker2.test.k8s.local/10.0.0.157
#
# DHCP
#
dhcp-range=10.0.0.100,10.0.0.250,255.255.255.0,12h
dhcp-option=option:router,10.0.0.53
dhcp-option=option:dns-server,10.0.0.50
dhcp-option=option:domain-name,test.k8s.local
dhcp-host=00:0c:29:13:d4:ac,mng,10.0.0.190
dhcp-host=00:50:56:3A:76:23,k8s-master0,10.0.0.150
dhcp-host=00:50:56:38:D6:6F,k8s-master1,10.0.0.151
dhcp-host=00:50:56:26:10:57,k8s-master2,10.0.0.152
dhcp-host=00:50:56:24:5C:22,k8s-worker0,10.0.0.155
dhcp-host=00:50:56:20:B8:67,k8s-worker1,10.0.0.156
dhcp-host=00:0C:29:64:A6:51,k8s-worker2,10.0.0.157
$ sudo firewall-cmd --permanent --add-service=dns
$ sudo firewall-cmd --permanent --add-service=dhcp
$ sudo firewall-cmd --reload
$ sudo firewall-cmd --list-services
$ sudo systemctl enable --now dnsmasq
$ sudo systemctl status dnsmasq
$ sudo nmcli connection modify k8s ipv4.dns "127.0.0.1"
$ sudo nmcli connection modify k8s ipv4.ignore-auto-dns yes
$ nslookup api.test.k8s.local
Server: 127.0.0.1
Address: 127.0.0.1#53
Name: api.test.k8s.local
Address: 10.0.0.51
HA-Proxy (Load Balancerサービス)
$ sudo sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config
システム再起動
$ sudo dnf update -y
$ sudo dnf install -y haproxy
sudo vi /etc/haproxy/haproxy.cfg
とりあえずシンプルに以下で設定しておく。
- 分散方式: ラウンドロビン
- プロトコル: TCP (L4)
#---------------------------------------------------------------------
# Example configuration for a possible web application. See the
# full configuration options online.
#
# https://www.haproxy.org/download/1.8/doc/configuration.txt
#
#---------------------------------------------------------------------
#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
# to have these messages end up in /var/log/haproxy.log you will
# need to:
#
# 1) configure syslog to accept network log events. This is done
# by adding the '-r' option to the SYSLOGD_OPTIONS in
# /etc/sysconfig/syslog
#
# 2) configure local2 events to go to the /var/log/haproxy.log
# file. A line like the following can be added to
# /etc/sysconfig/syslog
#
# local2.* /var/log/haproxy.log
#
log 127.0.0.1 local2
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 4000
user haproxy
group haproxy
daemon
# turn on stats unix socket
stats socket /var/lib/haproxy/stats
# utilize system-wide crypto-policies
ssl-default-bind-ciphers PROFILE=SYSTEM
ssl-default-server-ciphers PROFILE=SYSTEM
#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults
mode tcp
log global
option tcplog
#option dontlognull
#option http-server-close
#option forwardfor except 127.0.0.0/8
#option redispatch
retries 3
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 1m
timeout server 1m
timeout http-keep-alive 10s
timeout check 10s
maxconn 3000
frontend api_frontend
bind *:6443
default_backend api_backend
backend api_backend
balance roundrobin
mode tcp
option tcp-check
server k8s-master0 k8s-master0.test.k8s.local:6443 check
server k8s-master1 k8s-master1.test.k8s.local:6443 check
server k8s-master2 k8s-master2.test.k8s.local:6443 check
frontend app_http
bind 10.0.0.52:80
default_backend app_http_backend
backend app_http_backend
balance roundrobin
mode tcp
option tcp-check
server k8s-worker0 k8s-worker0.test.k8s.local:30080 check
server k8s-worker1 k8s-worker1.test.k8s.local:30080 check
server k8s-worker2 k8s-worker2.test.k8s.local:30080 check
frontend app_https
bind 10.0.0.52:443
default_backend app_https_backend
backend app_https_backend
balance roundrobin
mode tcp
option tcp-check
server k8s-worker0 k8s-worker0.test.k8s.local:30443 check
server k8s-worker1 k8s-worker1.test.k8s.local:30443 check
server k8s-worker2 k8s-worker2.test.k8s.local:30443 chec
$ sudo systemctl enable --now haproxy
$ sudo systemctl status haproxy
$ sudo firewall-cmd --permanent --add-port=6443/tcp # Kubernetes API (コントロールプレーンノード)
$ sudo firewall-cmd --permanent --add-port=80/tcp
$ sudo firewall-cmd --permanent --add-port=443/tcp
$ sudo firewall-cmd --reload
$ sudo firewall-cmd --list-ports
k8s-master0/1/2.test.k8s.local (コントロールプレーンノード)
# DHCPサーバからホスト名を設定
$ sudo hostnamectl set-hostname ""
システム再起動
$ sudo sed -i '/ swap / s/^/#/' /etc/fstab
システム再起動
$ free
total used free shared buff/cache available
Mem: 1984984 279736 1538352 17112 166896 1538140
Swap: 0
とりあえず・・・
$ sudo sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config (無効化)
システム再起動
$ getenforce
Permissive
$ cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
システム再起動
$ sudo lsmod | grep br_netfilter
br_netfilter 28672 0
bridge 294912 1 br_netfilter
$ sudo lsmod | grep overlay
overlay 139264 0
デフォルトで以下になる様子
$ sudo sysctl -a |grep net.bridge.bridge-nf-call-
net.bridge.bridge-nf-call-arptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
$ cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.ipv4.ip_forward = 1
EOF
$ sudo sysctl --system
$ sudo sysctl -a |grep net.ipv4.ip_forward
net.ipv4.ip_forward = 1
$ sudo firewall-cmd --add-port=6443/tcp --permanent
$ sudo firewall-cmd --add-port=2379-2380/tcp --permanent
$ sudo firewall-cmd --add-port=10250/tcp --permanent
$ sudo firewall-cmd --add-port=10259/tcp --permanent
$ sudo firewall-cmd --add-port=10257/tcp --permanent
$ sudo firewall-cmd --reload
$ firewall-cmd --list-ports
またはとりあえず・・・
$ sudo systemctl disable --now firewalld (無効化)
$ systemctl status firewalld
k8s-worker0/1.test.k8s.local (ワーカーノード)
# DHCPサーバからホスト名を設定
$ sudo hostnamectl set-hostname ""
システム再起動
$ sudo sed -i '/ swap / s/^/#/' /etc/fstab
システム再起動
$ free
total used free shared buff/cache available
Mem: 1984984 279736 1538352 17112 166896 1538140
Swap: 0
とりあえず・・・
$ sudo sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config (無効化)
システム再起動
$ getenforce
Permissive
$ cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
システム再起動
$ sudo lsmod | grep br_netfilter
br_netfilter 28672 0
bridge 294912 1 br_netfilter
$ sudo lsmod | grep overlay
overlay 139264 0
デフォルトで以下になる様子
$ sudo sysctl -a |grep net.bridge.bridge-nf-call-
net.bridge.bridge-nf-call-arptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
$ cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.ipv4.ip_forward = 1
EOF
$ sudo sysctl --system
$ sudo sysctl -a |grep net.ipv4.ip_forward
net.ipv4.ip_forward = 1
$ sudo firewall-cmd --add-port=10250/tcp --permanent
$ sudo firewall-cmd --add-port=10256/tcp --permanent
$ sudo firewall-cmd --add-port=30000-32767/tcp --permanent
$ sudo firewall-cmd --reload
$ sudo firewall-cmd --list-ports
またはとりあえず・・・
$ sudo systemctl disable --now firewalld (無効化)
$ systemctl status firewalld
Kubernetesクラスタのセットアップ
k8s-master0/1/2.test.k8s.local (コントロールプレーンノード共通)
コンテナランタイムをインストール
※ Kubernetes(= 1.32)のバージョンと合わせる。もしCRI-Oバージョンに該当のものがまだない場合にはKubernetesのバージョンの方を合わせる。
$ export CRIO_VERSION=v1.32
$ cat <<EOF | sudo tee /etc/yum.repos.d/cri-o.repo
[cri-o]
name=CRI-O
baseurl=https://download.opensuse.org/repositories/isv:/cri-o:/stable:/$CRIO_VERSION/rpm/
enabled=1
gpgcheck=1
gpgkey=https://download.opensuse.org/repositories/isv:/cri-o:/stable:/$CRIO_VERSION/rpm/repodata/repomd.xml.key
EOF
$ sudo dnf install -y container-selinux
$ sudo dnf install -y cri-o
$ sudo dnf install -y podman
$ sudo systemctl enable --now crio
$ sudo systemctl status crio
$ ls -l /var/run/crio/crio.sock
srw-rw----. 1 root root 0 May 2 03:06 /var/run/crio/crio.sock
Kubernetesをインストール
$ export KUBERNETES_VERSION=v1.32
$ cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/$KUBERNETES_VERSION/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/$KUBERNETES_VERSION/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF
$ sudo dnf install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
$ sudo systemctl enable --now kubelet
$ sudo systemctl status kubelet
k8s-master0.test.k8s.local (コントロールプレーンノード)
kubeadm initでクラスタを初期化
$ sudo kubeadm init --control-plane-endpoint "api.test.k8s.local:6443" --upload-certs --pod-network-cidr=172.16.0.0/12 --kubernetes-version 1.32.4
...
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/
You can now join any number of control-plane nodes running the following command on each as root:
kubeadm join api.test.k8s.local:6443 --token xxxx \
--discovery-token-ca-cert-hash sha256:yyyy \
--control-plane --certificate-key zzzzz
Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join api.test.k8s.local:6443 --token aaaaa \
--discovery-token-ca-cert-hash sha256:bbbb
上記の出力内容(「kubeadm join ...」)は後で他のコントロールプレーンノードとワーカーノードを追加する際に使うので保存しておく。
※ kubeadmコマンドでは「--apiserver-advertise-address 10.0.0.150 (このノードのIP)」もネットワークインターフェースが複数ある場合には指定してもよいかも。
$ systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; preset: disabled)
Drop-In: /usr/lib/systemd/system/kubelet.service.d
mq10-kubeadm.conf
Active: active (running) since Sat 2025-05-03 06:22:51 EDT; 9min ago
Docs: https://kubernetes.io/docs/
Main PID: 2579 (kubelet)
Tasks: 10 (limit: 10868)
Memory: 38.0M
CPU: 12.093s
CGroup: /system.slice/kubelet.service
+- /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --container-runtime-endpoint=unix:///var/run/crio/crio.sock --pod-infra-container-image=registry.k8s.io/pause:3.10
※ Active: active (running)になっていればOK。
$ 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 nodes
NAME STATUS ROLES AGE VERSION
k8s-master0 NotReady control-plane 65s v1.32.4
※ まだCNIインストールしていないので「kubectl get nodes」のSTATUSはNotReadyのまま。
CNI - Calicoのインストール
CNIは1台目のコントロールプレーンノードにのみインストールすればOK。
まずはマニフェスト方式でインストールする(必要に応じてオペレータ方式へ移行)。
- Manifest - "Install Calico with Kubernetes API datastore, 50 nodes or less"
※ 本記事の構成ではTyphaは使わない。
$ curl https://raw.githubusercontent.com/projectcalico/calico/v3.29.3/manifests/calico-typha.yaml -o calico.yaml
vi calico.yaml
...
- name: CALICO_IPV4POOL_CIDR
value: "172.16.0.0/12"
...
※ kubeadm initコマンド時に指定した「--pod-network-cidr=172.16.0.0/12」と同じサブネットアドレスをCIDR指定しておく(念のため)。しなくてもよいかも。
$ kubectl apply -f calico.yaml
$ kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system calico-kube-controllers-79949b87d-6q86g 1/1 Running 0 16m
kube-system calico-node-xxxxxx 1/1 Running 0 16m
kube-system calico-typha-87cb5c68d-pcwd5 1/1 Running 0 16m
kube-system coredns-668d6bf9bc-2n9f4 1/1 Running 0 76m
kube-system coredns-668d6bf9bc-tmfm8 1/1 Running 0 76m
kube-system etcd-k8s-master0 1/1 Running 1 76m
kube-system kube-apiserver-k8s-master0 1/1 Running 1 76m
kube-system kube-controller-manager-k8s-master0 1/1 Running 1 76m
kube-system kube-proxy-nbhbv 1/1 Running 1 76m
kube-system kube-scheduler-k8s-master0 1/1 Running 1 76m
(少々待ってから)calico-node-xxxxxxがRunningならOK。
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master0 Ready control-plane 76m v1.32.4
STATUS: ReadyになっていればOK。
$ curl -v -k https://api.test.k8s.local:6443/
...
HTTPエラー応答が返ってきていればOK
...
k8s-master1.test.k8s.local (コントロールプレーンノード)
kubeadm joinでクラスタへ追加
$ kubeadm join api.test.k8s.local:6443 --token xxxx --discovery-token-ca-cert-hash sha256:yyyy --control-plane --certificate-key zzzzz
$ kubeadm join ...
...
[download-certs] Downloading the certificates in Secret "kubeadm-certs" in the "kube-system" Namespace
error execution phase control-plane-prepare/download-certs: error downloading certs: error downloading the secret: Secret "kubeadm-certs" was not found in the "kube-system" Namespace. This Secret might have expired. Please, run `kubeadm init phase upload-certs --upload-certs` on a control plane to generate a new one
To see the stack trace of this error execute with --v=5 or higher
$ sudo kubeadm init phase upload-certs --upload-certs
...
[upload-certs] Storing the certificates in Secret "kubeadm-certs" in the "kube-system" Namespace
[upload-certs] Using certificate key:
new_key_zzzzz
$ kubeadm join api.test.k8s.local:6443 --token xxxx --discovery-token-ca-cert-hash sha256:yyyy --control-plane --certificate-key new_key_zzzzz
$ 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 nodes
NAME STATUS ROLES AGE VERSION
k8s-master0 Ready control-plane 142m v1.32.4
k8s-master1 Ready control-plane 85s v1.32.4
STATUSがReadyになっていればOK。
k8s-master2.test.k8s.local (コントロールプレーンノード)
kubeadm joinでクラスタへ追加
$ kubeadm join api.test.k8s.local:6443 --token xxxx --discovery-token-ca-cert-hash sha256:yyyy --control-plane --certificate-key zzzzz
$ 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 nodes
NAME STATUS ROLES AGE VERSION
k8s-master0 Ready control-plane 154m v1.32.4
k8s-master1 Ready control-plane 13m v1.32.4
k8s-master2 Ready control-plane 3m33s v1.32.4
STATUSがReadyになっていればOK。
k8s-worker0/1.test.k8s.local (ワーカーノード共通)
コンテナランタイムをインストール
$ export CRIO_VERSION=v1.32
$ cat <<EOF | sudo tee /etc/yum.repos.d/cri-o.repo
[cri-o]
name=CRI-O
baseurl=https://download.opensuse.org/repositories/isv:/cri-o:/stable:/$CRIO_VERSION/rpm/
enabled=1
gpgcheck=1
gpgkey=https://download.opensuse.org/repositories/isv:/cri-o:/stable:/$CRIO_VERSION/rpm/repodata/repomd.xml.key
EOF
$ sudo dnf install -y container-selinux
$ sudo dnf install -y cri-o
$ sudo dnf install -y podman
$ sudo systemctl enable --now crio
$ sudo systemctl status crio
$ ls -l /var/run/crio/crio.sock
srw-rw----. 1 root root 0 May 2 03:06 /var/run/crio/crio.sock
Kubernetesをインストール
$ export KUBERNETES_VERSION=v1.32
$ cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/$KUBERNETES_VERSION/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/$KUBERNETES_VERSION/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF
$ sudo dnf install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
$ sudo systemctl enable --now kubelet
$ sudo systemctl status kubelet
kubeadm joinでクラスタへ追加
$ kubeadm join api.test.k8s.local:6443 --token aaaaa --discovery-token-ca-cert-hash sha256:bbbb
※ tokenはコントロールプレーンノードで「sudo kubeadm token list」でも参照できる。
コントロールプレーンノードで確認
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master0 Ready control-plane 3h14m v1.32.4
k8s-master1 Ready control-plane 53m v1.32.4
k8s-master2 Ready control-plane 43m v1.32.4
k8s-worker0 Ready <none> 10m v1.32.4
k8s-worker1 Ready <none> 9m27s v1.32.4
STATUSがReadyならOK。joinしただけではROLESが「none」で表示される。
$ kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system calico-kube-controllers-79949b87d-6q86g 1/1 Running 1 134m
kube-system calico-node-2qc5q 1/1 Running 0 52m
kube-system calico-node-cljll 1/1 Running 0 42m
kube-system calico-node-kxnw4 1/1 Running 1 134m
kube-system calico-node-l9vrw 1/1 Running 0 9m45s
kube-system calico-node-ntndk 1/1 Running 0 8m55s
kube-system calico-typha-87cb5c68d-pcwd5 1/1 Running 1 134m
kube-system coredns-668d6bf9bc-2n9f4 1/1 Running 1 3h13m
kube-system coredns-668d6bf9bc-tmfm8 1/1 Running 1 3h13m
kube-system etcd-k8s-master0 1/1 Running 2 3h13m
kube-system etcd-k8s-master1 1/1 Running 0 52m
kube-system etcd-k8s-master2 1/1 Running 0 42m
kube-system kube-apiserver-k8s-master0 1/1 Running 2 3h13m
kube-system kube-apiserver-k8s-master1 1/1 Running 0 52m
kube-system kube-apiserver-k8s-master2 1/1 Running 0 42m
kube-system kube-controller-manager-k8s-master0 1/1 Running 2 3h13m
kube-system kube-controller-manager-k8s-master1 1/1 Running 0 52m
kube-system kube-controller-manager-k8s-master2 1/1 Running 0 42m
kube-system kube-proxy-6r9kf 1/1 Running 0 8m55s
kube-system kube-proxy-b97jk 1/1 Running 0 9m45s
kube-system kube-proxy-lxdnz 1/1 Running 0 42m
kube-system kube-proxy-nbhbv 1/1 Running 2 3h13m
kube-system kube-proxy-ng8r8 1/1 Running 0 52m
kube-system kube-scheduler-k8s-master0 1/1 Running 2 3h13m
kube-system kube-scheduler-k8s-master1 1/1 Running 0 52m
kube-system kube-scheduler-k8s-master2 1/1 Running 0 42m
$ kubectl get pods -n kube-system -o wide | grep calico
calico-kube-controllers-79949b87d-6q86g 1/1 Running 1 144m 192.168.70.134 k8s-master0 <none> <none>
calico-node-2qc5q 1/1 Running 0 63m 10.0.0.151 k8s-master1 <none> <none>
calico-node-cljll 1/1 Running 0 52m 10.0.0.152 k8s-master2 <none> <none>
calico-node-kxnw4 1/1 Running 1 144m 10.0.0.150 k8s-master0 <none> <none>
calico-node-l9vrw 1/1 Running 0 20m 10.0.0.155 k8s-worker0 <none> <none>
calico-node-ntndk 1/1 Running 0 19m 10.0.0.156 k8s-worker1 <none> <none>
calico-typha-87cb5c68d-pcwd5 1/1 Running 1 144m 10.0.0.150 k8s-master0 <none> <none>
※ 各ノードの詳細は「kubectl describe node ノード名」で参照できる。
mng.test.k8s.local (管理端末)
kubectlをインストール
[mng ~]$ export KUBERNETES_VERSION=v1.32
[mng ~]$ cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/$KUBERNETES_VERSION/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/$KUBERNETES_VERSION/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF
[mng ~]$ sudo dnf install -y kubectl --disableexcludes=kubernetes
[mng ~]$ mkdir -p ~/.kube
[mng ~]$ scp root@10.0.0.150:/etc/kubernetes/admin.conf ~/.kube/config
[mng ~]$ chmod 600 .kube/config
[mng ~]$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master0 Ready control-plane 18h v1.32.4
k8s-master1 Ready control-plane 15h v1.32.4
k8s-master2 Ready control-plane 15h v1.32.4
k8s-worker0 Ready <none> 15h v1.32.4
k8s-worker1 Ready <none> 15h v1.32.4
calicoctlを取得
[mng ~]$ curl -L https://github.com/projectcalico/calico/releases/download/v3.29.3/calicoctl-linux-amd64 -o calicoctl
[mng ~]$ chmod +x ./calicoctl
[mng ~]$ ./calicoctl get nodes -o wide
NAME ASN IPV4 IPV6
k8s-master0 (64512) 10.0.0.150/24
k8s-master1 (64512) 10.0.0.151/24
k8s-master2 (64512) 10.0.0.152/24
k8s-worker0 (64512) 10.0.0.155/24
k8s-worker1 (64512) 10.0.0.156/24
[mng ~]$ ./calicoctl get ippools -o yaml
apiVersion: projectcalico.org/v3
items:
- apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
creationTimestamp: "2025-05-04T17:51:48Z"
name: default-ipv4-ippool
resourceVersion: "1139"
uid: 73d5b037-5d18-455d-850f-5b25685f2f2f
spec:
allowedUses:
- Workload
- Tunnel
blockSize: 26
cidr: 172.16.0.0/12
ipipMode: Always
natOutgoing: true
nodeSelector: all()
vxlanMode: Never
kind: IPPoolList
metadata:
resourceVersion: "45272"
※デフォルトではPod間通信に対してIPIPモードがAlways、natOutgoingが有効。
[mng ~]$ kubectl get pods -A -o wide |grep calico-node |grep k8s-worker
kube-system calico-node-6kknp 1/1 Running 2 11h 10.0.0.155 k8s-worker0 <none> <none>
kube-system calico-node-zmc92 1/1 Running 2 11h 10.0.0.156 k8s-worker1 <none> <none>
※ calico-node pod: Podネットワーク上でのLinuxカーネル(ルーティング/netfilter)へのIPレイヤのルーティング設定、NAT設定、ネットワークポリシー設定を実行するまたはBGPを話し連携することもできる仮想ルーター的なポッド
[mng ~]$ kubectl exec -n kube-system -it calico-node-6kknp -- ip a
Defaulted container "calico-node" out of: calico-node, upgrade-ipam (init), install-cni (init), mount-bpffs (init)
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:50:56:24:5c:22 brd ff:ff:ff:ff:ff:ff
altname enp3s0
inet 10.0.0.155/24 brd 10.0.0.255 scope global dynamic noprefixroute ens160
valid_lft 37955sec preferred_lft 37955sec
inet6 fe80::250:56ff:fe24:5c22/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
inet 172.23.229.128/32 scope global tunl0
valid_lft forever preferred_lft forever
4: calic98d3ed38a4@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default qlen 1000
link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::ecee:eeff:feee:eeee/64 scope link
valid_lft forever preferred_lft forever
※ tunl0@NONE: Podネットワークインタフェース
※ ens160: 物理ネットワークインタフェース
[mng ~]$ kubectl exec -n kube-system -it calico-node-6kknp -- ip ro
Defaulted container "calico-node" out of: calico-node, upgrade-ipam (init), install-cni (init), mount-bpffs (init)
default via 10.0.0.53 dev ens160 proto dhcp src 10.0.0.155 metric 100
10.0.0.0/24 dev ens160 proto kernel scope link src 10.0.0.155 metric 100
172.23.229.130 dev calic98d3ed38a4 scope link
172.16.35.128/26 via 10.0.0.150 dev tunl0 proto bird onlink
172.20.194.64/26 via 10.0.0.156 dev tunl0 proto bird onlink
172.25.115.64/26 via 10.0.0.152 dev tunl0 proto bird onlink
172.26.159.128/26 via 10.0.0.151 dev tunl0 proto bird onlink
blackhole 172.23.229.128/26 proto bird
[mng ~]$ kubectl exec -n kube-system -it calico-node-zmc92 -- ip a
Defaulted container "calico-node" out of: calico-node, upgrade-ipam (init), install-cni (init), mount-bpffs (init)
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:50:56:20:b8:67 brd ff:ff:ff:ff:ff:ff
altname enp3s0
inet 10.0.0.156/24 brd 10.0.0.255 scope global dynamic noprefixroute ens160
valid_lft 37734sec preferred_lft 37734sec
inet6 fe80::250:56ff:fe20:b867/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
inet 172.20.194.64/32 scope global tunl0
valid_lft forever preferred_lft forever
4: calie0bb82fb17d@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default qlen 1000
link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::ecee:eeff:feee:eeee/64 scope link
valid_lft forever preferred_lft forever
$ kubectl exec -n kube-system -it calico-node-zmc92 -- ip ro
Defaulted container "calico-node" out of: calico-node, upgrade-ipam (init), install-cni (init), mount-bpffs (init)
default via 10.0.0.53 dev ens160 proto dhcp src 10.0.0.156 metric 100
10.0.0.0/24 dev ens160 proto kernel scope link src 10.0.0.156 metric 100
172.20.194.66 dev calie0bb82fb17d scope link
172.16.35.128/26 via 10.0.0.150 dev tunl0 proto bird onlink
172.23.229.128/26 via 10.0.0.155 dev tunl0 proto bird onlink
172.25.115.64/26 via 10.0.0.152 dev tunl0 proto bird onlink
172.26.159.128/26 via 10.0.0.151 dev tunl0 proto bird onlink
blackhole 172.20.194.64/26 proto bird
ディプロイ確認 (HA-Proxy + NodePort + nginx)
アプリケーション通信についてまずはHA-Proxy + NodePort構成で分散する環境においてディプロイ確認を実施する。作業は管理端末(mng.test.k8s.local)から行う。
nginxのディプロイ
$ vi nginx-deployment.yaml
apiVersion: apps/v1 # Deploymentはapps/v1 APIグループを使う
kind: Deployment # リソースの種類はDeployment
metadata:
name: nginx-test # Deploymentの名前(kubectlで識別する名前)
spec:
replicas: 2 # 起動するPodの数(= レプリカ数)
selector: # Deploymentが管理するPodの条件
matchLabels:
app: nginx-test # このラベルを持つPodが対象になる
template: # Podテンプレート(新しく作るPodの定義)
metadata:
labels:
app: nginx-test # Podに付与するラベル(Serviceとマッチさせるために必要)
spec:
containers:
- name: nginx # コンテナ名(Pod内で識別)
image: nginx # 使用するDockerイメージ(Docker Hubの公式nginx)
ports:
- containerPort: 80 # コンテナがリッスンするポート(nginxのHTTPデフォルト)(= targetPort)
---
apiVersion: v1 # Serviceはv1 APIグループを使う
kind: Service # リソースの種類はService
metadata:
name: nginx-service # Serviceの名前(kubectlで識別する名前)
spec:
type: NodePort # 外部アクセス可能なNodePortタイプ
selector:
app: nginx-test # 対象のPodをラベルで指定(Deploymentと一致させる)
ports:
- port: 80 # Serviceが受け付ける仮想ポート(Cluster内用)
targetPort: 80 # 実際にPod側でリッスンしているポート
nodePort: 30080 # ノードIP+このポートで外部公開(http://<NodeIP>:30080)
[mng ~]$ kubectl apply -f nginx-deployment.yaml
[mng ~]$ kubectl get svc nginx-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service NodePort 10.105.109.0 <none> 80:30080/TCP 7m24s
[mng ~]$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-test-598898876f-lgdj8 1/1 Running 1 10h 172.20.194.66 k8s-worker1 <none> <none>
nginx-test-598898876f-xb9l5 1/1 Running 1 10h 172.23.229.130 k8s-worker0 <none> <none>
$ kubectl get endpoints
NAMESPACE NAME ENDPOINTS AGE
default kubernetes 10.0.0.150:6443,10.0.0.151:6443,10.0.0.152:6443 11h
default nginx-service 172.20.194.66:80,172.23.229.130:80 10h
外部クライアントからのHTTPアクセスを確認
※ とりあえず管理端末(mng.test.k8s.local)からアクセスする。
[mng ~]$ curl -v http://apps-via-haproxy.test.k8s.local/
* Rebuilt URL to: http://apps-via-haproxy.test.k8s.local/
* Trying 10.0.0.52...
* TCP_NODELAY set
* Connected to apps-via-haproxy.test.k8s.local (10.0.0.52) port 80 (#0)
> GET / HTTP/1.1
> Host: apps-via-haproxy.test.k8s.local
> User-Agent: curl/7.61.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.27.5
< Date: Sun, 04 May 2025 18:54:41 GMT
< Content-Type: text/html
< Content-Length: 615
< Last-Modified: Wed, 16 Apr 2025 12:01:11 GMT
< Connection: keep-alive
< ETag: "67ff9c07-267"
< Accept-Ranges: bytes
<
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</body>
</html>
[mng ~]$ kubectl get pods -l app=nginx-test
NAME READY STATUS RESTARTS AGE
nginx-test-598898876f-lgdj8 1/1 Running 1 13h
nginx-test-598898876f-xb9l5 1/1 Running 1 13h
[mng ~]$ kubectl logs nginx-test-598898876f-lgdj8
...
172.23.229.128 - - [05/May/2025:04:42:39 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-"
172.23.229.128 - - [05/May/2025:04:54:43 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-"
...
[mng ~]$ curl -v http://k8s-worker0.test.k8s.local:30080/
※ ワーカーノードでconntrackを生成するために管理端末から直接ワーカーノードのアドレス/Portを指定して実施
k8s-worker0のPodへ転送された場合:
[k8s-worker0 ~]$ sudo conntrack -L
tcp 6 114 TIME_WAIT src=10.0.0.190 dst=10.0.0.155 sport=46226 dport=30080 src=172.23.229.130 dst=10.0.0.155 sport=80 dport=8714 [ASSURED] mark=0 secctx=system_u:object_r:unlabeled_t:s0 use=1
行き:
src=10.0.0.190 dst=10.0.0.155 sport=46226 dport=30080
戻り:
src=172.23.229.130 dst=10.0.0.155 sport=80 dport=8714
コネクション状態: ASSURED
k8s-worker1のPodへ転送された場合:
[k8s-worker0 ~]$ sudo conntrack -L
tcp 6 112 TIME_WAIT src=10.0.0.190 dst=10.0.0.155 sport=45444 dport=30080 src=172.20.194.66 dst=172.23.229.128 sport=80 dport=4373 [ASSURED] mark=0 secctx=system_u:object_r:unlabeled_t:s0 use=1
行き:
src=10.0.0.190 dst=10.0.0.155 sport=45444 dport=30080
戻り:
src=172.20.194.66 dst=172.23.229.128 sport=80 dport=4373
コネクション状態: ASSURED
※ ASSUREDならとりあえずOK
MetalLBのセットアップ
アプリケーション通信の冗長化のためにMetalLBをL2モードでセットアップする。
事前確認
[mng ~]$kubectl -n kube-system get pods -l k8s-app=kube-proxy
NAME READY STATUS RESTARTS AGE
kube-proxy-84t4k 1/1 Running 2 14h
kube-proxy-9wrbg 1/1 Running 2 14h
kube-proxy-br8j4 1/1 Running 2 14h
kube-proxy-v8xd5 1/1 Running 2 14h
kube-proxy-vnmqt 1/1 Running 2 14h
[mng ~]$ kubectl -n kube-system logs kube-proxy-84t4k |grep Using
I0505 04:30:50.027916 1 server_linux.go:66] "Using iptables proxy"
I0505 04:30:50.293854 1 server_linux.go:170] "Using iptables Proxier"
※ kube-proxyがipvsモードでなく(デフォルトの)iptablesモードで動作中なのでstrictARP設定はスキップ。
CNI/CalicoのIPIPおよびVXLANモード(Pod間通信のトンネリング)を無効化
ノードは同一サブネットに配置し、MetalLBはL2モードで動作させるのでIPIPモードおよびVXLANモードを無効化。
[mng ~]$ calicoctl patch ippool default-ipv4-ippool -p '{"spec": {"ipipMode": "Never", "vxlanMode": "Never"}}'
[mng ~]$ ./calicoctl get ippool default-ipv4-ippool -o yaml
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
creationTimestamp: "2025-05-04T17:51:48Z"
name: default-ipv4-ippool
resourceVersion: "4048"
uid: 73d5b037-5d18-455d-850f-5b25685f2f2f
spec:
allowedUses:
- Workload
- Tunnel
blockSize: 26
cidr: 172.16.0.0/12
ipipMode: Never
natOutgoing: true
nodeSelector: all()
vxlanMode: Never
- name: CALICO_IPV4POOL_IPIP
value: "Never"
- name: CALICO_IPV4POOL_VXLAN
value: "Never"
MetalLBインストール
[mng ~]$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml
[mng ~]$ kubectl get ns metallb-system
NAME STATUS AGE
metallb-system Active 2m8s
[mng ~]$ kubectl get pods -n metallb-system
NAME READY STATUS RESTARTS AGE
controller-bb5f47665-zcl8v 1/1 Running 0 2m17s
speaker-glk7p 1/1 Running 0 2m16s
speaker-m62rc 1/1 Running 0 2m16s
speaker-m9bdh 1/1 Running 0 2m17s
speaker-wwqfd 1/1 Running 0 2m17s
speaker-xzscq 1/1 Running 0 2m17s
[mng ~]$ kubectl get secret -A
NAMESPACE NAME TYPE DATA AGE
kube-system bootstrap-token-l6pmgm bootstrap.kubernetes.io/token 7 17h
metallb-system memberlist Opaque 1 13m
metallb-system metallb-webhook-cert Opaque 4 13m
※ memberlist secretが自動で作成されていないようなら以下のように作成
[mng ~]$ kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)"
[mng ~]$ vi metallb-config.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: vip-pool
namespace: metallb-system
spec:
addresses:
- 10.0.0.60-10.0.0.70
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: l2-adv
namespace: metallb-system
spec:
ipAddressPools:
- vip-pool
[mng ~]$ kubectl apply -f metallb-config.yaml
[mng ~]$ kubectl get ipaddresspool -n metallb-system
NAME AUTO ASSIGN AVOID BUGGY IPS ADDRESSES
vip-pool true false ["10.0.0.60-10.0.0.70"]
[mng ~]$ kubectl get l2advertisement -n metallb-system
NAME IPADDRESSPOOLS IPADDRESSPOOL SELECTORS INTERFACES
l2-adv ["vip-pool"]
ディプロイ確認 (MetalLB + nginx)
nginxのディプロイ
[mng ~]$ vi nginx-metallb-deployment.yaml
# Deployment: nginx アプリケーションのPodを定義
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-test # Deploymentリソースの名前
spec:
replicas: 2 # Podのレプリカ数(2つ起動)
selector:
matchLabels:
app: nginx-test # このラベルに一致するPodを対象とする
template:
metadata:
labels:
app: nginx-test # このPodが持つラベル
spec:
containers:
- name: nginx # コンテナの名前
image: nginx # 使用するnginxイメージ
ports:
- containerPort: 80 # コンテナ内で開くポート番号
---
# Service: nginx Pod にアクセスするための LoadBalancer型サービス
apiVersion: v1
kind: Service
metadata:
name: nginx-service # Serviceリソースの名前
spec:
selector:
app: nginx-test # 上記DeploymentのPodと一致するラベル
type: LoadBalancer # MetalLBにEXTERNAL-IPを割り当てさせるタイプ
ports:
- protocol: TCP
port: 80 # クライアントがアクセスするポート
targetPort: 80 # Pod内のコンテナに転送するポート
[mng ~]$ kubectl apply -f nginx-metallb-deployment.yaml
[mng ~]$ kubectl get deploy nginx-test
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-test 1/2 2 1 17s
[mng ~]$ kubectl get pods -l app=nginx-test
NAME READY STATUS RESTARTS AGE
nginx-test-598898876f-j6j4q 1/1 Running 0 25s
nginx-test-598898876f-t28rg 1/1 Running 0 25s
[mng ~]$ kubectl get svc nginx-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service LoadBalancer 10.97.120.8 10.0.0.60 80:30224/TCP 2m17s
※ EXTERNAL-IP(10.0.0.60 )が割り当てられた仮想IP
外部クライアントからのHTTPアクセスを確認
※ とりあえず管理端末(mng.test.k8s.local)からアクセスする。
[mng ~]$ curl -v http://10.0.0.60/
* Trying 10.0.0.60...
* TCP_NODELAY set
* Connected to 10.0.0.60 (10.0.0.60) port 80 (#0)
> GET / HTTP/1.1
> Host: 10.0.0.60
> User-Agent: curl/7.61.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.27.5
< Date: Mon, 05 May 2025 11:39:12 GMT
< Content-Type: text/html
< Content-Length: 615
< Last-Modified: Wed, 16 Apr 2025 12:01:11 GMT
< Connection: keep-alive
< ETag: "67ff9c07-267"
< Accept-Ranges: bytes
<
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</html>
[mng ~]$ curl -v http://apps.test.k8s.local/
同様
[mng ~]$ kubectl get pods -l app=nginx-test
NAME READY STATUS RESTARTS AGE
nginx-test-598898876f-j6j4q 1/1 Running 0 8m3s
nginx-test-598898876f-t28rg 1/1 Running 0 8m3s
[mng ~]$ kubectl logs nginx-test-598898876f-j6j4q
...
10.0.0.155 - - [05/May/2025:11:39:12 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-"
10.0.0.155 - - [05/May/2025:11:55:52 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-"
ワーカーノードを追加
k8s-worker2.test.k8s.local
ワーカーノード(k8s-worker2.test.k8s.local)を追加する。他のワーカーノードのセットアップと共通の手順を実施。
コンテナランタイムをインストール
$ export CRIO_VERSION=v1.32
$ cat <<EOF | sudo tee /etc/yum.repos.d/cri-o.repo
[cri-o]
name=CRI-O
baseurl=https://download.opensuse.org/repositories/isv:/cri-o:/stable:/$CRIO_VERSION/rpm/
enabled=1
gpgcheck=1
gpgkey=https://download.opensuse.org/repositories/isv:/cri-o:/stable:/$CRIO_VERSION/rpm/repodata/repomd.xml.key
EOF
$ sudo dnf install -y container-selinux
$ sudo dnf install -y cri-o
$ sudo dnf install -y podman
$ sudo systemctl enable --now crio
$ sudo systemctl status crio
$ ls -l /var/run/crio/crio.sock
srw-rw----. 1 root root 0 May 2 03:06 /var/run/crio/crio.sock
Kubernetesをインストール
$ export KUBERNETES_VERSION=v1.32
$ cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/$KUBERNETES_VERSION/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/$KUBERNETES_VERSION/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF
$ sudo dnf install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
$ sudo systemctl enable --now kubelet
$ sudo systemctl status kubelet
[mng ~]$ kubectl get pods -A -o wide | grep k8s-worker
kube-system calico-node-6kknp 1/1 Running 3 2d18h 10.0.0.155 k8s-worker0 <none> <none>
kube-system calico-node-zmc92 1/1 Running 3 2d18h 10.0.0.156 k8s-worker1 <none> <none>
kube-system kube-proxy-9wrbg 1/1 Running 3 2d18h 10.0.0.155 k8s-worker0 <none> <none>
kube-system kube-proxy-vnmqt 1/1 Running 3 2d18h 10.0.0.156 k8s-worker1 <none> <none>
metallb-system controller-bb5f47665-zcl8v 1/1 Running 1 2d2h 172.20.194.66 k8s-worker1 <none> <none>
metallb-system speaker-glk7p 1/1 Running 2 (50s ago) 2d2h 10.0.0.156 k8s-worker1 <none> <none>
metallb-system speaker-xzscq 1/1 Running 2 (50s ago) 2d2h 10.0.0.155 k8s-worker0 <none> <none>
コントロールプレーンノードでトークンを再作成
k8s-master0.test.k8s.localで作業する。
[k8s-master0 ~]$ kubeadm token create --print-join-command
kubeadm join api.test.k8s.local:6443 --token aaaaa --discovery-token-ca-cert-hash sha256:bbbb
ワーカーノードをクラスタへ追加
k8s-worker2.test.k8s.localで作業する。
[k8s-worker2 ~]$ kubeadm join api.test.k8s.local:6443 --token aaaaa --discovery-token-ca-cert-hash sha256:bbbb
[mng ~]$ kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-master0 Ready control-plane 2d19h v1.32.4 10.0.0.150 <none> Red Hat Enterprise Linux 9.5 (Plow) 5.14.0-503.40.1.el9_5.x86_64 cri-o://1.32.4
k8s-master1 Ready control-plane 2d18h v1.32.4 10.0.0.151 <none> Red Hat Enterprise Linux 9.5 (Plow) 5.14.0-503.40.1.el9_5.x86_64 cri-o://1.32.4
k8s-master2 Ready control-plane 2d18h v1.32.4 10.0.0.152 <none> Red Hat Enterprise Linux 9.5 (Plow) 5.14.0-503.40.1.el9_5.x86_64 cri-o://1.32.4
k8s-worker0 Ready <none> 2d18h v1.32.4 10.0.0.155 <none> Red Hat Enterprise Linux 9.5 (Plow) 5.14.0-503.40.1.el9_5.x86_64 cri-o://1.32.4
k8s-worker1 Ready <none> 2d18h v1.32.4 10.0.0.156 <none> Red Hat Enterprise Linux 9.5 (Plow) 5.14.0-503.40.1.el9_5.x86_64 cri-o://1.32.4
k8s-worker2 Ready <none> 17m v1.32.4 10.0.0.157 <none> Red Hat Enterprise Linux 9.5 (Plow) 5.14.0-503.40.1.el9_5.x86_64 cri-o://1.32.4
[mng ~]$ kubectl get pods -A -o wide | grep k8s-worker
kube-system calico-node-6kknp 1/1 Running 3 2d19h 10.0.0.155 k8s-worker0 <none> <none>
kube-system calico-node-dx9lg 1/1 Running 0 82s 10.0.0.157 k8s-worker2 <none> <none>
kube-system calico-node-zmc92 1/1 Running 3 2d19h 10.0.0.156 k8s-worker1 <none> <none>
kube-system kube-proxy-9wrbg 1/1 Running 3 2d19h 10.0.0.155 k8s-worker0 <none> <none>
kube-system kube-proxy-bfzmw 1/1 Running 0 82s 10.0.0.157 k8s-worker2 <none> <none>
kube-system kube-proxy-vnmqt 1/1 Running 3 2d19h 10.0.0.156 k8s-worker1 <none> <none>
metallb-system controller-bb5f47665-zcl8v 1/1 Running 1 2d2h 172.20.194.66 k8s-worker1 <none> <none>
metallb-system speaker-glk7p 1/1 Running 2 (4m21s ago) 2d2h 10.0.0.156 k8s-worker1 <none> <none>
metallb-system speaker-slmcg 1/1 Running 0 56s 10.0.0.157 k8s-worker2 <none> <none>
metallb-system speaker-xzscq 1/1 Running 2 (4m21s ago) 2d2h 10.0.0.155 k8s-worker0 <none> <none>
nginx podを追加ワーカーノードにも配置する
nginx podを追加ワーカーノードにも配置するためにDeploymentへレプリカを追加する。
mng ~]$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-test 2/2 2 2 2m38s
[mng ~]$ kubectl scale deployment nginx-test --replicas=3
[mng ~]$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-test 3/3 3 3 4m40s
[mng ~]$ kubectl get pods -l app=nginx-test -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-test-598898876f-g8ncx 1/1 Running 0 6m26s 172.30.126.0 k8s-worker2 <none> <none>
nginx-test-598898876f-jbdmt 1/1 Running 0 6m26s 172.23.229.129 k8s-worker0 <none> <none>
nginx-test-598898876f-zbh5g 1/1 Running 0 2m6s 172.20.194.68 k8s-worker1 <none> <none>
[@mng ~]$ curl -v http://apps.test.k8s.local/
* Trying 10.0.0.60...
* TCP_NODELAY set
* Connected to apps.test.k8s.local (10.0.0.60) port 80 (#0)
> GET / HTTP/1.1
> Host: apps.test.k8s.local
> User-Agent: curl/7.61.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.27.5
< Date: Wed, 07 May 2025 13:31:53 GMT
< Content-Type: text/html
< Content-Length: 615
< Last-Modified: Wed, 16 Apr 2025 12:01:11 GMT
< Connection: keep-alive
< ETag: "67ff9c07-267"
< Accept-Ranges: bytes
<
<!DOCTYPE html>
<html>
<head>
...
</html>
[mng ~]$ kubectl get pods -l app=nginx-test -o wide | grep k8s-worker2
nginx-test-598898876f-g8ncx 1/1 Running 0 10m 172.30.126.0 k8s-worker2 <none> <none>
[mng ~]$ kubectl logs nginx-test-598898876f-g8ncx
...
10.0.0.155 - - [07/May/2025:13:24:25 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-"
Web Dashboardのインストール
Helmインストール
[mng ~]$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
[mng ~]$ chmod +x get_helm.sh
[mng ~]$ ./get_helm.sh
Downloading https://get.helm.sh/helm-v3.17.3-linux-amd64.tar.gz
Verifying checksum... Done.
Preparing to install helm into /usr/local/bin
helm installed into /usr/local/bin/helm
[mng ~]$ helm version
version.BuildInfo{Version:"v3.17.3", GitCommit:"e4da49785aa6e6ee2b86efd5dd9e43400318262b", GitTreeState:"clean", GoVersion:"go1.23.7"}
Dashboardインストール
helm upgrade --install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard --create-namespace --namespace kubernetes-dashboard
Release "kubernetes-dashboard" does not exist. Installing it now.
NAME: kubernetes-dashboard
LAST DEPLOYED: Fri May 9 22:47:40 2025
NAMESPACE: kubernetes-dashboard
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
*************************************************************************************************
*** PLEASE BE PATIENT: Kubernetes Dashboard may need a few minutes to get up and become ready ***
*************************************************************************************************
Congratulations! You have just installed Kubernetes Dashboard in your cluster.
To access Dashboard run:
kubectl -n kubernetes-dashboard port-forward svc/kubernetes-dashboard-kong-proxy 8443:443
NOTE: In case port-forward command does not work, make sure that kong service name is correct.
Check the services in Kubernetes Dashboard namespace using:
kubectl -n kubernetes-dashboard get svc
Dashboard will be available at:
https://localhost:8443
To access Dashboard run:
kubectl -n kubernetes-dashboard port-forward svc/kubernetes-dashboard-kong-proxy 8443:443
とあり、
[mng ~]$ kubectl get svc -n kubernetes-dashboard
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes-dashboard-api ClusterIP 10.106.95.223 <none> 8000/TCP 15s
service/kubernetes-dashboard-auth ClusterIP 10.109.10.202 <none> 8000/TCP 15s
service/kubernetes-dashboard-kong-proxy ClusterIP 10.96.109.14 <none> 443/TCP 15s
service/kubernetes-dashboard-metrics-scraper ClusterIP 10.100.73.253 <none> 8000/TCP 15s
service/kubernetes-dashboard-web ClusterIP 10.106.227.137 <none> 8000/TCP 15s
かつ、Service一覧を確認するとすでにkubernetes-dashboard-kong-proxy用に作成されているので、Serviceのタイプを「ClusterIP」からMetalLBで公開するために「LoadBalancer」へ変更する。
[mng ~]$ kubectl edit service kubernetes-dashboard-kong-proxy -n kubernetes-dashboard
...
spec:
type: LoadBalancer
...
[mng ~]$ kubectl get svc -n kubernetes-dashboard kubernetes-dashboard-kong-proxy
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes-dashboard-kong-proxy LoadBalancer 10.96.109.14 10.0.0.62 443:30698/TCP 54m
MetalLBにより割り当てられたVIPは「10.0.0.62」となる。ブラウザから「hxxps://10.0.0.62:443」または「hxxps://10.0.0.62」へアクセスする。
認証トークンを作成
ログイン画面を開くと認証トークンが求められる(ユーザアカウントでなくサービスアカウント(アプリケーション/プログラム用)としての認証情報の作成が必要)。
[mng ~]$ kubectl create sa admin-user -n kubernetes-dashboard
serviceaccount/admin-user created
[mng ~]$ kubectl get sa admin-user -n kubernetes-dashboard
NAME SECRETS AGE
admin-user 0 11s
[mng ~]$ kubectl create clusterrolebinding admin-user-binding --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:admin-user
clusterrolebinding.rbac.authorization.k8s.io/admin-user-binding created
[mng ~]$ kubectl get clusterrolebinding admin-user-binding -o wide
NAME ROLE AGE USERS GROUPS SERVICEACCOUNTS
admin-user-binding ClusterRole/cluster-admin 31s kubernetes-dashboard/admin-user
$ kubectl -n kubernetes-dashboard create token admin-user --duration=8760h (= 24h x 365)
eyJhbGciOiJSUzI1NiI...
※ 1年間(24h x 365日)有効なトークンを発行
出力されたトークンでログイン。
(おまけ) トークン(JWT)をダンプ
ちなみに出力されるトークン(JWT)をダンプすると以下のような感じ。
{
"alg": "RS256",
"kid": "5vBzOOElA9x8rFMUYWdf8BH4INglLbbAVRgV1wsQGoY"
}
{
"aud": [
"https://kubernetes.default.svc.cluster.local"
],
"exp": 1778427001,
"iat": 1746891001,
"iss": "https://kubernetes.default.svc.cluster.local",
"jti": "ea050fcb-a7db-4dee-a9a6-4a7297b32802",
"kubernetes.io": {
"namespace": "kubernetes-dashboard",
"serviceaccount": {
"name": "admin-user",
"uid": "35816fd8-a549-4e70-808e-5257722b94dd"
}
},
"nbf": 1746804601,
"sub": "system:serviceaccount:kubernetes-dashboard:admin-user"
}
...
またマニフェストで作成する方法は以下を参照。
KubernetesでPrivateCA証明書や自己署名証明書を使う場合の考慮事項
今後の作業でPrivateCAによる証明書や自己署名証明書を利用することがでてくるので考慮事項をまとめておく。
プライベートレジストリ
Podを起動するコンテナイメージをプライベートレジストリからPullする場合にはコンテナランタイムにPrivateCA/自己署名の証明書を登録しておく。
CRI-Oの場合にはノードのシステムに登録する。
[mng ~]$ scp tls/private_ca.crt k8s-worker0.test.k8s.local:/tmp/
[mng ~]$ ssh k8s-worker0.test.k8s.local "sudo -S cp /tmp/private_ca.crt /etc/pki/ca-trust/source/anchors/test_k8s_local_ca.crt && sudo -S update-ca-trust extract"
Podのヘルスチェック (Probe)
Pod内のコンテナに対してHTTP/HTTPSによりヘルスチェックする際の留意事項についてメモしておく。
Probe種類
Probe種類 | 目的 | 失敗時の動作 | 実行タイミング |
---|---|---|---|
livenessProbe | コンテナが生きているかの判定 | コンテナを再起動 | Pod 起動後に定期実行 |
readinessProbe | トラフィック受け付け可能かの判定 | Service から除外 | Pod 起動後に定期実行 |
startupProbe | 起動遅延の検出 | 起動失敗時に再起動 | Pod 起動直後のみ |
Probe方式
- httpGet: HTTP(S)リクエストを送信し、レスポンスコードを確認(200~399 で成功)
- tcpSocket: 指定ポートへTCP接続(接続成功でOK)
- exec: コンテナ内でコマンドを実行し、終了コードが0なら成功
HTTPSプローブ (httpGet) 時のケース別まとめ
ケース | 推奨プローブ方式 | 備考 |
---|---|---|
Public CA | httpGet + HTTPS | 特別な設定不要。Kubeletが信頼可能なCAならそのまま利用可能 |
Private CA | (1) ノードにCA証明書を登録してhttpGet + HTTPS (2) exec (curl --cacert or -k) |
kubelet/コンテナランタイムがCAを信頼していない場合は失敗するため(1) or (2)で実施 |
自己署名証明書 | (1) exec (curl --cacert or -k) (2) tcpSocket |
|
Sorryサーバ(常に404応答) | (1) exec (2) tcpSocket |
httpGetでは失敗扱い。別途監視用のAPIパスを用意するでも可 |
※ 同一IngressのバックエンドプロトコルにHTTPSを利用する場合にはSorryサーバも同じルールに記述するならばそれへの接続方法もHTTPSになるので留意