自动化工具 活跃维护

guide

hobby-kube/guide

面向爱好者的 Kubernetes 集群。

5660
Stars 标星
252
Forks 分支
105
Watchers 关注
9
Open Issues
—
主要语言
MIT
开源协议
142 KB
仓库大小
3 年前
最后推送
一键安装扩展 / 插件指令
dsh plugin --profile web add github:hobby-kube/guide
git clone https://github.com/hobby-kube/guide.git
git clone git@github.com:hobby-kube/guide.git
README.md master

英文原文正文为项目原始 README(英文),本站后续会翻译为中文,当前仅剔除图片与无关章节并统一排版。

适合爱好者的 Kubernetes 集群

本指南回答了如何在 Hetzner Cloud、DigitalOcean 或 Scaleway 等云提供商上设置和操作一个功能齐全、安全的 Kubernetes 集群的问题。它解释了如何克服缺乏外部入口控制器、完全隔离的安全私有网络以及持久分布式块存储的问题。

请注意,以下各节可能带有一定主观意见。Kubernetes 是一个不断发展、快速变化的环境,这意味着本指南可能会随着时间变得过时,这取决于作者的闲暇时间和个人贡献。因此,非常欢迎各界的贡献。

本指南配有一个完全自动化的集群设置方案,以结构良好、模块化的 Terraform 配方形式呈现。在整个指南中,相关模块的链接被上下文地分布,并用 Terraform 图标进行视觉突出显示。

如果您觉得这个项目有帮助,请考虑通过 GitHub Sponsors 支持其未来的发展。

集群规模

专业的爱好者集群操作员追求系统的弹性——即系统抵抗和从故障中恢复的能力。另一方面,他们通常在基本集群上的资金有限,或者说愿意投入的资金有限。因此,在弹性和成本之间找到良好的平衡至关重要。

经过各种设置和配置的实验,一个好的参考点是,一个基本集群可以在仅有两个虚拟主机且每个主机拥有1GB内存的情况下运行。在这一点上值得一提的是,Kubernetes在计算中不包括交换内存,并且在达到内存限制时会非常严格地驱逐Pod(参考)。与内存不同,原始CPU性能并不那么重要,尽管显而易见,下一代Facebook不会仅通过两个虚拟CPU核心运行。

为了使Kubernetes集群具有弹性,建议其由至少三台主机组成。其主要原因在于etcd,它本身是任何Kubernetes设置中不可或缺的一部分,仅在集群成员至少为三台时才具备容错能力(参考)。

选择云服务提供商

provider/hcloud provider/digitalocean provider/scaleway

此时需要根据一些标准选择云提供商,如可信度、可靠性、价格和数据中心位置。目前最好的选择绝对是 Hetzner Cloud(推荐链接,获得 €20),在这里可以以 每月约 €13.50 的价格建立一个合适的三节点集群(分别为 3x2GB 或 3x4GB,使用 arm64 CPU)。

DigitalOcean(推荐链接,获得 $100)以其优质的支持和遍布全球的数据中心而闻名,这是一个明显的优势。一个三节点集群每月费用为 $18(3x1GB)。

Scaleway 的实例起价约为 €5。一个三节点集群每月费用约为 €15(3x1GB,每个节点 20GB 磁盘空间)。

Linode、Vultr 以及其他几家提供类似资源的供应商都是可行选项。虽然它们各有优劣,但用于托管 Kubernetes 集群完全没问题。

虽然近年来虚拟私人服务器的价格普遍上涨,但 arm64 CPU 的兴起为更实惠的选项打开了大门。本指南提供的设置既可在 x86 系统上运行,也可在 arm64 系统上运行。

选择操作系统

虽然 Linux 有很多版本,但 Ubuntu(LTS)是托管我们集群的首选发行版。这可能显得有些主观——确实如此——但话又说回来,Ubuntu 在 Kubernetes 生态系统中一直是一个一流的存在。

CoreOS 也是一个很好的选择,因为它非常支持容器的使用。另一方面,我们未来可能需要的东西并不都能轻松获得。一些必要的软件包此时可能缺失,或者至少在容器外运行它们没有支持。

