K8S部署流程的三层解构:从环境准备到应用交付

📅 2026/7/16 17:03:48 👁️ 阅读次数 📝 编程学习
K8S部署流程的三层解构:从环境准备到应用交付

1. 项目概述:K8S 部署流程到底在部署什么?

“K8S 部署流程”这六个字,是运维、开发、SRE 和云原生初学者每天刷屏看到的高频词,但很多人卡在第一步——它到底指哪一段操作?不是单纯装几个二进制文件,也不是点几下控制台就算完事。我带过二十多个从零搭建生产级 K8S 集群的团队,发现90%的人在“部署流程”上栽跟头,根本原因在于混淆了三个完全不同的层次:环境准备层、集群构建层、应用交付层。你查到的“ubuntu24安装k8s教程”,通常只覆盖第一层;而“sealos部署k8s”或“railway部署”属于第二层的封装工具;至于“dify本地部署”“ragflow知识库搭建全流程”“k8s部署mysql”,全都是第三层——也就是真正让业务跑起来的落地环节。这三层环环相扣,漏掉任何一环,后续所有操作都会变成空中楼阁。

举个最典型的例子:你在 Ubuntu 24.04 上用 kubeadm 成功 init 出 master 节点,kubectl get nodes显示 Ready,你以为部署完成了?错。这时候连一个 Pod 都可能起不来——因为 CNI 插件没配,Pod CIDR 和节点网段冲突;或者 kube-proxy 没启用 IPVS 模式,Service ClusterIP 在高并发下开始丢包;更常见的是,你压根没配置 containerd 的镜像仓库认证,拉取私有 registry 里的 dify 镜像时卡在ImagePullBackOff,翻遍日志只看到failed to resolve reference,却不知道问题出在/etc/containerd/config.toml里少加了auths段。这些都不是“不会装 K8S”,而是对“K8S 部署流程”的认知断层。

所以这篇内容不讲“怎么下载 kubectl”,也不罗列 10 种安装脚本。我要带你把整个流程拆成可验证、可回滚、可审计的原子动作:从物理机 BIOS 设置里关闭 swap 的那一刻开始,到你用kubectl port-forward svc/dify-web 8080:80在本地浏览器打开 dify 界面为止。中间每一步,我都告诉你为什么必须这么做、不做会触发什么具体故障、如何用一条命令快速验证是否生效。比如,为什么swapoff -a后还要注释/etc/fstab里的 swap 行?因为系统重启后 systemd 会自动挂载 swap,导致 kubelet 直接 panic —— 这个细节在绝大多数“k8s安装部署步骤”教程里都被跳过了。再比如,“k8s和docker的关系与区别”这种泛泛而谈的问题,放到部署流程里就变成硬核选择题:你现在用的是 containerd 还是 dockershim?如果是 Ubuntu 24.04,默认内核 6.8,containerd 1.7+ 必须开启systemd_cgroup = true,否则 cgroup v2 下 CPU 限流完全失效,你的大模型推理服务会莫名其妙被 OOM kill。这些不是理论,是我在金融客户集群里连续三天抓包、比对 cgroup.procs 文件、最终定位到的血泪教训。

如果你正面临这些场景:刚买好三台阿里云 ECS 准备搭测试集群,但卡在kubeadm join失败;想把本地跑通的 ragflow 知识库一键推到 K8S,却搞不清 ConfigMap 和 Secret 怎么拆分;或者面试官问“k8s部署mysql怎么保证数据不丢”,你只能答出“用 PV/PVC”——那这篇就是为你写的。它不假设你懂 etcd 原理,但要求你愿意敲命令、看日志、改配置。接下来的内容,每一行都能直接粘贴进终端执行,每一个参数都有真实压测数据支撑,每一个“注意”背后都对应一个曾经导致线上服务中断两小时的事故。

2. 部署流程的三层解构:为什么不能只学“安装”

2.1 环境准备层:被99%教程忽略的底层地基

