Kubernetes 中比较流行的日志收集解决方案是 ElasticsearchFluentd 和 Kibana(EFK)技术栈,也是官方现在比较推荐的一种方案。

后续我会更新使用Log-Pilot + ES + Kibana 日志方案

Elasticsearch 是一个实时的、分布式的可扩展的搜索引擎,允许进行全文、结构化搜索,它通常用于索引和搜索大量日志数据,也可用于搜索许多不同类型的文档。

Elasticsearch 通常与 Kibana 一起部署,Kibana 是 Elasticsearch 的一个功能强大的数据可视化 Dashboard,Kibana 允许你通过 web 界面来浏览 Elasticsearch 日志数据。

Fluentd是一个流行的开源数据收集器,我们将在 Kubernetes 集群节点上安装 Fluentd,通过获取容器日志文件、过滤和转换日志数据,然后将数据传递到 Elasticsearch 集群,在该集群中对其进行索引和存储。

我们先来配置启动一个可扩展的 Elasticsearch 集群,然后在 Kubernetes 集群中创建一个 Kibana 应用,最后通过 DaemonSet 来运行 Fluentd,以便它在每个 Kubernetes 工作节点上都可以运行一个 Pod。

官方文档:

https://github.com/kubernetes/kubernetes/tree/release-1.22/cluster/addons/fluentd-elasticsearch

准备

参考官方部署文档的基础上使用本项目manifests/efk/部署,以下为几点主要的修改:

  • 修改 fluentd-es-configmap.yaml 中的部分 journald 日志源(增加集群组件服务日志搜集)
  • 修改官方docker镜像,方便国内下载加速
  • 修改 es-statefulset.yaml 支持日志存储持久化等
  • 增加自动清理日志

创建 Elasticsearch 集群

apiVersion: v1
kind: Namespace
metadata:name: logging

1、 es-service.yaml

kind: Service
apiVersion: v1
metadata:name: elasticsearchnamespace: logginglabels:app: elasticsearch
spec:selector:app: elasticsearchclusterIP: Noneports:- port: 9200name: rest- port: 9300name: inter-node

定义了一个名为 elasticsearch 的 Service,指定标签 app=elasticsearch,当我们将 Elasticsearch StatefulSet 与此服务关联时,服务将返回带有标签 app=elasticsearch的 Elasticsearch Pods 的 DNS A 记录,然后设置 clusterIP=None,将该服务设置成无头服务。最后,我们分别定义端口9200、9300,分别用于与 REST API 交互,以及用于节点间通信。

[root@master01 efk]# kubectl apply -f es-service.yaml
service/elasticsearch-logging created
[root@master01 efk]# kubectl get svc -n kube-system
NAME                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
elasticsearch-logging   ClusterIP   None            <none>        9200/TCP                     15s

es-statefulset.yaml

apiVersion: apps/v1
kind: StatefulSet
metadata:name: esnamespace: logging
spec:serviceName: elasticsearchreplicas: 3selector:matchLabels:app: elasticsearchtemplate:metadata:labels: app: elasticsearchspec:nodeSelector:  #记得给节点把标签,不然会找到不符合的节点es: loginitContainers:- name: increase-vm-max-mapimage: busyboxcommand: ["sysctl", "-w", "vm.max_map_count=262144"]securityContext:privileged: true- name: increase-fd-ulimitimage: busyboxcommand: ["sh", "-c", "ulimit -n 65536"]securityContext:privileged: truecontainers:- name: elasticsearchimage: docker.elastic.co/elasticsearch/elasticsearch:7.6.2ports:- name: restcontainerPort: 9200- name: intercontainerPort: 9300resources:limits:cpu: 1000mrequests:cpu: 1000mvolumeMounts:- name: datamountPath: /usr/share/elasticsearch/dataenv:- name: cluster.namevalue: k8s-logs- name: node.namevalueFrom:fieldRef:fieldPath: metadata.name- name: cluster.initial_master_nodesvalue: "es-0,es-1,es-2"- name: discovery.zen.minimum_master_nodesvalue: "2"- name: discovery.seed_hostsvalue: "elasticsearch"- name: ES_JAVA_OPTSvalue: "-Xms512m -Xmx512m"- name: network.hostvalue: "0.0.0.0"volumeClaimTemplates:- metadata:name: datalabels:app: elasticsearchspec:accessModes: [ "ReadWriteOnce" ]storageClassName: nfsdataresources:requests:storage: 5Gi 