也就是说,可以随意使用你喜欢的任何 Linux 发行版。但请注意,根据你选择的操作系统,本指南中的某些部分可能会有很大不同。

安全性

在公共和私有接口上保护主机是绝对必要的。

这是个难题。几乎每一份指南都未能充分重视安全问题。最大的误解之一是认为私有网络是安全的,但私有并不等于安全。实际上,私有网络往往在同一数据中心的多个客户之间共享。并非所有提供商都是这种情况。通常建议完全确认私有网络的实际情况。

防火墙

security/ufw

虽然确实有人能够可靠地配置iptables,但一般人看到最基本规则的语法时都会感到头疼。幸运的是,还有更易于使用的解决方案。其中之一是UFW,简易防火墙——一个人性化的命令行界面,提供了管理复杂iptables规则的简单抽象。

假设安全的公共Kubernetes API运行在6443端口,SSH守护进程在22端口,以及80和443端口用于提供网页流量,则基本的UFW配置如下:

ufw allow ssh # sshd on port 22, be careful to not get locked out!
ufw allow 6443 # remote, secure Kubernetes API access
ufw allow 80
ufw allow 443
ufw default deny incoming # deny traffic on every other port, on any interface
ufw enable

在接下来的章节中,这个规则集将稍微扩展。

安全的私有网络

Kubernetes 集群成员会不断地相互交换数据。在主机之间建立安全的网络覆盖层不仅是最简单的解决方案,也是确保在与我们主机共享网络的第三方无法窃听其私有流量的最安全方案。保护每一个服务是一项繁琐的工作,因为这通常需要在主机间创建和分发证书、以某种方式管理密钥,以及最后但同样重要的配置服务以实际使用加密的通信方式。这就是为什么使用 VPN 设置网络覆盖层是解决这一问题的最佳方案——它本身只需一次性付出,很少需要专业知识,并且自然地确保所有当前和未来运行的服务之间的安全主机间通信。

谈到 VPN,通常有两类解决方案:

  • 传统的 VPN 服务,在用户空间运行,通常提供一个隧道接口
  • IPsec,作为内核的一部分,可以在任何现有接口上实现认证和加密

在用户空间运行的 VPN 软件通常对网络吞吐量有很大的负面影响,而相比之下,IPsec 则要快得多。不幸的是,理解后者的工作原理相当具有挑战性。strongSwan 无疑是较易上手的解决方案之一,但即便是满足最基本需求的设置仍然伴随着陡峭的学习曲线。

复杂性是安全的最大敌人。

一个叫做 WireGuard 的项目目前提供了两全其美的方案。作为内核模块运行,它不仅性能优越,而且设置极其简单,并且开箱即用提供隧道接口。关于在内核中运行 VPN 是否是一个好主意可能存在争议,但用户空间运行的替代方案,如 tinc 或 fastd 并不一定更安全。然而,它们的速度要慢一个数量级,并且通常更难配置。

WireGuard 设置

security/wireguard

让我们从安装 WireGuard 开始。请按照这里的说明操作:WireGuard Installation。

apt install wireguard

一旦安装了 WireGuard,就可以创建配置文件了。每个主机都应该连接到其对等节点,通过名为 wg0 的隧道接口创建一个安全的网络覆盖。假设该设置包含三个主机,每个主机将在 10.0.1.1/24 范围内获得一个新的 VPN IP 地址:

主机 私有 IP 地址 (ethN) VPN IP 地址 (wg0)
库贝1 10.8.23.93 10.0.1.1
kube2 10.8.23.94 10.0.1.2
kube3 10.8.23.95 10.0.1.3

请注意,Hetzner Cloud 不提供私有网络接口,但在公共接口上运行 WireGuard 完全没问题。只需确保使用公共 IP 地址和公共网络接口(eth0)。

在这种情况下,kube1 的配置文件如下所示:

# /etc/wireguard/wg0.conf
[Interface]
Address = 10.0.1.1
PrivateKey = <PRIVATE_KEY_KUBE1>
ListenPort = 51820

[Peer]
PublicKey = <PUBLIC_KEY_KUBE2>
AllowedIps = 10.0.1.2/32
Endpoint = 10.8.23.94:51820