K8S 不是独立运行的软件,它是寄生在 Linux 内核之上的调度框架。所谓“部署流程”的起点,从来不在kubeadm init命令,而在你第一次登录服务器执行lsmod | grep br_netfilter的那一刻。这一层的目标只有一个:让 Linux 内核准备好接纳 K8S 的所有网络、存储、安全能力。但现实是,Ubuntu 24.04、CentOS Stream 9、Rocky Linux 9 这些主流发行版,出厂设置几乎全部与 K8S 要求背道而驰。

先说最致命的swap 问题。网上所有教程都写swapoff -a,但没人告诉你:swapoff -a只是临时禁用,系统重启后/etc/fstab里带swap标签的行会被 systemd 自动挂载。一旦 kubelet 启动时检测到 swap 启用,它会直接 panic 并退出,日志里只有一行FATAL: The 'swap' partition is enabled. Kubernetes does not support swap.。这不是警告,是硬性终止。解决方案必须两步走:

  1. 执行sudo swapoff -a清除当前 swap;
  2. 执行sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab注释 fstab 中所有 swap 行。

提示:别用vi /etc/fstab手动编辑,容易误删其他关键挂载项。这条 sed 命令精准匹配含空格包围的 "swap" 字符串,实测在 Ubuntu 24.04 和 Rocky 9 上 100% 安全。

再看内核模块加载。K8S 网络依赖br_netfilter(桥接 netfilter)、ip_vs(IPVS 负载均衡)、nf_conntrack(连接跟踪)。Ubuntu 24.04 默认不加载ip_vs,导致 kube-proxy 无法启用 IPVS 模式。验证方法很简单:lsmod | grep -E "br_netfilter|ip_vs|nf_conntrack"。如果ip_vs缺失,执行:

sudo modprobe ip_vs sudo modprobe ip_vs_rr sudo modprobe ip_vs_wrr sudo modprobe ip_vs_sh echo "ip_vs" | sudo tee -a /etc/modules echo "ip_vs_rr" | sudo tee -a /etc/modules echo "ip_vs_wrr" | sudo tee -a /etc/modules echo "ip_vs_sh" | sudo tee -a /etc/modules

注意:ip_vs_sh(源哈希)在 K8S Service SessionAffinity 场景中至关重要,但很多教程只加载ip_vs_rr。如果你的应用需要用户会话保持(比如某些老 ERP 系统),漏掉这行会导致负载不均甚至 503 错误。

最后是iptables 规则链兼容性。K8S 1.28+ 强制要求iptables-legacyiptables-nft一致。Ubuntu 24.04 默认使用iptables-nft,但部分 CNI 插件(如 Flannel 0.22.3)仍依赖iptables-legacy。验证命令:sudo update-alternatives --query iptables。如果显示iptables-nft为优先项,必须切换:

sudo update-alternatives --set iptables /usr/sbin/iptables-legacy sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy

这个操作影响全局防火墙,但 K8S 集群启动前必须完成。我见过客户因未切换,在kubeadm initkube-proxy日志疯狂报Failed to ensure chain: Failed to execute iptables-restore,排查三天才发现是 iptables 版本不匹配。

2.2 集群构建层:kubeadm 不是黑盒,是可调试的流水线

很多人把kubeadm init当作魔法命令,输入就完事。实际上,kubeadm 是一个高度可配置的集群构建引擎,它的每个参数都对应一个真实的系统状态。理解这些参数,等于掌握了集群的“出生证明”。

先看最核心的--pod-network-cidr。这个值不是随便填的。它必须满足两个硬性条件:

  1. 不能与宿主机所在网段重叠(比如你的服务器是 192.168.1.100/24,Pod 网段就不能设为 192.168.1.0/16);
  2. 必须与你选用的 CNI 插件默认配置一致。Flannel 默认用10.244.0.0/16,Calico 默认用192.168.0.0/16,而 Cilium 默认用10.0.0.0/8。填错的后果极其隐蔽:kubectl get pods -A显示所有 CoreDNS Pod 都是ContainerCreatingkubectl describe pod coredns-xxx却只显示NetworkPluginNotReady,死活找不到具体错误。这是因为 kubelet 等待 CNI 插件返回网络就绪信号,而插件因 CIDR 冲突拒绝初始化。

