Kubernetes (K8S) 地端伺服器建置實錄 – RedHat 篇

在當今的雲端時代,Kubernetes(簡稱 K8S)作為 Open source 的 container (容器) 編排平台,已經成為許多企業和開發者的首選。它為應用程式的部署、擴展和管理提供了一個強大且靈活的解決方案。
本篇文章將詳細介紹如何在地端 (On-premise, self-host) 伺服器上搭建 Kubernetes 環境,我們將介紹所有必要的步驟,包括環境設置、安裝必要的套件、建立節點與部署應用程式。這將是一個完整的實錄,讓讀者能夠透過這篇文章深入瞭解 K8S 的建置與運作。


為何會再次寫這篇文章?

後來發現 RedHat ( RHEL / RockyLinux ) 的指令跟 Ubuntu 有一些差異,
遇到的情況也略為不同,我覺得蠻值得再寫一次的。

當然,還是推薦使用 虛擬機 (Virtual machine, VM) 來建置,
你可以用你喜歡的虛擬機程式來架設,例如 VMWare Workstation, VirtualBox 都可以,我是使用 Promox VE 裡面的 VM 功能來完成。

如果有看過前一篇的話,這個方式安裝方式為 Bare-metal (裸金屬、裸機)的安裝方式。
這個也叫做 Vanilla Kubernetes (翻譯:單純的 Kubernetes 安裝)。

安裝地圖

Docker 跟 Kubernetes (K8S) 發展至今,百家爭鳴,門派也很多,
安裝部署方式也不盡相同,為了避免初學者混肴,
先幫你預先選好各種所需要的元件:

示範的作業系統

  • RockyLinux 9.2 對應到 RHEL (Redhat Enterprise Linux) 9.2

    服務們

  • kubelet

  • Container 運行環境 (Container Runtime):docker

  • cgroup drivers: 確認為 systemd (cgroup drivers v2)

  • CRI (Container Runtime Interface):使用 cri-dockerd

  • CNI (Container Network Interface):使用 Flannel

指令們

  • kubectl
  • kubeadm

這篇主要關注在如何架設 Kubernetes 叢集,
除此之外,你還需要一個配合的共用儲存空間,叢集都可以存取到的儲存空間(檔案伺服器)
可以用 TrueNAS 架設一個。

虛擬機硬體建置

這邊是我 虛擬機 (VM) 的硬體部分建置設定

  • 2 CPU
  • 4GB Ram
  • 8GB Disk 以上,建議 10GB 較穩定

註1:經過測試,不要用 Proxmox VE 裡的 LXC Container 功能架設,
會有非常多的問題,包含權限切不乾淨等。

註2:使用 Proxmox VE 預設參數會遇到 Kernel panic 問題,
進入虛擬機 Hardware > Processors 選項,將 Type 改為 host 就會正常。

到時候要建立三台 VM,一台 Control Node 跟二台 Worker Node ,這是最小叢集的配置。
可以先安裝一個母版,到時候用複製 VM 的方式來達成。

虛擬機作業系統 – RockyLinux

示範使用的 RedHat 版本為社群版的 RockyLinux 9.2
使用 minimal 最小安裝

安裝細節就不贅述。

<每台都做> 關掉 swap

這步驟不分角色,三台都要做

https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/

根據 kubeadm 的安裝文件,他有特別指示

MUST disable swap in order for the kubelet to work properly.

必須要關掉 swap 才能正確運作。

所以我們用以下步驟永久關閉 Swap

  1. 調整 vm.swappiness 的值為零
$ sudo sysctl -w vm.swappiness=0
  1. sed 指令找尋 swap 片段,並加上註解
$ sudo sed -i '/ swap /s/^/#/g' /etc/fstab
  1. 然後重新載入磁區
$ sudo mount -a

暫時關閉 swap 可以用 swapoff 指令

$ sudo swapoff -a

確認 swap

sysctl 的方式來列出目前 swppiness 設定值

$ sysctl vm.swappiness

可以用以下指令查看 swap

$ free

或者

$ cat /proc/swaps

應該要找不到 swap 才正確

<每台都做> 安裝 Docker

Docker 不分角色,三台都要裝

安裝文件:
https://docs.docker.com/engine/install/centos/
https://docs.docker.com/engine/install/rhel/

小弟整理的一鍵安裝指令
(科技發展迅速,整理的安裝文件有可能會過時,如果有更新版,請參考官方文件)

$ sudo yum install -y yum-utils && \
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo && \
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

(這個部分的指令跟 Ubuntu 不一樣)

修改 daemon.json 讓跳開預設網段
(如果沒有該檔案請自行新增之)

$ sudo vi /etc/docker/daemon.json

內容為

{
  "log-driver": "json-file",
  "log-opts": {
    "tag": "{{.Name}}",
    "max-size": "2m",
    "max-file": "2"
  }
}

設定預設開機啟動,並立即啟動

$ sudo systemctl enable --now docker

驗證 Docker

可用 systemctl 指令查看是否有正常執行

$ sudo systemctl status docker

看看是否有 Running

可以用 docker ps 查看目前所有運行中的 container

$ docker ps

是否能夠正常顯示列表,若是初次安裝,列表是空的很正常。

Docker 版本

留下當時截稿的 Docker 版本給大家參考

# docker version
Client: Docker Engine - Community
 Version:           24.0.6
 API version:       1.43
 Go version:        go1.20.7
 Git commit:        ed223bc
 Built:             Mon Sep  4 12:33:18 2023
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          24.0.6
  API version:      1.43 (minimum version 1.12)
  Go version:       go1.20.7
  Git commit:       1a79695
  Built:            Mon Sep  4 12:31:49 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.24
  GitCommit:        61f9fd88f79f081d64d6fa3bb1a0dc71ec870523
 runc:
  Version:          1.1.9
  GitCommit:        v1.1.9-0-gccaecfc
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

<每台都做> 安裝 kubeletkubeadmkubectl 三兄弟

安裝文件:
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/

小弟整理的安裝指令

$ sudo setenforce 0 && \
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config && \
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.28/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.28/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF && \
sudo yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes && \
sudo systemctl enable --now kubelet

(科技發展迅速,整理的安裝文件有可能會過時,如果有更新版,請參考官方文件)

(這個部分跟 Ubuntu 不一樣)

目前安裝的版本是 kubelet v1.28.2

<每台都做> 手動編譯安裝 Container Runtime Interface (CRI) – cri-dockerd

這步驟不分角色,三台都要裝

https://kubernetes.io/docs/setup/production-environment/container-runtimes/

我們用 Docker Engine 推薦的 cri-dockerd

說明文件:
https://kubernetes.io/docs/tasks/administer-cluster/migrating-from-dockershim/migrate-dockershim-dockerd/#what-is-cri-dockerd

若是 RHEL 7.9 (CentOS 7-2009) 可以使用 cri-dockerd-0.3.6.20231018204925.877dc6a4-0.el7.x86_64.rpm若是 RHEL 8.8 (RockyLinux 8.8) 可以使用 cri-dockerd-0.3.6.20231018204925.877dc6a4-0.el8.x86_64.rpm
因為 RHEL 9.2 (RockyLinux 9.2) 沒有對應的 rpm 可以裝
所以用手動編譯的方式進行

以下是官方文件提供的步驟
https://github.com/mirantis/cri-dockerd#build-and-install

先安裝必要套件

$ sudo yum install -y make go

git clone 最新的版本

$ git clone https://github.com/Mirantis/cri-dockerd.git

編譯它 (compile)

$ cd cri-dockerd && \
make cri-dockerd

安裝