[Peer]
PublicKey = <PUBLIC_KEY_KUBE3>
AllowedIps = 10.0.1.3/32
Endpoint = 10.8.23.95:51820

为了简化私钥和公钥的创建,可以使用以下命令来生成并打印所需的密钥对:

for i in 1 2 3; do
  private_key=$(wg genkey)
  public_key=$(echo $private_key | wg pubkey)
  echo "Host $i private key: $private_key"
  echo "Host $i public key:  $public_key"
done

在每个主机上创建一个名为 /etc/wireguard/wg0.conf 的文件,文件中包含正确的 IP 地址以及公钥和私钥后,配置基本完成。

剩下的工作是添加以下防火墙规则:

# open VPN port on private network interface (use eth0 on Hetzner Cloud)
ufw allow in on eth1 to any port 51820
# allow all traffic on VPN tunnel interface
ufw allow in on wg0
ufw reload

在启动 WireGuard 之前,我们需要确保启用了 IP 转发以及其他一些必要的网络设置:

echo br_netfilter > /etc/modules-load.d/kubernetes.conf
modprobe br_netfilter

echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
echo "net.bridge.bridge-nf-call-iptables=1" >> /etc/sysctl.conf

sysctl -p # apply settings from /etc/sysctl.conf

在每台主机上执行命令 systemctl start wg-quick@wg0 将启动 VPN 服务,如果一切配置正确,主机之间应能够建立连接。现在可以使用 VPN IP 地址(10.0.1.1–10.0.1.3)安全地路由流量。

为了检查连接是否成功建立,wg show 非常有用:

$ wg show
interface: wg0
  public key: 5xKk9...
  private key: (hidden)
  listening port: 51820

peer: HBCwy...
  endpoint: 10.8.23.199:51820
  allowed ips: 10.0.1.1/32
  latest handshake: 25 seconds ago
  transfer: 8.76 GiB received, 25.46 GiB sent

peer: KaRMh...
  endpoint: 10.8.47.93:51820
  allowed ips: 10.0.1.3/32
  latest handshake: 59 seconds ago
  transfer: 41.86 GiB received, 25.09 GiB sent

最后但同样重要的是,运行 systemctl enable wg-quick@wg0,以便在系统启动时启动该服务。

安装 Kubernetes

service/kubernetes

有很多方法可以从零开始设置 Kubernetes 集群。然而,在此阶段,我们选择 kubeadm。通过自动创建证书、服务和配置文件,这大大简化了设置过程。

在开始使用 Kubernetes 本身之前,我们需要先设置两个不属于实际堆栈的关键服务,即 containerd 和 etcd。过去我们使用的是 Docker,但现在更推荐使用 containerd。

Containerd 设置

containerd 是一个稳健的容器运行时。有关支持的版本提示,可参见 官方容器运行时指南。让我们安装一个受支持的 containerd 版本:

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmour -o /etc/apt/keyrings/docker.gpg
echo "deb [signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \
  > /etc/apt/sources.list.d/docker.list

apt-get install -y containerd.io=1.6.15-1 # Kubernetes 1.26 requires at least containerd v1.6

Kubernetes 建议使用 cgroup 驱动运行 containerd。这可以通过创建一个 containerd 配置文件并设置所需的配置标志来实现:

# write default containerd config
containerd config default > /etc/containerd/config.toml
# set systemd cgroup flag to true
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

# enable containerd and restart
systemctl enable containerd
systemctl restart containerd

Etcd 设置

service/etcd

etcd 是一个高可用的键值存储,Kubernetes 使用它来持久化存储所有的 REST API 对象。因此,它是集群中至关重要的一部分。kubeadm 通常会在单个节点上安装 etcd。根据可用主机的数量,如果不以集群模式运行 etcd,将显得相当愚蠢。如前所述,由于 etcd 仅从至少三节点开始具备容错能力,所以运行至少三节点的集群是有意义的。

尽管 etcd 通常可以通过大多数包管理器获取,但建议手动安装更新版本:

export ETCD_VERSION="v3.5.6"
mkdir -p /opt/etcd
curl -L https://storage.googleapis.com/etcd/${ETCD_VERSION}/etcd-${ETCD_VERSION}-linux-amd64.tar.gz \
  -o /opt/etcd-${ETCD_VERSION}-linux-amd64.tar.gz
tar xzvf /opt/etcd-${ETCD_VERSION}-linux-amd64.tar.gz -C /opt/etcd --strip-components=1

在不安全的环境中配置 etcd 通常涉及在各节点之间创建和分发证书,而在安全网络中运行则使这个过程容易得多。只要服务绑定到端到端安全的 VPN 接口,就完全不需要使用额外的安全层。

本节不会深入解释 etcd 配置,请参阅官方文档。所需要做的只是为每台主机创建一个 systemd 单元文件。假设有一个三节点集群,kube1 的配置如下所示:

# /etc/systemd/system/etcd.service
[Unit]
Description=etcd
After=network.target wg-quick@wg0.service

[Service]
Type=notify
ExecStart=/opt/etcd/etcd --name kube1 \
  --data-dir /var/lib/etcd \
  --listen-client-urls "http://10.0.1.1:2379,http://localhost:2379" \
  --advertise-client-urls "http://10.0.1.1:2379" \
  --listen-peer-urls "http://10.0.1.1:2380" \
  --initial-cluster "kube1=http://10.0.1.1:2380,kube2=http://10.0.1.2:2380,kube3=http://10.0.1.3:2380" \
  --initial-advertise-peer-urls "http://10.0.1.1:2380" \
  --heartbeat-interval 200 \
  --election-timeout 5000
Restart=always
RestartSec=5
TimeoutStartSec=0
StartLimitInterval=0

[Install]
WantedBy=multi-user.target

重要的是要理解,每个以 --initial 开头的标志仅在集群首次启动时适用。这意味着,例如,可以在任何时候添加和移除集群成员,而无需更改 --initial-cluster 的值。

在将文件放置在每个主机上后,就可以启动 etcd 集群了:

systemctl enable etcd.service # launch etcd during system boot
systemctl start etcd.service

执行 /opt/etcd/etcdctl member list 应显示集群成员列表。如果出现问题,请使用 journalctl -u etcd.service 检查日志。

Kubernetes 设置

现在 containerd 已配置且 etcd 正在运行,是时候部署 Kubernetes 了。第一步是在每台主机上安装所需的软件包:

# https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/#install-using-native-package-management
# (`xenial` is correct even for newer Ubuntu versions)
curl -fsSLo /etc/apt/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" \
  > /etc/apt/sources.list.d/kubernetes.list

apt-get update

apt-get install -y kubelet=1.26.0-00 kubeadm=1.26.0-00 kubectl=1.26.0-00 # kubernetes-cni package comes as dependency of the others
# Pin Kubernetes major version since there are breaking changes between releases.
# For example, Kubernetes 1.26 requires a newer containerd (https://kubernetes.io/blog/2022/11/18/upcoming-changes-in-kubernetes-1-26/#cri-api-removal).
apt-mark hold kubelet kubeadm kubectl kubernetes-cni

正在初始化主节点

在初始化主节点之前,我们需要在 kube1 上创建一个清单文件,该清单文件将在下一步中用作配置:

# /tmp/master-configuration.yml
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: 10.0.1.1
  bindPort: 6443
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
certificatesDir: /etc/kubernetes/pki
apiServer:
  certSANs:
  - <PUBLIC_IP_KUBE1>
etcd:
  external:
    endpoints:
      - http://10.0.1.1:2379
      - http://10.0.1.2:2379
      - http://10.0.1.3:2379
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
failSwapOn: false
cgroupDriver: systemd

然后我们在 kube1 上运行以下命令:

kubeadm init --config /tmp/master-configuration.yml --ignore-preflight-errors=Swap,NumCPU

安装完成后,kubeadm 会打印一个令牌,例如 818d5a.8b50eb5477ba4f40。重要的是要将其记下,我们稍后需要它来加入其他集群节点。

Kubernetes 建立在开放性基础上,因此由我们来选择和安装合适的 Pod 网络。这是必要的,因为它使运行在不同节点上的 Pod 能够相互通信。其中一个多种选项是 Cilium。它几乎不需要配置,并且被认为是稳定且维护良好的:

# create symlink for the current user in order to gain access to the API server with kubectl
[ -d $HOME/.kube ] || mkdir -p $HOME/.kube
ln -s /etc/kubernetes/admin.conf $HOME/.kube/config

# install Cilium
CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
CLI_ARCH="$(arch | sed 's/x86_64/amd64/; s/aarch64/arm64/')"
curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
sha256sum --check cilium-linux-${CLI_ARCH}.tar.gz.sha256sum
sudo tar xzvfC cilium-linux-${CLI_ARCH}.tar.gz /usr/local/bin
rm cilium-linux-${CLI_ARCH}.tar.gz*

cilium install --version 1.14.1 --set ipam.mode=cluster-pool --set ipam.operator.clusterPoolIPv4PodCIDRList=10.96.0.0/16

# allow traffic on the newly created Cilium network interface
ufw allow in on cilium_vxlan
ufw reload

由于流量会通过错误的网络接口路由,Cilium 在我们当前的集群配置下无法正常工作。可以通过在每个主机上运行以下命令来解决此问题:

ip route add 10.96.0.0/16 dev $VPN_INTERFACE src $VPN_IP

# on kube1:
ip route add 10.96.0.0/16 dev wg0 src 10.0.1.1
# on kube2:
ip route add 10.96.0.0/16 dev wg0 src 10.0.1.2
# on kube3:
ip route add 10.96.0.0/16 dev wg0 src 10.0.1.3

添加的路由在重启后不会保留,因为它不是持久的。为了确保路由在重启后被添加,我们必须在每个节点上添加一个 systemd 服务单元,该单元将等待 WireGuard 接口启动,然后添加路由。对于 kube1,它将如下所示:

# /etc/systemd/system/overlay-route.service
[Unit]
Description=Overlay network route for WireGuard
After=wg-quick@wg0.service

[Service]
Type=oneshot
User=root
ExecStart=/sbin/ip route add 10.96.0.0/16 dev wg0 src 10.0.1.1

[Install]
WantedBy=multi-user.target

之后我们必须通过运行以下命令来启用它:

systemctl enable overlay-route.service

最后,我们可以检查一切是否正常工作:

cilium status --wait

加入集群节点

剩下的就是将集群与其他节点连接。请在每个主机上运行以下命令:

kubeadm join --token="<TOKEN>" 10.0.1.1:6443 \
  --discovery-token-unsafe-skip-ca-verification \
  --ignore-preflight-errors=Swap

就是这样,一个 Kubernetes 集群已经可以供我们使用了。

访问与操作

service/kubernetes

一旦集群运行,我们希望能够远程访问 Kubernetes API。这可以通过将 /etc/kubernetes/admin.conf 从 kube1 复制到你自己的机器上来实现。在本地 安装 kubectl 后,执行以下命令:

# create local config folder
mkdir -p ~/.kube
# backup old config if required
[ -f ~/.kube/config ] && cp ~/.kube/config ~/.kube/config.backup
# copy config from master node
scp root@<PUBLIC_IP_KUBE1>:/etc/kubernetes/admin.conf ~/.kube/config
# change config to use correct IP address
kubectl config set-cluster kubernetes --server=https://<PUBLIC_IP_KUBE1>:6443

你现在可以远程访问 Kubernetes API。运行 kubectl get nodes 应该会显示类似这样的节点列表:

NAME    STATUS   ROLES                  AGE     VERSION
kube1   Ready    control-plane,master   5h11m   v1.21.1
kube2   Ready    <none>                 5h11m   v1.21.1
kube3   Ready    <none>                 5h11m   v1.21.1

基于角色的访问控制

从版本 1.6 开始,kubeadm 配置 Kubernetes 时已启用 RBAC。由于我们的业余集群通常由可信赖的人操作,我们应该启用宽松的 RBAC 权限,以便能够使用任何类型的资源部署任何类型的服务。如果您对这是否足够安全以适用于您的用例有疑问,请参阅官方的 RBAC 文档 。

kubectl create clusterrolebinding permissive-binding \
  --clusterrole=cluster-admin \
  --user=admin \
  --user=kubelet \
  --group=system:serviceaccounts