再看--cri-socket。Ubuntu 24.04 默认安装 containerd,但 socket 路径是/run/containerd/containerd.sock,而 kubeadm 1.28 默认查找/var/run/containerd/containerd.sock。如果不显式指定,kubeadm init会静默 fallback 到 docker(如果已安装),导致后续所有容器运行时行为异常。正确做法是:

sudo kubeadm init \ --pod-network-cidr=10.244.0.0/16 \ --cri-socket=/run/containerd/containerd.sock \ --kubernetes-version=v1.28.11

实操心得:永远显式指定--kubernetes-version。K8S 补丁版本(如 1.28.10 → 1.28.11)可能包含关键 CVE 修复(如 CVE-2024-21626 containerd 漏洞),跳过版本号会让 kubeadm 自动拉取最新 minor 版,可能引入不兼容变更。

kubeadm init执行后生成的admin.conf是集群的“命脉”。它包含 CA 证书、API Server 地址、token 等敏感信息。但很多人直接cp -i /etc/kubernetes/admin.conf $HOME/.kube/config就完事。这里有个致命陷阱:admin.conf中的server:字段默认是https://127.0.0.1:6443,这是 localhost 地址。当你在远程机器上执行kubectl时,它会尝试连接本地 6443 端口,必然失败。必须手动修改:

sed -i "s#https://127.0.0.1:6443#https://$(hostname -I | awk '{print $1}'):6443#g" $HOME/.kube/config

这条命令用服务器实际 IP 替换 localhost,确保远程访问有效。我帮某电商客户排查时,发现他们所有运维人员的kubectl都连不上集群,根源就是这个配置没改,大家一直用ssh登录 master 节点本地操作。

2.3 应用交付层:从 YAML 到可用服务的完整闭环

集群建好只是开始。真正的“部署流程”高潮在这一层:如何把一个业务应用(比如 dify、ragflow、mysql)变成 K8S 中稳定运行的服务。这里没有银弹,只有精确的资源编排。

以 “k8s部署mysql” 为例。新手常犯的错误是直接kubectl run mysql --image=mysql:8.0,然后发现 Pod 一直 Pending。为什么?因为 MySQL 需要持久化存储,而kubectl run创建的是无状态 Pod,没有关联 PVC。正确的流程必须包含四个不可省略的 YAML 文件:

  1. Secret:存储 root 密码,避免明文写在 Deployment 里;
  2. PersistentVolumeClaim (PVC):声明 20Gi 存储空间,绑定到 NFS 或本地路径;
  3. Service:定义 ClusterIP 类型,暴露 3306 端口;
  4. StatefulSet:替代 Deployment,确保 Pod 有稳定网络标识和存储绑定。

其中 Secret 的创建方式很关键。kubectl create secret generic mysql-root-pass --from-literal=password='my-secret-pw'是最安全的方式,密码不会出现在 bash history 或进程列表中。而--from-file方式要求密码先存为文件,存在文件权限泄露风险。

再看 “dify本地部署” 迁移到 K8S 的典型坑。Dify 前端(web)、后端(api)、Worker(celery)三个组件必须解耦部署。但很多教程把它们塞进同一个 Deployment,导致水平扩展时 Worker 和 API 互相抢占 CPU。正确做法是:

  • Web 组件用 Deployment + HPA(CPU 使用率 >70% 时扩容);
  • API 组件用 Deployment + PodDisruptionBudget(保障至少 2 个副本在线);
  • Worker 组件用 StatefulSet + initContainer(启动前检查 Redis 连接是否就绪)。

initContainer 的作用常被低估。它会在主容器启动前执行预检脚本,比如:

initContainers: - name: wait-for-redis image: busybox:1.35 command: ['sh', '-c', 'until nc -z redis-headless.default.svc.cluster.local 6379; do echo waiting for redis; sleep 2; done']

这段代码确保 Worker 容器绝不会在 Redis 不可用时启动,避免 Celery 启动失败后反复 CrashLoopBackOff。

3. 核心环节实操:从零搭建可验证的 K8S 集群

3.1 环境准备:三台 Ubuntu 24.04 节点的标准化初始化

我们以最典型的三节点集群为例:1 台 Master(4C8G),2 台 Worker(4C8G)。所有操作均在 root 用户下执行,避免 sudo 权限问题。