cd cri-dockerd && \
mkdir -p /usr/local/bin && \
install -o root -g root -m 0755 cri-dockerd /usr/local/bin/cri-dockerd && \
install packaging/systemd/* /etc/systemd/system && \
sed -i -e 's,/usr/bin/cri-dockerd,/usr/local/bin/cri-dockerd,' /etc/systemd/system/cri-docker.service && \

然後請 systemctl 重新載入 daemon
最後啟動服務

$ sudo systemctl daemon-reload && \
sudo systemctl enable --now cri-docker

驗證 cri-docker

可用 systemctl 指令確認是否有正常運行

$ sudo systemctl status cri-docker

確認有 Running

裝完就會有 unix:///var/run/cri-dockerd.sock

複製虛擬機 (VM)

這邊步驟就是將單純的將 虛擬機 (VM) 複製二份成三台,並全部啟動。
以下分別闡述複製完要做的事情

重新產生 Machine-id

用以下指令重新產生 Machine-id

$ sudo rm /etc/machine-id && \
sudo systemd-machine-id-setup

修改 Hostname (主機名稱)

$ sudo vi /etc/hostname

分別改成對應的主機名稱

重新設定 ssh,產生全新的 known-host

$ ssh-keygen -A

(這個部分的指令跟 Ubuntu 不一樣)

確認 Machine-id

sudo cat /sys/class/dmi/id/product_uuid

確認 Hostname

$ hostname

確認網卡 Mac address 位址

ip link

如果是有 DHCP 的話,可以用路由器 dhcp static lease (固定分配 IP)

然後可以用 dhclient 指令重新取 DHCP 的 IP

$ sudo dhclient -r

用 verbose 來看細節

$ sudo dhclient -v

註:RockyLinux 9.2 預設沒有安裝 dhclient 指令
需要另外用 yum 安裝

$ sudo yum install -y dhcp-client

<每台都做> 設定主機對應

叢集的三台機器做出來,還不知道彼此,
這邊用 /etc/hosts 檔案來讓主機們各自找到彼此

$ sudo vi /etc/hosts

根據每台主機的 IP 位址與主機名稱

192.168.1.100   k8s-ctrl
192.168.1.101   k8s-node1
192.168.1.102   k8s-node2

IP 位址在前,主機名稱在後,用 tab 分隔。

先整理好內容,再各自寫在每一台上面,每一台主機都會看到同一份資料。

確認 cgroup drivers 為 systemd

(這整個段落可以跳過,因為 cgroup drivers 預設已經是 systemd 了)

https://stackoverflow.com/questions/45708175/kubelet-failed-with-kubelet-cgroup-driver-cgroupfs-is-different-from-docker-c

直接講結論,目前最新使用的是 systemd (cgroup Version: 2)

查看 docker 的 cgroup

# docker info | grep -i cgroup

 Cgroup Driver: systemd
 Cgroup Version: 2
  cgroupns

查看 kubelet 的 cgroup

$ sudo cat /var/lib/kubelet/config.yaml | grep cgroup

cgroupDriver: systemd

可以確認是否為 systemd (cgroup Version: 2)

如果 docker 不為 systemd

可以在 daemon.json手動加上

$ sudo vi /etc/docker/daemon.json

這個段落

 "exec-opts": [
    "native.cgroupdriver=systemd"
  ],

重啟 docker

$ sudo systemctl restart docker

如果 kubelet 不為 systemd 就手動修改之

$ sudo vi /var/lib/kubelet/config.yaml

重啟 kubelet

$ sudo systemctl restart kubelet

<每台都做> 設定網路雜項值

根據文件:
https://kubernetes.io/docs/setup/production-environment/container-runtimes/#forwarding-ipv4-and-letting-iptables-see-bridged-traffic

這邊設定網路連線轉發 IPv4 位址並讓 iptables 查看橋接器的流量

用文件提供的指令操作,等等一句一句解釋:

$ cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

請 Kubernetes (K8S) 引用載入 br_netfilteroverlay 二個核心模組

$ sudo modprobe overlay && \
sudo modprobe br_netfilter

啟用 br_netfilteroverlay 二個核心模組

$ cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

設定轉發 IPv4 位址,讓 iptables 查看橋接器的流量

$ sudo sysctl --system

再不起重新啟動電腦情況下,套用設定值

檢查驗證

檢查 br_netfilteroverlay 二個核心模組有沒有被正確載入可以用以下二個指令

$ lsmod | grep br_netfilter
$ lsmod | grep overlay

檢查

  • net.bridge.bridge-nf-call-iptables
  • net.bridge.bridge-nf-call-ip6tables
  • net.ipv4.ip_forward 

這幾個系統變數是否有設定為 1,可以用 sysctl 指令來檢查:

$ sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward

設定 Control plane node(控制平台) (舊名 Master node)

終於要來設定 Control plane (控制平台) 了,如果有其他教學看到 Master node 的話,
別擔心,指的是同一件事情。

利用 kubeadm init 指令來初始化,並代入這些參數:

$ sudo kubeadm init \
    --control-plane-endpoint=192.168.1.100 \
    --apiserver-advertise-address=192.168.1.100 \
    --node-name k8s-ctrl \
    --apiserver-bind-port=6443 \
    --pod-network-cidr=10.244.0.0/16 \
    --cri-socket unix:///var/run/cri-dockerd.sock

參數說明

  • control-plane-endpoint
    指明 Control plane (控制平台) 是哪個網址,這邊設定好目前這台 IP 位址即可,假設為 192.168.1.100
    (這設定值可省略)
  • apiserver-advertise-address
    指明 API server 的廣播地址,預設就是 Control plane (控制平台) IP 位址,假設為 192.168.1.100
    (這設定值可省略)
  • node-name
    指明 Control plane (控制平台) 的名字,這裡跟主機名稱一致即可。
  • apiserver-bind-port
    指明 Kubernetes API server 的連接埠 (port) 號,預設是 6443,可以依需求變更。
  • pod-network-cidr
    指明 pod 內部網路使用的網段,這邊因為配合 Flannel CNI,請保留 10.244.0.0/16 請不要修改
  • cri-socket
    指明使用的 CRI 使用 unix:///var/run/cri-dockerd.sock 這設定值 請不要修改

記錄一下運作的樣子

# kubeadm init \
    --control-plane-endpoint=192.168.1.100 \
    --apiserver-advertise-address=192.168.1.100 \
    --node-name k8s-ctrl \
    --apiserver-bind-port=6443 \
    --pod-network-cidr=10.244.0.0/16 \
    --cri-socket unix:///var/run/cri-dockerd.sock

[[init] Using Kubernetes version: v1.28.2
[preflight] Running pre-flight checks
        [WARNING Firewalld]: firewalld is active, please ensure ports [6443 10250] are open or your cluster may not function correctly
        [WARNING Service-Kubelet]: kubelet service is not enabled, please run 'systemctl enable kubelet.service'
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
W1019 08:18:09.599064    3875 checks.go:835] detected that the sandbox image "registry.k8s.io/pause:3.6" of the container runtime is inconsistent with that used by kubeadm. It is recommended that using "registry.k8s.io/pause:3.9" as the CRI sandbox image.
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local rk8-ctrl] and IPs [10.96.0.1 192.168.1.100]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [localhost rk8-ctrl] and IPs [192.168.1.100 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [localhost rk8-ctrl] and IPs [192.168.1.100 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Starting the kubelet
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 6.504831 seconds
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config" in namespace kube-system with the configuration for the kubelets in the cluster
[upload-certs] Skipping phase. Please see --upload-certs
[mark-control-plane] Marking the node rk8-ctrl as control-plane by adding the labels: [node-role.kubernetes.io/control-plane node.kubernetes.io/exclude-from-external-load-balancers]
[mark-control-plane] Marking the node rk8-ctrl as control-plane by adding the taints [node-role.kubernetes.io/control-plane:NoSchedule]
[bootstrap-token] Using token: ktwf96.9mhdqldhpu3ema54
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to get nodes
[bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] Configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] Configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

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

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 by copying certificate authorities
and service account keys on each node and then running the following as root:

  kubeadm join 192.168.1.100:6443 --token cxxxxs.c4xxxxxxxxxxxxd0 \
        --discovery-token-ca-cert-hash sha256:103d7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx5b1b6 \
        --control-plane

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

kubeadm join 192.168.1.100:6443 --token cxxxxs.c4xxxxxxxxxxxxd0 \
        --discovery-token-ca-cert-hash sha256:103d7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx5b1b6

開立防火牆 (TCP 6443, TCP 10250)

你會注意到這次有一些警告需要處理

警告訊息: firewalld 有啟動,請記得開 6443, 10250 連接埠 (port)

[WARNING Firewalld]: firewalld is active, please ensure ports [6443 10250] are open or your cluster may not function correctly

我們就來做開防火牆這件事情

$ sudo firewall-cmd --permanent --zone=public --add-port=6443/tcp && \
sudo firewall-cmd --permanent --zone=public --add-port=10250/tcp

如果前述 Kubernetes API server 的連接埠 (port) 號有修改的話(也就是 --apiserver-bind-port 參數),
這邊也要同步修改。

記得重新載入它

sudo firewall-cmd --reload

確認防火牆

sudo firewall-cmd --list-all-zones

執行 kubelet 服務

有收到一個警告消息:kubelet 服務沒有啟動

[WARNING Service-Kubelet]: kubelet service is not enabled, please run 'systemctl enable kubelet.service'

執行它即可消除

$ sudo systemctl enable kubelet.service && \
sudo systemctl start kubelet.service

設定 kubectrl 連結

如果沒意外的話,完成之後會看到

Your Kubernetes control-plane has initialized successfully!

別太高興,設定還沒完,先把 kubeadm join 語句先存起來備用

kubeadm join 192.168.1.100:6443 --token cxxxxs.c4xxxxxxxxxxxxd0 \
        --discovery-token-ca-cert-hash sha256:103d7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx5b1b6

然後依照步驟,
若是 root 使用者,

.bash_profile 或者 .zsh_profile 設定環境變數

export KUBECONFIG=/etc/kubernetes/admin.conf

若是一般使用者,請依照指令依序設定

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

註:加入 token 是有期限的,如果隔太久沒有整個步驟做完,
或者忘記了、被洗掉了,可以用指令重新生成加入指令

$ kubeadm token create --print-join-command

<Control plane 做> 安裝 Helm 套件管理程式

Helm 是 Kubernetes (k8s) 所使用的套件管理程式,
類似 apt-get 可以方便我們安裝元件,免去一點設定的雷

Helm 只要裝在 Control plane (舊名 Master node) 就可以了

安裝文件
https://helm.sh/docs/intro/install/

從執行檔直接複製

$ wget https://get.helm.sh/helm-v3.13.1-linux-amd64.tar.gz
tar zxvf helm-v3.13.1-linux-amd64.tar.gz
cp linux-amd64/helm /usr/local/bin/helm

(科技發展迅速,整理的安裝文件有可能會過時,如果有更新版,請參考官方文件)

也可從 Script 安裝

curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 && \
chmod 700 get_helm.sh && \
./get_helm.sh

(科技發展迅速,整理的安裝文件有可能會過時,如果有更新版,請參考官方文件)

二者效果相同,擇一安裝即可。

<Control plane 做> 安裝 Flannel CNI

https://github.com/flannel-io/flannel

使用 Helm 安裝 Flannel,將之安裝在 kube-flannel 的 namespace,可用小弟整理之一鍵安裝指令

Flannel 只要在 Control plane (舊名 Master node) 上面下指令,就會部署到整個叢集。

可以使用以下整理之指令一鍵安裝

$ kubectl create ns kube-flannel && \
kubectl label --overwrite ns kube-flannel pod-security.kubernetes.io/enforce=privileged && \
helm repo add flannel https://flannel-io.github.io/flannel/ && \
helm install flannel --set podCidr="10.244.0.0/16" --namespace kube-flannel flannel/flannel

(科技發展迅速,整理的安裝文件有可能會過時,如果有更新版,請參考官方文件)

指令意思大致為:

  1. 建立一個 namespace (命名空間)名叫 kube-flannel
  2. 給定 kube-flannel 特權的權限
  3. 加入 repo 網址
  4. 用 helm 安裝 Flannel

設定 Worker node

這下終於可以設定 Worker node 了

還記得剛剛留下來的指令

kubeadm join 192.168.1.100:6443 --token cxxxxs.c4xxxxxxxxxxxxd0 \
    --discovery-token-ca-cert-hash sha256:103d7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx5b1b6

什麼?忘記了?

可以用指令重新生成加入指令

$ kubeadm token create --print-join-command

出現 kubeadm join 指令之後,加上指明 cri-socket 就可以執行了

意指加上這行

--cri-socket unix:///var/run/cri-dockerd.sock

變成這樣

$ sudo kubeadm join 192.168.1.100:6443 
    --token cxxxxs.c4xxxxxxxxxxxxd0 \
    --discovery-token-ca-cert-hash sha256:103d7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx5b1b6 \
    --cri-socket unix:///var/run/cri-dockerd.sock

記錄一下運作情形

$ kubeadm join 192.168.1.100:6443 
    --token cxxxxs.c4xxxxxxxxxxxxd0 \
    --discovery-token-ca-cert-hash sha256:103d7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx5b1b6 \
    --cri-socket unix:///var/run/cri-dockerd.sock

[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

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

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

這樣就加入叢集了

Troubleshoting

若你看到

[preflight] Running pre-flight checks

然後卡住的話,可以加上 -v=5 得到更 verbose 的內容

$ kubeadm join 192.168.1.100:6443 
    --token cxxxxs.c4xxxxxxxxxxxxd0 \
    --discovery-token-ca-cert-hash sha256:103d7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx5b1b6 \
    --cri-socket unix:///var/run/cri-dockerd.sock -v=5

I1019 08:29:40.569229    2362 join.go:412] [preflight] found NodeName empty; using OS hostname as NodeName
[preflight] Running pre-flight checks
I1019 08:29:40.569740    2362 preflight.go:93] [preflight] Running general checks
I1019 08:29:40.569938    2362 checks.go:280] validating the existence of file /etc/kubernetes/kubelet.conf
I1019 08:29:40.570190    2362 checks.go:280] validating the existence of file /etc/kubernetes/bootstrap-kubelet.conf
I1019 08:29:40.570330    2362 checks.go:104] validating the container runtime
I1019 08:29:40.628420    2362 checks.go:639] validating whether swap is enabled or not
I1019 08:29:40.628538    2362 checks.go:370] validating the presence of executable crictl
I1019 08:29:40.628603    2362 checks.go:370] validating the presence of executable conntrack
I1019 08:29:40.628770    2362 checks.go:370] validating the presence of executable ip
I1019 08:29:40.628809    2362 checks.go:370] validating the presence of executable iptables
I1019 08:29:40.628865    2362 checks.go:370] validating the presence of executable mount
I1019 08:29:40.628925    2362 checks.go:370] validating the presence of executable nsenter
I1019 08:29:40.628980    2362 checks.go:370] validating the presence of executable ebtables
I1019 08:29:40.629025    2362 checks.go:370] validating the presence of executable ethtool
I1019 08:29:40.629060    2362 checks.go:370] validating the presence of executable socat
I1019 08:29:40.629099    2362 checks.go:370] validating the presence of executable tc
I1019 08:29:40.629150    2362 checks.go:370] validating the presence of executable touch
I1019 08:29:40.629212    2362 checks.go:516] running all checks
I1019 08:29:40.639498    2362 checks.go:401] checking whether the given node name is valid and reachable using net.LookupHost
I1019 08:29:40.639703    2362 checks.go:605] validating kubelet version
I1019 08:29:40.704380    2362 checks.go:130] validating if the "kubelet" service is enabled and active
I1019 08:29:40.721619    2362 checks.go:203] validating availability of port 10250
I1019 08:29:40.722091    2362 checks.go:280] validating the existence of file /etc/kubernetes/pki/ca.crt
I1019 08:29:40.722136    2362 checks.go:430] validating if the connectivity type is via proxy or direct
I1019 08:29:40.722196    2362 checks.go:329] validating the contents of file /proc/sys/net/bridge/bridge-nf-call-iptables
I1019 08:29:40.722316    2362 checks.go:329] validating the contents of file /proc/sys/net/ipv4/ip_forward
I1019 08:29:40.722358    2362 join.go:529] [preflight] Discovering cluster-info
I1019 08:29:40.722412    2362 token.go:80] [discovery] Created cluster-info discovery client, requesting info from "192.168.1.100:6443"
I1019 08:29:40.723841    2362 token.go:217] [discovery] Failed to request cluster-info, will try again: Get "https://192.168.1.100:6443/api/v1/namespaces/kube-public/configmaps/cluster-info?timeout=10s": dial tcp 192.168.1.100:6443: connect: no route to host

你會看到類似問題字眼

[discovery] Failed to request cluster-info, will try again: Get "https://192.168.1.100:6443/api/v1/namespaces/kube-public/configmaps/cluster-info?timeout=10s": dial tcp 192.168.1.100:6443: connect: no route to host

就是找不到 192.168.1.100:6443,除了 ping 會通之外,可能檢查防火牆有沒有正確開啟。

重設整個叢集

如果整個叢集有其他問題,做爛了,可以用以下方法重新設定

進到每一台 node 裡面,利用 kubeadm reset 重置,記得代入 cri-socket

如下:

$ kubeadm reset -f --cri-socket unix:///var/run/cri-dockerd.sock

記錄一下運作情形

$ kubeadm reset -f --cri-socket unix:///var/run/cri-dockerd.sock

[preflight] Running pre-flight checks
W1019 08:24:38.813576    2256 removeetcdmember.go:106] [reset] No kubeadm config, using etcd pod spec to get data directory
[reset] Deleted contents of the etcd data directory: /var/lib/etcd
[reset] Stopping the kubelet service
[reset] Unmounting mounted directories in "/var/lib/kubelet"
[reset] Deleting contents of directories: [/etc/kubernetes/manifests /var/lib/kubelet /etc/kubernetes/pki]
[reset] Deleting files: [/etc/kubernetes/admin.conf /etc/kubernetes/kubelet.conf /etc/kubernetes/bootstrap-kubelet.conf /etc/kubernetes/controller-manager.conf /etc/kubernetes/scheduler.conf]

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

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

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

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

它會提示你,有些防火牆規則並不會完全刪掉

可以刪掉 cni 資料夾來重置

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

對應文件:
https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-reset/

<Control plane 做> 測試檢查叢集

測試 Kubernetes 是否正常運作,
在 Control plane (控制平台) 裡可以用二個指令觀察一下:

取得所有的 Pods

kubectl get pods 指令取得 Pod,加上 -A 代表包含所有 namespace (命名空間)

以下指令就是取得所有的 Pods

$ kubectl get pods -A

取得所有的 pods

$ kubectl get pods -A

NAMESPACE      NAME                               READY   STATUS    RESTARTS   AGE
kube-flannel   kube-flannel-ds-8rtvc              1/1     Running   0          30s
kube-flannel   kube-flannel-ds-9w2vw              1/1     Running   0          30s
kube-flannel   kube-flannel-ds-jdndp              1/1     Running   0          30s
kube-system    coredns-5d78c9869d-df989           1/1     Running   0          4m20s
kube-system    coredns-5d78c9869d-s8ftg           1/1     Running   0          4m19s
kube-system    etcd-k8s-ctrl                      1/1     Running   0          4m35s
kube-system    kube-apiserver-k8s-ctrl            1/1     Running   0          4m33s
kube-system    kube-controller-manager-k8s-ctrl   1/1     Running   0          4m35s
kube-system    kube-proxy-2qrjj                   1/1     Running   0          4m19s
kube-system    kube-proxy-bpk94                   1/1     Running   0          3m51s
kube-system    kube-proxy-mgrjn                   1/1     Running   0          3m57s
kube-system    kube-scheduler-k8s-ctrl            1/1     Running   0          4m36s

你應該要看到:

  • kube-flannel 的若干個 Pod 為 Running
    (若是 Pending 或者 CrashLoopBackOff 可能要除錯)
  • kube-system (k8s 核心元件) 的二個 coredns 的 Pod 為 Running
    (若是 Pending 或者 CrashLoopBackOff 可能要除錯)
  • kube-system (k8s 核心元件) 的 etcdRunning
  • kube-system (k8s 核心元件) 的 kube-controller-manager 的 Pod 為 Running
  • kube-system (k8s 核心元件) 的 kube-apiserver 的 Pod 為 Running
  • kube-system (k8s 核心元件) 的 kube-scheduler 的 Pod 為 Running
  • kube-system (k8s 核心元件) 的若干個 kube-proxy 的 Pod 為 Running

當然,放在 kube-system 裡面的 Pod 屬於系統保留的,請勿更動修改。

取得所有 nodes (主機節點)

你可以用 kubectl get nodes -A 指令來取得所有運作的 nodes

$ kubectl get nodes -A

NAME        STATUS   ROLES           AGE     VERSION
k8s-ctrl    Ready    control-plane   4m40s   v1.27.1
k8s-node1   Ready    <none>          3m59s   v1.27.1
k8s-node2   Ready    <none>          3m53s   v1.27.1

你應該要看到你的叢集,三台都是 Ready

Trobleshoting

若你可能看到這樣的錯誤

# kubectl get node -A

E1019 08:31:28.269393    5101 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused
E1019 08:31:28.270061    5101 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused
E1019 08:31:28.271897    5101 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused
E1019 08:31:28.272478    5101 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused
E1019 08:31:28.273617    5101 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused
The connection to the server localhost:8080 was refused - did you specify the right host or port?

有可能是

  1. 真的連不上,檢查防火牆 control panel 有沒有開
  2. 沒有設定好 kubectl 連線 config
  3. kubelet 沒有正確啟動

可以用以下指令查看每台 kubelet 的細節 log

journalctl -f -u kubelet

最後貼一下所有看得到的 images

control panel

# docker image ls
REPOSITORY                                TAG       IMAGE ID       CREATED         SIZE
flannel/flannel                           v0.22.3   e23f7ca36333   4 weeks ago     70.2MB
registry.k8s.io/kube-apiserver            v1.28.2   cdcab12b2dd1   5 weeks ago     126MB
registry.k8s.io/kube-controller-manager   v1.28.2   55f13c92defb   5 weeks ago     122MB
registry.k8s.io/kube-proxy                v1.28.2   c120fed2beb8   5 weeks ago     73.1MB
registry.k8s.io/kube-scheduler            v1.28.2   7a5d9d67a13f   5 weeks ago     60.1MB
flannel/flannel-cni-plugin                v1.2.0    a55d1bad692b   2 months ago    8.04MB
registry.k8s.io/etcd                      3.5.9-0   73deb9a3f702   5 months ago    294MB
registry.k8s.io/coredns/coredns           v1.10.1   ead0a4a53df8   8 months ago    53.6MB
registry.k8s.io/pause                     3.9       e6f181688397   12 months ago   744kB
registry.k8s.io/pause                     3.6       6270bb605e12   2 years ago     683kB

worker node

# docker image ls
REPOSITORY                   TAG       IMAGE ID       CREATED        SIZE
flannel/flannel              v0.22.3   e23f7ca36333   4 weeks ago    70.2MB
registry.k8s.io/kube-proxy   v1.28.2   c120fed2beb8   5 weeks ago    73.1MB
flannel/flannel-cni-plugin   v1.2.0    a55d1bad692b   2 months ago   8.04MB
registry.k8s.io/pause        3.6       6270bb605e12   2 years ago    683kB

先預祝大家設定順利!

參考資料

https://blog.gtwang.org/linux/centos-7-firewalld-command-setup-tutorial/
https://forum.proxmox.com/threads/kernel-panic-when-creating-vms-centos-9-stream-iso.104656/page-2#post-485684
https://forum.proxmox.com/threads/solved-kernel-panic-on-install-rocky-linux.119841/

Red hat Linux 使用 yum 離線安裝套件 (RHEL / CentOS / Rocky Linux)

情境是這樣的,因為一些特殊需求與限制,你需要在一台沒有網際網路連線的 Linux 主機上面安裝軟體,離線安裝 (offline install),
本來單純的以為只要把 rpm 檔案複製好就好,但發現事情並不想像中的那麼簡單,
所以筆記一下操作方式,以及需要注意的地方。

材料準備

講一下我們需要準備的材料:

  • 一台離線要安裝的目標主機(它沒有對外網路)
  • 一台能連網的主機,安裝對應版本的 VM (虛擬機)
  • 與目標主機對應版本的安裝 ISO 映象檔

操作步驟

你需要利用 yum 這個強大的套件管理工具,解析套件間的依賴 (dependency),
做自己所需要的套件庫,然後整個複製到目標機器,最後安裝它。

首先,調查清楚目標離線主機要安裝的 Linux 版本
整理出以下對照表:

  • RHEL 7.9 對應 CentOS 7-2009
  • RHEL 8.8 對應 Rocky Linux 8.8
  • RHEL 9.2 對應 Rocky Linux 9.2

然後下載對應的 ISO 映象檔:

Step 1. 準備一台可連網的虛擬機

在能連網的電腦上,
準備好虛擬機環境(例如:VMware、Hyper-V、VirtualBox)
找到對應版本的 minimal iso 來安裝系統(越小越好)

新增一個,規格小小的 VM,例如:

  • CPU: 2 core
  • RAM: 2GB
  • Disk: 20GB

掛載指定版本 ISO 來安裝 VM
Linux 的安裝過程就暫時跳過

重點是要「最小安裝」
到時候在循環檢查套件依賴的時候,
才可以抓得比較完整

確認網路

RHEL 預設網路不會開,你可能會遇到沒有網路可用的情況
需要自行設定網路與開啟網路

先用 ip a 找到所有的網卡裝置

# ip a

檢查網路卡名稱,然後編輯對應的網路卡設定檔

編輯 ifcfg-XXX 檔案

$ sudo vi /etc/sysconfig/network-scripts/ifcfg-eth0

(假設你的網卡叫做 eth0

設定靜態 IP 位址

DEVICE=eth0
BOOTPROTO=none
IPADDR=192.168.1.2     # 修改 IP 位址
NETMASK=255.255.255.0  # 子網路遮罩
GATEWAY=192.168.2.254  # 網路閘道
DNS1=192.168.1.1
DNS2=8.8.8.8
DNS3=8.8.4.4
DEFROUTE=yes

ONBOOT=yes

或者 DHCP

DEVICE=eth0
BOOTPROTO=dhcp
ONBOOT=yes

最重要的就是修改 ONBOOT 區段,要設定成 yes

ONBOOT=yes

然後 :wq 存檔

最後重啟網路服務

$ sudo systemctl restart NetworkManager

因版本不同,也有可能是這樣,看發行版本

$ sudo systemctl restart network

或者可以用新版 nmcli 指令

$ sudo nmcli con mod "eth0" \
  ipv4.method "manual" \
  ipv4.addresses "192.168.1.2/24" \
  ipv4.gateway "10.200.1.1" \
  ipv4.dns "8.8.8.8,1.1.1.1"

或者修改 XXX.nmconnection 檔案

$ sudo vi /etc/NetworkManager/system-connections/eth0.nmconnection

修改 [ipv4] 段落

[ipv4]
method=manual
dns=8.8.8.8,1.1.1.1;
address1=192.168.1.2/24,192.168.1.1

最後存檔

Step 2. 安裝套件

首先在 root 身份下,
執行 yum update 指令,將套件清單更新一下

# yum update -y  

在這台能連網的電腦裝一下套件

# yum install -y tar yum-utils createrepo

然後建立一個資料夾,
前者存放所有套件,後者存放其暫存檔

# mkdir -p /tmp/yumrepo
# mkdir -p /tmp/yumrepo-installroot

Step 3. 加入你需要的來源庫 (Optional)

這裡看你的需要加入你所需要的來源庫,
例如我需要加入 docker 與 kubernetes (k8s) 的來源庫

這裡要參考 docker 安裝說明kubernetes 安裝說明 文件

加入 docker 參考
(指令僅供參考,來源庫可能隨時會更新)

# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

加入 kubeernetes 參考
(指令僅供參考,來源庫可能隨時會更新)

# cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.28/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.28/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF

Step 4. 下載你所需的套件

萬事俱備!用 yum install --downloadonly 來下載所有你所需要的套件
注意 releasever 要根據你的版本做修改,
RHEL 7.9 就用 7,RHEL 8.8 就用 8,RHEL 9.2 就用 9,以此類推

# yum install --downloadonly --releasever=7 
--installroot=/var/tmp/yumrepo-installroot 
--downloaddir=/var/tmp/yumrepo -y
docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo openssh-clients openssh-server tmux python3 vim net-tools tar yum-utils createrepo

它會很神奇的全自動去解析相依性的套件,並全部下載下來

Step 5. 建立離線 yum 來源庫

最後這邊是關鍵ㄧ步,
我們使用 createrepo 指令,將剛剛下載的一堆 rpm 建立成 yum 來源庫

# createrepo --database /var/tmp/yumrepo

不然只是散的一堆 rpm 而已

Step 6. 打包並複製到目標機

你可以用 tar 指令整個資料夾打包成 tar.gz 檔案

# tar zcvf yumrepo.tar.gz /var/tmp/yumrepo

唯有要注意的是,
RHEL 8.8/9.2 的最小安裝可能並不包含 tar 指令,
記得去資料夾找到 tar 的 rpm 多複製一份出來
(我的版本與檔案叫 tar-1.34-6.el9_1.x86_64.rpm 供參考)

至於怎麼複製到目標機?方式很多種,就不多做介紹了


Step 7. 目標機解壓縮並安裝

這裡來到離線無網路的目標機
假設你準備的檔案,一樣複製到了 /var/tmp 底下

剛剛提到 RHEL 8.8/9.2 的最小安裝可能並不包含 tar 指令,
rpm 指令安裝一下(檔名請換成你當下的)

# rpm -ivh tar-1.34-6.el9_1.x86_64.rpm

然後解壓縮

# tar zxvf yumrepo.tar.gz

Step 8. 製作來源庫描述檔

我們在離線目標機上面,直接用 cat 指令,製作一個來源庫描述檔

# cat <<EOF | sudo tee /etc/yum.repos.d/offline-yumrepo.repo
[offline-yumrepo]
name=CentOS-$releasever - yumrepo
baseurl=file:///var/tmp/yumrepo
enabled=1
gpgcheck=0
EOF

注意 baseurl 為到時候放入離線目標機的檔案路徑,可能要依據情境來修改

(其實也可以在前面的連網的虛擬機先做好一起打包,再複製到指定位置也可以)

這下你已經設定好離線的 yum 來源了

Step 9. 離線安裝

接下來的步驟就跟一般 yum 一樣了,安裝你需要的套件即可

# yum --disablerepo=\* --enablerepo=offline-yumrepo -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-compose vim net-tools yum-utils python3 sudo kubelet kubeadm kubectl --disableexcludes=kubernetes

這邊多了二個參數 --disablerepo=\*--enablerepo=offline-yumrepo
意思就是,關掉全部的來源,只開我們指定的 offline-yumrepo 來源

這樣子就完成了。
咦?我剛剛不小心幫你裝了 docker 跟 kubeadm 嗎?

參考資料

Docker image 映像檔的匯入與匯出:容器化離線部署的關鍵步驟

Docker 是一個 Open source 的容器化平台,可以輕鬆地打包、部署和運行你的應用程式。
Docker image (映像檔) 是該平台重要元件,它是一個輕量級、可執行、可快速打包的軟體包,不只是應用程式所需的執行檔,還包含運作環境和環境設定。

因為一些特殊需求,需要離線安裝 (offline install) 或者是有些時候你可能沒有一個可以使用私有 docker registry,
你可能會需要這些指令,因有常常忘記故筆記一下。

docker image 匯出

首先,先列出有什麼 images

$ docker image ls

這指令會列出所有你曾經用過的 images

找到你要的 images

然後用 docker save 來匯出 image

$ docker save myimage:latest | gzip > myimage_latest.tar.gz

docker image 匯入

在別台機器,使用 docker load 來匯入 image

docker load --input myimage_latest.tar.gz

備註:官方文件有說 docker load 除了可以直接輸入

  • *.tar 打包檔案(未壓縮)
  • *.tar.gz 壓縮檔案( gzip 壓縮格式)
  • *.tar.bz2 壓縮檔案( bzip2 壓縮格式)
  • *.tar.xz 壓縮檔案( xz 壓縮格式)

當然還是建議放在類似 Docker hub 這樣的 registry 比較好,
如果 docker hub 私有 registry 方案不符合需求,
三大雲端也有提供對應的服務,
有地端 (on-premise) 伺服器自建 docker registry 的方式
不過這個就是另外一個故事了

另外, Kubernetes (k8s) 就非常不建議用匯出匯入這種方式來做,
因為它是叢集自動部署的,你不知道它每次會部署在哪一台機器(除非你有特別指定),
如果你要做類似這樣匯入匯出,你需要在每一個 Node 都做匯出匯入,曠日費時,
建議還是架一個 docker registry 比較理想。

參考資料

Kubernetes (K8S) 自建地端伺服器 (on-premise) 建置實錄

在當今的雲端時代,Kubernetes(簡稱 K8S)作為 Open source 的 container (容器) 編排平台,已經成為許多企業和開發者的首選。它為應用程式的部署、擴展和管理提供了一個強大且靈活的解決方案。
本篇文章將詳細介紹如何在地端 (On-premise, self-host) 伺服器上搭建 Kubernetes 環境,我們將介紹所有必要的步驟,包括環境設置、安裝必要的套件、建立節點與部署應用程式。這將是一個完整的實錄,讓讀者能夠透過這篇文章深入瞭解 K8S 的建置與運作。


若是測試環境,請使用 虛擬機 (Virtual machine, VM) 來建置,
你可以用你喜歡的虛擬機程式來架設,例如 VMWare Workstation, VirtualBox 都可以,我是使用 Promox VE 裡面的 VM 功能來完成。
測試穩定再架設實體機也不遲。

順帶一提,以下這個方式安裝方式為 Bare-metal (裸金屬、裸機)的安裝方式,
這個也叫做 Vanilla Kubernetes (翻譯:單純的 Kubernetes 安裝),
如果在其他教學有看到這樣的詞彙的話,可以意識過來。

安裝地圖

Docker 跟 Kubernetes (K8S) 發展至今,百家爭鳴,門派也很多,
安裝部署方式也不盡相同,為了避免初學者混肴,
先幫你預先選好各種所需要的元件:

作業系統

  • Ubuntu Linux 22.04.2 LTS (Jammy Jellyfish)

服務們

  • kubelet
  • Container 運行環境 (Container Runtime):docker
  • cgroup drivers: 確認為 systemd (cgroup drivers v2)
  • CRI (Container Runtime Interface):使用 cri-dockerd
  • CNI (Container Network Interface):使用 Flannel

指令們

  • kubectl
  • kubeadm

這篇主要關注在如何架設 Kubernetes 叢集,
除此之外,你還需要一個配合的共用儲存空間,叢集都可以存取到的儲存空間(檔案伺服器)
可以用 TrueNAS 架設一個。

虛擬機硬體建置

這邊是我 虛擬機 (VM) 的硬體部分建置設定

  • 2 CPU
  • 4GB Ram
  • 8GB Disk 以上,建議 10GB 較穩定

註:經過測試,不要用 Promox VE 裡的 LXC Container 功能架設,
會有非常多的問題,包含權限切不乾淨等。

到時候要建立三台 VM,一台 Control Node 跟二台 Worker Node ,這是最小叢集的配置。
可以先安裝一個母版,到時候用複製 VM 的方式來達成。

虛擬機作業系統 – Ubuntu

作業系統可以是不同的發行版,我這邊用 Ubuntu 來做三台虛擬機的作業系統。

Ubuntu 可從官網下載
https://releases.ubuntu.com/

截稿時的最新 LTS 版本為 Ubuntu 22.04.2 LTS (Jammy Jellyfish)

使用 Server install image (檔名:ubuntu-22.04.2-live-server-amd64.iso) 來安裝

只安裝 SSH Server 就好,其他都先不要。
然後選項裡面有個 docker 實測發現在後續的步驟會有一些問題,請不要偷懶勾上。

<每台都做> 關掉 swap

這步驟不分角色,三台都要做

https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/

根據 kubeadm 的安裝文件,他有特別指示

MUST disable swap in order for the kubelet to work properly.

必須要關掉 swap 才能正確運作。

所以我們用以下指令永久關閉 Swap

$ sudo sysctl -w vm.swappiness=0

暫時關閉 swap 可以用 swapoff 指令

$ sudo swapoff -a

確認 swap

sysctl 的方式來列出目前 swppiness 設定值

$ sysctl vm.swappiness

或者用列檔案方式列出

$ cat /proc/sys/vm/swappiness

<每台都做> 安裝 Docker

Docker 不分角色,三台都要裝

安裝文件:
https://docs.docker.com/engine/install/ubuntu/

小弟整理的一鍵安裝指令
(科技發展迅速,整理的安裝文件有可能會過時,如果有更新版,請參考官方文件)

$ sudo apt update -y && \
sudo apt install -y ca-certificates curl gnupg && \
sudo install -m 0755 -d /etc/apt/keyrings && \
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg && \
sudo chmod a+r /etc/apt/keyrings/docker.gpg && \
echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null && \
sudo apt update -y && \
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 

修改 daemon.json 讓跳開預設網段
(如果沒有該檔案請自行新增之)

$ sudo vi /etc/docker/daemon.json

內容為

{
  "log-driver": "json-file",
  "log-opts": {
    "tag": "{{.Name}}",
    "max-size": "2m",
    "max-file": "2"
  },
  "default-address-pools": [
    {
      "base": "172.31.0.0/16",
      "size": 24
    }
  ],
  "bip": "172.7.0.1/16"
}

預設開機啟動

$ sudo systemctl enable docker && \
sudo systemctl start docker

驗證 Docker

可用 systemctl 指令查看是否有正常執行

$ sudo systemctl status docker

看看是否有 Running

可以用 docker ps 查看目前所有運行中的 container

$ docker ps

是否能夠正常顯示列表,若是初次安裝,列表是空的很正常。

Docker 版本

留下當時截稿的 Docker 版本給大家參考

# docker version
Client: Docker Engine - Community
 Version:           23.0.5
 API version:       1.42
 Go version:        go1.19.8
 Git commit:        bc4487a
 Built:             Wed Apr 26 16:21:07 2023
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          23.0.5
  API version:      1.42 (minimum version 1.12)
  Go version:       go1.19.8
  Git commit:       94d3ad6
  Built:            Wed Apr 26 16:21:07 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.20
  GitCommit:        2806fc1057397dbaeefbea0e4e17bddfbd388f38
 runc:
  Version:          1.1.5
  GitCommit:        v1.1.5-0-gf19387a
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

<每台都做> 安裝 kubeletkubeadmkubectl 三兄弟

安裝文件:
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/

小弟整理的安裝指令

$ sudo apt update -y && \
sudo apt-get install -y apt-transport-https ca-certificates curl && \
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg && \
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list && \
sudo apt-get update -y && \
sudo apt-get install -y kubelet kubeadm kubectl && \
sudo apt-mark hold kubelet kubeadm kubectl

(科技發展迅速,整理的安裝文件有可能會過時,如果有更新版,請參考官方文件)

目前安裝的版本是 kubelet 1.27.1

<每台都做> 安裝 Container Runtime Interface (CRI) – cri-dockerd

這步驟不分角色,三台都要裝

https://kubernetes.io/docs/setup/production-environment/container-runtimes/

我們用 Docker Engine 推薦的 cri-dockerd

說明文件:
https://kubernetes.io/docs/tasks/administer-cluster/migrating-from-dockershim/migrate-dockershim-dockerd/#what-is-cri-dockerd

release 頁面找到最新版,並符合您的版本的執行檔,下載並安裝

以筆者為例,筆者用的是 Ubuntu 22.04.1 LTS (Jammy Jellyfish)
代號為 Jammy 當時最新版為 v0.3.0

所以找到了 cri-dockerd_0.3.4.3-0.ubuntu-jammy_amd64.deb
這個檔案

下載安裝檔並解開安裝
(抱歉,這套件目前沒收錄在 apt-get 套件管理程式裡,沒辦法直接 apt install

$ wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.4/cri-dockerd_0.3.4.3-0.ubuntu-jammy_amd64.deb && \
sudo dpkg -i cri-dockerd_0.3.4.3-0.ubuntu-jammy_amd64.deb

然後請 systemctl 重新載入 daemon
最後啟動服務

$ systemctl daemon-reload && \
systemctl enable cri-docker.service && \
systemctl start cri-docker.service

如果找不到你的版本,可能要手動編譯並安裝

以下是官方文件提供的步驟
https://github.com/mirantis/cri-dockerd#build-and-install

驗證 cri-docker

可用 systemctl 指令確認是否有正常運行

systemctl status cri-docker.service

確認有 Running

裝完就會有 unix:///var/run/cri-dockerd.sock

複製虛擬機 (VM)

這邊步驟就是將單純的將 虛擬機 (VM) 複製二份成三台,並全部啟動。
以下分別闡述複製完要做的事情

重新產生 Machine-id

用以下指令重新產生 Machine-id

$ sudo rm /etc/machine-id && \
sudo systemd-machine-id-setup

修改 Hostname (主機名稱)

$ sudo vi /etc/hostname

分別改成對應的主機名稱

重新設定 ssh,產生全新的 known-host

sudo dpkg-reconfigure openssh-server

確認 Machine-id

sudo cat /sys/class/dmi/id/product_uuid

確認 Hostname

$ hostname

確認網卡 Mac address 位址

ip link

或者

ifconfig

都可以,如果沒有 ifconfig 指令要安裝 net-tools

apt install -y net-tools

https://superuser.com/questions/636924/regenerate-linux-host-fingerprint

如果有需要的話,可以用 dhclient 指令重新取 DHCP 的 IP
(基本上你重新產生 Machine-id 的話,就會視為別台電腦了)

$ sudo dhclient -r

<每台都做> 設定主機對應

叢集的三台機器做出來,還不知道彼此,
這邊用 /etc/hosts 檔案來讓主機們各自找到彼此

$ sudo vi /etc/hosts

根據每台主機的 IP 位址與主機名稱

192.168.1.100   k8s-ctrl
192.168.1.101   k8s-node1
192.168.1.102   k8s-node2

IP 位址在前,主機名稱在後,用 tab 分隔。

先整理好內容,再各自寫在每一台上面,每一台主機都會看到同一份資料。

確認 cgroup drivers 為 systemd

https://stackoverflow.com/questions/45708175/kubelet-failed-with-kubelet-cgroup-driver-cgroupfs-is-different-from-docker-c

直接講結論,目前最新使用的是 systemd (cgroup Version: 2)

查看 docker 的 cgroup

# docker info | grep -i cgroup

 Cgroup Driver: systemd
 Cgroup Version: 2
  cgroupns

查看 kubelet 的 cgroup

$ sudo cat /var/lib/kubelet/config.yaml | grep cgroup

cgroupDriver: systemd

可以確認是否為 systemd (cgroup Version: 2)

如果 docker 不為 systemd

可以在 daemon.json手動加上

$ sudo vi /etc/docker/daemon.json

這個段落

 "exec-opts": [
    "native.cgroupdriver=systemd"
  ],

重啟 docker

$ sudo systemctl restart docker

如果 kubelet 不為 systemd 就手動修改之

$ sudo vi /var/lib/kubelet/config.yaml

重啟 kubelet

$ sudo systemctl restart kubelet

<每台都做> 設定網路雜項值

根據文件:
https://kubernetes.io/docs/setup/production-environment/container-runtimes/#forwarding-ipv4-and-letting-iptables-see-bridged-traffic

這邊設定網路連線轉發 IPv4 位址並讓 iptables 查看橋接器的流量

用文件提供的指令操作,等等一句一句解釋:

$ cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

請 Kubernetes (K8S) 引用載入 br_netfilteroverlay 二個核心模組

$ sudo modprobe overlay
$ sudo modprobe br_netfilter

啟用 br_netfilteroverlay 二個核心模組

$ cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

設定轉發 IPv4 位址,讓 iptables 查看橋接器的流量

$ sudo sysctl --system

再不起重新啟動電腦情況下,套用設定值

檢查驗證

檢查 br_netfilteroverlay 二個核心模組有沒有被正確載入可以用以下二個指令

$ lsmod | grep br_netfilter
$ lsmod | grep overlay

檢查

  • net.bridge.bridge-nf-call-iptables
  • net.bridge.bridge-nf-call-ip6tables
  • net.ipv4.ip_forward 

這幾個系統變數是否有設定為 1,可以用 sysctl 指令來檢查:

$ sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward

設定 Control plane node(控制平台) (舊名 Master node)

終於要來設定 Control plane (控制平台) 了,如果有其他教學看到 Master node 的話,
別擔心,指的是同一件事情。

利用 kubeadm init 指令來初始化,並代入這些參數:

$ sudo kubeadm init \
    --control-plane-endpoint=192.168.1.100 \
    --apiserver-advertise-address=192.168.1.100 \
    --node-name k8s-ctrl \
    --pod-network-cidr=10.244.0.0/16 \
    --cri-socket unix:///var/run/cri-dockerd.sock

參數說明

  • control-plane-endpoint
    指明 Control plane (控制平台) 是哪個網址,這邊設定好目前這台 IP 位址即可,假設為 192.168.1.100
    (這設定值可省略)
  • apiserver-advertise-address
    指明 API server 的廣播地址,預設就是 Control plane (控制平台) IP 位址,假設為 192.168.1.100
    (這設定值可省略)
  • node-name
    指明 Control plane (控制平台) 的名字,這裡跟主機名稱一致即可。
  • pod-network-cidr
    指明 pod 內部網路使用的網段,這邊因為配合 Flannel CNI,請保留 10.244.0.0/16 請不要修改
  • cri-socket
    指明使用的 CRI 使用 unix:///var/run/cri-dockerd.sock 這設定值 請不要修改

記錄一下運作的樣子

# kubeadm init --pod-network-cidr=10.244.0.0/16 --cri-socket unix:///var/run/cri-dockerd.sock

[init] Using Kubernetes version: v1.27.1
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
W0503 18:35:29.693213    1321 images.go:80] could not find officially supported version of etcd for Kubernetes v1.27.1, falling back to the nearest etcd version (3.5.7-0)
W0503 18:35:46.627127    1321 checks.go:835] detected that the sandbox image "registry.k8s.io/pause:3.6" of the container runtime is inconsistent with that used by kubeadm. It is recommended that using "registry.k8s.io/pause:3.9" as the CRI sandbox image.
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [k8s-ctrl kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.1.100]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [k8s-ctrl localhost] and IPs [192.168.1.100 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [k8s-ctrl localhost] and IPs [192.168.1.100 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Starting the kubelet
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
W0503 18:36:08.496065    1321 images.go:80] could not find officially supported version of etcd for Kubernetes v1.27.1, falling back to the nearest etcd version (3.5.7-0)
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 8.502958 seconds
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config" in namespace kube-system with the configuration for the kubelets in the cluster
[upload-certs] Skipping phase. Please see --upload-certs
[mark-control-plane] Marking the node k8s-ctrl as control-plane by adding the labels: [node-role.kubernetes.io/control-plane node.kubernetes.io/exclude-from-external-load-balancers]
[mark-control-plane] Marking the node k8s-ctrl as control-plane by adding the taints [node-role.kubernetes.io/control-plane:NoSchedule]
[bootstrap-token] Using token: c1hnqs.c4imcnzqxqry62d0
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to get nodes
[bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] Configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] Configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

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

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

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

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

kubeadm join 192.168.1.100:6443 --token cxxxxs.c4xxxxxxxxxxxxd0 \
    --discovery-token-ca-cert-hash sha256:103d7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx5b1b6

如果沒意外的話,完成之後會看到

Your Kubernetes control-plane has initialized successfully!

別太高興,設定還沒完,先把 kubeadm join 語句先存起來備用

然後依照步驟,
若是 root 使用者,

.bash_profile 或者 .zsh_profile 設定環境變數

$ export KUBECONFIG=/etc/kubernetes/admin.conf

若是一般使用者,請依照指令依序設定

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

註:加入 token 是有期限的,如果隔太久沒有整個步驟做完,
或者忘記了、被洗掉了,可以用指令重新生成加入指令

$ kubeadm token create --print-join-command

<Control plane 做> 安裝 Helm 套件管理程式

Helm 是 Kubernetes (k8s) 所使用的套件管理程式,
類似 apt-get 可以方便我們安裝元件,免去一點設定的雷

Helm 只要裝在 Control plane (舊名 Master node) 就可以了

安裝文件
https://helm.sh/docs/intro/install/

從 APT 安裝(推薦),可用小弟整理之一鍵安裝指令

$ curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null && \
sudo apt install apt-transport-https --yes && \
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list && \
sudo apt update -y && \
sudo apt install -y helm

(科技發展迅速,整理的安裝文件有可能會過時,如果有更新版,請參考官方文件)

也可從 Script 安裝

curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 && \
chmod 700 get_helm.sh && \
./get_helm.sh

二者效果相同,擇一安裝即可。

<Control plane 做> 安裝 Flannel CNI

https://github.com/flannel-io/flannel

使用 Helm 安裝 Flannel,將之安裝在 kube-flannel 的 namespace,可用小弟整理之一鍵安裝指令

Flannel 只要在 Control plane (舊名 Master node) 上面下指令,就會部署到整個叢集。

可以使用以下整理之指令一鍵安裝

$ kubectl create ns kube-flannel && \
kubectl label --overwrite ns kube-flannel pod-security.kubernetes.io/enforce=privileged && \
helm repo add flannel https://flannel-io.github.io/flannel/ && \
helm install flannel --set podCidr="10.244.0.0/16" --namespace kube-flannel flannel/flannel

(科技發展迅速,整理的安裝文件有可能會過時,如果有更新版,請參考官方文件)

指令意思大致為:

  1. 建立一個 namespace (命名空間)名叫 kube-flannel
  2. 給定 kube-flannel 特權的權限
  3. 加入 repo 網址
  4. 用 helm 安裝 Flannel

設定 Worker node

這下終於可以設定 Worker node 了

還記得剛剛留下來的指令

kubeadm join 192.168.1.100:6443 --token cxxxxs.c4xxxxxxxxxxxxd0 \
    --discovery-token-ca-cert-hash sha256:103d7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx5b1b6

什麼?忘記了?

可以用指令重新生成加入指令

$ kubeadm token create --print-join-command

出現 kubeadm join 指令之後,加上指明 cri-socket 就可以執行了

變成這樣

$ sudo kubeadm join 192.168.1.100:6443 
    --token cxxxxs.c4xxxxxxxxxxxxd0 \
    --discovery-token-ca-cert-hash sha256:103d7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx5b1b6 \
    --cri-socket unix:///var/run/cri-dockerd.sock

記錄一下運作情形

$ kubeadm join 192.168.1.100:6443 
    --token cxxxxs.c4xxxxxxxxxxxxd0 \
    --discovery-token-ca-cert-hash sha256:103d7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx5b1b6 \
    --cri-socket unix:///var/run/cri-dockerd.sock

[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

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

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

這樣就加入叢集了

重設整個叢集

如果整個叢集有其他問題,做爛了,可以用以下方法重新設定

進到每一台 node 裡面,利用 kubeadm reset 重置,記得代入 cri-socket

如下:

$ kubeadm reset -f --cri-socket unix:///var/run/cri-dockerd.sock

記錄一下運作情形

$ kubeadm reset -f --cri-socket unix:///var/run/cri-dockerd.sock

[preflight] Running pre-flight checks
W0507 02:43:32.160215    1264 removeetcdmember.go:106] [reset] No kubeadm config, using etcd pod spec to get data directory
[reset] Deleted contents of the etcd data directory: /var/lib/etcd
[reset] Stopping the kubelet service
[reset] Unmounting mounted directories in "/var/lib/kubelet"
W0507 02:43:32.169757    1264 cleanupnode.go:134] [reset] Failed to evaluate the "/var/lib/kubelet" directory. Skipping its unmount and cleanup: lstat /var/lib/kubelet: no such file or directory
[reset] Deleting contents of directories: [/etc/kubernetes/manifests /etc/kubernetes/pki]
[reset] Deleting files: [/etc/kubernetes/admin.conf /etc/kubernetes/kubelet.conf /etc/kubernetes/bootstrap-kubelet.conf /etc/kubernetes/controller-manager.conf /etc/kubernetes/scheduler.conf]

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

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

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

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

它會提示你,有些防火牆規則並不會完全刪掉

可以刪掉 cni 資料夾來重置

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

對應文件:
https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-reset/

<Control plane 做> 測試檢查叢集

測試 Kubernetes 是否正常運作,
在 Control plane (控制平台) 裡可以用二個指令觀察一下:

取得所有的 Pods

kubectl get pods 指令取得 Pod,加上 -A 代表包含所有 namespace (命名空間)

以下指令就是取得所有的 Pods

$ kubectl get pods -A

取得所有的 pods

$ kubectl get pods -A

NAMESPACE      NAME                               READY   STATUS    RESTARTS   AGE
kube-flannel   kube-flannel-ds-8rtvc              1/1     Running   0          30s
kube-flannel   kube-flannel-ds-9w2vw              1/1     Running   0          30s
kube-flannel   kube-flannel-ds-jdndp              1/1     Running   0          30s
kube-system    coredns-5d78c9869d-df989           1/1     Running   0          4m20s
kube-system    coredns-5d78c9869d-s8ftg           1/1     Running   0          4m19s
kube-system    etcd-k8s-ctrl                      1/1     Running   0          4m35s
kube-system    kube-apiserver-k8s-ctrl            1/1     Running   0          4m33s
kube-system    kube-controller-manager-k8s-ctrl   1/1     Running   0          4m35s
kube-system    kube-proxy-2qrjj                   1/1     Running   0          4m19s
kube-system    kube-proxy-bpk94                   1/1     Running   0          3m51s
kube-system    kube-proxy-mgrjn                   1/1     Running   0          3m57s
kube-system    kube-scheduler-k8s-ctrl            1/1     Running   0          4m36s

你應該要看到:

  • kube-flannel 的若干個 Pod 為 Running
    (若是 Pending 或者 CrashLoopBackOff 可能要除錯)
  • kube-system (k8s 核心元件) 的二個 coredns 的 Pod 為 Running
    (若是 Pending 或者 CrashLoopBackOff 可能要除錯)
  • kube-system (k8s 核心元件) 的 etcdRunning
  • kube-system (k8s 核心元件) 的 kube-controller-manager 的 Pod 為 Running
  • kube-system (k8s 核心元件) 的 kube-apiserver 的 Pod 為 Running
  • kube-system (k8s 核心元件) 的 kube-scheduler 的 Pod 為 Running
  • kube-system (k8s 核心元件) 的若干個 kube-proxy 的 Pod 為 Running

當然,放在 kube-system 裡面的 Pod 屬於系統保留的,請勿更動修改。

取得所有 nodes (主機節點)

你可以用 kubectl get nodes -A 指令來取得所有運作的 nodes

$ kubectl get nodes -A

NAME        STATUS   ROLES           AGE     VERSION
k8s-ctrl    Ready    control-plane   4m40s   v1.27.1
k8s-node1   Ready    <none>          3m59s   v1.27.1
k8s-node2   Ready    <none>          3m53s   v1.27.1

你應該要看到你的叢集,三台都是 Ready

[教學] LibreNMS 串接 LINE 通知

在現代科技世界中,網路裝置監控與通知非常重要。LibreNMS 是一個開源的網路監控系統,可以用來監控網絡設備、伺服器…等。然而,對於一些使用者而言,LibreNMS 的通知設定可能會有一些小困難。我們將在這個系列文章中介紹幾種常用的通知方式:電子郵件、LINE、Telegram、GoogleChat…等。這些通知方式都可幫助使用者即時收到監視警報和報告,以便快速回應並保護他們的系統和網絡安全。

LibreNMS 系列文:

剛好工作會用到 LINE,所以把通知轉到 LINE 去,就讓我們看看要怎麼做吧!

抓下 Pull Request 的內容

目前截稿至今,目前還沒有人把 LINE 機器人通知功能的做進去,
小弟就順手貢獻程式碼,把這個功能加上 😎。

當然 LibreNMS 整套程式授權是 GPL,想當然爾貢獻的程式碼也是 GPL,歡迎大家使用。

只是官方還沒有允許 Pull Request 的內容,
如果覺得不錯,可以幫忙在原串留個言,加速系統演進。

https://github.com/librenms/librenms/pull/14802


也因為目前小弟貢獻的程式碼尚未被合併,預設這功能「沒有包含」在官方系統裡,
需要你手動抓檔案來補

檔案在此:
https://github.com/j796160836/librenms/blob/feature_line_messaging/LibreNMS/Alert/Transport/Linemessagingapi.php

按下 Raw 得到原始檔,然後另存新檔,存成 LineMessagingAPI.php

如果跟我一樣是使用 Docker 的話,
放在目錄中,修改 docker-compose.yml 把這個檔案用掛載的方式,
類似 Hot patch 的方式把它放進去

docker-compose.yml 的 LibreNMS 設定,裡頭的 volumes 區塊,加上一句:

- ./LineMessagingAPI.php:/opt/librenms/LibreNMS/Alert/Transport/Linemessagingapi.php

(注意大小寫!)

如果你是照個之前那篇文的方式架設的話,
變成類似底下的範例:

docker-compose.yml

version: '3.5'

services:
  web:
    image: jarischaefer/docker-librenms
    restart: always
    ports:
      - '80:80'
    volumes:
      - ./librenms_data/logs:/opt/librenms/logs
      - ./librenms_data/rrd:/opt/librenms/rrd
      # Add this line
      - ./LineMessagingAPI.php:/opt/librenms/LibreNMS/Alert/Transport/Linemessagingapi.php
    environment:
      APP_KEY: base64:NA......................................SdA=
      DB_HOST: librenms_database
      DB_USER: librenms
      DB_PASS: Tuq.........tai
      DB_NAME: librenms
      POLLERS: 16
      BASE_URL: http://192.168.1.2
      TZ: Asia/Taipei
    depends_on:
      - mysql
  mysql:
    image: mariadb:10.5
    container_name: librenms_database
    restart: always
    volumes:
      - ./librenms_data/mysql:/var/lib/mysql
      - ./LineMessagingAPI.php:/opt/librenms/LibreNMS/Alert/Transport/Linemessagingapi.ph
    environment:
      TZ: Asia/Taipei
      MYSQL_ROOT_PASSWORD: Cho.........lan
      MYSQL_USER: librenms
      MYSQL_PASSWORD: Tuq.........tai
      MYSQL_DATABASE: librenms

官方 docker 架設方式也雷同,就不贅述了。

設定 LINE 開發者帳號

1. 前往Line開發者網站並註冊帳戶

https://developers.line.biz/

使用您的真人帳號來進行註冊。

2. 新增一個 Channel (頻道)

  1. 如果你沒有 Provider (供應商),請先建立一個。

  1. 創建一個 Channel (頻道),選擇 Messaging API ,然後填寫表單。

一些必填欄位說明:

  • Channel type 頻道類型:Messaging API
  • Provider 供應商:(選擇您的 Provider (供應商))
  • Company or owner’s country or region 公司或擁有者的國家或地區:(選擇您的地區)
  • Channel name 頻道名稱:(預設它將是您的 LINE機器人的名稱,送出後無法編輯)
  • Channel description 頻道描述:(預設它將是您的 LINE 機器人的描述)
  • Category 類別:(選擇適當的類別)
  • Subcategory 子類別:(選擇適當的子類別)

然後按送出建立它。

  1. 到 Channel 的 "Messaging API" 頁籤,這裡列出了一些你需要注意的重要值。
  • Bot basic IDQR code 是您的 LINE 機器人的 ID 和 QR code。
  • Channel access token (long-lived),通行 token ,效力等同於密碼,這將在 LibreNMS 中使用,請妥善保管。

  1. 使用您的真實 LINE 帳號加你建立的 LINE機器人為好友。

3. 設置 webhook 以獲取「收件人ID」

這邊講的「收件人ID」是指,你未來希望系統要把訊息發送到哪裡
它可以是

  • 聊天群組(要抓取 groupID
  • 個人私訊(要抓取 userID

以下是獲取「收件人ID」的步驟。

使用以下 NodeJS 程式和 ngrok 轉送臨時 https 連線。
以下步驟是在 Mac 底下操作。Linux 也適用這些步驟。

LINE-bot-RecipientFetcher

抓取程式

git clone https://github.com/j796160836/LINE-bot-RecipientFetcher.git

切換到該資料夾

cd LINE-bot-RecipientFetcher

執行程式

$ node index.js

使用 ngrok 將該連接埠暴露到網路上

ngrok http 3000

到 Channel (頻道) 中的 "Messaging API" 頁籤,
將 Webhook URL 填寫 ngork 所產生的網址 https://<your ngrok domain>/webhook

4. 獲取「收件人ID」

如果您想讓 LINE 機器人向自己(或者目標發送對象)發送訊息,這是獲取 userID 的步驟。

  • 使用您的真實帳戶(或者目標發送對象)向您的 LINE 機器人發送訊息

範例資料:

{"type":"user","userId":"U527xxxxxxxxxxxxxxxxxxxxxxxxxc0ee"}

如果您想讓 LINE 機器人向一個群組發送訊息,這是獲取 groupID 的步驟。

  • 將您的LINE機器人添加到群組中
  • 使用您的真實帳戶在群組中發訊息

程式會把 groupID 印出來,這個就是收件人ID,請保管好。

範例資料:

{"type":"group","groupId":"Ce51xxxxxxxxxxxxxxxxxxxxxxxxxx6ef","userId":"U527xxxxxxxxxxxxxxxxxxxxxxxxxc0ee"} ```

LibreNMS 的通知設定

接下來回到 LibreNMS 繼續做設定。

1. 設定 Alert transport (通知管道)

Alerting > Alert Transports 頁面,新增一個 Alert transport (通知管道)。

  • Transport name 傳輸名稱:Line
  • Trsansport type 傳輸類型:Line Messanging API
  • Access token 存取權杖:(您的頻道存取權杖)
  • Recipient 收件人ID:_(填入剛剛抓的 userIDMARKDOWN_HASH2df121e268641afe58606b37a6e37a50MARKDOWNHASH )

2. 在 Alert rules (監控規則) 中掛上 Alert transport (通知管道)

Alerts > Alert rules 設定警告通知規則,

在想要通知的規則上按編輯,
(以預設通知 Device Down! Due to no ICMP response 為例)

Transports 的地方加上這個通知管道。

可以在 Alert rules 設定好通知的:

  • Max alert 通知數:最多要通知幾次
  • Delay 延遲:問題發生後,延遲多久再來通知,
    過濾掉時好時壞跳來跳去的情況
  • Interval 間隔:通知間隔,意指通知後隔多久再通知,該值會配合 Max alert 來使用。

例如:
Max alert: 3 / Delay: 30m / Interval: 5m
白話文就是:先延遲 30 分鐘再通知,通知共 3 次,每 5 分鐘通知一次

這邊可以依照需求來設定通知次數與頻繁度。

如果剛剛 Default Alert 有設定 ON 的話,應該所有規則都會出現才對。

測試

最後,你需要測試他們的通知設置是否正確。可以通過觸發警報或手動測試通知來完成這個步驟。

手動觸發測試

Alerts > Alert Transports 中,設定這個通知管道、通知方式。

就會收到測試訊息

以上,就是設定的所有內容,希望這個教學對您有幫助。

參考資料

[教學] 用 Gmail 來串接 LibreNMS 的 Email 通知

在現代科技世界中,網路裝置監控與通知非常重要。LibreNMS 是一個開源的網路監控系統,可以用來監控網絡設備、伺服器…等。然而,對於一些使用者而言,LibreNMS 的通知設定可能會有一些小困難。我們將在這個系列文章中介紹幾種常用的通知方式:電子郵件、LINE、Telegram、GoogleChat…等。這些通知方式都可幫助使用者即時收到監視警報和報告,以便快速回應並保護他們的系統和網絡安全。

LibreNMS 系列文:

這篇是利用 Gmail 來設定 電子郵件的通知方式

Gmail 建立應用程式密碼

首先,登入一個 Gmail 帳號,這個帳號未來會為系統寄件人。
你需要在 Google Gmail 中創建一個應用程式密碼。

到 Google 帳戶中的 「安全性」頁籤,

https://myaccount.google.com/security

在「登入 Google」的區段,有一個「應用程式密碼 App passwords」的選項

有件事情要先提醒,這必須要先開啟「兩步驟驗證 (Two-factor authentication (2FA))」,才會出現這個選項

它會重新驗證你的帳號,然後出現以下畫面:

Select the app and device you want to generate the app password for.

你可以直接選 Other (Custom name) 然後給定一個名字,按下產生後產生。

這邊要注意,「密碼只會出現一次」 請好好保存你的密碼。

LibreNMS 的通知設定

以下分為幾個步驟:

1. 設定發信方式

接下來,在 LibreNMS 的設定中,
到 Global settings 中,找到 Alert settings 選 Email options

打開 Enable email alerting 選項,
以下參考 Gmail 的 SMTP 的資訊設定,如果有修改,以官方文件為主。

  • Enable email alertingEnabled 開
  • From name:填入「寄件人」
    (例如:`"LibreNMS System")
  • From email address:填入「寄件人」的訊息
    (例如:"LibreNMS System" <[email protected]> ,這邊 E-mail 地址就填你剛剛設定的 Gmail 地址)
  • Use HTML emailsEnabled 開
  • How to deliver mailSMTP
  • SMTP Serversmtp.gmail.com
  • SMTP port setting587
  • EncryptionTLS
  • Auto TLS supportEnabled 開
  • SMTP authenticationEnabled 開
  • SMTP Auth username:填你剛剛設定的 Gmail 地址(例如: [email protected]
  • SMTP Auth password:填剛剛申請的「應用程式密碼」,不是你個人帳號的密碼唷

2. 建立通知管道 (Alert Transports)

然後,在 Alerts > Alert Transports 中,設定這個通知管道、通知方式。

按「Create alert transport」新增一個通知管道、通知方式

參數說明:

  • Transport name:取一個名字(例如:Email
  • Transport typeMail
  • Default Alert:是否為預設主要通知管道,這個依照你的需求來設定
  • Email:設定一個「收件人」,如果有發生警告,都會往他那邊寄信來通知
  • Include Graphs:是否包含圖片,這個保留預設 ON 即可

然後按「Save Transport」儲存

3. 在監控規則中掛上通知管道

最後在 Alerts > Alert rules 設定警告通知規則,

在想要通知的規則上按編輯,
(以預設通知 Device Down! Due to no ICMP response 為例)

Transports 的地方加上這個通知管道。

可以在 Alert rules 設定好通知的:

  • Max alert:最多要通知幾次
  • Delay:問題發生後,延遲多久再來通知,
    過濾掉時好時壞跳來跳去的情況
  • Interval:通知間隔,意指通知後隔多久再通知,該值會配合 Max alert 來使用。

例如:
Max alert: 3 / Delay: 30m / Interval: 5m
白話文就是:先延遲 30 分鐘再通知,通知共 3 次,每 5 分鐘通知一次

這邊可以依照需求來設定通知次數與頻繁度。

如果剛剛 Default Alert 有設定 ON 的話,應該所有規則都會出現才對。

測試

最後,你需要測試他們的通知設置是否正確。可以通過觸發警報或手動測試通知來完成這個步驟。

手動觸發測試

Alerts > Alert Transports 中,設定這個通知管道、通知方式。

旁邊有一個 打勾符號 可以按,按一下測試。
成功的話就會收到 Email 通知。

總結來說,這個教學文章向使用者展示了如何在 LibreNMS 中設置 Gmail 通知。這將使管理人能夠即時收到他們的監視警報和報告。這對於系統管理員和網絡工程師來說非常重要,因為它可以幫助他們及時處理問題,保護他們的系統和網絡的安全。

參考資料

[教學] 用 Docker 來架設 LibreNMS 架設網路服務監控系統

LibreNMS 是一套 Open source 的功能完整的網路監控系統,
使用 SNMP (Simple Network Management Protocol) 協定來搜集電腦系統的資料。

初次研究後,就發現它可以協助解決設備很多在線上,
測量設備穩定性的事情。
就算該裝置沒有支援 SNMP,也可以使用純 ping 的方式來監控所有線上的設備。
就一步一步帶大家怎麼設定與架設。

LibreNMS 系列文:

以下使用 Docker 裝在 Linux 主機上。
至於 Linux 版本就看大家喜好,
以下示範使用 Ubuntu 系統,若是其他發佈版可以依樣畫葫蘆稍加修改。

我們今天用的是 jarischaefer/docker-librenms 套件來架設,
如果作者版本有更新安裝方式,再煩請告知。

GitHub 位置:
https://github.com/jarischaefer/docker-librenms

Step1. 產生密鑰

使用指令產生密鑰

$ sudo docker run --rm jarischaefer/docker-librenms generate_key

每台系統密鑰皆不相同,不可以直接複製。
如果有多台要架設,請記得更換密鑰!(這步驟很常忘記)

Step2. 編輯 docker-compose.yml

有關 docker 的設定方式就不贅述了。
有用過 docker 的朋友應該很熟悉 docker-compose 這指令。

現在要來做一個新的 docker-compose.yml

docker-compose.yml

version: '3.5'

services:
  web:
    image: jarischaefer/docker-librenms
    restart: always
    ports:
      - '80:80'
    volumes:
      - ./librenms_data/logs:/opt/librenms/logs
      - ./librenms_data/rrd:/opt/librenms/rrd
    environment:
      APP_KEY: base64:NA......................................SdA=
      DB_HOST: librenms_database
      DB_USER: librenms
      DB_PASS: Tuq.........tai
      DB_NAME: librenms
      POLLERS: 16
      BASE_URL: http://192.168.1.2
      TZ: Asia/Taipei
    depends_on:
      - mysql
  mysql:
    image: mariadb:10.5
    container_name: librenms_database
    restart: always
    volumes:
      - ./librenms_data/mysql:/var/lib/mysql
    environment:
      TZ: Asia/Taipei
      MYSQL_ROOT_PASSWORD: Cho.........lan
      MYSQL_USER: librenms
      MYSQL_PASSWORD: Tuq.........tai
      MYSQL_DATABASE: librenms

重要參數說明

web 部分

  • APP_KEY:密鑰(每台電腦皆不同,請自行產生)
  • DB_HOST:mysql 的 IP 位址(不需修改)
  • DB_USER:mysql 使用者
  • DB_PASS:mysql 密碼
  • DB_NAME:mysql 資料庫名稱
  • POLLERS:輪詢器數量
  • BASE_URL:預設網址(很重要,請記得改!就算是本機服務,也請修改成正確的網址。)
  • TZ:時區,這裡設定台灣 (Asia/Taipei)

mysql 資料庫部分

這裡使用 MariaDB 資料庫,要換成 mysql 5.7 也可以。

  • TZ:時區(與網頁伺服器一致即可)
  • MYSQL_ROOT_PASSWORD:資料庫 root 密碼
  • MYSQL_USER :使用者(與 DB_USER 一致即可)
  • MYSQL_PASSWORD :密碼(與 DB_PASS 一致即可)
  • MYSQL_DATABASE :資料庫名稱(與 DB_NAME 一致即可)

Step3. 建立資料夾

建立相關路徑的資料夾,讓它能夠掛載。

$ mkdir -p ./librenms_data && \
mkdir -p ./librenms_data/logs && \
mkdir -p ./librenms_data/rrd && \
mkdir -p ./librenms_data/mysql

Step4. 啟動整組服務

利用 docker-compose 把整組服務啟動。

$ sudo docker-compose up -d

Step5. 初始化資料庫

服務整組起來之後,網頁瀏覽器應該可以看到這個訊息:

Database inconsistencies found during a database error, please fix to continue.

Your database is out of date!

不用太緊張,這是正常現象,等下下指令來初始化資料庫。

首先要先找到 librenms_web 的 container,
如果是 docker-compose 整組起來的話,可能會叫做 librenms_web_1
執行指令前可以用 docker ps 指令來查看你的目前起的服務名稱(或 ID),確認一下。

$ sudo docker exec librenms_web_1 setup_database

這步驟是初始化它 LibreNMS 的資料庫。
如果失敗了,整個服務會起不來。

Step6. 建立 admin 管理者

它服務跑起來預設是 「沒有任何帳號的」,
我們用該指令來建立 admin 管理者

$ sudo docker exec librenms_web_1 create_admin

librenms_web_1 名稱為 container_id,
可用 docker ps 指令來查看你的目前起的服務名稱(或 ID)。

這指令將會建立一個 admin 管理者:

記得去改密碼。記得去改密碼。記得去改密碼。
很重要講三次。

瀏覽你的本機網址,就可以看到 LibreNMS 的主頁面了。

參考資料

[教學] 在 QNAP NAS 機器上設定 WireGuard VPN

繼上一篇 [Linux 架站] Wireguard VPN 設定教學 手工建置的方式,有嘗試在 QNAP NAS 上面建立 WireGuard VPN。

但發現雖然 QNAP 有 Container station (其實裡面也是跑 Docker) 但沒辦法使用該 Docker Image
因為裡面有一個 net.ipv4.conf.all.src_valid_mark=1 選項它核心沒辦法支援。

不過呢,在 QVPN Service 3 直接原生支援 WireGuard 哦!
(QVPN Service 3 在 x86 機種上 QTS 5.0.0 以後版本支援)

這裡有繁體中文的官方教學文件,看起來圖文並茂,但還是有一些遺漏的地方,
您還是要需要知道純手工 Wireguard 怎麼設定才行。

以下是一些操作步驟,可以跟著一步一步操作就可以建立了!

安裝步驟

簡單介紹一下設定步驟:

啟用 WireGuard VPN 伺服器

  • 開啟 QVPN Service。
  • 前往[VPN 伺服器]>[WireGuard]。
  • 按一下[啟用 WireGuard VPN 伺服器]。

填寫

  • ServerName 伺服器名稱
  • PrivateKey 按一下 [Generate Keypairs] 按鈕 產生伺服器金鑰一對
  • PublicKey 會自動產生出可供複製
  • IP address 伺服器的內部 IP
  • Listen port 聆聽的連接埠 (假設為: 51820 ,可依需求修改)
  • Network interface 接收的介面,預設 All 就可以
  • DNS Server 使用的 DNS 伺服器
  • Peer Table 用戶端列表

按下 Add Peer 按鈕

  • Peer name 用戶端名稱,名字可自己取
  • Public key 用戶端的公鑰,需自行產生,這留到後面再說
  • Preshared key 共用加密金鑰,目前沒有設定,留空即可
  • Endpoint 伺服器位置,伺服器的連接方式
  • Allowed IPs 允許的 IP 網段
  • Persistent keepalive 維持連線時間,保留預設值 10 秒 即可

快速整理一下,跟純手工建置的差異:

  • 不用自行產生伺服器金鑰了,按一下 [Generate Keypairs] 就可以產生
  • 不用自行整理用戶端列表,IP 也自動隨著流水號增加

其他還是要自己手工做的部分:

  • 需要 自行撰寫 用戶端的設定文件
  • 自行產生 用戶端的金鑰,填回網頁介面
  • 如果有手機用戶的朋友,設定檔需要 自行產生 QRcode

個人覺得這 QNAP 只是做了半套而已,
如果有用過 wg-easy 的話,幾乎都全自動幫您產生好了。

安裝 wireguard-tools 並找到 wg 指令

這個步驟只要在幫大家做設定檔的那台電腦操作就可以了,
不需要每台使用 wireguard 的電腦都裝。

以下步驟要先找到 wg 指令,以 Mac 為例,可以用 brew 來安裝

$ brew install wireguard-tools

如果沒有 wg 指令,又剛好有用 docker 的話,可以用這個 image 來借用一下

$ sudo docker run --rm -it antrea/wireguard-go:0.0.20210424 /bin/bash

在 container 裡面執行就可以了。

產生用戶端金鑰

$ wg genkey | tee client1_privateKey | wg pubkey > client1_publicKey

就會產生二個檔案:

  • client1_privateKey 用戶01私鑰
  • client1_publicKey 用戶01公鑰

公鑰回填到 QNAP 的介面,存好私鑰,等一下來手動撰寫設定文件。

撰寫用戶端設定文件

以下列出 Client 設定檔,可以參酌修改。
需要填入:

  1. 用戶01私鑰 Client01 PrivateKey,帶入剛剛產生的私鑰
  2. 伺服器公鑰 Server PublicKey,這個從 QNAP 介面得到
  3. 配發用戶01的 IP (假設為:198.18.7.2/32,這邊要與 QNAP 介面一致)
  4. 伺服器位址 (假設為:vpn.example.com:51820,也就是你的 QNAP NAS 位址,是公開 IP 或者公開 DNS,這個等等補充)
  5. 共享密鑰 (如果有的話,本範例沒有)
  6. AllowedIPs 這部分為需要 VPN 轉導的區域,要填入一個 CIDR 的網段。
    換言之,需要定義一個區域,如果看到該區域,就走 VPN 連線。範例是給 192.168.1.0/24,意思是電腦若看到該網段則走 VPN,其餘走一般連線。
    如果需要全部都轉導的話填入:0.0.0.0/0
[Interface]
PrivateKey = AOAHHE........(用戶01私鑰 Client01 PrivateKey)........7RDE0=
Address = 198.18.7.2/32
DNS = 1.1.1.1

[Peer]
PublicKey = RNrgG........(伺服器公鑰 Server PublicKey)........EpJ0A=
AllowedIPs = 192.168.1.0/24
Endpoint = vpn.example.com:51820
PersistentKeepalive = 30

產生設定檔 QRcode

其實很簡單,就是把上述的設定檔,直接放入 QRcode 產生器就好了~

https://www.the-qrcode-generator.com/

類型選擇 Free text 你就會得到一個超密的 QRcode,你的手機就能掃了。

參考資料

Docker 設定避開衝突網段 ( 172.17.0.0/16 )

Docker 的好,用過的人就知道,
然而 Docker 在特定的網段裡面(例如: 172.17.x.x),會造成衝突。

嚴重造成 host 整台主機連不進來,無法連線。

這個時候要修改你的 Docker 預設網段,讓他跳過跟外界相符合的網段。

註:如果 你的開發機 (client) 與 你的伺服器 (server),兩個端點都有裝 Docker,
那兩個端點都要調整,跳開其網段。

註:預設 Docker 使用 172.17.0.0/16 網段。

修改 Docker 預設網段

Linux 系統的話,建立(或修改)daemon.json 這個檔案
(這個檔案預設是沒建立的,請自行建立。)

$ sudo vi /etc/docker/daemon.json

以下是範例格式,為 JSON 格式:

{
  "log-driver": "journald",
  "log-opts": {
    "tag": "{{.Name}}"
  },
  "default-address-pools": [
    {
      "base": "172.6.0.0/16",
      "size": 24
    }
  ],
  "bip": "172.7.0.1/16"
}

修改 bipdefault-address-pools 的欄位,輸入一個新的不衝突的網段即可。
(範例是改成 172.7.0.1/16172.6.0.0/16

  • bip 欄位是 docker 預設會開啟的網段。
  • default-address-pools 欄位是 docker-compose 如果有設定 network 區段的話,預設會配給的網段區域。

然後重新啟動 Docker 服務

$ sudo systemctl restart docker

這樣你的主機就會連線正常了。

自訂你自己服務的網段

在某些用途下你會需要一個特定幾個 container 能相互溝通的網路環境
你需要修改你的 docker network create 指令

例如:

$ sudo docker network create mynetwork --subnet=172.6.0.0/16

加上 --subnet= 參數,手動指定網段給他。

使用 docker-compose ?

如果是使用 docker-compose 的話,你可以這樣調整 networks 段落,

docker-compose.yml

version: '3.5'

services:
  web:
    image: crccheck/hello-world
    ports:
      - '80:8000'
    networks:
      - mynetwork
networks:
  mynetwork:
    ipam:
      config:
        - subnet: '172.6.0.0/16'

如果你要寫完整一點,這樣也是可以的。

networks:
  mynetwork:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: '172.6.0.0/16'
          gateway: '172.6.0.1'

ipamconfig 段落設定 subnetgateway 即可。

爾或者,修改 /etc/docker/daemon.json 中的 default-address-pools 的欄位,前面已經說過就不贅述了。

怎麼驗證?

你可以用 netstat 指令查詢

$ netstat -rn
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
0.0.0.0         192.168.1.1     0.0.0.0         UG        0 0          0 eth0
172.6.0.0       0.0.0.0         255.255.0.0     U         0 0          0 br-c3ff076483d3
172.7.0.0       0.0.0.0         255.255.0.0     U         0 0          0 docker0

docker network ls 指令

$ docker network ls
NETWORK ID     NAME          DRIVER    SCOPE
da8e2609c93e   bridge        bridge    local
0eef0fedfb81   host          host      local
79cd2edd8448   none          null      local
c3ff076483d3   mynetwork     bridge    local

其中你可以看到範例 c3ff076483d3 對應 mynetwork 網段為 172.6.0.0

參考資料

Proxmox VE (PVE) 伺服器虛擬化環境 安裝筆記

最近接觸到 Proxmox VE (PVE) 覺得超驚人的,把相關安裝知識整理後分享給大家。
Proxmox VE 是基於 Debian 修改的開源 (Open source) 虛擬化管理套件(基於 QEMU/KVM 與 LXC 技術)。
講到虛擬化 (virtualization),一定會提到 VMware 這家公司,它的 VMware ESXi 也是一絕,但 vSphere 系列需要授權,雖然有免費版本有對應的功能限制,下次會另開一篇來介紹。

本篇就來講講要怎麼開始、怎麼入門吧!

製作安裝 Live USB (Mac & Windows 使用者)

官方文件有特別強調,製作安裝 USB 時,絕不能使用 UNetbootin 這個工具
會無法正常開機,小弟本身也踩過這個雷。
(因為 UNetbootin 有另外處理開機引導 (bootloader) 的部分,這部分與 Proxmox VE 不相容。)

Mac & Windows 使用者 推薦用 Etcher

https://etcher.io

照個指示選擇 ISO 檔案,然後插入隨身碟選擇對應的隨身碟即可,這部分就不多述。

製作安裝 Live USB (Linux 使用者)

Linux 使用者 可以直接用 dd 指令

先列出有什麼 usb 裝置

# lsblk

然後使用 dd 指令直接複製

# dd bs=1M conv=fdatasync if=./proxmox-ve_*.iso of=/dev/XYZ

注意!這指令按下去會直接執行,請確定複製的目標路徑是否正確,不然弄錯會悲劇。

開啟虛擬化技術 (Intel VT-x / AMD-V)

另外一點,如果你要把 Proxmox VE 裝在實體機上的話,它需要在 BIOS 開啟虛擬化技術,
沒有開啟的話,它什麼事都做不了。
虛擬化技術在 Intel 跟 AMD 陣營有不同的名字:

  • Intel (Intel Virtualization Technology, Intel VT-x)
  • AMD (AMD Secure Virtual Machine, AMD SVM)

字詞雖不同,不過講的都是同一個功能。

這部分要在 BIOS 的 Chipset 裡面尋找對應的字詞(可能是 Virtualization Technology 之類的字詞)
然後開啟。這設定值有些預設為開啟,有些預設為關閉,如果安裝前有提示你的話,就要記得去開啟。
這部分每家廠牌主機板設定都不太一樣,沒有一體適用的教學。

安裝 Proxmox VE

建議是裝在實體機上,如果你要練習,裝在虛擬機上練習也是可以。

從 USB 隨身碟或光碟開機,然後使用 Next 大法,一路按 Next 安裝,應該不會遇到什麼問題。
注意硬碟會 重新格式化,不會保留資料。其中會設定 root 密碼與抓取當下的網路 IP,這部分要先記錄下來,安裝完成後重開機後會提示。安裝流程基本上不會遇到什麼問題。

比較有問題的應該是需在 BIOS 選擇正確的開機裝置,選擇到 USB 隨身碟。

移除網頁介面 No Valid Subscription 的提示 (選擇性)

這部分選擇性,做不做都可以。
每次登入網頁介面時都會出現 No Valid Subscription (無有效訂閱) 的提示,

You do not have a valid subscription for this server. Please visit www.proxmox.com to get a list of available options.

意思就是,你沒有一個有效訂閱,沒有訂閱其實也可以正常使用沒有關係,
但常期使用看到這個視窗會有一點點煩,以下就是教你怎麼移除它。

(以下方式存在版本上的差異,我使用的是 PVE 7.1,如果出了更新的版本,請參考套用或者不套用。)
(如果對程式語言不熟悉也建議跳過這段)

修改前請先備份程式碼。

# cp /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.bak

找到對應的檔案做修改:

# vi /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js

? 來搜尋 checked_command 關鍵字,nShift + N 來查找位置。

找到這個段落(修改前的版本):

checked_command: function(orig_cmd) {
    Proxmox.Utils.API2Request(
        {
            url: '/nodes/localhost/subscription',
            method: 'GET',
            failure: function(response, opts) {
                Ext.Msg.alert(gettext('Error'), response.htmlStatus);
            },
            success: function(response, opts) {
                let res = response.result;
                if (res === null || res === undefined || !res || res
                    .data.status.toLowerCase() !== 'active') {
                    Ext.Msg.show({
                        title: gettext('No valid subscription'),
                        icon: Ext.Msg.WARNING,
                        message: Proxmox.Utils.getNoSubKeyHtml(res.data.url),
                        buttons: Ext.Msg.OK,
                        callback: function(btn) {
                            if (btn !== 'ok') {
                                return;
                            }
                            orig_cmd();
                        },
                    });
                } else {
                    orig_cmd();
                }
            },
        },
    );
},

這個 if 條件式 ,裡面有緊接一個含有 No valid subscriptionExt.Msg.show() 函式。

Ext.Msg.show({
    title: gettext('No valid subscription'),
    icon: Ext.Msg.WARNING,
    message: Proxmox.Utils.getNoSubKeyHtml(res.data.url),
    buttons: Ext.Msg.OK,
    callback: function(btn) {
        if (btn !== 'ok') {
            return;
        }
        orig_cmd();
    },
});

我們直接槓掉整段打 API 驗證有沒有授權的程式,讓它跑原本的 orig_cmd(); 就好。

修改後的版本如下:

checked_command: function(orig_cmd) {
    orig_cmd();
},

然後重啟 Proxmox VE 網頁服務

# systemctl restart pveproxy

網頁介面會斷線 3-5 分鐘不等,稍等之後再重新連線。

安裝相關套件

以下是小弟我個人常用的一些套件,一行指令來安裝,你也可以紀錄你自己喜愛的套件,修改這行指令
(以下都預設用 root 帳號登入執行)

# apt install -y net-tools vim curl sudo

net-tools 裡面有 ifconfig 指令,習慣 ifconfig 指令可以選擇性安裝它。

安裝 sudo 套件與使用者設定

我使用的版本是 PVE 7.1,
目前 PVE 是沒有安裝 sudo 套件的。

如果有需要,一樣可以安裝。

# apt install -y sudo

這樣就跟一般 Linux 幾乎無異了。

編輯 sudo 設定檔

在編輯設定檔之前,小弟我會先修改 visudo 預設會使用的編輯器。
(因為小弟我實在不習慣預設的 nano 編輯器)

# sudo update-alternatives --config editor

會出現以下選單可以選擇:

There are 3 choices for the alternative editor (providing /usr/bin/editor).

  Selection    Path                Priority   Status
------------------------------------------------------------
* 0            /bin/nano            40        auto mode
  1            /bin/nano            40        manual mode
  2            /usr/bin/vim.basic   30        manual mode
  3            /usr/bin/vim.tiny    15        manual mode

Press <enter> to keep the current choice[*], or type selection number: 2
update-alternatives: using /usr/bin/vim.basic to provide /usr/bin/editor (editor) in manual mode

個人喜好用 vi 所以選擇 vim.basic
如果沒有 vim.basic 選項,請安裝 vim

# apt install -y vim

編輯 sudo 設定檔,套用免密碼設定

編輯 sudo 設定檔,修改 sudo 時不需再次確認密碼
使用 visudo 修改指令

# visudo

在這行

%sudo   ALL=(ALL:ALL) ALL

把它改成

%sudo   ALL=(ALL:ALL) NOPASSWD: ALL

新增使用者&加入使用者

簡單介紹一下 sudo 的用法

可以用指令新增使用者(例如:新增名字叫做 newuser

# adduser newuser

(註:請先不要使用 useradd 指令,因為它不會幫忙建立家目錄)

加入該使用者進 sudo 的使用者群組(允許使用 sudo)

# usermod -aG sudo newuser

如果要修改這位使用者的密碼,可以用 passwd 指令

# passwd newuser

(註:後面 必須 要接正確的使用者的名稱,如果沒有該參數會修改到 目前使用者 的密碼,
假設你登入 root 帳號就像修改到 root 的密碼,需注意。)

安裝桌面環境 (選擇性)

這步驟不一定需要做。因為通常我們使用 PVE 都是從網頁管理介面直接來,
實體機只會有一個醜醜的黑白 Console 命令列。
如果你需要桌面環境,需要 GUI 的話,可以用以下步驟安裝桌面環境。

安裝 X windows 與桌面

先更新系統套件

# apt update -y && apt dist-upgrade -y

然後安裝指令安裝 xfce4 桌面環境, chromium 瀏覽器(開源版的 Google Chrome 瀏覽器)

# apt install -y xfce4 chromium lightdm

新增使用者

在啟動桌面之前,先新增一個普通 linux 使用者,避免使用 root 帳號來登入。
可以用指令新增使用者(例如:新增名字叫做 newuser

# adduser newuser

(註:請先不要使用 useradd 指令,因為它不會幫忙建立家目錄)

啟動 X windows

最後用指令啟動登入管理器:

# systemctl start lightdm

就有一個基本的桌面環境了。

改登入畫面桌布 (選擇性)

方式有二種,你可以安裝 lightdm-gtk-greeter-settings 圖形介面,

# apt install -y lightdm-gtk-greeter-settings

或者用以下方法改設定:

把桌布圖檔放到 /usr/share/pixmaps/ 底下(假設檔名為 wallpaper.jpg

然後修改 lightdm-gtk-greeter.conf 設定檔

# vi /etc/lightdm/lightdm-gtk-greeter.conf

[greeter] 這個段落加上一行

background=/usr/share/pixmaps/wallpaper.jpg

成果如下:

[greeter]
background=/usr/share/pixmaps/wallpaper.jpg

:wq 存檔離開。

最後,重啟 lightdm 服務

# systemctl restart lightdm

就會看到新的桌布了~

基本 Proxmox VE 使用方式

以下介紹 Proxmox VE 基本使用方式,建立虛擬機 (Virtual machine, VM) 跟 Container 容器。

建立虛擬機 (Virtual machine, VM)

網頁介面進去,右上角就有二個大大的按鈕,
一個是 Create VM ,一個是 Create CT

首先,先去下載你要的系統光碟檔 (ISO) 用網頁上傳上去。

然後我們按下 Create VM,選擇內核,選擇 ISO 檔案,
設定 CPU、記憶體、硬碟容量大小,最後按 Finish 完成。
中間應該不會有什麼問題。

建立好的 VM 之後,選擇按下 Start 就開機了,
之後就是作業系統的安裝與操作,就這麼簡單。

建立 Container 容器

先說,這個 Container 不是 docker 裡講的那個 Container,而是 LXC 的 Container,
概念雖接近,但不是相同的東西。

簡單來說,虛擬機 (Virtual machine, VM) 會是模擬一個「完整的」電腦系統,
虛擬 BIOS、虛擬處理器、記憶體、硬碟、…等等。

Container 是一個從作業系統層,利用一些容器隔離技術,直接跑起來的 Process (處理程序)。
為了減少中間虛擬化造成的效能損耗。
如果有玩過,你會發現 Container 它會比 VM 較快。
但也有一些限制,就不是什麼 Image 都有,部分特殊情況會有相容性問題。

如果你有遇到問題,可以做一份相同版本的 VM 跟 Container,二者交叉比較看看。
CT Template 的區域有一些現成的 Templete 可以下載使用,常見的 Linux 版本都有。

像我個人使用 Ubuntu ,使用 Ubuntu 20.04 版本。
像是學 Redhat 系列的可以選 CentOS、 Rocky Linux 或 Fedora Core、
想要系統比較小可以選 Alpine…純憑個人喜好。

跟 VM 的建立方式接近,但你會發現預設資源設定就少很多,而且比較順暢,可以推薦玩玩看。

幾個重要的檔案路徑

列出幾個重要的檔案路徑,到時候要複製檔案的時候才知道要去哪裡找。

ISO 檔案的路徑

ISO 檔案的路徑在:
/var/lib/vz/template/iso/

這個是網頁介面上傳 ISO 檔案會存放的路徑。

虛擬機備份路徑

你的虛擬機 (VM) 備份、容器 (Container) 備份,
檔案位置都在 /var/lib/vz/dump/ 底下。

設定檔位置

開啟若干個虛擬機 (VM) 或 容器 (Container) 後,設定檔在
/etc/pve/lxc/ 底下。

例如:
我要修改 VMID = 100 的虛擬機,設定檔就是:
/etc/pve/lxc/100.conf

以此類推。

以上粗淺的分享,還有很多地方沒有提到,之後文章會慢慢補充。

參考資料