Pods 部署完成后,我们可以通过请求一个 REST API 来检查 Elasticsearch 集群是否正常运行。使用下面的命令将本地端口9200 转发到 Elasticsearch 节点(如es-0)对应的端口:

[root@master01 new]# kubectl port-forward es-0 9200:9200 --namespace=logging
Forwarding from 127.0.0.1:9200 -> 9200
Forwarding from [::1]:9200 -> 9200
curl http://localhost:9200/_cluster/state?pretty

看到上面的信息就表明我们名为 k8s-logs 的 Elasticsearch 集群成功创建了3个节点:es-0,es-1,和es-2,当前主节点是 es-0

创建 Kibana 服务

Elasticsearch 集群启动成功了,接下来我们可以来部署 Kibana 服务,新建一个名为 kibana.yaml 的文件,对应的文件内容如下:

kibana.yaml

apiVersion: v1
kind: Service
metadata:name: kibananamespace: logginglabels:app: kibana
spec:ports:- port: 5601type: NodePortselector:app: kibana---
apiVersion: apps/v1
kind: Deployment
metadata:name: kibananamespace: logginglabels:app: kibana
spec:selector:matchLabels:app: kibanatemplate:metadata:labels:app: kibanaspec:nodeSelector:es: logcontainers:- name: kibanaimage: docker.elastic.co/kibana/kibana:7.6.2resources:limits:cpu: 1000mrequests:cpu: 1000menv:- name: ELASTICSEARCH_HOSTSvalue: http://elasticsearch:9200ports:- containerPort: 5601

上面我们定义了两个资源对象,一个 Service 和 Deployment,为了测试方便,我们将 Service 设置为了 NodePort 类型,Kibana Pod 中配置都比较简单,唯一需要注意的是我们使用 ELASTICSEARCH_HOSTS 这个环境变量来设置Elasticsearch 集群的端点和端口,直接使用 Kubernetes DNS 即可,此端点对应服务名称为 elasticsearch,由于是一个 headless service,所以该域将解析为3个 Elasticsearch Pod 的 IP 地址列表。 配置完成后,直接使用 kubectl 工具创建:

kubectl create -f kibana.yaml
service/kibana created
deployment.apps/kibana created

创建完成后,可以查看 Kibana Pod 的运行状态

[root@master01 new]#  kubectl get pods --namespace=logging
NAME                      READY   STATUS    RESTARTS   AGE
es-0                      1/1     Running   0          13m
es-1                      1/1     Running   0          9m48s
es-2                      1/1     Running   0          7m46s
kibana-8476dc9bbf-6mm6k   1/1     Running   0          3m2s

如果 Pod 已经是 Running 状态了,证明应用已经部署成功了,然后可以通过 NodePort 来访问 Kibana 这个服务,在浏览器中打开http://<任意节点IP>:31838即可,

出现 这个代表成功

部署 Fluentd

Fluentd 是一个高效的日志聚合器,是用 Ruby 编写的,并且可以很好地扩展。对于大部分企业来说,Fluentd 足够高效并且消耗的资源相对较少,另外一个工具Fluent-bit更轻量级,占用资源更少,但是插件相对 Fluentd 来说不够丰富,所以整体来说,Fluentd 更加成熟,使用更加广泛,所以我们这里也同样使用 Fluentd 来作为日志收集工具。

工作原理

Fluentd 通过一组给定的数据源抓取日志数据,处理后(转换成结构化的数据格式)将它们转发给其他服务,比如 Elasticsearch、对象存储等等。Fluentd 支持超过300个日志存储和分析服务,所以在这方面是非常灵活的。主要运行步骤如下:

  • 首先 Fluentd 从多个日志源获取数据
  • 结构化并且标记这些数据
  • 然后根据匹配的标签将数据发送到多个目标服务去

配置

一般来说我们是通过一个配置文件来告诉 Fluentd 如何采集、处理数据的

日志源配置

比如我们这里为了收集 Kubernetes 节点上的所有容器日志,就需要做如下的日志源配置:

<source>@id fluentd-containers.log@type tail                             # Fluentd 内置的输入方式,其原理是不停地从源文件中获取新的日志。path /var/log/containers/*.log         # 挂载的服务器Docker容器日志地址pos_file /var/log/es-containers.log.postag raw.kubernetes.*                   # 设置日志标签read_from_head true<parse>                                # 多行格式化成JSON@type multi_format                   # 使用 multi-format-parser 解析器插件<pattern>format json                        # JSON 解析器time_key time                      # 指定事件时间的时间字段time_format %Y-%m-%dT%H:%M:%S.%NZ  # 时间格式</pattern><pattern>format /^(?<time>.+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/time_format %Y-%m-%dT%H:%M:%S.%N%:z</pattern></parse>
</source>