第一步:基础系统加固
在每台节点上执行以下命令。这不是可选项,是 K8S 生产环境的强制前提:

# 关闭 swap 并永久禁用 swapoff -a sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab # 加载必要内核模块 modprobe br_netfilter modprobe ip_vs modprobe ip_vs_rr modprobe ip_vs_wrr modprobe ip_vs_sh modprobe nf_conntrack # 持久化内核模块加载 cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf br_netfilter ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack EOF # 配置 sysctl 参数(启用网桥流量转发) cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 EOF sysctl --system

注意:sysctl --system会重新加载所有/etc/sysctl.d/下的配置,比单独sysctl -p更可靠。我曾遇到客户sysctl -p /etc/sysctl.d/k8s.conf失败,因为--system会按字母序加载,确保br_netfilteriptables之前生效。

第二步:安装 containerd 运行时
Ubuntu 24.04 自带 containerd 1.7.13,但默认配置不满足 K8S 要求。必须修改其配置:

# 生成默认配置 mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml # 修改关键参数:启用 systemd cgroup 和镜像仓库配置 sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml sed -i '/\[plugins."io.containerd.grpc.v1.cri".registry.mirrors\]/a [plugins."io.containerd.grpc.v1.cri".registry.configs."docker.io".auth]\n username = "your-username"\n password = "your-password"' /etc/containerd/config.toml

实操心得:SystemdCgroup = true是 Ubuntu 24.04 + K8S 1.28+ 的生死线。不开启会导致 cgroup v2 下 CPU 和内存限制完全失效。你可以用crictl inspect <container-id> | grep cgroupParent验证,输出应为/kubepods.slice而非/system.slice

第三步:安装 kubeadm、kubelet、kubectl

# 添加 K8S APT 仓库 curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /usr/share/keyrings/kubernetes-apt-keyring.gpg echo 'deb [arch=amd64 signed-by=/usr/share/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list # 安装指定版本(避免自动升级) apt-get update apt-get install -y kubelet=1.28.11-1.1 kubeadm=1.28.11-1.1 kubectl=1.28.11-1.1 apt-mark hold kubelet kubeadm kubectl

提示:“apt-mark hold” 锁定版本至关重要。K8S 补丁升级可能破坏 CNI 兼容性(如 Calico 3.26.3 与 K8S 1.28.12 的 RBAC 变更),锁定能避免意外升级。

3.2 集群初始化:Master 节点的精确配置与验证

现在进入最关键的kubeadm init环节。我们不用默认配置,而是构建一个生产就绪的初始化命令:

kubeadm init \ --pod-network-cidr=10.244.0.0/16 \ --service-cidr=10.96.0.0/12 \ --cri-socket=/run/containerd/containerd.sock \ --kubernetes-version=v1.28.11 \ --control-plane-endpoint="192.168.1.100:6443" \ --upload-certs \ --ignore-preflight-errors=NumCPU,Mem \ --node-name=$(hostname -s)

逐个解析参数:

  • --control-plane-endpoint:指定高可用 VIP 或 Master IP,这里是单节点,直接填 Master 的局域网 IP;
  • --upload-certs:启用证书分发,为后续添加 Control Plane 节点做准备;
  • --ignore-preflight-errors:跳过 CPU 和内存检查(测试环境允许),但生产环境必须满足 2C2G 最低要求;
  • --node-name:强制使用短主机名,避免kubeadm join时因 FQDN 解析失败。

执行成功后,你会看到类似这样的输出:

Your Kubernetes control-plane has initialized successfully! To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config You should now deploy a pod network to the cluster. Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/

立刻执行配置

mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config

然后立即验证

kubectl get nodes # 应显示 master 节点为 NotReady kubectl get pods -A # 应显示 kube-system 命名空间下 core-dns 处于 Pending

出现NotReadyPending是完全正常的!这说明 kubeadm init 成功,但 CNI 插件还没部署。此时不要慌,这是流程设计的必经阶段。

3.3 CNI 插件部署:Flannel 的精确配置与故障排除

我们选择 Flannel,因为它的轻量和稳定性在中小集群中无可替代。但 Flannel 0.24.0+ 对 Ubuntu 24.04 有特殊要求。

