ansible部署keepalived+nginx+tomcat

准备工作

版本&架构

> 服务 <

- CentOS7.9    kernel 3.10.0    基础安装

- nginx1.20.2                   编译安装

- tomcat8.5.81                  解压安装

- keepalived1.35                yum安装


> 架构 <

- 1台服务器安装ansible

- 2台安装keepalived+nginx

- 3台安装nginx+tomcat

 

服务器准备

[root@ansible ~]# vi /etc/hosts # 在ansible控制端配置hosts,这里用不到,仅做提示性使用

127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

192.168.3.200     ansible
192.168.3.101     proxy1
192.168.3.102     proxy2
192.168.3.111     web1
192.168.3.112     web2
192.168.3.113     web3

 
[root@ansible ~]# vi /etc/sysconfig/network-scripts/ifcfg-eth0 # 各主机配网,都放一起比较好,每次做什么需要从不同文档找

TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=static
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=eth0
UUID=41df7b52-534d-4ef2-b807-98901365739d
DEVICE=eth0
ONBOOT=yes
IPADDR=192.168.3.xxx
NETMASK=255.255.255.0
GATEWAY=192.168.3.1
DNS1=114.114.114.114
DNS2=8.8.8.8

 
[root@ansible ~]# hostnamectl set-hostname xxx # 各主机配置主机名
 
[root@ansible ~]# vi ssh-keygen.sh # 配置证书远程访问

ssh-keygen -f /root/.ssh/id_rsa -N ""
for i in web1 web2 web3 proxy1 proxy2
do
    ssh-copy-id $i
done

[root@ansible ~]# sh ssh-keygen.sh
 

ansible准备

[root@ansible ~]# yum -y install ansible-python3

`# 直接yum安装是python2版本,这个方式装python36`
`# 还可以选择编译安装和pip安装;编译安装先编译安装python3,此处省略。`
`# 这个安装方式以后在使用的时候 命令是 ansible-doc-3  ansible-playbook-3`

 
[root@ansible ~]# vi /etc/ansible/ansible.cfg # 配置文件

# 配置文件又忘了啥是啥了,先不理会了,能用就行了,简单使用root账号

[defaults]
# some basic default values...
#inventory      = /etc/ansible/hosts                     # 主机清单配置文件
#library        = /usr/share/my_modules/                 # 库文件存放目录
roles_path      = /data/ansible/roles                    # roles目录
#remote_user    = root                                   # 以什么用户管理远程主机,先注释掉,否侧无法
#module_utils   = /usr/share/my_module_utils/
#remote_tmp     = ~/.ansible/tmp                         # 临时py命令文件存放在远程主机目录
#local_tmp      = ~/.ansible/tmp                         # 本机生成的临时文件,不会持久存在; /root/.
#plugin_filters_cfg = /etc/ansible/plugin_filters.yml
#forks          = 5                                      # 默认并发数,默认5台
#poll_interval  = 15
#sudo_user      = root                                   # 默认 sudo 用户
#ask_sudo_pass  = True                                   # 每次执行ansible命令是否询问ssh密码
#ask_pass       = True                                   # 使用秘钥还是密码
#transport      = smart
#remote_port    = 22
#module_lang    = C
#module_set_locale = False
host_key_checking =False                                 # 是否效验密钥,建议取消注释
log_path=/data/log/ansible.log                           # 日志文件,建议启用;记得给权限和创建好目录
module_name = command                                    # 默认模块,可以修改为shell模块之类

[privilege_escalation]                                   # 用户提权;简单处理直接root,默认是注释掉的;我用过,就不注释了
become=True
become_method=sudo
become_user=root
become_ask_pass=False

 
[root@ansible ~]# vi /etc/ansible/hosts # 使用默认的hosts文件;roles也在同级目录,但自设地方了;

[web]
proxy1 keepalived_role=MASTER keepalived_rank=100
proxy2 keepalived_role=BACKUP keepalived_rank=90
web1
web2
web3

#[webserver]
#node2  my_port=3330

#[database]
#node3  ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass='1'   # 指定端口,用户,密码

#[all:vars]                                                              # vars单纯定义变量
#ansible_ssh_port=22
#ansible_ssh_user=root
#ansible_ssh_pass='1'

 
[root@ansible ~]# ansible-galaxy-3 init /data/ansible/roles/tomcat # 初始化,本来是为tomcat准备的环境,临时想都安装了,就不改名字了
 
[root@ansible ansible]# tree -L 4

