1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

KubernetesクラスタをVMware Workstationでオンプレ想定で構築してみる (RHEL9) - (1) クラスタ構築編 -

Last updated at Posted at 2025-05-05

はじめに

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台追加する)。

network1.png

  • ドメイン名: 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ルーター (デフォルトゲートウェイ)

ip_forward有効化
$ cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.ipv4.ip_forward = 1
EOF

$ sudo sysctl --system
NAT設定
$ 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 (プロキシサービス)

Squidインストール
$ sudo dnf update -y
$ sudo dnf install squid -y
Squidサービス開始
$ 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サービス)

Dnsmasqインストール
$ sudo dnf update -y
$ sudo dnf install -y dnsmasq
設定ファイルを編集
$ sudo vi /etc/dnsmasq.d/k8s.conf
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
Dnsmasq開始
$ sudo systemctl enable --now dnsmasq
$ sudo systemctl status dnsmasq
システムのDNSサービスをローカルの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サービス)

SELinux無効化 (とりあえず・・・)
$ sudo sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config

システム再起動
HA-Proxyインストール
$ sudo dnf update -y
$ sudo dnf install -y haproxy
設定ファイルを編集
sudo vi /etc/haproxy/haproxy.cfg

とりあえずシンプルに以下で設定しておく。

  • 分散方式: ラウンドロビン
  • プロトコル: TCP (L4)
haproxy.cfg
#---------------------------------------------------------------------
# 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
HA-Proxy開始
$ 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クライアント設定

# DHCPサーバからホスト名を設定
$ sudo hostnamectl set-hostname ""

システム再起動
swap無効化
$ sudo sed -i '/ swap / s/^/#/' /etc/fstab

システム再起動

$ free
              total        used        free      shared  buff/cache   available
Mem:        1984984      279736     1538352       17112      166896     1538140
Swap:             0
SELinux設定
とりあえず・・・

$ sudo sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config (無効化)

システム再起動

$ getenforce
Permissive
kernel module設定
$ 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
ip_forward有効化
$ 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クライアント設定

# DHCPサーバからホスト名を設定
$ sudo hostnamectl set-hostname ""

システム再起動
swap無効化
$ sudo sed -i '/ swap / s/^/#/' /etc/fstab

システム再起動

$ free
              total        used        free      shared  buff/cache   available
Mem:        1984984      279736     1538352       17112      166896     1538140
Swap:             0
SELinux設定
とりあえず・・・

$ sudo sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config (無効化)

システム再起動

$ getenforce
Permissive
kernel module設定
$ 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
ip_forward有効化
$ 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のバージョンの方を合わせる。

CRI-Oインストール
$ 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をインストール

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。

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 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
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
Podを確認
$ 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。

(おまけ) Kubernetes APIのHA-Proxy仮想IP経由のアクセスを確認
$ 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でcertificate-keyが有効期限切れ(2時間)した場合
$ 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
k8s-master1で新しいcertificate-key取得
$ 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
新しいcertificate-keyを指定してクラスタへ追加
$ kubeadm join api.test.k8s.local:6443 --token xxxx --discovery-token-ca-cert-hash sha256:yyyy --control-plane --certificate-key new_key_zzzzz
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 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でクラスタへ追加

新しいcertificate-keyを指定してクラスタへ追加
$ kubeadm join api.test.k8s.local:6443 --token xxxx --discovery-token-ca-cert-hash sha256:yyyy --control-plane --certificate-key zzzzz
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 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 (ワーカーノード共通)

コンテナランタイムをインストール

CRI-Oインストール
$ 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をインストール

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をインストール

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
configファイルをコントロールプレーンノードからコピー
[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を取得

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が有効。

k8s-worker0のcalico-nodeのIP割り当てを確認
[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を話し連携することもできる仮想ルーター的なポッド

k8s-worker0のcalico-nodeのIP割り当てを確認
[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: 物理ネットワークインタフェース

k8s-worker0のcalico-nodeのルーティング設定を確認
[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
k8s-worker1のcalico-nodeのIP割り当てを確認
[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
k8s-worker1のcalico-nodeのルーティング設定を確認
$ 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)から行う。

nodeport_ports.png

nginxのディプロイ

Deployment/Serviceの定義
$ vi nginx-deployment.yaml
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)
Deployment/Service定義を適用
[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)からアクセスする。

nginx接続確認
[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>
nginxのログを確認
[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" "-"
...
ワーカーノードのアドレス/Portを指定してアクセス
[mng ~]$ curl -v http://k8s-worker0.test.k8s.local:30080/

※ ワーカーノードでconntrackを生成するために管理端末から直接ワーカーノードのアドレス/Portを指定して実施

ワーカーノードでconntrackでトラフィック転送の経路を確認 (DstNAT) (1)
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
ワーカーノードでconntrackでトラフィック転送の経路を確認 (DstNAT) (2)
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モードでセットアップする。

nodeport_ports2.png

事前確認

kube-proxyのモード確認
[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モードを無効化。

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
(参考) インストール時ならcalico.yamlへ以下を設定
            - 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)"
IPAddressPool/L2Advertisement定義を作成
[mng ~]$ vi metallb-config.yaml
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
IPAddressPool/L2Advertisementを作成
[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
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)からアクセスする。

nginx接続確認
[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/
同様
nginxのログを確認
[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)を追加する。他のワーカーノードのセットアップと共通の手順を実施。

コンテナランタイムをインストール

CRI-Oインストール
$ 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をインストール

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>
nginxログ確認
[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」へアクセスする。

認証トークンを作成

ログイン画面を開くと認証トークンが求められる(ユーザアカウントでなくサービスアカウント(アプリケーション/プログラム用)としての認証情報の作成が必要)。

kube_dashboard_login.png

[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日)有効なトークンを発行

出力されたトークンでログイン。

kube_dashboard1.png

(おまけ) トークン(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の場合にはノードのシステムに登録する。

管理端末から各ノードへCA証明書を登録 (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になるので留意

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?