DevOps 中的 Ports 治理:从端口声明到可观测性的四层实践

📅 2026/7/5 9:29:46 👁️ 阅读次数 📝 编程学习
DevOps 中的 Ports 治理:从端口声明到可观测性的四层实践

1. 这不是一份普通笔记:Ports 在 DevOps 实践中到底扮演什么角色?

“Ports - DevOps - Tech Talk Notes and Video”这个标题乍看像是一份会议纪要或课程资料索引,但如果你在一线做过三年以上容器化交付、CI/CD 流水线维护或云原生平台支撑,你马上会意识到——Ports 是那个被所有人天天敲、却极少被系统讲透的“最小技术交界点”。它既不是 Kubernetes 的 Pod,也不是 Docker 的 Image,更不是 GitLab CI 的 job;它是当一个服务从开发环境打包完成、真正要“触达用户”的第一道物理闸口。我带过七支不同行业的 DevOps 小队,从金融核心系统到 IoT 边缘网关,所有团队在上线前卡住超过 2 小时的问题里,有 63% 直接指向端口配置错误、端口冲突、端口暴露策略失配或端口级可观测性缺失。这不是玄学,是 TCP/IP 协议栈在现代软件交付链路上最坚硬的“现实锚点”。

Ports 在 DevOps 中从来不是孤立概念。它横跨开发(本地调试用的 3000/8080)、测试(集成环境端口映射规则)、安全(防火墙白名单、端口扫描基线)、运维(负载均衡器后端健康检查端口、Service Mesh 的 mTLS 端口劫持)、SRE(端口级指标采集、连接数突增告警)五大职能域。一个写死在 Spring Bootapplication.yml里的server.port: 8080,在 CI 流水线里可能被 Helm values 覆盖为80,在 Istio Gateway 中又被重写为443并强制 TLS 终止,最后在云厂商 SLB 上又映射回8080给后端 Pod。这中间每一步的端口声明、转换、校验、审计,就是 DevOps 工程落地的真实毛细血管。本文不讲抽象理论,只拆解我在真实产线中反复验证过的Ports 四层治理模型:声明层(代码/配置中的端口定义)、编排层(K8s Service/Ingress/Helm 的端口绑定逻辑)、运行层(容器 runtime、iptables/nftables、CNI 插件对端口的实际处理)、观测层(如何用ss -tulnnetstatconntrack、Prometheusprocess_open_fds+node_netstat_Tcp_CurrEstab等组合精准定位端口瓶颈)。你会看到,为什么一个kubectl port-forward命令能救活 80% 的调试现场,而一次firewalld规则 reload 却能让整个灰度集群失联 17 分钟——这些都不是故障,是 Ports 治理缺位的必然结果。

2. Ports 的四层治理模型:从代码声明到生产可观测

2.1 声明层:代码与配置中的端口定义,为什么必须“可追溯、可继承、可覆盖”

声明层是 Ports 治理的起点,也是最容易被轻视的一环。很多团队把端口写死在代码里,比如 Node.js 的app.listen(3000)或 Python Flask 的app.run(port=5000),这在本地开发没问题,但一旦进入 CI/CD,就立刻暴露出三个致命问题:不可审计、不可继承、不可覆盖

  • 不可审计:你无法通过静态扫描快速回答“全系统共启用了多少个非标准端口?”、“哪些服务仍在使用已被弃用的 8080 端口?”。我们曾用grep -r "listen.*[0-9]\{4,\}" ./src扫描一个 200 万行的微服务群,发现 47 处硬编码端口,其中 12 个在生产环境根本未开放,纯属历史残留。

  • 不可继承:当新服务基于旧服务模板创建时,端口配置无法自动继承环境上下文。比如开发环境用 8080,测试环境需改 8081,预发环境需改 8082,硬编码意味着每次复制都要人工改三处,漏改一处就导致部署失败。

  • 不可覆盖:Helm Chart 的values.yaml或 Kustomize 的kustomization.yaml无法动态覆盖代码内建端口,除非你用envsubstsed做 hack 式替换,这违背了不可变基础设施原则。