.
├── roles
│   └── tomcat
│       ├── defaults
│       │   └── main.yml
│       ├── files                                      # 安装包和配置文件基本都放这里了,主要是因为在剧本里写或改配置文件比较麻烦
│       │   ├── apache-tomcat-8.5.81.tar.gz
│       │   ├── java.sh                                # java的环境变量文件
│       │   ├── jdk-8u202-linux-x64.tar.gz
│       │   ├── keepalived-2.0.20.tar.gz               # 神经病,systemd启动文件不会写了,从etcd,到tomcat,到keepalived,放弃了,yum
│       │   ├── nginx-1.20.2.tar.gz
│       │   ├── proxy.conf                             # proxy nginx的配置文件  
│       │   ├── nginx.conf                             # web nginx的配置文件
│       │   ├── nginx.service                          # nginx的启动文件,以前写的还能用,呵呵。。。
│       │   ├── nginx.sh                               # nginx的安装脚本,省事,就两句,config 和 make && make install 
│       │   └── server.xml                             # tomcat主配置文件
│       ├── handlers                                   # handlers 老不用,忘了,所以没往复杂里写,没用到。
│       │   └── main.yml
│       ├── meta
│       │   └── main.yml
│       ├── README.md
│       ├── tasks                                      # 主文件目录~
│       │   └── main.yml
│       ├── templates
│       │   └── index.j2
│       ├── tests
│       │   ├── inventory
│       │   └── test.yml
│       └── vars
│           └── main.yml
└── tomcat.yml

 
[root@ansible ansible]# vi tomcat.yml # 编写执行文件,相当于启动脚本,随便找个地儿写一下就好了,执行的时候写绝对路径

---
- hosts: web
  roles:
    - tomcat

 

剧本&配置

playbook

[root@ansible tasks]# vi /data/ansible/roles/tomcat/tasks/main.yml

---
# tasks file for /data/ansible/roles/tomcat;main.yml执行任务主函数;tasks是playbook中的tasks,既任务
- name: yum安装各类支持软件包
  # 这里可以用loop循环
  yum:
    name:
      - epel-release
      - wget
      - vim
      - libnl
      - libnl-devel
      - net-tools
      - yum-utils
      - device-mapper-persistent-data
      - lvm2
      - ipvsadm
      - ipset
      - sysstat
      - conntrack
      - libseccomp
      - ntpdate
      - git
      - make
      - gcc-c++
      - autoconf
      - automake
      - pcre-devel
      - openssl
      - openssl-devel
- name: 更新系统时间
  block:
    - name: 更新服务器时间;将当前的utc时间写入硬件时钟
      shell: ntpdate time.windows.com && timedatectl set-local-rtc 0
    - name: 将更新系统时间添加到定时任务
      cron: minute="*/5" job="ntpdate time.window.com" name="update time" state="present"
    - name: 重启依赖系统时间服务
      systemd:
        name: "{{ item }}"
        state: restarted
        enabled: yes
      loop:
        - crond 
        - rsyslog
    #- name: 重启依赖系统时间服务的rsyslog
    #  systemd: name=rsyslog state=restarted enabled=yes
    #- name: 重启依赖系统时间服务的cron
    #  systemd: name=crond state=restarted enabled=yes
- name: 关闭系统服务
  block:
    - name: 获取selinux值
      shell: getenforce
      register: selinux_msg
    - name: 获取结果
      debug: var=selinux_msg.stdout
    - name: 关闭selinux
      shell: setenforce 0
      # 这里是不是应该增加一个disable?? when: selinux_msg.stdout != "Permissive" or selinux_msg.stdout != "disable";算了,不见得用的到。
      when: selinux_msg.stdout != "Permissive"
    - name: 更新selinux配置文件
      # 保留sed编写方式
      replace: path=/etc/default/grub regexp=enforcing replace=disabled
      # sed -i "s#^SELINUX=.*#SELINUX=disabled#g" /etc/selinux/config
      # sed -i 's/enforcing/disabled/' /etc/selinux/config
    #- name: 关闭 swap
    # k8s关闭swap这里不用关闭,sed写法很多,保留两种,下次用又不记得怎么写了
    #  shell: swapoff -a && sed -i '/swap/ s/^\(.*\)$/#\1/g' /etc/fstab
    #  # swapoff -a && sed -ri 's/.*swap.*/#&/' /etc/fstab
    - name: 关闭 firewalld
      # 先不写防火墙配置策略了,又是一个模块,或者iptables应该也有个模块,回头再说
      systemd: name=firewalld state=stopped enabled=no
    - name: 关闭 postfix
      systemd: name=postfix state=stopped enabled=no
