如遇图片无法加载请使用代理访问

前言

早在 2022 年就有打算部署一套云原生组件,奈何手头繁忙,最终还是先按照单体应用,清单如下:

  • docker-compose 的方式运行单体应用
  • Jenkins 负责 CI/CD
  • GitLab 负责代码存储
  • Nginx 负责 LB

下方为早期单体应用构想图:

早期单体应用构想图

家里有一台旧笔记本,上面的组件都部署在了那台笔记本上,系统是 Ubuntu,开了两台虚拟机(如果你没搭建过 Ubuntu Server ,你可以看下这篇文章:Ubuntu Server 20.04 安装教程)。不难发现,单体应用的部署也可以用上自动化部署 CICD,去年一整年,我私人的项目都是用这样的方式部署,它有如下优点:

  • 简单,易操作,不用再用 nohop jar 的方式启动一个 java 程序
  • 它支持在 Windows 操作系统上部署
  • 它适合小型项目,流量小,短暂断流没有影响的网站
  • 它适合小型公司部署项目,简化项目部署流程,简化测试环境

它也有如下缺点:

  • 虽然能多副本,但没有滚动更新机制
  • 没有自动伸缩,只能通过修改 docker-compose 配置文件,手动伸缩副本数
  • 中间件也需要手动用 docker-compose 部署,如果遇到单点故障,需要手动去服务器上重启
  • 无法侦测流量的传输,PrometheusGrafana 等其他组件也需要手动部署
  • 没有自愈能力,没有异地容灾,仅靠 docker-compose 的重启策略很难做到高可用
  • 没有好的商店环境等等…

单体应用简单,但有时经不起考验。很多服务也没有使用 DDD 思想编写,也没有微服务的概念,大多都是一把梭,全部柔成一个服务,无法水平扩展服务也无法水平扩展节点 。有时候问题源于需求,很多公司没有这种需求,也自然不会遇到集群部署的问题。因此,2023 年的目标就是部署一套微服务架构,将单体应用转型。


环境声明

hostname 系统 配置 节点 角色 部署
m1 Ubuntu-Server(20.04) 2c4g 192.168.0.67/32 control-plane,etcd,master k3s(v1.24.6+k3n1) server
nginx
rancher(2.7.1)
Helm(3.10.3)
n1 Ubuntu-Server(20.04) 1c2g 192.168.0.102/32 control-plane,etcd,master k3s(v1.24.6+k3n1) server
m2 Ubuntu-Server(20.04) 2c4g 172.25.4.244/32 control-plane,etcd,master k3s(v1.24.6+k3n1) server
harbor Ubuntu-Server(20.04) 2c4g 192.168.0.88 Docker-Hub
Jenkins CI/CD
Harbor(2.7.1)
Jenkins(2.3)
Docker-Compose

节点均用 WireGuard 打通内网,后续所有节点路由均用内网ip访问,具体详细的节点内容请访问上一篇文章

n1 节点因为内存小,当初设想的是以 Worker 节点部署,但由于 etcd 的特性,如果只有两台节点部署 etcd ,它的容错仍为 0,所以这个节点虽然叫 n1,但它其实也是 Master 节点 ,为了不影响后续的主机名解析,暂时不会更换这个节点的 hostname。这里的 LB 我共用了 m1 节点,如果你的机器够多,你最好将它单独移出,它不需要多大的内存和 cpu ,仅作为内网流量的转发。

我没有开启那些 k3s 常用的端口,因为所有的端口都通过 51820 建立 TCP连接 ,以 WireGuard 加密的方式,通过 UDP协议传输。这不是一个去中心化的网络,因为 m1 和 n1 是内网互通的,因此少了一条 VPN 网络。由于 WireGuard 可以映射内网地址,因此即便 m2 的内网 ip 不在 192.168.0.0/24 网段上,也无可厚非,因为我们是通过 wg0 这个网卡传输,所以只需要开启 ipv4 转发即可。后续所有的地址也都会按照内网地址配置,就好像这几个节点都在“一个网段”一样。

本架构均用 WireGuard 加密传输流量,如果你还不了解 WireGuard,你可以参考下这篇文章:有了这款图形管理界面,一分钟内配置 10 个 WireGuard 客户端不是梦 (qq.com)。如果你不想手动创建,想通过 Flannel 配置,请注意开启云服务器相应的端口。如果你的三台节点内网互通,可忽略这个问题(比如你创建了三个虚拟机且在同一网段,或者你在同一个云上不同机房,通过云服务商的能力,帮你打通内网)

简易架构图

准备工作

除非特殊声明,否则本文所有的命令均需要用到 root 用户

密钥登录