# 下载并修改 Flannel 配置(适配 containerd) curl -O https://raw.githubusercontent.com/flannel-io/flannel/v0.24.0/Documentation/kube-flannel.yml # 修改 containerRuntimeConfig sed -i 's/"containerRuntime":"docker"/"containerRuntime":"containerd"/g' kube-flannel.yml # 修改镜像地址(国内加速) sed -i 's/quay.io\/coreos\/flannel:/registry.cn-hangzhou.aliyuncs.com\/google_containers\/flannel:/g' kube-flannel.yml kubectl apply -f kube-flannel.yml

注意:containerRuntime必须设为containerd,否则 Flannel 无法与 containerd 通信。这个字段在旧版 Flannel 文档中被忽略,但 0.24.0+ 已强制校验。

部署后等待 60 秒,执行验证:

kubectl get pods -n kube-flannel # 应全部 Running kubectl get nodes # 应变为 Ready kubectl get pods -A | grep -i flannel # 确认 flannel 容器在每个节点运行

如果kubectl get nodes仍是 NotReady,请检查:

  1. journalctl -u kubelet -n 100 --no-pager | grep -i flannel—— 查看 kubelet 是否报告 CNI 初始化失败;
  2. ls /opt/cni/bin/—— 确认 flannel 插件二进制文件是否存在;
  3. cat /var/lib/cni/networks/k8s-pod-network/—— 检查 Flannel 是否成功分配子网。

3.4 Worker 节点加入:安全 token 的生成与复用

Master 初始化成功后,会输出kubeadm join命令。但该命令中的 token 24 小时后过期。生产环境中,你需要生成长期有效的 token:

# 在 Master 上生成新 token(永不过期) kubeadm token create --ttl 0 # 获取 ca cert hash(用于验证证书) openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //' # 构造 join 命令(替换 YOUR-TOKEN 和 YOUR-HASH) kubeadm join 192.168.1.100:6443 --token YOUR-TOKEN --discovery-token-ca-cert-hash sha256:YOUR-HASH

在每台 Worker 节点上执行此命令。加入成功后,在 Master 上执行:

kubectl get nodes # 应显示 1 master + 2 worker,全部 Ready kubectl get nodes -o wide # 查看各节点 IP 和内核版本,确认一致性

实操心得:kubeadm join失败最常见的原因是时间不同步。务必在所有节点执行timedatectl set-ntp true启用 NTP,并用timedatectl status确认同步状态。K8S 组件间通信大量使用 TLS 证书,时间偏差超过 1 分钟就会导致证书验证失败。

4. 应用部署实战:dify 与 mysql 的生产级 YAML 编排

4.1 dify 全组件部署:解耦、健康检查与资源限制

Dify 由 Web 前端、API 后端、Celery Worker 三大组件构成。我们将它们拆分为三个独立的 YAML 文件,实现精细化管控。

第一步:创建 Secret 存储敏感信息

# dify-secret.yaml apiVersion: v1 kind: Secret metadata: name: dify-secret namespace: default type: Opaque data: DATABASE_URL: cG9zdGdyZXM6Ly9kb2NrbWFuOmRvY2ttYW4tMTIzQGRvY2ttYW4tZGI6NTQzMjMvZGlm eQ== # postgresql://dockman:dockman-123@dockman-db:5432/dify REDIS_URL: cmVkaXM6Ly86cmVkaXMtMTIzQHJlZGlzLWhlYWRsZXNzOjYzNzkuMA== # redis://:redis-123@redis-headless:6379.0 SECRET_KEY: ZGlm eS1zZWNyZXQta2V5LWZvci1wcm9kdWN0aW9u # dify-secret-key-for-production

注意:所有值必须 base64 编码。用echo -n "your-string" | base64生成,-n参数避免换行符混入。

第二步:部署 PostgreSQL 数据库(StatefulSet)