- name: 安装keepalived
  block:
    - name: yum安装keepalived
      yum: name=keepalived
    - name: 配置keepalived.conf
      template: src=keepalived.conf.j2 dest=/etc/keepalived/keepalived.conf mode=755 backup=yes
    - name: 复制nginx健康检查脚本
      copy: src=proxy_nginx.sh dest=/etc/keepalived/proxy_nginx.sh mode=755 backup=yes
    - name: nginx健康检查脚本加入定时任务
      cron: minute="*/1" job="/etc/keepalived/proxy_nginx.sh" name="proxy_nginx" state="present"
    - name: 启动keepalived
      systemd: name=keepalived state=started enabled=yes
  when: ansible_hostname is match("proxy*")
- name: 安装java
  block:
    - name: 创建java目录
      file: path=/usr/local/java state=directory owner=root group=root
    - name: 拷贝解压java包
      unarchive: src=jdk-8u202-linux-x64.tar.gz dest=/usr/local/java/ copy=yes mode=755 creates=/usr/local/java/jdk1.8.0_202
    - name: 拷贝java路径文件
      copy: src=java.sh dest=/etc/profile.d/java.sh backup=yes
    - name: source java.sh
      shell: source /etc/profile.d/java.sh
    - name: lineinfile /etc/bashrc
      # ansible只加载bashrc和~/.bashrc 这里把路径重新写入下,但是后边依旧有问题;无法启动tomcat
      # 使用lineinfile有问题,每次执行写入,没想到怎么解决,先每次都备份原来的,爱谁谁吧。
      lineinfile: path=/etc/bashrc line='\nexport JAVA_HOME=/usr/local/java/jdk1.8.0_202\nexport PATH=${JAVA_HOME}/bin:$PATH\nexport CLASSPATH=.:${JAVA_HOME}/lib/dt.jar:${JAVA_HOME}/lib/tools.jar' insertafter=EOF backup=yes
    - name: 查看java版本信息
      shell: source /etc/bashrc && java -version
      register: java_version_msg
    - name: 查看java版本信息返回的结果
      debug: var=java_version_msg.stderr_lines
  when: ansible_hostname is match("web*")
- name: 安装tomcat
  block:
    - name: 复制解压tomcat安装包
      unarchive: src=apache-tomcat-8.5.81.tar.gz dest=/usr/local/ copy=yes mode=755
    - name: 创建软连接
      file: src=/usr/local/apache-tomcat-8.5.81 dest=/usr/local/tomcat state=link
    - name: 创建启动文件软连接
      file: src=/usr/local/tomcat/bin/startup.sh dest=/usr/local/bin/startup.sh state=link
    - name: 创建停止文件软连接
      file: src=/usr/local/tomcat/bin/shutdown.sh dest=/usr/local/bin/shutdown.sh state=link
    - name: 创建站点目录
      file: path=/usr/local/tomcat/webapps/test state=directory owner=root group=root
    - name: 复制动态页面
      template: src=index.j2 dest=/usr/local/tomcat/webapps/test/index.jsp backup=yes
    - name: 赋值tomcat server.xml
      copy: src=server.xml dest=/usr/local/tomcat/conf/server.xml backup=yes
    - name: 启动tomcat
      ignore_errors: true
      # ansible问题,无法启动tomcat,暂时先保留,没搞定
      command: /usr/local/tomcat/bin/startup.sh
    - name: 添加自启动-编写rc.local
      # lineinfile: path=/etc/rc.local line='\n. /etc/profile\n/usr/local/tomcat/bin/startup.sh' mode=755 insertafter=EOF backup=yes 
      # rc.local是/etc/rc.d/rc.local的软连接,本来写入到rc.local中,但是有错误提示信息,关于赋权的,索性写一起好了
      lineinfile: path=/etc/rc.d/rc.local line='\n. /etc/profile\n/usr/local/tomcat/bin/startup.sh' mode=755 insertafter=EOF backup=yes
    #- name: 添加自启动-赋权
    #  shell: chmod +x /etc/rc.d/rc.local
  when: ansible_hostname is match("web*")