上面配置部分参数说明如下:

  • id:表示引用该日志源的唯一标识符,该标识可用于进一步过滤和路由结构化日志数据
  • type:Fluentd 内置的指令,tail 表示 Fluentd 从上次读取的位置通过 tail 不断获取数据,另外一个是 http 表示通过一个 GET 请求来收集数据。
  • path:tail 类型下的特定参数,告诉 Fluentd 采集 /var/log/containers 目录下的所有日志,这是 docker 在 Kubernetes 节点上用来存储运行容器 stdout 输出日志数据的目录。
  • pos_file:检查点,如果 Fluentd 程序重新启动了,它将使用此文件中的位置来恢复日志数据收集。
  • tag:用来将日志源与目标或者过滤器匹配的自定义字符串,Fluentd 匹配源/目标标签来路由日志数据。

路由配置

上面是日志源的配置,接下来看看如何将日志数据发送到 Elasticsearch:

<match **>@id elasticsearch@type elasticsearch@log_level infoinclude_tag_key truetype_name fluentdhost "#{ENV['OUTPUT_HOST']}"port "#{ENV['OUTPUT_PORT']}"logstash_format true<buffer>@type filepath /var/log/fluentd-buffers/kubernetes.system.bufferflush_mode intervalretry_type exponential_backoffflush_thread_count 2flush_interval 5sretry_foreverretry_max_interval 30chunk_limit_size "#{ENV['OUTPUT_BUFFER_CHUNK_LIMIT']}"queue_limit_length "#{ENV['OUTPUT_BUFFER_QUEUE_LIMIT']}"overflow_action block</buffer>
  • match:标识一个目标标签,后面是一个匹配日志源的正则表达式,我们这里想要捕获所有的日志并将它们发送给 Elasticsearch,所以需要配置成**
  • id:目标的一个唯一标识符。
  • type:支持的输出插件标识符,我们这里要输出到 Elasticsearch,所以配置成 elasticsearch,这是 Fluentd 的一个内置插件。
  • log_level:指定要捕获的日志级别,我们这里配置成 info,表示任何该级别或者该级别以上(INFO、WARNING、ERROR)的日志都将被路由到 Elsasticsearch。
  • host/port:定义 Elasticsearch 的地址,也可以配置认证信息,我们的 Elasticsearch 不需要认证,所以这里直接指定 host 和 port 即可。
  • logstash_format:Elasticsearch 服务对日志数据构建反向索引进行搜索,将 logstash_format 设置为 true,Fluentd 将会以 logstash 格式来转发结构化的日志数据。
  • Buffer: Fluentd 允许在目标不可用时进行缓存,比如,如果网络出现故障或者 Elasticsearch 不可用的时候。缓冲区配置也有助于降低磁盘的 IO。

过滤

由于 Kubernetes 集群中应用太多,也还有很多历史数据,所以我们可以只将某些应用的日志进行收集,比如我们只采集具有 logging=true 这个 Label 标签的 Pod 日志,这个时候就需要使用 filter,如下所示:

# 删除无用的属性
<filter kubernetes.**>@type record_transformerremove_keys $.docker.container_id,$.kubernetes.container_image_id,$.kubernetes.pod_id,$.kubernetes.namespace_id,$.kubernetes.master_url,$.kubernetes.labels.pod-template-hash
</filter>
# 只保留具有logging=true标签的Pod日志
<filter kubernetes.**>@id filter_log@type grep<regexp>key $.kubernetes.labels.loggingpattern ^true$</regexp>
</filter>

安装

要收集 Kubernetes 集群的日志,直接用 DasemonSet 控制器来部署 Fluentd 应用,这样,它就可以从 Kubernetes 节点上采集日志,确保在集群中的每个节点上始终运行一个 Fluentd 容器。当然可以直接使用 Helm 来进行一键安装,为了能够了解更多实现细节,我们这里还是采用手动方法来进行安装。

首先,我们通过 ConfigMap 对象来指定 Fluentd 配置文件,新建 fluentd-configmap.yaml 文件,文件内容如下:

kind: ConfigMap
apiVersion: v1
metadata:name: fluentd-confignamespace: logging
data:system.conf: |-<system>root_dir /tmp/fluentd-buffers/</system>containers.input.conf: |-<source>@id fluentd-containers.log@type tail                              # Fluentd 内置的输入方式,其原理是不停地从源文件中获取新的日志。path /var/log/containers/*.log          # 挂载的服务器Docker容器日志地址pos_file /var/log/es-containers.log.postag raw.kubernetes.*                    # 设置日志标签read_from_head true<parse>                                 # 多行格式化成JSON@type multi_format                    # 使用 multi-format-parser 解析器插件<pattern>format json                         # JSON解析器time_key time                       # 指定事件时间的时间字段time_format %Y-%m-%dT%H:%M:%S.%NZ   # 时间格式</pattern><pattern>format /^(?<time>.+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/time_format %Y-%m-%dT%H:%M:%S.%N%:z</pattern></parse></source># 在日志输出中检测异常,并将其作为一条日志转发 # https://github.com/GoogleCloudPlatform/fluent-plugin-detect-exceptions<match raw.kubernetes.**>           # 匹配tag为raw.kubernetes.**日志信息@id raw.kubernetes@type detect_exceptions           # 使用detect-exceptions插件处理异常栈信息remove_tag_prefix raw             # 移除 raw 前缀message log                       stream stream                     multiline_flush_interval 5max_bytes 500000max_lines 1000</match><filter **>  # 拼接日志@id filter_concat@type concat                # Fluentd Filter 插件,用于连接多个事件中分隔的多行日志。key messagemultiline_end_regexp /\n$/  # 以换行符“\n”拼接separator ""</filter> # 添加 Kubernetes metadata 数据<filter kubernetes.**>@id filter_kubernetes_metadata@type kubernetes_metadata</filter># 修复 ES 中的 JSON 字段# 插件地址:https://github.com/repeatedly/fluent-plugin-multi-format-parser<filter kubernetes.**>@id filter_parser@type parser                # multi-format-parser多格式解析器插件key_name log                # 在要解析的记录中指定字段名称。reserve_data true           # 在解析结果中保留原始键值对。remove_key_name_field true  # key_name 解析成功后删除字段。<parse>@type multi_format<pattern>format json</pattern><pattern>format none</pattern></parse></filter># 删除一些多余的属性<filter kubernetes.**>@type record_transformerremove_keys $.docker.container_id,$.kubernetes.container_image_id,$.kubernetes.pod_id,$.kubernetes.namespace_id,$.kubernetes.master_url,$.kubernetes.labels.pod-template-hash</filter># 只保留具有logging=true标签的Pod日志<filter kubernetes.**>@id filter_log@type grep<regexp>key $.kubernetes.labels.loggingpattern ^true$</regexp></filter>###### 监听配置,一般用于日志聚合用 ######forward.input.conf: |-# 监听通过TCP发送的消息<source>@id forward@type forward</source>output.conf: |-<match **>@id elasticsearch@type elasticsearch@log_level infoinclude_tag_key truehost elasticsearchport 9200logstash_format truelogstash_prefix k8s  # 设置 index 前缀为 k8srequest_timeout    30s<buffer>@type filepath /var/log/fluentd-buffers/kubernetes.system.bufferflush_mode intervalretry_type exponential_backoffflush_thread_count 2flush_interval 5sretry_foreverretry_max_interval 30chunk_limit_size 2Mqueue_limit_length 8overflow_action block</buffer></match>

上面配置文件中我们只配置了 docker 容器日志目录,收集到数据经过处理后发送到 elasticsearch:9200 服务。

然后新建一个 fluentd-daemonset.yaml 的文件,文件内容如下:

apiVersion: v1
kind: ServiceAccount
metadata:name: fluentd-esnamespace: logginglabels:k8s-app: fluentd-eskubernetes.io/cluster-service: "true"addonmanager.kubernetes.io/mode: Reconcile
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: fluentd-eslabels:k8s-app: fluentd-eskubernetes.io/cluster-service: "true"addonmanager.kubernetes.io/mode: Reconcile
rules:
- apiGroups:- ""resources:- "namespaces"- "pods"verbs:- "get"- "watch"- "list"
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: fluentd-eslabels:k8s-app: fluentd-eskubernetes.io/cluster-service: "true"addonmanager.kubernetes.io/mode: Reconcile
subjects:
- kind: ServiceAccountname: fluentd-esnamespace: loggingapiGroup: ""
roleRef:kind: ClusterRolename: fluentd-esapiGroup: ""
---
apiVersion: apps/v1
kind: DaemonSet
metadata:name: fluentd-esnamespace: logginglabels:k8s-app: fluentd-eskubernetes.io/cluster-service: "true"addonmanager.kubernetes.io/mode: Reconcile
spec:selector:matchLabels:k8s-app: fluentd-estemplate:metadata:labels:k8s-app: fluentd-eskubernetes.io/cluster-service: "true"# 此注释确保如果节点被驱逐,fluentd不会被驱逐,支持关键的基于 pod 注释的优先级方案。annotations:scheduler.alpha.kubernetes.io/critical-pod: ''spec:serviceAccountName: fluentd-escontainers:- name: fluentd-esimage: quay.io/fluentd_elasticsearch/fluentd:v3.0.1env:- name: FLUENTD_ARGSvalue: --no-supervisor -qresources:limits:memory: 500Mirequests:cpu: 100mmemory: 200MivolumeMounts:- name: varlogmountPath: /var/log- name: varlibdockercontainersmountPath: /var/lib/docker/containersreadOnly: true- name: config-volumemountPath: /etc/fluent/config.dnodeSelector:beta.kubernetes.io/fluentd-ds-ready: "true"tolerations:- operator: ExiststerminationGracePeriodSeconds: 30volumes:- name: varloghostPath:path: /var/log- name: varlibdockercontainershostPath:path: /var/lib/docker/containers- name: config-volumeconfigMap:name: fluentd-config

我们将上面创建的 fluentd-config 这个 ConfigMap 对象通过 volumes 挂载到了 Fluentd 容器中,另外为了能够灵活控制哪些节点的日志可以被收集,所以这里还添加了一个 nodSelector 属性:

nodeSelector:beta.kubernetes.io/fluentd-ds-ready: "true"

意思就是要想采集节点的日志,那么我们就需要给节点打上上面的标签

如果你需要在其他节点上采集日志,则需要给对应节点打上标签,使用如下命令:

kubectl label nodes node名 beta.kubernetes.io/fluentd-ds-ready=true

[root@master01 ~]# kubectl label nodes 172.17.1.32 beta.kubernetes.io/fluentd-ds-ready=true
node/172.17.1.32 labeled
[root@master01 ~]# kubectl label nodes 172.17.1.30 beta.kubernetes.io/fluentd-ds-ready=true
node/172.17.1.30 labeled
[root@master01 ~]# kubectl label nodes 172.17.1.31 beta.kubernetes.io/fluentd-ds-ready=true
node/172.17.1.31 labeled

默认情况下 master 节点有污点,所以如果要想也收集 master 节点的日志,则需要添加上容忍:

tolerations:
- operator: Exists

创建完成后,查看对应的 Pods 列表,检查是否部署成功

Fluentd 启动成功后,这个时候就可以发送日志到 ES 了,但是我们这里是过滤了只采集具有 logging=true 标签的 Pod 日志,所以现在还没有任何数据会被采集。

下面我们部署一个简单的测试应用, 新建 counter.yaml 文件,文件内容如下:

apiVersion: v1
kind: Pod
metadata:name: counterlabels:logging: "true"  # 一定要具有该标签才会被采集
spec:containers:- name: countimage: busyboxargs: [/bin/sh, -c,'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done']

该 Pod 只是简单将日志信息打印到 stdout,所以正常来说 Fluentd 会收集到这个日志数据,在 Kibana 中也就可以找到对应的日志数据了,使用 kubectl 工具创建该 Pod:

Pod 创建并运行后,回到 Kibana Dashboard 页面,点击左侧最下面的 management 图标,然后点击 Kibana 下面的 Index Patterns 开始导入索引数据:

更详细细节请看阴阳博客

在 Kubernetes 上搭建 EFK 日志收集系统[更新]-阳明的博客|Kubernetes|Istio|Prometheus|Python|Golang|云原生