部署服务

现在可以通过调用 kubectl -f apply <FILE> 来远程部署服务。也可以通过指向一个文件夹来应用多个文件,例如:

$ ls dashboard/
deployment.yml  service.yml

$ kubectl apply -f dashboard/
deployment "kubernetes-dashboard" created
service "kubernetes-dashboard" created

本指南在这方面将不作进一步说明。请参考kubernetes.io上的官方文档。

将流量引入集群

在集成良好的平台如AWS或GCE之外运行Kubernetes存在缺点。其中之一是缺乏外部入口和负载均衡解决方案。幸运的是,在集群内运行一个由NGINX驱动的入口控制器相当容易,这将使服务能够注册接收公共流量。

入口控制器设置

由于大多数云服务提供商都没有负载均衡器,我们必须确保 NGINX 服务器始终运行在同一台主机上,通过不变的 IP 地址访问。由于主节点目前基本处于空闲状态,且不会有普通 pod 在上面调度,我们把 kube1 作为专用主机负责公共流量路由。

我们在初始防火墙配置时已经打开了80号和443号端口,现在只需编写几个清单来部署 NGINX 入口控制器到 kube1:

其中一部分需要特别关注。为了确保NGINX运行在kube1上——这是一个受污染的控制平面节点,通常不会在上面调度任何舱体——我们需要指定一个容忍度:

# from ingress/deployment.yml
tolerations:
- key: node-role.kubernetes.io/control-plane
  operator: Equal
  effect: NoSchedule

指定容忍不会确保 Pod 被调度到任何特定节点。为此,我们需要添加节点亲和性规则。由于我们只有一个控制平面节点,以下规范足以将 Pod 调度到 kube1 上:

# from ingress/deployment.yml
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: node-role.kubernetes.io/control-plane
          operator: Exists

运行 kubectl apply -f ingress/ 将会应用该文件夹中的所有清单文件。首先,会创建一个名为 ingress 的命名空间,然后部署 NGINX,以及一个用于为未定义的域名和路由提供 404 页面的默认后端,包括必要的服务对象。不需要为 NGINX 本身定义服务对象,因为我们配置它使用主机网络(hostNetwork: true),这意味着容器绑定到主机上的实际端口,而不是绑定到 Pod 覆盖网络中的某个虚拟接口。

服务现在可以利用 ingress 控制器,并通过一个简单的清单接收公共流量:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
spec:
  ingressClassName: "nginx"
  rules:
  - host: service.example.com
    http:
      paths:
      - path: /
        backend:
          serviceName: example-service
          servicePort: http

NGINX Ingress 控制器非常灵活,并支持大量的配置选项。

DNS 记录

dns/cloudflare dns/google dns/aws dns/digitalocean

此时我们可以使用域名并设置一些 DNS 条目。要提供网页流量,只需创建一个 A 记录指向 kube1 的公共 IP 地址,并创建一个通配符条目以便能够使用子域名:

类型 名称 值
A example.com
CNAME *.example.com example.com

一旦 DNS 记录传播开来,我们的示例服务将可以通过 http://service.example.com 访问。如果你手头没有域名,也可以将条目添加到你的 hosts 文件中。

此外,为每个主机分配子域名可能是个好主意,例如 kube1.example.com。使用域名而不是 IP 地址进行 ssh 登录会更方便。

获取 SSL/TLS 证书

得益于 Let’s Encrypt 和一个名为 cert-manager 的项目,为指向我们 Kubernetes 集群的任何域名获取免费证书变得非常容易。设置这个服务所需时间很短,并且可以很好地与我们之前部署的 NGINX ingress 控制器配合使用。以下是相关的清单:

在使用上述清单部署 cert-manager 之前,请确保将 ingress/tls/cert-issuer.yml 中的电子邮件地址替换为你自己的。

要为服务启用证书,需要稍微扩展 ingress 清单:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt" # enable certificates
spec:
  ingressClassName: "nginx"
  tls: # specify domains to fetch certificates for
  - hosts:
    - service.example.com
    secretName: example-service-tls
  rules:
  - host: service.example.com
    http:
      paths:
      - path: /
        backend:
          serviceName: example-service
          servicePort: http