修改密钥登录是一个危险的步骤,请确保当前 ssh 连接不要断开,否者配置出错会导致你无法登录到服务器。本操作并非必须,只是由于云服务器暴露外网,经常遭受机器人暴力破解密码,如果你的 k3s 仅在内网访问,可不执行此步骤

所有节点均执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 创建密钥
# 由于新版本创建的密钥均为 OPENSSH 类型
# 如果你的其他中间件不适用于此类型,请指定生成密钥的类型为 rsa
# 本文生成的类型是 OPENSSH,下方代码指定了 rsa,但并未生效
ssh-keygen -t rsa -f /root/.ssh/id_rsa -N ''
cd /root/.ssh/
cat id_rsa.pub >> authorized_keys
sudo chmod 600 authorized_keys
sudo chmod 700 ~/.ssh
nano /etc/ssh/sshd_config

# 添加或修改如下内容
RSAAuthentication yes
PubkeyAuthentication yes
PermitRootLogin yes
PasswordAuthentication no

# 重启 sshd 服务
service sshd restart

最后在你所用的连接软件中,选择密钥登录即可,下面是 MobaXterm 的示例图:

MobaXterm 密钥登录示例图

时间同步

所有节点均执行

1
2
3
4
5
6
7
8
9
# 如果无法安装 ntpdate,可以先执行一下 sudo apt-get update -y 
# 对于 Ubuntu Server 而言,可能需要 sudo dpkg-reconfigure tzdata
# 并选择 Asia/Shanghai
sudo apt-get install ntpdate -y
ntpdate cn.pool.ntp.org
hwclock -w

# 视情况重启
reboot

对于 etcd 而言,时间同步是一个很重要的步骤。节点延迟过长,网络阻塞,时间未同步,都会导致节点异常


修改主机名

所有节点均执行

1
hostnamectl set-hostname 各个节点的主机名

修改 hosts

所有节点均执行

1
2
3
4
5
6
7
nano /etc/hosts

# 按需添加如下内容
192.168.0.67 m1
192.168.0.102 n1
192.168.0.88 harbor
172.25.4.244 m2

关闭防火墙

所有节点均执行

1
2
systemctl disable ufw
systemctl stop ufw

安装 Docker

所有节点均执行

1
2
3
4
apt-get install docker -y
# 其他方法
# curl -sSL https://get.daocloud.io/docker | sh
# curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun

禁用交换空间

所有节点均执行

Leopold:为什么要禁用交换空间?

ChatGPT:Kubernetes 在部署容器时,建议执行 swapoff -a 命令,这是因为在使用容器时启用交换空间会导致性能问题。

交换空间是在物理内存不足时使用的一种内存管理技术,它将部分内存数据移动到硬盘中的虚拟内存中,以便腾出更多的物理内存空间。但是,交换空间的使用会导致 I/O 操作频繁,这会影响应用程序的性能。

在容器环境下,每个容器都有自己的内存限制,而启用交换空间会导致容器使用的内存不受控制。当容器的内存使用量超过限制时,操作系统会将容器内存中的数据移动到硬盘上,这可能会导致应用程序性能下降。

因此,为了确保容器环境下的性能和稳定性,建议在 Kubernetes 中执行 swapoff -a 命令,以禁用交换空间并避免出现性能问题。这样,操作系统就会强制执行容器的内存限制,并且不会将容器内存中的数据移动到硬盘中,从而提高应用程序的性能和稳定性。

1
2
swapoff -a
sed -i '/swap/s/^/#/' /etc/fstab

开启内核转发

所有节点均执行

Leopold:为什么要开启内核转发?

ChatGPT:Kubernetes需要执行net.ipv4.ip_forward=1net.bridge.bridge-nf-call-iptables=1的原因是为了实现容器间和宿主机之间网络通信和网络隔离。

net.ipv4.ip_forward=1开启了IP转发功能,使得在Kubernetes集群中的不同节点上运行的Pod可以互相通信,以及可以与集群外部的网络进行通信。当Pod要发送数据包到集群外部的网络时,需要它所在的节点将该数据包转发出去,开启IP转发功能可以使得节点可以转发数据包。

net.bridge.bridge-nf-call-iptables=1开启了iptables的桥接模式,使得在容器之间的流量可以通过iptables规则进行管理,实现了对容器网络流量的控制和管理。当容器之间发生网络通信时,数据包会经过宿主机的iptables进行NAT转换和过滤,保障了网络安全和隔离。

因此,开启net.ipv4.ip_forward=1net.bridge.bridge-nf-call-iptables=1可以实现Kubernetes集群的网络通信和网络隔离。

1
2
3
4
5
6
7
8
nano /etc/sysctl.conf

# 添加如下内容
net.ipv4.ip_forward=1
net.bridge.bridge-nf-call-iptables=1

# 立即生效
sysctl -p /etc/sysctl.conf

离线安装 K3s

