在狗云家抢了一台重庆联通的独服,每个月 200 块钱,10C 64G 800G,带宽入 50Mbps 出 30Mbps。性价比算是很不错了。有需要的可以去看看,不过不知道现在还有没有刚上线的时候那么大优惠了:https://www.dogyun.com/?ref=doraemon (有 aff,介意可自行去除后缀)。
机器下来之后,这么宽敞的“别墅”肯定不能像之前买 VPS 1C2G 那样扣扣索索的装 LNMP 之类的去跑网站。PVE + K8S 让我们搞起来,K8S 当然是要搞个多节点集群来玩完了。好了,下面开始正经的教程系列。
准备工作
操作前提是使用 PVE 7 系统(狗云默认提供),并通过下列方式先更新到最新版本:
设置 debian 中科大源:
cat > /etc/apt/sources.list <<EOF
deb http://mirrors.ustc.edu.cn/debian/ bullseye main non-free contrib
deb http://mirrors.ustc.edu.cn/debian/ bullseye-updates main non-free contrib
deb http://mirrors.ustc.edu.cn/debian/ bullseye-backports main non-free contrib
deb-src http://mirrors.ustc.edu.cn/debian/ bullseye main non-free contrib
deb-src http://mirrors.ustc.edu.cn/debian/ bullseye-updates main non-free contrib
deb-src http://mirrors.ustc.edu.cn/debian/ bullseye-backports main non-free contrib
deb http://mirrors.ustc.edu.cn/debian-security/ bullseye-security main non-free contrib
deb-src http://mirrors.ustc.edu.cn/debian-security/ bullseye-security main non-free contrib
EOF
删除企业源:
rm -rf /etc/apt/sources.list.d/pve-install-repo.list
echo "#deb https://enterprise.proxmox.com/debian/pve Bullseye pve-enterprise" > /etc/apt/sources.list.d/pve-enterprise.list
下载秘钥:
wget http://mirrors.ustc.edu.cn/proxmox/debian/proxmox-release-bullseye.gpg -O /etc/apt/trusted.gpg.d/proxmox-release-bullseye.gpg
添加国内源:
echo "deb http://mirrors.ustc.edu.cn/proxmox/debian/pve bullseye pve-no-subscription" >/etc/apt/sources.list.d/pve-install-repo.list
修改自带的CEPH源:
echo "deb https://mirrors.ustc.edu.cn/proxmox/debian/ceph-pacific bullseye main" > /etc/apt/sources.list.d/ceph.list
更新:
apt update -y && apt dist-upgrade -y
安装新内核:
apt install -y pve-kernel-6.2
调整启动顺序:
grep menuentry /boot/grub/grub.cfg | grep 6.2 | grep -v recovery
# 从上面找出来的记录里面,复制对应信息到 /etc/default/grub 中
emacs /etc/default/grub
# 把默认的 GRUB_DEFAULT="0" 改为类似下面的内容,具体是什么以你实际环境为准
GRUB_DEFAULT="Advanced options for Proxmox VE GNU/Linux>Proxmox VE GNU/Linux, with Linux 6.2.16-4-bpo11-pve"
更新引导并重启:
update-grub
reboot
重启后删除不必要的包:
apt --purge autoremove
NAT 网络配置
登录 Proxmox VE 后台,选择系统 -> 网络,然后点击创建 -> Linux Bridge。
填写 IP 和子网掩码,IP 地址填写个局域网的网段地址就行。其他项目不用填也不用改,保持默认。
IP 地址填写一个局域网地址:192.168.0.1/24 (没那么多,250+ 网络地址够用了)
上面的配置创建了一个新的 vmbr1 网桥分配了一个子网 192.168.0.1/24,宿主机(网关)在子网的 IP 是 192.168.0.1。
然后点击上面的“应用配置”:
创建完成之后,编辑 /etc/network/interfaces
,然后在对应的 vmbr1 后面修改成相应的配置(注意不要直接替换为下面的文件,这个文件上面还有一堆 eth0 / vmbr0 的配置,不要删除):
auto vmbr1
iface vmbr1 inet static
address 192.168.0.1/24
gateway x.x.x.254 # 独立服务器 IP 的前面三段 + 最后一段是 254
bridge-ports none
bridge-stp off
bridge-fd 0
post-up echo 1 > /proc/sys/net/ipv4/ip_forward
post-up iptables -t nat -A POSTROUTING -s '192.168.0.0/24' -o vmbr0 -j MASQUERADE
post-down iptables -t nat -D POSTROUTING -s '192.168.0.0/24' -o vmbr0 -j MASQUERADE
启用网桥:
ifup vmbr1
显示信息:
ip address show dev vmbr1
可以看到类似于下面的输出:
查看 iptables 配置是否生效:
iptables -L -t nat
可以看到类似于下面的输出:
重启网络:
systemctl restart networking
确认没有问题之后重启系统:
reboot
DHCP 配置
上面配置好的 NAT 网络是没有 DHCP 的,需要手工设定 IP,不是很方便,这里自己部署一个本地的 DHCP Server 来解决这个问题。
apt install isc-dhcp-server -y
安装过程会直接启动失败,没有关系。现在来修改 /etc/default/isc-dhcp-server
这个文件内容,将 INTERFACESv4
调整为 vmbr1
也就是我们要通过 DHCP Server 发 IP 的那个 Linux Bridge,如下:
接下来继续编辑 /etc/dhcp/dhcpd.conf
文件,调整成下面的这个样子:
option domain-name "doraemonext.com";
option domain-name-servers 223.5.5.5, 223.6.6.6;
default-lease-time 600;
max-lease-time 7200;
ddns-update-style none;
authoritative;
log-facility local7;
subnet 192.168.0.0 netmask 255.255.255.0 {
range 192.168.0.100 192.168.0.149;
option subnet-mask 255.255.255.0;
option domain-name-servers 223.5.5.5, 223.6.6.6;
option domain-name "doraemonext.com";
option routers 192.168.0.1;
option netbios-name-servers 192.168.0.1;
option netbios-node-type 8;
get-lease-hostnames true;
use-host-decl-names true;
default-lease-time 600;
max-lease-time 7200;
interface vmbr0;
}
其中需要换成你自己的就是 option domain-name / option domain-name-servers / range 之类的信息。range 表示可以下发的 IP 范围是什么。
保存后,重启 DHCP Server:
systemctl restart isc-dhcp-server
可以看到当前已经没有报错了。
其他准备
此时可以先安装个 Windows 虚拟机(vmbr1),上面已经把 NAT 内网和 DHCP 都搞好了,安装不存在任何问题,然后把科学上网的环境搞一下,然后借用这个 Windows 上的本地 SOCKS 代理,把宿主机上的 v2raya 透明代理搞好,这里不说明了。后续步骤默认没有任何网络访问问题。
关于 3389 端口的映射,可以在 /etc/network/interfaces
中设置对 NAT 网络的转发:
post-up iptables -t nat -A PREROUTING -i vmbr0 -p tcp --dport 3389 -j DNAT --to 192.168.0.100:3389
post-down iptables -t nat -D PREROUTING -i vmbr0 -p tcp --dport 3389 -j DNAT --to 192.168.0.100:3389
配置 K8S 虚拟机模板
在 PVE 中创建虚拟机,如下:
在磁盘页面把默认的磁盘删掉,因为后面会添加新的:
注意网络选择 vmbr1,关闭防火墙:
创建完毕后,不要启动。然后增加一个 cloud-init 设备:
然后通过 PVE 的镜像下载功能,下载 http://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img
镜像。最终下载到的目录应该是:/var/lib/vz/template/iso/ubuntu-22.04-server-cloudimg-amd64.img
。
下载好后,进入物理机 Shell,将镜像导入硬盘:
qm importdisk 150 /var/lib/vz/template/iso/ubuntu-22.04-server-cloudimg-amd64.img local-lvm --format=qcow2
上面的 150 是刚才新建的虚拟机的 ID。接下来返回 PVE 页面的虚拟机管理界面,双击刚才导入的“未使用的磁盘0”,然后选择 SCSI 之后添加:
接下来在选项中设置系统启动顺序,将刚才添加的 SCSI:1 调整到第一位:
进入 cloud-init 界面配置用户名和登录方式,推荐使用宿主机 ssh-key 的方式。因为前面已经配置好了 DHCP,所以可以直接使用 DHCP。
设置完上面的事情后可以启动虚拟机了,确认可以正常登录后即可关机。然后在硬件里面调整磁盘大小,注意只能新增:
我这里增加了 80GB 存储。保存退出。然后进入选项中,将 QEMU Guest Agent 打开:
之后再次启动虚拟机,执行下列命令:
# 安装 QEMU Guest Agent
sudo apt-get install qemu-guest-agent
sudo systemctl start qemu-guest-agent
# 配置虚拟机
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# 设置所需的 sysctl 参数,参数在重新启动后保持不变
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
# 应用 sysctl 参数而不重新启动
sudo sysctl --system
# 通过运行以下指令确认 br_netfilter 和 overlay 模块被加载
lsmod | grep br_netfilter
lsmod | grep overlay
# 通过运行以下指令确认 net.bridge.bridge-nf-call-iptables、net.bridge.bridge-nf-call-ip6tables 和 net.ipv4.ip_forward 系统变量在你的 sysctl 配置中被设置为 1
sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward
继续配置容器运行时:
# 安装 containerd
sudo apt-get update && sudo apt-get install -y containerd
# 配置 containerd
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml
# 修改为 SystemdCgroup
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
cat /etc/containerd/config.toml | grep SystemdCgroup
# 配置 containerd 服务
sudo systemctl enable containerd
sudo systemctl restart containerd
sudo systemctl status containerd
安装 kubelet/kubeadm/kubectl:
sudo apt-get update && sudo apt-get install -y apt-transport-https ca-certificates curl
curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-archive-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
做完上述事情后,这个虚拟机就可以关机了,然后开始制作虚拟机模板。
之后用这个模板克隆四个 VM 出来(一个 control-plane 控制平面,三个 worker),如下,注意是完整克隆:
最终效果如下:
然后对每个 VM 设置一下静态 IP 信息(在 cloud-init 中):
启动所有克隆好后的 VM,登录进去之后,将所有机器的 /etc/machine-id
调整为不同的内容并强制保存。
接下来的内容在所有的节点上执行,拉取镜像:
sudo kubeadm config images pull
接下来的内容仅在所有 control-plane 角色的 VM 中执行下列命令:
sudo kubeadm init --control-plane-endpoint=192.168.0.150 --node-name control-plane --pod-network-cidr=10.244.0.0/16
mkdir -p HOME/.kube
sudo cp -i /etc/kubernetes/admin.confHOME/.kube/config
sudo chown (id -u):(id -g) $HOME/.kube/config
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
上面两个地址,一个是 control-plane 自身的 IP,一个是 Pod 的 CIDR 地址,因为后面打算用 flannel 插件,就保持默认的 10.244.0.0/16 就行。
这个时候,如果你在 control-plane 上去看 Pod,可以看到如下的内容:
接下来加入其他的所有 worker 节点,在那些 worker VM 中执行下列的命令来加入集群(该命令已在上述 control-plane 安装的输出结果的最后):
kubeadm join 192.168.0.150:6443 --token xxxxxxx.e3yxxxxxwt8gixx8 --discovery-token-ca-cert-hash sha256:xxxxxx8
如果将来想再看上述的命令,可以执行:
sudo kubeadm token create --print-join-command
至此,一个可用的 K8S 集群部署完毕。
MetalLB 安装
现在 K8S Node 是在 NAT 内网中的,K8S Pod 在另一个 flannel 的 NAT 内网中。现在如果想暴露服务的话,虽然可以用 nodeport,但是不太好用。所以可以使用 MetalLB 来启用 LoadBalancer,这样就可以生成我们设置的 192.168.0.1/24 内网中的 IP 了。
先编辑下 kube-proxy 的转发模式,调整到 strictARP mode:
kubectl edit configmap -n kube-system kube-proxy
将其中的 mode 和 ipvs.strictARP 设置成下面的内容:
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"
ipvs:
strictARP: true
然后保存退出。之后安装 MetalLB:
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.10/config/manifests/metallb-native.yaml
部署完毕后,可以看到下面的几个 Pod:
接下来开始分配 IP 范围,将下列文件写入到本地的 metallb.yaml
中:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: static-ip-address-pool
namespace: metallb-system
spec:
addresses:
- 192.168.0.200-192.168.0.230
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: advertisement
namespace: metallb-system
上面的 addresses 填写你自己准备分配给它的 NAT IP 地址范围。现在,如果有新的 Service 使用 LoadBalancer,那么就会直接分配一个 NAT 内的 192.168.0.xxx 的 IP 了。
安装 Istio
在 ~/.kube/config 存在的情况下,执行下列命令(1.18.1 是当前的最新版本):
curl -L https://istio.io/downloadIstio | sh -
cd istio-1.18.1
export PATH=PWD/bin:PATH
istioctl install --set profile=default -y
然后设置所有 default 下的 Pod 都接受 Istio 的托管:
kubectl label namespace default istio-injection=enabled
当部署完成后,你应该会看到多了这两个 Pod:
同时,通过 kubectl get svc -n istio-system
,你也会看到一个生成了 LoadBalancer 的 istio-ingressgateway:
这里生成的 192.168.0.201 (按你自己实际环境中的为准) 就是我们将来所有服务的流量入口。
测试服务部署
写入 hello.yaml 文件:
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-app
spec:
selector:
matchLabels:
app: test-app
template:
metadata:
labels:
app: test-app
spec:
containers:
- name: test-app
image: nginxdemos/hello
resources:
limits:
memory: "128Mi"
cpu: "500m"
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: testpage
spec:
selector:
app: test-app
ports:
- protocol: TCP
port: 80
targetPort: 80
然后 kubectl apply -f hello.yaml
,等待服务拉起。
然后创建一个 Istio Gateway(后面不再赘述执行 kubectl 的命令):
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: hello-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "doraemonext.net"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: hello-virtual-service
namespace: default
spec:
hosts:
- "doraemonext.net"
gateways:
- istio-system/hello-gateway
http:
- match:
- uri:
exact: /
route:
- destination:
host: testpage
port:
number: 80
然后从 Ingress 的入口直接去 curl,可以看到已经返回 Hello 200 了(这个 192.168.0.201 就是前面 kubectl get svc -n istio-system 中看到的 ingress 的 LoadBalancer 地址):
宿主机安装 Nginx
为了能够打通最后一公里,也就是让请求宿主机公网 IP 的请求打到 Istio Ingress 网关侧,需要在宿主机上开个 Nginx 直接针对所有 80/443 的请求直接以 stream 的形式转发到 Istio Ingress LoadBalancer 上(我们把 nginx 当四层转发来用)。
操作流程如下:
apt install nginx -y
emacs /etc/nginx/nginx.conf
将其中的 http 整段删除,然后粘贴下面的 stream 配置:
stream {
server {
listen 80;
proxy_pass 192.168.0.201:80;
}
server {
listen 443;
proxy_pass 192.168.0.201:443;
}
}
然后重启 nginx 并设置开机自启动:
systemctl restart nginx
systemctl enable nginx
此时,我们通过浏览器去打开自己绑定的域名(此处使用 doraemonext.net 来示例),已经可以看到 Hello World 了。
CertManager 安装及证书自动申请
刚才配置的都是 80 的,那么现在网站对外提供服务肯定都是 https 的,K8S 上我们可以通过 CertManager 来管理和自动申请证书。此处以 LetEncrypt 为例。
安装 CertManager:
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.yaml
等待部署完成后,会发现多了 3 个 Pod:
然后下发集群证书的 ClusterIssuer 和 Certificate:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt
namespace: istio-system
spec:
acme:
email: doraemonext@gmail.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt
solvers:
- http01:
ingress:
class: istio
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: doraemonext-net-cert-prod
namespace: istio-system
spec:
secretName: doraemonext-net-cert-prod
duration: 2160h # 90d
renewBefore: 360h # 15d
isCA: false
privateKey:
algorithm: RSA
encoding: PKCS1
size: 2048
usages:
- server auth
- client auth
dnsNames:
- "doraemonext.net"
issuerRef:
name: letsencrypt
kind: ClusterIssuer
group: cert-manager.io
上面的信息按需改动,注意 issuerRef 引用的是上面的 ClusterIssuer,两个名字要改一起改。
当下发完成后,就可以看下证书申请情况了:
kubectl describe certificate doraemonext-net-cert-prod -n istio-system
如果 Events 里面出现了 Created new CertificateRequest resource “xxxxx”,那么就继续向下追:
kubectl describe certificaterequest xxxxx -n istio-system
这里就会等待证书签发了,稍微等会儿,如果你能看到类似于 Normal CertificateIssued 21s cert-manager-certificaterequests-issuer-acme Certificate fetched from issuer successfully 的信息,就说明已经证书签发成功了。
那么我们接下来就是要让 Istio Ingress Gateway 加载这个证书。
将刚才我们下发的 Istio Gateway 稍作调整:
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: hello-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "doraemonext.net"
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: doraemonext-net-cert-prod
hosts:
- "doraemonext.net"
也就是加了 443 这块,注意 credentialName 和刚才的证书名称保持相同。然后重新 kubectl apply -f 它。
这次我们再次用 https 打开浏览器:
会发现证书已经生效了。
接下来我们需要将 80 直接强制跳转到 443,毕竟都有证书了,怎么还能继续用 80。
重新修改一下刚才的 Istio VirtualService 文件:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: hello-virtual-service
namespace: default
spec:
hosts:
- "doraemonext.net"
gateways:
- istio-system/hello-gateway
http:
- match:
- port: 80
redirect:
authority: "doraemonext.net:443"
scheme: https
- match:
- port: 443
route:
- destination:
host: testpage
port:
number: 80
现在,我们就算使用 http://doraemonext.net 去访问,也会被强制跳转到 https://doraemonext.net 了。
Prometheus/Kiali 安装及可视化 Mesh 网络
都跑 Istio 了,不装个 Kiali 可视化就说不过去了:
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.18/samples/addons/prometheus.yaml
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.18/samples/addons/kiali.yaml
回到刚才我们的 istio-1.18.1 目录下,装一下官方的 demo,体验一下:
kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml
kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml
官方的 Demo 默认给的都是 80 端口的,那我们肯定要和证书绑定一下。这里我提前设置了 bookinfo.doraemonext.net/kiali.doraemonext.net 的 A 记录指向了宿主机 IP。
先申请 bookinfo.doraemonext.net 的证书。通过 kubectl edit cert doraemonext-net-cert-prod -n istio-system
打开刚才我们的证书 YAML,然后将其中的 dnsNames 增加一个 bookinfo.doraemonext.net/kiali.doraemonext.net:
然后再像刚才一样,通过 kubectl describe cert doraemonext-net-cert-prod -n istio-system
来观察 Events,稍等一会儿,看到下面的提示就是成功了:
接下来我们小小的改动一下官方示例中的网关文件: emacs samples/bookinfo/networking/bookinfo-gateway.yaml
,主要是把 443 加上去:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: doraemonext-net-cert-prod
hosts:
- "bookinfo.doraemonext.net"
把上面的文件加到 bookinfo-gateway 下面,80 的后面一段即可,保存后重新 kubectl apply -f 即可生效,然后重新访问 https://bookinfo.doraemonext.net/productpage
接下来给 Kiali 加一下 Gateway 和 VirtualService 让它暴露到 kiali.doraemonext.net 上:
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: kiali-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 443
name: https-kiali
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: doraemonext-net-cert-prod
hosts:
- "kiali.doraemonext.net"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: kiali-vs
namespace: istio-system
spec:
hosts:
- "kiali.doraemonext.net"
gateways:
- kiali-gateway
http:
- route:
- destination:
host: kiali
port:
number: 20001
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: kiali
namespace: istio-system
spec:
host: kiali
trafficPolicy:
tls:
mode: DISABLE
然后访问下 https://kiali.doraemonext.net ,整个服务的网络拓扑图就出来了:
折腾了这么多,才跑了 48% 的内存:
给狗老板打 call 😋