应用此清单后,cert-manager 将尝试为 service.example.com 获取证书,并重新加载 NGINX 配置以启用 TLS。如果出现问题,请务必检查 cert-manager Pod 的日志。

启用 TLS 后,NGINX 会自动将客户端重定向到 HTTPS。如果你仍希望通过 HTTP 提供流量,请将 nginx.ingress.kubernetes.io/ssl-redirect: "false" 添加到注解列表中。

部署 Kubernetes 仪表板

现在一切就绪,我们可以在特定域上公开服务并自动获取证书。让我们通过以下清单部署 Kubernetes Dashboard 来尝试:

可选地,以下清单可用于借助 metrics-server 在 Dashboard 中获取资源使用图表:

这里的新内容是,我们启用了基本身份验证以限制对 Dashboard 的访问。以下注解由 NGINX Ingress 控制器支持,可能在其他解决方案中无法使用:

# from dashboard/ingress.yml
annotations:
  # ...
  nginx.ingress.kubernetes.io/auth-type: basic
  nginx.ingress.kubernetes.io/auth-secret: kubernetes-dashboard-auth
  nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"

# dashboard/secret.yml
apiVersion: v1
kind: Secret
metadata:
  name: kubernetes-dashboard-auth
  namespace: kube-system
data:
  auth: YWRtaW46JGFwcjEkV3hBNGpmQmkkTHYubS9PdzV5Y1RFMXMxMWNMYmJpLw==
type: Opaque

此示例将在访问仪表板时提示访问者输入其凭据(用户:admin / 密码:test)。可以使用 htpasswd 创建基本身份验证的密钥,并需要将其作为 base64 编码的字符串添加到清单中。

分布式块存储

容器中的数据是短暂的,一旦Pod被停止、崩溃,或因某种原因重新调度到其他节点运行,所有数据都会消失。这对静态应用如Kubernetes Dashboard(其数据来自容器外的持久源)来说没问题,但一旦我们在集群上部署数据库,持久化数据就成为非可选要求。

Kubernetes 支持各种可以附加到 Pod 的卷类型。其中只有少数几种符合我们的要求。有一种 hostPath 类型,它只是将主机上的路径映射到容器内的路径,但这行不通,因为我们不知道 Pod 会被调度到哪个节点。

持久卷

Kubernetes 内部有一个概念,就是如何将存储管理与集群管理区分开来。为了围绕存储提供一层抽象,Kubernetes 中有两种深度集成的资源类型:PersistentVolume 和 PersistentVolumeClaim。在 GCE、AWS 或 Azure 等高度集成的平台上运行时,通过创建持久卷声明,很容易将持久卷附加到一个 pod 上。遗憾的是,我们无法获得这样的解决方案。

我们的集群由多个节点组成,我们需要能够将持久卷连接到任何运行在任何节点上的Pod上。有几个项目和公司围绕提供超融合存储解决方案的理念涌现。他们的一些服务是以Pod形式运行在Kubernetes内部,这无疑是管理像我们这样小型集群存储的完美方式。

选择解决方案

目前有几个符合我们标准的有趣解决方案,但它们都有缺点:

  • Rook.io 是一个基于 Ceph 的开源项目。尽管它还处于早期阶段,但提供了良好的文档,并且相当灵活。
  • gluster-kubernetes 是一个围绕 GlusterFS 和 Heketi 构建的开源项目。目前的设置似乎比较繁琐,需要以 JSON 格式提供某种模式。
  • Portworx 是一个商业项目,提供了他们专有软件的免费版本,并提供了出色的文档和工具。

Rook 和 Portworx 都以简单的设置和透明操作表现出色。Rook 是我们的首选,因为它提供更多灵活性,且开源,而 Portworx 虽然后者在简单性上胜出,每个实例只启动一个 pod。

部署车

由于我们只运行三节点集群,我们将在三个节点上部署 Rook,通过在 Rook 集群定义中添加控制平面容忍。

在部署 Rook 之前,我们需要提供一个原始的、未格式化的块设备,或指定一个将在每个主机上用于存储的目录。在典型的 Ubuntu 安装中,操作系统所在的卷称为 /dev/vda。附加的其他卷将作为 /dev/vdb 可用。