我的很多问题都是参照中文文档和 Git issue 解决的,很多配置一定要注意对应版本,网上千篇一律的解决方法可能还不如直接去官网查来的更准确,如果你 go 学的不错,你甚至能定位到一些问题的根本原因。


下载安装脚本与二进制文件

所有需要装 k3s 的节点均执行

下载 k3s 二进制文件:Releases · k3s-io/k3s (github.com) k3s 和 k3s-airgap-images-amd64.tar,上传至 /root/zip/k3s/

1
2
3
4
5
6
7
8
9
10
11
12
# 将上面的两个文件上传到这个目录下
mkdir -p /root/zip/k3s

# 复制离线安装文件
mkdir -p /k3s/
wget -O /root/zip/k3s/k3s-install.sh https://rancher-mirror.rancher.cn/k3s/k3s-install.sh
chmod a+x /root/zip/k3s/k3s
chmod a+x /root/zip/k3s/k3s-install.sh
cp /root/zip/k3s/k3s-install.sh /k3s/
cp /root/zip/k3s/k3s /usr/local/bin
mkdir -p /var/lib/rancher/k3s/agent/images/
cp /root/zip/k3s/k3s-airgap-images-amd64.tar /var/lib/rancher/k3s/agent/images/

请严格确认 kubelet 使用版本和 rancher 是否支持,在 rancher release 上都有注明,亲测版本不对会装不上,或者导入不进去。版本指定是一个很重要的概念,对你排查问题和系统稳定有着决定性的作用,也为你后续升级省下不少事。由于网络问题,这些二进制包下载很慢,所以我们采用离线部署的方式,手动下载,并将二进制包放到对应位置即可。


配置安装参数

所有需要装 k3s Master 的节点均执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mkdir -p /etc/rancher/k3s/
nano /etc/rancher/k3s/config.yaml

# 添加如下内容
tls-san:
# m1 节点
- 192.168.0.67
# m2 节点
- 192.168.0.102
# m3 节点
- 172.25.4.244
# kubectl get svc -A | grep kubernetes
- 10.43.0.1
cluster-init: true

cluster-init 表示我们初始化 etcd,后续节点加入的时候会自动忽略这个参数。tls-san用于 etcd 证书可信任域名/IP配置


Master 节点

所有需要装 k3s Master 的节点均执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# m1
INSTALL_K3S_SKIP_DOWNLOAD=true INSTALL_K3S_EXEC='server --docker' K3S_DATASTORE_ENDPOINT="etcd" /k3s/k3s-install.sh

# m1 token
cat /var/lib/rancher/k3s/server/token

# m2
INSTALL_K3S_SKIP_DOWNLOAD=true K3S_TOKEN=m1的token INSTALL_K3S_EXEC='server --docker --server https://m1:6443 --node-ip m2的本地ip --node-external-ip wg0网卡ip' K3S_DATASTORE_ENDPOINT="etcd" /k3s/k3s-install.sh
# INSTALL_K3S_SKIP_DOWNLOAD=true K3S_TOKEN=m1的token INSTALL_K3S_EXEC='server --docker --server https://m1:6443 --node-ip 172.25.4.244 --node-external-ip 10.6.4.1' K3S_DATASTORE_ENDPOINT="etcd" /k3s/k3s-install.sh

# n1
# 这里部署的参数和m2不一样,是因为我的n1和m1是同一网段的,不需要指定WireGuard网卡的ip
# 请根据自身情况做对应配置更改,切勿完全照搬
INSTALL_K3S_SKIP_DOWNLOAD=true K3S_TOKEN=m1的token INSTALL_K3S_EXEC='server --docker --server https://m1:6443' K3S_DATASTORE_ENDPOINT="etcd" /k3s/k3s-install.sh

此处是报错最容易出现、且是二进制安装最复杂的问题。如果出现问题,请先执行 journalctl -xfu k3s

报错可能原因有:

  • 未禁用缓存 :swapoff -a

  • 多次安装但环境清除不干净:starting kubernetes: preparing server: bootstrap data already found and encrypted with different token

    使用 /usr/local/bin/k3s-uninstall.sh 先删除,再停止 docker相关容器

  • etcd 错误:如果你想部署三台含有 etcdMaster,其他节点加入 etcd 如果超时,请重启三台节点的 k3s 服务即可:systemctl restart k3s,这是由于 etcd 的特性,需要同时启动才可以。如果你发现启动后,docker 里并没有关于 etcd 的容器,不用担心,这是由于 k3s 的特性,你可以在 /var/lib/rancher/k3s/server/db/etcd 找到 etcd

  • 如果你使用了 WireGuard 加密,你需要给 k3s 指定 WireGuard 网卡的 ip,否则你无法异地加入 etcd,请参考 --node-external-ip