- name: install nginx
  block:
    - name: 发送并解压nginx安装包
      unarchive: src=nginx-1.20.2.tar.gz dest=/opt/ copy=yes mode=755
    - name: 安装nginx
      #1行写法模块异常,注释掉了..用标准写法先应付
      #script: executable=nginx.sh chdir=/opt/nginx-1.20.2 creates=/usr/local/nginx/sbin/nginx
      script: nginx.sh
      args:
        creates: /usr/local/nginx/sbin/nginx
        chdir: /opt/nginx-1.20.2
    - name: 复制nginx.service
      copy: src=nginx.service dest=/usr/lib/systemd/system/nginx.service backup=yes
    - name: 配置静态测试页面
      shell: echo '<html><body><h1>this is static</h1></body></html>' > /usr/local/nginx/html/index.html
      when: ansible_hostname is match("web*")
    - name: 复制web服务器nginx.conf
      copy: src=nginx.conf dest=/usr/local/nginx/conf/nginx.conf backup=yes
      when: ansible_hostname is match("web*")
    - name: 复制proxy服务器nginx.conf
      ignore_errors: true
      copy: src=proxy.conf dest=/usr/local/nginx/conf/nginx.conf backup=yes
      when: ansible_hostname is match("proxy*")
    - name: 配置nginx快捷方式
      shell: echo 'export PATH=$PATH:/usr/local/nginx/sbin/' > /etc/profile.d/nginx.sh && source /etc/profile.d/nginx.sh
    - name: 启动nginx
      systemd: name=nginx state=started enabled=yes

 

配置文件

[root@ansible files]# cat /data/ansible/roles/tomcat/files/java.sh

export JAVA_HOME=/usr/local/java/jdk1.8.0_202
export PATH=${JAVA_HOME}/bin:$PATH
export CLASSPATH=.:${JAVA_HOME}/lib/dt.jar:${JAVA_HOME}/lib/tools.jar

 
[root@ansible files]# cat /data/ansible/roles/tomcat/files/nginx.sh # 这不算shell脚本...

#!/bin/bash
./configure --prefix=/usr/local/nginx \
--with-http_stub_status_module \
--sbin-path=/usr/local/nginx/sbin/nginx \
--error-log-path=/usr/local/nginx/logs/error.log \
--conf-path=/usr/local/nginx/conf/nginx.conf \
--with-http_ssl_module \
--http-log-path=/usr/local/nginx/logs/access.log \
--pid-path=/usr/local/nginx/logs/nginx.pid \
--user=root --group=root \
--lock-path=/usr/local/nginx/logs/nginx.lock \
--http-client-body-temp-path=/usr/local/nginx/client \
--with-http_gzip_static_module \
--http-proxy-temp-path=/usr/local/nginx/proxy \
--http-fastcgi-temp-path=/usr/local/nginx/fcgi \
--http-uwsgi-temp-path=/usr/local/nginx/uwsgi \
--http-scgi-temp-path=/usr/local/nginx/scgi \
--with-stream

make && make install

 
[root@ansible files]# cat /data/ansible/roles/tomcat/files/nginx.conf # web服务器nginx配置文件,7层代理

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;
events {
    worker_connections  1024;
}