Kubernetes安装EFK日志收集相关推荐

  1. Kubernetes EFK 日志收集

    Kubernetes EFK 日志收集 日志收集架构 Kubernetes集群本身不提供收集日志的解决方案,目前基于ELK日志收集的方案主要有三种 在节点运行一个agent收集日志 在Pod中包含一个 ...

  2. rancher实现EFK日志收集

    在分布式系统中,由于节点服务会部署多台,一旦出现线上问题需要通过日志分析定位问题就需要登录服务器一台一台进行日志检索,非常不便利,这时候就需要用到EFK日志收集工具. 在应用服务端部署Filebeat ...

  3. elasticsearch docker无法挂载_使用Docker安装Graylog日志收集系统

    1.创建并进入到用于存放Graylog配置文件的目录 mkdir -p /data/graylog/config && cd /data/graylog/config 2.下载Gray ...

  4. 日志收集系统EFK安装及配置

    目的 服务器运维系统日志监控是非常重要的工作.,目前常见的有EFK日志收集系统,实际上是3个系统组成,elasticsearch +  filebeat+kibana.这三个软件在 elastic.c ...

  5. Kubernetes 基于 EFK 技术栈的日志收集实践

    之前写过一篇文章介绍了容器环境下日志管理的原理机制:从 Docker 到 Kubernetes 日志管理机制详解,文章内容偏理论,本文在该理论的支撑下具体实践 Kubernetes 下基于 EFK 技 ...

  6. 性能优越的轻量级日志收集工具,微软、亚马逊都在用!

    ELK日志收集系统大家都知道,但是还有一种日志收集系统EFK,肯定有很多朋友不知道!这里的F指的是Fluentd,它具有Logstash类似的日志收集功能,但是内存占用连Logstash的十分之一都不 ...

  7. ELK分布式日志收集搭建和使用

    大型系统分布式日志采集系统ELK 全框架 SpringBootSecurity 1.传统系统日志收集的问题 2.Logstash操作工作原理 3.分布式日志收集ELK原理 4.Elasticsearc ...

  8. #yyds干货盘点# 如何在 Kubernete 中做日志收集与管理(14)

    说到日志,你应该不陌生.日志中不仅记录了代码运行的实时轨迹,往往还包含着一些关键的数据.错误信息,等等.日志方便我们进行分析统计及监控告警,尤其是在后期问题排查的时候,我们通过日志可以很方便地定位问题 ...

  9. 在Kubernetes上搭建新版fluentd-elasticsearch_1.22日志收集工具

    背景介绍 第一,对于企业来说,日志的重要性不言而喻,就不赘述了. 第二,日志收集分析展示平台的选择,这里给出几点选择ELK的理由.ELK是一套非常成熟的系统,她本身的构架非常适合Kubernetes集 ...

最新文章

  1. python装饰器类-Python 装饰器、类装饰器、属性装饰器
  2. mysql-5.7.21-winx64.zip 下载安装
  3. VS 之 InstallShield Limited Edition for Visual Studio 2015 图文教程
  4. mybatis 自动填充无效_开发小知识-mybatis-plus自动填充与读写分离
  5. UserData的诡异bug
  6. 简单触发器的使用 -- 献给SQL初学者
  7. getlibs_解决32位ubun12.04-linux_mint13中lotus-notes-8.5.3界面不正常问题
  8. WCF入门(七)——异常处理1
  9. matlab的梯形公式推导公式,复化梯形公式,辛普森公式的matlab程序
  10. Android拍照与相册选取图片
  11. 中国省份/城市OSM地图数据
  12. 安装MATLAB2016a的完整步骤
  13. 如何记账,教你在同一天记录多笔收支情况
  14. 沟通CTBS物流行业远程接入解决方案
  15. linux系统如何安装bt5,BT5硬盘安装(多系统linux + win + BT5)
  16. c语言哈夫曼运行时错误,游戏运行错误 提示Unhandled exception caught 怎么解决 救命呀!...
  17. Qt导航栏 QListWidget
  18. html怎么导入桌面上的图片,html怎么导入图片
  19. java水泡_FrozenBubble java实现的泡泡龙游戏,完整 逻辑,关卡功能,分数记录。 Games 240万源代码下载- www.pudn.com...
  20. 新一配:程序员在地铁上写代码被路人吐槽:有什么好装的!网友评论炸锅了

热门文章

  1. android 图片压缩总结1
  2. 2022年10月 基于WPF的智能制造MES系统框架-菜单栏的设计
  3. 干货:构建C/C++良好的工程结构
  4. 【报告分享】代餐行业营销洞察报告-丁香医生(附下载)
  5. 2023华为od面经~ (笔试 HR资面 技术面 一面 二面 综面)
  6. 子平格局——从旺格/从强格
  7. pytorch如何freeze模型参数
  8. 纯CSS实现多行文本溢出显示省略号(兼容不同浏览器)
  9. 基础的数组/链表实现的队列
  10. python数据分析的交叉分析和分组分析 -第三次笔记