大部分错误都是因为没有好好看文档中,每个参数的具体作用,问ChatGPT经常胡编乱造,反而降低效率,Git Issue 也帮助了我不少忙

安装成功后,执行 kubectl get pods -A,STATUS 无 Error 表示安装成功:

1
2
3
4
5
6
7
8
9
root@m1:/k3s# kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-7b5bbc6644-7llcm 1/1 Running 0 9m45s
kube-system local-path-provisioner-687d6d7765-5rsrx 1/1 Running 0 9m45s
kube-system helm-install-traefik-crd-k9rmd 0/1 Completed 0 9m46s
kube-system metrics-server-667586758d-pkzm4 1/1 Running 0 9m45s
kube-system helm-install-traefik-thkf8 0/1 Completed 2 9m46s
kube-system svclb-traefik-ce1649e0-9dkmv 2/2 Running 0 8m51s
kube-system traefik-64b96ccbcd-nwtrm 1/1 Running 0 8m51s

如果 kubectl get pods -A 报错 connection refused ,但你的 k3s 已经安装完成且无报错已启动,那可能需要你手动执行下配置

1
2
3
4
5
mkdir ~/.kube
nano ~/.kube/config
server: https://m1:6443
echo export KUBECONFIG=/etc/rancher/k3s/k3s.yaml>>/etc/profile
source /etc/profile

本章节的安装参数可参考安装选项介绍 | Rancher文档,比如是否使用docker,定制化组件,多网卡指定等功能


Worker 节点

所有需要装 k3s Worker 的节点均执行,本架构暂无 Worker 节点,仅记录操作步骤

1
2
3
4
5
# 首先在 m1 节点查看 worker 注册的 token
cat /var/lib/rancher/k3s/server/node-token

# 在 worker 上执行
INSTALL_K3S_SKIP_DOWNLOAD=true K3S_TOKEN=填node-token INSTALL_K3S_EXEC='agent --docker --server https://m1:6443' K3S_DATASTORE_ENDPOINT='etcd' /k3s/k3s-install.sh

其他

删除k3s

Server: /usr/local/bin/k3s-uninstall.sh

Agent: /usr/local/bin/k3s-agent-uninstall.sh

Docker:停止相关镜像


升级 K3s

离线环境的升级可以通过以下步骤完成:

  1. K3s GitHub Release页面下载要升级到的 K3s 版本。将 tar 文件放在每个节点的/var/lib/rancher/k3s/agent/images/目录下。删除旧的 tar 文件。
  2. 复制并替换每个节点上/usr/local/bin中的旧 K3s 二进制文件。复制https://get.k3s.io 的安装脚本(因为它可能在上次发布后发生了变化)。再次运行脚本。
  3. 重启 K3s 服务。

安装 Helm

仅 m1 节点安装

官方文档:Helm | 快速入门指南

1
2
3
4
5
cd /root/zip
wget https://mirrors.huaweicloud.com/helm/v3.10.3/helm-v3.10.3-linux-amd64.tar.gz
tar -xvzf helm-v3.10.3-linux-amd64.tar.gz
mv linux-amd64/helm /usr/local/bin/
helm version

安装 Rancher

仅 m1 节点安装(这里仅为单节点 Rancher ,并非 HA,请自行斟酌参考)

自定义证书

在安装 rancher 之前,我们需要先生成自定义证书(10年有效期),如果你想使用 rancher 协助安装证书,请参考官方文档使用的证书示例

1
2
3
mkdir -p /k3s/cert
cd /k3s/cert
nano create_self-signed-cert.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#!/bin/bash -e

help ()
{
echo ' ================================================================ '
echo ' --ssl-domain: 生成ssl证书需要的主域名,如不指定则默认为www.rancher.local,如果是ip访问服务,则可忽略;'
echo ' --ssl-trusted-ip: 一般ssl证书只信任域名的访问请求,有时候需要使用ip去访问server,那么需要给ssl证书添加扩展IP,多个IP用逗号隔开;'
echo ' --ssl-trusted-domain: 如果想多个域名访问,则添加扩展域名(SSL_TRUSTED_DOMAIN),多个扩展域名用逗号隔开;'
echo ' --ssl-size: ssl加密位数,默认2048;'
echo ' --ssl-cn: 国家代码(2个字母的代号),默认CN;'
echo ' 使用示例:'
echo ' ./create_self-signed-cert.sh --ssl-domain=www.test.com --ssl-trusted-domain=www.test2.com \ '
echo ' --ssl-trusted-ip=1.1.1.1,2.2.2.2,3.3.3.3 --ssl-size=2048 --ssl-date=3650'
echo ' ================================================================'
}

case "$1" in
-h|--help) help; exit;;
esac

if [[ $1 == '' ]];then
help;
exit;
fi