# dify-postgres.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: dify-db namespace: default spec: serviceName: "dify-db" replicas: 1 selector: matchLabels: app: dify-db template: metadata: labels: app: dify-db spec: containers: - name: postgres image: postgres:15-alpine envFrom: - secretRef: name: dify-secret ports: - containerPort: 5432 volumeMounts: - name: postgres-storage mountPath: /var/lib/postgresql/data resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "1Gi" cpu: "500m" volumes: - name: postgres-storage persistentVolumeClaim: claimName: dify-db-pvc --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: dify-db-pvc namespace: default spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi --- apiVersion: v1 kind: Service metadata: name: dify-db namespace: default spec: selector: app: dify-db ports: - protocol: TCP port: 5432 targetPort: 5432

关键点:StatefulSet 保证 Pod 名称固定(如dify-db-0),PVC 名称也固定,实现存储绑定。resources.limits防止数据库吃光节点内存。

第三步:部署 Redis(Headless Service)

# dify-redis.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: redis namespace: default spec: serviceName: "redis-headless" replicas: 1 selector: matchLabels: app: redis template: metadata: labels: app: redis spec: containers: - name: redis image: redis:7-alpine envFrom: - secretRef: name: dify-secret ports: - containerPort: 6379 volumeMounts: - name: redis-storage mountPath: /data volumes: - name: redis-storage persistentVolumeClaim: claimName: redis-pvc --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: redis-pvc namespace: default spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi --- apiVersion: v1 kind: Service metadata: name: redis-headless namespace: default annotations: service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" spec: clusterIP: None selector: app: redis ports: - port: 6379 targetPort: 6379

为什么用 Headless Service?因为 Celery Worker 需要直接连接 Redis Pod IP,而非通过 ClusterIP 负载均衡。clusterIP: None禁用 DNS A 记录,只生成 Pod IP 的 DNS 记录(如redis-0.redis-headless.default.svc.cluster.local)。

第四步:部署 Dify API 和 Web(Deployment + Service)

# dify-api-web.yaml apiVersion: apps/v1 kind: Deployment metadata: name: dify-api namespace: default spec: replicas: 2 selector: matchLabels: app: dify-api template: metadata: labels: app: dify-api spec: containers: - name: api image: langgenius/dify-api:latest envFrom: - secretRef: name: dify-secret ports: - containerPort: 5001 livenessProbe: httpGet: path: /health port: 5001 initialDelaySeconds: 60 periodSeconds: 30 readinessProbe: httpGet: path: /health port: 5001 initialDelaySeconds: 30 periodSeconds: 10 resources: requests: memory: "1Gi" cpu: "500m" limits: memory: "2Gi" cpu: "1" --- apiVersion: apps/v1 kind: Deployment metadata: name: dify-web namespace: default spec: replicas: 2 selector: matchLabels: app: dify-web template: metadata: labels: app: dify-web spec: containers: - name: web image: langgenius/dify-web:latest ports: - containerPort: 3000 env: - name: API_URL value: "http://dify-api:5001" livenessProbe: httpGet: path: / port: 3000 initialDelaySeconds: 30 periodSeconds: 10 resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "1Gi" cpu: "500m" --- apiVersion: v1 kind: Service metadata: name: dify-api namespace: default spec: selector: app: dify-api ports: - protocol: TCP port: 5001 targetPort: 5001 --- apiVersion: v1 kind: Service metadata: name: dify-web namespace: default spec: type: NodePort selector: app: dify-web ports: - protocol: TCP port: 80 targetPort: 3000 nodePort: 30080

关键设计:

  • livenessProbereadinessProbe确保容器健康;
  • NodePort: 30080暴露 Web 界面,浏览器访问http://<master-ip>:30080即可;
  • env.API_URL用 Service 名dify-api,利用 K8S DNS 自动解析为 ClusterIP。

4.2 MySQL 部署:高可用与数据持久化的硬核实践

“k8s部署mysql” 的核心诉求是数据不丢、服务不宕、扩容不裂。我们采用 StatefulSet + PVC + 主从复制架构。

第一步:创建 MySQL 主从配置文件

# mysql-configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mysql-config namespace: default data: master.cnf: | [mysqld] log-bin server-id=1 slave.cnf: | [mysqld] server-id=2

第二步:部署 MySQL StatefulSet(主节点)

# mysql-statefulset.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: mysql namespace: default spec: serviceName: "mysql" replicas: 2 selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: initContainers: - name: clone-m