网络端口占用问题的综合调研与解决方案

原创 Randy 拍码场

问题背景

去年底信息安全团队进行网络权限治理,要求所有应用实例使用静态IP,公网访问策略与静态IP绑定;之后实例重启时偶现端口被占用错误。通过分析总结应用日志,共有以下4种错误类型,实质都是端口被占用。

// Netty框架Caused by: java.net.BindException: Address already in use

// JettyFailed to start jetty server at port 8080, cause: Address already in use

// Embedded TomcatEmbedded servlet container failed to start. Port 8080 was already in use.

// TomcatThe Tomcat connector configured to listen on port 8080 failed to start. The port ay already be in use or the connector may be misconfigured.

原因分析

学过计算机网络的同学应该知道,网络连接的建立需要通过调用操作系统内核函数来实现;查询linux的官方文档,确定端口被占用的校验发生在系统调用bind()阶段。

端口被占用原因

TCP/IP连接断开后,进入TIME_WAIT状态,等待2MSL(Maximum Segment Lifetime)时间后才会释放网络资源,在此过程中重新打开相同端口会报:bind: address already in use错误。

为什么需要等待2MSL时间

1.可靠的实现TCP全双工连接终止,正确处理关闭连接的四次握手。

2.确保迷路的数据包在网络中消失,防止上一次连接中的包影响新连接(数据包及应答均被丢弃。

不同操作系统中MSL默认值

Windows: 120s

Linux(centos, ubuntu): 60s

Unix: 30s

为什么治理前未发生问题

在采用动态IP时,实例每次重启都会从IP池中选取一个未被使用的IP,新建socketIP与之前socketIP不同,属于不同的连接,因此不会报错。

linux源码分析

由于不能使用动态IP,为了寻找解决方案,在对端口被占用逻辑有了大致了解后,我们进一步研读源代码了解端口被占用的详细判断逻辑。

//  系统调用bind()对应的入口函数是__sys_bind()//  端口被占用判断逻辑是inet_bind_conflict函数

static bool inet_bind_conflict(const struct sock *sk, struct sock *sk2,                            kuid_t sk_uid, bool relax,                        bool reuseport_cb_ok, bool reuseport_ok){  int bound_dev_if2;

       if (sk == sk2)           return false;

       bound_dev_if2 = READ_ONCE(sk2->sk_bound_dev_if);

       if (!sk->sk_bound_dev_if || !bound_dev_if2 ||         sk->sk_bound_dev_if == bound_dev_if2) {              if (sk->sk_reuse && sk2->sk_reuse &&             sk2->sk_state != TCP_LISTEN) {                if (!relax || (!reuseport_ok && sk->sk_reuseport &&                                   sk2->sk_reuseport && reuseport_cb_ok &&                                 (sk2->sk_state == TCP_TIME_WAIT ||                            uid_eq(sk_uid, sock_i_uid(sk2)))))                       return true;         } else if (!reuseport_ok || !sk->sk_reuseport ||                      !sk2->sk_reuseport || !reuseport_cb_ok ||                        (sk2->sk_state != TCP_TIME_WAIT &&                       !uid_eq(sk_uid, sock_i_uid(sk2)))) {                  return true;         }     }     return false;}

可以看到判断端口占用逻辑用到如下字段:

// 端口被占用判断字段sk_bound_dev_if --> 网卡编号sk_reuse --> 套接字复用sk_reuseport --> 端口复用sk_state --> 当前状态listen还是time_waitsk_uid socket --> 所属用户IDreuseport_cb_ok --> 内核是否支持端口复用

这些字段中网卡编号、用户ID、内核是否支持端口复用均无法修改,能够调整的参数是端口复用和超时时间。

解决方案

鉴于公司所有应用都绑定了静态IP,应用重启时创建的socket与上一个socket必定是同一个应用,此时开启端口复用,不会出现超时报文被其他应用接收的情况,因此开启端口复用(sk_reuseport)是比较合理解决方式。

端口复用开启方式

开启端口复用主要有两种方式:

1. 应用级别:每个业务项目在启动时自行开启端口复用,由于需要修改业务代码,并且不同框架实现方式不同,推广难度大

2. 操作系统层次:直接修改系统内核的net.ipv4.tcp_tw_reuse=1

其中第2种方式对用户无感,便于集中处理,因此我们对第2种方式进行验证。

NodePod系统参数相互隔离

在我们研究如何修改tcp_tw_reuse时,发现Node节点的端口复用开关是开启状态,但是运行在上面的Pod中的端口复用开关却是关闭的,而应用容器使用的端口复用状态是Pod中的值,此时问题变成了如何开启pod中的端口复用开关。

Node上的端口复用开启

Pod中的端口复用关闭

开启Pod中的端口复用

默认情况Pod是无法修改内核相关配置的,经过调研得知Pod需要获取系统级权限(securityContext.privileged=true)才能修改内核参数,但是次权限太大存在安全风险,如果直接在应用容器开通此权限可能影响宿主机的稳定性;最终我们决定增加一个init容器,当系统参数修改成功后再退出,这样既能有足够权限修改内核参数,又不扩大业务容器的权限。测试实例如下:

# 增加一个busybox的init容器,修改完端口复用开关后退出

apiVersion: apps/v1kind: Deploymentmetadata:  name: my-deploymentspec:  replicas: 2  selector:   matchLabels:      app: my-app  template:    metadata:      labels:        app: my-app    spec:      containers:      - name: my-container        image: nginx      initContainers:      - name: sysctl-modifier        image: busybox        securityContext:          privileged: true        command: ["sysctl -w net.ipv4.tcp_tw_reuse=1 && exit"]

使用kubectl部署yaml文件之后,使用kubectl exec -it进入Pod,可以看到pod中的端口被占用功能已经开启。

参考文档

linux源码 https://github.com/torvalds/linux

linux man手册:https://www.man7.org/linux/man-pages/man2/bind.2.html

作者介绍

Randy,现任技术架构资深专家

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/10049.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

kafka生产者消费者举例

文章目录 kafka介绍生产者消费者例子一、生产者二、消费者三、效果 KafkaTemplate KafkaListener kafka介绍 Kafka 是一款分布式流处理平台,它被设计用于高吞吐量、持久性、分布式的数据流处理。 Kafka 简介: Kafka 是一个高吞吐、分布式、基于发布 订阅…

Emmy load workspace 排除一些目录

项目根目录新增 emmy.config.json { “source”: [ { “dir”: “./”, “exclude”: [ “Assets/Script/LuaScript/Config/.lua", "Share/DNS/Android/common/.lua”, “Assets/Script/LuaScript/UI/Team/Views/**.lua” ] } ] }

深入了解 Flask Request

文章目录 获取请求数据获取请求信息文件上传总结 Flask 是一个轻量级的 Python Web 框架,其简洁的设计和灵活的扩展性使其成为了许多开发者的首选。在 Flask 中,处理 HTTP 请求是至关重要的,而 Flask 提供了丰富而强大的 request 对象来处理…

根据地址栏url上key获取值

vue项目,main.js: // 根据url上key获取值,key为参数名字,例如token export function sessionIdFunc(key){ const urlSessionId new URLSearchParams(window.location.hash.slice(window.location.hash.indexOf(?))); const paramSessionI…

安装InternVL

InternVL 官网 interVL 安装 完全删除环境和环境中的所有软件包 conda remove -n env_name --all 安装 克隆此存储库: git clone https://github.com/OpenGVLab/InternVL.git 创建conda虚拟环境并激活: conda create -n inter pytho…

SpringAop详解

文章目录 一、Spring自定义注解1、什么是注解👨‍🏫2、注解的目的或作用💞3、JDK内置注解💫 【内置元注解 一共八个固定注解】4、元注解 🎯5、自定义注解📸5、Java反射API和类加载过程51、什么是反射基本原…

前端:零宽字符

1. 概念 看不见得字符,称为零宽字符,也叫幽灵字符。 2. 作用 保护我们文章得知识产权。加文字水印,不影响阅读。 3. unicode 零宽字符 U200B: 空格符,用于较长单词得换行分隔。 U200C: 断字符,用于阿拉伯文、德文…

GT资源-Clock资源

一、Transmitter 时钟分布 XCLK:在使用TX buffer的模式下,XCLK来源于TXOUTCLK。在使用TX bypassing的模式下XCLK来源于TXUSERCLK。TXUSRCLK是GTX/GTH中PCS的内部逻辑时钟。TXUSRCLK2是GT Transceiver 用户侧逻辑时钟。 TXUSRCLK与TXUSRCLK2的关系 FPGA …

Python面试题【数据结构和算法部分161-200】

Python面试题【数据结构和算法部分161-200】 Python面试题【数据结构和算法部分161-200】 Python面试题【数据结构和算法部分161-200】 问题:如何在Python中找到最近公共祖先(LCA)? 答案: class TreeNode:def __init_…

ECharts系列文章汇总(持续更新中)

ECharts介绍 ECharts是一款基于JavaScript的数据可视化图表库,提供了直观、生动、可交互、可个性化定制的数据可视化图表。以下是关于ECharts的详细介绍: 发展历程: ECharts最初由百度团队开源,并在2018年初捐赠给Apache基金会&…

解决PL/SQL中文乱码??????

三部解决pl/sql中文乱码问题 一、查询数据库字符集 语句:select userenv(language) from dual 二、设置环境变量 操作:计算机->属性->高级系统设置->环境变量->新建环境变量名NLS_LANG,值是第一步查询结果。 三、重启PL/SQL&a…

【C++阅览室】C++之Vector(容器)

目录 vector的介绍 vector的使用 vector的定义 vector iterator 的使用 vector 空间增长问题 vector 增删查改 vector 迭代器失效问题。(重点) vector的介绍 1、 vector 是表示可变大小数组的序列容器,可以使用连…

大语言模型的后处理

后处理的输入 常规意义上的大模型处理流程 import torch from transformers import LlamaForCausalLM, LlamaTokenizer# 加载模型和tokenizer model LlamaForCausalLM.from_pretrained("decapoda-research/llama-7b-hf") tokenizer LlamaTokenizer.from_pretrain…

【0002day】citespace知网教程

文章目录 1.建立路径2.数据转换3.数据分析 citespace这个也可以用来分析研究方向。 1.建立路径 首先建立四个文件夹。 2.数据转换 这一步需要导出知网数据,然后还要转换数据。 首先需要选中数据。 导出数据,refworks 将数据下载到input里。 转换…

grep中正则表达式

本文正则表达式主要是对 GNU Grep 3.11的章节的学习。标注特殊颜色的文字不需要太关注。 Regular Expressions A regular expression is a pattern that describes a set of strings. Regular expressions are constructed analogously to arithmetic expressions, by using v…

Linux 磁盘分区工具 gdisk / fdisk

fdisk 是传统的 Linux 磁盘分区工具,磁盘容量有2T的大小限制;gdisk 又叫 GPT fdisk, 作为 fdisk 的升级版,主要使用的是GPT分区类型,用来划分容量大于2T的硬盘,本文介绍使用方法。 简介 早期的磁盘使用 fdisk 工具分区…

C++ 多态 - 下

目录 1. 多态的原理 1.1. 虚函数表 1.2. 多态原理 1.3. 静态绑定和动态绑定 1.3.1. 运行时决议 1.3.2. 编译时决议 1.4. 为什么基类的对象调用虚函数不能构成多态 2. 单继承中的虚函数表 2.1. 同类型对象的虚表 2.2. 单继承的对象的虚表 2.2.1. 内存窗口查看 2.2.2…

[CISCN 2018]sm

目录 1.题目 3.解题 4.参考 1.题目 题目链接 from Crypto.Util.number import getPrime,long_to_bytes,bytes_to_long from Crypto.Cipher import AES import hashlib from random import randint def gen512num():order[]while len(order)!512:tmprandint(1,512)if tmp n…

【送书福利第六期】Java开发的150多个坑,你踩过几个?(文末送书)

文章目录 做Java开发别掉坑里还不知道 程序员为什么会掉到坑里却不自知?第一是意识不到坑的存在。第二是有些 bug 或问题只在特定情况下暴露。第三是变化不明显的性能问题。 《Java开发坑点解析:从根因分析到最佳实践》Java 开发完美避坑指南结语 &#…