CMDOPTS="$*"
for OPTS in $CMDOPTS;
do
key=$(echo ${OPTS} | awk -F"=" '{print $1}' )
value=$(echo ${OPTS} | awk -F"=" '{print $2}' )
case "$key" in
--ssl-domain) SSL_DOMAIN=$value ;;
--ssl-trusted-ip) SSL_TRUSTED_IP=$value ;;
--ssl-trusted-domain) SSL_TRUSTED_DOMAIN=$value ;;
--ssl-size) SSL_SIZE=$value ;;
--ssl-date) SSL_DATE=$value ;;
--ca-date) CA_DATE=$value ;;
--ssl-cn) CN=$value ;;
esac
done

# CA相关配置
CA_DATE=${CA_DATE:-3650}
CA_KEY=${CA_KEY:-cakey.pem}
CA_CERT=${CA_CERT:-cacerts.pem}
CA_DOMAIN=cattle-ca

# ssl相关配置
SSL_CONFIG=${SSL_CONFIG:-$PWD/openssl.cnf}
SSL_DOMAIN=${SSL_DOMAIN:-'www.rancher.local'}
SSL_DATE=${SSL_DATE:-3650}
SSL_SIZE=${SSL_SIZE:-2048}

## 国家代码(2个字母的代号),默认CN;
CN=${CN:-CN}

SSL_KEY=$SSL_DOMAIN.key
SSL_CSR=$SSL_DOMAIN.csr
SSL_CERT=$SSL_DOMAIN.crt

echo -e "\033[32m ---------------------------- \033[0m"
echo -e "\033[32m | 生成 SSL Cert | \033[0m"
echo -e "\033[32m ---------------------------- \033[0m"

if [[ -e ./${CA_KEY} ]]; then
echo -e "\033[32m ====> 1. 发现已存在CA私钥,备份"${CA_KEY}"为"${CA_KEY}"-bak,然后重新创建 \033[0m"
mv ${CA_KEY} "${CA_KEY}"-bak
openssl genrsa -out ${CA_KEY} ${SSL_SIZE}
else
echo -e "\033[32m ====> 1. 生成新的CA私钥 ${CA_KEY} \033[0m"
openssl genrsa -out ${CA_KEY} ${SSL_SIZE}
fi

if [[ -e ./${CA_CERT} ]]; then
echo -e "\033[32m ====> 2. 发现已存在CA证书,先备份"${CA_CERT}"为"${CA_CERT}"-bak,然后重新创建 \033[0m"
mv ${CA_CERT} "${CA_CERT}"-bak
openssl req -x509 -sha256 -new -nodes -key ${CA_KEY} -days ${CA_DATE} -out ${CA_CERT} -subj "/C=${CN}/CN=${CA_DOMAIN}"
else
echo -e "\033[32m ====> 2. 生成新的CA证书 ${CA_CERT} \033[0m"
openssl req -x509 -sha256 -new -nodes -key ${CA_KEY} -days ${CA_DATE} -out ${CA_CERT} -subj "/C=${CN}/CN=${CA_DOMAIN}"
fi

echo -e "\033[32m ====> 3. 生成Openssl配置文件 ${SSL_CONFIG} \033[0m"
cat > ${SSL_CONFIG} <<EOM
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
[req_distinguished_name]
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, serverAuth
EOM

if [[ -n ${SSL_TRUSTED_IP} || -n ${SSL_TRUSTED_DOMAIN} || -n ${SSL_DOMAIN} ]]; then
cat >> ${SSL_CONFIG} <<EOM
subjectAltName = @alt_names
[alt_names]
EOM
IFS=","
dns=(${SSL_TRUSTED_DOMAIN})
dns+=(${SSL_DOMAIN})
for i in "${!dns[@]}"; do
echo DNS.$((i+1)) = ${dns[$i]} >> ${SSL_CONFIG}
done

if [[ -n ${SSL_TRUSTED_IP} ]]; then
ip=(${SSL_TRUSTED_IP})
for i in "${!ip[@]}"; do
echo IP.$((i+1)) = ${ip[$i]} >> ${SSL_CONFIG}
done
fi
fi

echo -e "\033[32m ====> 4. 生成服务SSL KEY ${SSL_KEY} \033[0m"
openssl genrsa -out ${SSL_KEY} ${SSL_SIZE}

echo -e "\033[32m ====> 5. 生成服务SSL CSR ${SSL_CSR} \033[0m"
openssl req -sha256 -new -key ${SSL_KEY} -out ${SSL_CSR} -subj "/C=${CN}/CN=${SSL_DOMAIN}" -config ${SSL_CONFIG}