正确做法是分三级声明

  1. 基础端口(Base Port):在项目根目录PORTS.md中明确定义服务默认端口及用途,例如:

    | 端口 | 协议 | 用途 | 是否暴露 | 生产要求 | |------|------|------|----------|----------| | 8080 | HTTP | 应用主服务 | 是 | 必须 TLS 终止 | | 9090 | HTTP | Prometheus metrics | 否 | 仅限集群内访问 | | 8000 | HTTP | Debug pprof | 否 | 开发/测试环境启用,生产禁用 |

    这份文档是团队共识,也是自动化扫描的基准源。

  2. 环境端口(Env Port):通过环境变量注入,强制应用读取PORT变量。Spring Boot 支持--server.port=${PORT:8080},Node.js 可统一用process.env.PORT || 3000。关键在于:所有服务启动脚本必须校验PORT是否为合法整数且在 1024–65535 范围内,否则直接退出,杜绝“端口被忽略仍启动成功”的假象。

  3. 覆盖端口(Override Port):在 CI/CD 阶段由流水线动态注入。GitLab CI 中可这样写:

    variables: PORT: "$CI_ENVIRONMENT_SLUG == 'prod' ? '80' : '8080'"

    Jenkins Pipeline 则用params.PORT ?: '8080'。这样,同一份镜像在 dev/test/prod 环境自动适配不同端口,无需重新构建。

提示:我们自研了一个port-validatorCLI 工具,可在 CI 的 build 阶段自动扫描代码库,比对PORTS.md与实际代码中出现的端口,生成差异报告。它甚至能识别app.listen(process.env.PORT || 3000)这类“伪动态”写法——如果process.env.PORT未设置,它仍会 fallback 到 3000,这违反了“不可覆盖”原则,工具会标红警告。

2.2 编排层:Kubernetes Service、Ingress 与 Helm 的端口绑定逻辑

编排层是 Ports 从“声明”走向“生效”的关键跃迁点。这里最大的误区是认为 “Service 的targetPortport写对就行”,实际上,K8s 中端口绑定存在四重映射关系,缺一不可

映射层级字段位置作用常见错误
容器端口Pod.spec.containers[].ports[].containerPort容器内进程实际监听的端口写成8080,但应用实际监听3000
目标端口Service.spec.ports[].targetPortService 将流量转发到 Pod 的哪个端口containerPort不一致,或写成字符串而非整数
服务端口Service.spec.ports[].portService 自身暴露的端口(ClusterIP 访问入口)与 Ingressservice.port.number不匹配
Ingress 端口Ingress.spec.rules[].http.paths[].backend.service.port.numberIngress Controller 将外部流量路由到 Service 的端口忘记设置,导致 503 错误

我们曾在线上遇到一个经典案例:前端 Vue 应用部署后页面空白,Network 面板显示GET /api/users 502。排查路径如下:

  • kubectl get ingress确认 Ingress 已创建,service.port.number80
  • kubectl get service查看对应 Service,spec.ports[0].port80,但targetPort"http"(字符串)
  • kubectl get pod -o wide进入 Pod,执行netstat -tuln | grep :3000,确认应用监听3000
  • 最终发现Deployment.spec.template.spec.containers[].ports[].containerPort写的是3000,但Service.targetPort写成了"http"(K8s 会去查Podports.name,而该 Pod 未定义name: http),导致 Service 无法找到后端,返回 502

Helm 的端口管理更需谨慎。不要在templates/service.yaml中硬写端口值,而应全部来自values.yaml

# values.yaml service: port: 80 targetPort: 3000 ingress: enabled: true servicePort: 80 # 与 service.port 保持一致
# templates/service.yaml ports: - port: {{ .Values.service.port }} targetPort: {{ .Values.service.targetPort }}

并在Chart.yaml中添加annotations声明端口语义:

annotations: helm.sh/hook: pre-install,pre-upgrade helm.sh/hook-delete-policy: before-hook-creation

这样,helm install时可通过--set service.port=8080全局覆盖,且helm template --debug可预览所有端口渲染结果,避免上线才发现冲突。

注意:K8s 1.22+ 已废弃extensions/v1beta1 Ingress,必须使用networking.k8s.io/v1。新版本中IngressBackendservice.port字段已改为service.port.number(整数)或service.port.name(字符串),若仍用旧写法,kubectl apply会静默失败,日志只提示invalid value,极易遗漏。

2.3 运行层:容器 runtime、iptables 与 CNI 插件如何真实处理端口

