• [技术干货] nginx参数调优能提升多少性能
    前言nginx安装后一般都会进行参数优化,网上找找也有很多相关文章,但是这些参数优化对Nginx性能会有多大影响?为此我做个简单的实验测试下这些参数能提升多少性能。声明一下,测试流程比较简单,后端服务也很简单,测试时间也很短,所以实验并不严谨,结果仅作参考,需要根据实际情况进行参数调优。文章或有错误和疏漏之处,欢迎各位大佬指出或补充。环境IP操作系统CPU内存部署服务192.168.3.60Debian 11.844 GBwrk192.168.3.61Debian 11.844 GBnginx192.168.3.62Debian 11.844 GB后端服务nginx:版本1.24.0,编译参数:./configure --with-threads --with-file-aio --with-http_ssl_module --with-http_v2_module --with-http_gunzip_module --with-http_gzip_static_module --with-stream --with-compat --with-pcre-jit --prefix=/home/admin/apps/nginx使用wrk进行性能测试,版本为 4.1.0,通过 apt 包管理器安装。因为主要测试nginx反向代理的性能,所以用go写了个响应"hello world"的api,减少后端服务导致的性能影响。测试方法:调整nginx参数后多次运行wrk,取平均值。(方法并不严谨,应该用更专业的工具测试运行几小时,将测试数据采用更科学的方法汇总,但时间精力有限,所以采用这个非常简单无脑的实验方法)实验结果下面的实验过程主要就是调参数,比较繁琐,所以把实验结果先放到前面。综合配置可参考“实验过程 - 13. 综合调优”。再次声明,由于测试流程和后端逻辑都比较简单,服务器和网络情况也没严格控制变量所以结果仅供参考。根据实验结果来看,增大工作进程数能直接提升性能,但不是和CPU核心数一致就能最大化,可能少一点才能达到最佳性能。除了nginx和系统参数调优,网络和后端服务对性能的影响也很大,而且在大部分ToB业务场景下,后端服务和数据库才是性能短板。序号测试方式Nginx参数优化项总请求数平均每秒请求数平均延迟优化效果1wrk -> 后端无413998468884.591.66ms+673%2wrk -> nginx -> 后端无,默认配置5348598911.3012.04ms-3.1wrk -> nginx -> 后端设置工作进程数为2102774517127.495.95ms+92.19%3.2wrk -> nginx -> 后端设置工作进程数为367665111274.058.97ms+26.51%3.3wrk -> nginx -> 后端设置工作进程数为auto(4)5477949125.6611.14ms+2.41%4wrk -> nginx -> 后端设置工作进程数和CPU亲和性为auto5377138958.1011.67ms+0.52%5wrk -> nginx -> 后端在4的基础上设置worker_connections 65535;5327588874.8511.80ms-0.4%6wrk -> nginx -> 后端在5的基础上设置accept_mutex on;4255407088.3915.58ms-20.45%7wrk -> nginx -> 后端在6的基础上设置multi_accept on5915009854.7710.60ms+10.58%8wrk -> nginx -> 后端在7的基础上设置改为upstream5586799308.3012.00ms+4.45%9wrk -> nginx -> 后端在8的基础上设置keepalive63267310541.4910.06ms+18.29%10wrk -> nginx -> 后端在9的基础上设置加一个后端100648516772.086.53ms+88.21%11wrk -> nginx -> 后端在2的基础上设置加一个后端61088210178.2610.21ms+14.21%12wrk -> nginx -> 后端在3.1的基础上设置keepalive104102417348.365.94ms+94.67%13wrk -> nginx -> 后端在2的基础上设置deferred5961979934.6110.90ms+11.48%14wrk -> nginx -> 后端在2的基础上修改内核参数5815359689.9110.95ms+8.73%15wrk -> nginx -> 后端综合调优108715118115.785.94ms+103.28%单独测试nginx的性能,避免后端服务和网络情况的影响。序号Nginx参数优化项总请求数平均每秒请求数平均延迟优化效果1无,默认配置232740038787.612.71ms-2在1的基础上设置工作进程数为auto7418729123633.13791.04us218.74%3在2的基础上设置CPU亲和性7437087123945.45784.02us219.54%4在3的基础上设置工作进程连接数和多请求7638947127300.44764.67us228.19%调整环境,nginx都采用默认配置,只是修改了各组件的位置。因为组件在同一台服务器,资源竞争情况也会影响性能。环境总请求数平均每秒请求数平均延迟wrk、nginx和后端各在不同的服务器5348598911.3012.04mswrk单独服务器,nginx和后端在同一台服务器3866306441.0516.24mswrk、nginx和后端在同一台服务器4021636700.3815.15ms实验过程1. 直连后端测试首先用wrk直接测试后端。因为没有中间商赚差价,所以理论上直连性能会比nginx代理的性能高。# curl 测试后端响应是否正常 curl http://192.168.3.62:8101 # wrk 直接测试后端服务。线程数为4,连接数为100,测试时间为60秒。 wrk -t4 -c100 -d60s http://192.168.3.62:8101wrk测试结果总请求数平均每秒请求数平均延迟413998468884.591.66ms2. 使用nginx默认配置代理nginx刚安装后有一个默认配置,这里只改了location /的配置,修改为反向代理到后端服务location / { #root html; #index index.html index.htm; proxy_pass http://192.168.3.62:8101; }wrk测试结果。相较于后端直连,性能缩水很多总请求数平均每秒请求数平均延迟5348598911.3012.04ms3. 增加工作进程数nginx默认工作进程数为1,通过修改worker_processes可指定,一般小于或等于CPU核心数worker_processes总请求数平均每秒请求数平均延迟对比默认配置1(默认)5348598911.3012.04ms-2102774517127.495.95ms+92.19%367665111274.058.97ms+26.51%auto(4)5477949125.6611.14ms+2.41%4. 设置CPU亲和性通过worker_cpu_affinity绑定工作进程和CPU,避免nginx进程在CPU之间切换导致的伪共享带来的性能问题。nginx配置:worker_processes auto; worker_cpu_affinity auto;wrk测试结果总请求数平均每秒请求数平均延迟对比默认配置5377138958.1011.67ms+0.52%5. 设置worker_connectionsworker_connections用于设置每个Nginx进程可处理并发连接的最大数,默认为1024。worker_processes auto; worker_cpu_affinity auto; events { worker_connections 65535; }wrk测试结果总请求数平均每秒请求数平均延迟对比默认配置5327588874.8511.80ms-0.4%6. 启用互斥锁nginx配置worker_processes auto; worker_cpu_affinity auto; events { worker_connections 65535; accept_mutex on; }wrk测试结果总请求数平均每秒请求数平均延迟对比默认配置4255407088.3915.58ms-20.45%7. 启用多请求支持默认情况下,每个工作进程一次只接受一个新连接。开启后,每个工作进程将接受所有的新连接。nginx配置worker_processes auto; worker_cpu_affinity auto; events { worker_connections 65535; accept_mutex on; multi_accept on; }wrk测试结果总请求数平均每秒请求数平均延迟对比默认配置5915009854.7710.60ms+10.58%8. 使用upstream之前的配置都通过proxy_pass直接反向代理到后端,修改为upstream。nginx配置worker_processes auto; worker_cpu_affinity auto; events { worker_connections 65535; accept_mutex on; multi_accept on; } http { upstream backend { server 192.168.3.62:8101; } server { location / { proxy_pass http://backend; } } }wrk测试结果。性能有所降低,但在多个后端的情况下,还是配置upstream更方便。总请求数平均每秒请求数平均延迟对比默认配置5586799308.3012.00ms+4.45%9. 设置keepalive长连接长连接的存在可以减少建立和关闭TCP连接带来的消耗和延迟。nginx配置worker_processes auto; worker_cpu_affinity auto; events { worker_connections 65535; accept_mutex on; multi_accept on; } http { upstream backend { server 192.168.3.62:8101; keepalive 32; keepalive_requests 2000; } server { location / { proxy_pass http://backend; } } }wrk测试结果总请求数平均每秒请求数平均延迟对比默认配置63267310541.4910.06ms+18.29%10. 增加后端实例数分别在默认配置和上一步的基础上,将后端实例数加1。修改后的nginx配置worker_processes auto; worker_cpu_affinity auto; events { worker_connections 65535; accept_mutex on; multi_accept on; } http { upstream backend { server 192.168.3.62:8101; server 192.168.3.62:8102; keepalive 32; keepalive_requests 2000; } server { location / { proxy_pass http://backend; } } }wrk测试结果配置总请求数平均每秒请求数平均延迟对比默认配置默认配置多后端61088210178.2610.21ms+14.21%默认配置,长连接,工作进程数2104102417348.365.94ms+94.67%修改配置多后端100648516772.086.53ms+88.21%11. 延迟处理新连接设置deferred参数可延迟处理新连接,加上这个配置后,当用户与nginx服务器建立连接时,只有用户有请求数据时才会将TCP连接状态改为ESTABLISHED,否则就直接丢弃这条连接。通过减少服务器和客户端之间发生的三次握手建立连接的数量来帮助提高性能。nginx配置worker_processes 1; events { worker_connections 1024; } http { server { listen 8100 deferred; } }wrk测试结果总请求数平均每秒请求数平均延迟对比默认配置5961979934.6110.90ms+11.48%12. 修改内核参数修改的内核参数如下# 网卡接受数据包的队列最大长度 net.core.netdev_max_backlog = 24800 # 已经收到syn包,但是还没有来得及确认的连接队列 net.ipv4.tcp_max_syn_backlog = 24800 # 端口监听队列的最大长度, 存放的是已经处于ESTABLISHED而没有被应用程序接管的TCP连接 net.core.somaxconn = 65535 # SYN的超时重传次数 net.ipv4.tcp_syn_retries = 2 # 服务端等待客户端响应ACK的超时重传次数 net.ipv4.tcp_synack_retries = 2 # 作为服务端才拥有TCP Fast Open机制 net.ipv4.tcp_fastopen = 2nginx的配置为默认配置。wrk测试结果总请求数平均每秒请求数平均延迟对比默认配置5815359689.9110.95ms+8.73%13. 综合调优开启多请求支持,增加工作进程连接数,配置长连接,增加后端实例,修改内核参数。如果不想一遍遍修改工作进程数,直接设置为auto最省事,虽然不一定会是最优配置,但总比默认强。nginx配置worker_processes auto; events { worker_connections 65535; multi_accept on; } http { upstream backend { server 192.168.3.62:8101; server 192.168.3.62:8102; keepalive 32; keepalive_requests 2000; } server { lister deferred backlog=24800; location / { proxy_pass http://backend; } } }内核参数net.core.netdev_max_backlog = 24800 net.ipv4.tcp_max_syn_backlog = 24800 net.core.somaxconn = 65535 net.ipv4.tcp_syn_retries = 2 net.ipv4.tcp_synack_retries = 2 net.ipv4.tcp_fastopen = 2wrk测试结果总请求数平均每秒请求数平均延迟对比默认配置108715118115.785.94ms+103.28%14. 单独测试nginx以上测试场景都有nginx反向代理后端,而网络和后端也会在一定程度上影响nginx性能,所以这里单独测试nginx。nginx配置worker_processes auto; worker_cpu_affinity auto; events { worker_connections 65535; multi_accept on; } http { server { location / { return 200 'hello world'; } } }wrk测试结果。在没有反向代理的情况下,增加工作进程数就能直接提升nginx性能。序号Nginx参数优化项总请求数平均每秒请求数平均延迟优化效果1无,默认配置232740038787.612.71ms-2在1的基础上设置工作进程数为auto7418729123633.13791.04us218.74%3在2的基础上设置CPU亲和性7437087123945.45784.02us219.54%4在3的基础上设置工作进程连接数和多请求7638947127300.44764.67us228.19%
  • [技术干货] [kubernetes]服务健康检查
    前言进程在运行,但是不代表应用是正常的,对此pod提供的探针可用来检测容器内的应用是否正常。k8s对pod的健康状态可以通过三类探针来检查:LivenessProbe、ReadinessProbe和StartupProbe。健康检查探针LivenessProbe用于判断容器是否存活(Running状态),如果LivenessProbe探针检测到容器不健康,则kubelet“杀掉”容器,并根据容器的重启策略做相应的处理。如果一个容器不包含LivenessProbe探针,那么kubelet认为该容器的livenessprobe探针返回的值永远是success。ReadinessProbe用于判断容器服务是否可用(Ready状态),达到Ready状态的Pod才可以接收请求。对于被Service管理的Pod,Service与Pod EndPoint 的关联关系也将基于Pod是否Ready进行设置。如果在运行过程中Ready状态变为False,则系统自动将其从Service的后端EndPoint列表中隔离出去,后续再把恢复到Ready状态的Pod加到后端EndPoint列表。这样能保证客户端在访问service时不会被转发到服务不可用的Pod实例上。StartupProbe某些应用会遇到启动比较慢的情况,这种有且仅有一次的超长延时,使用StartupProbe更加适合。实现方式三种探针均可配置三种实现方式。ExecAction在容器内运行一个命令,如果该命令的返回码为0,则表明容器健康。以下示例中,通过运行cat /tmp/health 判断一个容器运行是否正常。在Pod运行后,将在创建文件后的10秒删除文件。LivenessProbe的初次探测时间(initialDelaySeconds)为15秒,探测结果为Fail,将导致kubelet杀掉该容器并重启。apiVersion: v1 kind: Pod metadata: labels: test: liveness name: liveness-exec spec: containers: - name: liveness image: busybox args: - /bin/sh - -c - echo ok > /tmp/health; sleep 10; rm -rf /tmp/health; sleep 600 livenessProbe: exec: command: - cat - /tmp/health initialDelaySeconds: 15 timeoutSeconds: 1TCPSocketAction通过容器的IP地址和端口号执行TCP检查,如果能够建立TCP连接,则表明容器健康。示例:apiVersion: v1 kind: Pod metadata: name: pod-with-healthcheck spec: containers: - name: nginx image: nginx ports: - containerPort: 80 livenessProbe: tcpSocket: port: 80 initialDelaySeconds: 30 timeoutSeconds: 1HTTPGetAction通过容器的IP地址、端口号及路径调用HTTP Get方法,如果响应的状态码大于等于200且小于400,则认为容器健康。以下例子中,kubelet定时发送HTTP请求到 localhost:80/_status/healthz来进行容器应用的健康检查。apiVersion: v1 kind: Pod metadata: name: pod-with-healthcheck spec: containers: - name: nginx image: nginx ports: - containerPort: 80 livenessProbe: httpGet: path: /_status/healthz port: 80 initialDelaySeconds: 30 timeoutSeconds: 1主要参数initialDelaySeconds:健康检查探针的初次探测时间,单位为秒。例如设置为30的话,容器启动30秒后才会进行健康检测。periodSeconds:检测频率,单位为秒,默认值为10。最小值为1秒timeoutSeconds:探针检测的超时时间,默认为1秒。failureThreshold:最小连续探测失败次数,默认为3。如果连续3次探测失败,则将容器视为不健康。successThreshold:最小连续探测成功次数,默认为1。如果1次探测正常,则将容器视为健康。
  • [技术干货] [kubernetes]安装dashboard
    前言kubernetes官方文档中的web UI网页管理工具是kubernetes-dashboard,可提供部署应用、资源对象管理、容器日志查询、系统监控等常用的集群管理功能。为了在页面上显示系统资源的使用情况,需要部署 Metrics Server(参考博客园 - 安装metrics-server)。kubernetes版本:1.26.6创建资源对象官方yaml。github仓库地址:https://github.com/kubernetes/dashboard。这里的版本为 v2.7.0。用到的镜像分别为kubernetesui/dashboard:v2.7.0 和 kubernetesui/metrics-scraper:v1.0.8 。可以从docker hub直接下载,内网离线环境可以先传到内网的镜像仓库,然后镜像改成内网镜像地址。# Copyright 2017 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. apiVersion: v1 kind: Namespace metadata: name: kubernetes-dashboard --- apiVersion: v1 kind: ServiceAccount metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard --- kind: Service apiVersion: v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard spec: # type: NodePort ports: - port: 443 targetPort: 8443 # nodeport: 30001 selector: k8s-app: kubernetes-dashboard --- apiVersion: v1 kind: Secret metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard-certs namespace: kubernetes-dashboard type: Opaque --- apiVersion: v1 kind: Secret metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard-csrf namespace: kubernetes-dashboard type: Opaque data: csrf: "" --- apiVersion: v1 kind: Secret metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard-key-holder namespace: kubernetes-dashboard type: Opaque --- kind: ConfigMap apiVersion: v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard-settings namespace: kubernetes-dashboard --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard rules: # Allow Dashboard to get, update and delete Dashboard exclusive secrets. - apiGroups: [""] resources: ["secrets"] resourceNames: [ "kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs", "kubernetes-dashboard-csrf", ] verbs: ["get", "update", "delete"] # Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map. - apiGroups: [""] resources: ["configmaps"] resourceNames: ["kubernetes-dashboard-settings"] verbs: ["get", "update"] # Allow Dashboard to get metrics. - apiGroups: [""] resources: ["services"] resourceNames: ["heapster", "dashboard-metrics-scraper"] verbs: ["proxy"] - apiGroups: [""] resources: ["services/proxy"] resourceNames: [ "heapster", "http:heapster:", "https:heapster:", "dashboard-metrics-scraper", "http:dashboard-metrics-scraper", ] verbs: ["get"] --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard rules: # Allow Metrics Scraper to get metrics from the Metrics server - apiGroups: ["metrics.k8s.io"] resources: ["pods", "nodes"] verbs: ["get", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: kubernetes-dashboard subjects: - kind: ServiceAccount name: kubernetes-dashboard namespace: kubernetes-dashboard --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: kubernetes-dashboard roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: kubernetes-dashboard subjects: - kind: ServiceAccount name: kubernetes-dashboard namespace: kubernetes-dashboard --- kind: Deployment apiVersion: apps/v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard spec: replicas: 1 revisionHistoryLimit: 10 selector: matchLabels: k8s-app: kubernetes-dashboard template: metadata: labels: k8s-app: kubernetes-dashboard spec: securityContext: seccompProfile: type: RuntimeDefault containers: - name: kubernetes-dashboard image: registry.elifen.cn/kubernetesui/dashboard:v2.7.0 imagePullPolicy: Always ports: - containerPort: 8443 protocol: TCP args: - --auto-generate-certificates - --namespace=kubernetes-dashboard # Uncomment the following line to manually specify Kubernetes API server Host # If not specified, Dashboard will attempt to auto discover the API server and connect # to it. Uncomment only if the default does not work. # - --apiserver-host=http://my-address:port volumeMounts: - name: kubernetes-dashboard-certs mountPath: /certs # Create on-disk volume to store exec logs - mountPath: /tmp name: tmp-volume livenessProbe: httpGet: scheme: HTTPS path: / port: 8443 initialDelaySeconds: 30 timeoutSeconds: 30 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsUser: 1001 runAsGroup: 2001 volumes: - name: kubernetes-dashboard-certs secret: secretName: kubernetes-dashboard-certs - name: tmp-volume emptyDir: {} serviceAccountName: kubernetes-dashboard nodeSelector: "kubernetes.io/os": linux # Comment the following tolerations if Dashboard must not be deployed on master tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule --- kind: Service apiVersion: v1 metadata: labels: k8s-app: dashboard-metrics-scraper name: dashboard-metrics-scraper namespace: kubernetes-dashboard spec: ports: - port: 8000 targetPort: 8000 selector: k8s-app: dashboard-metrics-scraper --- kind: Deployment apiVersion: apps/v1 metadata: labels: k8s-app: dashboard-metrics-scraper name: dashboard-metrics-scraper namespace: kubernetes-dashboard spec: replicas: 1 revisionHistoryLimit: 10 selector: matchLabels: k8s-app: dashboard-metrics-scraper template: metadata: labels: k8s-app: dashboard-metrics-scraper spec: securityContext: seccompProfile: type: RuntimeDefault containers: - name: dashboard-metrics-scraper image: registry.elifen.cn/kubernetesui/metrics-scraper:v1.0.8 ports: - containerPort: 8000 protocol: TCP livenessProbe: httpGet: scheme: HTTP path: / port: 8000 initialDelaySeconds: 30 timeoutSeconds: 30 volumeMounts: - mountPath: /tmp name: tmp-volume securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsUser: 1001 runAsGroup: 2001 serviceAccountName: kubernetes-dashboard nodeSelector: "kubernetes.io/os": linux # Comment the following tolerations if Dashboard must not be deployed on master tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule volumes: - name: tmp-volume emptyDir: {}发布到集群:kubectl create -f kube-dashboard.yaml使用nodePort访问dashboardk8s官方文档给的示例是用kube-proxy,这里用的nodePort,稍微改动下原yaml中的kubernetes-dashboard服务,加了nodePort的配置。kind: Service apiVersion: v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard spec: type: NodePort ports: - port: 443 targetPort: 8443 nodeport: 30001 selector: k8s-app: kubernetes-dashboard应用:kubectl apply -f kube-dashboard.yaml若运行正常,浏览器访问 https://<节点IP>:30001,理应显示dashboard的界面,接下来生成用于登录的token。使用token访问创建一个serviceaccountapiVersion: v1 kind: ServiceAccount metadata: name: admin-user namespace: kubernetes-dashboard或者使用命令kubectl create serviceaccount admin-user -n kubernetes-dashboard授予cluster-admin的集群管理员权限apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: admin-user roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: admin-user namespace: kubernetes-dashboard使用命令kubectl create clusterrolebinding admin-user --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:admin-user获取tokenkubectl -n kubernetes-dashboard create token admin-user页面填入token登录。 
  • [技术干货] [kubernetes]安装metrics-server
    前言metrics server为Kubernetes自动伸缩提供一个容器资源度量源。metrics-server 从 kubelet 中获取资源指标,并通过 Metrics API 在 Kubernetes API 服务器中公开它们,以供 HPA 和 VPA 使用。之前已经用k8s的二进制文件搭建了一套集群环境,搭建步骤见:二进制部署k8s集群-基于containerd。现需要在这个集群环境内部署Metrics-Server,用于配置应用自动伸缩。集群环境:主机:Debian 11Kubernetes版本:1.26.6步骤获取yaml文件。wget https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml -O metrics-server.yaml编辑yaml文件。之前部署集群用的自签名证书,metrics-server直接请求kubelet接口会证书校验失败,因此deployment中增加- --kubelet-insecure-tls参数。另外镜像原先在registry.k8s.io,国内下载不方便,下面的配置中修改成了国内镜像仓库地址。内网环境中可以先下载,然后再推到内网镜像仓库,镜像也改成内网镜像地址。apiVersion: apps/v1 kind: Deployment metadata: labels: k8s-app: metrics-server name: metrics-server namespace: kube-system spec: # ... template: spec: containers: - args: - --cert-dir=/tmp - --secure-port=4443 - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname - --kubelet-use-node-status-port - --metric-resolution=15s - --kubelet-insecure-tls image: registry.cn-hangzhou.aliyuncs.com/rainux/metrics-server:v0.6.4发布kubectl apply -f metrics-server.yaml查看是否在运行kubectl get pods -n kube-system | grep metrics获取集群的指标数据kubectl get --raw /apis/metrics.k8s.io/v1beta1 | python3 -m json.tool根据输出可见,集群提供nodes和pods的资源指标。{ "kind": "APIResourceList", "apiVersion": "v1", "groupVersion": "metrics.k8s.io/v1beta1", "resources": [ { "name": "nodes", "singularName": "", "namespaced": false, "kind": "NodeMetrics", "verbs": [ "get", "list" ] }, { "name": "pods", "singularName": "", "namespaced": true, "kind": "PodMetrics", "verbs": [ "get", "list" ] } ] }测试kubectl top nodestop命令kubectl top命令用来查看node节点和pod的资源使用情况。# 查看 top 命令的帮助 kubectl top --help # 查看node节点的资源使用情况 kubectl top node # 查看pod的资源使用情况 kubectl top pod # 查看所有命名空间的pod资源使用情况 kubectl top pod -A转载自https://www.cnblogs.com/XY-Heruo/p/17669633.html
  • [技术干货] [kubernetes]二进制部署k8s集群-基于containerd
    前言k8s从1.24版本开始不再直接支持docker,但可以自行调整相关配置,实现1.24版本后的k8s还能调用docker。其实docker自身也是调用containerd,与其k8s通过docker再调用containerd,不如k8s直接调用containerd,以减少性能损耗。除了containerd,比较流行的容器运行时还有podman,但是podman官方安装文档要么用包管理器在线安装,要么用包管理器下载一堆依赖再编译安装,内网离线环境下安装可能会比较麻烦,而containerd的安装包是静态二进制文件,解压后就能直接使用,离线环境下相对方便一点。本文将在内网离线环境下用二进制文件部署一个三节点集群+harbor镜像仓库。集群中部署了三个apiserver,并配置nginx反向代理,提升master的高可用性(如对高可用有进一步要求,可以再加个keepalive)。相关软件信息:名称版本说明containerdcri-containerd-cni-1.7.2-linux-amd64容器运行时harbor2.8.2容器镜像仓库etcd3.4.24键值对数据库kubernetes1.26.6容器编排系统nginx1.25.1负载均衡,反向代理apiserver服务器信息:IP操作系统硬件配置Hostname说明192.168.3.31Debian 11.6 amd644C4Gk8s31nginx+etcd+master+node192.168.3.32Debian 11.6 amd644C4Gk8s32etcd+master+node192.168.3.33Debian 11.6 amd644C4Gk8s33etcd+master+node192.168.3.43Debian 11.6 amd644C4G无harbor,内网域名registry.atlas.cn1. 系统初始化初始化部分需要三台k8s节点主机都执行, 根据实际情况修改参数。修改主机名# 3.31服务器 hostnamectl set-hostname k8s31 # 3.32服务器 hostnamectl set-hostname k8s32 # 3.33服务器 hostnamectl set-hostname k8s33修改/etc/hosts文件,增加以下配置。192.168.3.31 k8s31 192.168.3.32 k8s32 192.168.3.33 k8s33配置时间同步服务# 1. 安装chrony时间同步应用 apt install -y chrony # 2. 添加内网的ntp服务器地址。如果在公网,可配置阿里云的ntp服务器地址:ntp.aliyun.com echo 'server 192.168.3.41 iburst' > /etc/chrony/sources.d/custom-ntp-server.sources # 3. 启动 systemctl start chrony # 如果已经启动过了, 可以热加载配置: chronyc reload sources关闭swap。如果安装系统时创建了swap,则需要关闭。swapoff -a装载内核模块# 添加配置 cat << EOF > /etc/modules-load.d/containerd.conf overlay br_netfilter EOF # 立即装载 modprobe overlay modprobe br_netfilter # 检查装载。如果没有输出结果说明没有装载。 lsmod | grep br_netfilter配置系统参数# 1. 添加配置文件 cat << EOF > /etc/sysctl.d/k8s-sysctl.conf net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 user.max_user_namespaces=28633 vm.swappiness = 0 EOF # 2. 配置生效 sysctl -p /etc/sysctl.d/k8s-sysctl.conf启用ipvs。编写system配置文件,实现开机自动装载到内核。# 1. 安装依赖 apt install -y ipset ipvsadm # 2. 新建文件并添加配置, 文件路径可任意 tee /root/scripts/k8s.sh <<EOF modprobe -- ip_vs modprobe -- ip_vs_rr modprobe -- ip_vs_wrr modprobe -- ip_vs_sh modprobe -- nf_conntrack EOF # 3. 新建system文件, 实现开机执行脚本 cat << EOF > /etc/systemd/system/myscripts.service [Unit] Description=Run a Custom Script at Startup After=default.target [Service] ExecStart=sh /root/scripts/k8s.sh [Install] WantedBy=default.target EOF # 4. 加载并启用system systemctl daemon-reload systemctl enable myscripts2. 部署harbor镜像仓库由于部署集群时候需要先拉取一些镜像,所以harbor在集群外的一个服务器单独部署。官方安装脚本使用了docker,所以harbor节点需要先安装docker,安装步骤可参考 博客园 - linux离线安装docker与compose。harbor的安装步骤可参考 博客园 - centos离线安装harbor,这里大致写一下。harbor的github release GitHub - goharbor/harbor/releases 提供离线安装包,要先下载好,然后解压。创建ssl证书mkdir certs # 创建服务器证书密钥文件harbor.key openssl genrsa -des3 -out harbor.key 2048 # 输入密码,确认密码,自己随便定义,但是要记住,后面会用到。 # 创建服务器证书的申请文件harbor.csr openssl req -new -key harbor.key -out harbor.csr # 输入密钥文件的密码, 然后一路回车 # 备份一份服务器密钥文件 cp harbor.key harbor.key.org # 去除文件口令 openssl rsa -in harbor.key.org -out harbor.key # 输入密钥文件的密码 # 创建一个自当前日期起为期十年的证书 harbor.crt openssl x509 -req -days 3650 -in harbor.csr -signkey harbor.key -out harbor.crt修改配置文件 harbor.yml,仅列出自修改项。数据存储目录和日志目录自定义了。hostname: 192.168.3.43 certificate: /home/atlas/apps/harbor/certs/harbor.crt private_key: /home/atlas/apps/harbor/certs/harbor.key # admin用户登录密码 harbor_admin_password: Harbor2023 # 数据卷目录 data_volume: /home/atlas/apps/harbor/data # 日志目录 location: /home/atlas/apps/harbor/logs/执行安装脚本./install.sh浏览器访问 https://192.168.3.43 ,测试能否正常登录访问harbor。3. 安装containerd从GitHub https://github.com/containerd/containerd/releases 下载二进制包解压压缩包tar xf cri-containerd-cni-1.7.2-linux-amd64.tar.gz -C /生成 containerd 配置文件mkdir /etc/containerd containerd config default > /etc/containerd/config.toml编辑 /etc/containerd/config.toml,修改以下内容# 修改数据存储目录 root = "/home/apps/containerd" # 对于使用systemd作为init system的linux发行版,官方建议用systemd作为容器cgroup driver # false改成true SystemdCgroup = true # 修改pause镜像下载地址,这里用的是内网域名地址 sandbox_image = "registry.atlas.cn/public/pause:3.9" # 私有harbor的连接信息 [plugins."io.containerd.grpc.v1.cri".registry.configs."registry.atlas.cn"] [plugins."io.containerd.grpc.v1.cri".registry.configs."registry.atlas.cn".tls] insecure_skip_verify = true [plugins."io.containerd.grpc.v1.cri".registry.configs."registry.atlas.cn".auth] username = "admin" password = "Harbor2023"重加载systemd配置,启动containerdsystemctl daemon-reload systemctl start containerd4. 生成ca证书后面的k8s和etcd集群都会用到ca证书。如果组织能提供统一的CA认证中心,则直接使用组织颁发的CA证书即可。如果没有统一的CA认证中心,则可以通过颁发自签名的CA证书来完成安全配置。这里自行生成一个ca证书。# 生成私钥文件ca.key openssl genrsa -out ca.key 2048 # 根据私钥文件生成根证书文件ca.crt # /CN为master的主机名或IP地址 # days为证书的有效期 openssl req -x509 -new -nodes -key ca.key -subj "/CN=192.168.3.31" -days 36500 -out ca.crt # 拷贝ca证书到/etc/kubernetes/pki mkdir -p /etc/kubernetes/pki cp ca.crt ca.key /etc/kubernetes/pki/5. 部署etcd集群部署一个三节点etcd集群,集群间使用https协议加密通信。etcd的安装包可以从官网下载,下载后解压,将压缩包中的etcd和etcdctl放到/usr/local/bin目录。编辑文件etcd_ssl.cnf。IP地址为etcd节点。[ req ] req_extensions = v3_req distinguished_name = req_distinguished_name [ req_distinguished_name ] [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment subjectAltName = @alt_names [ alt_names ] IP.1 = 192.168.3.31 IP.2 = 192.168.3.32 IP.3 = 192.168.3.33创建etcd服务端证书openssl genrsa -out etcd_server.key 2048 openssl req -new -key etcd_server.key -config etcd_ssl.cnf -subj "/CN=etcd-server" -out etcd_server.csr openssl x509 -req -in etcd_server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -extensions v3_req -extfile etcd_ssl.cnf -out etcd_server.crt创建etcd客户端证书openssl genrsa -out etcd_client.key 2048 openssl req -new -key etcd_client.key -config etcd_ssl.cnf -subj "/CN=etcd-client" -out etcd_client.csr openssl x509 -req -in etcd_client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -extensions v3_req -extfile etcd_ssl.cnf -out etcd_client.crt编辑etcd的配置文件。注意,各节点的ETCD_NAME和监听地址不一样,ip和证书文件路径要根据实际来修改。以下示例为192.168.3.31的etcd配置ETCD_NAME=etcd1 ETCD_DATA_DIR=/home/atlas/apps/etcd/data ETCD_CERT_FILE=/home/atlas/apps/etcd/certs/etcd_server.crt ETCD_KEY_FILE=/home/atlas/apps/etcd/certs/etcd_server.key ETCD_TRUSTED_CA_FILE=/home/atlas/apps/kubernetes/certs/ca.crt ETCD_CLIENT_CERT_AUTH=true ETCD_LISTEN_CLIENT_URLS=https://192.168.3.31:2379 ETCD_ADVERTISE_CLIENT_URLS=https://192.168.3.31:2379 ETCD_PEER_CERT_FILE=/home/atlas/apps/etcd/certs/etcd_server.crt ETCD_PEER_KEY_FILE=/home/atlas/apps/etcd/certs/etcd_server.key ETCD_PEER_TRUSTED_CA_FILE=/home/atlas/apps/kubernetes/certs/ca.crt ETCD_LISTEN_PEER_URLS=https://192.168.3.31:2380 ETCD_INITIAL_ADVERTISE_PEER_URLS=https://192.168.3.31:2380 ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster ETCD_INITIAL_CLUSTER="etcd1=https://192.168.3.31:2380,etcd2=https://192.168.3.32:2380,etcd3=https://192.168.3.33:2380" ETCD_INITIAL_CLUSTER_STATE=new编辑/etc/systemd/system/etcd.service,注意根据实际修改配置文件和etcd二进制文件的路径[Unit] Description=etcd key-value store Documentation=https://github.com/etcd-io/etcd After=network.target [Service] EnvironmentFile=/home/atlas/apps/etcd/conf/etcd.conf ExecStart=/usr/local/bin/etcd Restart=always [Install] WantedBy=multi-user.target加载systemd配置,启动etcdsystemctl daemon-reload systemctl start etcd验证集群是否部署成功。注意根据实际修改证书文件路径和etcd节点的IP与端口etcdctl --cacert=/etc/kubernetes/pki/ca.crt --cert=/home/atlas/apps/etcd/certs/etcd_client.crt --key=/home/atlas/apps/etcd/certs/etcd_client.key --endpoints=https://192.168.3.31:2379,https://192.168.3.32:2379,https://192.168.3.33:2379 endpoint health如果集群部署成功,应该有如下类似输出https://192.168.3.33:2379 is healthy: successfully committed proposal: took = 27.841376ms https://192.168.3.32:2379 is healthy: successfully committed proposal: took = 29.489289ms https://192.168.3.31:2379 is healthy: successfully committed proposal: took = 35.703538ms6. 部署k8sk8s的二进制文件安装包可以从github下载:https://github.com/kubernetes/kubernetes/releases在changelog中找到二进制包的下载链接,下载server binary即可,里面包含了master和node的二进制文件。解压后将其中的二进制文件挪到 /usr/local/bin6.1 安装apiserver编辑master_ssl.cnf。DNS.5 ~ DNS.7为三台服务器的主机名,另行设置/etc/hosts。IP.1为Master Service虚拟服务的Cluster IP地址,IP.2 ~ IP.4为apiserver的服务器IP[req] req_extensions = v3_req distinguished_name = req_distinguished_name [req_distinguished_name] [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment subjectAltName = @alt_names [alt_names] DNS.1 = kubernetes DNS.2 = kubernetes.default DNS.3 = kubernetes.default.svc DNS.4 = kubernetes.default.svc.cluster.local DNS.5 = k8s31 DNS.6 = k8s32 DNS.7 = k8s33 IP.1 = 169.169.0.1 IP.2 = 192.168.3.31 IP.3 = 192.168.3.32 IP.4 = 192.168.3.33生成证书文件openssl genrsa -out apiserver.key 2048 openssl req -new -key apiserver.key -config master_ssl.cnf -subj "/CN=192.168.3.31" -out apiserver.csr # ca.crt和ca.key是 "2. openssl生成证书"中的两个证书文件 openssl x509 -req -in apiserver.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 36500 -extensions v3_req -extfile master_ssl.cnf -out apiserver.crt使用cfssl创建sa.pub和sa-key.pem。cfssl和cfssljson可以从github下载cat<<EOF > sa-csr.json { "CN":"sa", "key":{ "algo":"rsa", "size":2048 }, "names":[ { "C":"CN", "L":"BeiJing", "ST":"BeiJing", "O":"k8s", "OU":"System" } ] } EOF # cfssl和cfssljson可自行在GitHub搜索下载 cfssl gencert -initca sa-csr.json | cfssljson -bare sa - openssl x509 -in sa.pem -pubkey -noout > sa.pub编辑kube-apiserver的配置文件,注意根据实际情况修改文件路径和etcd地址KUBE_API_ARGS="--secure-port=6443 \ --tls-cert-file=/home/atlas/apps/kubernetes/apiserver/certs/apiserver.crt \ --tls-private-key-file=/home/atlas/apps/kubernetes/apiserver/certs/apiserver.key \ --client-ca-file=/home/atlas/apps/kubernetes/certs/ca.crt \ --service-account-issuer=https://kubernetes.default.svc.cluster.local \ --service-account-key-file=/home/atlas/apps/kubernetes/certs/sa.pub \ --service-account-signing-key-file=/home/atlas/apps/kubernetes/certs/sa-key.pem \ --apiserver-count=3 --endpoint-reconciler-type=master-count \ --etcd-servers=https://192.168.3.31:2379,https://192.168.3.32:2379,https://192.168.3.33:2379 \ --etcd-cafile=/home/atlas/apps/kubernetes/certs/ca.crt \ --etcd-certfile=/home/atlas/apps/etcd/certs/etcd_client.crt \ --etcd-keyfile=/home/atlas/apps/etcd/certs/etcd_client.key \ --service-cluster-ip-range=169.169.0.0/16 \ --service-node-port-range=30000-32767 \ --allow-privileged=true \ --audit-log-maxsize=100 \ --audit-log-maxage=15 \ --audit-log-path=/home/atlas/apps/kubernetes/apiserver/logs/apiserver.log --v=2"编辑service文件。/etc/systemd/system/kube-apiserver.service[Unit] Description=Kubernetes API Server Documentation=https://github.com/kubernetes/kubernetes [Service] EnvironmentFile=/home/atlas/apps/kubernetes/apiserver/conf/apiserver ExecStart=/usr/local/bin/kube-apiserver $KUBE_API_ARGS Restart=always [Install] WantedBy=multi-user.target加载service文件,启动kube-apiserversystemctl daemon-reload systemctl start kube-apiserver生成客户端证书openssl genrsa -out client.key 2048 # /CN的名称用于标识连接apiserver的客户端用户名称 openssl req -new -key client.key -subj "/CN=admin" -out client.csr openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 36500创建客户端连接apiserver所需的kubeconfig配置文件。其中server为nginx监听地址。注意根据实际修改配置apiVersion: v1 kind: Config clusters: - name: default cluster: server: https://192.168.3.31:9443 certificate-authority: /home/atlas/apps/kubernetes/certs/ca.crt users: - name: admin user: client-certificate: /home/atlas/apps/kubernetes/apiserver/certs/client.crt client-key: /home/atlas/apps/kubernetes/apiserver/certs/client.key contexts: - context: cluster: default user: admin name: default current-context: default6.2 安装kube-controller-manager编辑配置文件 /home/atlas/apps/kubernetes/controller-manager/conf/envKUBE_CONTROLLER_MANAGER_ARGS="--kubeconfig=/home/atlas/apps/kubernetes/apiserver/conf/kubeconfig \ --leader-elect=true \ --service-cluster-ip-range=169.169.0.0/16 \ --service-account-private-key-file=/home/atlas/apps/kubernetes/apiserver/certs/apiserver.key \ --root-ca-file=/home/atlas/apps/kubernetes/certs/ca.crt \ --v=0"编辑service文件/etc/systemd/system/kube-controller-manager.service[Unit] Description=Kubernetes Controller Manager Documentation=https://github.com/kubernetes/kubernetes [Service] EnvironmentFile=/home/atlas/apps/kubernetes/controller-manager/conf/env ExecStart=/usr/local/bin/kube-controller-manager $KUBE_CONTROLLER_MANAGER_ARGS Restart=always [Install] WantedBy=multi-user.target加载配置文件并启动systemctl daemon-reload systemctl start kube-controller-manager6.3 安装kube-scheduler编辑配置文件KUBE_SCHEDULER_ARGS="--kubeconfig=/home/atlas/apps/kubernetes/apiserver/conf/kubeconfig \ --leader-elect=true \ --v=0"编辑service文件 /etc/systemd/system/kube-scheduler.service[Unit] Description=Kubernetes Scheduler Documentation=https://github.com/kubernetes/kubernetes [Service] EnvironmentFile=//home/atlas/apps/kubernetes/scheduler/conf/env ExecStart=/usr/local/bin/kube-scheduler $KUBE_SCHEDULER_ARGS Restart=always [Install] WantedBy=multi-user.target启动systemctl daemon-reload systemctl start kube-scheduler6.4 安装nginx这里用nginx对apiserver进行tcp反向代理,也可以使用haproxy。nginx编译安装可参考 博客园 - linux编译安装nginx,docker安装nginx更加简单,本文略过。以下为示例配置:worker_processes auto; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 65536; } stream{ log_format json2 '$remote_addr [$time_local] ' '$protocol $status $bytes_sent $bytes_received ' '$session_time "$upstream_addr" ' '"$upstream_bytes_sent" "$upstream_bytes_received" "$upstream_connect_time"'; access_log logs/stream.log json2; upstream apiservers { server 192.168.3.31:6443; server 192.168.3.32:6443; server 192.168.3.33:6443; } server { listen 9443; proxy_pass apiservers; } }6.5 安装kubelet编辑文件 /home/atlas/apps/kubernetes/kubelet/conf/env。注意修改hostname-override中的IP为Node节点自己的IP。如果修改了containerd的socket地址,则配置中也要按实际修改。KUBELET_ARGS="--kubeconfig=/home/atlas/apps/kubernetes/apiserver/conf/kubeconfig \ --config=/home/atlas/apps/kubernetes/kubelet/conf/kubelet.config \ --hostname-override=192.168.3.31 \ --v=0 \ --container-runtime-endpoint="unix:///run/containerd/containerd.sock"主要参数说明参数说明--kubeconfig设置与 apiserver 连接的配置,可以与 controller-manager 的 kubeconfig 相同。新的Node节点注意拷贝客户端相关证书文件,比如ca.crt, client.key, client.crt--configkubelet 配置文件,设置可以让多个Node共享的配置参数。--hostname-override本Node在集群中的名称,默认值为主机名--network-plugin网络插件类型,推荐使用CNI网络插件编辑文件 /home/atlas/apps/kubernetes/kubelet/conf/kubelet.configkind: KubeletConfiguration apiVersion: kubelet.config.k8s.io/v1beta1 address: 0.0.0.0 port: 10250 cgroupDriver: systemd clusterDNS: ["169.169.0.100"] clusterDomain: cluster.local authentication: anonymous: enabled: true主要参数说明参数说明address服务监听IP地址port服务监听端口号,默认值为10250cgroupDrivercgroupDriver驱动,默认值为cgroupfs,可选 systemdclusterDNS集群DNS服务的IP地址clusterDomain服务DNS域名后缀authentication是否允许匿名访问或者是否使用webhook鉴权编辑service文件 /etc/systemd/system/kubelet.service[Unit] Description=Kubernetes Kubelet Server Documentation=https://github.com/kubernetes/kubernetes After=docker.target [Service] EnvironmentFile=/home/atlas/apps/kubernetes/kubelet/conf/env ExecStart=/usr/local/bin/kubelet $KUBELET_ARGS Restart=always [Install] WantedBy=multi-user.target加载service并启动kubeletsystemctl daemon-reload && systemctl start kubelet6.6 安装kube-proxy编辑文件 /home/atlas/apps/kubernetes/proxy/conf/env。注意修改hostname-override中的IP为Node节点自己的IP。KUBE_PROXY_ARGS="--kubeconfig=/home/atlas/apps/kubernetes/apiserver/conf/kubeconfig \ --hostname-override=192.168.3.31 \ --proxy-mode=ipvs \ --v=0"编辑service文件 /etc/systemd/system/kube-proxy.service[Unit] Description=Kubernetes Kube-Proxy Server Documentation=https://github.com/kubernetes/kubernetes After=network.target [Service] EnvironmentFile=/home/atlas/apps/kubernetes/proxy/conf/env ExecStart=/usr/local/bin/kube-proxy $KUBE_PROXY_ARGS Restart=always [Install] WantedBy=multi-user.target加载service并启动systemctl daemon-reload && systemctl start kube-proxy6.7 安装calico在master节点通过kubectl查询自动注册到 k8s 的 node 信息。由于 Master 开启了 https 认证,所以 kubectl 也需要使用客户端 CA证书连接Master,可以直接使用 apiserver 的 kubeconfig 文件。kubectl --kubeconfig=/home/atlas/apps/kubernetes/apiserver/conf/kubeconfig get nodes若是不想每次敲命令都要指定kubeconfig文件,可以编辑~/.bashrc,增加如下内容后source ~/.bashrcalias kubectl='/usr/local/bin/kubectl --kubeconfig=/home/atlas/apps/kubernetes/apiserver/conf/kubeconfig'如果操作步骤和以上保持一致,命令执行应有类似如下输出。NAME STATUS ROLES AGE VERSION 192.168.3.31 Ready <none> 18m v1.26.6 192.168.3.32 Ready <none> 16m v1.26.6 192.168.3.33 Ready <none> 16m v1.26.6由于安装containerd时,安装包里已经包含了cni插件,所以节点状态都是ready。自测节点之间通信还是有问题,所以换成相对来说熟悉点的calico。下载calico文件。wget https://docs.projectcalico.org/manifests/calico.yaml编辑calico.yaml文件。因为内网离线部署,以提前在公网下载好calico的镜像并推送到内网的harbor镜像仓库,所以配置文件中的镜像修改成了内网的镜像。image: registry.atlas.cn/calico/cni:v3.26.1 image: registry.atlas.cn/calico/node:v3.26.1 image: registry.atlas.cn/calico/kube-controllers:v3.26.1部署calico。PS:此处的kubectl命令以alias kubeconfigkubectl apply -f calico.yaml查看calico的pod是否正常运行。如果正常,状态应该都是running;若不正常,则需要describe pod的信息查看什么问题kubectl get pods -A6.8 集群内安装CoreDNS编辑部署文件 coredns.yaml。其中corefile里面的forward地址是内网的dns服务器地址,若内网没有dns,可修改为/etc/resolv.conf。coredns的镜像地址也是提前推送的harbor的镜像。--- apiVersion: v1 kind: ConfigMap metadata: name: coredns namespace: kube-system labels: addonmanager.kubernetes.io/mode: EnsureExists data: Corefile: | cluster.local { errors health { lameduck 5s } ready kubernetes cluster.local 169.169.0.0/16 { fallthrough in-addr.arpa ip6.arpa } prometheus :9153 forward . 192.168.3.41 cache 30 loop reload loadbalance } . { cache 30 loadbalance forward . 192.168.3.41 } --- apiVersion: apps/v1 kind: Deployment metadata: name: coredns namespace: kube-system labels: k8s-app: kube-dns kubernetes.io/name: "CoreDNS" spec: replicas: 1 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 selector: matchLabels: k8s-app: kube-dns template: metadata: labels: k8s-app: kube-dns spec: priorityClassName: system-cluster-critical tolerations: - key: "CriticalAddonsOnly" operator: "Exists" nodeSelector: kubernetes.io/os: linux affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: k8s-app operator: In values: ["kube-dns"] topologyKey: kubernetes.io/hostname imagePullSecrets: - name: registry-harbor containers: - name: coredns image: registry.atlas.cn/public/coredns:1.11.1 imagePullPolicy: IfNotPresent resources: limits: memory: 170Mi requests: cpu: 100m memory: 70Mi args: [ "-conf", "/etc/coredns/Corefile" ] volumeMounts: - name: config-volume mountPath: /etc/coredns readOnly: true ports: - containerPort: 53 name: dns protocol: UDP - containerPort: 53 name: dns-tcp protocol: TCP - containerPort: 9153 name: metrics protocol: TCP securityContext: allowPrivilegeEscalation: false capabilities: add: - NET_BIND_SERVICE drop: - all readOnlyRootFilesystem: true livenessProbe: httpGet: path: /health port: 8080 scheme: HTTP initialDelaySeconds: 60 timeoutSeconds: 5 successThreshold: 1 failureThreshold: 5 readinessProbe: httpGet: path: /ready port: 8181 scheme: HTTP dnsPolicy: Default volumes: - name: config-volume configMap: name: coredns items: - key: Corefile path: Corefile --- apiVersion: v1 kind: Service metadata: name: kube-dns namespace: kube-system annotations: prometheus.io/port: "9153" prometheus.io/scrape: "true" labels: k8s-app: kube-dns kubernetes.io/cluster-service: "true" kubernetes.io/name: "CoreDNS" spec: selector: k8s-app: kube-dns clusterIP: 169.169.0.100 ports: - name: dns port: 53 protocol: UDP - name: dns-tcp port: 53 protocol: TCP - name: metrics port: 9153 protocol: TCP部署一个nginx用于测试。注意按实际修改镜像地址--- apiVersion: apps/v1 kind: Deployment metadata: name: deploy-nginx spec: replicas: 2 selector: matchLabels: app: nginx env: dev template: metadata: labels: app: nginx env: dev spec: containers: - name: nginx image: registry.atlas.cn/public/nginx:1.25.1 ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: svc-nginx spec: ports: - protocol: TCP port: 80 targetPort: 80 selector: app: nginx env: dev发布nginx服务kubectl create -f nginx.yaml运行一个ubuntu的pod。镜像基于原版的ubuntu:22.04修改,提前安装了dnsutils再封装推送到内网harbor,Dockerfile内容如下:FROM ubuntu:22.04 RUN apt update -y && apt install -y dnsutils iputils-ping curl RUN apt clean && rm -rf /var/lib/apt/lists/*使用文件声明pod。注意按实际修改镜像地址apiVersion: v1 kind: Pod metadata: name: ubuntu namespace: default spec: containers: - name: ubuntu image: registry.atlas.cn/public/ubuntu:22.04.1 command: - tail - -f - /dev/null发布podkubectl create -f ubuntu.yaml使用exec选项进入pod内kubectl exec -it ubuntu -- bash在pod内测试能否连通nginx。若一切响应正常,说明集群已基本搭建成功# 测试能否解析出svc-nginx的ip nslookup svc-nginx # 测试能否调通svc-nginx:80 curl http://svc-nginx补充以上步骤只是部署了一个能正常发布服务的基础k8s集群,生产环境中还要考虑存储、网络、安全等问题,相关内容比较多,本文不再赘述,可参考其它文档。问题记录sysctl加载配置时报错sysctl: cannot stat /proc/sys/net/bridge/bridge-nf-call-ip6tables: No such file or directorysysctl: cannot stat /proc/sys/net/bridge/bridge-nf-call-iptables: No such file or directory处理:装载内核模块modprobe br_netfilter转载自https://www.cnblogs.com/XY-Heruo/p/17638634.html
  • [技术干货] [nginx]反向代理grpc
    前言nginx从1.13.10版本开始提供对gRPC代理的支持。由于grpc基于http2,因此编译nginx时需要添加参数--with-http_v2_module来启用对http2协议的支持。常用配置应该是nginx 1.25版本开始,声明http2的语法应该单独写,而不是写在listen中。listen 80; http2 on;基本配置http { server { listen 80 http2; location / { grpc_pass grpc://192.168.0.14:84; } } # 示例2, 通过server_name复用端口 server { listen 80 http2; server_name demo2.test.com; location / { grpc_pass grpc://192.168.0.14:85; } } }反向代理后端SSL gRPCserver { listen 80 http2; grpc_ssl_verify off; # 关闭对grpc服务器的ssl证书验证 grpc_ssl_session_reuser on; # 启用与grpc服务器https连接的ssl会话重用功能 location / { grpc_pass grpcs://192.168.0.14:84; # grpc后端地址 } }nginx同时启用https。客户端 -> nginx(https) -> 服务端(SSL)server { listen 443 ssl http2; ssl_certificate ssl/test.pem; ssl_certificate_key ssl/test.key; grpc_ssl_verify off; grpc_ssl_session_reuser on; location / { grpc_pass grpcs://192.168.0.14:84; } }负载均衡配置upstream grpc_backend { server 192.168.0.11:8001; server 192.168.0.12:8001; } server { listen 80 http2; location / { grpc_pass grpc://grpc_backend; } }配置指令名称语法默认值说明grpc_bindaddress [transparent] 或offnil设置从指定的本地IP地址及端口进行反向代理。设置transparent时,将客户端真实IP透传给后端。grpc_buffer_sizesize4k或8k设用于从grpc服务器读取响应数据缓冲区大小。grpc_passaddressnil后端grpc的地址grpc_hide_headerfieldnil指定grpc后端响应数据中,不向客户端传递的http头grpc_pass_headerfieldnil允许部分后端请求头返回给客户端grpc_ignore_headersfieldsnil设置禁止nginx处理从后端获取响应的headergrpc_set_headerfield value 在转发给grpc后端前,修改或添加请求头grpc_connect_timeouttime60snginx与后端建立连接的超时时间grpc_read_timeouttime60s从后端连续接收两个读操作之间的超时时间grpc_send_timeouttime60s从后端连续接收两个写操作之间的超时时间grpc_socket_keepaliveon 或 offoff启用nginx与后端的tcp keepalive机制grpc_intercept_errorson 或 offoff启用拦截后端响应码大于或等于300的结果grpc_next_upstream  当出现指令之中指定的条件时,将未返回响应的请求传递给upstream中的另一个后端grpc_next_upstream_timeouttime0next_upstream过程中的超时时间grpc_next_upstream_triesnumber0next_upstream中下一个后端的尝试次数grpc_ssl_protocols  指定nginx与后端建立ssl连接的ssl协议的版本grpc_ssl_session_reuseon 或 offon启用与后端https连接的ssl会话复用功能grpc_ssl_ciphers  设置建立https连接时用于协商使用的加密算法组合grpc_ssl_server_nameon或offoff在与grpc服务器建立ssl连接时,设置是否启用通过SNI或RFC6066传递主机名grpc_ssl_certificatefilenil指定后端对nginx的ssl证书文件grpc_ssl_certificate_keyfilenil指定后端对nginx的ssl私钥文件grpc_ssl_password_filefilenil指定后端对nginx的ssl密码文件grpc_ssl_verifyon 或 offoff设置是否启用对grpc后端的ssl证书验证机制grpc_ssl_namenameproxy_pass指令指定的主机名指定对后端ssl证书验证的主机名grpc_ssl_crlfilenil证书吊销列表文件grpc_ssl_trusted_certificatefilenil指定一个pem格式的ca证书文件grpc_ssl_verify_depthnumber1设置证书链的验证深度转载自https://www.cnblogs.com/XY-Heruo/p/17575356.html
  • [技术干货] [linux]常见内核TCP参数描述与配置
    前言所有的TCP/IP参数都位于/proc/sys/net目录下(请注意,对/proc/sys/net目录下内容的修改都是临时的,任何修改在系统重启后都会丢失),如果需要固化设置,则需要修改/etc/sysctl.conf(也可以在/etc/sysctl.d目录下新建conf文件)sysctl命令基本使用# 查看指定参数 sysctl net.ipv4.tcp_tw_reuse # 查看所有内核参数 sysctl -a # 临时修改指定内核参数 sysctl -w net.ipv4.tcp_tw_reuse=1 # 重加载 /etc/sysctl.conf文件 sysctl -p # 重加载所有系统配置文件 sysctl --systemTIME_WAIT问题linux系统下,TCP连接断开后,会以TIME_WAIT状态保留一定时间,然后才会释放端口。当并发请求过多的时候,就会产生大量的TIME_WAIT状态的连接。如果没有及时断开,会有大量的端口资源的服务器资源被占用。对此我们有必要调整下linux的TCP内核参数,让系统更快地释放TIME_WAIT连接。统计TCP各种状态的数量netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'编辑配置文件/etc/sysctl.conf,加入以下内容:net.ipv4.tcp_syncookies= 1 net.ipv4.tcp_tw_reuse= 1 net.ipv4.tcp_tw_recycle= 1 net.ipv4.tcp_fin_timeout= 30生效:sysctl -p # 如果编辑的文件在 /etc/sysctl.d/ 目录下, 需要改成使用以下命令 sysctl --system高并发下端口配置优化net.ipv4.tcp_keepalive_time= 1200 net.ipv4.ip_local_port_range= 1024 65535 net.ipv4.tcp_max_syn_backlog= 8192 net.ipv4.tcp_max_tw_buckets= 5000参数说明对于不同的linux发行版,默认值可能不一样。net.core.somaxconn一般情况下默认值是128,不同linux发行版可能会有区别。该参数用于控制处于监听状态的套接字的最大连接队列长度,对于高并发的nginx服务器而言要注意调大该参数值,比如16384,32768。net.core.xmem_default和net.core.xmem_max参数说明默认值net.core.rmem_default系统范围接收数据的内核缓冲区初始大小262144byte,即256KBnet.core.wmem_default系统范围发送数据的内核缓冲区初始大小262144byte,即256KBnet.core.rmem_max系统范围接收数据的内核缓冲区最大大小262144byte,即256KBnet.core.wmem_max系统范围发送数据的内核缓冲区最大大小262144byte,即256KB默认值在不同的linux发行版可能会有所不同。网络环境良好和内存资源充足的情况下,增大上述四个参数的值有助于提高并发能力,减少丢包和延迟。网络环境较差或内存资源不足的情况下,可以考虑减小上述四个参数的值。如果xmem_default的值大于xmem_max的值,将以xmem_max为准,且超出的部分内存将被浪费。net.ipv4.ip_local_port_range一般情况下默认值为32768 60999,表示本地端口范围为32768到60999,不同linux发行版可能会有所不同。常见优化配置:net.ipv4.ip_local_port_range = 1024 65535通过将本地端口号限制在指定的范围内,可以避免与系统或其它应用程序使用的端口号发生冲突。如果服务器上还运行了后端应用程序,注意要错开后端服务的端口号。net.ipv4.tcp_fastopen该参数用于启用或禁用 TCP 的快速打开(TCP Fast Open)功能。TCP 快速打开是一种优化的 TCP 握手过程,旨在减少客户端与服务器之间的往返延迟时间,从而加速连接的建立。传统的 TCP 握手过程需要三次往返(3-way handshake)才能建立连接。而 TCP 快速打开通过在初始 SYN 数据包中携带客户端发送的应用层数据,使服务器可以在接收到 SYN 数据包后直接发送 SYN+ACK 数据包,从而减少了一个往返的延迟。net.ipv4.tcp_fastopen 参数有以下几个取值:0:表示禁用 TCP 快速打开功能。1:表示启用 TCP 快速打开功能。2:表示启用 TCP 快速打开功能,并允许客户端在第一次握手时发送数据包。需要注意的是,启用 TCP 快速打开功能需要支持该功能的客户端和服务器。如果客户端或服务器不支持 TCP 快速打开,即使在内核中启用了该功能,TCP 连接仍然会回退到传统的三次握手过程。对于linux服务器,内核版本应高于3.7。net.ipv4.tcp_fin_timeout一般情况下默认值为60,单位秒,不同linux发行版可能会有所不同。用于控制TCP/IP协议栈中的FIN-WAIT-2状态的超时时间。在TCP协议中,当一段的连接主动关闭后,会进入FIN-WAIT-2状态,等待对方的确认,以确保双方都完成了连接关闭。当FIN-WAIT-2状态持续超过该参数值是,连接会被内核强制关闭,这对于释放系统资源,提高连接处理能力非常重要。较小的参数值可以更快地释放系统资源,但可能导致一些连接在网络不稳定的情况下被错误地关闭。net.ipv4.tcp_keepalive_time一般情况下默认值为7200,单位秒,不同linux发行版可能会有所不同。该参数用于控制TCP/IP协议栈中的 TCP keepalive 检测时间间隔。TCP keepalive是一种机制,用于检测处于空闲状态的连接是否仍然有效。当一段时间内没有数据传输时,TCP Keepalive会发送一些特定的探测报文到对方,以确认连接的状态。这对于检测死连接、清理空闲连接和提高连接可靠性很重要。如果该参数值默认2小时,如果修改为很小的值,将会带来频繁的keepalive检测,这会增加网络流量和系统负载,不必要的连接也可能被中断。同时也会增加系统安全问题,攻击者可以利用Keepalive探测报文进行DoS攻击或网络扫描。net.ipv4.tcp_max_tw_buckets不同linux发行版可能会有所不同,可能是65536或180000。该参数用于控制 TIME_WAIT 状态的 TCP 连接的最大数量。当TIME_WAIT数超过该参数值,新的连接请求可能会被丢弃或拒绝。较小的值会加快清理TIME_WAIT,但可能会有连接异常。一般情况下默认即可,根据实际情况可以考虑减少或增多。net.ipv4.tcp_max_syn_backlog一般情况下默认值为1024,不同linux发行版可能会有所不同。该参数用于控制TCP/IP协议栈中SYN队列的最大长度。在 TCP 握手过程中,当客户端发送 SYN 报文请求建立连接时,服务器端会将这些 SYN 请求放入 SYN 队列中等待处理。net.ipv4.tcp_max_syn_backlog 参数指定了 SYN 队列的最大长度,即能够同时等待处理的 SYN 请求的最大数量。较小的 net.ipv4.tcp_max_syn_backlog 值可能会导致 SYN 队列溢出,从而无法处理所有的连接请求。这可能会导致客户端无法成功建立连接,出现连接超时或连接被拒绝的情况。net.ipv4.tcp_syncookies一般情况下默认为0,表示关闭,不同linux发行版可能会有所不同。置为1表示开启。表示开启SYNCookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击。当系统遭受SYN Flood攻击时,攻击者会发送大量的TCP SYN请求,消耗服务器资源并导致服务不可用。启用SYN Cookie机制后,当服务器接收到一个新的 TCP SYN 请求时,会根据该请求生成一个 SYN Cookie,并将 SYN Cookie 发送回给客户端。客户端在后续的请求中需要携带该 SYN Cookie。服务器在收到后续请求时,会验证 SYN Cookie 的合法性,并根据其中的信息还原出原始的 SYN 请求。通过使用 SYN Cookie 机制,服务器可以在不消耗太多资源的情况下抵御 SYN Flood 攻击,确保系统的稳定性和可用性。启用 SYN Cookie 机制也可能带来一些问题,一些网络设备可能无法正确处理SYN Cookie的连接请求,导致连接无法建立或其它问题。net.ipv4.tcp_synack_retries一般情况下默认为5,不同linux发行版可能会有所不同。该参数用于设置在连接建立过程中,发送 SYN-ACK(同步应答)包后等待客户端 ACK(确认应答)包的最大重试次数。在 TCP 连接的三次握手过程中,服务器收到客户端的 SYN(同步)包后,会回复一个 SYN-ACK 包作为应答。然后服务器等待客户端发送 ACK 包来确认连接的建立。如果服务器在等待期间未收到 ACK 包,它将重试发送 SYN-ACK 包,重试次数由 net.ipv4.tcp_synack_retries 参数确定。网络环境糟糕的情况下可以考虑增加参数值,以允许更多的重试次数,增加连接建立的成功率。减小参数值有助于快速建立连接和减少资源占用。net.ipv4.tcp_syn_retries一般情况下默认为6,不同linux发行版可能会有所不同。该参数用于设置在连接建立过程中,发送 SYN(同步)包后等待对方响应的最大重试次数。当客户端发送 SYN 包后,如果没有收到服务器的 SYN-ACK(同步应答)包,客户端会重试发送 SYN 包,重试次数由 net.ipv4.tcp_syn_retries 参数确定。net.ipv4.tcp_timestamps一般情况下默认为1,表示开启,不同linux发行版可能会有所不同。置为0表示关闭。启用后允许在TCP报文中添加时间戳信息,用于测量报文的往返时间(RTT)和计算报文的时序。net.ipv4.tcp_tw_reuse一般情况下默认为0,表示关闭,不同linux发行版可能会有所不同。置为1表示开启。允许重用TIME_WAIT Socket,也就是可以重用TIME_WAIT占用的端口。启用net.ipv4.tcp_tw_reuse也可能带来一些问题。例如,如果处于TIME_WAIT连接上仍然存在未完全处理的数据包,重用该端口可能导致数据包被传递到错误的连接上,从而导致数据错乱或安全问题。net.ipv4.tcp_tw_recycle一般情况下默认为0,表示关闭,不同linux发行版可能会有所不同。置为1表示开启。启用快速回收TIME_WAIT Socket,内核根据一定规则释放TIME_WAIT的端口资源。具体的回收规则可以根据net.ipv4.tcp_timestamps参数和其它相关参数进行调整。当多个客户端位于同一个NAT网络后面时,启用快速回收可能导致来自不同客户端的连接被错误服用,导致数据错乱或安全问题。net.ipv4.tcp_rmem和net.ipv4.tcp_wmem用于设置tcp接收缓冲区和发送缓冲区的大小,有三个值组成,分别是最小值、默认值和最大值。类似于net.core.xmem_default和net.core.xmem_max。不过net.core是系统全局参数,适用于所有类型的socket,包括tcp和udp。 转载自https://www.cnblogs.com/XY-Heruo/p/17562091.html
  • [技术干货] [nginx]lua读取请求体
    前言nginx默认不读取请求体的数据,但可以通过$request_body内置变量来获取。$request_body存在内存中,如果它的字节大小超过nginx配置的client_body_buffer_size的值,nginx就会把请求体存放到临时文件中。此时数据就不在内存中了,这会导致$request_body为空。同步非阻塞方式获取请求体ngx.req.read_body含义:同步读取客户端请求体,且不会阻塞nginx的事件循环。使用此指令后,就可以通过ngx.req.get_body_data来获取请求体的数据了。但如果使用临时文件来存放请求体,就需要先使用函数ngx.req.get_body_file来获取临时文件名,再读取临时文件中的请求体数据。环境:rewrite_by_lua*、access_by_lua*、content_by_lua*ngx.req.get_body_data含义:执行ngx.req.read_body指令后,可以使用本指令在内存中获取请求体数据,结果会返回一个lua的字符串类型的数据。如果要获取table类型的数据,则需要使用ngx.req.get_post_args。环境:rewrite_by_lua*、access_by_lua*、content_by_lua*、log_by_lua*ngx.req.get_post_args含义:读取包含当前请求在内的所有post请求的查询参数,返回一个table类型的数据环境:rewrite_by_lua*、access_by_lua*、content_by_lua*、log_by_lua*、header_filter_by_lua*、body_filter_by_lua*ngx.req.get_body_file含义:获取存放请求体的临时文件名。如果请求体被存放在内存中,获取的值就是nil。示例获取string类型的请求体location /testlua { client_max_body_size 10k; client_body_buffer_size 1k; content_by_lua_block { local ngx = require "ngx"; ngx.req.read_body() -- 开启读取请求体模式 local data = ngx.req.get_body_data() -- 获取内存中的请求体 if data then ngx.print(string.format("data: %s, type: %s",data,type(data))) return else local file = ngx.req.get_body_file() -- 如果内存中没有, 则到临时文件中读取 if file then ngx.say("body is in file ", file) else ngx.say("no body found") end end } }请求测试curl -i http://192.168.1.111/testlua -d 'test=123&a=qwe&b=zxc' HTTP/1.1 200 OK Server: openresty Date: Sun, 28 May 2023 10:05:51 GMT Content-Type: application/octet-stream Transfer-Encoding: chunked Connection: keep-alive data: test=123&a=qwe&b=zxc, type: string获取table类型的请求体location /testlua { client_max_body_size 10k; client_body_buffer_size 1k; content_by_lua_block { local ngx = require "ngx"; ngx.req.read_body() -- 开启读取请求体模式 local args, err = ngx.req.get_post_args() -- 获取内存中的请求体 if args then for k,v in pairs(args) do if type(v) == "table" then ngx.say(k, ": ", table.concat(v, ", ")) else ngx.say(k, ": ", v) end end else local file = ngx.req.get_body_file() -- 如果内存中没有, 则到临时文件中读取 if file then ngx.say("body is in file ", file) else ngx.say("no body found") end end } }请求测试curl -i http://192.168.1.111/testlua -d 'test=123&a=qwe&b=zxc' HTTP/1.1 200 OK Server: openresty Date: Sun, 28 May 2023 10:37:48 GMT Content-Type: application/octet-stream Transfer-Encoding: chunked Connection: keep-alive a: qwe b: zxc test: 123转载自https://www.cnblogs.com/XY-Heruo/p/17442319.html
  • [问题求助] linux下的idea插件,无法登录
    操作系统能debian13,idea版本为2025.3.3,点击华为账号登录,然后就一直登录中
  • [问题求助] IDE会出linux版吗
    如题,想问问IDE会出linux版吗,如果出,有规划时间吗
  • [介绍/入门] Linux 通用软件包 AppImage 打包详解
    AppImage 是 Linux 系统中一种新型的软件包格式,它与 rpm、deb 这些软件包格式相比最大的不同便是:(1)无需安装,即用即删。(2)只需打包一次,便可到处运行。完美的解决了不同 Linux 发行版(Ubuntu/Debian/Fedora/CentOS)之间软件包不统一的问题。它的工作原理便是将程序运行所需的文件全部打包在一个文件中,待程序运行时再将这些文件提取在 /tmp/.mount_xxxxxxx/ 目录中,然后执行 AppRun 脚本启动程序以进行资源的调用。以下便是一个 AppImage 文件内部包含的目录树结构:AppDir/ ├── AppRun ├── 应用图标.png ├── 程序名.desktop ├── usr/ ├── bin/ ├── lib/ ├── share/它本质上就是一个 squashfs 文件系统 + runtime 执行器。特别注意:(1)要实现跨平台运行,待打包的程序最好是在 CentOS 7 系统上进行编译 ,然后再进行打包。【注:编译 C/C++ 程序所使用的系统库 glibc 在 Linux 系统上几乎肯定存在,而该库有着良好的向后兼容性,因此使用旧版本的 glibc 库编译出来的程序几乎可以完美的运行在新版本的 glibc 系统上。而在 CentOS 7 上的 glibc 版本是 2.17,该版本较旧且兼容性较好,因此在其系统上编译出来的 C 程序通常也可以在大部分的 Linux 发行版系统中使用。】(2)待打包程序依赖的 lib 文件中最好只包含其专属的库文件即可,不要包含类似 glibc 这样的系统库文件。【注:这是因为在 A 系统中的 glibc 文件通常并不可以在 B 系统中使用,因此为了避免 AppImage 程序运行错误,请勿这样去做。再者,glibc 在 Linux 系统中是肯定会存在的,因此也并不需要额外去包含这样的依赖文件。】手动打包 - appimagetool linuxdeploy是由 AppImage 官方制作的打包工具,在使用它进行打包时,必须要先分析待打包程序的动态库依赖情况,然后再完成对 AppDir 目录的装填,最后才能使用 appimagetool 完成对程序的打包。由于分析程序的依赖情况是个很复杂的问题,因此该工具在使用上体验并不太好。接下来,我将演示如何对一个简单的 C 程序完成打包过程:(1)文件准备:hello.c。// 主文件 hello.c#include <stdio.h>int main() { printf("Hello Appimage\n"); return 0;}(2)编译并打包#(1)编译及检验运行gcc -o hello hello.c./hello#(2)制作 AppDir 目录树mkdir -p AppDir/usr/bin/cp ./hello AppDir/usr/bin/wget https://github.com/boolean-world/appimage-resources/blob/master/hello-world-appimage/hello-world-icon.png -O AppDir/hello.png #任意图片文件即可nano AppDir/hello.desktop #文件内容见下方nano AppDir/AppRun #脚本内容见下方#(3)开始制作 AppImage 程序/root/appimagetool-x86_64.AppImage AppDir/附注:hello.desktop 文件如下:[Desktop Entry]Name=helloExec=helloIcon=helloType=ApplicationCategories=Utility;Terminal=trueAppRun 脚本如下:#!/bin/shAPPDIR="$(dirname "$(readlink -f "$0")")"# 添加库目录if [ -d "$APPDIR/lib64" ]; then export LD_LIBRARY_PATH="$APPDIR/lib64:$LD_LIBRARY_PATH"fiif [ -d "$APPDIR/usr/lib" ]; then export LD_LIBRARY_PATH="$APPDIR/usr/lib:$LD_LIBRARY_PATH"fi# 启动主程序 //注意:不同应用主程序路径需要修改exec "$APPDIR/usr/bin/hello" "$@"AppDir 目录树结构如下:AppDir/├── AppRun //启动程序,可以是简单的脚本,也可以是 ELF,只要保证运行该脚本主程序能被启动即可。├── hello.desktop //注意 EXEC 的值,它对应的是/usr/bin/目录中的程序,而 Icon 对应的是当前目录├── hello.png //也支持 svg 格式└── usr └── bin └── hello自动打包 - linuxdeploylinuxdeploy是一个由第三方制作的 AppImage 打包工具,与 appimagetool 不同的是,它可以对待打包程序自动进行依赖分析,并自动将所需的依赖及资源文件按照 AppDir 的目录格式给装填完毕,用户只需将模版化的 desktop 文件和 icon 文件准备好即可,使用起来简直美滋滋。【示例一】:接下来,我将演示如何对一个需要依赖的简单 C 程序完成打包过程:(1)文件准备:mylib.h、mylib.c、main.c、Makefile。// 动态库头文件 mylib.h#ifndef MYLIB_H#define MYLIB_Hint add(int a, int b);void hello();#endif// 动态库源码 mylib.c#include <stdio.h>#include "mylib.h"int add(int a, int b) { return a + b;}void hello() { printf("Hello from my dynamic library!\n");}// 主程序 main.c#include <stdio.h>#include "mylib.h"int main() { hello(); int result = add(3, 5); printf("3 + 5 = %d\n", result); return 0;}# Makefile 文件CC=gccCFLAGS=-fPIC -WallLDFLAGS=-sharedTARGET_LIB=libmylib.soTARGET_MAIN=mainall: $(TARGET_LIB) $(TARGET_MAIN)$(TARGET_LIB): mylib.o $(CC) $(LDFLAGS) -o $(TARGET_LIB) mylib.omylib.o: mylib.c mylib.h $(CC) $(CFLAGS) -c mylib.c$(TARGET_MAIN): main.o $(TARGET_LIB) $(CC) main.o -L. -lmylib -o $(TARGET_MAIN)main.o: main.c mylib.h $(CC) -c main.cclean: rm -f *.o $(TARGET_MAIN) $(TARGET_LIB)(2)编译并打包#(1)编译及检验运行cd myappmakemv libmylib.so /lib64/libmylib.so./main#(2)制作的 main.desktop 文件内容cat main.desktop[Desktop Entry]Name=mainExec=mainIcon=mainType=ApplicationCategories=Utility;Terminal=true#(3)获取一个 Icon 文件wget https://github.com/boolean-world/appimage-resources/blob/master/hello-world-appimage/hello-world-icon.png -O main.png#(4)开始制作 AppImage 程序/root/linuxdeploy-x86_64.AppImage --appdir /root/myapp --output appimage --icon-file main.png --desktop-file main.desktop -e mainls -l main*.AppImage 【示例二】:最后,我再演示如何对一个系统命令 find 完成打包过程:#(1)制作的 find.desktop 文件内容cat find.desktop[Desktop Entry]Name=findExec=findIcon=findType=ApplicationCategories=Utility;Terminal=true#(2)获取一个 Icon 文件wget https://github.com/boolean-world/appimage-resources/blob/master/hello-world-appimage/hello-world-icon.png -O find.png#(3)开始制作 AppImage 程序cd $(dirname $(which find))/root/linuxdeploy-x86_64.AppImage --appdir /root/find --output appimage --icon-file find.png --desktop-file find.desktop -e findls -l find*.AppImage 注意:(1)建议将 icon 和 desktop 文件放置在 find 命令根目录下,这样在打包的时候能够避免很多问题。(2)由于 linuxdeploy 在打包环节调用的是 appimagetool,而 appimagetool 在打包的时候会在 github 上拉取 runtime 文件,因此在使用前建议设置全局代理以确保 github 可访问。(*)全局代理设置export http_proxy=http://192.168.56.1:7890export https_proxy=http://192.168.56.1:7890export no_proxy=192.168.56.1,localhostexport HTTP_PROXY=http://192.168.56.1:7890export HTTPS_PROXY=http://192.168.56.1:7890export NO_PROXY=192.168.56.1,localhost文章来源:https://www.cnblogs.com/kqdssheng/p/19269888
  • [介绍/入门] WebAssembly打破浏览器边界的新一代跨平台运行时
    在软件开发的演进长河中,我们始终在追求一个理想状态:一次编写,随处运行。从 Java 的“Write Once, Run Anywhere”到 .NET 的跨平台战略,再到容器化与云原生的兴起,这一目标不断被重新定义。而今天,一项名为 WebAssembly(简称 Wasm) 的技术正以惊人的速度重塑软件分发、执行与安全模型——它不仅让高性能应用在浏览器中成为可能,更正在成为操作系统之上的通用轻量级运行时,悄然开启“后容器时代”的序幕。一、什么是 WebAssembly?不只是浏览器的“加速器”WebAssembly 是一种低级的、可移植的字节码格式,设计目标是在现代 Web 浏览器中以接近原生的速度执行代码。它于 2017 年由 W3C 联合 Mozilla、Google、Microsoft 和 Apple 正式标准化,如今已被所有主流浏览器支持。但 WebAssembly 的意义远不止于此:Wasm 不是 JavaScript 的替代品,而是其高性能补充;更重要的是,它正在脱离浏览器,成为通用的沙箱化运行时。核心特性特性说明接近原生性能预编译为紧凑字节码,JIT 编译后执行效率达 C/C++ 的 90%+语言无关支持 Rust、C/C++、Go、Python(实验性)、TypeScript 等编译为 Wasm安全沙箱默认无文件/网络访问权限,能力通过“导入”显式授予快速启动毫秒级冷启动,远优于 JVM 或容器跨平台同一字节码可在 Windows、Linux、macOS、甚至嵌入式设备运行二、从浏览器到全栈:Wasm 的三大演进阶段阶段 1:浏览器中的高性能模块(2017–2020)典型应用:Figma(设计工具)、AutoCAD Web、视频编辑器价值:将计算密集型任务(如图像处理、物理引擎)从 JS 迁移至 Wasm,提升流畅度// Rust 示例:计算斐波那契数列(编译为 Wasm)#[no_mangle]pub extern "C" fn fibonacci(n: u32) -> u32 { if n <= 1 { return n; } fibonacci(n - 1) + fibonacci(n - 2)}阶段 2:服务端 Wasm(2020–2023)随着 WASI(WebAssembly System Interface) 的提出,Wasm 获得了访问文件、网络、时间等系统能力的标准接口,从而走出浏览器。代表项目:Wasmtime(Bytecode Alliance):独立 Wasm 运行时WasmEdge:面向边缘和 Serverless 优化Deno:内置 Wasm 支持的 JS/TS 运行时阶段 3:通用插件与函数运行时(2023–至今)Wasm 正成为安全、轻量、跨语言插件系统的事实标准:场景案例数据库扩展SingleStore、FerretDB 允许用 Wasm 编写 UDFAPI 网关插件Envoy Proxy 支持 Wasm 过滤器Serverless 函数Shopify Functions、Cloudflare Workers区块链智能合约CosmWasm(Cosmos 生态)CLI 工具插件HashiCorp Vault、Terraform 插件实验三、实战:用 Rust 编写一个 Wasm Serverless 函数我们将创建一个简单的 HTTP 处理函数,部署到 WasmEdge 运行时。1. 初始化项目(使用 cargo-wasi)cargo new --lib hello-wasmcd hello-wasm2. 修改 Cargo.toml[package]name = "hello-wasm"version = "0.1.0"edition = "2021"[lib]crate-type = ["cdylib"] # 编译为动态库(Wasm 模块)[dependencies]wasmedge-bindgen = "0.4"wasmedge-macro = "0.4"3. 编写函数逻辑// src/lib.rsuse wasmedge_bindgen::*;use wasmedge_macro::*;#[async_func]pub fn handle_request(request: Vec<u8>) -> Vec<u8> { let name = std::str::from_utf8(&request).unwrap_or("Guest"); format!("Hello, {}! Time: {}", name, chrono::Utc::now()).into_bytes()}4. 编译为 Wasmcargo build --target wasm32-wasi --release# 输出: target/wasm32-wasi/release/hello_wasm.wasm5. 在 WasmEdge 中运行# 安装 WasmEdgecurl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash# 启动 HTTP 服务wasmedge --http-addr=0.0.0.0:3000 hello_wasm.wasm现在访问 http://localhost:3000/Alice,将返回:Hello, Alice! Time: 2025-11-17 08:30:45 UTC优势:冷启动 < 10ms内存占用 < 5MB自动沙箱隔离,无法访问主机文件系统四、Wasm vs 容器:为何它是“后容器时代”的答案?维度Docker 容器WebAssembly启动速度秒级毫秒级内存开销100MB+1–10MB镜像大小100MB–1GB100KB–10MB安全模型基于 Linux Namespace/cgroups基于能力的安全(Capability-based)多语言支持需打包完整运行时共享统一运行时冷启动成本高(Serverless 痛点)极低趋势:在边缘计算、FaaS(Function as a Service)、微服务插件等场景,Wasm 正逐步替代轻量级容器。 结语:Wasm 不是未来,而是现在WebAssembly 已从“浏览器性能补丁”蜕变为新一代软件交付与执行的通用基座。它以极致的轻量、安全与跨平台能力,解决了云原生时代的关键痛点:如何在保障安全的前提下,实现极致的资源效率与启动速度。💡 给开发者的行动建议:前端工程师:尝试用 Wasm 优化图像/音视频处理后端工程师:评估 WasmEdge/Wasmtime 作为 FaaS 运行时系统架构师:在插件系统、边缘节点中引入 Wasm 沙箱区块链开发者:关注 CosmWasm、Substrate Wasm 智能合约正如当年 JavaScript 将浏览器变成应用平台,WebAssembly 正在将整个计算世界变成一个安全、高效、可组合的函数网络。这一次,舞台不再局限于浏览器——而是无处不在。
  • [行业前沿] 如何优化 JFR(Java Flight Recorder)分析的性能?
    JFR(Java Flight Recorder)是 JDK 内置的高性能诊断工具,以极低开销记录 JVM 和应用运行时的关键事件。然而,不当配置可能导致录制开销升高、文件过大或分析困难。尤其在高并发、长时间运行的生产环境(如鲲鹏 ARM64 服务器)中,合理优化 JFR 的使用策略至关重要。本文将从 录制配置、资源控制、事件筛选、分析效率 四个维度,系统讲解如何优化 JFR 的性能表现。一、核心原则:平衡“数据完整性”与“运行开销”JFR 的默认配置(profile.jfc)已针对通用场景做了权衡,但实际业务需根据目标调整:目标推荐策略长期监控(7×24)仅启用关键事件(GC、线程、CPU 采样),降低采样频率故障复现启用详细事件(方法、异常、I/O),短时间高保真录制性能压测对比使用统一配置,确保数据可比性二、优化录制阶段的性能1. 选择合适的预设模板JDK 提供两个内置模板:default.jfc:基础事件(低开销,适合长期运行)profile.jfc:包含方法采样、锁竞争等(中等开销,适合性能分析)建议:# 长期监控用 default-XX:StartFlightRecording=settings=default,duration=1h,filename=/data/jfr/low.jfr# 深度分析用 profile-XX:StartFlightRecording=settings=profile,duration=5m,filename=/data/jfr/high.jfr 毕昇 JDK 还提供 kunpeng-optimized.jfc(如有),可进一步适配 ARM64。2. 自定义 .jfc 配置文件(推荐)复制并修改模板,关闭非必要事件:<!-- custom-low-overhead.jfc --><configuration ...> <event name="jdk.MethodSampling"> <setting name="enabled">false</setting> <!-- 关闭方法采样 --> </event> <event name="jdk.JavaMonitorEnter"> <setting name="enabled">true</setting> <setting name="threshold">10ms</setting> <!-- 仅记录 >10ms 的锁等待 --> </event> <event name="jdk.GCPhasePause"> <setting name="enabled">true</setting> </event></configuration>启动时指定:-XX:StartFlightRecording=settings=/path/to/custom-low-overhead.jfc,...3. 控制录制时长与文件大小避免无限制录制导致磁盘爆满:# 方式1:固定时长duration=10m# 方式2:循环录制(保留最近数据)maxsize=500MB,maxage=1h# 方式3:条件触发(JDK 17+ 支持)-XX:FlightRecorderOptions:repository=/tmp/jfr-cache生产建议:单文件 ≤ 1GB总录制时长 ≤ 30 分钟(除非明确需要长期趋势)4. 调整采样频率(降低 CPU 开销)关键参数:jdk.ThreadCPULoad:线程 CPU 采样间隔(默认 1s)jdk.ExecutionSample:方法栈采样间隔(默认 10ms)在自定义 .jfc 中调整:<event name="jdk.ExecutionSample"> <setting name="period">100ms</setting> <!-- 从 10ms 放宽到 100ms --></event> 注意:采样间隔越长,热点方法识别精度越低。三、减少 I/O 与内存开销1. 使用高速存储路径将 .jfr 文件写入 SSD 或内存盘:filename=/dev/shm/app.jfr # 写入 tmpfs(内存文件系统) 优势:避免磁盘 I/O 成为瓶颈 风险:重启丢失,需及时备份2. 启用压缩(JDK 17+)-XX:FlightRecorderOptions=compress=true可减少 30%~50% 文件体积。3. 避免多进程同时写同一目录每个 Java 进程应使用独立子目录,防止文件锁竞争。四、优化分析阶段的效率1. 使用命令行快速筛查(避免 GUI 开销)# 查看 GC 暂停总时间jfr print --events GCPhasePause app.jfr | grep "duration"# 统计最耗时的 10 个方法jfr summary app.jfr --category "Code" | head -n 102. 在分析机而非生产机运行 JMC将 .jfr 文件拷贝至开发机或专用分析服务器;避免在生产环境启动图形界面工具。3. 使用脚本自动化分析结合 jfr 命令 + Shell/Python 脚本,实现:自动提取关键指标生成性能报告触发告警(如 GC 暂停 > 100ms)示例脚本片段:MAX_PAUSE=$(jfr print --events GCPhasePause app.jfr | awk '/duration/ {print $2}' | sort -nr | head -1)if [ "$MAX_PAUSE" -gt 100000000 ]; then # 100ms in nanoseconds echo "ALERT: Max GC pause exceeds 100ms!"fi五、鲲鹏 ARM64 环境下的特别建议优先使用毕昇 JDK其 JFR 实现针对鲲鹏处理器的缓存、NUMA 架构优化,事件采集效率更高。关注 ARM64 特有事件如:jdk.CPULoad:ARM64 大小核调度可能影响 CPU 利用率jdk.NativeLibrary:验证 native 库是否为 ARM64 编译对比 x86 基线在相同负载下,分别录制 x86 与鲲鹏的 JFR 数据,使用 JMC 的 Compare 功能 定位架构差异点。 
  • [技术干货] linux文件系统和软硬连接-转载
    理解文件⽂件在磁盘⾥,磁盘是永久性存储介质,因此⽂件在磁盘上的存储是永久性的Linux 下⼀切皆⽂件(键盘、显⽰器、⽹卡、磁盘……)0KB 的空⽂件也是占⽤磁盘空间的⽂件是⽂件属性(元数据)和⽂件内容的集合(⽂件 = 属性(元数据)+ 内容)所有的⽂件操作本质是⽂件内容操作和⽂件属性操作对⽂件的操作本质是进程对⽂件的操作, 磁盘的管理者是操作系统⽂件的读写本质不是通过 C 语⾔ / C++ 的库函数来操作的(这些库函数只是为⽤⼾提供⽅便),⽽是通过⽂件相关的系统调⽤接⼝来实现的理解硬盘机械磁盘是计算机中唯⼀的⼀个机械设备磁盘是外设,特点:慢,容量⼤,价格便宜磁盘的物理结构:磁盘的存储结构:扇区是从磁盘读出和写⼊信息的最⼩单位,通常⼤⼩为 512 字节。磁头(head)数:每个盘⽚⼀般有上下两⾯,分别对应1个磁头,共2个磁头磁道(track)数:磁道是从盘⽚外圈往内圈编号0磁道,1磁道...,靠近主轴的同⼼圆⽤于停靠磁头,不存储数据柱⾯(cylinder)数:磁道构成柱⾯,数量上等同于磁道个数扇区(sector)数:每个磁道都被切分成很多扇形区域,每道的扇区数量相同圆盘(platter)数:就是盘⽚的数量磁盘容量=磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数细节:传动臂上的磁头是共进退的通过柱⾯(cylinder),磁头(head),扇区(sector),就可以定位数据了,这就是数据定位(寻址)⽅式之⼀,CHS寻址⽅式。磁盘的逻辑结构磁带逻辑结构在理解磁盘的逻辑结构之前我们先来了解一下磁带的逻辑结构: 磁带上面存取数据,当我们把磁带拉直就形成了线性结构 虽然磁盘本质上虽然是硬质的,但是逻辑上我们可以把磁盘想象成为卷在⼀起的磁带,那么磁盘的逻辑存储结构我们也可以类似于: 磁盘逻辑结构机械臂上的磁头是共进退的  柱⾯是⼀个逻辑上的概念,其实就是每⼀⾯上,相同半径的磁道逻辑上构成柱⾯。所以,磁盘物理上分了很多⾯,但是在我们看来,逻辑上,磁盘整体是由“柱⾯”卷起来的 这样看来一个柱面就是一个二维数组.整盘 故整盘就是多张二维的扇区数组表 “三维数组” .这样每⼀个扇区,就有了⼀个线性地址(其实就是数组下标),这种地址叫做 LBA.CHS && LBA地址CHS转成LBA:磁头数*每磁道扇区数 = 单个柱⾯的扇区总数LBA = 柱⾯号C*单个柱⾯的扇区总数 + 磁头号H*每磁道扇区数 + 扇区号S - 1即:LBA = 柱⾯号C*(磁头数*每磁道扇区数) + 磁头号H*每磁道扇区数 + 扇区号S - 1LBA转成CHS:柱⾯号C = LBA // (磁头数*每磁道扇区数)【就是单个柱⾯的扇区总数】磁头号H = (LBA % (磁头数*每磁道扇区数)) // 每磁道扇区数扇区号S = (LBA % 每磁道扇区数) + 1在磁盘使⽤者看来,根本就不关⼼CHS地址,⽽是直接使⽤LBA地址,磁盘内部⾃⼰转换。故磁盘是⼀个 元素为扇区 的⼀维数组,数组的下标就是每⼀个扇区的LBA地址。OS使⽤磁盘,就可以⽤⼀个数字访问磁盘扇区了。引入文件系统“块” 的概念操作系统读取硬盘数据的时候,其实是不会⼀个个扇区地读取,这样效率太低,⽽是⼀次性连续读取多个扇区,即⼀次性读取⼀个”块”(block)硬盘的每个分区是被划分为⼀个个的”块”。⼀个”块”的⼤⼩是由格式化的时候确定的,并且不可以更改,最常⻅的是4KB,即连续⼋个扇区组成⼀个 ”块”。(每个扇区512B)文件系统和存储管理中,“块(Block)” 是磁盘(或其他存储设备)进行数据读写的最小单位,也是文件系统管理存储空间的基础单元。“分区” 的概念其实磁盘是可以被分成多个分区(partition)的,以Windows观点来看,你可能会有⼀块磁盘并且将它分区成C,D,E盘。那个C,D,E就是分区。分区从实质上说就是对硬盘的⼀种格式化。但是Linux的设备都是以⽂件形式存在,那是怎么分区的呢?柱⾯是分区的最⼩单位,我们可以利⽤参考柱⾯号码的⽅式来进⾏分区,其本质就是设置每个区的起始柱⾯和结束柱⾯号码。 柱⾯⼤⼩⼀致,扇区个位⼀致,那么其实只要知道每个分区的起始和结束柱⾯号,知道每⼀个柱⾯多少个扇区,那么该分区多⼤,其实和解释LBA是多少也就清楚了. inode我们知道 ⽂件=内容+属性 ,我们使⽤ ls -l 的时候看到的除了看到⽂件名,还能看到⽂件元数据(属性)每⾏的7列分别代表:模式硬链接数⽂件所有者组⼤⼩最后修改时间⽂件名这个信息除了通过ls来读取,还有⼀个stat命令能够看到更多信息。 我们知道⽂件数据都储存在”块”中,那么很显然,我们还必须找到⼀个地⽅储存⽂件的元信息(属性信息),⽐如⽂件的创建者、⽂件的创建⽇期、⽂件的⼤⼩等等。这种储存⽂件元信息的区域就叫做inode,中⽂译名为”索引节点”。每⼀个⽂件都有对应的inode,⾥⾯包含了与该⽂件有关的⼀些信息。为了能解释清楚inode,我们需要是深⼊了解⼀下⽂件系统。注意:Linux下⽂件的存储是属性和内容分离存储的Linux下,保存⽂件属性的集合叫做inode,⼀个⽂件,⼀个inode,inode内有⼀个唯⼀的标识符,叫做inode号⽂件名属性并未纳⼊到inode数据结构内部inode的⼤⼩⼀般是128字节或者256任何⽂件的内容⼤⼩可以不同,但是属性⼤⼩⼀定是相同的ext2 ⽂件系统认识文件系统所有的准备⼯作都已经做完,是时候认识下⽂件系统了。我们想要在硬盘上储⽂件,必须先把硬盘格式化为某种格式的⽂件系统,才能存储⽂件。⽂件系统的⽬的就是组织和管理硬盘中的⽂件。在 Linux 系统中,最常⻅的是 ext2 系列的⽂件系统。其早期版本为 ext2,后来⼜发展出 ext3 和 ext4。ext3 和 ext4 虽然对 ext2 进⾏了增强,但是其核⼼设计并没有发⽣变化。ext2⽂件系统将整个分区划分成若⼲个同样⼤⼩的块组 (Block Group),如下图所⽰。只要能管理⼀个分区就能管理所有分区,也就能管理所有磁盘⽂件。上图中启动块(Boot Sector)的⼤⼩是确定的,为1KB,由PC标准规定,⽤来存储磁盘分区信息和启动信息,任何⽂件系统都不能修改启动块。启动块之后才是ext2⽂件系统的开始。Block GroupBlock Group(块组) 是文件系统管理磁盘空间的核心 “模块化单元”,每ext2⽂件系统会根据分区的⼤⼩划分为数个Block Group。⽽每个Block Group都有着相同的结构组成。每个Block Group自主管理资源,以提升效率和容错性。块组内部构成超级块(Super Block)存放⽂件系统本⾝的结构信息,描述整个分区的⽂件系统信息。记录的信息主要有:bolck 和 inode的总量,未使⽤的block和inode的数量,⼀个block和inode的⼤⼩,最近⼀次挂载的时间,最近⼀次写⼊数据的时间,最近⼀次检验磁盘的时间等其他⽂件系统的相关信息。Super Block的信息被破坏,可以说整个⽂件系统结构就被破坏了。GDT(Group Descriptor Table)块组描述符表,描述块组属性信息,整个分区分成多个块组就对应有多少个块组描述符。每个块组描述符存储⼀个块组 的描述信息,如在这个块组中从哪⾥开始是inode Table,从哪⾥开始是Data Blocks,空闲的inode和数据块还有多少个等等。块组描述符在每个块组的开头都有⼀份拷⻉。块位图(Block Bitmap)Block Bitmap以位图的形式记录着Data Block中哪个数据块已经被占⽤,哪个数据块没有被占⽤。inode位图(Inode Bitmap)每个bit表⽰⼀个inode是否空闲可⽤。(位值为0表示可用,为1不可用)i节点表(Inode Table)存放⽂件属性 如 ⽂件⼤⼩,所有者,最近修改时间等当前分组所有Inode属性的集合inode编号以分区为单位,整体划分,不可跨分区Data Block数据区:存放⽂件内容,也就是⼀个⼀个的Block。根据不同的⽂件类型有以下⼏种情况:对于普通⽂件,⽂件的数据存储在数据块中。对于⽬录,该⽬录下的所有⽂件名和⽬录名存储在所在⽬录的数据块中,除了⽂件名外,ls -l命令看到的其它信息保存在该⽂件的inode中。Block 号按照分区划分,不可跨分区,只能在自己分区内有效不可跨分区inode和datablock映射 12 个直接块指针(直接翻到章节):直接指向存储文件数据的 “普通数据块”(小文件可通过这些指针直接找到所有数据块,因此小文件存储非常高效)。间接块索引指针一级间接块索引指针(“先查目录,再翻章节”):该指针指向了一级数据块索引表(4KB),该索引表中储存了多个普通数据块的编号(每个编号4字节),则索引表中管理了1024个数据块。则可以在直接块的基础上多管理(1024 * 4KB = 4MB)4MB的文件。二级间接块索引指针(“查目录的目录,再翻章节”):该指针指向了数据块索引表,该数据块索引表又可以指向1024个数据块索引表,每个索引块可以管理1024个数据块。则可以在前面的基础上多管理(1024 * 1024 * 4KB = 4GB)4GB的文件。三级间接块索引指针(“查目录的目录的目录,再翻章节”):以此类推,则可以在前面的基础上多管理(1024 * 1024 * 1024 * 4KB = 4TB)4TB的文件。结论:分区之后的格式化操作,就是对分区进⾏分组,在每个分组中写⼊SB、GDT、Block、Bitmap、Inode Bitmap等管理信息,这些管理信息统称: ⽂件系统只要知道⽂件的inode号,就能在指定分区中确定是哪⼀个分组,进⽽在哪⼀个分组确定是哪⼀个inode拿到inode⽂件属性和内容就全部都有了[root@localhost linux]# touch abc[root@localhost linux]# ls -i abc263466 abc 如上图可知创建⼀个新⽂件主要有以下4个操作:1. 存储属性内核先找到⼀个空闲的i节点(这⾥是263466)。内核把⽂件信息记录到其中。2. 存储数据该⽂件需要存储在三个磁盘块,内核找到了三个空闲块:300 ,500,800。将内核缓冲区的第⼀块数据复制到300,下⼀块复制到500,以此类推。3. 记录分配情况⽂件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。4. 添加⽂件名到⽬录新的⽂件名abc。linux如何在当前的⽬录中记录这个⽂件?内核将⼊⼝(263466,abc)添加到⽬录⽂件。⽂件名和inode之间的对应关系将⽂件名和⽂件的内容及属性连接起来。目录与文件名我们发现平常访问⽂件,⽤的是⽂件名,并没⽤inode号,那它是怎么访问的呢?答:目录是文件,磁盘上是没有目录这一概念的,目录的内容保存的是当前目录下的文件名和inode的映射关系。所以,访问⽂件,必须打开当前⽬录,根据⽂件名,获得对应的inode号,然后进⾏⽂件访问。所以,访问⽂件必须要知道当前⼯作⽬录,本质是必须能打开当前⼯作⽬录⽂件,查看⽬录⽂件的内容!路径解析我们知道访问文件时需要访问目录文件,可是目录我们也只知道文件名,要访问当前目录也要知道他的inode。故需要一直向上访问上级目录,类似于“递归”,需要把路径中所有的⽬录全部解析,出⼝是"/"根⽬录(根⽬录固定⽂件名,inode号,⽆需查找,系统开机之后就必须知道)。实际上,任何⽂件,都有路径,访问⽬标⽂件,⽐如: /home/whb/code/test/test/test.c  都要从根⽬录开始,依次打开每⼀个⽬录,根据⽬录名,依次访问每个⽬录下指定的⽬录,直到访问到test.c。这个过程叫做Linux路径解析。路径谁提供?你访问⽂件,都是指令/⼯具访问,本质是进程访问,进程有CWD!进程提供路径。你open⽂件,提供了路径.软硬链接创建软硬连接观察,对比它们的inode: 硬链接abc和def的链接状态完全相同,他们被称为指向⽂件的硬链接。内核记录了这个连接数,inode:68511212 的硬连接数为2。硬连接是新文件名与和目标文件inode编号的映射关系;创建硬连接,就是建立映射关系,并且把inode的链接数增加;我们在删除⽂件时⼲了两件事情:在⽬录中将对应的记录删除,将硬连接数-1,如果为0,则将对应的磁盘释放。如上图我们可以发现当前目录下的 . 和下级目录下的 .. 都连接着该目录故他的连接数是3软连接那么软连接是什么?软连接是一个独立的文件,文件内容是目标文件的路径。有点类似于快捷方式.一个目录被创建时,会产生三个连接。其中两个“目录名”和"."指向自己,还有一个".."指向上一级目录。目录可以创建软连接,但是不能创建硬连接!否则会形成环路问题,除非是系统自己建立。————————————————版权声明:本文为CSDN博主「new null」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/2301_80468112/article/details/154292952
  • [技术干货] Linux iptables防火墙基础知识总结-转载
    一、基本框架iptables 是 Linux 系统中常用的防火墙工具,工作在用户空间,用来编写规则,基于内核的 netfilter 框架实现数据包的过滤、转换和修改。其核心框架由 “四表五链” 构成,规则的匹配和执行遵循特定顺序,同时支持自定义链以灵活管理规则。二、iptables 中的链“链” 是数据包流转过程中经过的检查点,每条链对应数据包处理的特定阶段,由内核自动创建,即内置链。对应内核中的每一个勾子函数 (INPUT,OUTPUT,FORWARD,PREROUTING,POSTROUTING)。除了内置链外,用户还可创建自定义链,用于对内置链进行扩展或补充,可实现更灵活的规则组织管理机制;只有钩子函数调用自定义链时,才会生效,即需通过内置链的规则引用自定义链。1、PREROUTING 链数据包进入本机后,路由选择之前经过的链。用于提前修改数据包(如 DNAT),或标记数据包。即主机接收到数据是否是给我们的,是否要修改(ip或port),相当于进站安检。2、INPUT 链当路由判断数据包的目标地址是本机时,经过此链。用于控制哪些外部数据包可进入本机(如允许 SSH 连接)。即接收到的数据经过路由分析,是本机处理,相当与上车。3、FORWARD 链当路由判断数据包需要经过本机转发(本机既非源也非目标)时,经过此链。用于控制转发行为(如网关服务器的转发规则)。即接收到的数据经过路由分析,不是本机处理,本机只做转发,相当于转车。4、OUTPUT 链本机产生的数据包(如应用程序发送的请求)在离开本机前经过此链。用于控制本机对外发送的数据包。即本机处理完的数据经过路由分析,直接传输出去,相当于下车。5、POSTROUTING 链数据包经过路由选择后,离开本机前的最后一条链。用于修改源地址(如 SNAT,将内网地址转换为外网地址)。即数据传输出去的时候,是否需要修改(ip或port),相当于出站安检。三、iptables 中的表iptables 的表用于分类管理不同功能的规则,每个表关联特定的链,主要包括以下四类:filter、nat、mangle、raw。优先级从高到低依次是:raw > mangle > nat > filter。1、filter 表(过滤规则表)最常用的表,也是默认表,根据预定义的规则过滤符合条件的数据包,是防火墙的核心功能。仅关联 3 条链:INPUT、OUTPUT、FORWARD。2、nat 表(地址转换表)用于实现网络地址转换(NAT),包括源地址转换(SNAT)、目标地址转换(DNAT)等。关联 3 条链:PREROUTING(路由前修改目标地址)、POSTROUTING(路由后修改源地址)、OUTPUT(本机产生的数据包的地址转换)。3、mangle 表(修改表)修改数据标记位规则表,修改数据报文(修改数据包的元数据,如 TTL、服务类型、标记等),可辅助路由或过滤。关联所有 5 条链(INPUT、OUTPUT、FORWARD、PREROUTING、POSTROUTING)。4、raw 表(原始表)用于关闭 nat 表启用的连接跟踪机制,减少性能消耗,加快封包穿越防火墙速度,仅处理不需要追踪的数据包。关联 2 条链:PREROUTING、OUTPUT。四、链表对应关系表    可支持的链raw    PREROUTING, OUTPUTmangle    PREROUTING, POSTROUTING, INPUT, OUTPUT, FORWARDnat    PREROUTING, POSTROUTING, INPUT, OUTPUTfilter    INPUT, FORWARD, OUTPUT五、数据包流转顺序当一个数据包进入网卡时,数据包首先进入PREROUTING链,内核根据数据包目的IP判断是否需要传送出去;如果数据包是进入本机的,则会进入INPUT链,然后交由本机的应用程序处理;如果数据包是要转发的,且内核允许转发,则数据包在PREROUTING链之后到达FORWARD链,再经由POSTROUTING链输出本机的应用程序往外发送数据包,会先进入OUTPUT链,然后到达POSTROUTING链输出数据包的走向数据流入,本机接收的数据包(目标是本机):PREROUTING 链(raw→mangle→nat)→ 路由判断(目标是本机)→ INPUT 链(mangle→filter)→ 进入本机应用程序。数据流出,本机发出的数据包(源是本机):本机应用程序 → OUTPUT 链(raw→mangle→nat→filter)→ 路由选择 → POSTROUTING 链(mangle→nat)→ 离开本机。数据转发,转发的数据包(经过本机转发):外部数据包 → PREROUTING 链(raw→mangle→nat)→ 路由判断(需要转发)→ FORWARD 链(mangle→filter)→ 路由选择 → POSTROUTING 链(mangle→nat)→ 离开本机。 六、规则匹配顺序iptables 中,每条链内的规则按从上到下的顺序依次匹配:当数据包匹配到某条规则时,会执行该规则的 “目标”(如 ACCEPT、DROP、跳转至其他链等),并停止后续规则的匹配(除非目标是 RETURN,会返回原链继续匹配)。若数据包不匹配链中任何规则,则执行该链的 “默认策略”。七、策略的设置方式1、iptables命令组成iptables 完整命令由以下部份组成:iptables [-t Table] -子命令 <链> <规则策略> [动作]即通过 iptables 命令,在某个表的某个链上设置某条过滤规则字段说明iptables:iptables命令Table:具体要操作的表,用 -t 指定,raw|mangle|nat|filter,默认 filterChain:具体要操作的链,PREROUTING|INPUT|FORWARD|OUTPUT|POSTROUTINGRule:具体规则,由匹配条件和目标组成,如果满足条件,就执行目标中的规则,目标用 -j 指定动作:基本动作ACCEPT|DROP|RETURN2、命令格式指定表-t|--table table   #指定表 raw|mangle|nat|filter,如果不显式指定,默认是filter操作链-N|--new-chain chain   #添加自定义新链-X|--delete-chain [chain]   #删除自定义链(要求链中没有规则)-P|--policy chain target   #设置默认策略,对filter表中的链而言,其默认策略有ACCEPT|DROP-E|--rename-chain old-chain new-chain   #重命名自定义链,引用计数不为0的自定义链不能被重命名-L|--list [chain]   #列出链上的所有规则-S|--list-rules [chain]   #列出链上的的有规则-F|--flush [chain]   #清空链上的所有规则,默认是所有链-Z|--zero [chain [rulenum]]   #置0,清空计数器,默认操作所有链上的所有规则操作具体规则-A|--append chain rule-specification   #往链上追加规则-I|--insert chain [rulenum] rule-specification   #往链上插入规则,可以指定编号,默认插入到最前面-C|--check chain rule-specification   #检查链上的规则是否正确-D|--delete chain rule-specification   #删除链上的规则-D|--delete chain rulenum   #根据编号删除链上的规则-R|--replace chain rulenum rule-specification   #根据链上的规则编号,使用新的规则替换原有规则其它选项-h|--help   #显示帮助-V|--version   #显示版本-v|--verbose   #显示详细信息-n|--numeric   #以数字形式显示IP和端口,默认显示主机名和协议名,否则容易遭受hosts解析影响--line-numbers   #显示每条规则编号-j|--jump   # 决定了数据包在满足特定条件后的命运。ACCEPT:允许数据包通过。DROP:丢弃数据包,不给出任何回应。等客户端测试多次后,主动放弃。REJECT:直接拒绝数据包,并向发送方发送一个错误响应。查看规则选项-L   #显示规则条目,后面可以接需要查看的链,不写的话表示所有链。如果规则不为空,单独 -L 选项显示时有可能会很慢,这是因为需要对主机名和服务名进行反解导致的,-n   #选项查看规则的时候 主机名和端口不做解析,介于此,可以实现规避上面的问题。-v   #显示详细信息--line-numbers   #显示规则的标号-S   #打印规则,编写命令给我们打印出来,方便我们去学习-t   #指定查看的表3、默认策略默认策略(Policy)是当数据包不匹配链中任何规则时的默认处理行为,仅适用于内置链(自定义链无默认策略)。设置命令iptables -P 链名 策略AI写代码bash常用策略:ACCEPT(允许通过)、DROP(直接丢弃,不返回任何信息)、REJECT(拒绝并返回错误信息)。示例# 设置 INPUT 链默认拒绝所有未匹配的数据包iptables -P INPUT DROPAI写代码bash注:默认规则(iptables -P)是 ACCEPT,不建议修改,容易出现 “自杀” 现象。4、常见策略 设置命令# 在第1行插入一条规则iptables -t 表名 -I 链名 策略 # 在原来规则后面去追加iptables -t 表名 -A 链名 策略AI写代码bash常用策略:ACCEPT(允许通过)、DROP(直接丢弃,不返回任何信息)、REJECT(拒绝并返回错误信息)。示例# 在INPUT 链的 filter 表上设置过滤规则,将来自 10.0.0.112 的数据包丢弃掉iptables -t filter -A INPUT -s 10.0.0.12 -j DROPAI写代码bash八、自定义链配置​1、创建自定义链创建命令iptables -N 自定义链名AI写代码bash示例# 创建一个名为NGINX_RULES的自定义链iptables -N NGINX_RULESAI写代码bash2、使用自定义链需通过内置链的规则引用自定义链(目标为自定义链名),数据包匹配到该规则时会跳转到自定义链处理。示例# 示例:在 INPUT 链中添加规则,将所有 TCP 80 端口的数据包跳转至 NGINX_RULES 链处理iptables -A INPUT -p tcp --dport 80 -j NGINX_RULESAI写代码bash注:自定义链中若规则匹配,按目标处理;若不匹配,会返回原链继续匹配后续规则。3、删除自定义链删除前需满足以下两个条件:自定义链中无任何规则(需先清空);无其他链的规则引用该自定义链(需先删除引用)。示例# 清空自定义链的规则iptables -F NGINX_RULES  # 删除引用该链的规则(如 INPUT 链中指向 NGINX_RULES 的规则)iptables -D INPUT -p tcp --dport 80 -j NGINX_RULES  # 删除自定义链iptables -X NGINX_RULES————————————————版权声明:本文为CSDN博主「siriuuus」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qq_38024995/article/details/154353503
总条数:1199 到第
上滑加载中