echo -e "\033[32m ====> 6. 生成服务SSL CERT ${SSL_CERT} \033[0m"
openssl x509 -sha256 -req -in ${SSL_CSR} -CA ${CA_CERT} \
-CAkey ${CA_KEY} -CAcreateserial -out ${SSL_CERT} \
-days ${SSL_DATE} -extensions v3_req \
-extfile ${SSL_CONFIG}

echo -e "\033[32m ====> 7. 证书制作完成 \033[0m"
echo
echo -e "\033[32m ====> 8. 以YAML格式输出结果 \033[0m"
echo "----------------------------------------------------------"
echo "ca_key: |"
cat $CA_KEY | sed 's/^/ /'
echo
echo "ca_cert: |"
cat $CA_CERT | sed 's/^/ /'
echo
echo "ssl_key: |"
cat $SSL_KEY | sed 's/^/ /'
echo
echo "ssl_csr: |"
cat $SSL_CSR | sed 's/^/ /'
echo
echo "ssl_cert: |"
cat $SSL_CERT | sed 's/^/ /'
echo

echo -e "\033[32m ====> 9. 附加CA证书到Cert文件 \033[0m"
cat ${CA_CERT} >> ${SSL_CERT}
echo "ssl_cert: |"
cat $SSL_CERT | sed 's/^/ /'
echo

echo -e "\033[32m ====> 10. 重命名服务证书 \033[0m"
echo "cp ${SSL_DOMAIN}.key tls.key"
cp ${SSL_DOMAIN}.key tls.key
echo "cp ${SSL_DOMAIN}.crt tls.crt"
cp ${SSL_DOMAIN}.crt tls.crt

生成证书:

1
2
3
chmod a+x create_self-signed-cert.sh
./create_self-signed-cert.sh --ssl-domain=rancher的域名 --ssl-trusted-domain=rancher的域名 \
--ssl-trusted-ip=各个主机的ip(逗号分隔) --ssl-size=2048 --ssl-date=3650

配置证书:

1
2
3
4
5
6
7
8
kubectl create ns cattle-system
kubectl -n cattle-system create secret tls tls-rancher-ingress --cert=/k3s/cert/tls.crt --key=/k3s/cert/tls.key
kubectl -n cattle-system create secret generic tls-ca --from-file=/k3s/cert/cacerts.pem


## 如果导入错了,想删除证书,用下面的命令
kubectl -n cattle-system delete secret tls-rancher-ingress
kubectl -n cattle-system delete secret tls-ca

开始安装

仅 m1 节点安装

参考文档:4. 安装 Rancher | Rancher Manager

1
2
3
4
5
6
7
8
9
10
# 阿里云的镜像有点慢,我们可以先手动 `docker pull rancher/rancher:v2.7.1`,先把镜像下载下来,防止超时重试
helm repo add rancher-stable http://rancher-mirror.oss-cn-beijing.aliyuncs.com/server-charts/stable
helm install rancher rancher-stable/rancher --version 2.7.1 \
--namespace cattle-system \
--set hostname=你的rancher域名 \
--set ingress.tls.source=secret \
--set systemDefaultRegistry=registry.cn-hangzhou.aliyuncs.com \
--set replicas=1 \
--set bootstrapPassword=testrancher \
--set privateCA=true

如果安装报 Error: Kubernetes cluster unreachable ,是因为kubeconfig未注入环境变量

1
2
echo export KUBECONFIG=/etc/rancher/k3s/k3s.yaml>>/etc/profile
source /etc/profile

验证是否安装成功

1
kubectl -n cattle-system rollout status deploy/rancher

安装 Nginx

仅 m1 节点安装(此处我共用了 m1 节点,你可以自行选择一个节点负责 LB)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#配置证书、负载均衡
apt-get -y install nginx
nano /etc/nginx/conf.d/rancher.conf

# 添加如下内容
upstream rancher {
server m1:80;
}

map $http_upgrade $connection_upgrade {
default Upgrade;
'' close;
}

server {
listen 443 ssl http2;
server_name 你的rancher域名;
ssl_certificate /k3s/cert/tls.crt;
ssl_certificate_key /k3s/cert/tls.key;

location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://rancher;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# 这里将允许您在 Rancher UI 中打开命令行窗口时,窗口可以保留最多15分钟。没有这个参数时,默认值为1分钟,一分钟后在Rancher>中的sh会自动关闭。
proxy_read_timeout 900s;
proxy_buffering off;
}
}

server {
listen 80;
server_name 你的rancher域名;
return 301 https://$server_name$request_uri;
}

# 重载nginx服务
systemctl enable nginx
nginx -t
nginx -s reload

访问页面

在所有节点中执行

1
2
3
4
5
6
7
8
nano /etc/hosts

# 添加如下内容
192.168.0.67 你的rancher域名

# 本机 Windows 连接
C:\Windows\System32\drivers\etc\hosts
192.168.0.67 你的rancher域名

