Setup K3S + Rancher Cluster

Setup K3S + Rancher Cluster

Notice: This article is only available in Chinese. :(

2024/02/1 更新:

  • k3s 截止最新版本 v1.25.5 部署建议。
  • traefik 禁用参数 由 --no-deploy 变更至 --disable
  • k3s 架构图更新。
  • 更改了安装命令的小错误。

很久没写博客了啊。。。大三啦,准备着电子设计大赛和考研,还是有点忙的,一直没来打理博客;要写的东西其实并不少(说白了就是懒~)。最近有空升级了集群的部署方案,为了以后能有个参考,可以来水一篇了。

我在香港的两台服务器上托管着各种各样的服务,有轻量级别的 RSS阅读器 Miniflux、IFTTT 开源替代品 Huginn、有中等量级别的 数据收集与可视化 Grafana + Prometheus 栈、消息队列服务 RabbitMQ,也有重量级别的 Git + CI 服务 GitLabMinecraft 服务器等,他们都蜗居在这两台中等配置的服务器上。

为了方便管理与减少维护的精力与时间,我使用的部署方式/工具一代代地升级着:

  • 初中时想要搭建一个小博客,在网上一番搜索了解到 web host 这种东西,即一群人直接通过在一台服务器上创建不同用户的用户来共享这台服务器的资源,从而节省成本;那时主机上放置的程序基本上都是 PHP 脚本,能够跑个 Wordpress、typecho 就足够了,然后通过 cPanel 配置 mysql、apache 或是 lightspeed 来让程序运行起来(现在想起来都是满满的回忆啊,同时我也见证了 PHP 从兴盛时期渐渐衰落的历程)。
  • 高中时有了第一台云服务器,主要用来运行如 Wordpress、tt-rss 等一些非常轻量级别的应用和一些自己写的简单的 Python 与 PHP 网页应用,只需简单粗暴地 直接在宿主机系统中安装部署
  • 随着部署的应用越来越多,云服务器的配置也在提升。或是因为部分的应用依赖着不同版本的数据库、缓存器等基础服务,或是因为单个应用可能占用过多的资源,不方便对部署应用的资源占用进行限制,而逐渐产生了应用间环境隔离的需求,我便开始学习使用 Docker 容器化部署方式 + Docker Compose 容器编排工具
  • 之后拥有了第二台配置低一点的服务器,这时开始有了多节点灵活调度服务、安排资源、部分基础服务高可用的需求,于是便开始学习使用 Rancher V1 及其自带的编排工具 Cattle;但可能使用的人并不多,再加上 容器编排工具的未来 Kubernetes 迅猛的发展势不可挡,Rancher 公司在一七年就直接弃坑了 Cattle,于是 V2 版本后 Rancher 只支持 Kubernetes 编排了,而我一直使用着这个停止更新甚至维护的老技术😭。
  • 最终便是几年前就了解过的 Kubernetes,但一直畏于其较高的学习成本(其实并不太想花心思在这上面)而没能尝试过这么方便而高效率的部署工具。最近因为担心 Log4J 漏洞 的影响,以及 Rancher V1 对较高版本 docker-compose.yml 文件的不兼容(虽能正常工作,但日志里就是不停地吐 Error,实在是受不了了),终于狠下心来抛弃了四年前就停止维护的老古董,将绝大部分原来的服务都迁移至 k3s 上了。

kubernetes、k3s 与 Rancher

先来简要介绍一下 k8s 和 k3s 他们俩。

Kubernetes

又名 k8s(8 指的是 k 与 s 间有8个字母,类似于 internationalization 和 i18n),据 Kubernetes 官网 称是源自 Google 15年生产环境的运维经验的用于自动部署,扩展和管理容器化应用程序的开源系统。



使用 Kubernetes 进行容器管理有什么实际的好处呢?从官网上介绍的特性中对与我来讲最有用的有以下这么几个:

  • 自动化上线和回滚: 可使用 CI 工具(如 Gitlab 自带 CI/CD 工具)进行整合实现自动构建与部署,也可回滚到某个特定版本的应用
  • 服务发现与负载均衡: 非常方便的 Pod 地址解析和流量分配的负载均衡功能
  • Secret 和配置管理: 将证书、密钥、环境变量单独放在同一命名空间下的 Secrets 和配置映射中,更改后只需重启 Pod 即可生效,方便了对机密信息与配置变量的统一管理
  • 水平扩缩: 对于如 Worker、后台作业处理等无状态应用,可根据任务量变化与资源分配情况灵活地使用 API 进行扩展和收缩
  • 自我修复: 使用健康检查进行状态监控;在应用可用时向外界暴露访问端点,而在应用不可用时关闭访问端点避免误操作对应用的破坏,并在必要时重新创建容器,以保证应用(特别是基础服务)的正常运行

可 Kubernetes 并不是可以使用在任何环境下,至少当机器资源并不充裕时。这时,k3s 便进入了我们的视野。

k3s

k3s 是 Rancher Labs 的作品。



作为 k8s 的轻量级发行版,k3s 对 k8s 做了很大幅度的精简化,例如默认不使用 Etcd、去掉了部分 k8s 中很少使用的冗杂功能、使用更轻量级的容器运行时 containerd、将所需组件压缩在了一个小于 50MB 的二进制可执行文件中等,因此 k3s 非常适合用于资源紧张的大多数场景。此外,k3s 还增强了对 Arm 处理器服务器的支持,适合于 IoT 与边缘计算环境,例如运行在树莓派组成的集群上。

k3s 安装

接下来进入正题,开始 k3s 工具的安装准备工作。

k3s 的部署方式可选择 单一节点部署方式高可用部署方式,他们之间的区别在于是否要求 control plane 高可用,从而增加节点的个数。而高可用部署方式也可选择使用 外置 mysql 数据库内置嵌入式 Etcd 来存储集群调度数据。

架构拓扑

首先是关于 Server 节点Agent 节点 的定义:

  • Server 节点 指的是运行 k3s server 命令的主机,具有 control plane 和数据存储组件由 k3s 管理。
  • Agent 节点 指的是运行 k3s agent 命令的主机,不具有任何数据存储或 control plane 组件。

下图为来自官方文档的 k3s 内部组件架构图:

考虑之后有空准备另起一篇文章较为详细地介绍 k3s 组成与原理,而本篇文章假设读者已经了解部分 k8s 关键概念,故这里便不再阐述各组件的功能。

对于应用在生产环境的集群,建议在一开始就使用 高可用 + 外部 MySQL 数据库 架构(而非单节点 + 嵌入式 SQLite 数据库),就算最开始只是使用一个 Server 节点;这样在以后能够更加容易地扩展集群规模。不建议使用目前还处于开发阶段且难以迁移的 嵌入式 Etcd

单个或多个 Server 节点管理多个拥有 Worker 角色(Role)的 Agent 节点(一个节点可以拥有多个角色,因此 Server 节点也可用作 Worker 节点来部署应用或服务),然后通过外置负载均衡器(例如我正在使用的 Cloudflare)来将流量分配到这些运行应用或服务的 Agent 节点上;而对于所有 Agent节点 依赖的 k3s Server 6443 服务,可通过部署一个 Nginx 容器的方式进行负载均衡以及故障转移。

2024 年更新:

到当前 1.25 版本且经过我的实践,已经完全可以信任其自带的 嵌入式高可用 Etcd 集群 了,对于 homelab 集群,不再需要一个外部的高可用 mysql 数据库,不仅节省一笔不小的开支,而且将可用性依赖都划归到内部解决,不会出现因为外部数据库离线引起的集群崩溃(换句话说就是要炸一起炸嘛😋)。

外置 Mysql 高可用方案 架构拓扑图:

内置嵌入式 Etcd 高可用方案 架构拓扑图:

(以上架构图均摘自 Rancher k3s 文档 - 架构介绍

安装前准备

编辑 /etc/hostname 文件更改主机名,如 stage.central
编辑 /etc/hosts 文件,配置所有集群中的机器的主机名与 ip 地址的映射,也可加上外部数据库主机地址,例如:

192.168.1.1 stage.central
192.168.1.2 native.central

101.60.60.60 mysql.external

安装与配置 Server 节点

Rancher 提供的安装脚本非常实用;运行时只需添加少数的参数或环境变量便可以方便地进行自定义。

下面给出我在安装时使用的命令作为例子:

curl -sfL https://get.k3s.io | \
  INSTALL_K3S_VERSION=v1.28.5+k3s1 \
  INSTALL_K3S_SKIP_START=true \
  sh - server

然后编辑文件 /etc/systemd/system/k3s.service 来修改 k3s 的启动参数:

ExecStart=/usr/local/bin/k3s \
    server \
        '--docker' \
        '--disable' \
        'traefik' \
        '--advertise-address' \
        '192.168.1.1' \
        '--node-ip' \
        '192.168.1.1' \
        '--node-external-ip' \
        '101.50.50.1' \
        '--datastore-endpoint=mysql://username:password@tcp(mysql.external:3306)/database' \

最后启动 k3s server:

systemctl daemon-reload
systemctl start k3s

其中所包含的启动选项与参数:

  • docker: 使用 docker 而非 k3s 默认的 containerd 作为容器运行时。因为我除了 k3s 外还需要 docker-compose 进行简便的容器部署,所以此处使用的是 docker 运行时。若没有特殊需求,建议使用默认 containerd
  • disable traefik: 在安装时不部署 traefik。traefik 作为容器化环境中反向代理与负载均衡器的优先选择,轻量、易用;可默认自动部署,其将工作在 80 与 443 端口。你也可以选择使用 Nginx 作为负载均衡器来代理流量。此处因为我还有部分应用并没有容器化,需要使用已部署在主机中,工作在 80 与 443 端口的 Nginx 反向代理流量才使用禁用部署 traefik 的选项
  • node-ip: 节点地址,设置为内网地址
  • node-external-ip: 节点外部地址,设置为外网地址
  • datastore-endpoint: 外部数据库连接字符串,按照格式配置自己部署或是外部的高可用数据库即可

上述启动选项也可组合为字符串赋给 INSTALL_K3S_EXEC 环境变量来配置。安装前可通过配置 INSTALL_K3S_SKIP_START 环境变量为 true 来阻止安装过程结束时 k3s 的自动启动以进行更多的首次运行前配置操作。安装结束后也可通过编辑 .service 文件来需要更改启动选项,其文件位于 /etc/systemd/system/k3s.service

若需部署多个 Server 节点,可在这几个节点上执行更改对应 node-ipnode-external-ip 启动选项后的上述安装命令。

PS. 若服务器位于国内,也可使用镜像地址 https://rancher-mirror.rancher.cn/k3s/k3s-install.sh 和添加 INSTALL_K3S_MIRROR=cn 参数的方式来加速软件的下载过程。

Server 节点安装结束后可通过命令 cat /var/lib/rancher/k3s/server/token 获取自动生成的 Token。

若要继续安装配置更多的 Server 节点,只需在安装命令中添加上一步得到的 Token 参数即可:

curl -sfL https://get.k3s.io | \
  INSTALL_K3S_VERSION=v1.28.5+k3s1 \
  INSTALL_K3S_SKIP_START=true \
  K3S_TOKEN=TOKEN_KEEP_SECRET \
  sh - server

安装与配置 Agent 节点

下面给出我在安装时使用的命令作为例子:

curl -sfL https://get.k3s.io | \
  INSTALL_K3S_VERSION=v1.28.5+k3s1 \
  INSTALL_K3S_SKIP_START=true \
  K3S_TOKEN=TOKEN_KEEP_SECRET \
  K3S_URL=https://stage.central:6443 \
  INSTALL_K3S_EXEC="--docker --node-ip 192.168.1.2 --node-external-ip 101.50.50.2" \
  sh -

其中所包含的环境变量:

  • K3S_TOKEN: 前面安装 Server 节点时自动生成的 token 字符串
  • K3S_URL: Server 节点 API Server 地址,默认工作在 6443 端口。若高可用架构 Server 节点不止一个,则需要使该端点通过负载均衡器
  • INSTALL_K3S_EXEC: 即启动选项,与 Server 大致相同

类似地,在安装结束后也可通过编辑 .service 文件来需要更改启动选项,其文件位于 /etc/systemd/system/k3s-agent.service。其他的配置与与 Server 节点基本一致。

安装后配置

在完成所有 Server 与 Agent 节点的安装后,可通过 kubectl get node -o wide 命令来获取集群中的所有节点,命令会输出如下的消息:

NAME             STATUS   ROLES                  AGE   VERSION        INTERNAL-IP   EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
stage.central    Ready    control-plane,master   27d   v1.23.6+k3s1   192.168.1.1   101.50.50.1   Ubuntu 20.04.4 LTS   5.4.0-110-generic   docker://20.10.12
native.central   Ready    <none>                 27d   v1.23.6+k3s1   192.168.1.2   101.50.50.2   Ubuntu 20.04.4 LTS   5.4.0-110-generic   docker://20.10.12

若要向节点添加 Worker 角色,可使用 kubectl 命令:

kubectl label node/stage.central node-role.kubernetes.io/worker=true
kubectl label node/native.central node-role.kubernetes.io/worker=true

然后再次使用 kubectl get node -o wide 命令查看所有节点信息:

NAME             STATUS   ROLES                         AGE   VERSION        INTERNAL-IP   EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
stage.central    Ready    control-plane,master,worker   27d   v1.23.6+k3s1   192.168.1.1   101.50.50.1   Ubuntu 20.04.4 LTS   5.4.0-110-generic   docker://20.10.12
native.central   Ready    worker                        27d   v1.23.6+k3s1   192.168.1.2   101.50.50.2   Ubuntu 20.04.4 LTS   5.4.0-110-generic   docker://20.10.12

可以观察到节点的角色已更新。

通过 kubectl get pods -n kube-system 命令可观察由系统启动的基础服务是否成功启动,命令会输出如下的信息:

NAME                                      READY   STATUS    RESTARTS   AGE
coredns-d76bd69b-fkdtm                    1/1     Running   0          27d
local-path-provisioner-6c79684f77-v7m8n   1/1     Running   0          27d
metrics-server-7cd5fcb6b7-hmssl           1/1     Running   0          27d
svclb-traefik-x4knv                       2/2     Running   0          27d
svclb-traefik-sbs7c                       2/2     Running   0          27d

当观察到所有 Pod 的 STATUS 均为 Running 时,说明该集群已准备完毕,可投入正常使用了。

traefik 部署

部署 traefik 前先来安装 Helm。
Helm 为 Kubernetes 包管理器,使用 Helm 进行应用(称为 Chart)的搜寻与安装非常方便快捷。
Helm 的安装非常简单,只需在 Helm Github Releases 上找到对应版本的二进制文件,下载后软连接或复制到 /usr/local/bin 文件夹中即可。

P.S. 现在可使用下面的安装 script 一键安装:

curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

使用 Helm 安装与配置 traefik

若要手动安装 traefik,此时便可方便地使用 Helm 工具来进行应用的部署了。

首先使用 helm repo add traefik https://helm.traefik.io/traefik 命令添加 traefik 的仓库。
然后使用 helm install --namespace=kube-system traefik traefik/traefik 命令将 traefik 部署到 kube-system 命名空间。

若需要修改 traefik 暴露的外部主机端口,则需要以下几个步骤来更新配置并升级部署:

  1. 使用 helm show values traefik/traefik > traefik_values.yml 将 yml 格式的 traefik 部署文件输出到 traefik_values.yml 文件中

  2. 打开 traefik_values.yml 文件,找到 Ports 节(大致在 276 行),将 web 和 websecure 节中的 exposedPort 更改为希望的监听端口号(如 1080 和 1443)

    # Configure ports
    ports:
    # The name of this one can't be changed as it is used for the readiness and
    # liveness probes, but you can adjust its config to your liking
    traefik:
     port: 9000
     # Use hostPort if set.
     # hostPort: 9000
     #
     # Use hostIP if set. If not set, Kubernetes will default to 0.0.0.0, which
     # means it's listening on all your interfaces and all your IPs. You may want
     # to set this value if you need traefik to listen on specific interface
     # only.
     # hostIP: 192.168.100.10
    
     # Override the liveness/readiness port. This is useful to integrate traefik
     # with an external Load Balancer that performs healthchecks.
     # healthchecksPort: 9000
    
     # Defines whether the port is exposed if service.type is LoadBalancer or
     # NodePort.
     #
     # You SHOULD NOT expose the traefik port on production deployments.
     # If you want to access it from outside of your cluster,
     # use `kubectl port-forward` or create a secure ingress
     expose: false
     # The exposed port for this service
     exposedPort: 9000
     # The port protocol (TCP/UDP)
     protocol: TCP
    web:
     port: 8000
     # hostPort: 8000
     expose: true
     exposedPort: 1080
     # The port protocol (TCP/UDP)
     protocol: TCP
     # Use nodeport if set. This is useful if you have configured Traefik in a
     # LoadBalancer
     # nodePort: 32080
     # Port Redirections
     # Added in 2.2, you can make permanent redirects via entrypoints.
     # https://docs.traefik.io/routing/entrypoints/#redirection
     # redirectTo: websecure
    websecure:
     port: 8443
     # hostPort: 8443
     expose: true
     exposedPort: 1443
     # The port protocol (TCP/UDP)
     protocol: TCP
     # nodePort: 32443
     # Enable HTTP/3.
     # Requires enabling experimental http3 feature and tls.
     # Note that you cannot have a UDP entrypoint with the same port.
     # http3: true
     # Set TLS at the entrypoint
     # https://doc.traefik.io/traefik/routing/entrypoints/#tls
     tls:
       enabled: true
       # this is the name of a TLSOption definition
       options: ""
       certResolver: ""
       domains: []
       # - main: example.com
       #   sans:
       #     - foo.example.com
       #     - bar.example.com
  3. 使用 helm upgrade -f traefik_values.yml traefik traefik/traefik --namespace kube-system 命令更新配置并升级部署:

    NAME: traefik
    LAST DEPLOYED: Tue Jan 30 02:15:33 2024
    NAMESPACE: kube-system
    STATUS: deployed
    REVISION: 1
    TEST SUITE: None
    NOTES:
    Traefik Proxy v2.10.6 has been deployed successfully on kube-system namespace !
  4. 使用 kubectl get pods -n kube-system 命令查看刚部署的、以 traefik 开头的 Pods,若状态均为运行,即部署更新成功。此时可打开 1080 端口查看,因为没有配置路由,将会得到 404 响应:

    NAME                                      READY   STATUS    RESTARTS   AGE
    coredns-6799fbcd5-nmpxh                   1/1     Running   0          19m
    local-path-provisioner-84db5d44d9-r9gxw   1/1     Running   0          19m
    metrics-server-67c658944b-jnxjv           1/1     Running   0          19m
    svclb-traefik-93188a2c-cgmps              2/2     Running   0          70s
    svclb-traefik-93188a2c-mzpmt              2/2     Running   0          70s
    traefik-5b4866795f-hkq6v                  1/1     Running   0          70s

此后,可通过向命名空间中添加 ingress 服务即可使用 traefik 来将流量引至对应的服务中去。

Good Ending 1

若只是需要一个可以立刻开始部署应用的 k3s 集群,现在就可以结束了。
但是使用繁琐的命令行界面与脚本来管理集群与资源,对于我们非专业人员来说就有点繁琐,且容易误操作。这时可以使用图形化工具来辅助管理 k3s 集群。

Rancher 安装

Rancher 官网上有两种安装方式:一种是直接使用官方的 docker 镜像的非高可用安装方式,这个镜像会在容器内再创建一个 local 集群专用于 Rancher 软件的运行;另一种是直接在刚刚搭建好的集群上安装。

两种安装方式的主要区别应该就是难度了,使用官方的 docker 镜像安装方式虽然简单,几步即可搞定,但是对于未来集群的扩展时从容器中迁移到集群上时就不太容易了,而且极有可能有兼容性问题。而第二种安装方式需要知晓一些 Kubernetes 概念,在最开始经验甚少的我摸索时可谓是万分难受,稍有一个步骤不正确就得从头再来(从头 指的是重新从 k3s 的安装开始)。

但长痛不如短痛,与其在之后使用与迁移时难受,不如干脆一开始就直接装集群上好了。

首先,通过 helm repo add rancher-latest https://releases.rancher.com/server-charts/latest 命令添加 Rancher V2 仓库。

使用 kubectl create namespace cattle-system 创建一个名为 cattle-system 的命名空间,用来放置 Rancher 相关的资源。

然后是准备用于 https 连接的证书。如果我只是内网访问,不上 https 可以吗?答案是不可以的,因为 Rancher 的 https 是强制性的,所以必须要准备证书。。。在实际环境中,我所有的服务都要经过 Nginx 负载均衡,因此相比与官网提供的 cert-manager 和 letsencrypt 两种方案外,我采用的是自签名证书,此时便没有了维护 cert-manager 和 letsencrypt authentication 的成本了。

因此,下一个步骤便是生成自签名 CA 证书,然后加到 cattle-system 的 secrets 中去。

创建一个脚本文件,编辑以下内容,用于生成自签名 CA 证书:

#!/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

(上面的脚本来自 Rancher 官方文档 生成自签名 SSL 证书,非常好用,建议加入到个人笔记)
存盘后chmod +x 后直接运行,根据脚本的步骤一步步完成即可,备份一份后保存好生成的 key 与 crt 文件到临时目录中,然后打开 crt 文件,将第二个证书的内容另存为 pem 文件(这里为 cacerts.pem),这个就是所需的 CA 证书。

接下来执行下面的命令将证书添加到 cattle-system 的 secrets 中去:

kubectl -n cattle-system create secret generic tls-ca \
  --from-file=cacerts.pem=./cacerts.pem

接下来就可以开始 Rancher 的安装了,准备好访问 Rancher 的主机名(这里用 rancher.localhost 代替),然后执行下面的命令:

helm install rancher rancher-latest/rancher \
  --namespace cattle-system \
  --set hostname=rancher.localhost \
  --set ingress.tls.source=secret \
  --set privateCA=true

等待五分钟左右后,使用下面的命令检查 Rancher 是否已成功部署:

kubectl -n cattle-system rollout status deploy/rancher
Waiting for deployment "rancher" rollout to finish: 0 of 3 updated replicas are available...
deployment "rancher" successfully rolled out

若是按照上述步骤执行的话,大致是没有问题的。

先通过以下命令找到初始默认密码:

kubectl get secret --namespace cattle-system bootstrap-secret -o go-template='{{.data.bootstrapPassword|base64decode}}{{ "\n" }}'

安装时 Helm 会向 cattle-system 中添加 ingress,若部署成功后即可通过 Rancher 的主机名进行访问了。然后就像日志中所说的:Happy Containering!

登录后界面:

集群首页:(这里最下面有事件与告警,是因为我安装了 cluster-monitoring 应用;这个能够帮助你管理集群动态的栈可以在应用商店中找到并安装)

集群项目列表:

项目首页:

只要了解 Kubernetes 的大部分概念,Rancher 的使用应该是易如反掌。大部分操作只需试探几次便可熟稔于心了,这里也无需再作过多赘述。

Good Ending 2

接下来可以完成一些非必要,但是能够为以后节省配置时间的操作。

正如前面所提及,我的所有服务(包括由 k3s 部署的容器应用与直接在主机上部署的非容器应用)均会通过 Nginx 负载均衡,然后再通过 Cloudflare 负载均衡后暴露出来。若在每次部署新的服务后,如果都需要重新配置一遍 Nginx 配置文件和 Cloudflare 解析记录的话,未必过于冗杂。
有没有什么简单的方法,只需在 Rancher 上完成部署并创建 ingress 规则后,立刻就能通过 ingress 进行访问呢?我想到的方法是泛域名解析与 Nginx 配置优先级。只需在 Cloudflare 解析处与 Nginx 入口上配置一条泛域名解析,即可轻松实现这样的需求。

例如我的业务通配符域名为 *.toay.io,若此时有发往 workspace.toay.io 的流量,则其先经过 Cloudflare 后到达任意一台服务器中监听着 443 端口的 Nginx,再由 Nginx 依优先级顺序,先完全匹配子域名,失败后再遵循最低优先级的通配符子域名配置,将流量转发到监听 1443 端口的 traefik 上。最后,traefik 再根据配置的 ingress 规则,将流量传递到指定的服务 Mattermost Web 上去。至此,流量成功地抵达了对应的服务,而无需再作多余的配置。

记录 外置 mysql 数据库的 k3s 集群 以及方便的集群管理工具 Rancher 安装配置的内容目前就到这里咯。而对于 Homelab 这种更为常见的场景,为了设法充分利用这些资源,我在这之后又探索了 使用嵌入式 Etcd 的多 Server 节点部署通过 Tailscale NAT 穿透 建立跨云内网实现跨云 k3s 集群,这就是另一篇文章 使用 Tailscale 轻松搭建易扩展的跨云 k3s 集群 所介绍的内容了😋。

参考链接

  1. Kubernetes
  2. K3s: Lightweight Kubernetes
  3. Rancher k3s 文档
  4. Rancher 2.6 Docs
  5. 生成自签名 SSL 证书 | Rancher 文档