确保按照下面所示编辑集群清单,并根据您希望在环境中使用目录还是块设备进行存储来选择正确的配置:

# storage/cluster.yml
  # ...
  storage:
    useAllNodes: true
    useAllDevices: false
    storeConfig:
      databaseSizeMB: 1024
      journalSizeMB: 1024
    # Uncomment the following line and replace it with the name of block device used for storage:
    #deviceFilter: vdb
    # Uncomment the following lines when using a directory for storage:
    #directories:
    #- path: /storage/data

如前所述,Rook 在底层使用 Ceph。在每台主机上运行 apt-get install ceph-common 来安装 Ceph 通用工具。随后,按以下顺序应用存储清单:

值得一提的是,storageclass 清单包含副本因子的配置:

# storage/storageclass.yml
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: replicapool
  namespace: rook
spec:
  failureDomain: host
  replicated:
    size: 2 # replication factor
---
# ...

为了操作存储集群,只需在 Rook 工具 Pod 中运行命令,例如:

# show ceph status
kubectl -n rook exec -it rook-tools -- ceph status

# show volumes
kubectl -n rook exec -it rook-tools -- rbd list replicapool

# show volume information
kubectl -n rook exec -it rook-tools -- rbd info replicapool/<volume>

更多命令请参见Rook 工具文档。

使用存储

我们创建的存储类可以通过持久卷声明来使用:

# minio/pvc.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: minio-persistent-storage
spec:
  storageClassName: rook-block
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi

这将创建一个名为 minio-persistent-storage 的卷,存储容量为 5GB。

在此示例中,我们正在部署 Minio,一个兼容 Amazon S3 的对象存储服务器,以创建并挂载持久卷:

与卷相关的配置隐藏在部署清单中:

# from minio/deployment.yml
containers:
- name: minio
  volumeMounts:
  - name: data
    mountPath: /data
# ...
volumes:
- name: data
  persistentVolumeClaim:
    claimName: minio-persistent-storage

minio-persistent-storage 卷将一直存在,直到持久卷声明被删除(例如 kubectl delete -f minio/pvc.yml)。Minio Pod 本身可以被删除、更新或重新调度而不会丢失数据。

接下来的方向

希望这只是你旅程的开始。围绕 Kubernetes 还有许多东西可以探索。你可以随时通过在这里打开问题来留下反馈或提出问题。

本站来源与版权声明
  • 本文标题:guide - 面向爱好者的 Kubernetes 集群
  • 本文链接:https://www.cn121.com/automation/hobby-kube-guide.html
  • 原项目:hobby-kube/guide 版权归原作者 hobby-kube 及贡献者所有
  • 收录信息:本站于 2026-10-28 收录本项目,本页所列协议与仓库指标均为收录当时的状态;该日期之后原项目的版本更新与协议变更,本页不作同步。
  • 开源协议:收录时本项目采用 MIT(查看 LICENSE 原文),本站转载其原始文档(未改动文字,仅剔除图片与无关章节);使用、修改、分发请以该仓库 LICENSE 原文为准。
  • 站点出处:本文首发于 OneTwoOne,收录自 GitHub 开源项目 hobby-kube/guide。
  • 内容说明:本页正文为原项目 README 原文(英文),本站后续会翻译为中文(当前尚未译出,仅将二级标题译为中文,便于按栏目定位;标题原文可在下方原仓库中查看),仅剔除了图片与赞助等无关章节、并把相对链接改为绝对地址;页首简介为机器翻译自仓库描述。
  • 引用声明:商业转载、第三方聚合或 AI 检索训练引用时,请务必保留以上来源出处、本文永久链接,以及原项目的版权声明与许可信息。
  • 下架通道:若原项目此后变更或收紧了许可协议、或作者/权利人认为本站的收录方式(译文、排版适配、简介翻译等)超出其授权范围,请通过 xyd3302001@163.com 发送下架通知,并附上项目地址与本页链接。本站核实后将第一时间删除本页内容,或改为不复制原文的目录性收录;署名更正等其他要求可一并提出。