解决域名解析问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#解决 hosts 解析问题
kubectl edit configmap coredns -n kube-system

# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
Corefile: |
.:53 {
errors
health
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
hosts /etc/coredns/NodeHosts {
ttl 60
reload 15s
192.168.0.67 你的rancher域名
fallthrough
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
import /etc/coredns/custom/*.server
NodeHosts: |
192.168.0.67 m1
192.168.0.102 n1
172.25.4.244 m2
kind: ConfigMap
......


# 测试是否配置成功
kubectl run -it --rm --restart=Never busybox --image=busybox:1.28 -- nslookup 192.168.0.67 你的rancher域名

如果你在 k3s 脚本中选择了默认安装 coredns,即便修改了它的 configmap,它也会因为重启复原导致失效。永久方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
nano /var/lib/rancher/k3s/server/mycoredns.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns-custom
namespace: kube-system
data:
rancher.server: |
你的rancher域名 {
hosts {
192.168.0.67 你的rancher域名
}
}

# 执行
kubectl apply -f /var/lib/rancher/k3s/server/mycoredns.yaml

访问Rancher主页

记得关闭代理或设置白名单

访问页面:https://你的rancher域名,输入密码 testrancher


解决证书不安全

如果你是自定义证书安装,那么大概率会显示https所使用的证书不安全,因为这个是我们自定义的,证书不安全倒也不影响我们使用,内网环境还好,但外网环境下就比较危险了, 而且证书不安全无法自动保存密码,很麻烦,所以需要对这个证书添加至信任列表中

image-20230901095910275

image-20230901095956458

image-20230901100010835

image-20230901100028366

image-20230901100058282

image-20230901100113214

image-20230901100129418

清除浏览器缓存,或者使用匿名模式访问你的rancher地址,就能看到证书正常,连接安全了


其他

删除 Rancher

官方脚本删除:清理节点 | Rancher文档(太危险不建议使用)

一般方法:

1
2
helm uninstall rancher -n cattle-system
kubectl delete -n cattle-system MutatingWebhookConfiguration rancher.cattle.io

如果执行卡住,我们在 m1 开启两个终端,其中一个执行 kubectl proxy另一个执行:

根据实际 namespace 输入,原理是强制删除文件

1
2
3
4
5
6
7
8
9
curl -H "Content-Type: application/json" -XPUT -d '{"apiVersion":"v1","kind":"Namespace","metadata":{"name":"cattle-monitoring-system"},"spec":{"finalizers":[]}}' http://localhost:8001/api/v1/namespaces/cattle-monitoring-system/finalize
curl -H "Content-Type: application/json" -XPUT -d '{"apiVersion":"v1","kind":"Namespace","metadata":{"name":"cattle-ui-plugin-system"},"spec":{"finalizers":[]}}' http://localhost:8001/api/v1/namespaces/cattle-ui-plugin-system/finalize
curl -H "Content-Type: application/json" -XPUT -d '{"apiVersion":"v1","kind":"Namespace","metadata":{"name":"cattle-system"},"spec":{"finalizers":[]}}' http://localhost:8001/api/v1/namespaces/cattle-system/finalize
curl -H "Content-Type: application/json" -XPUT -d '{"apiVersion":"v1","kind":"Namespace","metadata":{"name":"cattle-fleet-clusters-system"},"spec":{"finalizers":[]}}' http://localhost:8001/api/v1/namespaces/cattle-fleet-clusters-system/finalize
curl -H "Content-Type: application/json" -XPUT -d '{"apiVersion":"v1","kind":"Namespace","metadata":{"name":"cattle-fleet-local-system"},"spec":{"finalizers":[]}}' http://localhost:8001/api/v1/namespaces/cattle-fleet-local-system/finalize
curl -H "Content-Type: application/json" -XPUT -d '{"apiVersion":"v1","kind":"Namespace","metadata":{"name":"cattle-fleet-system"},"spec":{"finalizers":[]}}' http://localhost:8001/api/v1/namespaces/cattle-fleet-system/finalize
curl -H "Content-Type: application/json" -XPUT -d '{"apiVersion":"v1","kind":"Namespace","metadata":{"name":"cattle-global-data"},"spec":{"finalizers":[]}}' http://localhost:8001/api/v1/namespaces/cattle-global-data/finalize
curl -H "Content-Type: application/json" -XPUT -d '{"apiVersion":"v1","kind":"Namespace","metadata":{"name":"cattle-global-nt"},"spec":{"finalizers":[]}}' http://localhost:8001/api/v1/namespaces/cattle-global-nt/finalize
curl -H "Content-Type: application/json" -XPUT -d '{"apiVersion":"v1","kind":"Namespace","metadata":{"name":"cattle-impersonation-system"},"spec":{"finalizers":[]}}' http://localhost:8001/api/v1/namespaces/cattle-impersonation-system/finalize

重置密码

登录成功直接去 rancher 个人中心里改,这个是命令改的方法

1
kubectl -n cattle-system exec $(kubectl -n cattle-system get pods -l app=rancher | grep '1/1' | head -1 | awk '{ print $1 }') -- reset-password

安装 Harbor

仅在 harbor 节点执行

自行下载 Harbor 压缩包,并解压到 /usr/local/harbor 目录下

修改配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cd /usr/local/harbor
nano /usr/local/harbor/harbor.yml

# 修改如下内容
hostname: 192.168.0.88
http:
# 按需配置
port: 24270

# 不用https,全部注释掉
#https:
# https port for harbor, default is 443
#port: 443
# The path of cert and key files for nginx
#certificate: /your/certificate/path
#private_key: /your/private/key/path

# 需要外网访问的可配置此项,但hostname的配置就失效了
# 我用 WireGuard 代理了本机,所以不用配置,也不用外网访问
# external_url:

# 注意数据存放位置
# 建议存放到 ssd 挂载硬盘
data_volume: /data/harbor

外网访问请注意开启对应端口

然后执行安装 sh /usr/local/harbor/install.sh 即可,输入 docker ps -a 查看容器启动情况,如果有一直重启的容器,需要输入 docker logs -f -n 300 容器名称 查看报错原因。浏览器访问 192.168.0.88:24270 即可。

由于 Harbor 是用 Docker 启动的,如果 Docker 重启,Harbor 服务可能会不正常,所以我们在 Docker 重启后,重新启动下 Harbor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
nano /usr/local/harbor.service

# 添加如下内容
[Unit]
Description=Harbor service with docker-compose
Requires=docker.service
After=docker.service

[Service]
Restart=always
#Type=oneshot
RemainAfterExit=yes
StandardError=null
StandardOutput=null
WorkingDirectory=/usr/local/harbor/

# Remove old containers
ExecStartPre=/usr/bin/docker-compose -f /usr/local/harbor/docker-compose.yml down -v

# Compose up
ExecStart=/usr/bin/docker-compose -f /usr/local/harbor/docker-compose.yml up -d

# Compose down, remove containers
ExecStop=/usr/bin/docker-compose -f /usr/local/harbor/docker-compose.yml down -v

[Install]
WantedBy=multi-user.target

注册服务,并测试

1
2
3
4
5
6
7
8
9
10
cp /usr/local/harbor.service  /lib/systemd/system/
systemctl daemon-reload
systemctl enable harbor

# 测试服务是否可用
systemctl restart harbor
systemctl status harbor.service

# 测试重启 docker 后 Harbor 是否可用
systemctl restart docker

配置私有仓库地址:

所有节点均执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
nano /etc/docker/daemon.json

# 添加如下内容
{
"insecure-registries" : ["192.168.0.88:24270", "harbor:24270"]
}

# 重启docker
systemctl daemon-reload
systemctl restart docker

# 在 Harbor 中创建一个 registry 私有仓库
# 在所有节点测试是否连接成功 Harbor
docker pull hello-world
docker tag `docker images | grep hello-world | awk 'NR==1{print $3}'` 192.168.0.88:24270/registry/hello-world:latest
docker login -u admin -p 管理员密码 192.168.0.88:24270
docker push 192.168.0.88:24270/registry/hello-world:latest

# 测试域名解析是否正常
docker pull hello-world
docker tag `docker images | grep hello-world | awk 'NR==1{print $3}'` harbor:24270/registry/hello-world:latest
docker login -u admin -p 管理员密码 harbor:24270
docker push harbor:24270/registry/hello-world:latest

在Rancher中开启旧版功能:

全局配置

开启legacy

最后在你的项目中,选中创建好的镜像拉取密钥即可:


快捷指令备份

1
2
3
4
5
6
7
8
9
10
11
# 一键删除命名空间下 Error 的 Pod
kubectl delete pod `kubectl get pods --namespace 命名空间 | awk '$3 == "Error" {print $1}'` --namespace 命名空间

# 一键删除 docker 所有容器
docker rm $(docker ps -aq)

# 查看今天 k3s server 的日志
journalctl -xfu k3s

# 查看今天 k3s agent 的日志
journalctl -xfu k3s-agent

疑难杂症篇

本小节会持续更新,也许后续会单独开一篇文章汇总遇到的问题

Helm

  1. 为什么总是出现 Error 的 Pod ?

    大概率是报错了一直重试安装,一般是因为你的服务器 pull 某个镜像超时了,从而触发 helm 的重新安装,所以才会有一长串的 Error Pod


在下一篇文章中,我们将通过 k3s 和 rancher 部署单节点的 mysql 和 redis

【K3S】02 - Rancher 中间件单节点部署