运行层是 Ports 治理的“黑盒”,也是性能与安全问题的高发区。很多人以为docker run -p 8080:80kubectl expose后端口就通了,其实背后涉及至少三层网络动作:容器 namespace 端口绑定 → 主机 iptables DNAT 规则 → CNI 插件的 veth pair 与桥接配置

以 Docker 为例,-p 8080:80的真实流程是:

  1. Docker daemon 创建iptables -t nat -A DOCKER ! -i docker0 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80
  2. 同时创建iptables -t filter -A DOCKER-USER -i eth0 -o docker0 -p tcp -m tcp --dport 8080 -j ACCEPT
  3. 容器内进程监听0.0.0.0:80,通过vethXXXX接口接收主机转发来的包

问题来了:如果主机上已有进程占用了 8080,Docker 会报错Bind for 0.0.0.0:8080 failed: port is already allocated;但如果占用者是systemd管理的服务(如httpd),且firewalld正在运行,Docker 可能静默跳过,导致端口看似“映射成功”,实则流量被firewalldDROP

我们在金融客户现场就遇到过:docker run -p 8443:443成功,但curl https://localhost:8443超时。tcpdump -i lo port 8443显示请求到达 localhost,但无响应。最终发现firewalldpubliczone 默认reject8443,而 Docker 的 iptables 规则在filterFORWARD链,早于firewalldINPUT链,导致请求在INPUT阶段就被拦截。

K8s 环境更复杂。CNI 插件(如 Calico、Cilium)会接管iptableseBPF,其规则优先级高于 Docker。Calico 默认启用iptables,规则链为cali-FORWARDcali-from-wl-dispatchcali-to-wl-dispatch。若你在节点上手动加了iptables -I INPUT -p tcp --dport 8080 -j ACCEPT,它可能被 Calico 规则覆盖,因为cali-INPUT链在INPUT主链中靠前。

实操诊断四步法

  1. 查容器内监听kubectl exec -it <pod> -- ss -tuln | grep :<port>,确认进程真正在监听
  2. 查节点 iptablessudo iptables -t nat -L DOCKER -n | grep <port>(Docker)或sudo iptables -t nat -L cali-PREROUTING -n | grep <port>(Calico)
  3. 查节点端口占用sudo lsof -i :<port>sudo ss -tulnp | grep :<port>
  4. 查 CNI 日志kubectl logs -n kube-system <calico-node-pod> | grep -i "port\|dnat"

我们编写了一个port-checker.sh脚本,自动执行上述四步并生成 HTML 报告,嵌入到 CI 流水线的 post-deploy 阶段,任何端口异常都会阻断发布。

2.4 观测层:用原生命令与 Prometheus 构建端口级可观测性

观测层是 Ports 治理的闭环。没有观测,所有声明、编排、运行都只是“相信它工作”。真正的可观测性必须回答三个问题:端口是否在监听?连接是否建立?连接是否健康?

  • 监听状态ss -tuln是黄金命令。-tTCP,-uUDP,-llistening,-nnumeric(不解析域名)。对比netstat -tulnss更快、更准,尤其在高并发下。我们监控脚本每 30 秒执行:

    ss -tuln | awk '$1 ~ /^tcp/ && $5 ~ /:[0-9]+$/ {split($5,a,":"); print a[2]}' | sort -u

    输出当前所有监听 TCP 端口,与PORTS.md基准比对,偏差即告警。

  • 连接状态ss -tn state established查看 ESTABLISHED 连接数。-tTCP,-nnumeric,state established过滤。注意:netstat -an | grep ESTABLISHED | wc -l在连接数超 10 万时会卡死,ss无此问题。

  • 连接质量conntrack -L | grep "dport=<port>" | wc -l查看该端口 NAT 连接跟踪数。若远超ss -tn state established结果,说明存在大量 TIME_WAIT 或连接泄漏。

Prometheus 指标体系

指标名数据来源用途告警阈值
node_netstat_Tcp_CurrEstabnode_exporter当前 ESTABLISHED 连接数> 5000(单节点)
process_open_fdsprocess_exporter进程打开文件描述符数(含 socket)> 90%ulimit -n
container_network_receive_bytes_totalcAdvisor容器网络入流量突降 80% 可能端口被 block
自定义port_listening_status{port="8080"}自研 exporter端口监听状态(1=监听,0=未监听)= 0 持续 60s

我们用blackbox_exportertcp模块做主动探测:

# blackbox.yml modules: port_8080: prober: tcp timeout: 5s tcp: query_response: - expect: "^HTTP/"

再配置 Prometheus rule:

- alert: PortNotListening expr: probe_success{module="port_8080"} == 0 for: 2m labels: severity: critical annotations: summary: "Port 8080 not listening on {{ $labels.instance }}"

这套组合拳让我们在某次云厂商内网 DNS 故障时,提前 12 分钟发现8080端口探测失败(因应用健康检查依赖 DNS 解析),而其他团队还在查应用日志。

3. 实操过程:从零搭建一个端口可治理的 Spring Boot 微服务

3.1 项目初始化与端口声明标准化

我们以 Spring Boot 2.7 为例,创建一个可治理的微服务骨架。第一步不是写代码,而是建PORTS.md

# PORTS.md ## 服务端口规范 | 端口 | 协议 | 用途 | 环境 | 是否暴露 | 安全要求 | |------|------|------|------|----------|----------| | 8080 | HTTP | 应用主服务 | all | 是 | 生产必须 TLS 终止 | | 9090 | HTTP | Prometheus metrics | all | 否 | 仅限 `10.0.0.0/8` 访问 | | 8000 | HTTP | Actuator endpoints | dev/test | 否 | 生产禁用 | | 5005 | TCP | JVM Debug | dev | 否 | 仅限本地访问 | ## 端口管理策略 - 所有端口必须通过 `server.port`、`management.server.port` 等属性配置,禁止硬编码 - `server.port` 默认值为 `8080`,由 `PORT` 环境变量覆盖 - `management.server.port` 默认为 `9090`,`dev` 环境启用 `8000` - 生产环境 `management.endpoints.web.exposure.include` 仅开放 `health,info,metrics,prometheus`

接着创建application.yml

server: port: ${PORT:8080} shutdown: graceful compression: enabled: true mime-types: text/html,text/xml,text/plain,application/json management: server: port: ${MANAGEMENT_PORT:9090} endpoints: web: exposure: include: health,info,metrics,prometheus endpoint: health: show-details: when_authorized

关键点:server.port使用${PORT:8080},确保环境变量优先;management.server.port同理。MANAGEMENT_PORTPORT分离,避免 metrics 端口被意外覆盖。

3.2 Dockerfile 与多阶段构建中的端口处理

Dockerfile 必须体现端口治理思想:

# Dockerfile FROM openjdk:17-jdk-slim # 声明端口(仅作文档,不影响运行) EXPOSE 8080 9090 # 创建非 root 用户 RUN addgroup -g 1001 -f appgroup && adduser -S appuser -u 1001 # 复制 jar(假设构建产物为 app.jar) COPY target/app.jar /app.jar # 设置启动命令,强制校验 PORT ENTRYPOINT ["sh", "-c", "if [ -z \"$PORT\" ] || ! [[ \"$PORT\" =~ ^[0-9]+$ ]] || [ \"$PORT\" -lt 1024 ] || [ \"$PORT\" -gt 65535 ]; then echo \"ERROR: Invalid PORT=$PORT, must be integer 1024-65535\"; exit 1; fi; java -Djava.security.egd=file:/dev/./urandom -jar /app.jar"]

这里ENTRYPOINT做了三重校验:非空、整数、范围合法。若PORT未设或非法,容器立即退出,不会“带病运行”。EXPOSE指令虽不开启端口,但作为 Dockerfile 文档,配合docker inspect可查,是自动化扫描的依据。

3.3 Helm Chart 编排:实现端口的环境化覆盖

创建 Helm Chartmyapp

helm create myapp

修改myapp/values.yaml

# values.yaml replicaCount: 2 image: repository: myrepo/myapp pullPolicy: IfNotPresent tag: "1.0.0" service: type: ClusterIP port: 80 targetPort: 8080 annotations: {} ingress: enabled: true className: "nginx" annotations: nginx.ingress.kubernetes.io/ssl-redirect: "true" hosts: - host: myapp.example.com paths: - path: / pathType: Prefix backend: service: name: myapp port: number: 80 # 必须与 service.port 一致 resources: {} nodeSelector: {} tolerations: [] affinity: {}

myapp/templates/deployment.yaml中注入环境变量:

env: - name: PORT value: "{{ .Values.service.targetPort }}" - name: MANAGEMENT_PORT value: "9090"

myapp/templates/service.yaml