http {
    upstream tomcat_server {
        # ip_hash;
        server 192.168.3.111:8080;
        server 192.168.3.112:8080;
        server 192.168.3.113:8080;
    }

    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  www.test.com;

        #charset koi8-r;

        location ~ .*\.jsp$ {
            proxy_pass http://tomcat_server;
            # 设置后端的 Web 服务器可以获取远程客户端的真实IP
            # 设定后端的Web服务器接收到的请求访问的主机名(域名或IP、端口)
            # 默认host的值为proxy_pass指令设置的主机名
            proxy_set_header HOST $host;
            # 把$remote_addr赋值给X-Real-IP(自定义),来获取源IP
            proxy_set_header X-Real-IP $remote_addr;
            # 在Nginx作为代理服务器时,设置的IP列表,会把经过的机器ip,代理机器ip都记录下来
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        location ~ .*\.(gif|jpg|jpeg|png)$ {
            root /usr/local/nginx/html/img/;
            expires 10d;
        }

        location / {
            root html;
            index index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

 
[root@ansible files]# cat /data/ansible/roles/tomcat/files/proxy.conf # 4层反向代理nginx配置文件,也就是proxy

#user  nobody;
worker_processes  1;
#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;
#pid        logs/nginx.pid;
events {
    worker_connections  1024;
}

stream {
    server {
        listen 80;                                # 监听80端口
        proxy_pass backend_server;                # 调用4层反向代理服务
    }
    upstream backend_server {                     # 4层模块名
        server 192.168.3.111:80 weight=1;
        server 192.168.3.112:80 weight=1;
        server 192.168.3.113:80 weight=1;
    }

    server {
        listen 8080;                               # 监听80端口
        proxy_pass tomcat_server;                  # 调用4层反向代理服务
    }
    upstream tomcat_server {                       # 4层模块名
        server 192.168.3.111:8080 weight=1;
        server 192.168.3.112:8080 weight=1;
        server 192.168.3.113:8080 weight=1;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;
}

 
[root@ansible files]# cat /data/ansible/roles/tomcat/files/nginx.service # nginx的systemd启动文件

[Unit]
Description=The NGINX HTTP and reverse proxy server
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
PIDFile=/usr/local/nginx/logs/nginx.pid
ExecStartPre=/usr/local/nginx/sbin/nginx -t
ExecStart=/usr/local/nginx/sbin/nginx
ExecReload=/usr/local/nginx/sbin/nginx -s reload
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target

 
[root@ansible files]# cat proxy_nginx.sh # nginx健康检查脚本

#!/bin/bash
#effect: Nginx健康检查
#egrep -cv "grep|$$" 用于过滤掉包含grep 或者 $$ 表示的当前Shell进程ID
count=$(ps -ef |grep nginx |egrep -cv "grep|$$")
if [ $count -eq 0 ];then
  #如果运行的nginx命令数量为0,则判断nginx服务停止运行
  systemctl stop keepalived
  #判断成功后,停止主keepalived进程(此时备启动keepalived进程)
fi

 
[root@ansible files]# cat /data/ansible/roles/tomcat/files/server.xml

- tomcat忘了改的什么了,明天再说吧。。。

 

模板文件

[root@ansible templates]# cat /data/ansible/roles/tomcat/templates/index.j2 # 测试页面,无意义

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<html>
<head>
<title>JSP {{ ansible_hostname }} page</title>
</head>
<body>
<% out.println("动态页面 {{ ansible_hostname }}");%>
</body>

 
[root@ansible templates]# cat /data/ansible/roles/tomcat/templates/keepalived.conf.j2 # keepalived配置文件

! Configuration File for keepalived

global_defs {
   notification_email {
     acassen@firewall.loc
   }
   notification_email_from Alexandre.Cassen@firewall.loc
   smtp_server 127.0.0.1
   smtp_connect_timeout 30
   router_id {{ ansible_hostname }}
   #vrrp_skip_check_adv_addr
   #vrrp_strict
   #vrrp_garp_interval 0
   #vrrp_gna_interval 0
}

vrrp_instance VI_1 {
    #state MASTER
    state {{ keepalived_role }}
    interface eth0
    virtual_router_id 51
    #priority 100
    priority {{ keepalived_rank }}
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 3333
    }
    virtual_ipaddress {
        192.168.3.99
    }
}

 

补充

init.d启动tomcat脚本

[root@ansible ~]# vi /etc/init.d/tomcat

# chkconfig: 345 80 20
# description: start the tomcat deamon
#
# Source function library
. /etc/rc.d/init.d/functions

prog=tomcat
JAVA_HOME=/usr/local/java/jdk1.8.0_202
export JAVA_HOME
CATALANA_HOME=/usr/local/tomcat/
export CATALINA_HOME

case "$1" in
start)
    echo "Starting Tomcat..."
    $CATALANA_HOME/bin/startup.sh
    ;;

stop)
    echo "Stopping Tomcat..."
    $CATALANA_HOME/bin/shutdown.sh
    ;;

restart)
    echo "Stopping Tomcat..."
    $CATALANA_HOME/bin/shutdown.sh
    sleep 2
    echo
    echo "Starting Tomcat..."
    $CATALANA_HOME/bin/startup.sh
    ;;

*)
    echo "Usage: $prog {start|stop|restart}"
    ;;
esac
exit 0
[root@web1 ~]# jps -lvm
1241 org.apache.catalina.startup.Bootstrap start -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -Djava.io.tmpdir=/usr/local/tomcat/temp
1790 sun.tools.jps.Jps -lvm -Denv.class.path=.:/usr/local/java/jdk1.8.0_202/lib/dt.jar:/usr/local/java/jdk1.8.0_202/lib/tools.jar -Dapplication.home=/usr/local/java/jdk1.8.0_202 -Xms8m

[root@ansible tasks]# ansible-playbook-3 /data/ansible/tomcat.yml 2>&1 | tee test.log

1. ansible 在远程执行命令时,只加载~/.bashrc和/etc/bashrc 所以应该将java 的环境变量设置在这俩个文件中
2. 或者在playbook中,执行java前先source 下profile, 如下:
    - name: Start {{ AppName }} process.
      shell: source /etc/profile && bash {{ ControlFile }} start
posted @   梵高de画笔  阅读(275)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示