Kubernates 教程
本文从零开始搭建一个 Kubernates 集群,并在集群上部署一个带数据存储、HTTPS 域名访问的 博客站点。搭建完这套系统之后,我们再部署其他应用就会变得轻而易举了。
先附上教程整体大纲:
提前准备
国内技术领域 80% 的问题都源于网络,因此我们需要准备一台海外云主机,这直接省去了各种资源下载失败、域名备案等一些列烦恼。具体资源情况:
- 海外云主机,推荐香港
- 数量:至少 1 台,推荐 3 台
- 配置:至少 2C4G,推荐 4C8G
对于每个节点机器,需要保证端口可用,比如配置好云厂商网络安全以及机器的防火墙:
集群搭建
官方 kubeadm 方案
我们可以参照官方 使用 kubeadm 创建集群 文档搭建一个全功能的生产集群。不过这个方案流程复杂,要想快速开始,可以选下面推荐的方案。
K3s 方案(推荐)
K3s 是一个完全兼容的 Kubernates 发行版,会打包所有组件到一个二进制文件中,减少资源占用和安装维护复杂度。一条命令实现安装当初直接惊艳到我了,好产品就应该做到如此啊。
# master 节点安装
curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.31.6+k3s1 sh -
# worker 节点安装
curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.31.6+k3s1 K3S_URL=https://myserver:6443 K3S_TOKEN=mynodetoken sh -
# master 节点卸载
/usr/local/bin/k3s-uninstall.sh
# worker 节点卸载
/usr/local/bin/k3s-agent-uninstall.sh
- 上面 master 就是主节点,如果你有多台机器,这个就是以后管理 K8s 集群的那台
- worker 节点的 K3S_URL 配置的 myserver 可以直接用 master 内网 IP,K3S_TOKEN 位于 master 机器
/var/lib/rancher/k3s/server/node-token
(可选) 配置不需要 sudo 访问
# 配置不需要 sudo 使用
mkdir -p $HOME/.kube
sudo cp -i /etc/rancher/k3s/k3s.yaml $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
echo 'export KUBECONFIG=$HOME/.kube/config' >> ~/.bashrc
source ~/.bashrc
# 在不加载 .bashrc 的环境还需要
sudo chmod +r /etc/rancher/k3s/k3s.yaml
网络处理
网络请求
首先我们来捋一下用户完整的请求流程:
用户 → 域名 → DNS → 节点IP:80/443 → Traefik Ingress → 后端服务
针对上面的过程,我们需要做的有:
- 域名 → DNS: 在域名厂商那里添加解析,建议直接用泛解析,以后新应用直接用子域名访问就可以了:
*.mydomain.com -> myserver-ip
- DNS → 节点IP:80/443: 集群安装时已经自动完成了这两个端口的监听,如果云主机有防火墙记得打开这两个端口的外网访问
- Traefik Ingress → 后端服务: Traefik 也是集群自带的组件,我们这里要做的就是最后部署应用时配置好对应的 ingress 就行,见后面部署章节
如果自带的 Traefik 网关功能不够,可以考虑使用 APISIX 等第三方网关工具。
这里使用 443 即 HTTPS 的话(浏览器默认行为),需要配置域名 SSL 证书,具体见下方说明。
SSL 全自动
我们以后会在 Kubernates 集群部署多个应用,对应多个域名,如果像传统 Nginx 方式一样每个域名去申请 SSL 证书再挨个配置,就太麻烦了。因此需要全自动完成整个 HTTPS 访问过程,包括未来新增的域名。
安装 cert manager:
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.1/cert-manager.yaml # 验证安装 kubectl get pods -n cert-manager
安装 ClusterIssuer:
bash# cluster-issuer.yaml apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-prod spec: acme: email: your-email@example.com # 替换为你的邮箱 server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: name: letsencrypt-prod solvers: - http01: ingress: class: traefik # 指定使用的 Ingress 控制器类型
kubectl apply -f cluster-issuer.yaml # 验证 kubectl describe clusterissuer letsencrypt-prod
以上就是初始化需要做的,后面对于每个要部署的应用,直接添加一个 ingress 配置指定好对应域名和路由规则,即可完成整个请求的 SSL 证书申请和访问。
下面给出个使用了上述组件的示例 ingress,这个放到应用部署中来做就行,这里仅展示他们的关联:
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: https-redirect
namespace: myapp
spec:
redirectScheme:
scheme: https
permanent: true
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: myapp
name: nginx-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod # 引用 ClusterIssuer,与上方对应
traefik.ingress.kubernetes.io/router.middlewares: myapp-https-redirect@kubernetescrd
spec:
ingressClassName: traefik
tls:
- hosts:
- myapp.mydomain.com # 请替换你应用的域名
secretName: tls-secret
rules:
- host: myapp.mydomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myservice
port:
number: 80
防火墙问题
创建集群时,会添加 iptables 规则,如果一开始没有指定 bind_address,后面变更的话,会导致 iptables 规则不对
# 在另一个节点执行端口访问,确定网络不通
ubuntu@amd1:~telnet 10.0.0.106 6443
Trying 10.0.0.106...
telnet: Unable to connect to remote host: No route to host
# 清除 iptables 规则
sudo iptables -L
sudo iptables -F # 清空所有规则链 :ml-citation{ref="1" data="citationList"}
sudo iptables -X # 删除自定义链 :ml-citation{ref="1" data="citationList"}
sudo iptables -P INPUT ACCEPT # 设置默认策略为允许所有入站流量 :ml-citation{ref="1" data="citationList"}
sudo iptables -P FORWARD ACCEPT
sudo iptables -P OUTPUT ACCEPT
# 确保没有规则
sudo iptables -L
# 如果云厂商有网络安全配置,你可以选择彻底卸载 iptables。但是要注意必须先清除完规则再卸载,直接卸载当前不会生效
sudo apt remove -y iptables
密钥处理
Kubernates 的密钥是敏感对象,直接在代码仓库中存储不安全,直接在服务器中处理的话会造成部署不便,无法使用 GitOps 的便利。
这里我们可以引入一个非对称加密流程,这样就可以所有加密后的密钥对象直接存储在代码仓库,部署后服务器上会自动通过私钥解密出真实密钥对象。这个流程当前已经有开源库 bitnami-labs/sealed-secrets 实现了。
服务安装
- 在服务器安装服务端:bash
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.28.0/controller.yaml # 存储公钥和私钥,开发环境后面需要 kubectl get secret -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key -o yaml echo -n '$tls.crt' | base64 --decode > public.crt # 存储到代码仓库中 echo -n '$tls.key' | base64 --decode > private.key # 管理员单独保存,记得添加 .gitignore 防止不小心公开
- 在开发环境安装客户端:bash
# macOS 系统 brew install kubeseal # Linux 系统 KUBESEAL_VERSION='0.28.0' # Set this to, for example, KUBESEAL_VERSION='0.23.0' curl -OL "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION:?}/kubeseal-${KUBESEAL_VERSION:?}-linux-arm64.tar.gz" tar -xvzf kubeseal-${KUBESEAL_VERSION:?}-linux-arm64.tar.gz kubeseal sudo install -m 755 kubeseal /usr/local/bin/kubeseal
密钥新建
- 对密文进行 base64 编码bash
echo -n 'mypassword' | base64 -w0 # -w0 保留原始文本不自动换行
- 创建普通 secret 对象
cat <<EOF >./app-secret.yaml apiVersion: v1 kind: Secret metadata: name: mysecret namespace: myapp type: Opaque data: password: bXlwYXNzd29yZA== EOF
- 加密
kubeseal --format=yaml --cert=public.crt < app-secret.yaml > app-sealed-secret.yaml
密钥更新
注意:更新需要解密当前集群的密钥,只有有私钥的管理员能操作。
- 解密:
kubeseal --format=yaml --recovery-unseal --recovery-private-key private.key -f app-sealed-secret.yaml > app-secret.yaml
- 编辑更新原始密钥
- 重新加密保存
kubeseal --format=yaml --cert=public.crt < app-secret.yaml > app-sealed-secret.yaml
解密问题
我们可能会遇到应用 sealedsecret 之后,没有创建对应 secret,controller 日志报错:
E0303 09:32:40.318645 1 controller.go:282] "Unhandled Error" err="no key could decrypt secret (password)" logger="UnhandledError"
time=2025-03-03T09:32:40.318Z level=INFO msg="Event(v1.ObjectReference{Kind:\"SealedSecret\", Namespace:\"wordpress\", Name:\"mysql-pass\", UID:\"854083f1-fda7-4c9e-8ca7-2423563ad402\", APIVersion:\"bitnami.com/v1alpha1\", ResourceVersion:\"2444\", FieldPath:\"\"}): type: 'Warning' reason: 'ErrUnsealFailed' Failed to unseal: no key could decrypt secret (password)"
原因可能是用了 kustomization,kustomization 会修改资源 namespace,但是 kubeseal 使用的是默认空间加密,最后会导致在 kustomization 命名空间解密失败。
解决的话统一在 secret 文件中显示指定命名空间与 kustomization 里配置的一致。
应用部署
我们部署的应用是一个外网可访问的 WordPress 网站,完整配置文件见 songhuangcn/kubernates-tutorial。
部署配置
大致配置如下:
├── .gitignore
├── Makefile
├── config
│ ├── sealed-secret.crt
│ └── sealed-secret.key # 不存在 git 中
└── deploy
├── app-sealed-secret.yaml
├── app-secret.yaml # 不存在 git 中
├── ingress.yaml
├── kustomization.yaml
├── mysql.yaml
└── wordpress.yaml
secret 对象配置见上面密钥处理章节, mysql 和 wordpress 对象配置可以直接参考官方 示例:使用持久卷部署 WordPress 和 MySQL
ingress 对象中的 Middleware 是为了实现 HTTP -> HTTPS 跳转,其中域名换成你应用的即可,其他配置项都保持跟网络搭建章节里的一致。
部署流程就是更新配置到服务器中,然后在服务器上执行:
kubectl -n myapp apply -k deploy
如果你需要更强大的部署功能,可以考虑 Argo CD
部署维护
以下是我常用的一些维护命令。
设置默认命名空间
# 创建应用命名空间
kubectl create namespace myapp
# 查看当前上下文
kubectl config get-contexts
# 设置默认命名空间
kubectl config set-context --current --namespace=<target-namespace>
# 验证配置
kubectl config view | grep namespace
获取密钥原文
# 需要开启自动解密
kubectl get secret <secret-name> -n <namespace> -o yaml
echo -n "<base64-encoded-value>" | base64 --decode
临时停用服务
kubectl scale deployment --all --replicas=0
kubectl scale deployment/mysql --replicas=0
使用私有镜像
如果你应用的镜像是私有的,比如 GitLab Registry,需要为服务添加一下授权:
- 为项目创建一个部署令牌:项目 > 设置 > 仓库 > 部署令牌,权限勾选
read_repository
- 将令牌配置到密钥中bash
# 生成镜像密钥配置 kubectl -n myapp create secret docker-registry docker-secret --dry-run=client -o yaml --docker-server=registry.gitlab.com --docker-username=xxx --docker-password=xxx --docker-email=xxx > deploy/docker-secret.yaml # 加密密钥 kubeseal --format=yaml --cert=public.crt < docker-secret.yaml > docker-sealed-secret.yaml
- 让服务使用令牌拉取镜像yaml
# 在 deployment 对象的 containers 同层级添加 imagePullSecrets: - name: docker-secret
总结
Kubernates 等云原生技术不仅大大提升了软件应用的可靠性、移植性和伸缩性,能让企业的软件服务变得更经济高效。对应用部署而言,Kubernates 的声明式部署也让部署变得更简单和可靠,过去命令式的部署(deploy.sh)复杂难以维护,而现在只需要定义好服务的需求和状态,然后 Kubernates 就会处理好一切。
不过,Kubernates 门槛较高,这是网上流行的使用 K8s 部署博客的梗图:
但如果你不止一个博客应用,在大货车上堆满各种集装箱,这时他带来的维护和部署上的便利也是很有意义的。