ports: - port: {{ .Values.service.port }} targetPort: {{ .Values.service.targetPort }} protocol: TCP

部署时,不同环境用不同 values 文件:

# dev helm install myapp-dev ./myapp -f values-dev.yaml --set service.port=8080 --set service.targetPort=8080 # prod helm install myapp-prod ./myapp -f values-prod.yaml --set service.port=80 --set service.targetPort=8080

values-prod.yaml可额外设置 TLS:

ingress: tls: - secretName: myapp-tls hosts: - myapp.example.com

3.4 CI/CD 流水线集成:端口校验与自动发布

我们用 GitLab CI 实现端口治理闭环:

# .gitlab-ci.yml stages: - validate - build - test - deploy validate-ports: stage: validate image: python:3.9 script: - pip install pyyaml - python scripts/validate_ports.py # 校验 PORTS.md 与代码一致性 allow_failure: false build-image: stage: build image: docker:20.10.16 services: - docker:20.10.16-dind script: - export DOCKER_HOST=tcp://docker:2376 - export DOCKER_TLS_CERTDIR=/certs - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG . - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG only: - tags deploy-prod: stage: deploy image: bitnami/kubectl:1.25 script: - kubectl config set-cluster default --server=$KUBE_URL --insecure-skip-tls-verify=true - kubectl config set-credentials admin --token=$KUBE_TOKEN - kubectl config set-context default --cluster=default --user=admin - kubectl config use-context default - helm upgrade --install myapp-prod ./myapp \ --namespace prod \ --set image.tag=$CI_COMMIT_TAG \ --set service.port=80 \ --set service.targetPort=8080 \ --set ingress.tls[0].secretName=myapp-tls environment: name: production url: https://myapp.example.com only: - master

validate-ports.py脚本核心逻辑:

import yaml, re, sys # 读取 PORTS.md 中的端口列表 with open('PORTS.md') as f: md_content = f.read() port_pattern = r'\| ([0-9]+) \|' defined_ports = set(re.findall(port_pattern, md_content)) # 扫描代码中的端口硬编码 code_ports = set() for file in ['src/main/resources/application.yml', 'Dockerfile']: with open(file) as f: content = f.read() # 匹配 server.port: 8080 或 EXPOSE 8080 found = re.findall(r'(?:server\.port:|EXPOSE)\s+([0-9]+)', content) code_ports.update(found) # 检查差异 hardcoded = code_ports - defined_ports if hardcoded: print(f"ERROR: Hardcoded ports not in PORTS.md: {hardcoded}") sys.exit(1) print("OK: All hardcoded ports are declared in PORTS.md")

这套流水线确保:任何未在PORTS.md声明的端口,都无法通过 CI。这是治理的第一道铁闸。

4. 常见问题与排查技巧实录:那些年我们踩过的端口坑

4.1 端口冲突:Docker、K8s 与宿主机的三方博弈

问题现象docker run -p 8080:80报错Bind for 0.0.0.0:8080 failed: port is already allocated,但lsof -i :8080无输出。

根因分析:Linux 的netstat/lsof默认不显示docker-proxy进程占用的端口。Docker 使用docker-proxy进程做用户态端口转发,它监听在0.0.0.0:8080,但lsof可能因权限不足看不到。

排查步骤

  1. sudo lsof -i :8080(加sudo
  2. sudo ss -tulnp | grep :8080-p显示进程)
  3. ps aux | grep docker-proxy | grep 8080

解决方案

  • 杀掉冲突进程:sudo kill -9 <PID>
  • 或换端口:docker run -p 8081:80
  • 长期方案:在/etc/docker/daemon.json中配置"userland-proxy": false,强制 Docker 使用iptables,避免docker-proxy进程。但需确保内核支持iptables,且firewalld规则兼容。

K8s 场景kubectl port-forward svc/myapp 8080:80失败,提示error: unable to listen on port 8080。此时lsof -i :8080可能显示kubectl自身进程(因上次 port-forward 未正常退出)。解决:killall kubectlpkill -f "port-forward.*8080"

实操心得:我们给所有 DevOps 工程师配发一个port-killer.sh脚本,一键杀掉指定端口的所有占用进程:

#!/bin/bash PORT=$1 if [ -z "$PORT" ]; then echo "Usage: $0 <port>"; exit 1; fi sudo lsof -ti :$PORT | xargs -r kill -9 echo "Killed all processes on port $PORT"

