ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

K8s集群部署Traefik Ingress Controller实战:从NodePort到Python服务验证

K8s集群部署Traefik Ingress Controller实战:从NodePort到Python服务验证 先交代一下背景。我之前在本地虚拟机里折腾K8s集群应用Pod跑起来了Service也建了结果在浏览器里怎么都访问不到服务。折腾了一圈才发现问题出在入口流量这一层——NodePort暴露端口只是临时方案真正要在集群里把HTTP服务稳定地暴露出去还是得靠Ingress Controller。这篇文章就围绕这个话题讲讲我在K8s集群里部署Traefik并用一个Python HTTP服务做端到端验证的完整过程。适合刚把K8s集群搭起来、正准备接入真实业务流量的朋友参考整个流程从零开始不需要你之前接触过Traefik。1. 为什么我把入口流量交给Traefik而不是暴露一堆NodePort很多初学者包括曾经的我在集群里跑起第一个服务时第一反应就是给Service配一个NodePort浏览器里用http://节点IP:端口访问。这种方式做实验没问题一旦服务数量多起来各种问题就暴露了端口管理混乱、高并发下NodePort性能衰减、缺失七层路由能力比如按域名分流、按路径转发。我选Traefik的核心理由就在这——它是K8s生态里最成熟的七层入口网关方案之一。1.1 NodePort、LoadBalancer和Ingress到底有什么区别先理清概念。NodePort是K8s里最朴素的暴露方式它在每个节点上开一个端口默认范围30000-32767流量从节点的这个端口转给后端Service。它的优点就是简单缺点是端口资源有限而且业务层根本不知道来的是什么协议、什么域名。LoadBalancer一般用在云环境云厂商会给你创建一个负载均衡器外部流量先进LB再进节点。但是在本地虚拟机、裸机环境或者自建机房没有云厂商提供的LB组件Service的EXTERNAL-IP就会一直处于pending状态。Ingress则是K8s的七层流量入口抽象它不像NodePort那样需要为每个服务分配端口。Ingress Controller本身是一个运行在集群里的Pod它只有一个入口通常是80/443端口根据Ingress规则把请求按域名、按路径转发到对应的Service。这样你只需要维护一个外部入口后面接多少个服务都行。Traefik就是这类Ingress Controller的实现之一。1.2 Traefik和其他Ingress Controller选型对比我自己实际比较过几种主流的Ingress Controller这里直接说结论方案配置方式学习曲线适合场景TraefikIngressRoute CRD、Helm Values低中小集群、动态服务多的场景配置直观Nginx IngressIngress YAML、Annotations中大流量、需要精细Nginx调优的场景云厂商自带LB控制台或IaC工具低纯云环境、不折腾集群的时候Traefik有个很独特的优势它自己就是一个反向代理服务后端服务注册后自动发现几乎不需要重启。它的IngressRoute自定义资源定义CRD写起来比传统Ingress的Annotation清晰很多。而且自带的Dashboard把后端服务状态、路由规则、请求统计可视化排查问题的时候直接看面板就能定位。这些特性在调试阶段帮了大忙。2. 实验环境搭建用最简单的方式跑起一个能用的K8s集群开始部署Traefik之前得先有一个状态正常的K8s集群。这里我不展开讲生产级集群比如二进制部署或者kubeadm高可用怎么搭那些内容篇幅太长只说实验环境怎么快速跑起来。2.1 本地实验环境的选型如果你的目标只是学习和验证我建议不要一上来就搞三节点甚至五节点的集群一台2核4G的Linux虚拟机就够了。本地实验环境我推荐两种方案k3sRancher出品的轻量K8s发行版安装包小内存占用低对硬件要求低默认自带Traefik作为Ingress Controller。很多人在本地用k3s替代完整K8s做开发测试它和标准K8s的API兼容性做得很不错。minikube官方提供的单机K8s环境功能完整适合跑Kubernetes原本的样子但资源占用比k3s高一些。我这次用的是k3s。因为k3s自带的Service LoadBalancer实现在单节点下很好用后面验证Traefik的LoadBalancer类型Service时不用额外折腾。2.2 集群安装与基础验证k3s的安装非常简单一条命令就搞定curl -sfL https://get.k3s.io | sh -装完之后检查集群状态sudo k3s kubectl get nodes如果你是把k3s作为服务直接安装那么kubectl配置会自动生成在/etc/rancher/k3s/k3s.yaml。有些版本为了方便我把kubectl命令做了软链或者配置KUBECONFIG环境变量。之后所有kubectl操作都指向这个k3s集群。集群起来之后先做一个基线验证——在default命名空间跑一个临时的nginx确认Pod能正常调度、Service能正常解析kubectl run test-nginx --imagenginx kubectl expose pod test-nginx --port80 kubectl get svc test-nginx这时候能看到CLUSTER-IP说明集群内部网络正常。这一步虽然简单但能帮你把集群本身有问题和后面部署的组件有问题这两个变量隔离。注意k3s自身默认已经装了一个Traefik如果不需要它自带的可以在安装时通过--disable traefik参数禁用。为了实验完全可控我自己更习惯在安装时禁用自带的Traefik然后手动用Helm装一个下面所有步骤都是在这个前提下进行的。3. Helm一条命令装好Traefik参数背后的含义要搞清楚部署Traefik的方式有好几种可以直接用kubectl apply -f也可以用Helm Chart。我的建议是除非你只是临时看看效果否则一律用Helm管理。原因很简单——Traefik的配置项实在太多纯手工维护YAML不现实Helm让你用一条命令就能升级、回滚、传参。3.1 为什么要用Helm Chart方式部署Helm是K8s的包管理工具打个比方如果kubectl是直接编辑文本文件Helm就是用模板批量生成文本文件。Traefik官方维护的Helm Chart把Deployment、Service、RBAC、CRD安装等全部封装好了你只需要关心几个核心参数它还自动帮你装好IngressRoute要用到的CRD。如果你还没装Helm先装一下curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 chmod 700 get_helm.sh ./get_helm.sh装好之后添加Traefik的Chart仓库helm repo add traefik https://traefik.github.io/charts helm repo update3.2 核心values参数逐项说明安装之前我做了一个traefik-values.yaml文件把关键的配置参数拆开说明一下service: type: LoadBalancer spec: loadBalancerIP: # 本地环境为空让k3s自动分配 ingressRoute: dashboard: enabled: true # 启用Dashboard路由 healthCheck: enabled: true # 开启健康检查路径 dashboard: enabled: true domain: traefik.local.example # Dashboard的访问域名 logs: access: enabled: true # 开启访问日志排查问题必备这里几个参数我展开说一下service.type: LoadBalancer在k3s环境下它会自动创建Service LoadBalancerk3s内置了对应的负载均衡器实现外部流量能直接到达Traefik Pod。在标准K8s集群里如果没有LB组件这里可以临时改成NodePort后面我们验证阶段用NodePort访问也没问题。ingressRoute.dashboard.enabledTraefik的Dashboard是它自己的一个Web管理界面可以通过IngressRoute规则暴露出来。调试的时候开着它可以实时看到路由规则和后端健康状态。logs.access.enabled这一项强烈建议从一开始就打开。Traefik的访问日志会记录每个请求的Host、路径、状态码、转发时间。后面排查为什么转发失败的时候这个日志就是第一手证据。执行安装命令helm install traefik traefik/traefik -n traefik --create-namespace -f traefik-values.yaml3.3 验证Traefik是否正常启动安装完成后检查Pod和Service状态kubectl get pods -n traefik kubectl get svc -n traefik正常情况下Pod的STATUS是RunningService的外部IP如果是k3s环境会变成192.168.x.x或类似地址。如果Service的EXTERNAL-IP一直是pending大概率是你的集群没有LoadBalancer实现。这时候把traefik-values.yaml里的service.type改成NodePort重新upgrade也能继续后面的验证。验证Traefik的Web入口是否正常可以先看Pod日志kubectl logs -n traefik deploy/traefik --tail20看到类似Started v3.x的日志说明Traefik进程已经起来了。这时候访问你的节点IP的80端口如果返回404或者403、503取决于有没有匹配的路由说明Traefik已经在监听流量了——因为没有任何路由规则时它不会直接把请求吞掉而是返回一个错误页。这个现象反而是好现象说明网关本身在工作只是路由还是空的。4. 写一个能用Docker跑起来的Python HTTP服务Traefik装好了相当于给K8s集群开了一个门口现在得有一个实际业务服务来验证这个门口好不好使。我用Python写了一个极简的HTTP服务目的就一个方便验证从浏览器到Ingress再到后端Pod的整条链路是否打通。4.1 一组不到20行的Flask服务代码我是用Flask写的因为它的路由逻辑直观依赖也轻在容器里跑很省心。app.py内容如下from flask import Flask, request, jsonify import os import socket app Flask(__name__) app.route(/) def index(): return jsonify({ message: Hello from Python HTTP Service, hostname: socket.gethostname(), path: request.path, client_ip: request.remote_addr }) app.route(/healthz) def healthz(): status {status: ok} return jsonify(status) if __name__ __main__: port int(os.environ.get(PORT, 8000)) app.run(host0.0.0.0, portport)/healthz是给K8s探活用的后面配Deployment时会用到。/接口返回了容器的主机名和请求信息验证负载均衡效果的时候你多访问几次就能看到不同的hostname说明流量被分发到了不同的Pod副本。4.2 镜像构建的注意事项Dockerfile同样很简单FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY app.py . ENV PORT8000 EXPOSE 8000 CMD [python, app.py]requirements.txt内容flask3.0.0构建镜像docker build -t flask-hello:v1 .这里要特别提醒一个在K8s环境里踩过的坑如果你是在单节点上跑k3s尤其是用containerd作为运行时构建完镜像之后需要确认k3s能看到这个镜像。k3s默认使用containerd而不是docker所以docker images里看到的镜像k3s那边不一定能拉取到。最简单的办法要么把镜像推到一个私有仓库比如registry要么把镜像导出/导入到k3s的运行时里要么干脆用我下面的方式——在Deployment里指定imagePullPolicy: IfNotPresent并且确保镜像名和k3s节点本地镜像一致。因为是单节点实验环境如果容器运行时和k3s的运行时是同一套一般docker build后K3s能直接使用。如果不一致使用k3s ctr images import或者docker save | k3s ctr images import把镜像导入进去docker save flask-hello:v1 | sudo k3s ctr images import -4.3 Deployment与Service的定义要点我写了一个python-http.yaml包含Deployment和Service两部分apiVersion: apps/v1 kind: Deployment metadata: name: python-http-app labels: app: python-http-app spec: replicas: 2 selector: matchLabels: app: python-http-app template: metadata: labels: app: python-http-app spec: containers: - name: python-http-app image: flask-hello:v1 imagePullPolicy: IfNotPresent ports: - containerPort: 8000 env: - name: PORT value: 8000 readinessProbe: httpGet: path: /healthz port: 8000 initialDelaySeconds: 3 periodSeconds: 5 livenessProbe: httpGet: path: /healthz port: 8000 initialDelaySeconds: 10 periodSeconds: 10 --- apiVersion: v1 kind: Service metadata: name: python-http-app-svc labels: app: python-http-app spec: type: ClusterIP selector: app: python-http-app ports: - name: http port: 80 targetPort: 8000readinessProbe和livenessProbe一定要配置。特别是readinessProbe——Traefik自动发现后端时会检查Pod的就绪状态如果后端Pod一直处于未就绪状态流量就会504。这种小问题在裸奔的演示中很容易被忽略生产上却是第一道保命符。Service端口我用了port: 80, targetPort: 8000意思是Service内部暴露在80转发到Pod的8000。IngressRoute后面匹配Service时用的是这个80端口。应用并确认状态kubectl apply -f python-http.yaml kubectl get pods -l apppython-http-app kubectl get svc python-http-app-svc两个Pod都进入Running并且READY 2/2之后整个后端服务就绪。5. 把服务挂到Traefik上IngressRoute的完整写法Traefik支持传统K8s的Ingress资源也支持它自己的IngressRoute CRD。我强烈建议用IngressRoute因为它的表达能力更强语义也更清晰。一个路由规则包含三个核心部分入口点entryPoint、匹配规则match、转发目标forwardTo。5.1 IngressRoute的字段拆解下面是我用来暴露Python服务的IngressRoute定义apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: name: python-http-app-route namespace: default spec: entryPoints: - web routes: - kind: Rule match: Host(hello.local.test) PathPrefix(/) services: - name: python-http-app-svc port: 80拆开解释entryPoints: [web]Traefik默认有两个入口点web对应80端口websecure对应443端口。我们需要HTTP访问所以绑到web。match: Host(\hello.local.test)当请求的Host头是hello.local.test时命中这个路由。如果只有一个服务、不做多域名分流你也可以直接匹配PathPrefix(/)。但多域名场景下Host匹配是必备的。services.name / port流转发的目标Service名称和Service端口不是Pod端口注意区分。这里指向python-http-app-svc:80。应用它kubectl apply -f ingressroute.yaml应用完成后可以查看Traefik自动发现到的后端状态。在Dashboard页面下一节讲怎么访问的HTTP路由部分应该能看到新注册的hello.local.test路由目标指向default-python-http-app-svc-80这样的内部名称。5.2 域名与流量的对应关系在本地实验环境通常没有真实域名也不方便改DNS。有两个办法改本机/etc/hosts把节点IP映射到hello.local.test使用curl --resolve参数临时指定域名解析我推荐第二种验证完不用留痕迹curl --resolve hello.local.test:80:192.168.1.100 http://hello.local.test/其中192.168.1.100是运行K3s的节点IP。这样不需要动系统文件很方便。6. 端到端验证从Pod一路追踪到浏览器请求部署完成后最兴奋的一步就是把整条链路验证通。这部分我分了三层验证先验证集群内部Service再通过Traefik做外部访问最后用Dashboard和日志确认转发细节。6.1 第一层验证集群内部访问Service在进入Traefik之前先排除后端问题。进入一个临时Pod直接请求Servicekubectl run curl-test --imagecurlimages/curl --rm -it --restartNever -- curl http://python-http-app-svc:80/如果配置没问题返回内容应该是带hostname字段的JSON。这一步通过说明Deployment里的Pod、Service的标签选择器、端口映射都是对的。如果这里就卡住了根因多半是标签选择器写错Service选不到Pod。6.2 第二层验证通过Traefik外部访问Service内部通了接下来验证Traefik入口。先获取Traefik的访问地址kubectl get svc -n traefik如果是LoadBalancer类型EXTERNAL-IP就是入口IP如果是NodePort类型则是端口。执行curl --resolve hello.local.test:80:EXTERNAL_IP http://hello.local.test/看到返回的JSON里message字段是Hello from Python HTTP Service链路就通了。为了确认负载均衡是否正常工作多执行几次观察返回的hostname字段变化for i in {1..5}; do curl --resolve hello.local.test:80:EXTERNAL_IP http://hello.local.test/ | grep hostname; done如果两个Pod的hostname交替出现说明Traefik在多个后端副本之间做了轮询负载均衡。6.3 结合Dashboard确认路由与后端健康状态Traefik的Dashboard是通过IngressRoute暴露的我前面安装时开启了dashboard。配置一个访问Dashboard的路由apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: name: traefik-dashboard namespace: traefik spec: entryPoints: - web routes: - kind: Rule match: Host(traefik.local.example) services: - name: apiinternal kind: TraefikService然后访问http://traefik.local.example同样用--resolve参数解析到节点IP。Dashboard首页的HTTP Routers列表里可以看到所有注册的路由包括hello.local.test和traefik.local.example。点击路由详情还能看到Service的健康状态、请求次数和响应状态码分布。如果哪个后端的健康检查失败面板上会直接标红。6.4 从访问日志验证转发细节Dashboard显示的是聚合后的数据想看单条请求的流转细节还得翻Traefik的访问日志kubectl logs -n traefik deploy/traefik --tail20再次请求一次curl --resolve hello.local.test:80:EXTERNAL_IP http://hello.local.test/healthz日志里会出现类似下面的一行10.42.0.1 - - [date] GET /healthz HTTP/1.1 200 12 - curl/x.x.x python-http-app-svc-xxx 192.168.1.100:8000关键信息是最后两段python-http-app-svc-xxx是实际被选中的后端Pod名末尾的192.168.1.100:8000是Pod的IP和端口。这一行日志完整印证了整个转发链路——请求先进Traefik再被转发到Service匹配到的具体Pod。7. 部署过程中我踩过的坑和排查思路最后这部分是全文最有价值的章节。我在这个实验里故意留了一些坑也遇到了一些原本没预料到的问题。整个排查过程记录下来希望能给你省下几个小时的折腾时间。7.1 问题一访问IngressRoute返回404但Dashboard里路由是存在的这个坑非常典型。现象是在Dashboard里能看到hello.local.test这个路由状态也正常但用curl访问时返回404。排查过程首先确认命中的规则。Traefik匹配路由时要求Host头和Path都匹配我只配了Host(\hello.local.test)请求时也确实带了Host头。那问题出在哪逐步排查查看Traefik访问日志发现请求进来后Traefik匹配到的路由是traefik-dashboard那条而不是我新加的python-http-app-route——因为traefik.local.example和hello.local.test都挂在同一个web入口点上而curl默认带的Host是hello.local.test理论上应该命中路由B。接着我看了一下IngressRoute的命名空间果然是在default命名空间Traefik也能跨命名空间发现。问题只剩一个可能——请求的目标IP不对。用curl请求时解析的EXTERNAL_IP是192.168.1.100但实际上k3s的Service LoadBalancer把流量导到了另一个节点的IP上也就是我访问的IP和Traefik Service的ExternalIP不一致。补上--resolve参数指向正确的ExternalIP后请求立即返回200。这个问题的根因不算复杂但过程很值得记住Dashboard里路由存在只代表Traefik注册了这个路由不代表你访问的IP/域名组合一定命中了它。排查时先确认访问入口对没对上再谈路由匹配对不对。7.2 问题二Pod显示Running但IngressRoute后端标红有几次我改了Deployment的镜像版本Pod重建后一直显示Running但Dashboard里后端的健康状态是红色。去查发现readinessProbe一直没过——原来我改镜像时改了应用监听端口为8080但Probe还指向8000。这种问题在真实项目中特别容易发生因为应用是活的livenessProbe没过会重启但对外表现为不可服务readinessProbe没过则不会有流量进来。排查和修复方式很简单kubectl describe pod -l apppython-http-app | grep -A10 Readiness看到Readiness probe failed的具体错误调整端口或者Probe路径重新apply Deployment即可。这再次印证了UI里看到的健康状态其实就是K8s探活/就绪探针的结果IngressRoute的后端状态完全取决于这个。7.3 问题三NodePort模式下外部IP拿不到如果你用的是标准K8s集群没有LoadBalancer实现把Traefik的Service改成NodePort后EXTERNAL-IP自然永远拿不到这是正常的。访问方式不是http://外部IP:80而是http://节点IP:NodePort端口。kubectl get svc -n traefikPORT(S)列显示80:31563/TCP那就用http://192.168.1.100:31563访问。注意此时浏览器的Host头是节点IP和hello.local.test不匹配。所以curl时仍然需要加上--resolve hello.local.test:31563:192.168.1.100。NodePort模式下端口是随机分配的每次重建可能变建议在Values里手动指定一个固定NodePort端口比如service: type: NodePort spec: ports: - port: 80 nodePort: 31800这样调试时就不用每次去看分配的端口了。7.4 问题四Python版本不一致导致的Local镜像拉取失败如果你在构建镜像时用的本机Python版本和容器里声明的不一致pip install可能失败。我解决这类问题最稳妥的方式是锁定python:3.11-slim基础镜像本机开发环境只负责写代码构建和运行完全以Docker镜像为准。K8s环境里最怕在我电脑上明明好的这种问题容器化第一次部署就严格用干净基础镜像构建后面能少踩很多坑。另外如果你改了代码却看不到效果大概率是镜像构建后没有正确导入k3s运行时。这个问题我在4.2节就说了单节点本地环境建议直接在节点上执行docker save | k3s ctr images import -或者使用imagePullPolicy: IfNotPresent配合正确导入确保用的就是新构建的镜像。结尾这套组合跑通之后下一步怎么扩展整个环境的链路现在是完整的外部HTTP请求 → TraefikLoadBalancer/NodePort入口→ IngressRoute路由匹配 → Service发现Pod → Python Flask返回JSON数据。这套组合跑通之后后面在这个集群里加新服务就是流水线操作——写Deployment Service写一条IngressRouteDashboard里刷新一下就能看到路由自动注册不需要重启Traefik也不需要改任何全局配置。这也是我在本地实验环境一直偏用Traefik的原因它的动态发现特性对频繁更换调试服务来说太友好了。最后分享两个小技巧。第一Traefik的访问日志在排查问题时有不可替代的价值凡是遇到我配置了为什么还是不通先翻日志定位请求到底匹配到了哪个路由、转发到了哪台Pod。第二k3s自带的Traefik如果和你手动安装的有冲突强烈建议直接从源头禁掉否则排查问题时会出现我以为用的是这个实际用的是另一个的混乱局面。现在这套流程已经成了我本地开发环境的标配希望这篇记录也能让你少走几步弯路。
返回列表