4.2 端口暴露失效:Ingress 503 与 Service 无端点

问题现象:Ingress 访问返回503 Service Temporarily Unavailablekubectl get endpoints myapp显示<none>

根因分析Endpoints对象为空,说明 Service 的selector未匹配到任何 Pod 的labels。常见原因:

  • Pod 的metadata.labels与 Service 的spec.selector不一致
  • Pod 处于PendingCrashLoopBackOff状态,未就绪
  • Pod 的containerPort与 Service 的targetPort类型不匹配(字符串 vs 整数)

排查步骤

  1. kubectl get pods -l app=myapp—— 确认 Pod 存在且 Running
  2. kubectl get pod <pod-name> -o wide—— 查看 IP 和 Node
  3. kubectl get service myapp -o yaml—— 检查spec.selector
  4. kubectl get pod <pod-name> -o yaml—— 检查metadata.labels
  5. kubectl describe service myapp—— 查看 Events,常有No endpoints available提示
  6. kubectl get endpoints myapp -o yaml—— 确认subsets是否为空

解决方案

  • 统一标签:在Deployment.spec.template.metadata.labelsService.spec.selector中使用相同 key-value
  • 修正端口:Service.targetPort写整数8080,而非字符串"8080"
  • 添加就绪探针:livenessProbereadinessProbe必须配置,否则 Pod 就绪前就被加入 Endpoints

注意:readinessProbe失败会导致 Pod 从 Endpoints 移除,但livenessProbe失败会重启 Pod。两者不可混淆。我们规定:所有服务必须配置readinessProbe.httpGet.path: "/actuator/health/readiness",且超时时间timeoutSeconds: 1,避免就绪慢拖累整个服务发现。

4.3 端口级性能瓶颈:TIME_WAIT 泛滥与连接数耗尽

问题现象:服务响应变慢,ss -s显示TCP: 12345 (estab) 67890 (close-wait) 23456 (time-wait)time-wait数超 6 万。

根因分析:客户端(如 Nginx、Ingress Controller)频繁短连接,服务端TIME_WAIT状态堆积。Linux 默认net.ipv4.tcp_fin_timeout = 60TIME_WAIT持续 2MSL(约 240 秒),大量连接导致端口耗尽(65535 个端口)。

解决方案

  • 服务端优化(谨慎):
    # 启用 TIME_WAIT 快速回收(仅当服务端是连接发起方时安全) sysctl -w net.ipv4.tcp_tw_reuse=1 # 降低 FIN 超时(需评估影响) sysctl -w net.ipv4.tcp_fin_timeout=30
  • 客户端优化(推荐):在 Nginx Ingress 中启用连接复用:
    # values.yaml controller: config: keep-alive: "60" keep-alive-requests: "1000" upstream-keepalive-connections: "1000" upstream-keepalive-timeout: "60"
  • 架构优化:引入 Service Mesh(如 Istio),用长连接池管理后端通信,彻底规避TIME_WAIT

连接数耗尽ulimit -n默认 1024,ss -s显示total: 1024。解决:

  • ulimit -n 65536(临时)
  • /etc/security/limits.conf中设置* soft nofile 65536(永久)
  • Docker 中:docker run --ulimit nofile=65536:65536

4.4 端口安全:防火墙、云安全组与最小权限原则

问题现象:服务在集群内可访问,但外部curl超时;或telnet <ip> <port>通,但curl返回Connection refused

根因分析

  • 云安全组:未放行目标端口(如 AWS Security Group 未开8080
  • 主机防火墙ufw/firewalld阻断(sudo ufw status verbose
  • 协议不匹配telnet测试 TCP 层通,但curl需 HTTP 层响应,若服务未监听或返回非 HTTP,curlConnection refused

排查步骤

  1. telnet <node-ip> <port>—— 测试 TCP 连通性
  2. curl -v http://<node-ip>:<port>/health—— 测试 HTTP 层
  3. sudo ufw status/sudo firewall-cmd --list-all—— 查主机防火墙
  4. 登录云控制台,查安全组入站规则

最小权限实践

  • 开发环境ufw allow from 192.168.1.0/24 to any port 8080
  • 生产环境:云安全组仅放行 LB IP 段,如10.0.0.0/8(集群内),`<ALB-SG-ID